From 80b6cbfc9c861469146318d0b3dd5f8b8b525b8a Mon Sep 17 00:00:00 2001
From: xiejun <xiejun@vci-tech.com>
Date: 星期五, 01 十一月 2024 15:11:19 +0800
Subject: [PATCH] Revert "集成获取mdm分发通用数据格式接口集成"
---
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Version.java | 202
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/config/BladeErrorMvcAutoConfiguration.java | 64
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/BladeRequestMappingHandlerMapping.java | 104
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/error/BladeRestExceptionTranslator.java | 83
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/annotation/VersionMapping.java | 107
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisCacheManagerConfig.java | 43
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/props/BladePropertySourcePostProcessor.java | 179
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Base64Util.java | 115
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncryptDes.java | 28
Source/BladeX-Tool/blade-starter-api-crypto/pom.xml | 34
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/sub/api.js.btl | 50
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/config/ApiCryptoConfiguration.java | 30
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/XssProperties.java | 44
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/RestTemplateConfiguration.java | 191
Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/annotation/DataAuth.java | 61
Source/BladeX-Tool/blade-starter-social/src/main/java/org/springblade/core/social/cache/AuthStateRedisCache.java | 69
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/BeanUtil.java | 424
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantId.java | 32
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/BladeBootAutoConfiguration.java | 37
Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/utils/JsonNodeInfo.java | 85
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/ClientSecure.java | 41
Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/OsChina.java | 22
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/AuthSecure.java | 47
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/crud/crud.vue.btl | 347
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/tree/api.js.btl | 60
Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/EnableSwagger.java | 33
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/XssHttpServletRequestWrapper.java | 175
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/BladeLockAutoConfiguration.java | 152
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/CharPool.java | 74
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/crud/Modal.vue.btl | 90
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/tree/index.vue.btl | 124
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/BladeWebMvcConfiguration.java | 62
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/bean/DecryptHttpInputMessage.java | 20
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/RequestProperties.java | 44
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/BladeLockProperties.java | 105
Source/BladeX-Tool/blade-core-db/src/main/java/org/springblade/core/db/config/DbConfiguration.java | 31
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/FlowConstant.java | 31
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/enums/CryptoType.java | 25
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/BladeSqlMethod.java | 47
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/RestTemplateHeaderInterceptor.java | 48
Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/model/SmsCode.java | 57
Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/HttpRequestProxyTest.java | 33
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/crud/const.js.btl | 54
Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/LogLevel.java | 88
Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/model/DataScopeModel.java | 68
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/BasicSecure.java | 51
Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/listener/ImportListener.java | 72
Source/BladeX-Tool/blade-starter-social/src/main/java/org/springblade/core/social/config/SocialConfiguration.java | 57
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/RedisKeySerializer.java | 75
Source/BladeX-Tool/blade-core-test/src/main/java/org/springblade/core/test/BladeSpringExtension.java | 88
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/intercept/QueryInterceptor.java | 56
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/registry/SecureRegistry.java | 196
Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/Exchange.java | 209
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantHolder.java | 49
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/spel/BladeExpressionEvaluator.java | 111
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceGlobalInterceptor.java | 54
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/props/BladeUploadProperties.java | 43
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecryptRsa.java | 18
Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/Slf4jLogger.java | 36
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/controller.java.btl | 222
Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/MultiSetMap.java | 132
Source/BladeX-Tool/blade-starter-mybatis/pom.xml | 57
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/api/IResultCode.java | 42
Source/BladeX-Tool/blade-starter-oss/pom.xml | 59
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/FileUtil.java | 383
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/exception/KeyNotConfiguredException.java | 14
Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/props/ReportDatabaseProperties.java | 33
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/config/BladeConverterConfiguration.java | 23
Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/exception/DataScopeException.java | 36
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/ImagePosition.java | 150
Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/props/BladeContextProperties.java | 81
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/sub/const.js.btl | 31
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/BladeFallbackFactory.java | 45
Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/props/BladeLoadBalancerProperties.java | 52
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/sub/sub.vue.btl | 358
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/methods/Replace.java | 36
Source/BladeX-Tool/blade-starter-metrics/pom.xml | 42
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/api/R.java | 225
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantInterceptor.java | 419
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/tree/const.js.btl | 63
Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/BladeUser.java | 96
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/StringUtil.java | 1577 +
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/publisher/UsualLogPublisher.java | 56
Source/BladeX-Tool/blade-starter-mongo/pom.xml | 37
Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/converter/JsonNodeToDocumentConverter.java | 25
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DesUtil.java | 208
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/entity.java.btl | 61
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/CopyProperty.java | 26
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/data.data.ts.btl | 102
Source/BladeX-Tool/blade-starter-prometheus/pom.xml | 57
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiDecryptParamResolver.java | 67
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceHolder.java | 118
Source/BladeX-Tool/blade-starter-transaction/src/main/java/org/springblade/core/transaction/annotation/SeataCloudApplication.java | 40
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/utils/ElkPropsUtil.java | 40
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/PathUtil.java | 64
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/ErrorLogEvent.java | 35
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/Query.java | 58
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/ClientInterceptor.java | 61
Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/constant/JwtConstant.java | 36
Source/BladeX-Tool/blade-starter-report/pom.xml | 35
Source/BladeX-Tool/blade-core-auto/README.md | 12
Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/constant/LoadBalancerConstant.java | 31
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/service/LauncherService.java | 60
Source/BladeX-Tool/LICENSE | 34
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/FastStringWriter.java | 255
Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/AliSmsTemplate.java | 110
Source/BladeX-Tool/blade-starter-mybatis/src/main/resources/blade-mybatis.yml | 35
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiDecryptRequestBodyAdvice.java | 87
Source/BladeX-Tool/blade-starter-transaction/src/main/resources/file.conf | 3
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/auth/AuthFun.java | 151
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ReflectUtil.java | 180
Source/BladeX-Tool/blade-core-log4j2/src/main/java/org/springblade/core/log4j2/LogLauncherServiceImpl.java | 45
Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/AsyncCall.java | 76
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/config/RegistryConfiguration.java | 62
Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeCallableWrapper.java | 65
Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/endpoint/ServiceEndpoint.java | 135
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/PlaceholderUtil.java | 152
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/resolver/PageArgumentResolver.java | 75
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/LbRestTemplate.java | 27
Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/handler/BladeDataScopeHandler.java | 84
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/BladeHttpServletRequestWrapper.java | 120
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/utils/INetUtil.java | 244
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanMapKey.java | 16
Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/constant/SmsConstant.java | 31
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceAnnotationInterceptor.java | 50
Source/BladeX-Tool/blade-starter-ehcache/src/main/java/org/springblade/core/ehcache/EhcacheConfiguration.java | 30
Source/BladeX-Tool/pom.xml | 216
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ResourceUtil.java | 71
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/AliossConfiguration.java | 78
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/constant/SecureConstant.java | 71
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/rule/BladeOssRule.java | 50
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/constant/PermissionConstant.java | 62
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/IClientDetails.java | 56
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/TreeNode.java | 61
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/TableExclude.java | 34
Source/BladeX-Tool/blade-bom/pom.xml | 626
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/client/BladeCloudApplication.java | 40
Source/BladeX-Tool/blade-core-db/src/main/resources/blade-db.yml | 39
Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_appenders.xml | 33
Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/Method.java | 31
Source/BladeX-Tool/blade-starter-transaction/pom.xml | 46
Source/BladeX-Tool/blade-starter-social/src/main/java/org/springblade/core/social/utils/SocialUtil.java | 168
Source/BladeX-Tool/blade-starter-flowable/src/main/resources/processes/LeaveProcess.bpmn20.xml | 123
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/list.js.vm | 84
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RandomType.java | 45
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ConvertUtil.java | 81
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/convert/EnumToStringConverter.java | 126
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/config/ApiCryptoProperties.java | 46
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/view.js.vm | 77
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/crud/api.js.btl | 50
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecrypt.java | 32
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiEncryptResponseBodyAdvice.java | 64
Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/HttpResponse.java | 199
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/mapper.java.btl | 52
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/CollectionUtil.java | 177
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/LocalFile.java | 157
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DatatypeConverterUtil.java | 57
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/IClientDetailsService.java | 34
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/service/BladeService.java | 88
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/handler/SecureHandlerHandler.java | 59
Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/service/RegistrationService.java | 112
Source/BladeX-Tool/blade-core-db/pom.xml | 81
Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/provider/ReportPlaceholderProvider.java | 40
Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/HttpRequest.java | 492
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/resolver/TokenArgumentResolver.java | 64
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/feign.java.btl | 49
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/logger/OkHttpSlf4jLogger.java | 33
Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/AbstractBladeProcessor.java | 81
Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeHttpHeadersGetter.java | 50
Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoRunListener.java | 35
Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeServletContext.java | 80
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisTemplateConfiguration.java | 83
Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/handler/DataScopeHandler.java | 40
Source/BladeX-Tool/blade-starter-datascope/pom.xml | 33
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/BladeRedis.java | 824
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/annotation/UrlVersion.java | 38
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/RequestConfiguration.java | 57
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/BaseNode.java | 75
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/constant/TenantBaseConstant.java | 76
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/TokenConstant.java | 51
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/exception/EncryptBodyFailException.java | 17
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/annotation/PreAuth.java | 42
Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/factories/FactoriesFiles.java | 80
Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/AliSmsConfiguration.java | 54
Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/entity/ReportFileEntity.java | 66
Source/BladeX-Tool/blade-starter-develop/src/main/resources/beetl.properties | 10
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/BladeSpringMvcContract.java | 101
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/ApiLogListener.java | 59
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/serviceImpl.java.btl | 52
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncryptAes.java | 28
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/SqlKeyword.java | 130
Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/config/BladeContextAutoConfiguration.java | 53
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/logger/HttpLoggingInterceptor.java | 257
Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/HttpRequestDemo.java | 130
Source/BladeX-Tool/doc/mvn/mvn命令.md | 1
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/VersionMappingAutoConfiguration.java | 38
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/props/BladeFileProperties.java | 76
Source/BladeX-Tool/blade-starter-log/src/main/resources/log/logback-test.xml | 151
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/AntPathFilter.java | 51
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/JsonUtil.java | 705
Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/JwtUtil.java | 229
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/TenantIgnore.java | 30
Source/BladeX-Tool/blade-core-boot/src/main/resources/blade-boot.yml | 35
Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/props/SmsProperties.java | 66
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ProtostuffUtil.java | 99
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/model/LogApi.java | 50
Source/BladeX-Tool/blade-starter-metrics/src/main/java/org/springblade/core/metrics/druid/DruidDataSourcePoolMetadata.java | 58
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/config/TenantConfiguration.java | 88
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/AbstractReadWriteJackson2HttpMessageConverter.java | 127
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/RedisLock.java | 82
Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_test.xml | 30
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/tree/data.data.ts.btl | 102
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/publisher/ApiLogPublisher.java | 54
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/ErrorLogListener.java | 57
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/props/OssProperties.java | 82
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedConsumer.java | 39
Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/service/AutoServiceProcessor.java | 254
Source/BladeX-Tool/blade-starter-develop/src/main/java/org/springblade/develop/constant/DevelopConstant.java | 64
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/convert/BladeConversionService.java | 50
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/action.js.vm | 37
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/BladeFeignFallback.java | 102
Source/BladeX-Tool/blade-core-launch/pom.xml | 46
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/SuffixFileFilter.java | 63
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSource.java | 54
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/HttpMethod.java | 42
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/model.js.vm | 88
Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/ResponseSpec.java | 279
Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/rule/GrayscaleEnvPostProcessor.java | 50
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/plugins/QueryInterceptorExecutor.java | 52
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/constant/AuthConstant.java | 81
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedFunction.java | 40
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Holder.java | 39
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/yml/YmlPropertyLoaderFactory.java | 73
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/TokenInterceptor.java | 50
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/ClientDetailsServiceImpl.java | 43
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/wrapper.java.btl | 61
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/Modal.vue.btl | 90
Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/ChangeItem.java | 33
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/config/BladeLogToolAutoConfiguration.java | 97
Source/BladeX-Tool/blade-starter-cache/src/main/java/org/springblade/core/cache/utils/CacheUtil.java | 331
Source/BladeX-Tool/blade-core-boot/pom.xml | 60
Source/BladeX-Tool/blade-starter-log/src/main/resources/blade-log.yml | 3
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/SpringUtil.java | 115
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/BytesWrapper.java | 52
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/props/BladePropertySource.java | 56
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/logger/BladeLogger.java | 56
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/config/BladeLaunchConfiguration.java | 34
Source/BladeX-Tool/blade-core-db/src/main/java/org/springblade/core/db/package-info.java | 6
Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/TencentSmsConfiguration.java | 51
Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/service/ServicesFiles.java | 77
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/BladeRequestFilter.java | 75
Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_ontest.xml | 31
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/sub/crud.vue.btl | 371
Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/config/JwtConfiguration.java | 60
Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_dev.xml | 32
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/ssl/DisableValidationTrustManager.java | 46
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/BladeRedisCacheAutoConfiguration.java | 114
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/AliossTemplate.java | 314
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/config/MybatisPlusConfiguration.java | 134
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanCopier.java | 405
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/SentinelConstant.java | 30
Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/BootAutoType.java | 68
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeSentinelAutoConfiguration.java | 62
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/BladeHttpConfiguration.java | 30
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/aspect/ApiLogAspect.java | 53
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/AuthInterceptor.java | 109
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ImageUtil.java | 489
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/base/BaseServiceImpl.java | 154
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/CoreMain.java | 30
Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/endpoint/ReportBootEndpoint.java | 39
Source/BladeX-Tool/blade-starter-loadbalancer/pom.xml | 77
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/BeanDiff.java | 31
Source/BladeX-Tool/blade-starter-redis/src/main/resources/META-INF/scripts/blade_rate_limiter.lua | 28
Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeContext.java | 73
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/config/MessageConfiguration.java | 73
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RuntimeUtil.java | 77
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/constant/RoleConstant.java | 42
Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/HttpCacheAble.java | 54
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/TenantDS.java | 34
Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/AuthInfo.java | 47
Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/CssQuery.java | 78
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/tuple/KeyPair.java | 62
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Func.java | 2156 ++
Source/BladeX-Tool/blade-starter-flowable/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java | 1647 +
Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/support/ExcelException.java | 30
Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/Agent.java | 35
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ThreadUtil.java | 55
Source/BladeX-Tool/blade-starter-trace/pom.xml | 31
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/annotation/ApiVersion.java | 39
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/handler/IPermissionHandler.java | 41
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/tree/api.js.btl | 60
Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/datasource/ReportDataSource.java | 55
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/SignInterceptor.java | 170
Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerUtil.java | 74
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/launcher/TenantLauncherServiceImpl.java | 45
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/OssConfiguration.java | 46
Source/BladeX-Tool/blade-starter-redis/src/main/resources/additional-spring-configuration-metadata.json | 11
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/publisher/ErrorLogPublisher.java | 63
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/feign/ILogClient.java | 68
Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerWebConfiguration.java | 42
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisAutoCacheManager.java | 64
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/add.js.vm | 75
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/exception/EncryptMethodNotFoundException.java | 14
Source/BladeX-Tool/blade-starter-excel/pom.xml | 29
Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/AsyncCallback.java | 56
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Unchecked.java | 107
Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/config/PrometheusConfiguration.java | 52
Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/serializer/JwtRedisKeySerializer.java | 75
Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerLauncherServiceImpl.java | 48
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/HuaweiObsTemplate.java | 225
Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerProperties.java | 177
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeJavaTimeModule.java | 35
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/handler/ISecureHandler.java | 72
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/MinioConfiguration.java | 66
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RateLimiterClient.java | 91
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/ForestNode.java | 45
Source/BladeX-Tool/blade-starter-transaction/src/main/java/org/springblade/core/transaction/config/TransactionConfiguration.java | 34
Source/BladeX-Tool/blade-starter-http/pom.xml | 41
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/handler/BladePermissionHandler.java | 110
Source/BladeX-Tool/blade-starter-prometheus/src/main/resources/blade-prometheus.yml | 8
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/index.vue.btl | 143
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/constant/BladeConstant.java | 146
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/IMultiOutputStream.java | 36
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/tree/crud.vue.btl | 360
Source/BladeX-Tool/blade-core-test/pom.xml | 30
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/config/TenantDataSourceConfiguration.java | 179
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/entityVO.java.btl | 87
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/listener/LoggerStartupListener.java | 88
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/ctrl/BladeController.java | 279
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/crud/index.vue.btl | 124
Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/mapper/ReportFileMapper.java | 28
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BeanProperty.java | 16
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ObjectUtil.java | 37
Source/BladeX-Tool/blade-starter-jwt/pom.xml | 40
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/BladeWebMvcRegistrations.java | 44
Source/BladeX-Tool/blade-starter-log/src/main/resources/log/logback-prod.xml | 151
Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/ServletHttpHeadersGetter.java | 75
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RateLimiterException.java | 43
Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/util/ExcelUtil.java | 186
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/ZookeeperConstant.java | 45
Source/BladeX-Tool/blade-starter-actuate/README.md | 34
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/crud/data.data.ts.btl | 102
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanCopierKey.java | 20
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/BinderSupplier.java | 32
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/ApiLogEvent.java | 35
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/model/BladeFile.java | 48
Source/BladeX-Tool/blade-starter-metrics/src/main/java/org/springblade/core/metrics/druid/DruidDataSourceMetadataProviderConfiguration.java | 46
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/server/ServerInfo.java | 52
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/crypto/ApiCryptoAes.java | 22
Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/converter/DBObjectToJsonNodeConverter.java | 30
Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/handler/BladeScopeModelHandler.java | 118
Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/CssQueryMethodInterceptor.java | 176
Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerHandlerConfiguration.java | 71
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/tree/const.js.btl | 31
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/BladeSqlInjector.java | 48
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/Condition.java | 99
Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/config/DataScopeConfiguration.java | 65
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/mapper/BladeMapper.java | 53
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/utils/SecureUtil.java | 227
Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/ServiceHealth.java | 81
Source/BladeX-Tool/blade-core-test/src/main/java/org/springblade/core/test/BladeBootTest.java | 60
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/crud/crud.vue.btl | 188
Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/config/MongoConfiguration.java | 28
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/BladeFeignRequestInterceptor.java | 48
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/NodeTest.java | 33
Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/YunpianSmsTemplate.java | 91
Source/BladeX-Tool/blade-starter-actuate/pom.xml | 30
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RedisRateLimiterClient.java | 83
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/base/BaseService.java | 49
Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/listener/DataListener.java | 51
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/crud/api.js.btl | 50
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/NonDS.java | 30
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/BladeRetryConfiguration.java | 49
Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/config/BladeServletListenerConfiguration.java | 42
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/props/BladeProperties.java | 226
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Charsets.java | 60
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RateLimiter.java | 67
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BigNumberSerializer.java | 44
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/enums/PolicyType.java | 55
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/sub.vue.btl | 113
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/XmlUtil.java | 299
Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/HttpCacheConfiguration.java | 67
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncrypt.java | 33
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeBlockExceptionHandler.java | 26
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RsaUtil.java | 381
Source/BladeX-Tool/blade-starter-develop/pom.xml | 40
Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/config/ReportConfiguration.java | 66
Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/props/DataScopeProperties.java | 49
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/tree/crud.vue.btl | 189
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/OssTemplate.java | 203
Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/TokenInfo.java | 39
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/HuaweiObsConfiguration.java | 65
Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/OsChinaTest.java | 38
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/plugins/BladePaginationInterceptor.java | 49
Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/service/AutoService.java | 47
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/crud/data.ts.btl | 30
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/service.java.btl | 54
Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/support/ExcelImporter.java | 35
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/config/ToolConfiguration.java | 53
Source/BladeX-Tool/blade-starter-social/src/main/resources/blade-social.yml | 3
Source/BladeX-Tool/blade-core-log4j2/pom.xml | 40
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecryptAes.java | 28
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/model/OssFile.java | 54
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/bean/CryptoInfoBean.java | 25
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/BladePage.java | 70
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/edit.js.vm | 99
Source/BladeX-Tool/blade-core-test/src/main/java/org/springblade/core/test/BladeBootTestException.java | 30
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/HexUtil.java | 164
Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/Service.java | 49
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DigestUtil.java | 450
Source/BladeX-Tool/blade-starter-cache/pom.xml | 40
Source/BladeX-Tool/blade-core-tool/pom.xml | 77
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanMap.java | 125
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/IoUtil.java | 109
Source/BladeX-Tool/blade-starter-sms/pom.xml | 58
Source/BladeX-Tool/blade-starter-log/pom.xml | 51
Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/enums/SmsStatusEnum.java | 46
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/BladeMediaType.java | 49
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/enums/OssEnum.java | 84
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/NumberUtil.java | 210
Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/model/SmsResponse.java | 50
Source/BladeX-Tool/blade-starter-social/pom.xml | 40
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/BladeRedisSerializerConfigAble.java | 66
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/tree/Modal.vue.btl | 90
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/props/BladeRequestLogProperties.java | 50
Source/BladeX-Tool/blade-core-cloud/pom.xml | 54
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/error/BladeErrorAttributes.java | 63
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/XssHtmlFilter.java | 536
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/enums/OssStatusEnum.java | 46
Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/HttpCacheInterceptor.java | 95
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Lazy.java | 72
Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/YunpianSmsConfiguration.java | 50
Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/config/JwtRedisConfiguration.java | 52
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/MappingApiJackson2HttpMessageConverter.java | 108
Source/BladeX-Tool/blade-starter-trace/src/main/java/org/springblade/core/trace/TraceAutoConfiguration.java | 30
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncryptRsa.java | 18
Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/controller/CodeSyncUniversalController.java | 45
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RegexUtil.java | 114
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceGlobalAdvisor.java | 75
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedRunnable.java | 35
Source/BladeX-Tool/blade-starter-ehcache/pom.xml | 29
Source/BladeX-Tool/blade-starter-flowable/pom.xml | 35
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/UsualLogListener.java | 57
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/UrlUtil.java | 92
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ThreadLocalUtil.java | 136
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/server/UndertowHttp2Configuration.java | 44
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanMapEmitter.java | 192
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/DsTenantIdProcessor.java | 42
Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoEnvPostProcessor.java | 35
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/constant/EventConstant.java | 35
Source/BladeX-Tool/blade-starter-cache/src/main/java/org/springblade/core/cache/config/CacheConfiguration.java | 30
Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/config/BladeLoadBalancerConfiguration.java | 60
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedCallable.java | 38
Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/listener/BladeServletRequestListener.java | 74
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/StartEventListener.java | 47
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/data.ts.btl | 30
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecryptDes.java | 28
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/LockType.java | 34
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RedisRateLimiterAspect.java | 102
Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoIgnore.java | 35
Source/BladeX-Tool/blade-core-log4j2/src/main/java/org/springblade/core/log4j2/LogPrintStream.java | 90
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/tuple/Pair.java | 84
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/AppConstant.java | 131
Source/BladeX-Tool/blade-starter-develop/src/main/java/org/springblade/develop/support/BladeCodeGenerator.java | 356
Source/BladeX-Tool/blade-starter-redis/pom.xml | 56
Source/BladeX-Tool/blade-starter-tenant/pom.xml | 46
Source/BladeX-Tool/.editorconfig | 21
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/crud/const.js.btl | 31
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/BladeHttpProperties.java | 65
Source/BladeX-Tool/blade-starter-ehcache/src/main/resources/ehcache.xml | 132
Source/BladeX-Tool/blade-starter-transaction/src/main/resources/blade-transaction.yml | 16
Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/DomMapper.java | 160
Source/BladeX-Tool/blade-starter-cache/src/main/java/org/springblade/core/cache/constant/CacheConstant.java | 52
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/utils/PageUtil.java | 81
Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/service/IReportFileService.java | 28
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedComparator.java | 38
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/ssl/TrustAllHostNames.java | 35
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/rule/OssRule.java | 42
Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/QiniuSmsConfiguration.java | 52
Source/BladeX-Tool/blade-core-boot/src/main/resources/static/favicon.ico | 0
Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/utils/AuthUtil.java | 449
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/model/MinioItem.java | 54
Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoFailureAnalyzer.java | 35
Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/BladeHttpCacheProperties.java | 52
Source/BladeX-Tool/blade-core-auto/pom.xml | 38
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/aspect/LogTraceAspect.java | 46
Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoListener.java | 35
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedSupplier.java | 39
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DateUtil.java | 634
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeNumberModule.java | 48
Source/BladeX-Tool/README.md | 43
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/ProtoStuffSerializer.java | 62
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeSentinelFilterConfiguration.java | 73
Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/Sets.java | 43
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/BladeMetaObjectHandler.java | 41
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/feignclient.java.btl | 53
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/crypto/ApiCryptoRsa.java | 22
Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/props/ReportProperties.java | 36
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/CacheKey.java | 50
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ClassUtil.java | 130
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/error/BladeErrorController.java | 56
Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/utils/MongoJsonUtils.java | 126
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/TencentCosTemplate.java | 269
Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/util/HttpUtil.java | 133
Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/model/SmsInfo.java | 45
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/Kv.java | 221
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ConcurrentDateFormat.java | 87
Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/props/JwtProperties.java | 60
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DateTimeUtil.java | 226
Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/endpoint/ReportEndpoint.java | 72
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/log/BladeLogLevel.java | 114
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/methods/AbstractInsertMethod.java | 71
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sql/menu.sql.btl | 10
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/service.js.vm | 26
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantHandler.java | 125
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeJacksonProperties.java | 46
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/config/JacksonConfiguration.java | 82
Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/QiniuSmsTemplate.java | 85
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/utils/LogTraceUtil.java | 53
Source/BladeX-Tool/blade-starter-develop/src/main/java/org/springblade/develop/support/BladeTemplateEngine.java | 153
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/BasicInterceptor.java | 113
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/QiniuTemplate.java | 260
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/TenantId.java | 33
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/StringPool.java | 87
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/error/ErrorType.java | 57
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/props/MybatisPlusProperties.java | 64
Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/service/impl/ReportFileServiceImpl.java | 32
Source/BladeX-Tool/blade-starter-swagger/pom.xml | 41
Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_prod.xml | 29
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/BaseEntityWrapper.java | 63
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/crypto/ApiCryptoDes.java | 22
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeSentinelInvocationHandler.java | 169
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/mp/TenantEntity.java | 40
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Exceptions.java | 99
Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/Config.java | 35
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/ProtoStuffSerializerConfiguration.java | 46
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/ClientDetails.java | 52
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/tree/data.ts.btl | 30
Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/rule/GrayscaleLoadBalancer.java | 115
Source/BladeX-Tool/blade-starter-api-crypto/README.md | 4
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/exception/ServiceException.java | 64
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/base/BaseEntity.java | 98
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/WebUtil.java | 314
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/spel/BladeExpressionRootObject.java | 42
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/ForestNodeMerger.java | 50
Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/SmsConfiguration.java | 33
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/launch/LogLauncherServiceImpl.java | 43
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/service/impl/BladeServiceImpl.java | 76
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/TenantParamDS.java | 33
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/Try.java | 88
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/aspect/BladeTenantAspect.java | 49
Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/model/SmsData.java | 55
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/aspect/AuthAspect.java | 123
Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/RetryPolicy.java | 58
Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/TencentSmsTemplate.java | 101
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/exception/DecryptBodyFailException.java | 13
Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/BaseAuthenticator.java | 42
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/error/ErrorUtil.java | 53
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeBeanSerializerModifier.java | 107
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/BladeRedisProperties.java | 53
Source/BladeX-Tool/blade-starter-trace/src/main/resources/blade-trace.yml | 5
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/util/ApiCryptoUtil.java | 124
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/BladeSecureProperties.java | 84
Source/BladeX-Tool/blade-starter-log/src/main/resources/log/logback-dev.xml | 113
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/StrSpliter.java | 501
Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/FormBuilder.java | 68
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/sub/crud.vue.btl | 375
Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/provider/DatabaseProvider.java | 111
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantProperties.java | 71
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/exception/TenantDataSourceException.java | 43
Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/constant/DataScopeConstant.java | 65
Source/BladeX-Tool/blade-starter-develop/src/main/java/org/springblade/develop/CodeGenerator.java | 100
Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/endpoint/AgentEndpoint.java | 47
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/IFileProxy.java | 53
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/BladeExecutorConfiguration.java | 132
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeFeignSentinel.java | 128
Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/VNews.java | 24
Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoContextInitializer.java | 35
Source/BladeX-Tool/blade-starter-swagger/src/main/resources/blade-swagger.yml | 11
Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/SmsTemplate.java | 111
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/ForestNodeManager.java | 85
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/sub/const.js.btl | 54
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/aspect/RequestLogAspect.java | 260
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/QiniuConfiguration.java | 81
Source/BladeX-Tool/blade-starter-metrics/src/main/java/org/springblade/core/metrics/sentinel/SentinelMetricsExtension.java | 80
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/AesUtil.java | 309
Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/handler/ScopeModelHandler.java | 55
Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/RetryInterceptor.java | 74
Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/enums/SmsEnum.java | 80
Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/MultipartFormBuilder.java | 97
Source/BladeX-Tool/blade-starter-auth/pom.xml | 29
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/TencentCosConfiguration.java | 82
Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/enums/DataScopeEnum.java | 78
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RateLimiterAutoConfiguration.java | 64
Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerAutoConfiguration.java | 152
Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/EnableBladeFeign.java | 81
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/code.properties | 5
Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/VBlog.java | 30
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/RedisLockClientImpl.java | 79
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/SignSecure.java | 47
Source/BladeX-Tool/blade-core-context/pom.xml | 31
Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/factories/AutoFactoriesProcessor.java | 199
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/plugins/SqlLogInterceptor.java | 164
Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/exception/SecureException.java | 53
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/LocalFileProxyFactory.java | 117
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/INode.java | 59
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/entityDTO.java.btl | 34
Source/BladeX-Tool/blade-core-secure/pom.xml | 47
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/UsualLogEvent.java | 35
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/sub/api.js.btl | 50
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/utils/PropsUtil.java | 43
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/RedisLockClient.java | 91
Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/interceptor/DataScopeInterceptor.java | 139
Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/HttpCacheService.java | 64
Source/BladeX-Tool/blade-starter-flowable/src/main/java/org/flowable/common/engine/impl/db/LiquibaseBasedSchemaManager.java | 191
Source/BladeX-Tool/.gitignore | 27
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/api/ResultCode.java | 113
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/IntegerPool.java | 28
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/convert/BladeConverter.java | 77
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/feign/LogClientFallback.java | 49
Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeRunnableWrapper.java | 64
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/model/LogError.java | 58
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/ConsulConstant.java | 50
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/config/BladePropertyConfiguration.java | 43
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/NacosConstant.java | 128
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/filter/LogTraceFilter.java | 51
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/FileProxyManager.java | 51
Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/BladeProxySelector.java | 61
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/utils/LogAbstractUtil.java | 71
Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceJdbcProvider.java | 91
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/config/SecureConfiguration.java | 118
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/model/LogUsual.java | 50
Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/methods/InsertIgnore.java | 33
Source/BladeX-Tool/blade-starter-social/src/main/java/org/springblade/core/social/props/SocialProperties.java | 58
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/ResponseProvider.java | 50
Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/BladeApplication.java | 130
Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/crypto/ApiCrypto.java | 22
Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/mapper.xml.btl | 39
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/RedisLockAspect.java | 103
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/annotation/ApiLog.java | 38
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/error/RestExceptionTranslator.java | 147
/dev/null | 25
Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/ICacheKey.java | 71
Source/BladeX-Tool/blade-core-auto/src/main/resources/META-INF/services/javax.annotation.processing.Processor | 2
Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/convert/StringToEnumConverter.java | 126
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/model/LogAbstract.java | 107
Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/BladeFileUtil.java | 240
Source/BladeX-Tool/blade-core-boot/src/main/resources/banner.txt | 8
Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/TypeHelper.java | 123
Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/MinioTemplate.java | 418
650 files changed, 61,448 insertions(+), 65 deletions(-)
diff --git a/Source/BladeX-Tool/.editorconfig b/Source/BladeX-Tool/.editorconfig
new file mode 100644
index 0000000..8cfd370
--- /dev/null
+++ b/Source/BladeX-Tool/.editorconfig
@@ -0,0 +1,21 @@
+# http://editorconfig.org
+root = true
+
+# 绌烘牸鏇夸唬Tab缂╄繘鍦ㄥ悇绉嶇紪杈戝伐鍏蜂笅鏁堟灉涓�鑷�
+[*]
+indent_style = space
+indent_size = 4
+charset = utf-8
+end_of_line = lf
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.java]
+indent_style = tab
+
+[*.{json,yml}]
+indent_size = 2
+
+[*.md]
+insert_final_newline = false
+trim_trailing_whitespace = false
diff --git a/Source/BladeX-Tool/.gitignore b/Source/BladeX-Tool/.gitignore
new file mode 100644
index 0000000..987273c
--- /dev/null
+++ b/Source/BladeX-Tool/.gitignore
@@ -0,0 +1,27 @@
+# maven #
+target
+
+logs
+
+# windows #
+Thumbs.db
+
+# Mac #
+.DS_Store
+
+# eclipse #
+.settings
+.project
+.classpath
+.log
+*.class
+
+# idea #
+.idea
+*.iml
+
+# Package Files #
+*.jar
+*.war
+*.ear
+/target
diff --git a/Source/BladeX-Tool/LICENSE b/Source/BladeX-Tool/LICENSE
new file mode 100644
index 0000000..3ad5f69
--- /dev/null
+++ b/Source/BladeX-Tool/LICENSE
@@ -0,0 +1,34 @@
+BladeX鍟嗕笟鎺堟潈璁稿彲鍗忚
+
+涓�銆� 鐭ヨ瘑浜ф潈锛�
+BladeX绯诲垪浜у搧鐭ヨ瘑浜ф潈褰掍笂娴峰竷闆峰痉绉戞妧鏈夐檺鍏徃鐙珛鎵�鏈�
+
+浜屻�� 璁稿彲锛�
+1. 鍦ㄦ偍瀹屽叏鎺ュ彈骞堕伒瀹堟湰鍗忚鐨勫熀纭�涓婏紝鏈崗璁巿浜堟偍浣跨敤BladeX鐨勬煇浜涙潈鍒╁拰闈炵嫭鍗犳�ц鍙��
+2. 鏈崗璁腑锛屽皢鏈骇鍝佷娇鐢ㄧ敤閫斿垎涓衡�滀笓涓氱増鐢ㄩ�斺�濆拰鈥滀紒涓氱増鐢ㄩ�斺�濄��
+3. 鈥滀笓涓氱増鐢ㄩ�斺�濆畾涔夛細鎸囦釜浜哄湪闈炲洟浣撴満鏋勪腑鍑轰簬浠讳綍鐩殑浣跨敤鏈骇鍝侊紙浠讳綍鐩殑鍖呮嫭鍟嗕笟鐩殑鎴栭潪鐩堝埄鐩殑锛夈��
+4. 鈥滀紒涓氱増鐢ㄩ�斺�濆畾涔夛細鎸囧洟浣撴満鏋勶紙渚嬪鍏徃浼佷笟銆佹斂搴溿�佸鏍°�佸啗闃熴�佸尰闄€�佺ぞ浼氬洟浣撶瓑鍚勭被缁勭粐锛夛紙涓嶅寘鍚泦鍥紝鑻ラ泦鍥娇鐢ㄥ垯闇�涓哄悇涓瓙鍏徃鍒嗗埆璐拱浼佷笟鎺堟潈锛夊嚭浜庝换浣曠洰鐨勪娇鐢ㄦ湰浜у搧锛堜换浣曠洰鐨勫寘鎷晢涓氱洰鐨勬垨闈炵泩鍒╃洰鐨勶級銆�
+
+涓夈�� 绾︽潫鍜岄檺鍒讹細
+1. 鏈骇鍝佸彧鑳界敱鎮ㄤ负鏈崗璁鍙殑鐩殑鑰屼娇鐢紝鎮ㄤ笉寰楅�忛湶缁欎换浣曠涓夋柟锛�
+2. 浠庢湰浜у搧鍙栧緱鐨勪换浣曚俊鎭�佽蒋浠躲�佷骇鍝佹垨鏈嶅姟锛屾偍涓嶅緱瀵瑰叾杩涜淇敼銆佹敼缂栨垨鍩轰簬浠ヤ笂鍐呭鍒涘缓鍚岀绫诲埆鐨勮鐢熶骇鍝佸苟鍞崠銆�
+3. 鎮ㄤ笉寰楀鏈骇鍝佷互鍙婁笌涔嬪叧鑱旂殑鍟嗕笟鎺堟潈杩涜鍙戝竷銆佸嚭绉熴�侀攢鍞�佸垎閿�銆佹姷鎶笺�佽浆璁┿�佽鍙垨鍙戞斁瀛愯鍙瘉銆�
+4. 鏈骇鍝佸晢涓氭巿鏉冪増鍙兘鍖呭惈涓�浜涚嫭绔嬪姛鑳芥垨鐗规�э紝杩欎簺鍔熻兘鍙湁鍦ㄦ偍璐拱鍟嗕笟鎺堟潈鍚庢墠鍙互浣跨敤銆傚湪鏈彇寰楀晢涓氭巿鏉冪殑鎯呭喌涓嬶紝鎮ㄤ笉寰椾娇鐢ㄣ�佸皾璇曚娇鐢ㄦ垨澶嶅埗杩欎簺鎺堟潈鐗堢嫭绔嬪姛鑳姐��
+5. 鑻ユ偍鐨勫鎴疯姹備互婧愮爜鏂瑰紡浜や粯杞欢锛岄渶缂寸撼浼佷笟鐗堟巿鏉冭垂鐢紝鍚﹀垯鏈骇鍝侀儴鍒嗕笉寰楁彁渚涙簮鐮併��
+
+鍥涖�� 涓嶅緱鐢ㄤ簬闈炴硶鎴栫姝㈢殑鐢ㄩ�旓細
+鎮ㄥ湪浣跨敤鏈骇鍝佹垨鏈嶅姟鏃讹紝涓嶅緱灏嗘湰浜у搧浜у搧鎴栨湇鍔$敤浜庝换浣曢潪娉曠敤閫旀垨鏈崗璁潯娆俱�佹潯浠跺拰澹版槑绂佹鐨勭敤閫斻��
+
+浜斻�� 鍏嶈矗璇存槑锛�
+1. 鏈骇鍝佹寜鈥滅幇鐘垛�濇巿浜堣鍙紝鎮ㄩ』鑷鎵挎媴浣跨敤鏈骇鍝佺殑椋庨櫓銆侭ladeX鍥㈤槦涓嶅姝ゆ彁渚涗换浣曟槑绀恒�佹殫绀烘垨浠讳綍鍏跺畠褰㈠紡鐨勬媴淇濆拰琛ㄧず銆傚湪浠讳綍鎯呭喌涓嬶紝瀵逛簬鍥犱娇鐢ㄦ垨鏃犳硶浣跨敤鏈蒋浠惰�屽鑷寸殑浠讳綍鎹熷け锛堝寘鎷絾涓嶄粎闄愪簬鍟嗕笟鍒╂鼎鎹熷け銆佷笟鍔′腑鏂垨涓氬姟淇℃伅涓㈠け锛夛紝BladeX鍥㈤槦鏃犻渶鍚戞偍鎴栦换浣曠涓夋柟璐熻矗锛屽嵆浣緽ladeX鍥㈤槦宸茶鍛婄煡鍙兘浼氶�犳垚姝ょ被鎹熷け銆傚湪浠讳綍鎯呭喌涓嬶紝 BladeX鍥㈤槦鍧囦笉灏变换浣曠洿鎺ョ殑銆侀棿鎺ョ殑銆侀檮甯︾殑銆佸悗鏋滄�х殑銆佺壒鍒殑銆佹儵鎴掓�х殑鍜屽缃氭�х殑鎹熷璧斿伩鎵挎媴浠讳綍璐d换锛屾棤璁鸿涓诲紶鏄熀浜庝繚璇併�佸悎鍚屻�佷镜鏉冿紙鍖呮嫭鐤忓拷锛夋垨鏄熀浜庡叾浠栧師鍥犱綔鍑恒��
+2. 鏈骇鍝佸彲鑳藉唴缃湁绗笁鏂规湇鍔★紝鎮ㄥ簲鑷璇勪及浣跨敤杩欎簺绗笁鏂规湇鍔$殑椋庨櫓锛岀敱浣跨敤姝ょ被绗笁鏂规湇鍔¤�屼骇鐢熺殑绾犵悍锛屽叏閮ㄨ矗浠荤敱鎮ㄨ嚜琛屾壙鎷呫��
+3. BladeX鍥㈤槦涓嶅浣跨敤鏈骇鍝佹瀯寤虹殑缃戠珯涓换浣曚俊鎭唴瀹逛互鍙婂鑷寸殑浠讳綍鐗堟潈绾犵悍銆佹硶寰嬩簤璁拰鍚庢灉鎵挎媴浠讳綍璐d换锛屽叏閮ㄨ矗浠荤敱鎮ㄨ嚜琛屾壙鎷呫��
+4. BladeX鍥㈤槦鍙兘浼氱粡甯告彁渚涗骇鍝佹洿鏂版垨鍗囩骇锛屼絾BladeX鍥㈤槦娌℃湁涓烘牴鎹湰鍗忚璁稿彲鐨勪骇鍝佹彁渚涚淮鎶ゆ垨鏇存柊鐨勮矗浠汇��
+5. BladeX鍥㈤槦鍙兘浼氭寜鐓у畼鏂瑰埗瀹氱殑绛旂枒瑙勫垯涓烘偍杩涜绛旂枒锛屼絾BladeX鍥㈤槦娌℃湁涓烘牴鎹湰鍗忚璁稿彲鐨勪骇鍝佹彁渚涙妧鏈敮鎸佺殑涔夊姟鎴栬矗浠汇��
+
+鍏�� 鏉冨埄鍜屾墍鏈夋潈鐨勪繚鐣欙細
+BladeX鍥㈤槦淇濈暀鎵�鏈夋湭鍦ㄦ湰鍗忚涓槑纭巿浜堟偍鐨勬墍鏈夋潈鍒┿�侭ladeX鍥㈤槦淇濈暀闅忔椂鏇存柊鏈崗璁殑鏉冨埄锛屽苟鍙渶鍏ず浜庡搴斾骇鍝侀」鐩殑LICENSE鏂囦欢锛屾棤闇�寰佸緱鎮ㄧ殑浜嬪厛鍚屾剰涓旀棤闇�鍙﹁閫氱煡锛屾洿鏂板悗鐨勫唴瀹瑰簲浜庡叕绀哄嵆鏃剁敓鏁堛�傛偍鍙互闅忔椂璁块棶浜у搧鍦板潃骞舵煡闃呮渶鏂扮増璁稿彲鏉℃锛屽湪鏇存柊鐢熸晥鍚庢偍缁х画浣跨敤鏈骇鍝佸垯琚浣滄偍宸叉帴鍙椾簡鏂扮殑鏉℃銆�
+
+涓冦�� 鍗忚缁堟
+1. 鎮ㄤ竴鏃﹀紑濮嬪鍒躲�佷笅杞姐�佸畨瑁呮垨鑰呬娇鐢ㄦ湰浜у搧锛屽嵆琚涓哄畬鍏ㄧ悊瑙e苟鎺ュ彈鏈崗璁殑鍚勯」鏉℃锛屽湪浜湁涓婅堪鏉℃鎺堜簣鐨勮鍙潈鍔涘悓鏃讹紝涔熷彈鍒扮浉鍏崇殑绾︽潫鍜岄檺鍒讹紝鏈崗璁鍙寖鍥翠互澶栫殑琛屼负锛屽皢鐩存帴杩濆弽鏈崗璁苟鏋勬垚渚垫潈銆�
+2. 涓�鏃︽偍杩濆弽鏈崗璁殑鏉℃锛孊ladeX鍥㈤槦闅忔椂鍙兘缁堟鏈崗璁�佹敹鍥炶鍙拰鎺堟潈锛屽苟瑕佹眰鎮ㄦ壙鎷呯浉搴旀硶寰嬪拰缁忔祹璐d换銆�
diff --git a/Source/BladeX-Tool/README.md b/Source/BladeX-Tool/README.md
new file mode 100644
index 0000000..0de6efe
--- /dev/null
+++ b/Source/BladeX-Tool/README.md
@@ -0,0 +1,43 @@
+## 鐗堟潈澹版槑
+* BladeX鏄竴涓晢涓氬寲杞欢锛岀郴鍒椾骇鍝佺煡璇嗕骇鏉冨綊**涓婃捣甯冮浄寰风鎶�鏈夐檺鍏徃**鐙珛鎵�鏈�
+* 鎮ㄤ竴鏃﹀紑濮嬪鍒躲�佷笅杞姐�佸畨瑁呮垨鑰呬娇鐢ㄦ湰浜у搧锛屽嵆琚涓哄畬鍏ㄧ悊瑙e苟鎺ュ彈鏈崗璁殑鍚勯」鏉℃
+* 鏇村璇︽儏璇风湅锛歔BladeX鍟嗕笟鎺堟潈璁稿彲鍗忚](/LICENSE)
+
+## 绛旂枒娴佺▼
+>1. 閬囧埌闂鎴朆ug
+>2. 涓氬姟鍨嬮棶棰樻墦鏂偣璋冭瘯灏濊瘯鎵惧嚭闂鎵�鍦�
+>3. 绯荤粺鍨嬮棶棰橀�氳繃鐧惧害銆佽胺姝屻�佺ぞ鍖烘煡鎵捐В鍐虫柟妗�
+>4. 鏈В鍐抽棶棰樺垯杩涘叆鎶�鏈ぞ鍖鸿繘琛屽彂甯栨彁闂細[https://sns.bladex.vip/](https://sns.bladex.vip/)
+>5. 灏嗗笘瀛愬湴鍧�鍙戣嚦鍟嗕笟缇わ紝鐗瑰埆绠�鍗曚笁瑷�涓よ灏辫兘鎻忚堪娓呮鐨勪篃鍙湪绛旂枒鏃堕棿鍐呭彂鑷冲晢涓氱兢鎻愰棶
+>6. 鍙戝笘鐨勬椂鍊欎竴瀹氳鎻忚堪娓呮锛岃缁嗘弿杩伴亣鍒伴棶棰樼殑**閲嶇幇姝ラ**銆�**鎶ラ敊璇︾粏淇℃伅**銆�**鐩稿叧浠g爜涓庨�昏緫**銆�**浣跨敤杞欢鐗堟湰**浠ュ強**鎿嶄綔绯荤粺鐗堟湰**锛屽惁鍒欓殢鎰忓彂甯栨彁闂皢浼氭彁楂樻垜浠殑绛旂枒闅惧害銆�
+
+## 绛旂枒鏃堕棿
+* 宸ヤ綔鏃ワ細9:00 ~ 17:00 鎻愪緵绛旂枒锛屽懆鏈�佽妭鍋囨棩浼戞伅锛屾殏鍋滅瓟鐤�
+* 璇峰嬁**绉佽亰鎻愰棶**锛屼互鍏嶈鍏朵粬鐢ㄦ埛鐨勬秷鎭鐩栦粠鑰屾棤娉曡幏寰楃瓟鐤�
+* 绛旂枒鏃堕棿澶栭亣鍒伴棶棰樺彲浠ュ皢闂鍙戝笘鑷砙鎶�鏈ぞ鍖篯(https://sns.bladex.vip/)锛屾垜浠悗缁細閫愪釜鍥炲
+
+## 鎺堟潈鑼冨洿
+* 涓撲笟鐗堬細鍙彲鐢ㄤ簬**涓汉瀛︿範**鍙�**涓汉绉佹椿**椤圭洰锛屼笉鍙敤浜庡叕鍙告垨鍥㈤槦锛屼笉鍙硠闇茬粰浠讳綍绗笁鏂�
+* 浼佷笟鐗堬細鍙敤浜�**浼佷笟鍚嶄笅**鐨勪换浣曢」鐩紝浼佷笟鐗堝憳宸ュ湪**鏈喘涔�**涓撲笟鐗堟巿鏉冨墠锛屽彧鎺堟潈寮�鍙�**鎵�鍦ㄦ巿鏉冧紒涓氬悕涓�**鐨勯」鐩紝**涓嶅緱灏咮ladeX鐢ㄤ簬涓汉绉佹椿**
+* 鍏卞悓閬靛畧锛氳嫢鐢叉柟闇�瑕佹偍鎻愪緵椤圭洰婧愮爜锛屽垯闇�浠d负鐢叉柟璐拱BladeX浼佷笟鎺堟潈锛岀敳鏂硅喘涔板悗缁殑鎵�鏈夐」鐩兘鏃犻渶鍐嶆璐拱鎺堟潈
+
+## 鍟嗙敤鏉冪泭
+* 鉁旓笍 閬靛畧[鍟嗕笟鍗忚](/LICENSE)鐨勫墠鎻愪笅锛屽皢BladeX绯诲垪浜у搧鐢ㄤ簬鎺堟潈鑼冨洿鍐呯殑鍟嗙敤椤圭洰锛屽苟涓婄嚎杩愯惀
+* 鉁旓笍 閬靛畧[鍟嗕笟鍗忚](/LICENSE)鐨勫墠鎻愪笅锛屼笉闄愬埗椤圭洰鏁帮紝涓嶉檺鍒舵湇鍔″櫒鏁�
+* 鉁旓笍 閬靛畧[鍟嗕笟鍗忚](/LICENSE)鐨勫墠鎻愪笅锛屽皢鑷缂栧啓鐨勪笟鍔′唬鐮佺敵璇疯蒋浠惰憲浣滄潈
+
+## 浣曚负渚垫潈
+* 鉂� 涓嶉伒瀹堝晢涓氬崗璁紝绉佽嚜閿�鍞晢涓氭簮鐮�
+* 鉂� 浠ヤ换浣曠悊鐢卞皢BladeX婧愮爜鐢ㄤ簬鐢宠杞欢钁椾綔鏉�
+* 鉂� 灏嗗晢涓氭簮鐮佷互浠讳綍閫斿緞浠讳綍鐞嗙敱娉勯湶缁欐湭鎺堟潈鐨勫崟浣嶆垨涓汉
+* 鉂� 寮�鍙戝畬姣曢」鐩紝娌℃湁涓虹敳鏂硅喘涔颁紒涓氭巿鏉冿紝鍚戠敳鏂规彁渚涗簡BladeX浠g爜
+* 鉂� 鍩轰簬BladeX鎷撳睍鐮斿彂涓嶣ladeX鏈夌珵浜夊叧绯荤殑琛嶇敓妗嗘灦锛屽苟灏嗗叾寮�婧愭垨閿�鍞�
+
+## 渚垫潈鍚庢灉
+* 鎯呰妭杈冭交锛氱涓�娆″彂鐜拌鍛婂鐞�
+* 鎯呰妭杈冮噸锛氬皝绂佽处鍙凤紝韪㈠嚭鍟嗕笟缇わ紝骞朵繚鐣欒拷绌舵硶寰嬭矗浠荤殑鏉冨埄
+* 鎯呰妭涓ラ噸锛氫笌鏈湴寰嬪笀浜嬪姟鎵�鍚堜綔锛屼互鍏徃鍚嶄箟璧疯瘔渚电姱璁$畻鏈鸿蒋浠惰憲浣滄潈
+
+## 涓炬姤鏈夊
+* 鍚戝畼鏂规彁渚涙湁鐢ㄧ嚎绱㈠苟鎴愬姛鎹f瘉鐩楃増涓汉鎴栫獫鐐癸紝灏嗕細鐪嬫垚鏋滅粰浜� 500锝�10000 涓嶇瓑鐨勭幇閲戝鍔�
+* 瀹樻柟鍞竴鎸囧畾QQ锛�1272154962
\ No newline at end of file
diff --git a/Source/BladeX-Tool/blade-bom/pom.xml b/Source/BladeX-Tool/blade-bom/pom.xml
new file mode 100644
index 0000000..01ff45a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-bom/pom.xml
@@ -0,0 +1,626 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-parent</artifactId>
+ <version>2.7.1</version>
+ <relativePath />
+ </parent>
+
+ <groupId>org.springblade.platform</groupId>
+ <artifactId>blade-bom</artifactId>
+ <packaging>pom</packaging>
+ <version>${bladex.tool.version}</version>
+ <description>bladex缁熶竴鐗堟湰閰嶇疆</description>
+
+ <properties>
+ <bladex.tool.version>3.0.1.RELEASE</bladex.tool.version>
+
+ <swagger.version>2.10.5</swagger.version>
+ <swagger.models.version>1.6.2</swagger.models.version>
+ <knife4j.version>2.0.9</knife4j.version>
+ <mybatis.plus.version>3.5.2</mybatis.plus.version>
+ <mybatis.plus.generator.version>3.5.3</mybatis.plus.generator.version>
+ <mybatis.plus.dynamic.version>3.3.6</mybatis.plus.dynamic.version>
+ <protostuff.version>1.6.0</protostuff.version>
+ <disruptor.version>3.4.2</disruptor.version>
+ <logstash.version>6.2</logstash.version>
+ <druid.version>1.2.8</druid.version>
+ <jackson.version>2.13.3</jackson.version>
+ <okhttp.version>4.9.3</okhttp.version>
+ <xxl.job.version>2.1.2</xxl.job.version>
+ <log4j2.version>2.18.0</log4j2.version>
+ <logback.version>1.2.11</logback.version>
+
+ <mysql.connector.version>8.0.22</mysql.connector.version>
+ <oracle.connector.version>12.2.0.1</oracle.connector.version>
+ <postgresql.connector.version>42.2.22</postgresql.connector.version>
+ <sqlserver.connector.version>8.4.1.jre8</sqlserver.connector.version>
+ <dameng.connector.version>8.1.2.141</dameng.connector.version>
+
+ <spring.boot.admin.version>2.7.1</spring.boot.admin.version>
+ <alibaba.cloud.version>2021.0.1.0</alibaba.cloud.version>
+ <alibaba.seata.version>1.5.2</alibaba.seata.version>
+ <alibaba.nacos.version>2.1.0</alibaba.nacos.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <!-- Blade -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <version>${bladex.tool.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-boot</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-cloud</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-context</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-db</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-launch</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-log4j2</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-secure</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-test</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-tool</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-actuate</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-api-crypto</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-auth</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-cache</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-datascope</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-develop</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-ehcache</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-excel</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-flowable</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-http</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-jwt</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-log</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-metrics</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-mongo</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-mybatis</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-oss</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-prometheus</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-redis</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-report</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-loadbalancer</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-sms</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-social</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-swagger</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-tenant</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-trace</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-transaction</artifactId>
+ <version>${bladex.tool.version}</version>
+ </dependency>
+ <!-- Nacos -->
+ <dependency>
+ <groupId>com.alibaba.cloud</groupId>
+ <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+ <version>${alibaba.cloud.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.alibaba.cloud</groupId>
+ <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
+ <version>${alibaba.cloud.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.alibaba.nacos</groupId>
+ <artifactId>nacos-client</artifactId>
+ <version>${alibaba.nacos.version}</version>
+ </dependency>
+ <!-- Sentinel -->
+ <dependency>
+ <groupId>com.alibaba.cloud</groupId>
+ <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
+ <version>${alibaba.cloud.version}</version>
+ </dependency>
+ <!-- Seata-->
+ <dependency>
+ <groupId>com.alibaba.cloud</groupId>
+ <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
+ <version>${alibaba.cloud.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.seata</groupId>
+ <artifactId>seata-spring-boot-starter</artifactId>
+ <version>${alibaba.seata.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.seata</groupId>
+ <artifactId>seata-all</artifactId>
+ <version>${alibaba.seata.version}</version>
+ </dependency>
+ <!--Mybatis-->
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus</artifactId>
+ <version>${mybatis.plus.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-boot-starter</artifactId>
+ <version>${mybatis.plus.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-extension</artifactId>
+ <version>${mybatis.plus.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-generator</artifactId>
+ <version>${mybatis.plus.generator.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+ <version>${mybatis.plus.dynamic.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.mybatis</groupId>
+ <artifactId>mybatis-typehandlers-jsr310</artifactId>
+ <version>1.0.2</version>
+ </dependency>
+ <!-- JWT -->
+ <dependency>
+ <groupId>io.jsonwebtoken</groupId>
+ <artifactId>jjwt-impl</artifactId>
+ <version>0.11.2</version>
+ </dependency>
+ <dependency>
+ <groupId>io.jsonwebtoken</groupId>
+ <artifactId>jjwt-jackson</artifactId>
+ <version>0.11.2</version>
+ </dependency>
+ <!-- Admin -->
+ <dependency>
+ <groupId>de.codecentric</groupId>
+ <artifactId>spring-boot-admin-starter-server</artifactId>
+ <version>${spring.boot.admin.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>de.codecentric</groupId>
+ <artifactId>spring-boot-admin-starter-client</artifactId>
+ <version>${spring.boot.admin.version}</version>
+ </dependency>
+ <!-- Druid -->
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>druid</artifactId>
+ <version>${druid.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>druid-spring-boot-starter</artifactId>
+ <version>${druid.version}</version>
+ </dependency>
+ <!-- MySql -->
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ <version>${mysql.connector.version}</version>
+ </dependency>
+ <!-- Oracle -->
+ <dependency>
+ <groupId>com.oracle</groupId>
+ <artifactId>ojdbc7</artifactId>
+ <version>${oracle.connector.version}</version>
+ </dependency>
+ <!-- PostgreSql -->
+ <dependency>
+ <groupId>org.postgresql</groupId>
+ <artifactId>postgresql</artifactId>
+ <version>${postgresql.connector.version}</version>
+ </dependency>
+ <!-- SqlServer -->
+ <dependency>
+ <groupId>com.microsoft.sqlserver</groupId>
+ <artifactId>mssql-jdbc</artifactId>
+ <version>${sqlserver.connector.version}</version>
+ </dependency>
+ <!-- DaMeng -->
+ <dependency>
+ <groupId>com.dameng</groupId>
+ <artifactId>DmJdbcDriver18</artifactId>
+ <version>${dameng.connector.version}</version>
+ </dependency>
+ <!--Swagger-->
+ <dependency>
+ <groupId>io.springfox</groupId>
+ <artifactId>springfox-swagger2</artifactId>
+ <version>${swagger.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.swagger</groupId>
+ <artifactId>swagger-models</artifactId>
+ <version>${swagger.models.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.github.xiaoymin</groupId>
+ <artifactId>knife4j-micro-spring-boot-starter</artifactId>
+ <version>${knife4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.github.xiaoymin</groupId>
+ <artifactId>knife4j-aggregation-spring-boot-starter</artifactId>
+ <version>${knife4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.github.xiaoymin</groupId>
+ <artifactId>knife4j-spring-ui</artifactId>
+ <version>${knife4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.plugin</groupId>
+ <artifactId>spring-plugin-core</artifactId>
+ <version>2.0.0.RELEASE</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.plugin</groupId>
+ <artifactId>spring-plugin-metadata</artifactId>
+ <version>2.0.0.RELEASE</version>
+ </dependency>
+ <!-- protostuff -->
+ <dependency>
+ <groupId>io.protostuff</groupId>
+ <artifactId>protostuff-core</artifactId>
+ <version>${protostuff.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.protostuff</groupId>
+ <artifactId>protostuff-runtime</artifactId>
+ <version>${protostuff.version}</version>
+ </dependency>
+ <!-- http -->
+ <dependency>
+ <groupId>org.jsoup</groupId>
+ <artifactId>jsoup</artifactId>
+ <version>1.12.1</version>
+ </dependency>
+ <dependency>
+ <groupId>com.squareup.okhttp3</groupId>
+ <artifactId>okhttp</artifactId>
+ <version>${okhttp.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.squareup.okhttp3</groupId>
+ <artifactId>logging-interceptor</artifactId>
+ <version>${okhttp.version}</version>
+ </dependency>
+ <!-- redisson -->
+ <dependency>
+ <groupId>org.redisson</groupId>
+ <artifactId>redisson</artifactId>
+ <version>3.11.6</version>
+ </dependency>
+ <!-- Disruptor -->
+ <dependency>
+ <groupId>com.lmax</groupId>
+ <artifactId>disruptor</artifactId>
+ <version>${disruptor.version}</version>
+ </dependency>
+ <!-- Logstash -->
+ <dependency>
+ <groupId>net.logstash.logback</groupId>
+ <artifactId>logstash-logback-encoder</artifactId>
+ <version>${logstash.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.janino</groupId>
+ <artifactId>janino</artifactId>
+ <version>3.0.15</version>
+ </dependency>
+ <!-- xxljob -->
+ <dependency>
+ <groupId>com.xuxueli</groupId>
+ <artifactId>xxl-job-core</artifactId>
+ <version>${xxl.job.version}</version>
+ </dependency>
+ <!-- captcha -->
+ <dependency>
+ <groupId>com.github.whvcse</groupId>
+ <artifactId>easy-captcha</artifactId>
+ <version>1.6.2</version>
+ </dependency>
+ <!-- jackson -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.module</groupId>
+ <artifactId>jackson-module-jaxb-annotations</artifactId>
+ <version>${jackson.version}</version>
+ </dependency>
+ <!-- fastjson -->
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>fastjson</artifactId>
+ <version>1.2.83_noneautotype</version>
+ </dependency>
+ <!-- sentinel -->
+ <dependency>
+ <groupId>com.alibaba.csp</groupId>
+ <artifactId>sentinel-core</artifactId>
+ <version>1.8.4</version>
+ </dependency>
+ <!-- easyexcel -->
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>easyexcel</artifactId>
+ <version>2.2.11</version>
+ </dependency>
+ <!-- JustAuth -->
+ <dependency>
+ <groupId>me.zhyd.oauth</groupId>
+ <artifactId>JustAuth</artifactId>
+ <version>1.16.5</version>
+ </dependency>
+ <!-- Guava -->
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>30.1.1-jre</version>
+ </dependency>
+ <!-- Prometheus -->
+ <dependency>
+ <groupId>io.micrometer</groupId>
+ <artifactId>micrometer-core</artifactId>
+ <version>1.9.1</version>
+ </dependency>
+ <dependency>
+ <groupId>io.micrometer</groupId>
+ <artifactId>micrometer-registry-prometheus</artifactId>
+ <version>1.9.1</version>
+ </dependency>
+ <!--AliOss-->
+ <dependency>
+ <groupId>com.aliyun.oss</groupId>
+ <artifactId>aliyun-sdk-oss</artifactId>
+ <version>3.14.0</version>
+ </dependency>
+ <!--MinIO-->
+ <dependency>
+ <groupId>io.minio</groupId>
+ <artifactId>minio</artifactId>
+ <version>8.3.7</version>
+ </dependency>
+ <!--QiNiu-->
+ <dependency>
+ <groupId>com.qiniu</groupId>
+ <artifactId>qiniu-java-sdk</artifactId>
+ <version>7.9.4</version>
+ </dependency>
+ <!--鑵捐COS-->
+ <dependency>
+ <groupId>com.qcloud</groupId>
+ <artifactId>cos_api</artifactId>
+ <version>5.6.69</version>
+ </dependency>
+ <!--鍗庝负浜慜bs-->
+ <dependency>
+ <groupId>com.huaweicloud</groupId>
+ <artifactId>esdk-obs-java</artifactId>
+ <version>3.21.12</version>
+ </dependency>
+ <!--AliSms-->
+ <dependency>
+ <groupId>com.aliyun</groupId>
+ <artifactId>aliyun-java-sdk-core</artifactId>
+ <version>4.5.30</version>
+ </dependency>
+ <!--YunPian-->
+ <dependency>
+ <groupId>com.yunpian.sdk</groupId>
+ <artifactId>yunpian-java-sdk</artifactId>
+ <version>1.2.7</version>
+ </dependency>
+ <!--鑵捐SMS-->
+ <dependency>
+ <groupId>com.github.qcloudsms</groupId>
+ <artifactId>qcloudsms</artifactId>
+ <version>1.0.6</version>
+ </dependency>
+ <!-- oauth2 -->
+ <dependency>
+ <groupId>org.springframework.security.oauth</groupId>
+ <artifactId>spring-security-oauth2</artifactId>
+ <version>2.3.8.RELEASE</version>
+ </dependency>
+ <!-- findbugs -->
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <version>3.0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <version>1.18.22</version>
+ </dependency>
+ <!-- log4j2 -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ <version>${log4j2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <version>${log4j2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-jul</artifactId>
+ <version>${log4j2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-slf4j-impl</artifactId>
+ <version>${log4j2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-to-slf4j</artifactId>
+ <version>${log4j2.version}</version>
+ </dependency>
+ <!-- logback -->
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <version>${logback.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ <version>${logback.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <distributionManagement>
+ <repository>
+ <id>blade-release</id>
+ <name>Release Repository</name>
+ <url>http://nexus.javablade.com/repository/maven-releases/</url>
+ </repository>
+ </distributionManagement>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-core-auto/README.md b/Source/BladeX-Tool/blade-core-auto/README.md
new file mode 100644
index 0000000..0fe8cae
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/README.md
@@ -0,0 +1,12 @@
+# auto 浠g爜鑷姩鐢熸垚
+
+## 瑙勫垝
+1. 鐢熸垚 java spi
+2. 鐢ㄦ潵鐢熸垚 `spring.factories`
+3. 鑰冭檻鐢熸垚 `spring-devtools.properties`
+4. 鑰冭檻锛熺敓鎴� `swagger` 娉ㄨВ
+
+## 鍙傝��
+Google Auto: https://github.com/google/auto
+
+Spring 5 - spring-context-indexer锛歨ttps://github.com/spring-projects/spring-framework/tree/master/spring-context-indexer
\ No newline at end of file
diff --git a/Source/BladeX-Tool/blade-core-auto/pom.xml b/Source/BladeX-Tool/blade-core-auto/pom.xml
new file mode 100644
index 0000000..870b49f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/pom.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-core-auto</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>${maven.plugin.version}</version>
+ <configuration>
+ <annotationProcessorPaths>
+ <path>
+ <groupId>org.hibernate.validator</groupId>
+ <artifactId>hibernate-validator-annotation-processor</artifactId>
+ <version>6.0.13.Final</version>
+ </path>
+ </annotationProcessorPaths>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoContextInitializer.java b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoContextInitializer.java
new file mode 100644
index 0000000..2fc6e9a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoContextInitializer.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.auto.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * ApplicationContextInitializer 澶勭悊
+ *
+ * @author L.cm
+ */
+@Documented
+@Retention(SOURCE)
+@Target(TYPE)
+public @interface AutoContextInitializer {
+}
diff --git a/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoEnvPostProcessor.java b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoEnvPostProcessor.java
new file mode 100644
index 0000000..4679240
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoEnvPostProcessor.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.auto.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * EnvironmentPostProcessor 澶勭悊
+ *
+ * @author L.cm
+ */
+@Documented
+@Retention(SOURCE)
+@Target(TYPE)
+public @interface AutoEnvPostProcessor {
+}
diff --git a/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoFailureAnalyzer.java b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoFailureAnalyzer.java
new file mode 100644
index 0000000..4c274db
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoFailureAnalyzer.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.auto.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * FailureAnalyzer 澶勭悊
+ *
+ * @author L.cm
+ */
+@Documented
+@Retention(SOURCE)
+@Target(TYPE)
+public @interface AutoFailureAnalyzer {
+}
diff --git a/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoIgnore.java b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoIgnore.java
new file mode 100644
index 0000000..33df0a3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoIgnore.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.auto.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * AutoIgnore 澶勭悊
+ *
+ * @author L.cm
+ */
+@Documented
+@Retention(SOURCE)
+@Target(TYPE)
+public @interface AutoIgnore {
+}
diff --git a/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoListener.java b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoListener.java
new file mode 100644
index 0000000..0a5ed49
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoListener.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.auto.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * ApplicationListener 澶勭悊
+ *
+ * @author L.cm
+ */
+@Documented
+@Retention(SOURCE)
+@Target(TYPE)
+public @interface AutoListener {
+}
diff --git a/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoRunListener.java b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoRunListener.java
new file mode 100644
index 0000000..0605013
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/annotation/AutoRunListener.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.auto.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * SpringApplicationRunListener 澶勭悊
+ *
+ * @author L.cm
+ */
+@Documented
+@Retention(SOURCE)
+@Target(TYPE)
+public @interface AutoRunListener {
+}
diff --git a/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/AbstractBladeProcessor.java b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/AbstractBladeProcessor.java
new file mode 100644
index 0000000..458790c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/AbstractBladeProcessor.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.auto.common;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.tools.Diagnostic.Kind;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Set;
+
+/**
+ * 鎶借薄 澶勭悊鍣�
+ *
+ * @author L.cm
+ */
+public abstract class AbstractBladeProcessor extends AbstractProcessor {
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
+ /**
+ * AutoService 娉ㄨВ澶勭悊鍣�
+ * @param annotations 娉ㄨВ getSupportedAnnotationTypes
+ * @param roundEnv 鎵弿鍒扮殑 娉ㄨВ鏂�
+ * @return 鏄惁瀹屾垚
+ */
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ try {
+ return processImpl(annotations, roundEnv);
+ } catch (Exception e) {
+ fatalError(e);
+ return false;
+ }
+ }
+
+ protected abstract boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
+
+ protected void log(String msg) {
+ if (processingEnv.getOptions().containsKey("debug")) {
+ processingEnv.getMessager().printMessage(Kind.NOTE, msg);
+ }
+ }
+
+ protected void error(String msg, Element element, AnnotationMirror annotation) {
+ processingEnv.getMessager().printMessage(Kind.ERROR, msg, element, annotation);
+ }
+
+ protected void fatalError(Exception e) {
+ // We don't allow exceptions of any kind to propagate to the compiler
+ StringWriter writer = new StringWriter();
+ e.printStackTrace(new PrintWriter(writer));
+ fatalError(writer.toString());
+ }
+
+ protected void fatalError(String msg) {
+ processingEnv.getMessager().printMessage(Kind.ERROR, "FATAL ERROR: " + msg);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/BootAutoType.java b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/BootAutoType.java
new file mode 100644
index 0000000..86bdf07
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/BootAutoType.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.auto.common;
+
+import org.springblade.core.auto.annotation.*;
+
+/**
+ * 娉ㄨВ绫诲瀷
+ *
+ * @author L.cm
+ */
+public enum BootAutoType {
+ /**
+ * Component锛岀粍鍚堟敞瑙o紝娣诲姞鍒� spring.factories
+ */
+ COMPONENT("org.springframework.stereotype.Component", "org.springframework.boot.autoconfigure.EnableAutoConfiguration"),
+ /**
+ * ApplicationContextInitializer 娣诲姞鍒� spring.factories
+ */
+ CONTEXT_INITIALIZER(AutoContextInitializer.class.getName(), "org.springframework.context.ApplicationContextInitializer"),
+ /**
+ * ApplicationListener 娣诲姞鍒� spring.factories
+ */
+ LISTENER(AutoListener.class.getName(), "org.springframework.context.ApplicationListener"),
+ /**
+ * SpringApplicationRunListener 娣诲姞鍒� spring.factories
+ */
+ RUN_LISTENER(AutoRunListener.class.getName(), "org.springframework.boot.SpringApplicationRunListener"),
+ /**
+ * FailureAnalyzer 娣诲姞鍒� spring.factories
+ */
+ FAILURE_ANALYZER(AutoFailureAnalyzer.class.getName(), "org.springframework.boot.diagnostics.FailureAnalyzer"),
+ /**
+ * EnvironmentPostProcessor 娣诲姞鍒� spring.factories
+ */
+ ENV_POST_PROCESSOR(AutoEnvPostProcessor.class.getName(), "org.springframework.boot.env.EnvironmentPostProcessor");
+
+ private final String annotationName;
+ private final String configureKey;
+
+ BootAutoType(String annotationName, String configureKey) {
+ this.annotationName = annotationName;
+ this.configureKey = configureKey;
+ }
+
+ public final String getAnnotationName() {
+ return annotationName;
+ }
+
+ public final String getConfigureKey() {
+ return configureKey;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/MultiSetMap.java b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/MultiSetMap.java
new file mode 100644
index 0000000..6625c6b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/MultiSetMap.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.auto.common;
+
+import java.util.*;
+
+/**
+ * MultiSetMap
+ *
+ * @author L.cm
+ */
+public class MultiSetMap<K, V> {
+ private transient final Map<K, Set<V>> map;
+
+ public MultiSetMap() {
+ map = new HashMap<>();
+ }
+
+ private Set<V> createSet() {
+ return new HashSet<>();
+ }
+
+ /**
+ * put to MultiSetMap
+ *
+ * @param key 閿�
+ * @param value 鍊�
+ * @return boolean
+ */
+ public boolean put(K key, V value) {
+ Set<V> set = map.get(key);
+ if (set == null) {
+ set = createSet();
+ if (set.add(value)) {
+ map.put(key, set);
+ return true;
+ } else {
+ throw new AssertionError("New set violated the set spec");
+ }
+ } else if (set.add(value)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * 鏄惁鍖呭惈鏌愪釜key
+ *
+ * @param key key
+ * @return 缁撴灉
+ */
+ public boolean containsKey(K key) {
+ return map.containsKey(key);
+ }
+
+ /**
+ * 鏄惁鍖呭惈 value 涓殑鏌愪釜鍊�
+ *
+ * @param value value
+ * @return 鏄惁鍖呭惈
+ */
+ public boolean containsVal(V value) {
+ Collection<Set<V>> values = map.values();
+ return values.stream().anyMatch(vs -> vs.contains(value));
+ }
+
+ /**
+ * key 闆嗗悎
+ *
+ * @return keys
+ */
+ public Set<K> keySet() {
+ return map.keySet();
+ }
+
+ /**
+ * put list to MultiSetMap
+ *
+ * @param key 閿�
+ * @param set 鍊煎垪琛�
+ * @return boolean
+ */
+ public boolean putAll(K key, Set<V> set) {
+ if (set == null) {
+ return false;
+ } else {
+ map.put(key, set);
+ return true;
+ }
+ }
+
+ /**
+ * get List by key
+ *
+ * @param key 閿�
+ * @return List
+ */
+ public Set<V> get(K key) {
+ return map.get(key);
+ }
+
+ /**
+ * clear MultiSetMap
+ */
+ public void clear() {
+ map.clear();
+ }
+
+ /**
+ * isEmpty
+ *
+ * @return isEmpty
+ */
+ public boolean isEmpty() {
+ return map.isEmpty();
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/Sets.java b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/Sets.java
new file mode 100644
index 0000000..645667d
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/Sets.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.auto.common;
+
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 闆嗗悎 宸ュ叿绫�
+ *
+ * @author L.cm
+ */
+public class Sets {
+
+ /**
+ * 涓嶅彲鍙� 闆嗗悎
+ *
+ * @param es 瀵硅薄
+ * @param <E> 娉涘瀷
+ * @return 闆嗗悎
+ */
+ @SafeVarargs
+ public static <E> Set<E> ofImmutableSet(E... es) {
+ Objects.requireNonNull(es);
+ return Stream.of(es).collect(Collectors.toSet());
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/TypeHelper.java b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/TypeHelper.java
new file mode 100644
index 0000000..3c99b18
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/common/TypeHelper.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.auto.common;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.QualifiedNameable;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Type utilities.
+ *
+ * @author Stephane Nicoll
+ * @since 5.0
+ */
+public class TypeHelper {
+
+ private final ProcessingEnvironment env;
+
+ private final Types types;
+
+
+ public TypeHelper(ProcessingEnvironment env) {
+ this.env = env;
+ this.types = env.getTypeUtils();
+ }
+
+
+ public String getType(Element element) {
+ return getType(element != null ? element.asType() : null);
+ }
+
+ public String getType(AnnotationMirror annotation) {
+ return getType(annotation != null ? annotation.getAnnotationType() : null);
+ }
+
+ public String getType(TypeMirror type) {
+ if (type == null) {
+ return null;
+ }
+ if (type instanceof DeclaredType) {
+ DeclaredType declaredType = (DeclaredType) type;
+ Element enclosingElement = declaredType.asElement().getEnclosingElement();
+ if (enclosingElement != null && enclosingElement instanceof TypeElement) {
+ return getQualifiedName(enclosingElement) + "$" + declaredType.asElement().getSimpleName().toString();
+ } else {
+ return getQualifiedName(declaredType.asElement());
+ }
+ }
+ return type.toString();
+ }
+
+ private String getQualifiedName(Element element) {
+ if (element instanceof QualifiedNameable) {
+ return ((QualifiedNameable) element).getQualifiedName().toString();
+ }
+ return element.toString();
+ }
+
+ /**
+ * Return the super class of the specified聽{@link Element} or null if this
+ * {@code element} represents {@link Object}.
+ *
+ * @param element Element
+ * @return Element
+ */
+ public Element getSuperClass(Element element) {
+ List<? extends TypeMirror> superTypes = this.types.directSupertypes(element.asType());
+ if (superTypes.isEmpty()) {
+ // reached java.lang.Object
+ return null;
+ }
+ return this.types.asElement(superTypes.get(0));
+ }
+
+ /**
+ * Return the interfaces that are <strong>directly</strong> implemented by the
+ * specified {@link Element} or an empty list if this {@code element} does not
+ * implement any interface.
+ *
+ * @param element Element
+ * @return Element list
+ */
+ public List<Element> getDirectInterfaces(Element element) {
+ List<? extends TypeMirror> superTypes = this.types.directSupertypes(element.asType());
+ List<Element> directInterfaces = new ArrayList<>();
+ // index 0 is the super class
+ if (superTypes.size() > 1) {
+ for (int i = 1; i < superTypes.size(); i++) {
+ Element e = this.types.asElement(superTypes.get(i));
+ if (e != null) {
+ directInterfaces.add(e);
+ }
+ }
+ }
+ return directInterfaces;
+ }
+
+ public List<? extends AnnotationMirror> getAllAnnotationMirrors(Element e) {
+ return this.env.getElementUtils().getAllAnnotationMirrors(e);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/factories/AutoFactoriesProcessor.java b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/factories/AutoFactoriesProcessor.java
new file mode 100644
index 0000000..5c963e7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/factories/AutoFactoriesProcessor.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.auto.factories;
+
+import org.springblade.core.auto.annotation.AutoIgnore;
+import org.springblade.core.auto.common.AbstractBladeProcessor;
+import org.springblade.core.auto.common.BootAutoType;
+import org.springblade.core.auto.common.MultiSetMap;
+import org.springblade.core.auto.service.AutoService;
+
+import javax.annotation.processing.*;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+import javax.tools.FileObject;
+import javax.tools.StandardLocation;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * spring boot 鑷姩閰嶇疆澶勭悊鍣�
+ *
+ * @author L.cm
+ */
+@AutoService(Processor.class)
+@SupportedAnnotationTypes("*")
+@SupportedOptions("debug")
+public class AutoFactoriesProcessor extends AbstractBladeProcessor {
+ /**
+ * 澶勭悊鐨勬敞瑙� @FeignClient
+ */
+ private static final String FEIGN_CLIENT_ANNOTATION = "org.springframework.cloud.openfeign.FeignClient";
+ /**
+ * Feign 鑷姩閰嶇疆
+ */
+ private static final String FEIGN_AUTO_CONFIGURE_KEY = "org.springblade.core.cloud.feign.BladeFeignAutoConfiguration";
+ /**
+ * The location to look for factories.
+ * <p>Can be present in multiple JAR files.
+ */
+ private static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
+ /**
+ * devtools锛屾湁 Configuration 娉ㄨВ鐨� jar 涓�鑸渶瑕� devtools 閰嶇疆鏂囦欢
+ */
+ private static final String DEVTOOLS_RESOURCE_LOCATION = "META-INF/spring-devtools.properties";
+ /**
+ * 鏁版嵁鎵胯浇
+ */
+ private final MultiSetMap<String, String> factories = new MultiSetMap<>();
+ /**
+ * 鍏冪礌杈呭姪绫�
+ */
+ private Elements elementUtils;
+
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+ elementUtils = processingEnv.getElementUtils();
+ }
+
+ @Override
+ protected boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (roundEnv.processingOver()) {
+ generateFactoriesFiles();
+ } else {
+ processAnnotations(annotations, roundEnv);
+ }
+ return false;
+ }
+
+ private void processAnnotations(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ // 鏃ュ織 鎵撳嵃淇℃伅 gradle build --debug
+ log(annotations.toString());
+ Set<? extends Element> elementSet = roundEnv.getRootElements();
+ log("All Element set: " + elementSet.toString());
+
+ // 杩囨护 TypeElement
+ Set<TypeElement> typeElementSet = elementSet.stream()
+ .filter(this::isClassOrInterface)
+ .filter(e -> e instanceof TypeElement)
+ .map(e -> (TypeElement) e)
+ .collect(Collectors.toSet());
+ // 濡傛灉涓虹┖鐩存帴璺冲嚭
+ if (typeElementSet.isEmpty()) {
+ log("Annotations elementSet is isEmpty");
+ return;
+ }
+
+ for (TypeElement typeElement : typeElementSet) {
+ if (isAnnotation(elementUtils, typeElement, AutoIgnore.class.getName())) {
+ log("Found @AutoIgnore annotation锛宨gnore Element: " + typeElement.toString());
+ } else if (isAnnotation(elementUtils, typeElement, FEIGN_CLIENT_ANNOTATION)) {
+ log("Found @FeignClient Element: " + typeElement.toString());
+
+ ElementKind elementKind = typeElement.getKind();
+ // Feign Client 鍙鐞� 鎺ュ彛
+ if (ElementKind.INTERFACE != elementKind) {
+ fatalError("@FeignClient Element " + typeElement.toString() + " 涓嶆槸鎺ュ彛銆�");
+ continue;
+ }
+
+ String factoryName = typeElement.getQualifiedName().toString();
+ if (factories.containsVal(factoryName)) {
+ continue;
+ }
+
+ log("璇诲彇鍒版柊閰嶇疆 spring.factories factoryName锛�" + factoryName);
+ factories.put(FEIGN_AUTO_CONFIGURE_KEY, factoryName);
+ } else {
+ for (BootAutoType autoType : BootAutoType.values()) {
+ String annotation = autoType.getAnnotationName();
+ if (isAnnotation(elementUtils, typeElement, annotation)) {
+ log("Found @" + annotation + " Element: " + typeElement.toString());
+
+ String factoryName = typeElement.getQualifiedName().toString();
+ if (factories.containsVal(factoryName)) {
+ continue;
+ }
+
+ log("璇诲彇鍒版柊閰嶇疆 spring.factories factoryName锛�" + factoryName);
+ factories.put(autoType.getConfigureKey(), factoryName);
+ }
+ }
+ }
+ }
+ }
+
+ private void generateFactoriesFiles() {
+ if (factories.isEmpty()) {
+ return;
+ }
+ Filer filer = processingEnv.getFiler();
+ try {
+ // 1. spring.factories
+ FileObject factoriesFile = filer.createResource(StandardLocation.CLASS_OUTPUT, "", FACTORIES_RESOURCE_LOCATION);
+ FactoriesFiles.writeFactoriesFile(factories, factoriesFile.openOutputStream());
+ String classesPath = factoriesFile.toUri().toString().split("classes")[0];
+ Path projectPath = Paths.get(new URI(classesPath)).getParent();
+ // 2. devtools 閰嶇疆锛屽洜涓烘湁 @Configuration 娉ㄨВ鐨勯渶瑕� devtools
+ String projectName = projectPath.getFileName().toString();
+ FileObject devToolsFile = filer.createResource(StandardLocation.CLASS_OUTPUT, "", DEVTOOLS_RESOURCE_LOCATION);
+ FactoriesFiles.writeDevToolsFile(projectName, devToolsFile.openOutputStream());
+ } catch (IOException | URISyntaxException e) {
+ fatalError(e);
+ }
+ }
+
+ private boolean isClassOrInterface(Element e) {
+ ElementKind kind = e.getKind();
+ return kind == ElementKind.CLASS || kind == ElementKind.INTERFACE;
+ }
+
+ private boolean isAnnotation(Elements elementUtils, Element e, String annotationFullName) {
+ List<? extends AnnotationMirror> annotationList = elementUtils.getAllAnnotationMirrors(e);
+ for (AnnotationMirror annotation : annotationList) {
+ // 濡傛灉鏄浜庣殑娉ㄨВ
+ if (isAnnotation(annotationFullName, annotation)) {
+ return true;
+ }
+ // 澶勭悊缁勫悎娉ㄨВ
+ Element element = annotation.getAnnotationType().asElement();
+ // 濡傛灉鏄� java 鍏冩敞瑙o紝缁х画寰幆
+ if (element.toString().startsWith("java.lang")) {
+ continue;
+ }
+ // 閫掑綊澶勭悊 缁勫悎娉ㄨВ
+ if (isAnnotation(elementUtils, element, annotationFullName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isAnnotation(String annotationFullName, AnnotationMirror annotation) {
+ return annotationFullName.equals(annotation.getAnnotationType().toString());
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/factories/FactoriesFiles.java b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/factories/FactoriesFiles.java
new file mode 100644
index 0000000..d231ecc
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/factories/FactoriesFiles.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.auto.factories;
+
+import org.springblade.core.auto.common.MultiSetMap;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Set;
+import java.util.StringJoiner;
+
+/**
+ * spring boot 鑷姩鍖栭厤缃伐鍏风被
+ *
+ * @author L.cm
+ */
+class FactoriesFiles {
+ private static final Charset UTF_8 = StandardCharsets.UTF_8;
+
+ /**
+ * 鍐欏嚭 spring.factories 鏂囦欢
+ * @param factories factories 淇℃伅
+ * @param output 杈撳嚭娴�
+ * @throws IOException 寮傚父淇℃伅
+ */
+ static void writeFactoriesFile(MultiSetMap<String, String> factories,
+ OutputStream output) throws IOException {
+ BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, UTF_8));
+ Set<String> keySet = factories.keySet();
+ for (String key : keySet) {
+ Set<String> values = factories.get(key);
+ if (values == null || values.isEmpty()) {
+ continue;
+ }
+ writer.write(key);
+ writer.write("=\\\n ");
+ StringJoiner joiner = new StringJoiner(",\\\n ");
+ for (String value : values) {
+ joiner.add(value);
+ }
+ writer.write(joiner.toString());
+ writer.newLine();
+ }
+ writer.flush();
+ output.close();
+ }
+
+ /**
+ * 鍐欏嚭 spring-devtools.properties
+ * @param projectName 椤圭洰鍚�
+ * @param output 杈撳嚭娴�
+ * @throws IOException 寮傚父淇℃伅
+ */
+ static void writeDevToolsFile(String projectName,
+ OutputStream output) throws IOException {
+ BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, UTF_8));
+ String format = "restart.include.%s=/%s[\\\\w-]+\\.jar";
+ writer.write(String.format(format, projectName, projectName));
+ writer.flush();
+ output.close();
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/service/AutoService.java b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/service/AutoService.java
new file mode 100644
index 0000000..0240a5f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/service/AutoService.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.auto.service;
+
+import java.lang.annotation.*;
+
+/**
+ * An annotation for service providers as described in {@link java.util.ServiceLoader}. The {@link
+ * AutoServiceProcessor} generates the configuration files which
+ * allows service providers to be loaded with {@link java.util.ServiceLoader#load(Class)}.
+ *
+ * <p>Service providers assert that they conform to the service provider specification.
+ * Specifically, they must:
+ *
+ * <ul>
+ * <li>be a non-inner, non-anonymous, concrete class
+ * <li>have a publicly accessible no-arg constructor
+ * <li>implement the interface type returned by {@code value()}
+ * </ul>
+ *
+ * @author google
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.TYPE)
+public @interface AutoService {
+ /**
+ * Returns the interfaces implemented by this service provider.
+ *
+ * @return interface array
+ */
+ Class<?>[] value();
+}
diff --git a/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/service/AutoServiceProcessor.java b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/service/AutoServiceProcessor.java
new file mode 100644
index 0000000..16fd83f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/service/AutoServiceProcessor.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.auto.service;
+
+import org.springblade.core.auto.common.AbstractBladeProcessor;
+import org.springblade.core.auto.common.MultiSetMap;
+import org.springblade.core.auto.common.Sets;
+import org.springblade.core.auto.common.TypeHelper;
+
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedOptions;
+import javax.lang.model.element.*;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleAnnotationValueVisitor8;
+import javax.lang.model.util.Types;
+import javax.tools.FileObject;
+import javax.tools.StandardLocation;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * java spi 鏈嶅姟鑷姩澶勭悊鍣� 鍙傝�冿細google auto
+ *
+ * @author L.cm
+ */
+@SupportedOptions("debug")
+public class AutoServiceProcessor extends AbstractBladeProcessor {
+ /**
+ * spi 鏈嶅姟闆嗗悎锛宬ey 鎺ュ彛 -> value 瀹炵幇鍒楄〃
+ */
+ private final MultiSetMap<String, String> providers = new MultiSetMap<>();
+ private TypeHelper typeHelper;
+
+ @Override
+ public synchronized void init(ProcessingEnvironment env) {
+ super.init(env);
+ this.typeHelper = new TypeHelper(env);
+ }
+
+ @Override
+ public Set<String> getSupportedAnnotationTypes() {
+ return Sets.ofImmutableSet(AutoService.class.getName());
+ }
+
+ @Override
+ protected boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (roundEnv.processingOver()) {
+ generateConfigFiles();
+ } else {
+ processAnnotations(annotations, roundEnv);
+ }
+ return true;
+ }
+
+ private void processAnnotations(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);
+
+ log(annotations.toString());
+ log(elements.toString());
+
+ for (Element e : elements) {
+ TypeElement providerImplementer = (TypeElement) e;
+ AnnotationMirror annotationMirror = getAnnotationMirror(e, AutoService.class);
+ if (annotationMirror == null) {
+ continue;
+ }
+ Set<TypeMirror> typeMirrors = getValueFieldOfClasses(annotationMirror);
+ if (typeMirrors.isEmpty()) {
+ error("No service interfaces provided for element!", e, annotationMirror);
+ continue;
+ }
+ for (TypeMirror typeMirror : typeMirrors) {
+ String providerInterfaceName = typeHelper.getType(typeMirror);
+ Name providerImplementerName = providerImplementer.getQualifiedName();
+
+ log("provider interface: " + providerInterfaceName);
+ log("provider implementer: " + providerImplementerName);
+
+ if (checkImplementer(providerImplementer, typeMirror)) {
+ providers.put(providerInterfaceName, typeHelper.getType(providerImplementer));
+ } else {
+ String message = "ServiceProviders must implement their service provider interface. "
+ + providerImplementerName + " does not implement " + providerInterfaceName;
+ error(message, e, annotationMirror);
+ }
+ }
+ }
+ }
+
+ private void generateConfigFiles() {
+ Filer filer = processingEnv.getFiler();
+
+ for (String providerInterface : providers.keySet()) {
+ String resourceFile = "META-INF/services/" + providerInterface;
+ log("Working on resource file: " + resourceFile);
+ try {
+ SortedSet<String> allServices = new TreeSet<>();
+ try {
+ FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
+ log("Looking for existing resource file at " + existingFile.toUri());
+ Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
+ log("Existing service entries: " + oldServices);
+ allServices.addAll(oldServices);
+ } catch (IOException e) {
+ log("Resource file did not already exist.");
+ }
+
+ Set<String> newServices = new HashSet<>(providers.get(providerInterface));
+ if (allServices.containsAll(newServices)) {
+ log("No new service entries being added.");
+ return;
+ }
+
+ allServices.addAll(newServices);
+ log("New service file contents: " + allServices);
+ FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
+ OutputStream out = fileObject.openOutputStream();
+ ServicesFiles.writeServiceFile(allServices, out);
+ out.close();
+ log("Wrote to: " + fileObject.toUri());
+ } catch (IOException e) {
+ fatalError("Unable to create " + resourceFile + ", " + e);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Verifies {@link java.util.spi.LocaleServiceProvider} constraints on the concrete provider class.
+ * Note that these constraints are enforced at runtime via the ServiceLoader,
+ * we're just checking them at compile time to be extra nice to our users.
+ */
+ private boolean checkImplementer(TypeElement providerImplementer, TypeMirror providerType) {
+ // TODO: We're currently only enforcing the subtype relationship
+ // constraint. It would be nice to enforce them all.
+ Types types = processingEnv.getTypeUtils();
+
+ return types.isSubtype(providerImplementer.asType(), providerType);
+ }
+
+ /**
+ * 璇诲彇 AutoService 涓婄殑 value 鍊�
+ *
+ * @param annotationMirror AnnotationMirror
+ * @return value 闆嗗悎
+ */
+ private Set<TypeMirror> getValueFieldOfClasses(AnnotationMirror annotationMirror) {
+ return getAnnotationValue(annotationMirror, "value")
+ .accept(new SimpleAnnotationValueVisitor8<Set<TypeMirror>, Void>() {
+ @Override
+ public Set<TypeMirror> visitType(TypeMirror typeMirror, Void v) {
+ Set<TypeMirror> declaredTypeSet = new HashSet<>(1);
+ declaredTypeSet.add(typeMirror);
+ return Collections.unmodifiableSet(declaredTypeSet);
+ }
+
+ @Override
+ public Set<TypeMirror> visitArray(
+ List<? extends AnnotationValue> values, Void v) {
+ return values
+ .stream()
+ .flatMap(value -> value.accept(this, null).stream())
+ .collect(Collectors.toSet());
+ }
+ }, null);
+ }
+
+ /**
+ * Returns a {@link ExecutableElement} and its associated {@link AnnotationValue} if such
+ * an element was either declared in the usage represented by the provided
+ * {@link AnnotationMirror}, or if such an element was defined with a default.
+ *
+ * @param annotationMirror AnnotationMirror
+ * @param elementName elementName
+ * @return AnnotationValue map
+ * @throws IllegalArgumentException if no element is defined with the given elementName.
+ */
+ public AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String elementName) {
+ Objects.requireNonNull(annotationMirror);
+ Objects.requireNonNull(elementName);
+ for (Map.Entry<ExecutableElement, AnnotationValue> entry : getAnnotationValuesWithDefaults(annotationMirror).entrySet()) {
+ if (entry.getKey().getSimpleName().contentEquals(elementName)) {
+ return entry.getValue();
+ }
+ }
+ String name = typeHelper.getType(annotationMirror);
+ throw new IllegalArgumentException(String.format("@%s does not define an element %s()", name, elementName));
+ }
+
+ /**
+ * Returns the {@link AnnotationMirror}'s map of {@link AnnotationValue} indexed by {@link
+ * ExecutableElement}, supplying default values from the annotation if the annotation property has
+ * not been set. This is equivalent to {@link
+ * Elements#getElementValuesWithDefaults(AnnotationMirror)} but can be called statically without
+ * an {@link Elements} instance.
+ *
+ * <p>The iteration order of elements of the returned map will be the order in which the {@link
+ * ExecutableElement}s are defined in {@code annotation}'s {@linkplain
+ * AnnotationMirror#getAnnotationType() type}.
+ *
+ * @param annotation AnnotationMirror
+ * @return AnnotationValue Map
+ */
+ public Map<ExecutableElement, AnnotationValue> getAnnotationValuesWithDefaults(AnnotationMirror annotation) {
+ Map<ExecutableElement, AnnotationValue> values = new HashMap<>(32);
+ Map<? extends ExecutableElement, ? extends AnnotationValue> declaredValues = annotation.getElementValues();
+ for (ExecutableElement method : ElementFilter.methodsIn(annotation.getAnnotationType().asElement().getEnclosedElements())) {
+ // Must iterate and put in this order, to ensure consistency in generated code.
+ if (declaredValues.containsKey(method)) {
+ values.put(method, declaredValues.get(method));
+ } else if (method.getDefaultValue() != null) {
+ values.put(method, method.getDefaultValue());
+ } else {
+ String name = typeHelper.getType(method);
+ throw new IllegalStateException(
+ "Unset annotation value without default should never happen: " + name + '.' + method.getSimpleName() + "()");
+ }
+ }
+ return Collections.unmodifiableMap(values);
+ }
+
+ public AnnotationMirror getAnnotationMirror(Element element, Class<? extends Annotation> annotationClass) {
+ String annotationClassName = annotationClass.getCanonicalName();
+ for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
+ String name = typeHelper.getType(annotationMirror);
+ if (name.contentEquals(annotationClassName)) {
+ return annotationMirror;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/service/ServicesFiles.java b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/service/ServicesFiles.java
new file mode 100644
index 0000000..4cd13f7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/src/main/java/org/springblade/core/auto/service/ServicesFiles.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.auto.service;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A helper class for reading and writing Services files.
+ *
+ * @author L.cm
+ */
+class ServicesFiles {
+ private static final Charset UTF_8 = StandardCharsets.UTF_8;
+
+ /**
+ * Reads the set of service classes from a service file.
+ *
+ * @param input not {@code null}. Closed after use.
+ * @return a not {@code null Set} of service class names.
+ * @throws IOException
+ */
+ static Set<String> readServiceFile(InputStream input) throws IOException {
+ HashSet<String> serviceClasses = new HashSet<>();
+ try (
+ InputStreamReader isr = new InputStreamReader(input, UTF_8);
+ BufferedReader r = new BufferedReader(isr)
+ ) {
+ String line;
+ while ((line = r.readLine()) != null) {
+ int commentStart = line.indexOf('#');
+ if (commentStart >= 0) {
+ line = line.substring(0, commentStart);
+ }
+ line = line.trim();
+ if (!line.isEmpty()) {
+ serviceClasses.add(line);
+ }
+ }
+ return serviceClasses;
+ }
+ }
+
+ /**
+ * Writes the set of service class names to a service file.
+ *
+ * @param output not {@code null}. Not closed after use.
+ * @param services a not {@code null Collection} of service class names.
+ * @throws IOException
+ */
+ static void writeServiceFile(Collection<String> services, OutputStream output) throws IOException {
+ BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, UTF_8));
+ for (String service : services) {
+ writer.write(service);
+ writer.newLine();
+ }
+ writer.flush();
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-auto/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/Source/BladeX-Tool/blade-core-auto/src/main/resources/META-INF/services/javax.annotation.processing.Processor
new file mode 100644
index 0000000..170d3ea
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-auto/src/main/resources/META-INF/services/javax.annotation.processing.Processor
@@ -0,0 +1,2 @@
+org.springblade.core.auto.service.AutoServiceProcessor
+org.springblade.core.auto.factories.AutoFactoriesProcessor
diff --git a/Source/BladeX-Tool/blade-core-boot/pom.xml b/Source/BladeX-Tool/blade-core-boot/pom.xml
new file mode 100644
index 0000000..5c9400c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/pom.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.springblade</groupId>
+ <artifactId>BladeX-Tool</artifactId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-core-boot</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!-- Blade -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-context</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-db</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-secure</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-cloud</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-cache</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-redis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-log</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-mybatis</artifactId>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/BladeBootAutoConfiguration.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/BladeBootAutoConfiguration.java
new file mode 100644
index 0000000..35c2072
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/BladeBootAutoConfiguration.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.boot.config;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.launch.props.BladePropertySource;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+
+/**
+ * blade鑷姩閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@Slf4j
+@AutoConfiguration
+@AllArgsConstructor
+@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
+@BladePropertySource(value = "classpath:/blade-boot.yml")
+public class BladeBootAutoConfiguration {
+
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/BladeExecutorConfiguration.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/BladeExecutorConfiguration.java
new file mode 100644
index 0000000..2a6ada7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/BladeExecutorConfiguration.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.boot.config;
+
+import lombok.AllArgsConstructor;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.boot.error.ErrorType;
+import org.springblade.core.boot.error.ErrorUtil;
+import org.springblade.core.context.BladeContext;
+import org.springblade.core.context.BladeRunnableWrapper;
+import org.springblade.core.launch.props.BladeProperties;
+import org.springblade.core.log.constant.EventConstant;
+import org.springblade.core.log.event.ErrorLogEvent;
+import org.springblade.core.log.model.LogError;
+import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
+import org.springframework.boot.task.TaskExecutorCustomizer;
+import org.springframework.boot.task.TaskSchedulerCustomizer;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.lang.NonNull;
+import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.util.ErrorHandler;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 寮傛澶勭悊
+ *
+ * @author Chill
+ */
+@Slf4j
+@Configuration
+@EnableAsync
+@EnableScheduling
+@AllArgsConstructor
+public class BladeExecutorConfiguration extends AsyncConfigurerSupport {
+
+ private final BladeContext bladeContext;
+ private final BladeProperties bladeProperties;
+ private final ApplicationEventPublisher publisher;
+
+ @Bean
+ public TaskExecutorCustomizer taskExecutorCustomizer() {
+ return taskExecutor -> {
+ taskExecutor.setThreadNamePrefix("async-task-");
+ taskExecutor.setTaskDecorator(BladeRunnableWrapper::new);
+ taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+ };
+ }
+
+ @Bean
+ public TaskSchedulerCustomizer taskSchedulerCustomizer() {
+ return taskExecutor -> {
+ taskExecutor.setThreadNamePrefix("async-scheduler");
+ taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+ taskExecutor.setErrorHandler(new BladeErrorHandler(bladeContext, bladeProperties, publisher));
+ };
+ }
+
+ @Override
+ public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
+ return new BladeAsyncUncaughtExceptionHandler(bladeContext, bladeProperties, publisher);
+ }
+
+ @RequiredArgsConstructor
+ private static class BladeAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
+ private final BladeContext bladeContext;
+ private final BladeProperties bladeProperties;
+ private final ApplicationEventPublisher eventPublisher;
+
+ @Override
+ public void handleUncaughtException(@NonNull Throwable error, @NonNull Method method, @NonNull Object... params) {
+ log.error("Unexpected exception occurred invoking async method: {}", method, error);
+ LogError logError = new LogError();
+ // 鏈嶅姟淇℃伅銆佺幆澧冦�佸紓甯哥被鍨�
+ logError.setParams(ErrorType.ASYNC.getType());
+ logError.setEnv(bladeProperties.getEnv());
+ logError.setServiceId(bladeProperties.getName());
+ logError.setRequestUri(bladeContext.getRequestId());
+ // 鍫嗘爤淇℃伅
+ ErrorUtil.initErrorInfo(error, logError);
+ Map<String, Object> event = new HashMap<>(16);
+ event.put(EventConstant.EVENT_LOG, logError);
+ eventPublisher.publishEvent(new ErrorLogEvent(event));
+ }
+ }
+
+ @RequiredArgsConstructor
+ private static class BladeErrorHandler implements ErrorHandler {
+ private final BladeContext bladeContext;
+ private final BladeProperties bladeProperties;
+ private final ApplicationEventPublisher eventPublisher;
+
+ @Override
+ public void handleError(@NonNull Throwable error) {
+ log.error("Unexpected scheduler exception", error);
+ LogError logError = new LogError();
+ // 鏈嶅姟淇℃伅銆佺幆澧冦�佸紓甯哥被鍨�
+ logError.setParams(ErrorType.SCHEDULER.getType());
+ logError.setServiceId(bladeProperties.getName());
+ logError.setEnv(bladeProperties.getEnv());
+ logError.setRequestUri(bladeContext.getRequestId());
+ // 鍫嗘爤淇℃伅
+ ErrorUtil.initErrorInfo(error, logError);
+ Map<String, Object> event = new HashMap<>(16);
+ event.put(EventConstant.EVENT_LOG, logError);
+ eventPublisher.publishEvent(new ErrorLogEvent(event));
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/BladeRetryConfiguration.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/BladeRetryConfiguration.java
new file mode 100644
index 0000000..c3935a7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/BladeRetryConfiguration.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.boot.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.retry.interceptor.RetryInterceptorBuilder;
+import org.springframework.retry.interceptor.RetryOperationsInterceptor;
+
+/**
+ * 閲嶈瘯鏈哄埗
+ *
+ * @author Chill
+ */
+@Slf4j
+@AutoConfiguration
+public class BladeRetryConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean(name = "configServerRetryInterceptor")
+ public RetryOperationsInterceptor configServerRetryInterceptor() {
+ log.info(String.format(
+ "configServerRetryInterceptor: Changing backOffOptions " +
+ "to initial: %s, multiplier: %s, maxInterval: %s",
+ 1000, 1.2, 5000));
+ return RetryInterceptorBuilder
+ .stateless()
+ .backOffOptions(1000, 1.2, 5000)
+ .maxAttempts(10)
+ .build();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/BladeWebMvcConfiguration.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/BladeWebMvcConfiguration.java
new file mode 100644
index 0000000..78f7323
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/BladeWebMvcConfiguration.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.boot.config;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.boot.props.BladeFileProperties;
+import org.springblade.core.boot.props.BladeUploadProperties;
+import org.springblade.core.boot.resolver.TokenArgumentResolver;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.util.List;
+
+/**
+ * WEB閰嶇疆
+ *
+ * @author Chill
+ */
+@Slf4j
+@AutoConfiguration
+@Order(Ordered.HIGHEST_PRECEDENCE)
+@AllArgsConstructor
+@EnableConfigurationProperties({
+ BladeUploadProperties.class, BladeFileProperties.class
+})
+public class BladeWebMvcConfiguration implements WebMvcConfigurer {
+
+ private final BladeUploadProperties bladeUploadProperties;
+
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ String path = bladeUploadProperties.getSavePath();
+ registry.addResourceHandler("/upload/**")
+ .addResourceLocations("file:" + path + "/upload/");
+ }
+
+ @Override
+ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
+ argumentResolvers.add(new TokenArgumentResolver());
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/RequestConfiguration.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/RequestConfiguration.java
new file mode 100644
index 0000000..460d1bb
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/config/RequestConfiguration.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.boot.config;
+
+import lombok.AllArgsConstructor;
+import org.springblade.core.boot.request.BladeRequestFilter;
+import org.springblade.core.boot.request.RequestProperties;
+import org.springblade.core.boot.request.XssProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.Ordered;
+
+import javax.servlet.DispatcherType;
+
+/**
+ * 杩囨护鍣ㄩ厤缃被
+ *
+ * @author Chill
+ */
+@AutoConfiguration
+@AllArgsConstructor
+@EnableConfigurationProperties({RequestProperties.class, XssProperties.class})
+public class RequestConfiguration {
+
+ private final RequestProperties requestProperties;
+ private final XssProperties xssProperties;
+
+ /**
+ * 鍏ㄥ眬杩囨护鍣�
+ */
+ @Bean
+ public FilterRegistrationBean<BladeRequestFilter> bladeFilterRegistration() {
+ FilterRegistrationBean<BladeRequestFilter> registration = new FilterRegistrationBean<>();
+ registration.setDispatcherTypes(DispatcherType.REQUEST);
+ registration.setFilter(new BladeRequestFilter(requestProperties, xssProperties));
+ registration.addUrlPatterns("/*");
+ registration.setName("bladeRequestFilter");
+ registration.setOrder(Ordered.LOWEST_PRECEDENCE);
+ return registration;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/ctrl/BladeController.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/ctrl/BladeController.java
new file mode 100644
index 0000000..68991aa
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/ctrl/BladeController.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.boot.ctrl;
+
+import org.springblade.core.boot.file.LocalFile;
+import org.springblade.core.boot.file.BladeFileUtil;
+import org.springblade.core.secure.BladeUser;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.Charsets;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.WebUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.ResourceRegion;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.util.UriUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Blade鎺у埗鍣ㄥ皝瑁呯被
+ *
+ * @author Chill
+ */
+public class BladeController {
+
+ /**
+ * ============================ REQUEST =================================================
+ */
+
+ @Autowired
+ private HttpServletRequest request;
+
+ /**
+ * 鑾峰彇request
+ */
+ public HttpServletRequest getRequest() {
+ return this.request;
+ }
+
+ /**
+ * 鑾峰彇褰撳墠鐢ㄦ埛
+ *
+ * @return
+ */
+ public BladeUser getUser() {
+ return AuthUtil.getUser();
+ }
+
+ /** ============================ API_RESULT ================================================= */
+
+ /**
+ * 杩斿洖ApiResult
+ *
+ * @param data
+ * @return R
+ */
+ public <T> R<T> data(T data) {
+ return R.data(data);
+ }
+
+ /**
+ * 杩斿洖ApiResult
+ *
+ * @param data
+ * @param message
+ * @return R
+ */
+ public <T> R<T> data(T data, String message) {
+ return R.data(data, message);
+ }
+
+ /**
+ * 杩斿洖ApiResult
+ *
+ * @param data
+ * @param message
+ * @param code
+ * @return R
+ */
+ public <T> R<T> data(T data, String message, int code) {
+ return R.data(code, data, message);
+ }
+
+ /**
+ * 杩斿洖ApiResult
+ *
+ * @param message
+ * @return R
+ */
+ public R success(String message) {
+ return R.success(message);
+ }
+
+ /**
+ * 杩斿洖ApiResult
+ *
+ * @param message
+ * @return R
+ */
+ public R fail(String message) {
+ return R.fail(message);
+ }
+
+ /**
+ * 杩斿洖ApiResult
+ *
+ * @param flag
+ * @return R
+ */
+ public R status(boolean flag) {
+ return R.status(flag);
+ }
+
+
+ /**============================ FILE ================================================= */
+
+ /**
+ * 鑾峰彇BladeFile灏佽绫�
+ *
+ * @param file
+ * @return
+ */
+ public LocalFile getFile(MultipartFile file) {
+ return BladeFileUtil.getFile(file);
+ }
+
+ /**
+ * 鑾峰彇BladeFile灏佽绫�
+ *
+ * @param file
+ * @param dir
+ * @return
+ */
+ public LocalFile getFile(MultipartFile file, String dir) {
+ return BladeFileUtil.getFile(file, dir);
+ }
+
+ /**
+ * 鑾峰彇BladeFile灏佽绫�
+ *
+ * @param file
+ * @param dir
+ * @param path
+ * @param virtualPath
+ * @return
+ */
+ public LocalFile getFile(MultipartFile file, String dir, String path, String virtualPath) {
+ return BladeFileUtil.getFile(file, dir, path, virtualPath);
+ }
+
+ /**
+ * 鑾峰彇BladeFile灏佽绫�
+ *
+ * @param files
+ * @return
+ */
+ public List<LocalFile> getFiles(List<MultipartFile> files) {
+ return BladeFileUtil.getFiles(files);
+ }
+
+ /**
+ * 鑾峰彇BladeFile灏佽绫�
+ *
+ * @param files
+ * @param dir
+ * @return
+ */
+ public List<LocalFile> getFiles(List<MultipartFile> files, String dir) {
+ return BladeFileUtil.getFiles(files, dir);
+ }
+
+ /**
+ * 鑾峰彇BladeFile灏佽绫�
+ *
+ * @param files
+ * @param path
+ * @param virtualPath
+ * @return
+ */
+ public List<LocalFile> getFiles(List<MultipartFile> files, String dir, String path, String virtualPath) {
+ return BladeFileUtil.getFiles(files, dir, path, virtualPath);
+ }
+ /**
+ * 涓嬭浇鏂囦欢
+ *
+ * @param file 鏂囦欢
+ * @return {ResponseEntity}
+ * @throws IOException io寮傚父
+ */
+ protected ResponseEntity<ResourceRegion> download(File file) throws IOException {
+ String fileName = file.getName();
+ return download(file, fileName);
+ }
+
+ /**
+ * 涓嬭浇
+ *
+ * @param file 鏂囦欢
+ * @param fileName 鐢熸垚鐨勬枃浠跺悕
+ * @return {ResponseEntity}
+ * @throws IOException io寮傚父
+ */
+ protected ResponseEntity<ResourceRegion> download(File file, String fileName) throws IOException {
+ Resource resource = new FileSystemResource(file);
+ return download(resource, fileName);
+ }
+
+ /**
+ * 涓嬭浇
+ *
+ * @param resource 璧勬簮
+ * @param fileName 鐢熸垚鐨勬枃浠跺悕
+ * @return {ResponseEntity}
+ * @throws IOException io寮傚父
+ */
+ protected ResponseEntity<ResourceRegion> download(Resource resource, String fileName) throws IOException {
+ HttpServletRequest request = WebUtil.getRequest();
+ String header = request.getHeader(HttpHeaders.USER_AGENT);
+ // 閬垮厤绌烘寚閽�
+ header = header == null ? StringPool.EMPTY : header.toUpperCase();
+ HttpStatus status;
+ String msie= "MSIE";
+ String trident= "TRIDENT";
+ String edge= "EDGE";
+ if (header.contains(msie) || header.contains(trident) || header.contains(edge)) {
+ status = HttpStatus.OK;
+ } else {
+ status = HttpStatus.CREATED;
+ }
+ // 鏂偣缁紶
+ long position = 0;
+ long count = resource.contentLength();
+ String range = request.getHeader(HttpHeaders.RANGE);
+ if (null != range) {
+ status = HttpStatus.PARTIAL_CONTENT;
+ String[] rangeRange = range.replace("bytes=", StringPool.EMPTY).split(StringPool.DASH);
+ position = Long.parseLong(rangeRange[0]);
+ if (rangeRange.length > 1) {
+ long end = Long.parseLong(rangeRange[1]);
+ count = end - position + 1;
+ }
+ }
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
+ String encodeFileName = UriUtils.encode(fileName, Charsets.UTF_8);
+ // 鍏煎鍚勭娴忚鍣ㄤ笅杞斤細
+ // https://blog.robotshell.org/2012/deal-with-http-header-encoding-for-file-download/
+ String disposition = "attachment;" +
+ "filename=\"" + encodeFileName + "\";" +
+ "filename*=utf-8''" + encodeFileName;
+ headers.set(HttpHeaders.CONTENT_DISPOSITION, disposition);
+ return new ResponseEntity<>(new ResourceRegion(resource, position, count), headers, status);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/error/ErrorType.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/error/ErrorType.java
new file mode 100644
index 0000000..c7b4b80
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/error/ErrorType.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.boot.error;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.lang.Nullable;
+
+/**
+ * 寮傚父绫诲瀷
+ *
+ * @author L.cm
+ */
+@Getter
+@RequiredArgsConstructor
+public enum ErrorType {
+ /**
+ * 寮傚父绫诲瀷
+ */
+ REQUEST("request"),
+ ASYNC("async"),
+ SCHEDULER("scheduler"),
+ WEB_SOCKET("websocket"),
+ OTHER("other");
+
+ @JsonValue
+ private final String type;
+
+ @Nullable
+ @JsonCreator
+ public static ErrorType of(String type) {
+ ErrorType[] values = ErrorType.values();
+ for (ErrorType errorType : values) {
+ if (errorType.type.equals(type)) {
+ return errorType;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/error/ErrorUtil.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/error/ErrorUtil.java
new file mode 100644
index 0000000..d24bfe2
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/error/ErrorUtil.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.boot.error;
+
+import org.springblade.core.log.model.LogError;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.core.tool.utils.Exceptions;
+import org.springblade.core.tool.utils.ObjectUtil;
+
+/**
+ * 寮傚父宸ュ叿绫�
+ *
+ * @author L.cm
+ */
+public class ErrorUtil {
+
+ /**
+ * 鍒濆鍖栧紓甯镐俊鎭�
+ *
+ * @param error 寮傚父
+ * @param event 寮傚父浜嬩欢灏佽
+ */
+ public static void initErrorInfo(Throwable error, LogError event) {
+ // 鍫嗘爤淇℃伅
+ event.setStackTrace(Exceptions.getStackTraceAsString(error));
+ event.setExceptionName(error.getClass().getName());
+ event.setMessage(error.getMessage());
+ event.setCreateTime(DateUtil.now());
+ StackTraceElement[] elements = error.getStackTrace();
+ if (ObjectUtil.isNotEmpty(elements)) {
+ // 鎶ラ敊鐨勭被淇℃伅
+ StackTraceElement element = elements[0];
+ event.setMethodClass(element.getClassName());
+ event.setFileName(element.getFileName());
+ event.setMethodName(element.getMethodName());
+ event.setLineNumber(element.getLineNumber());
+ }
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/BladeFileUtil.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/BladeFileUtil.java
new file mode 100644
index 0000000..beae556
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/BladeFileUtil.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.boot.file;
+
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.*;
+
+/**
+ * 鏂囦欢宸ュ叿绫�
+ *
+ * @author Chill
+ */
+public class BladeFileUtil {
+
+ /**
+ * 瀹氫箟鍏佽涓婁紶鐨勬枃浠舵墿灞曞悕
+ */
+ private static final HashMap<String, String> EXT_MAP = new HashMap<>();
+ private static final String IS_DIR = "is_dir";
+ private static final String FILE_NAME = "filename";
+ private static final String FILE_SIZE = "filesize";
+
+ /**
+ * 鍥剧墖鎵╁睍鍚�
+ */
+ private static final String[] FILE_TYPES = new String[]{"gif", "jpg", "jpeg", "png", "bmp"};
+
+ static {
+ EXT_MAP.put("image", ".gif,.jpg,.jpeg,.png,.bmp,.JPG,.JPEG,.PNG");
+ EXT_MAP.put("flash", ".swf,.flv");
+ EXT_MAP.put("media", ".swf,.flv,.mp3,.mp4,.wav,.wma,.wmv,.mid,.avi,.mpg,.asf,.rm,.rmvb");
+ EXT_MAP.put("file", ".doc,.docx,.xls,.xlsx,.ppt,.htm,.html,.txt,.zip,.rar,.gz,.bz2");
+ EXT_MAP.put("allfile", ".gif,.jpg,.jpeg,.png,.bmp,.swf,.flv,.mp3,.mp4,.wav,.wma,.wmv,.mid,.avi,.mpg,.asf,.rm,.rmvb,.doc,.docx,.xls,.xlsx,.ppt,.htm,.html,.txt,.zip,.rar,.gz,.bz2");
+ }
+
+ /**
+ * 鑾峰彇鏂囦欢鍚庣紑
+ *
+ * @param fileName 鏂囦欢鍚�
+ * @return String 杩斿洖鍚庣紑
+ */
+ public static String getFileExt(String fileName) {
+ return fileName.substring(fileName.lastIndexOf(StringPool.DOT));
+ }
+
+ /**
+ * 娴嬭瘯鏂囦欢鍚庣紑 鍙鎸囧畾鍚庣紑鐨勬枃浠朵笂浼狅紝鍍廽sp,war,sh绛夊嵄闄╃殑鍚庣紑绂佹
+ *
+ * @param dir 鐩綍
+ * @param fileName 鏂囦欢鍚�
+ * @return 杩斿洖鎴愬姛涓庡惁
+ */
+ public static boolean testExt(String dir, String fileName) {
+ String fileExt = getFileExt(fileName);
+ String ext = EXT_MAP.get(dir);
+ return StringUtil.isNotBlank(ext) && (ext.contains(fileExt.toLowerCase()) || ext.contains(fileExt.toUpperCase()));
+ }
+
+ /**
+ * 鏂囦欢绠$悊鎺掑簭
+ */
+ public enum FileSort {
+
+ /**
+ * 澶у皬
+ */
+ size,
+
+ /**
+ * 绫诲瀷
+ */
+ type,
+
+ /**
+ * 鍚嶇О
+ */
+ name;
+
+ /**
+ * 鏂囨湰鎺掑簭杞崲鎴愭灇涓�
+ *
+ * @param sort
+ * @return
+ */
+ public static FileSort of(String sort) {
+ try {
+ return FileSort.valueOf(sort);
+ } catch (Exception e) {
+ return FileSort.name;
+ }
+ }
+ }
+
+ public static class NameComparator implements Comparator {
+ @Override
+ public int compare(Object a, Object b) {
+ Hashtable hashA = (Hashtable) a;
+ Hashtable hashB = (Hashtable) b;
+ if (((Boolean) hashA.get(IS_DIR)) && !((Boolean) hashB.get(IS_DIR))) {
+ return -1;
+ } else if (!((Boolean) hashA.get(IS_DIR)) && ((Boolean) hashB.get(IS_DIR))) {
+ return 1;
+ } else {
+ return ((String) hashA.get(FILE_NAME)).compareTo((String) hashB.get(FILE_NAME));
+ }
+ }
+ }
+
+ public static class SizeComparator implements Comparator {
+ @Override
+ public int compare(Object a, Object b) {
+ Hashtable hashA = (Hashtable) a;
+ Hashtable hashB = (Hashtable) b;
+ if (((Boolean) hashA.get(IS_DIR)) && !((Boolean) hashB.get(IS_DIR))) {
+ return -1;
+ } else if (!((Boolean) hashA.get(IS_DIR)) && ((Boolean) hashB.get(IS_DIR))) {
+ return 1;
+ } else {
+ if (((Long) hashA.get(FILE_SIZE)) > ((Long) hashB.get(FILE_SIZE))) {
+ return 1;
+ } else if (((Long) hashA.get(FILE_SIZE)) < ((Long) hashB.get(FILE_SIZE))) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+ }
+ }
+
+ public static class TypeComparator implements Comparator {
+ @Override
+ public int compare(Object a, Object b) {
+ Hashtable hashA = (Hashtable) a;
+ Hashtable hashB = (Hashtable) b;
+ if (((Boolean) hashA.get(IS_DIR)) && !((Boolean) hashB.get(IS_DIR))) {
+ return -1;
+ } else if (!((Boolean) hashA.get(IS_DIR)) && ((Boolean) hashB.get(IS_DIR))) {
+ return 1;
+ } else {
+ return ((String) hashA.get("filetype")).compareTo((String) hashB.get("filetype"));
+ }
+ }
+ }
+
+ public static String formatUrl(String url) {
+ return url.replaceAll("\\\\", "/");
+ }
+
+
+ /********************************BladeFile灏佽********************************************************/
+
+ /**
+ * 鑾峰彇BladeFile灏佽绫�
+ *
+ * @param file 鏂囦欢
+ * @return BladeFile
+ */
+ public static LocalFile getFile(MultipartFile file) {
+ return getFile(file, "image", null, null);
+ }
+
+ /**
+ * 鑾峰彇BladeFile灏佽绫�
+ *
+ * @param file 鏂囦欢
+ * @param dir 鐩綍
+ * @return BladeFile
+ */
+ public static LocalFile getFile(MultipartFile file, String dir) {
+ return getFile(file, dir, null, null);
+ }
+
+ /**
+ * 鑾峰彇BladeFile灏佽绫�
+ *
+ * @param file 鏂囦欢
+ * @param dir 鐩綍
+ * @param path 璺緞
+ * @param virtualPath 铏氭嫙璺緞
+ * @return BladeFile
+ */
+ public static LocalFile getFile(MultipartFile file, String dir, String path, String virtualPath) {
+ return new LocalFile(file, dir, path, virtualPath);
+ }
+
+ /**
+ * 鑾峰彇BladeFile灏佽绫�
+ *
+ * @param files 鏂囦欢闆嗗悎
+ * @return BladeFile
+ */
+ public static List<LocalFile> getFiles(List<MultipartFile> files) {
+ return getFiles(files, "image", null, null);
+ }
+
+ /**
+ * 鑾峰彇BladeFile灏佽绫�
+ *
+ * @param files 鏂囦欢闆嗗悎
+ * @param dir 鐩綍
+ * @return BladeFile
+ */
+ public static List<LocalFile> getFiles(List<MultipartFile> files, String dir) {
+ return getFiles(files, dir, null, null);
+ }
+
+ /**
+ * 鑾峰彇BladeFile灏佽绫�
+ *
+ * @param files 鏂囦欢闆嗗悎
+ * @param path 璺緞
+ * @param virtualPath 铏氭嫙璺緞
+ * @return BladeFile
+ */
+ public static List<LocalFile> getFiles(List<MultipartFile> files, String dir, String path, String virtualPath) {
+ List<LocalFile> list = new ArrayList<>();
+ for (MultipartFile file : files) {
+ list.add(new LocalFile(file, dir, path, virtualPath));
+ }
+ return list;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/FileProxyManager.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/FileProxyManager.java
new file mode 100644
index 0000000..124751e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/FileProxyManager.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.boot.file;
+
+import java.io.File;
+
+/**
+ * 鏂囦欢绠$悊绫�
+ *
+ * @author Chill
+ */
+public class FileProxyManager {
+ private IFileProxy defaultFileProxyFactory = new LocalFileProxyFactory();
+
+ private static final FileProxyManager ME = new FileProxyManager();
+
+ public static FileProxyManager me() {
+ return ME;
+ }
+
+ public IFileProxy getDefaultFileProxyFactory() {
+ return defaultFileProxyFactory;
+ }
+
+ public void setDefaultFileProxyFactory(IFileProxy defaultFileProxyFactory) {
+ this.defaultFileProxyFactory = defaultFileProxyFactory;
+ }
+
+ public String[] path(File file, String dir) {
+ return defaultFileProxyFactory.path(file, dir);
+ }
+
+ public File rename(File file, String path) {
+ return defaultFileProxyFactory.rename(file, path);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/IFileProxy.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/IFileProxy.java
new file mode 100644
index 0000000..7d3f326
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/IFileProxy.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.boot.file;
+
+import java.io.File;
+
+/**
+ * 鏂囦欢浠g悊鎺ュ彛
+ *
+ * @author Chill
+ */
+public interface IFileProxy {
+
+ /**
+ * 杩斿洖璺緞[鐗╃悊璺緞][铏氭嫙璺緞]
+ *
+ * @param file 鏂囦欢
+ * @param dir 鐩綍
+ * @return
+ */
+ String[] path(File file, String dir);
+
+ /**
+ * 鏂囦欢閲嶅懡鍚嶇瓥鐣�
+ *
+ * @param file 鏂囦欢
+ * @param path 璺緞
+ * @return
+ */
+ File rename(File file, String path);
+
+ /**
+ * 鍥剧墖鍘嬬缉
+ *
+ * @param path 璺緞
+ */
+ void compress(String path);
+
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/LocalFile.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/LocalFile.java
new file mode 100644
index 0000000..3a66029
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/LocalFile.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.boot.file;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Data;
+import org.springblade.core.boot.props.BladeFileProperties;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * 涓婁紶鏂囦欢灏佽
+ *
+ * @author Chill
+ */
+@Data
+public class LocalFile {
+ /**
+ * 涓婁紶鏂囦欢鍦ㄩ檮浠惰〃涓殑id
+ */
+ private Object fileId;
+
+ /**
+ * 涓婁紶鏂囦欢
+ */
+ @JsonIgnore
+ private MultipartFile file;
+
+ /**
+ * 鏂囦欢澶栫綉鍦板潃
+ */
+ private String domain;
+
+ /**
+ * 涓婁紶鍒嗙被鏂囦欢澶�
+ */
+ private String dir;
+
+ /**
+ * 涓婁紶鐗╃悊璺緞
+ */
+ private String uploadPath;
+
+ /**
+ * 涓婁紶铏氭嫙璺緞
+ */
+ private String uploadVirtualPath;
+
+ /**
+ * 鏂囦欢鍚�
+ */
+ private String fileName;
+
+ /**
+ * 鐪熷疄鏂囦欢鍚�
+ */
+ private String originalFileName;
+
+ /**
+ * 鏂囦欢閰嶇疆
+ */
+ private static BladeFileProperties fileProperties;
+
+ private static BladeFileProperties getBladeFileProperties() {
+ if (fileProperties == null) {
+ fileProperties = SpringUtil.getBean(BladeFileProperties.class);
+ }
+ return fileProperties;
+ }
+
+ public LocalFile(MultipartFile file, String dir) {
+ this.dir = dir;
+ this.file = file;
+ this.fileName = file.getName();
+ this.originalFileName = file.getOriginalFilename();
+ this.domain = getBladeFileProperties().getUploadDomain();
+ this.uploadPath = BladeFileUtil.formatUrl(File.separator + getBladeFileProperties().getUploadRealPath() + File.separator + dir + File.separator + DateUtil.format(DateUtil.now(), "yyyyMMdd") + File.separator + this.originalFileName);
+ this.uploadVirtualPath = BladeFileUtil.formatUrl(getBladeFileProperties().getUploadCtxPath().replace(getBladeFileProperties().getContextPath(), "") + File.separator + dir + File.separator + DateUtil.format(DateUtil.now(), "yyyyMMdd") + File.separator + this.originalFileName);
+ }
+
+ public LocalFile(MultipartFile file, String dir, String uploadPath, String uploadVirtualPath) {
+ this(file, dir);
+ if (null != uploadPath) {
+ this.uploadPath = BladeFileUtil.formatUrl(uploadPath);
+ this.uploadVirtualPath = BladeFileUtil.formatUrl(uploadVirtualPath);
+ }
+ }
+
+ /**
+ * 鍥剧墖涓婁紶
+ */
+ public void transfer() {
+ transfer(getBladeFileProperties().getCompress());
+ }
+
+ /**
+ * 鍥剧墖涓婁紶
+ *
+ * @param compress 鏄惁鍘嬬缉
+ */
+ public void transfer(boolean compress) {
+ IFileProxy fileFactory = FileProxyManager.me().getDefaultFileProxyFactory();
+ this.transfer(fileFactory, compress);
+ }
+
+ /**
+ * 鍥剧墖涓婁紶
+ *
+ * @param fileFactory 鏂囦欢涓婁紶宸ュ巶绫�
+ * @param compress 鏄惁鍘嬬缉
+ */
+ public void transfer(IFileProxy fileFactory, boolean compress) {
+ try {
+ File file = new File(uploadPath);
+
+ if (null != fileFactory) {
+ String[] path = fileFactory.path(file, dir);
+ this.uploadPath = path[0];
+ this.uploadVirtualPath = path[1];
+ file = fileFactory.rename(file, path[0]);
+ }
+
+ File pfile = file.getParentFile();
+ if (!pfile.exists()) {
+ pfile.mkdirs();
+ }
+
+ this.file.transferTo(file);
+
+ if (compress) {
+ fileFactory.compress(this.uploadPath);
+ }
+
+ } catch (IllegalStateException | IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/LocalFileProxyFactory.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/LocalFileProxyFactory.java
new file mode 100644
index 0000000..8165cc2
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/file/LocalFileProxyFactory.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.boot.file;
+
+import org.springblade.core.boot.props.BladeFileProperties;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.core.tool.utils.ImageUtil;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.core.tool.utils.StringPool;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+
+/**
+ * 鏂囦欢浠g悊绫�
+ *
+ * @author Chill
+ */
+public class LocalFileProxyFactory implements IFileProxy {
+
+ /**
+ * 鏂囦欢閰嶇疆
+ */
+ private static BladeFileProperties fileProperties;
+
+ private static BladeFileProperties getBladeFileProperties() {
+ if (fileProperties == null) {
+ fileProperties = SpringUtil.getBean(BladeFileProperties.class);
+ }
+ return fileProperties;
+ }
+
+ @Override
+ public File rename(File f, String path) {
+ File dest = new File(path);
+ f.renameTo(dest);
+ return dest;
+ }
+
+ @Override
+ public String[] path(File f, String dir) {
+ //閬垮厤缃戠粶寤惰繜瀵艰嚧鏃堕棿涓嶅悓姝�
+ long time = System.nanoTime();
+
+ StringBuilder uploadPath = new StringBuilder()
+ .append(getFileDir(dir, getBladeFileProperties().getUploadRealPath()))
+ .append(time)
+ .append(getFileExt(f.getName()));
+
+ StringBuilder virtualPath = new StringBuilder()
+ .append(getFileDir(dir, getBladeFileProperties().getUploadCtxPath()))
+ .append(time)
+ .append(getFileExt(f.getName()));
+
+ return new String[]{BladeFileUtil.formatUrl(uploadPath.toString()), BladeFileUtil.formatUrl(virtualPath.toString())};
+ }
+
+ /**
+ * 鑾峰彇鏂囦欢鍚庣紑
+ *
+ * @param fileName 鏂囦欢鍚�
+ * @return 鏂囦欢鍚庣紑
+ */
+ public static String getFileExt(String fileName) {
+ if (!fileName.contains(StringPool.DOT)) {
+ return ".jpg";
+ } else {
+ return fileName.substring(fileName.lastIndexOf(StringPool.DOT));
+ }
+ }
+
+ /**
+ * 鑾峰彇鏂囦欢淇濆瓨鍦板潃
+ *
+ * @param dir 鐩綍
+ * @param saveDir 淇濆瓨鐩綍
+ * @return 鍦板潃
+ */
+ public static String getFileDir(String dir, String saveDir) {
+ StringBuilder newFileDir = new StringBuilder();
+ newFileDir.append(saveDir)
+ .append(File.separator).append(dir).append(File.separator).append(DateUtil.format(DateUtil.now(), "yyyyMMdd"))
+ .append(File.separator);
+ return newFileDir.toString();
+ }
+
+
+ /**
+ * 鍥剧墖鍘嬬缉
+ *
+ * @param path 鏂囦欢鍦板潃
+ */
+ @Override
+ public void compress(String path) {
+ try {
+ ImageUtil.zoomScale(ImageUtil.readImage(path), new FileOutputStream(new File(path)), null, getBladeFileProperties().getCompressScale(), getBladeFileProperties().getCompressFlag());
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/props/BladeFileProperties.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/props/BladeFileProperties.java
new file mode 100644
index 0000000..a7cc3c9
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/props/BladeFileProperties.java
@@ -0,0 +1,76 @@
+package org.springblade.core.boot.props;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * BladeFileProperties
+ *
+ * @author Chill
+ */
+@Getter
+@Setter
+@ConfigurationProperties("blade.file")
+public class BladeFileProperties {
+
+ /**
+ * 杩滅▼涓婁紶妯″紡
+ */
+ private boolean remoteMode = false;
+
+ /**
+ * 澶栫綉鍦板潃
+ */
+ private String uploadDomain = "http://127.0.0.1:8999";
+
+ /**
+ * 涓婁紶涓嬭浇璺緞(鐗╃悊璺緞)
+ */
+ private String remotePath = System.getProperty("user.dir") + "/target/blade";
+
+ /**
+ * 涓婁紶璺緞(鐩稿璺緞)
+ */
+ private String uploadPath = "/upload";
+
+ /**
+ * 涓嬭浇璺緞
+ */
+ private String downloadPath = "/download";
+
+ /**
+ * 鍥剧墖鍘嬬缉
+ */
+ private Boolean compress = false;
+
+ /**
+ * 鍥剧墖鍘嬬缉姣斾緥
+ */
+ private Double compressScale = 2.00;
+
+ /**
+ * 鍥剧墖缂╂斁閫夋嫨:true鏀惧ぇ;false缂╁皬
+ */
+ private Boolean compressFlag = false;
+
+ /**
+ * 椤圭洰鐗╃悊璺緞
+ */
+ private String realPath = System.getProperty("user.dir");
+
+ /**
+ * 椤圭洰鐩稿璺緞
+ */
+ private String contextPath = "/";
+
+
+ public String getUploadRealPath() {
+ return (remoteMode ? remotePath : realPath) + uploadPath;
+ }
+
+ public String getUploadCtxPath() {
+ return contextPath + uploadPath;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/props/BladeUploadProperties.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/props/BladeUploadProperties.java
new file mode 100644
index 0000000..2ce0902
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/props/BladeUploadProperties.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.boot.props;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springblade.core.tool.utils.PathUtil;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.lang.Nullable;
+
+
+/**
+ * 鏂囦欢涓婁紶閰嶇疆
+ *
+ * @author Chill
+ */
+@Getter
+@Setter
+@RefreshScope
+@ConfigurationProperties("blade.upload")
+public class BladeUploadProperties {
+
+ /**
+ * 鏂囦欢淇濆瓨鐩綍锛岄粯璁わ細jar 鍖呭悓绾х洰褰�
+ */
+ @Nullable
+ private String savePath = PathUtil.getJarPath();
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/BladeHttpServletRequestWrapper.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/BladeHttpServletRequestWrapper.java
new file mode 100644
index 0000000..d217604
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/BladeHttpServletRequestWrapper.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.boot.request;
+
+import org.springblade.core.tool.utils.WebUtil;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * 鍏ㄥ眬Request鍖呰
+ *
+ * @author Chill
+ */
+public class BladeHttpServletRequestWrapper extends HttpServletRequestWrapper {
+
+ /**
+ * 娌¤鍖呰杩囩殑HttpServletRequest锛堢壒娈婂満鏅�,闇�瑕佽嚜宸辫繃婊わ級
+ */
+ private final HttpServletRequest orgRequest;
+ /**
+ * 缂撳瓨鎶ユ枃,鏀寔澶氭璇诲彇娴�
+ */
+ private byte[] body;
+
+
+ public BladeHttpServletRequestWrapper(HttpServletRequest request) {
+ super(request);
+ orgRequest = request;
+ }
+
+ @Override
+ public BufferedReader getReader() throws IOException {
+ return new BufferedReader(new InputStreamReader(getInputStream()));
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+ if (super.getHeader(HttpHeaders.CONTENT_TYPE) == null) {
+ return super.getInputStream();
+ }
+
+ if (super.getHeader(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
+ return super.getInputStream();
+ }
+
+ if (body == null) {
+ body = WebUtil.getRequestBody(super.getInputStream()).getBytes();
+ }
+
+ final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
+
+ return new ServletInputStream() {
+
+ @Override
+ public int read() {
+ return byteArrayInputStream.read();
+ }
+
+ @Override
+ public boolean isFinished() {
+ return false;
+ }
+
+ @Override
+ public boolean isReady() {
+ return false;
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+ }
+ };
+ }
+
+ /**
+ * 鑾峰彇鍒濆request
+ *
+ * @return HttpServletRequest
+ */
+ public HttpServletRequest getOrgRequest() {
+ return orgRequest;
+ }
+
+ /**
+ * 鑾峰彇鍒濆request
+ *
+ * @param request request
+ * @return HttpServletRequest
+ */
+ public static HttpServletRequest getOrgRequest(HttpServletRequest request) {
+ if (request instanceof BladeHttpServletRequestWrapper) {
+ return ((BladeHttpServletRequestWrapper) request).getOrgRequest();
+ }
+ return request;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/BladeRequestFilter.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/BladeRequestFilter.java
new file mode 100644
index 0000000..f6cf456
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/BladeRequestFilter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.boot.request;
+
+import lombok.AllArgsConstructor;
+import org.springframework.util.AntPathMatcher;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+/**
+ * Request鍏ㄥ眬杩囨护
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+public class BladeRequestFilter implements Filter {
+
+ private final RequestProperties requestProperties;
+ private final XssProperties xssProperties;
+ private final AntPathMatcher antPathMatcher = new AntPathMatcher();
+
+ @Override
+ public void init(FilterConfig config) {
+
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ String path = ((HttpServletRequest) request).getServletPath();
+ // 璺宠繃 Request 鍖呰
+ if (!requestProperties.getEnabled() || isRequestSkip(path)) {
+ chain.doFilter(request, response);
+ }
+ // 榛樿 Request 鍖呰
+ else if (!xssProperties.getEnabled() || isXssSkip(path)) {
+ BladeHttpServletRequestWrapper bladeRequest = new BladeHttpServletRequestWrapper((HttpServletRequest) request);
+ chain.doFilter(bladeRequest, response);
+ }
+ // Xss Request 鍖呰
+ else {
+ XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
+ chain.doFilter(xssRequest, response);
+ }
+ }
+
+ private boolean isRequestSkip(String path) {
+ return requestProperties.getSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
+ }
+
+ private boolean isXssSkip(String path) {
+ return xssProperties.getSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/RequestProperties.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/RequestProperties.java
new file mode 100644
index 0000000..997494e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/RequestProperties.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.boot.request;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Request閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@Data
+@ConfigurationProperties("blade.request")
+public class RequestProperties {
+
+ /**
+ * 寮�鍚嚜瀹氫箟request
+ */
+ private Boolean enabled = true;
+
+ /**
+ * 鏀捐url
+ */
+ private List<String> skipUrl = new ArrayList<>();
+
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/XssHtmlFilter.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/XssHtmlFilter.java
new file mode 100644
index 0000000..6fb952e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/XssHtmlFilter.java
@@ -0,0 +1,536 @@
+package org.springblade.core.boot.request;
+
+import org.springblade.core.tool.utils.StringPool;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * HTML filtering utility for protecting against XSS (Cross Site Scripting).
+ * <p>
+ * This code is licensed LGPLv3
+ * <p>
+ * This code is a Java port of the original work in PHP by Cal Hendersen.
+ * http://code.iamcal.com/php/lib_filter/
+ * <p>
+ * The trickiest part of the translation was handling the differences in regex handling
+ * between PHP and Java. These resources were helpful in the process:
+ * <p>
+ * http://java.sun.com/j2se/1.4.2/docs/api/java/util/regex/Pattern.html
+ * http://us2.php.net/manual/en/reference.pcre.pattern.modifiers.php
+ * http://www.regular-expressions.info/modifiers.html
+ * <p>
+ * A note on naming conventions: instance variables are prefixed with a "v"; global
+ * constants are in all caps.
+ * <p>
+ * Sample use:
+ * String input = ...
+ * String clean = new HtmlFilter().filter( input );
+ * <p>
+ * The class is not thread safe. Create a new instance if in doubt.
+ * <p>
+ * If you find bugs or have suggestions on improvement (especially regarding
+ * performance), please contact us. The latest version of this
+ * source, and our contact details, can be found at http://xss-html-filter.sf.net
+ *
+ * @author Joseph O'Connell
+ * @author Cal Hendersen
+ * @author Michael Semb Wever
+ */
+public final class XssHtmlFilter {
+
+ /**
+ * regex flag union representing /si modifiers in php
+ **/
+ private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL;
+ private static final Pattern P_COMMENTS = Pattern.compile("<!--(.*?)-->", Pattern.DOTALL);
+ private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI);
+ private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL);
+ private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI);
+ private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI);
+ private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI);
+ private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI);
+ private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI);
+ private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?");
+ private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?");
+ private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?");
+ private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))");
+ private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL);
+ private static final Pattern P_END_ARROW = Pattern.compile("^>");
+ private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)");
+ private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)");
+ private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)");
+ private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)");
+ private static final Pattern P_AMP = Pattern.compile("&");
+ private static final Pattern P_QUOTE = Pattern.compile("<");
+ private static final Pattern P_LEFT_ARROW = Pattern.compile("<");
+ private static final Pattern P_RIGHT_ARROW = Pattern.compile(">");
+ private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>");
+
+
+ private static final ConcurrentMap<String, Pattern> P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<String, Pattern>();
+ private static final ConcurrentMap<String, Pattern> P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<String, Pattern>();
+
+ /**
+ * set of allowed html elements, along with allowed attributes for each element
+ **/
+ private final Map<String, List<String>> vAllowed;
+ /**
+ * counts of open tags for each (allowable) html element
+ **/
+ private final Map<String, Integer> vTagCounts = new HashMap<String, Integer>();
+
+ /**
+ * html elements which must always be self-closing (e.g. "<img />")
+ **/
+ private final String[] vSelfClosingTags;
+ /**
+ * html elements which must always have separate opening and closing tags (e.g. "<b></b>")
+ **/
+ private final String[] vNeedClosingTags;
+ /**
+ * set of disallowed html elements
+ **/
+ private final String[] vDisallowed;
+ /**
+ * attributes which should be checked for valid protocols
+ **/
+ private final String[] vProtocolAtts;
+ /**
+ * allowed protocols
+ **/
+ private final String[] vAllowedProtocols;
+ /**
+ * tags which should be removed if they contain no content (e.g. "<b></b>" or "<b />")
+ **/
+ private final String[] vRemoveBlanks;
+ /**
+ * entities allowed within html markup
+ **/
+ private final String[] vAllowedEntities;
+ /**
+ * flag determining whether comments are allowed in input String.
+ */
+ private final boolean stripComment;
+ private final boolean encodeQuotes;
+ private boolean vDebug = false;
+ /**
+ * flag determining whether to try to make tags when presented with "unbalanced"
+ * angle brackets (e.g. "<b text </b>" becomes "<b> text </b>"). If set to false,
+ * unbalanced angle brackets will be html escaped.
+ */
+ private final boolean alwaysMakeTags;
+
+ /**
+ * Default constructor.
+ */
+ public XssHtmlFilter() {
+ vAllowed = new HashMap<>();
+
+ final ArrayList<String> aAtts = new ArrayList<String>();
+ aAtts.add("href");
+ aAtts.add("target");
+ vAllowed.put("a", aAtts);
+
+ final ArrayList<String> imgAtts = new ArrayList<String>();
+ imgAtts.add("src");
+ imgAtts.add("width");
+ imgAtts.add("height");
+ imgAtts.add("alt");
+ vAllowed.put("img", imgAtts);
+
+ final ArrayList<String> noAtts = new ArrayList<String>();
+ vAllowed.put("b", noAtts);
+ vAllowed.put("strong", noAtts);
+ vAllowed.put("i", noAtts);
+ vAllowed.put("em", noAtts);
+
+ vSelfClosingTags = new String[]{"img"};
+ vNeedClosingTags = new String[]{"a", "b", "strong", "i", "em"};
+ vDisallowed = new String[]{};
+ vAllowedProtocols = new String[]{"http", "mailto", "https"};
+ vProtocolAtts = new String[]{"src", "href"};
+ vRemoveBlanks = new String[]{"a", "b", "strong", "i", "em"};
+ vAllowedEntities = new String[]{"amp", "gt", "lt", "quot"};
+ stripComment = true;
+ encodeQuotes = true;
+ alwaysMakeTags = false;
+ }
+
+ /**
+ * Set debug flag to true. Otherwise use default settings. See the default constructor.
+ *
+ * @param debug turn debug on with a true argument
+ */
+ public XssHtmlFilter(final boolean debug) {
+ this();
+ vDebug = debug;
+
+ }
+
+ /**
+ * Map-parameter configurable constructor.
+ *
+ * @param conf map containing configuration. keys match field names.
+ */
+ public XssHtmlFilter(final Map<String, Object> conf) {
+
+ assert conf.containsKey("vAllowed") : "configuration requires vAllowed";
+ assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags";
+ assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags";
+ assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed";
+ assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols";
+ assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts";
+ assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks";
+ assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities";
+
+ vAllowed = Collections.unmodifiableMap((HashMap<String, List<String>>) conf.get("vAllowed"));
+ vSelfClosingTags = (String[]) conf.get("vSelfClosingTags");
+ vNeedClosingTags = (String[]) conf.get("vNeedClosingTags");
+ vDisallowed = (String[]) conf.get("vDisallowed");
+ vAllowedProtocols = (String[]) conf.get("vAllowedProtocols");
+ vProtocolAtts = (String[]) conf.get("vProtocolAtts");
+ vRemoveBlanks = (String[]) conf.get("vRemoveBlanks");
+ vAllowedEntities = (String[]) conf.get("vAllowedEntities");
+ stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true;
+ encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true;
+ alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true;
+ }
+
+ private void reset() {
+ vTagCounts.clear();
+ }
+
+ private void debug(final String msg) {
+ if (vDebug) {
+ Logger.getAnonymousLogger().info(msg);
+ }
+ }
+
+ public static String chr(final int decimal) {
+ return String.valueOf((char) decimal);
+ }
+
+ public static String htmlSpecialChars(final String s) {
+ String result = s;
+ result = regexReplace(P_AMP, "&", result);
+ result = regexReplace(P_QUOTE, """, result);
+ result = regexReplace(P_LEFT_ARROW, "<", result);
+ result = regexReplace(P_RIGHT_ARROW, ">", result);
+ return result;
+ }
+
+ //---------------------------------------------------------------
+
+ /**
+ * given a user submitted input String, filter out any invalid or restricted
+ * html.
+ *
+ * @param input text (i.e. submitted by a user) than may contain html
+ * @return "clean" version of input, with only valid, whitelisted html elements allowed
+ */
+ public String filter(final String input) {
+ reset();
+ String s = input;
+
+ debug("************************************************");
+ debug(" INPUT: " + input);
+
+ s = escapeComments(s);
+ debug(" escapeComments: " + s);
+
+ s = balanceHtml(s);
+ debug(" balanceHtml: " + s);
+
+ s = checkTags(s);
+ debug(" checkTags: " + s);
+
+ s = processRemoveBlanks(s);
+ debug("processRemoveBlanks: " + s);
+
+ s = validateEntities(s);
+ debug(" validateEntites: " + s);
+
+ debug("************************************************\n\n");
+ return s;
+ }
+
+ public boolean isAlwaysMakeTags() {
+ return alwaysMakeTags;
+ }
+
+ public boolean isStripComments() {
+ return stripComment;
+ }
+
+ private String escapeComments(final String s) {
+ final Matcher m = P_COMMENTS.matcher(s);
+ final StringBuffer buf = new StringBuffer();
+ if (m.find()) {
+ final String match = m.group(1);
+ m.appendReplacement(buf, Matcher.quoteReplacement("<!--" + htmlSpecialChars(match) + "-->"));
+ }
+ m.appendTail(buf);
+
+ return buf.toString();
+ }
+
+ private String balanceHtml(String s) {
+ if (alwaysMakeTags) {
+ //
+ // try and form html
+ //
+ s = regexReplace(P_END_ARROW, "", s);
+ s = regexReplace(P_BODY_TO_END, "<$1>", s);
+ s = regexReplace(P_XML_CONTENT, "$1<$2", s);
+
+ } else {
+ //
+ // escape stray brackets
+ //
+ s = regexReplace(P_STRAY_LEFT_ARROW, "<$1", s);
+ s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s);
+
+ //
+ // the last regexp causes '<>' entities to appear
+ // (we need to do a lookahead assertion so that the last bracket can
+ // be used in the next pass of the regexp)
+ //
+ s = regexReplace(P_BOTH_ARROWS, "", s);
+ }
+
+ return s;
+ }
+
+ private String checkTags(String s) {
+ Matcher m = P_TAGS.matcher(s);
+
+ final StringBuffer buf = new StringBuffer();
+ while (m.find()) {
+ String replaceStr = m.group(1);
+ replaceStr = processTag(replaceStr);
+ m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr));
+ }
+ m.appendTail(buf);
+
+ s = buf.toString();
+
+ // these get tallied in processTag
+ // (remember to reset before subsequent calls to filter method)
+ for (String key : vTagCounts.keySet()) {
+ for (int ii = 0; ii < vTagCounts.get(key); ii++) {
+ s += "</" + key + ">";
+ }
+ }
+
+ return s;
+ }
+
+ private String processRemoveBlanks(final String s) {
+ String result = s;
+ for (String tag : vRemoveBlanks) {
+ if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) {
+ P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?></" + tag + ">"));
+ }
+ result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result);
+ if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) {
+ P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>"));
+ }
+ result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result);
+ }
+
+ return result;
+ }
+
+ private static String regexReplace(final Pattern regexPattern, final String replacement, final String s) {
+ Matcher m = regexPattern.matcher(s);
+ return m.replaceAll(replacement);
+ }
+
+ private String processTag(final String s) {
+ Matcher m = P_END_TAG.matcher(s);
+ if (m.find()) {
+ final String name = m.group(1).toLowerCase();
+ if (allowed(name)) {
+ if (!inArray(name, vSelfClosingTags)) {
+ if (vTagCounts.containsKey(name)) {
+ vTagCounts.put(name, vTagCounts.get(name) - 1);
+ return "</" + name + ">";
+ }
+ }
+ }
+ }
+ m = P_START_TAG.matcher(s);
+ if (m.find()) {
+ final String name = m.group(1).toLowerCase();
+ final String body = m.group(2);
+ String ending = m.group(3);
+ if (allowed(name)) {
+ String params = "";
+ final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body);
+ final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body);
+ final List<String> paramNames = new ArrayList<String>();
+ final List<String> paramValues = new ArrayList<String>();
+ while (m2.find()) {
+ paramNames.add(m2.group(1));
+ paramValues.add(m2.group(3));
+ }
+ while (m3.find()) {
+ paramNames.add(m3.group(1));
+ paramValues.add(m3.group(3));
+ }
+ String paramName, paramValue;
+ for (int ii = 0; ii < paramNames.size(); ii++) {
+ paramName = paramNames.get(ii).toLowerCase();
+ paramValue = paramValues.get(ii);
+ if (allowedAttribute(name, paramName)) {
+ if (inArray(paramName, vProtocolAtts)) {
+ paramValue = processParamProtocol(paramValue);
+ }
+ params += " " + paramName + "=\"" + paramValue + "\"";
+ }
+ }
+ if (inArray(name, vSelfClosingTags)) {
+ ending = " /";
+ }
+ if (inArray(name, vNeedClosingTags)) {
+ ending = "";
+ }
+ if (ending == null || ending.length() < 1) {
+ if (vTagCounts.containsKey(name)) {
+ vTagCounts.put(name, vTagCounts.get(name) + 1);
+ } else {
+ vTagCounts.put(name, 1);
+ }
+ } else {
+ ending = " /";
+ }
+ return "<" + name + params + ending + ">";
+ } else {
+ return "";
+ }
+ }
+ m = P_COMMENT.matcher(s);
+ if (!stripComment && m.find()) {
+ return "<" + m.group() + ">";
+ }
+ return "";
+ }
+
+ private String processParamProtocol(String s) {
+ s = decodeEntities(s);
+ final Matcher m = P_PROTOCOL.matcher(s);
+ if (m.find()) {
+ final String protocol = m.group(1);
+ if (!inArray(protocol, vAllowedProtocols)) {
+ // bad protocol, turn into local anchor link instead
+ s = "#" + s.substring(protocol.length() + 1);
+ if (s.startsWith(StringPool.DOUBLE_SLASH)) {
+ s = "#" + s.substring(3);
+ }
+ }
+ }
+
+ return s;
+ }
+
+ private String decodeEntities(String s) {
+ StringBuffer buf = new StringBuffer();
+
+ Matcher m = P_ENTITY.matcher(s);
+ while (m.find()) {
+ final String match = m.group(1);
+ final int decimal = Integer.decode(match);
+ m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
+ }
+ m.appendTail(buf);
+ s = buf.toString();
+
+ buf = new StringBuffer();
+ m = P_ENTITY_UNICODE.matcher(s);
+ while (m.find()) {
+ final String match = m.group(1);
+ final int decimal = Integer.valueOf(match, 16).intValue();
+ m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
+ }
+ m.appendTail(buf);
+ s = buf.toString();
+
+ buf = new StringBuffer();
+ m = P_ENCODE.matcher(s);
+ while (m.find()) {
+ final String match = m.group(1);
+ final int decimal = Integer.valueOf(match, 16).intValue();
+ m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
+ }
+ m.appendTail(buf);
+ s = buf.toString();
+
+ s = validateEntities(s);
+ return s;
+ }
+
+ private String validateEntities(final String s) {
+ StringBuffer buf = new StringBuffer();
+
+ // validate entities throughout the string
+ Matcher m = P_VALID_ENTITIES.matcher(s);
+ while (m.find()) {
+ final String one = m.group(1);
+ final String two = m.group(2);
+ m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two)));
+ }
+ m.appendTail(buf);
+
+ return encodeQuotes(buf.toString());
+ }
+
+ private String encodeQuotes(final String s) {
+ if (encodeQuotes) {
+ StringBuffer buf = new StringBuffer();
+ Matcher m = P_VALID_QUOTES.matcher(s);
+ while (m.find()) {
+ final String one = m.group(1);
+ final String two = m.group(2);
+ final String three = m.group(3);
+ m.appendReplacement(buf, Matcher.quoteReplacement(one + regexReplace(P_QUOTE, """, two) + three));
+ }
+ m.appendTail(buf);
+ return buf.toString();
+ } else {
+ return s;
+ }
+ }
+
+ private String checkEntity(final String preamble, final String term) {
+
+ return ";".equals(term) && isValidEntity(preamble)
+ ? '&' + preamble
+ : "&" + preamble;
+ }
+
+ private boolean isValidEntity(final String entity) {
+ return inArray(entity, vAllowedEntities);
+ }
+
+ private static boolean inArray(final String s, final String[] array) {
+ for (String item : array) {
+ if (item != null && item.equals(s)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean allowed(final String name) {
+ return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed);
+ }
+
+ private boolean allowedAttribute(final String name, final String paramName) {
+ return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName));
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/XssHttpServletRequestWrapper.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/XssHttpServletRequestWrapper.java
new file mode 100644
index 0000000..50224bb
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/XssHttpServletRequestWrapper.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.boot.request;
+
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.core.tool.utils.WebUtil;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * XSS杩囨护
+ *
+ * @author Chill
+ */
+public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
+
+ /**
+ * 娌¤鍖呰杩囩殑HttpServletRequest锛堢壒娈婂満鏅�,闇�瑕佽嚜宸辫繃婊わ級
+ */
+ private final HttpServletRequest orgRequest;
+ /**
+ * 缂撳瓨鎶ユ枃,鏀寔澶氭璇诲彇娴�
+ */
+ private byte[] body;
+ /**
+ * html杩囨护
+ */
+ private final static XssHtmlFilter HTML_FILTER = new XssHtmlFilter();
+
+ public XssHttpServletRequestWrapper(HttpServletRequest request) {
+ super(request);
+ orgRequest = request;
+ }
+
+ @Override
+ public BufferedReader getReader() throws IOException {
+ return new BufferedReader(new InputStreamReader(getInputStream()));
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+ if (super.getHeader(HttpHeaders.CONTENT_TYPE) == null) {
+ return super.getInputStream();
+ }
+
+ if (super.getHeader(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
+ return super.getInputStream();
+ }
+
+ if (body == null) {
+ body = xssEncode(WebUtil.getRequestBody(super.getInputStream())).getBytes();
+ }
+
+ final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
+
+ return new ServletInputStream() {
+
+ @Override
+ public int read() {
+ return byteArrayInputStream.read();
+ }
+
+ @Override
+ public boolean isFinished() {
+ return false;
+ }
+
+ @Override
+ public boolean isReady() {
+ return false;
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+ }
+ };
+ }
+
+ @Override
+ public String getParameter(String name) {
+ String value = super.getParameter(xssEncode(name));
+ if (StringUtil.isNotBlank(value)) {
+ value = xssEncode(value);
+ }
+ return value;
+ }
+
+ @Override
+ public String[] getParameterValues(String name) {
+ String[] parameters = super.getParameterValues(name);
+ if (parameters == null || parameters.length == 0) {
+ return null;
+ }
+
+ for (int i = 0; i < parameters.length; i++) {
+ parameters[i] = xssEncode(parameters[i]);
+ }
+ return parameters;
+ }
+
+ @Override
+ public Map<String, String[]> getParameterMap() {
+ Map<String, String[]> map = new LinkedHashMap<>();
+ Map<String, String[]> parameters = super.getParameterMap();
+ for (String key : parameters.keySet()) {
+ String[] values = parameters.get(key);
+ for (int i = 0; i < values.length; i++) {
+ values[i] = xssEncode(values[i]);
+ }
+ map.put(key, values);
+ }
+ return map;
+ }
+
+ @Override
+ public String getHeader(String name) {
+ String value = super.getHeader(xssEncode(name));
+ if (StringUtil.isNotBlank(value)) {
+ value = xssEncode(value);
+ }
+ return value;
+ }
+
+ private String xssEncode(String input) {
+ return HTML_FILTER.filter(input);
+ }
+
+ /**
+ * 鑾峰彇鍒濆request
+ *
+ * @return HttpServletRequest
+ */
+ public HttpServletRequest getOrgRequest() {
+ return orgRequest;
+ }
+
+ /**
+ * 鑾峰彇鍒濆request
+ *
+ * @param request request
+ * @return HttpServletRequest
+ */
+ public static HttpServletRequest getOrgRequest(HttpServletRequest request) {
+ if (request instanceof XssHttpServletRequestWrapper) {
+ return ((XssHttpServletRequestWrapper) request).getOrgRequest();
+ }
+ return request;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/XssProperties.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/XssProperties.java
new file mode 100644
index 0000000..d2c4d3b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/request/XssProperties.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.boot.request;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Xss閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@Data
+@ConfigurationProperties("blade.xss")
+public class XssProperties {
+
+ /**
+ * 寮�鍚痻ss
+ */
+ private Boolean enabled = true;
+
+ /**
+ * 鏀捐url
+ */
+ private List<String> skipUrl = new ArrayList<>();
+
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/resolver/TokenArgumentResolver.java b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/resolver/TokenArgumentResolver.java
new file mode 100644
index 0000000..2e40c31
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/java/org/springblade/core/boot/resolver/TokenArgumentResolver.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.boot.resolver;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.secure.BladeUser;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springframework.core.MethodParameter;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.ModelAndViewContainer;
+
+/**
+ * Token杞寲BladeUser
+ *
+ * @author Chill
+ */
+@Slf4j
+public class TokenArgumentResolver implements HandlerMethodArgumentResolver {
+
+ /**
+ * 鍏ュ弬绛涢��
+ *
+ * @param methodParameter 鍙傛暟闆嗗悎
+ * @return 鏍煎紡鍖栧悗鐨勫弬鏁�
+ */
+ @Override
+ public boolean supportsParameter(MethodParameter methodParameter) {
+ return methodParameter.getParameterType().equals(BladeUser.class);
+ }
+
+ /**
+ * 鍑哄弬璁剧疆
+ *
+ * @param methodParameter 鍏ュ弬闆嗗悎
+ * @param modelAndViewContainer model 鍜� view
+ * @param nativeWebRequest web鐩稿叧
+ * @param webDataBinderFactory 鍏ュ弬瑙f瀽
+ * @return 鍖呰瀵硅薄
+ */
+ @Override
+ public Object resolveArgument(MethodParameter methodParameter,
+ ModelAndViewContainer modelAndViewContainer,
+ NativeWebRequest nativeWebRequest,
+ WebDataBinderFactory webDataBinderFactory) {
+ return AuthUtil.getUser();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/resources/banner.txt b/Source/BladeX-Tool/blade-core-boot/src/main/resources/banner.txt
new file mode 100644
index 0000000..c0f1066
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/resources/banner.txt
@@ -0,0 +1,8 @@
+${AnsiColor.BLUE} ______ _ _ ___ ___
+${AnsiColor.BLUE} | ___ \| | | | \ \ / /
+${AnsiColor.BLUE} | |_/ /| | __ _ __| | ___ \ V /
+${AnsiColor.BLUE} | ___ \| | / _` | / _` | / _ \ > <
+${AnsiColor.BLUE} | |_/ /| || (_| || (_| || __/ / . \
+${AnsiColor.BLUE} \____/ |_| \__,_| \__,_| \___|/__/ \__\
+
+${AnsiColor.BLUE}:: BladeX ${blade.service.version} :: ${spring.application.name}:${AnsiColor.RED}${blade.env}${AnsiColor.BLUE} :: Running SpringBoot ${spring-boot.version} :: ${AnsiColor.BRIGHT_BLACK}
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/resources/blade-boot.yml b/Source/BladeX-Tool/blade-core-boot/src/main/resources/blade-boot.yml
new file mode 100644
index 0000000..6a07495
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/resources/blade-boot.yml
@@ -0,0 +1,35 @@
+#鏈嶅姟鍣ㄩ厤缃�
+server:
+ undertow:
+ # 绾跨▼閰嶇疆
+ threads:
+ # 璁剧疆IO绾跨▼鏁�, 瀹冧富瑕佹墽琛岄潪闃诲鐨勪换鍔�,瀹冧滑浼氳礋璐e涓繛鎺�, 榛樿璁剧疆姣忎釜CPU鏍稿績涓�涓嚎绋�
+ io: 16
+ # 闃诲浠诲姟绾跨▼姹�, 褰撴墽琛岀被浼約ervlet璇锋眰闃诲鎿嶄綔, undertow浼氫粠杩欎釜绾跨▼姹犱腑鍙栧緱绾跨▼,瀹冪殑鍊艰缃彇鍐充簬绯荤粺鐨勮礋杞�
+ worker: 400
+ # 浠ヤ笅鐨勯厤缃細褰卞搷buffer,杩欎簺buffer浼氱敤浜庢湇鍔″櫒杩炴帴鐨処O鎿嶄綔,鏈夌偣绫讳技netty鐨勬睜鍖栧唴瀛樼鐞�
+ buffer-size: 1024
+ # 鏄惁鍒嗛厤鐨勭洿鎺ュ唴瀛�
+ direct-buffers: true
+ servlet:
+ # 缂栫爜閰嶇疆
+ encoding:
+ charset: UTF-8
+ force: true
+
+#spring閰嶇疆
+spring:
+ servlet:
+ multipart:
+ enabled: true
+ max-file-size: 1024MB
+ max-request-size: 1024MB
+ mvc:
+ throw-exception-if-no-handler-found: true
+ web:
+ resources:
+ add-mappings: false
+ devtools:
+ restart:
+ log-condition-evaluation-delta: false
+
diff --git a/Source/BladeX-Tool/blade-core-boot/src/main/resources/static/favicon.ico b/Source/BladeX-Tool/blade-core-boot/src/main/resources/static/favicon.ico
new file mode 100644
index 0000000..2d915c8
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-boot/src/main/resources/static/favicon.ico
Binary files differ
diff --git a/Source/BladeX-Tool/blade-core-cloud/pom.xml b/Source/BladeX-Tool/blade-core-cloud/pom.xml
new file mode 100644
index 0000000..98ebbb6
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/pom.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-core-cloud</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+
+ <dependencies>
+ <!-- Blade -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-launch</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-context</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-auth</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-loadbalancer</artifactId>
+ </dependency>
+ <!-- Admin -->
+ <dependency>
+ <groupId>de.codecentric</groupId>
+ <artifactId>spring-boot-admin-starter-client</artifactId>
+ </dependency>
+ <!-- Sentinel -->
+ <dependency>
+ <groupId>com.alibaba.cloud</groupId>
+ <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/annotation/ApiVersion.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/annotation/ApiVersion.java
new file mode 100644
index 0000000..6a303a1
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/annotation/ApiVersion.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.cloud.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * header 鐗堟湰 澶勭悊
+ *
+ * @author L.cm
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface ApiVersion {
+
+ /**
+ * header 璺緞涓殑鐗堟湰
+ *
+ * @return 鐗堟湰鍙�
+ */
+ String value() default "";
+
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/annotation/UrlVersion.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/annotation/UrlVersion.java
new file mode 100644
index 0000000..e053fe3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/annotation/UrlVersion.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.cloud.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 娉ㄨВ鐢ㄤ簬鐢熸垚 requestMappingInfo 鏃跺�欑洿鎺ユ嫾鎺ヨ矾寰勮鍒欙紝鑷姩鏀剧疆浜庢柟娉曡矾寰勫紑濮嬮儴鍒�
+ *
+ * @author L.cm
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface UrlVersion {
+
+ /**
+ * url 璺緞涓殑鐗堟湰
+ *
+ * @return 鐗堟湰鍙�
+ */
+ String value() default "";
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/annotation/VersionMapping.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/annotation/VersionMapping.java
new file mode 100644
index 0000000..6ac3fbf
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/annotation/VersionMapping.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.cloud.annotation;
+
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import java.lang.annotation.*;
+
+/**
+ * 鐗堟湰鍙峰鐞�
+ *
+ * <p>
+ * 1. url 鐗堟湰鍙凤細娣诲姞鍒� url 鍓�
+ * 2. Accept 鐗堟湰锛歛pplication/vnd.blade.VERSION+json
+ * </p>
+ *
+ * @author L.cm
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping
+@UrlVersion
+@ApiVersion
+@Validated
+public @interface VersionMapping {
+ /**
+ * Alias for {@link RequestMapping#name}.
+ * @return {String[]}
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String name() default "";
+
+ /**
+ * Alias for {@link RequestMapping#value}.
+ * @return {String[]}
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] value() default {};
+
+ /**
+ * Alias for {@link RequestMapping#path}.
+ * @return {String[]}
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] path() default {};
+
+ /**
+ * Alias for {@link RequestMapping#params}.
+ * @return {String[]}
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] params() default {};
+
+ /**
+ * Alias for {@link RequestMapping#headers}.
+ * @return {String[]}
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] headers() default {};
+
+ /**
+ * Alias for {@link RequestMapping#consumes}.
+ * @return {String[]}
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] consumes() default {};
+
+ /**
+ * Alias for {@link RequestMapping#produces}.
+ * default json utf-8
+ * @return {String[]}
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] produces() default {};
+
+ /**
+ * Alias for {@link UrlVersion#value}.
+ * @return {String}
+ */
+ @AliasFor(annotation = UrlVersion.class, attribute = "value")
+ String urlVersion() default "";
+
+ /**
+ * Alias for {@link ApiVersion#value}.
+ * @return {String}
+ */
+ @AliasFor(annotation = ApiVersion.class, attribute = "value")
+ String apiVersion() default "";
+
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/client/BladeCloudApplication.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/client/BladeCloudApplication.java
new file mode 100644
index 0000000..ec4ff61
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/client/BladeCloudApplication.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.cloud.client;
+
+import org.springblade.core.launch.constant.AppConstant;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+import java.lang.annotation.*;
+
+/**
+ * Cloud鍚姩娉ㄨВ閰嶇疆
+ *
+ * @author Chill
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@EnableDiscoveryClient
+@EnableFeignClients(AppConstant.BASE_PACKAGES)
+@SpringBootApplication
+public @interface BladeCloudApplication {
+
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/BladeFallbackFactory.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/BladeFallbackFactory.java
new file mode 100644
index 0000000..c3fedde
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/BladeFallbackFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.cloud.feign;
+
+import feign.Target;
+import org.springframework.cloud.openfeign.FallbackFactory;
+import lombok.AllArgsConstructor;
+import org.springframework.cglib.proxy.Enhancer;
+
+/**
+ * 榛樿 Fallback锛岄伩鍏嶅啓杩囧fallback绫�
+ *
+ * @param <T> 娉涘瀷鏍囪
+ * @author L.cm
+ */
+@AllArgsConstructor
+public class BladeFallbackFactory<T> implements FallbackFactory<T> {
+ private final Target<T> target;
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T create(Throwable cause) {
+ final Class<T> targetType = target.type();
+ final String targetName = target.name();
+ Enhancer enhancer = new Enhancer();
+ enhancer.setSuperclass(targetType);
+ enhancer.setUseCache(true);
+ enhancer.setCallback(new BladeFeignFallback<>(targetType, targetName, cause));
+ return (T) enhancer.create();
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/BladeFeignFallback.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/BladeFeignFallback.java
new file mode 100644
index 0000000..0b7e306
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/BladeFeignFallback.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.cloud.feign;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import feign.FeignException;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.api.ResultCode;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.utils.ObjectUtil;
+import org.springframework.cglib.proxy.MethodInterceptor;
+import org.springframework.cglib.proxy.MethodProxy;
+import org.springframework.lang.Nullable;
+
+import java.lang.reflect.Method;
+import java.util.*;
+
+/**
+ * blade fallBack 浠g悊澶勭悊
+ *
+ * @author L.cm
+ */
+@Slf4j
+@AllArgsConstructor
+public class BladeFeignFallback<T> implements MethodInterceptor {
+ private final Class<T> targetType;
+ private final String targetName;
+ private final Throwable cause;
+ private final static String CODE = "code";
+
+ @Nullable
+ @Override
+ public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
+ String errorMessage = cause.getMessage();
+ log.error("BladeFeignFallback:[{}.{}] serviceId:[{}] message:[{}]", targetType.getName(), method.getName(), targetName, errorMessage);
+ Class<?> returnType = method.getReturnType();
+ // 闆嗗悎绫诲瀷鍙嶉绌洪泦鍚�
+ if (List.class == returnType || Collection.class == returnType) {
+ return Collections.emptyList();
+ }
+ if (Set.class == returnType) {
+ return Collections.emptySet();
+ }
+ if (Map.class == returnType) {
+ return Collections.emptyMap();
+ }
+ // 鏆傛椂涓嶆敮鎸� flux锛宺x锛屽紓姝ョ瓑锛岃繑鍥炲�间笉鏄� R锛岀洿鎺ヨ繑鍥� null銆�
+ if (R.class != returnType) {
+ return null;
+ }
+ // 闈� FeignException
+ if (!(cause instanceof FeignException)) {
+ return R.fail(ResultCode.INTERNAL_SERVER_ERROR, errorMessage);
+ }
+ FeignException exception = (FeignException) cause;
+ byte[] content = exception.content();
+ // 濡傛灉杩斿洖鐨勬暟鎹负绌�
+ if (ObjectUtil.isEmpty(content)) {
+ return R.fail(ResultCode.INTERNAL_SERVER_ERROR, errorMessage);
+ }
+ // 杞崲鎴� jsonNode 璇诲彇锛屽洜涓虹洿鎺ヨ浆鎹紝鍙兘 瀵规柟鏀惧洖鐨勫苟 涓嶆槸 R 鐨勬牸寮忋��
+ JsonNode resultNode = JsonUtil.readTree(content);
+ // 鍒ゆ柇鏄惁 R 鏍煎紡 杩斿洖浣�
+ if (resultNode.has(CODE)) {
+ return JsonUtil.getInstance().convertValue(resultNode, R.class);
+ }
+ return R.fail(resultNode.toString());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ BladeFeignFallback<?> that = (BladeFeignFallback<?>) o;
+ return targetType.equals(that.targetType);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(targetType);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/BladeFeignRequestInterceptor.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/BladeFeignRequestInterceptor.java
new file mode 100644
index 0000000..4373d92
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/BladeFeignRequestInterceptor.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.cloud.feign;
+
+import feign.RequestInterceptor;
+import feign.RequestTemplate;
+import org.springblade.core.tool.constant.BladeConstant;
+import org.springblade.core.tool.utils.ThreadLocalUtil;
+import org.springframework.http.HttpHeaders;
+
+/**
+ * feign 浼犻�扲equest header
+ *
+ * <p>
+ * https://blog.csdn.net/u014519194/article/details/77160958
+ * http://tietang.wang/2016/02/25/hystrix/Hystrix%E5%8F%82%E6%95%B0%E8%AF%A6%E8%A7%A3/
+ * https://github.com/Netflix/Hystrix/issues/92#issuecomment-260548068
+ * </p>
+ *
+ * @author L.cm
+ */
+public class BladeFeignRequestInterceptor implements RequestInterceptor {
+
+ @Override
+ public void apply(RequestTemplate requestTemplate) {
+ HttpHeaders headers = ThreadLocalUtil.get(BladeConstant.CONTEXT_KEY);
+ if (headers != null && !headers.isEmpty()) {
+ headers.forEach((key, values) ->
+ values.forEach(value -> requestTemplate.header(key, value))
+ );
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/EnableBladeFeign.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/EnableBladeFeign.java
new file mode 100644
index 0000000..a436f1e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/EnableBladeFeign.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+
+package org.springblade.core.cloud.feign;
+
+
+import org.springblade.core.launch.constant.AppConstant;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+import java.lang.annotation.*;
+
+/**
+ * 寮�鍚疐eign娉ㄨВ
+ *
+ * @author Chill
+ */
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@EnableFeignClients(AppConstant.BASE_PACKAGES)
+public @interface EnableBladeFeign {
+ /**
+ * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
+ * declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
+ * {@code @ComponentScan(basePackages="org.my.pkg")}.
+ *
+ * @return the array of 'basePackages'.
+ */
+ String[] value() default {};
+
+ /**
+ * Base packages to scan for annotated components.
+ * <p>
+ * {@link #value()} is an alias for (and mutually exclusive with) this attribute.
+ * <p>
+ * Use {@link #basePackageClasses()} for a type-safe alternative to String-based
+ * package names.
+ *
+ * @return the array of 'basePackages'.
+ */
+ String[] basePackages() default {};
+
+ /**
+ * Type-safe alternative to {@link #basePackages()} for specifying the packages to
+ * scan for annotated components. The package of each class specified will be scanned.
+ * <p>
+ * Consider creating a special no-op marker class or interface in each package that
+ * serves no purpose other than being referenced by this attribute.
+ *
+ * @return the array of 'basePackageClasses'.
+ */
+ Class<?>[] basePackageClasses() default {};
+
+ /**
+ * A custom <code>@Configuration</code> for all feign clients. Can contain override
+ * <code>@Bean</code> definition for the pieces that make up the client, for instance
+ * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
+ */
+ Class<?>[] defaultConfiguration() default {};
+
+ /**
+ * List of classes annotated with @FeignClient. If not empty, disables classpath scanning.
+ *
+ * @return
+ */
+ Class<?>[] clients() default {};
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/BladeHttpConfiguration.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/BladeHttpConfiguration.java
new file mode 100644
index 0000000..be274a6
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/BladeHttpConfiguration.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.cloud.http;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+
+/**
+ * http 閰嶇疆
+ *
+ * @author L.cm
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(BladeHttpProperties.class)
+public class BladeHttpConfiguration {
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/BladeHttpProperties.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/BladeHttpProperties.java
new file mode 100644
index 0000000..efe27ba
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/BladeHttpProperties.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.cloud.http;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springblade.core.launch.log.BladeLogLevel;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * http 閰嶇疆
+ *
+ * @author L.cm
+ */
+@Getter
+@Setter
+@RefreshScope
+@ConfigurationProperties("blade.http")
+public class BladeHttpProperties {
+ /**
+ * 鏈�澶ц繛鎺ユ暟锛岄粯璁わ細200
+ */
+ private int maxConnections = 200;
+ /**
+ * 杩炴帴瀛樻椿鏃堕棿锛岄粯璁わ細900L
+ */
+ private long timeToLive = 900L;
+ /**
+ * 杩炴帴姹犲瓨娲绘椂闂村崟浣嶏紝榛樿锛氱
+ */
+ private TimeUnit timeUnit = TimeUnit.SECONDS;
+ /**
+ * 閾炬帴瓒呮椂锛岄粯璁わ細2000姣
+ */
+ private int connectionTimeout = 2000;
+ /**
+ * 鏄惁鏀寔閲嶅畾鍚戯紝榛樿锛歵rue
+ */
+ private boolean followRedirects = true;
+ /**
+ * 鍏抽棴璇佷功鏍¢獙
+ */
+ private boolean disableSslValidation = true;
+ /**
+ * 鏃ュ織绾у埆
+ */
+ private BladeLogLevel level = BladeLogLevel.NONE;
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/LbRestTemplate.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/LbRestTemplate.java
new file mode 100644
index 0000000..b4ac109
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/LbRestTemplate.java
@@ -0,0 +1,27 @@
+package org.springblade.core.cloud.http;
+
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+
+/**
+ * Loadbalancer RestTemplate
+ *
+ * @author L.cm
+ */
+public class LbRestTemplate extends RestTemplate {
+
+ public LbRestTemplate() {
+ super();
+ }
+
+ public LbRestTemplate(ClientHttpRequestFactory requestFactory) {
+ super(requestFactory);
+ }
+
+ public LbRestTemplate(List<HttpMessageConverter<?>> messageConverters) {
+ super(messageConverters);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/RestTemplateConfiguration.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/RestTemplateConfiguration.java
new file mode 100644
index 0000000..354660b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/RestTemplateConfiguration.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.cloud.http;
+
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.ConnectionPool;
+import okhttp3.OkHttpClient;
+import org.springblade.core.cloud.http.logger.HttpLoggingInterceptor;
+import org.springblade.core.cloud.http.logger.OkHttpSlf4jLogger;
+import org.springblade.core.tool.ssl.DisableValidationTrustManager;
+import org.springblade.core.tool.ssl.TrustAllHostNames;
+import org.springblade.core.tool.utils.Charsets;
+import org.springblade.core.tool.utils.Holder;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.cloud.client.loadbalancer.LoadBalanced;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.web.client.RestTemplate;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Http RestTemplateHeaderInterceptor 閰嶇疆
+ *
+ * @author L.cm
+ */
+@Slf4j
+@RequiredArgsConstructor
+@AutoConfiguration
+@ConditionalOnClass(OkHttpClient.class)
+@ConditionalOnProperty(value = "blade.http.enabled", matchIfMissing = true)
+public class RestTemplateConfiguration {
+ private final BladeHttpProperties properties;
+
+ /**
+ * okhttp3 璇锋眰鏃ュ織鎷︽埅鍣�
+ *
+ * @return HttpLoggingInterceptor
+ */
+ @Bean
+ public HttpLoggingInterceptor loggingInterceptor() {
+ HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new OkHttpSlf4jLogger());
+ interceptor.setLevel(properties.getLevel());
+ return interceptor;
+ }
+
+ /**
+ * okhttp3 閾炬帴姹犻厤缃�
+ *
+ * @return okhttp3.ConnectionPool
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ public ConnectionPool httpClientConnectionPool() {
+ int maxTotalConnections = properties.getMaxConnections();
+ long timeToLive = properties.getTimeToLive();
+ TimeUnit ttlUnit = properties.getTimeUnit();
+ return new ConnectionPool(maxTotalConnections, timeToLive, ttlUnit);
+ }
+
+ /**
+ * 閰嶇疆OkHttpClient
+ *
+ * @param connectionPool 閾炬帴姹犻厤缃�
+ * @param interceptor 鎷︽埅鍣�
+ * @return OkHttpClient
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ public OkHttpClient okHttpClient(ConnectionPool connectionPool, HttpLoggingInterceptor interceptor) {
+ boolean followRedirects = properties.isFollowRedirects();
+ int connectTimeout = properties.getConnectionTimeout();
+ return this.createBuilder(properties.isDisableSslValidation())
+ .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
+ .writeTimeout(30, TimeUnit.SECONDS)
+ .readTimeout(30, TimeUnit.SECONDS)
+ .followRedirects(followRedirects)
+ .connectionPool(connectionPool)
+ .addInterceptor(interceptor)
+ .build();
+ }
+
+ private OkHttpClient.Builder createBuilder(boolean disableSslValidation) {
+ OkHttpClient.Builder builder = new OkHttpClient.Builder();
+ if (disableSslValidation) {
+ try {
+ X509TrustManager disabledTrustManager = DisableValidationTrustManager.INSTANCE;
+ TrustManager[] trustManagers = new TrustManager[]{disabledTrustManager};
+ SSLContext sslContext = SSLContext.getInstance("SSL");
+ sslContext.init(null, trustManagers, Holder.SECURE_RANDOM);
+ SSLSocketFactory disabledSslSocketFactory = sslContext.getSocketFactory();
+ builder.sslSocketFactory(disabledSslSocketFactory, disabledTrustManager);
+ builder.hostnameVerifier(TrustAllHostNames.INSTANCE);
+ } catch (NoSuchAlgorithmException | KeyManagementException e) {
+ log.warn("Error setting SSLSocketFactory in OKHttpClient", e);
+ }
+ }
+ return builder;
+ }
+
+ @Bean
+ public RestTemplateHeaderInterceptor requestHeaderInterceptor() {
+ return new RestTemplateHeaderInterceptor();
+ }
+
+ @AutoConfiguration
+ @RequiredArgsConstructor
+ @ConditionalOnClass(OkHttpClient.class)
+ @ConditionalOnProperty(value = "blade.http.rest-template.enable")
+ public static class RestTemplateAutoConfiguration {
+ private final ApplicationContext context;
+
+ /**
+ * 鏅�氱殑 RestTemplate锛屼笉閫忎紶璇锋眰澶达紝涓�鑸彧鍋氬閮� http 璋冪敤
+ *
+ * @param okHttpClient OkHttpClient
+ * @return RestTemplate
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder, OkHttpClient okHttpClient) {
+ restTemplateBuilder.requestFactory(() -> new OkHttp3ClientHttpRequestFactory(okHttpClient));
+ RestTemplate restTemplate = restTemplateBuilder.build();
+ configMessageConverters(context, restTemplate.getMessageConverters());
+ return restTemplate;
+ }
+ }
+
+ @AutoConfiguration
+ @RequiredArgsConstructor
+ @ConditionalOnClass(OkHttpClient.class)
+ @ConditionalOnProperty(value = "blade.http.lb-rest-template.enable")
+ public static class LbRestTemplateAutoConfiguration {
+ private final ApplicationContext context;
+
+ /**
+ * 鏀寔璐熻浇鍧囪 鐨� LbRestTemplate
+ *
+ * @param okHttpClient OkHttpClient
+ * @return LbRestTemplate
+ */
+ @Bean
+ @LoadBalanced
+ @ConditionalOnMissingBean
+ public LbRestTemplate lbRestTemplate(RestTemplateBuilder restTemplateBuilder, OkHttpClient okHttpClient) {
+ restTemplateBuilder.requestFactory(() -> new OkHttp3ClientHttpRequestFactory(okHttpClient));
+ LbRestTemplate restTemplate = restTemplateBuilder.build(LbRestTemplate.class);
+ restTemplate.getInterceptors().add(context.getBean(RestTemplateHeaderInterceptor.class));
+ configMessageConverters(context, restTemplate.getMessageConverters());
+ return restTemplate;
+ }
+ }
+
+ private static void configMessageConverters(ApplicationContext context, List<HttpMessageConverter<?>> converters) {
+ converters.removeIf(x -> x instanceof StringHttpMessageConverter || x instanceof MappingJackson2HttpMessageConverter);
+ converters.add(new StringHttpMessageConverter(Charsets.UTF_8));
+ converters.add(new MappingJackson2HttpMessageConverter(context.getBean(ObjectMapper.class)));
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/RestTemplateHeaderInterceptor.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/RestTemplateHeaderInterceptor.java
new file mode 100644
index 0000000..f61d722
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/RestTemplateHeaderInterceptor.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.cloud.http;
+
+import lombok.AllArgsConstructor;
+import org.springblade.core.tool.constant.BladeConstant;
+import org.springblade.core.tool.utils.ThreadLocalUtil;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.lang.NonNull;
+
+import java.io.IOException;
+
+/**
+ * RestTemplateHeaderInterceptor 浼犻�扲equest header
+ *
+ * @author L.cm
+ */
+@AllArgsConstructor
+public class RestTemplateHeaderInterceptor implements ClientHttpRequestInterceptor {
+ @NonNull
+ @Override
+ public ClientHttpResponse intercept(@NonNull HttpRequest request, @NonNull byte[] bytes, @NonNull ClientHttpRequestExecution execution) throws IOException {
+ HttpHeaders headers = ThreadLocalUtil.get(BladeConstant.CONTEXT_KEY);
+ if (headers != null && !headers.isEmpty()) {
+ HttpHeaders httpHeaders = request.getHeaders();
+ headers.forEach((key, values) -> values.forEach(value -> httpHeaders.add(key, value)));
+ }
+ return execution.execute(request, bytes);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/logger/HttpLoggingInterceptor.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/logger/HttpLoggingInterceptor.java
new file mode 100644
index 0000000..2f7d7be
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/logger/HttpLoggingInterceptor.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.cloud.http.logger;
+
+import okhttp3.*;
+import okhttp3.internal.http.HttpHeaders;
+import okio.Buffer;
+import okio.BufferedSource;
+import okio.GzipSource;
+import org.springblade.core.launch.log.BladeLogLevel;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An OkHttp interceptor which logs request and response information. Can be applied as an
+ * {@linkplain OkHttpClient#interceptors() application interceptor} or as a {@linkplain
+ * OkHttpClient#networkInterceptors() network interceptor}. <p> The format of the logs created by
+ * this class should not be considered stable and may change slightly between releases. If you need
+ * a stable logging format, use your own interceptor.
+ *
+ * @author L.cm
+ */
+public final class HttpLoggingInterceptor implements Interceptor {
+ private static final Charset UTF8 = StandardCharsets.UTF_8;
+ private final Logger logger;
+ private volatile BladeLogLevel level = BladeLogLevel.NONE;
+
+ public interface Logger {
+ /**
+ * log
+ * @param message message
+ */
+ void log(String message);
+ }
+
+ public HttpLoggingInterceptor(Logger logger) {
+ this.logger = logger;
+ }
+
+ /**
+ * Change the level at which this interceptor logs.
+ * @param level log Level
+ * @return HttpLoggingInterceptor
+ */
+ public HttpLoggingInterceptor setLevel(BladeLogLevel level) {
+ this.level = Objects.requireNonNull(level, "level == null. Use Level.NONE instead.");
+ return this;
+ }
+
+ public BladeLogLevel getLevel() {
+ return level;
+ }
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ BladeLogLevel level = this.level;
+
+ Request request = chain.request();
+ if (level == BladeLogLevel.NONE) {
+ return chain.proceed(request);
+ }
+
+ boolean logBody = level == BladeLogLevel.BODY;
+ boolean logHeaders = logBody || level == BladeLogLevel.HEADERS;
+
+ RequestBody requestBody = request.body();
+ boolean hasRequestBody = requestBody != null;
+
+ Connection connection = chain.connection();
+ String requestStartMessage = "--> "
+ + request.method()
+ + ' ' + request.url()
+ + (connection != null ? " " + connection.protocol() : "");
+ if (!logHeaders && hasRequestBody) {
+ requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
+ }
+ logger.log(requestStartMessage);
+
+ if (logHeaders) {
+ if (hasRequestBody) {
+ // Request body headers are only present when installed as a network interceptor. Force
+ // them to be included (when available) so there values are known.
+ if (requestBody.contentType() != null) {
+ logger.log("Content-Type: " + requestBody.contentType());
+ }
+ if (requestBody.contentLength() != -1) {
+ logger.log("Content-Length: " + requestBody.contentLength());
+ }
+ }
+
+ Headers headers = request.headers();
+ for (int i = 0, count = headers.size(); i < count; i++) {
+ String name = headers.name(i);
+ // Skip headers from the request body as they are explicitly logged above.
+ if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
+ logger.log(name + ": " + headers.value(i));
+ }
+ }
+
+ if (!logBody || !hasRequestBody) {
+ logger.log("--> END " + request.method());
+ } else if (bodyHasUnknownEncoding(request.headers())) {
+ logger.log("--> END " + request.method() + " (encoded body omitted)");
+ } else {
+ Buffer buffer = new Buffer();
+ requestBody.writeTo(buffer);
+
+ Charset charset = UTF8;
+ MediaType contentType = requestBody.contentType();
+ if (contentType != null) {
+ charset = contentType.charset(UTF8);
+ }
+
+ logger.log("");
+ if (isPlaintext(buffer)) {
+ logger.log(buffer.readString(charset));
+ logger.log("--> END " + request.method()
+ + " (" + requestBody.contentLength() + "-byte body)");
+ } else {
+ logger.log("--> END " + request.method() + " (binary "
+ + requestBody.contentLength() + "-byte body omitted)");
+ }
+ }
+ }
+
+ long startNs = System.nanoTime();
+ Response response;
+ try {
+ response = chain.proceed(request);
+ } catch (Exception e) {
+ logger.log("<-- HTTP FAILED: " + e);
+ throw e;
+ }
+ long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
+
+ ResponseBody responseBody = response.body();
+ long contentLength = responseBody.contentLength();
+ String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
+ logger.log("<-- "
+ + response.code()
+ + (response.message().isEmpty() ? "" : ' ' + response.message())
+ + ' ' + response.request().url()
+ + " (" + tookMs + "ms" + (!logHeaders ? ", " + bodySize + " body" : "") + ')');
+
+ if (logHeaders) {
+ Headers headers = response.headers();
+ int count = headers.size();
+ for (int i = 0; i < count; i++) {
+ logger.log(headers.name(i) + ": " + headers.value(i));
+ }
+
+ if (!logBody || !HttpHeaders.hasBody(response)) {
+ logger.log("<-- END HTTP");
+ } else if (bodyHasUnknownEncoding(response.headers())) {
+ logger.log("<-- END HTTP (encoded body omitted)");
+ } else {
+ BufferedSource source = responseBody.source();
+ // Buffer the entire body.
+ source.request(Long.MAX_VALUE);
+ Buffer buffer = source.getBuffer();
+
+ Long gzippedLength = null;
+ if ("gzip".equalsIgnoreCase(headers.get("Content-Encoding"))) {
+ gzippedLength = buffer.size();
+ GzipSource gzippedResponseBody = null;
+ try {
+ gzippedResponseBody = new GzipSource(buffer.clone());
+ buffer = new Buffer();
+ buffer.writeAll(gzippedResponseBody);
+ } finally {
+ if (gzippedResponseBody != null) {
+ gzippedResponseBody.close();
+ }
+ }
+ }
+
+ Charset charset = UTF8;
+ MediaType contentType = responseBody.contentType();
+ if (contentType != null) {
+ charset = contentType.charset(UTF8);
+ }
+
+ if (!isPlaintext(buffer)) {
+ logger.log("");
+ logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
+ return response;
+ }
+
+ if (contentLength != 0) {
+ logger.log("");
+ logger.log(buffer.clone().readString(charset));
+ }
+
+ if (gzippedLength != null) {
+ logger.log("<-- END HTTP (" + buffer.size() + "-byte, "
+ + gzippedLength + "-gzipped-byte body)");
+ } else {
+ logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
+ }
+ }
+ }
+
+ return response;
+ }
+
+ /**
+ * Returns true if the body in question probably contains human readable text. Uses a small sample
+ * of code points to detect unicode control characters commonly used in binary file signatures.
+ */
+ private static boolean isPlaintext(Buffer buffer) {
+ try {
+ Buffer prefix = new Buffer();
+ long byteCount = buffer.size() < 64 ? buffer.size() : 64;
+ buffer.copyTo(prefix, 0, byteCount);
+ for (int i = 0; i < 16; i++) {
+ if (prefix.exhausted()) {
+ break;
+ }
+ int codePoint = prefix.readUtf8CodePoint();
+ if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
+ return false;
+ }
+ }
+ return true;
+ } catch (EOFException e) {
+ // Truncated UTF-8 sequence.
+ return false;
+ }
+ }
+
+ private boolean bodyHasUnknownEncoding(Headers headers) {
+ String contentEncoding = headers.get("Content-Encoding");
+ return contentEncoding != null
+ && !"identity".equalsIgnoreCase(contentEncoding)
+ && !"gzip".equalsIgnoreCase(contentEncoding);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/logger/OkHttpSlf4jLogger.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/logger/OkHttpSlf4jLogger.java
new file mode 100644
index 0000000..4467bc5
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/logger/OkHttpSlf4jLogger.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.cloud.http.logger;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * OkHttp Slf4j logger
+ *
+ * @author L.cm
+ */
+@Slf4j
+public class OkHttpSlf4jLogger implements HttpLoggingInterceptor.Logger {
+ @Override
+ public void log(String message) {
+ log.info(message);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeBlockExceptionHandler.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeBlockExceptionHandler.java
new file mode 100644
index 0000000..12ddec4
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeBlockExceptionHandler.java
@@ -0,0 +1,26 @@
+package org.springblade.core.cloud.sentinel;
+
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Sentinel缁熶竴闄愭祦绛栫暐
+ *
+ * @author Chill
+ */
+public class BladeBlockExceptionHandler implements BlockExceptionHandler {
+ @Override
+ public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
+ // Return 429 (Too Many Requests) by default.
+ response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ response.getWriter().print(JsonUtil.toJson(R.fail(e.getMessage())));
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeFeignSentinel.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeFeignSentinel.java
new file mode 100644
index 0000000..883046a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeFeignSentinel.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2013-2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.cloud.sentinel;
+
+import com.alibaba.cloud.sentinel.feign.SentinelContractHolder;
+import feign.Contract;
+import feign.Feign;
+import feign.InvocationHandlerFactory;
+import feign.Target;
+import org.springframework.cloud.openfeign.FallbackFactory;
+import lombok.SneakyThrows;
+import org.springblade.core.cloud.feign.BladeFallbackFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.cloud.openfeign.FeignContext;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.StringUtils;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+/**
+ * feign闆嗘垚sentinel鑷姩閰嶇疆
+ * 閲嶅啓 {@link com.alibaba.cloud.sentinel.feign.SentinelFeign} 閫傞厤鏈�鏂癆PI
+ *
+ * @author Chill
+ */
+public class BladeFeignSentinel {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static final class Builder extends Feign.Builder implements ApplicationContextAware {
+ private Contract contract = new Contract.Default();
+ private ApplicationContext applicationContext;
+ private FeignContext feignContext;
+
+ @Override
+ public Feign.Builder invocationHandlerFactory(
+ InvocationHandlerFactory invocationHandlerFactory) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Builder contract(Contract contract) {
+ this.contract = contract;
+ return this;
+ }
+
+ @Override
+ public Feign build() {
+ super.invocationHandlerFactory(new InvocationHandlerFactory() {
+ @SneakyThrows
+ @Override
+ public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
+ // 娉ㄨВ鍙栧�间互閬垮厤寰幆渚濊禆鐨勯棶棰�
+ FeignClient feignClient = AnnotationUtils.findAnnotation(target.type(), FeignClient.class);
+ Class fallback = feignClient.fallback();
+ Class fallbackFactory = feignClient.fallbackFactory();
+ String contextId = feignClient.contextId();
+
+ if (!StringUtils.hasText(contextId)) {
+ contextId = feignClient.name();
+ }
+
+ Object fallbackInstance;
+ FallbackFactory fallbackFactoryInstance;
+ // 鍒ゆ柇fallback绫诲瀷
+ if (void.class != fallback) {
+ fallbackInstance = getFromContext(contextId, "fallback", fallback, target.type());
+ return new BladeSentinelInvocationHandler(target, dispatch, new FallbackFactory.Default(fallbackInstance));
+ }
+ if (void.class != fallbackFactory) {
+ fallbackFactoryInstance = (FallbackFactory) getFromContext(contextId, "fallbackFactory", fallbackFactory, FallbackFactory.class);
+ return new BladeSentinelInvocationHandler(target, dispatch, fallbackFactoryInstance);
+ }
+ // 榛樿fallbackFactory
+ BladeFallbackFactory bladeFallbackFactory = new BladeFallbackFactory(target);
+ return new BladeSentinelInvocationHandler(target, dispatch, bladeFallbackFactory);
+ }
+
+ private Object getFromContext(String name, String type, Class fallbackType, Class targetType) {
+ Object fallbackInstance = feignContext.getInstance(name, fallbackType);
+ if (fallbackInstance == null) {
+ throw new IllegalStateException(
+ String.format("No %s instance of type %s found for feign client %s",
+ type, fallbackType, name)
+ );
+ }
+
+ if (!targetType.isAssignableFrom(fallbackType)) {
+ throw new IllegalStateException(
+ String.format("Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
+ type, fallbackType, targetType, name)
+ );
+ }
+ return fallbackInstance;
+ }
+ });
+ super.contract(new SentinelContractHolder(contract));
+ return super.build();
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ feignContext = this.applicationContext.getBean(FeignContext.class);
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeSentinelAutoConfiguration.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeSentinelAutoConfiguration.java
new file mode 100644
index 0000000..b72c43e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeSentinelAutoConfiguration.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.cloud.sentinel;
+
+import com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
+import feign.Feign;
+import feign.RequestInterceptor;
+import lombok.AllArgsConstructor;
+import org.springblade.core.cloud.feign.BladeFeignRequestInterceptor;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.context.annotation.Scope;
+
+/**
+ * Sentinel閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+@AutoConfiguration(before = SentinelFeignAutoConfiguration.class)
+@ConditionalOnProperty(name = "feign.sentinel.enabled")
+public class BladeSentinelAutoConfiguration {
+
+ @Bean
+ @Primary
+ @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+ public Feign.Builder feignSentinelBuilder(RequestInterceptor requestInterceptor) {
+ return BladeFeignSentinel.builder().requestInterceptor(requestInterceptor);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public RequestInterceptor requestInterceptor() {
+ return new BladeFeignRequestInterceptor();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public BlockExceptionHandler blockExceptionHandler() {
+ return new BladeBlockExceptionHandler();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeSentinelFilterConfiguration.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeSentinelFilterConfiguration.java
new file mode 100644
index 0000000..2877ab5
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeSentinelFilterConfiguration.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.cloud.sentinel;
+
+import com.alibaba.cloud.sentinel.SentinelProperties;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebInterceptor;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.DefaultBlockExceptionHandler;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.UrlCleaner;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcConfig;
+import lombok.RequiredArgsConstructor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
+import org.springframework.util.StringUtils;
+
+import java.util.Optional;
+
+/**
+ * 澶勭悊sentinel2021鍏煎闂
+ *
+ * @author Chill
+ */
+@RequiredArgsConstructor
+@Import(BladeSentinelFilterConfiguration.class)
+@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
+public class BladeSentinelFilterConfiguration {
+
+ @Bean
+ public SentinelWebInterceptor sentinelWebInterceptor(SentinelWebMvcConfig sentinelWebMvcConfig) {
+ return new SentinelWebInterceptor(sentinelWebMvcConfig);
+ }
+
+ @Bean
+ public SentinelWebMvcConfig sentinelWebMvcConfig(SentinelProperties properties,
+ Optional<UrlCleaner> urlCleanerOptional, Optional<BlockExceptionHandler> blockExceptionHandlerOptional,
+ Optional<RequestOriginParser> requestOriginParserOptional) {
+ SentinelWebMvcConfig sentinelWebMvcConfig = new SentinelWebMvcConfig();
+ sentinelWebMvcConfig.setHttpMethodSpecify(properties.getHttpMethodSpecify());
+ sentinelWebMvcConfig.setWebContextUnify(properties.getWebContextUnify());
+
+ if (blockExceptionHandlerOptional.isPresent()) {
+ blockExceptionHandlerOptional.ifPresent(sentinelWebMvcConfig::setBlockExceptionHandler);
+ } else {
+ if (StringUtils.hasText(properties.getBlockPage())) {
+ sentinelWebMvcConfig.setBlockExceptionHandler(
+ ((request, response, e) -> response.sendRedirect(properties.getBlockPage())));
+ } else {
+ sentinelWebMvcConfig.setBlockExceptionHandler(new DefaultBlockExceptionHandler());
+ }
+ }
+
+ urlCleanerOptional.ifPresent(sentinelWebMvcConfig::setUrlCleaner);
+ requestOriginParserOptional.ifPresent(sentinelWebMvcConfig::setOriginParser);
+ return sentinelWebMvcConfig;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeSentinelInvocationHandler.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeSentinelInvocationHandler.java
new file mode 100644
index 0000000..7a83969
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/sentinel/BladeSentinelInvocationHandler.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.cloud.sentinel;
+
+import com.alibaba.cloud.sentinel.feign.SentinelContractHolder;
+import com.alibaba.csp.sentinel.Entry;
+import com.alibaba.csp.sentinel.EntryType;
+import com.alibaba.csp.sentinel.SphU;
+import com.alibaba.csp.sentinel.Tracer;
+import com.alibaba.csp.sentinel.context.ContextUtil;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import feign.Feign;
+import feign.InvocationHandlerFactory;
+import feign.MethodMetadata;
+import feign.Target;
+import org.springframework.cloud.openfeign.FallbackFactory;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static feign.Util.checkNotNull;
+
+/**
+ * 閲嶅啓 {@link com.alibaba.cloud.sentinel.feign.SentinelInvocationHandler} 閫傞厤鏈�鏂癆PI
+ *
+ * @author Chill
+ */
+public class BladeSentinelInvocationHandler implements InvocationHandler {
+
+ private final Target<?> target;
+
+ private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
+
+ private FallbackFactory fallbackFactory;
+
+ private Map<Method, Method> fallbackMethodMap;
+
+ public BladeSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
+ FallbackFactory fallbackFactory) {
+ this.target = checkNotNull(target, "target");
+ this.dispatch = checkNotNull(dispatch, "dispatch");
+ this.fallbackFactory = fallbackFactory;
+ this.fallbackMethodMap = toFallbackMethod(dispatch);
+ }
+
+ public BladeSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
+ this.target = checkNotNull(target, "target");
+ this.dispatch = checkNotNull(dispatch, "dispatch");
+ }
+
+ @Override
+ public Object invoke(final Object proxy, final Method method, final Object[] args)
+ throws Throwable {
+ if ("equals".equals(method.getName())) {
+ try {
+ Object otherHandler = args.length > 0 && args[0] != null
+ ? Proxy.getInvocationHandler(args[0]) : null;
+ return equals(otherHandler);
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ } else if ("hashCode".equals(method.getName())) {
+ return hashCode();
+ } else if ("toString".equals(method.getName())) {
+ return toString();
+ }
+
+ Object result;
+ InvocationHandlerFactory.MethodHandler methodHandler = this.dispatch.get(method);
+ // only handle by HardCodedTarget
+ if (target instanceof Target.HardCodedTarget) {
+ Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target;
+ MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP
+ .get(hardCodedTarget.type().getName()
+ + Feign.configKey(hardCodedTarget.type(), method));
+ // resource default is HttpMethod:protocol://url
+ if (methodMetadata == null) {
+ result = methodHandler.invoke(args);
+ } else {
+ String resourceName = methodMetadata.template().method().toUpperCase()
+ + ":" + hardCodedTarget.url() + methodMetadata.template().path();
+ Entry entry = null;
+ try {
+ ContextUtil.enter(resourceName);
+ entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
+ result = methodHandler.invoke(args);
+ } catch (Throwable ex) {
+ // fallback handle
+ if (!BlockException.isBlockException(ex)) {
+ Tracer.trace(ex);
+ }
+ if (fallbackFactory != null) {
+ try {
+ Object fallbackResult = fallbackMethodMap.get(method)
+ .invoke(fallbackFactory.create(ex), args);
+ return fallbackResult;
+ } catch (IllegalAccessException e) {
+ // shouldn't happen as method is public due to being an
+ // interface
+ throw new AssertionError(e);
+ } catch (InvocationTargetException e) {
+ throw new AssertionError(e.getCause());
+ }
+ } else {
+ // throw exception if fallbackFactory is null
+ throw ex;
+ }
+ } finally {
+ if (entry != null) {
+ entry.exit(1, args);
+ }
+ ContextUtil.exit();
+ }
+ }
+ } else {
+ // other target type using default strategy
+ result = methodHandler.invoke(args);
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof BladeSentinelInvocationHandler) {
+ BladeSentinelInvocationHandler other = (BladeSentinelInvocationHandler) obj;
+ return target.equals(other.target);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return target.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return target.toString();
+ }
+
+ static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
+ Map<Method, Method> result = new LinkedHashMap<>();
+ for (Method method : dispatch.keySet()) {
+ method.setAccessible(true);
+ result.put(method, method);
+ }
+ return result;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/server/UndertowHttp2Configuration.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/server/UndertowHttp2Configuration.java
new file mode 100644
index 0000000..2408ac7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/server/UndertowHttp2Configuration.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.cloud.server;
+
+import io.undertow.Undertow;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
+import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
+import org.springframework.boot.web.server.WebServerFactoryCustomizer;
+import org.springframework.context.annotation.Bean;
+
+import static io.undertow.UndertowOptions.ENABLE_HTTP2;
+
+
+/**
+ * Undertow http2 h2c 閰嶇疆锛屽 servlet 寮�鍚�
+ *
+ * @author L.cm
+ */
+@AutoConfiguration(before = ServletWebServerFactoryAutoConfiguration.class)
+@ConditionalOnClass(Undertow.class)
+public class UndertowHttp2Configuration {
+
+ @Bean
+ public WebServerFactoryCustomizer<UndertowServletWebServerFactory> undertowHttp2WebServerFactoryCustomizer() {
+ return factory -> factory.addBuilderCustomizers(builder -> builder.setServerOption(ENABLE_HTTP2, true));
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/BladeMediaType.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/BladeMediaType.java
new file mode 100644
index 0000000..92fb2af
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/BladeMediaType.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.cloud.version;
+
+import lombok.Getter;
+import org.springframework.http.MediaType;
+
+/**
+ * blade Media Types锛宎pplication/vnd.github.VERSION+json
+ *
+ * <p>
+ * https://developer.github.com/v3/media/
+ * </p>
+ *
+ * @author L.cm
+ */
+@Getter
+public class BladeMediaType {
+ private static final String MEDIA_TYPE_TEMP = "application/vnd.%s.%s+json";
+
+ private final String appName = "blade";
+ private final String version;
+ private final MediaType mediaType;
+
+ public BladeMediaType(String version) {
+ this.version = version;
+ this.mediaType = MediaType.valueOf(String.format(MEDIA_TYPE_TEMP, appName, version));
+ }
+
+ @Override
+ public String toString() {
+ return mediaType.toString();
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/BladeRequestMappingHandlerMapping.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/BladeRequestMappingHandlerMapping.java
new file mode 100644
index 0000000..c88c3a3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/BladeRequestMappingHandlerMapping.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.cloud.version;
+
+import org.springblade.core.cloud.annotation.ApiVersion;
+import org.springblade.core.cloud.annotation.UrlVersion;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.lang.Nullable;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+/**
+ * url鐗堟湰鍙峰鐞� 鍜� header 鐗堟湰澶勭悊
+ *
+ * <p>
+ * url: /v1/user/{id}
+ * header: Accept application/vnd.blade.VERSION+json
+ * </p>
+ *
+ * 娉ㄦ剰锛歝 浠h〃瀹㈡埛绔増鏈�
+ *
+ * @author L.cm
+ */
+public class BladeRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
+
+ @Nullable
+ @Override
+ protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
+ RequestMappingInfo mappinginfo = super.getMappingForMethod(method, handlerType);
+ if (mappinginfo != null) {
+ RequestMappingInfo apiVersionMappingInfo = getApiVersionMappingInfo(method, handlerType);
+ return apiVersionMappingInfo == null ? mappinginfo : apiVersionMappingInfo.combine(mappinginfo);
+ }
+ return null;
+ }
+
+ @Nullable
+ private RequestMappingInfo getApiVersionMappingInfo(Method method, Class<?> handlerType) {
+ // url 涓婄殑鐗堟湰锛屼紭鍏堣幏鍙栨柟娉曚笂鐨勭増鏈�
+ UrlVersion urlVersion = AnnotatedElementUtils.findMergedAnnotation(method, UrlVersion.class);
+ // 鍐嶆灏濊瘯绫讳笂鐨勭増鏈�
+ if (urlVersion == null || StringUtil.isBlank(urlVersion.value())) {
+ urlVersion = AnnotatedElementUtils.findMergedAnnotation(handlerType, UrlVersion.class);
+ }
+ // Media Types 鐗堟湰淇℃伅
+ ApiVersion apiVersion = AnnotatedElementUtils.findMergedAnnotation(method, ApiVersion.class);
+ // 鍐嶆灏濊瘯绫讳笂鐨勭増鏈�
+ if (apiVersion == null || StringUtil.isBlank(apiVersion.value())) {
+ apiVersion = AnnotatedElementUtils.findMergedAnnotation(handlerType, ApiVersion.class);
+ }
+ boolean nonUrlVersion = urlVersion == null || StringUtil.isBlank(urlVersion.value());
+ boolean nonApiVersion = apiVersion == null || StringUtil.isBlank(apiVersion.value());
+ // 鍏堝垽鏂悓鏃朵笉绾湪
+ if (nonUrlVersion && nonApiVersion) {
+ return null;
+ }
+ // 濡傛灉 header 鐗堟湰涓嶅瓨鍦�
+ RequestMappingInfo.Builder mappingInfoBuilder = null;
+ if (nonApiVersion) {
+ mappingInfoBuilder = RequestMappingInfo.paths(urlVersion.value());
+ } else {
+ mappingInfoBuilder = RequestMappingInfo.paths(StringPool.EMPTY);
+ }
+ // 濡傛灉url鐗堟湰涓嶅瓨鍦�
+ if (nonUrlVersion) {
+ String vsersionMediaTypes = new BladeMediaType(apiVersion.value()).toString();
+ mappingInfoBuilder.produces(vsersionMediaTypes);
+ }
+ return mappingInfoBuilder.build();
+ }
+
+ @Override
+ protected void handlerMethodsInitialized(Map<RequestMappingInfo, HandlerMethod> handlerMethods) {
+ // 鎵撳嵃璺敱淇℃伅 spring boot 2.1 鍘绘帀浜嗚繖涓� 鏃ュ織鐨勬墦鍗�
+ if (logger.isInfoEnabled()) {
+ for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
+ RequestMappingInfo mapping = entry.getKey();
+ HandlerMethod handlerMethod = entry.getValue();
+ logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
+ }
+ }
+ super.handlerMethodsInitialized(handlerMethods);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/BladeSpringMvcContract.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/BladeSpringMvcContract.java
new file mode 100644
index 0000000..bc52928
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/BladeSpringMvcContract.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.cloud.version;
+
+import feign.MethodMetadata;
+import org.springblade.core.cloud.annotation.ApiVersion;
+import org.springblade.core.cloud.annotation.UrlVersion;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
+import org.springframework.cloud.openfeign.support.SpringMvcContract;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.http.HttpHeaders;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.List;
+
+/**
+ * 鏀寔 blade-boot 鐨� 鐗堟湰 澶勭悊
+ *
+ * @see org.springblade.core.cloud.annotation.UrlVersion
+ * @see org.springblade.core.cloud.annotation.ApiVersion
+ * @author L.cm
+ */
+public class BladeSpringMvcContract extends SpringMvcContract {
+
+ public BladeSpringMvcContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors, ConversionService conversionService) {
+ super(annotatedParameterProcessors, conversionService);
+ }
+
+ @Override
+ protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
+ if (RequestMapping.class.isInstance(methodAnnotation) || methodAnnotation.annotationType().isAnnotationPresent(RequestMapping.class)) {
+ Class<?> targetType = method.getDeclaringClass();
+ // url 涓婄殑鐗堟湰锛屼紭鍏堣幏鍙栨柟娉曚笂鐨勭増鏈�
+ UrlVersion urlVersion = AnnotatedElementUtils.findMergedAnnotation(method, UrlVersion.class);
+ // 鍐嶆灏濊瘯绫讳笂鐨勭増鏈�
+ if (urlVersion == null || StringUtil.isBlank(urlVersion.value())) {
+ urlVersion = AnnotatedElementUtils.findMergedAnnotation(targetType, UrlVersion.class);
+ }
+ if (urlVersion != null && StringUtil.isNotBlank(urlVersion.value())) {
+ String versionUrl = "/" + urlVersion.value();
+ data.template().uri(versionUrl);
+ }
+
+ // 娉ㄦ剰锛氬湪鐖剁被涔嬪墠 娣诲姞 url鐗堟湰锛屽湪鐖剁被涔嬪悗锛屽鐞� Media Types 鐗堟湰
+ super.processAnnotationOnMethod(data, methodAnnotation, method);
+
+ // 澶勭悊 Media Types 鐗堟湰淇℃伅
+ ApiVersion apiVersion = AnnotatedElementUtils.findMergedAnnotation(method, ApiVersion.class);
+ // 鍐嶆灏濊瘯绫讳笂鐨勭増鏈�
+ if (apiVersion == null || StringUtil.isBlank(apiVersion.value())) {
+ apiVersion = AnnotatedElementUtils.findMergedAnnotation(targetType, ApiVersion.class);
+ }
+ if (apiVersion != null && StringUtil.isNotBlank(apiVersion.value())) {
+ BladeMediaType bladeMediaType = new BladeMediaType(apiVersion.value());
+ data.template().header(HttpHeaders.ACCEPT, bladeMediaType.toString());
+ }
+ }
+ }
+
+ /**
+ * 鍙傝�冿細https://gist.github.com/rmfish/0ed59a9af6c05157be2a60c9acea2a10
+ * @param annotations 娉ㄨВ
+ * @param paramIndex 鍙傛暟绱㈠紩
+ * @return 鏄惁 http 娉ㄨВ
+ */
+ @Override
+ protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
+ boolean httpAnnotation = super.processAnnotationsOnParameter(data, annotations, paramIndex);
+ // 鍦� springMvc 涓鏋滄槸 Get 璇锋眰涓斿弬鏁颁腑鏄璞� 娌℃湁澹版槑涓篅RequestBody 鍒欓粯璁や负 Param
+ if (!httpAnnotation && StringPool.GET.equals(data.template().method().toUpperCase())) {
+ for (Annotation parameterAnnotation : annotations) {
+ if (!(parameterAnnotation instanceof RequestBody)) {
+ return false;
+ }
+ }
+ data.queryMapIndex(paramIndex);
+ return true;
+ }
+ return httpAnnotation;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/BladeWebMvcRegistrations.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/BladeWebMvcRegistrations.java
new file mode 100644
index 0000000..e28e0d0
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/BladeWebMvcRegistrations.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.cloud.version;
+
+import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
+import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+/**
+ * url鐗堟湰鍙峰鐞�
+ *
+ * @author L.cm
+ */
+public class BladeWebMvcRegistrations implements WebMvcRegistrations {
+ @Override
+ public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
+ return new BladeRequestMappingHandlerMapping();
+ }
+
+ @Override
+ public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
+ return null;
+ }
+
+ @Override
+ public ExceptionHandlerExceptionResolver getExceptionHandlerExceptionResolver() {
+ return null;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/VersionMappingAutoConfiguration.java b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/VersionMappingAutoConfiguration.java
new file mode 100644
index 0000000..b4dfa58
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/VersionMappingAutoConfiguration.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.cloud.version;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * url鐗堟湰鍙峰鐞�
+ *
+ * 鍙傝�冿細https://gitee.com/lianqu1990/spring-boot-starter-version-mapping
+ *
+ * @author L.cm
+ */
+@AutoConfiguration
+@ConditionalOnWebApplication
+public class VersionMappingAutoConfiguration {
+ @Bean
+ public WebMvcRegistrations bladeWebMvcRegistrations() {
+ return new BladeWebMvcRegistrations();
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-context/pom.xml b/Source/BladeX-Tool/blade-core-context/pom.xml
new file mode 100644
index 0000000..0258714
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-context/pom.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-core-context</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-tool</artifactId>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+
+</project>
diff --git a/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeCallableWrapper.java b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeCallableWrapper.java
new file mode 100644
index 0000000..9cf755c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeCallableWrapper.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.context;
+
+import org.slf4j.MDC;
+import org.springblade.core.tool.utils.ThreadLocalUtil;
+import org.springframework.lang.Nullable;
+
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+/**
+ * 澶氱嚎绋嬩腑浼犻�� context 鍜� mdc
+ *
+ * @author L.cm
+ */
+public class BladeCallableWrapper<V> implements Callable<V> {
+ private final Callable<V> delegate;
+ private final Map<String, Object> tlMap;
+ /**
+ * logback 涓嬫湁鍙兘涓� null
+ */
+ @Nullable
+ private final Map<String, String> mdcMap;
+
+ public BladeCallableWrapper(Callable<V> callable) {
+ this.delegate = callable;
+ this.tlMap = ThreadLocalUtil.getAll();
+ this.mdcMap = MDC.getCopyOfContextMap();
+ }
+
+ @Override
+ public V call() throws Exception {
+ if (!tlMap.isEmpty()) {
+ ThreadLocalUtil.put(tlMap);
+ }
+ if (mdcMap != null && !mdcMap.isEmpty()) {
+ MDC.setContextMap(mdcMap);
+ }
+ try {
+ return delegate.call();
+ } finally {
+ tlMap.clear();
+ if (mdcMap != null) {
+ mdcMap.clear();
+ }
+ ThreadLocalUtil.clear();
+ MDC.clear();
+ }
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeContext.java b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeContext.java
new file mode 100644
index 0000000..7435430
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeContext.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.context;
+
+import org.springframework.lang.Nullable;
+
+import java.util.function.Function;
+
+/**
+ * Blade寰湇鍔′笂涓嬫枃
+ *
+ * @author L.cm
+ */
+public interface BladeContext {
+
+ /**
+ * 鑾峰彇 璇锋眰 id
+ *
+ * @return 璇锋眰id
+ */
+ @Nullable
+ String getRequestId();
+
+ /**
+ * 璐﹀彿id
+ *
+ * @return 璐﹀彿id
+ */
+ @Nullable
+ String getAccountId();
+
+ /**
+ * 鑾峰彇绉熸埛id
+ *
+ * @return 绉熸埛id
+ */
+ @Nullable
+ String getTenantId();
+
+ /**
+ * 鑾峰彇涓婁笅鏂囦腑鐨勬暟鎹�
+ *
+ * @param ctxKey 涓婁笅鏂囦腑鐨刱ey
+ * @return 杩斿洖瀵硅薄
+ */
+ @Nullable
+ String get(String ctxKey);
+
+ /**
+ * 鑾峰彇涓婁笅鏂囦腑鐨勬暟鎹�
+ *
+ * @param ctxKey 涓婁笅鏂囦腑鐨刱ey
+ * @param function 鍑芥暟寮�
+ * @param <T> 娉涘瀷瀵硅薄
+ * @return 杩斿洖瀵硅薄
+ */
+ @Nullable
+ <T> T get(String ctxKey, Function<String, T> function);
+}
diff --git a/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeHttpHeadersGetter.java b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeHttpHeadersGetter.java
new file mode 100644
index 0000000..00a92d6
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeHttpHeadersGetter.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.context;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.lang.Nullable;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * HttpHeaders 鑾峰彇鍣紝鐢ㄤ簬璺ㄦ湇鍔″拰绾跨▼鐨勪紶閫掞紝
+ * <p>
+ * 鏆傛椂涓嶆敮鎸� webflux銆�
+ *
+ * @author L.cm
+ */
+public interface BladeHttpHeadersGetter {
+
+ /**
+ * 鑾峰彇 HttpHeaders
+ *
+ * @return HttpHeaders
+ */
+ @Nullable
+ HttpHeaders get();
+
+ /**
+ * 鑾峰彇 HttpHeaders
+ *
+ * @param request 璇锋眰
+ * @return HttpHeaders
+ */
+ @Nullable
+ HttpHeaders get(HttpServletRequest request);
+
+}
diff --git a/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeRunnableWrapper.java b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeRunnableWrapper.java
new file mode 100644
index 0000000..ac701b3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeRunnableWrapper.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.context;
+
+import org.slf4j.MDC;
+import org.springblade.core.tool.utils.ThreadLocalUtil;
+import org.springframework.lang.Nullable;
+
+import java.util.Map;
+
+/**
+ * 澶氱嚎绋嬩腑浼犻�� context 鍜� mdc
+ *
+ * @author L.cm
+ */
+public class BladeRunnableWrapper implements Runnable {
+ private final Runnable delegate;
+ private final Map<String, Object> tlMap;
+ /**
+ * logback 涓嬫湁鍙兘涓� null
+ */
+ @Nullable
+ private final Map<String, String> mdcMap;
+
+ public BladeRunnableWrapper(Runnable runnable) {
+ this.delegate = runnable;
+ this.tlMap = ThreadLocalUtil.getAll();
+ this.mdcMap = MDC.getCopyOfContextMap();
+ }
+
+ @Override
+ public void run() {
+ if (!tlMap.isEmpty()) {
+ ThreadLocalUtil.put(tlMap);
+ }
+ if (mdcMap != null && !mdcMap.isEmpty()) {
+ MDC.setContextMap(mdcMap);
+ }
+ try {
+ delegate.run();
+ } finally {
+ tlMap.clear();
+ if (mdcMap != null) {
+ mdcMap.clear();
+ }
+ ThreadLocalUtil.clear();
+ MDC.clear();
+ }
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeServletContext.java b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeServletContext.java
new file mode 100644
index 0000000..a7efafa
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/BladeServletContext.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.context;
+
+import lombok.RequiredArgsConstructor;
+import org.springblade.core.context.props.BladeContextProperties;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.core.tool.utils.ThreadLocalUtil;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.http.HttpHeaders;
+import org.springframework.lang.Nullable;
+
+import java.util.function.Function;
+
+import static org.springblade.core.tool.constant.BladeConstant.CONTEXT_KEY;
+
+/**
+ * blade servlet 涓婁笅鏂囷紝璺ㄧ嚎绋嬪け鏁�
+ *
+ * @author L.cm
+ */
+@RequiredArgsConstructor
+@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
+public class BladeServletContext implements BladeContext {
+ private final BladeContextProperties contextProperties;
+ private final BladeHttpHeadersGetter httpHeadersGetter;
+
+ @Nullable
+ @Override
+ public String getRequestId() {
+ return get(contextProperties.getHeaders().getRequestId());
+ }
+
+ @Nullable
+ @Override
+ public String getAccountId() {
+ return get(contextProperties.getHeaders().getAccountId());
+ }
+
+ @Nullable
+ @Override
+ public String getTenantId() {
+ return get(contextProperties.getHeaders().getTenantId());
+ }
+
+ @Nullable
+ @Override
+ public String get(String ctxKey) {
+ HttpHeaders headers = ThreadLocalUtil.getIfAbsent(CONTEXT_KEY, httpHeadersGetter::get);
+ if (headers == null || headers.isEmpty()) {
+ return null;
+ }
+ return headers.getFirst(ctxKey);
+ }
+
+ @Nullable
+ @Override
+ public <T> T get(String ctxKey, Function<String, T> function) {
+ String ctxValue = get(ctxKey);
+ if (StringUtil.isBlank(ctxValue)) {
+ return null;
+ }
+ return function.apply(ctxKey);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/ServletHttpHeadersGetter.java b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/ServletHttpHeadersGetter.java
new file mode 100644
index 0000000..06781c6
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/ServletHttpHeadersGetter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.context;
+
+import lombok.RequiredArgsConstructor;
+import org.springblade.core.context.props.BladeContextProperties;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.core.tool.utils.WebUtil;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.http.HttpHeaders;
+import org.springframework.lang.Nullable;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Enumeration;
+import java.util.List;
+
+/**
+ * HttpHeaders 鑾峰彇鍣�
+ *
+ * @author L.cm
+ */
+@RequiredArgsConstructor
+@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
+public class ServletHttpHeadersGetter implements BladeHttpHeadersGetter {
+ private final BladeContextProperties properties;
+
+ @Nullable
+ @Override
+ public HttpHeaders get() {
+ HttpServletRequest request = WebUtil.getRequest();
+ if (request == null) {
+ return null;
+ }
+ return get(request);
+ }
+
+ @Nullable
+ @Override
+ public HttpHeaders get(HttpServletRequest request) {
+ HttpHeaders headers = new HttpHeaders();
+ List<String> crossHeaders = properties.getCrossHeaders();
+ // 浼犻�掕姹傚ご
+ Enumeration<String> headerNames = request.getHeaderNames();
+ if (headerNames != null) {
+ List<String> allowed = properties.getHeaders().getAllowed();
+ while (headerNames.hasMoreElements()) {
+ String key = headerNames.nextElement();
+ // 鍙敮鎸侀厤缃殑 header
+ if (crossHeaders.contains(key) || allowed.contains(key)) {
+ String values = request.getHeader(key);
+ // header value 涓嶄负绌虹殑 浼犻��
+ if (StringUtil.isNotBlank(values)) {
+ headers.add(key, values);
+ }
+ }
+ }
+ }
+ return headers;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/config/BladeContextAutoConfiguration.java b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/config/BladeContextAutoConfiguration.java
new file mode 100644
index 0000000..44347b8
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/config/BladeContextAutoConfiguration.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.context.config;
+
+import org.springblade.core.context.BladeContext;
+import org.springblade.core.context.BladeHttpHeadersGetter;
+import org.springblade.core.context.BladeServletContext;
+import org.springblade.core.context.ServletHttpHeadersGetter;
+import org.springblade.core.context.props.BladeContextProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+
+/**
+ * blade 鏈嶅姟涓婁笅鏂囬厤缃�
+ *
+ * @author L.cm
+ */
+@AutoConfiguration
+@Order(Ordered.HIGHEST_PRECEDENCE)
+@EnableConfigurationProperties(BladeContextProperties.class)
+public class BladeContextAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public BladeHttpHeadersGetter bladeHttpHeadersGetter(BladeContextProperties contextProperties) {
+ return new ServletHttpHeadersGetter(contextProperties);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public BladeContext bladeContext(BladeContextProperties contextProperties, BladeHttpHeadersGetter httpHeadersGetter) {
+ return new BladeServletContext(contextProperties, httpHeadersGetter);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/config/BladeServletListenerConfiguration.java b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/config/BladeServletListenerConfiguration.java
new file mode 100644
index 0000000..7212d50
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/config/BladeServletListenerConfiguration.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.context.config;
+
+import org.springblade.core.context.BladeHttpHeadersGetter;
+import org.springblade.core.context.listener.BladeServletRequestListener;
+import org.springblade.core.context.props.BladeContextProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * Servlet 鐩戝惉鍣ㄨ嚜鍔ㄩ厤缃�
+ *
+ * @author L.cm
+ */
+@AutoConfiguration
+@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
+public class BladeServletListenerConfiguration {
+
+ @Bean
+ public ServletListenerRegistrationBean<?> registerCustomListener(BladeContextProperties properties,
+ BladeHttpHeadersGetter httpHeadersGetter) {
+ return new ServletListenerRegistrationBean<>(new BladeServletRequestListener(properties, httpHeadersGetter));
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/listener/BladeServletRequestListener.java b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/listener/BladeServletRequestListener.java
new file mode 100644
index 0000000..9a0468f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/listener/BladeServletRequestListener.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.context.listener;
+
+import lombok.RequiredArgsConstructor;
+import org.slf4j.MDC;
+import org.springblade.core.context.BladeHttpHeadersGetter;
+import org.springblade.core.context.props.BladeContextProperties;
+import org.springblade.core.tool.constant.BladeConstant;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.core.tool.utils.ThreadLocalUtil;
+import org.springframework.http.HttpHeaders;
+
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Servlet 璇锋眰鐩戝惉鍣�
+ *
+ * @author L.cm
+ */
+@RequiredArgsConstructor
+public class BladeServletRequestListener implements ServletRequestListener {
+ private final BladeContextProperties contextProperties;
+ private final BladeHttpHeadersGetter httpHeadersGetter;
+
+ @Override
+ public void requestInitialized(ServletRequestEvent event) {
+ HttpServletRequest request = (HttpServletRequest) event.getServletRequest();
+ // MDC 鑾峰彇閫忎紶鐨� 鍙橀噺
+ BladeContextProperties.Headers headers = contextProperties.getHeaders();
+ String requestId = request.getHeader(headers.getRequestId());
+ if (StringUtil.isNotBlank(requestId)) {
+ MDC.put(BladeConstant.MDC_REQUEST_ID_KEY, requestId);
+ }
+ String accountId = request.getHeader(headers.getAccountId());
+ if (StringUtil.isNotBlank(accountId)) {
+ MDC.put(BladeConstant.MDC_ACCOUNT_ID_KEY, accountId);
+ }
+ String tenantId = request.getHeader(headers.getTenantId());
+ if (StringUtil.isNotBlank(tenantId)) {
+ MDC.put(BladeConstant.MDC_TENANT_ID_KEY, tenantId);
+ }
+ // 澶勭悊 context锛岀洿鎺ヤ紶閫� request锛屽洜涓� spring 涓殑灏氭湭鍒濆鍖栧畬鎴�
+ HttpHeaders httpHeaders = httpHeadersGetter.get(request);
+ ThreadLocalUtil.put(BladeConstant.CONTEXT_KEY, httpHeaders);
+ }
+
+ @Override
+ public void requestDestroyed(ServletRequestEvent event) {
+ // 浼氳瘽閿�姣佹椂锛屾竻闄や笂涓嬫枃
+ ThreadLocalUtil.clear();
+ // 浼氳瘽閿�姣佹椂锛屾竻闄� mdc
+ MDC.remove(BladeConstant.MDC_REQUEST_ID_KEY);
+ MDC.remove(BladeConstant.MDC_ACCOUNT_ID_KEY);
+ MDC.remove(BladeConstant.MDC_TENANT_ID_KEY);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/props/BladeContextProperties.java b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/props/BladeContextProperties.java
new file mode 100644
index 0000000..c800cdc
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-context/src/main/java/org/springblade/core/context/props/BladeContextProperties.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.context.props;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springblade.core.launch.constant.TokenConstant;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Headers 閰嶇疆
+ *
+ * @author L.cm
+ */
+@Getter
+@Setter
+@ConfigurationProperties(BladeContextProperties.PREFIX)
+public class BladeContextProperties {
+ /**
+ * 閰嶇疆鍓嶇紑
+ */
+ public static final String PREFIX = "blade.context";
+ /**
+ * 涓婁笅鏂囦紶閫掔殑 headers 淇℃伅
+ */
+ private Headers headers = new Headers();
+
+ @Getter
+ @Setter
+ public static class Headers {
+ /**
+ * 璇锋眰id锛岄粯璁わ細Blade-RequestId
+ */
+ private String requestId = "Blade-RequestId";
+ /**
+ * 鐢ㄤ簬 鑱氬悎灞� 鍚戣皟鐢ㄥ眰浼犻�掔敤鎴蜂俊鎭� 鐨勮姹傚ご锛岄粯璁わ細Blade-AccountId
+ */
+ private String accountId = "Blade-AccountId";
+ /**
+ * 鐢ㄤ簬 鑱氬悎灞� 鍚戣皟鐢ㄥ眰浼犻�掔鎴穒d 鐨勮姹傚ご锛岄粯璁わ細Blade-TenantId
+ */
+ private String tenantId = "Blade-TenantId";
+ /**
+ * 鑷畾涔� RestTemplate 鍜� Feign 閫忎紶鍒颁笅灞傜殑 Headers 鍚嶇О鍒楄〃
+ */
+ private List<String> allowed = Arrays.asList("X-Real-IP", "x-forwarded-for", "version", "VERSION", "authorization", "Authorization", TokenConstant.HEADER.toLowerCase(), TokenConstant.HEADER);
+ }
+
+ /**
+ * 鑾峰彇璺ㄦ湇鍔$殑璇锋眰澶�
+ *
+ * @return 璇锋眰澶村垪琛�
+ */
+ public List<String> getCrossHeaders() {
+ List<String> headerList = new ArrayList<>();
+ headerList.add(headers.getRequestId());
+ headerList.add(headers.getAccountId());
+ headerList.add(headers.getTenantId());
+ headerList.addAll(headers.getAllowed());
+ return headerList;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-db/pom.xml b/Source/BladeX-Tool/blade-core-db/pom.xml
new file mode 100644
index 0000000..a0d7c42
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-db/pom.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-core-db</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!--Blade-->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-tool</artifactId>
+ </dependency>
+ <!--Spring-->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-jdbc</artifactId>
+ <exclusions>
+ <exclusion>
+ <artifactId>tomcat-jdbc</artifactId>
+ <groupId>org.apache.tomcat</groupId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <!--Mybatis-->
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-boot-starter</artifactId>
+ </dependency>
+ <!-- Druid -->
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>druid-spring-boot-starter</artifactId>
+ </dependency>
+ <!-- MySql -->
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ </dependency>
+ <!-- Oracle -->
+ <dependency>
+ <groupId>com.oracle</groupId>
+ <artifactId>ojdbc7</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <!-- PostgreSql -->
+ <dependency>
+ <groupId>org.postgresql</groupId>
+ <artifactId>postgresql</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <!-- SqlServer -->
+ <dependency>
+ <groupId>com.microsoft.sqlserver</groupId>
+ <artifactId>mssql-jdbc</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <!-- DaMeng -->
+ <dependency>
+ <groupId>com.dameng</groupId>
+ <artifactId>DmJdbcDriver18</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-core-db/src/main/java/org/springblade/core/db/config/DbConfiguration.java b/Source/BladeX-Tool/blade-core-db/src/main/java/org/springblade/core/db/config/DbConfiguration.java
new file mode 100644
index 0000000..3cc1f0f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-db/src/main/java/org/springblade/core/db/config/DbConfiguration.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.db.config;
+
+import org.springblade.core.launch.props.BladePropertySource;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+
+/**
+ * 鏁版嵁婧愰厤缃被
+ *
+ * @author Chill
+ */
+@AutoConfiguration
+@BladePropertySource(value = "classpath:/blade-db.yml")
+public class DbConfiguration {
+
+}
diff --git a/Source/BladeX-Tool/blade-core-db/src/main/java/org/springblade/core/db/package-info.java b/Source/BladeX-Tool/blade-core-db/src/main/java/org/springblade/core/db/package-info.java
new file mode 100644
index 0000000..06ffb6a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-db/src/main/java/org/springblade/core/db/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Created by Blade.
+ *
+ * @author zhuangqian
+ */
+package org.springblade.core.db;
diff --git a/Source/BladeX-Tool/blade-core-db/src/main/resources/blade-db.yml b/Source/BladeX-Tool/blade-core-db/src/main/resources/blade-db.yml
new file mode 100644
index 0000000..cb28d17
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-db/src/main/resources/blade-db.yml
@@ -0,0 +1,39 @@
+#spring-datasource閰嶇疆
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ druid:
+ initial-size: 5
+ max-active: 20
+ min-idle: 5
+ max-wait: 60000
+ # MySql銆丳ostgreSQL鏍¢獙
+ validation-query: select 1
+ # Oracle鏍¢獙
+ #validation-query: select 1 from dual
+ validation-query-timeout: 2000
+ test-on-borrow: false
+ test-on-return: false
+ test-while-idle: true
+ time-between-eviction-runs-millis: 60000
+ min-evictable-idle-time-millis: 300000
+ stat-view-servlet:
+ enabled: true
+ login-username: blade
+ login-password: 1qaz@WSX
+ web-stat-filter:
+ enabled: true
+ url-pattern: /*
+ exclusions: '*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*'
+ session-stat-enable: true
+ session-stat-max-count: 10
+ #hikari:
+ #connection-test-query: SELECT 1 FROM DUAL
+ #connection-timeout: 30000
+ #maximum-pool-size: 5
+ #max-lifetime: 1800000
+ #minimum-idle: 1
+
+
+
+
diff --git a/Source/BladeX-Tool/blade-core-launch/pom.xml b/Source/BladeX-Tool/blade-core-launch/pom.xml
new file mode 100644
index 0000000..b7ae3b8
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-core-launch</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!--Spring-->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-tomcat</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-undertow</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-starter-bootstrap</artifactId>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/BladeApplication.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/BladeApplication.java
new file mode 100644
index 0000000..34c52b6
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/BladeApplication.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.launch;
+
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.launch.constant.NacosConstant;
+import org.springblade.core.launch.service.LauncherService;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.env.*;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 椤圭洰鍚姩鍣紝鎼炲畾鐜鍙橀噺闂
+ *
+ * @author Chill
+ */
+public class BladeApplication {
+
+ /**
+ * Create an application context
+ * java -jar app.jar --spring.profiles.active=prod --server.port=2333
+ *
+ * @param appName application name
+ * @param source The sources
+ * @return an application context created from the current state
+ */
+ public static ConfigurableApplicationContext run(String appName, Class source, String... args) {
+ SpringApplicationBuilder builder = createSpringApplicationBuilder(appName, source, args);
+ return builder.run(args);
+ }
+
+ public static SpringApplicationBuilder createSpringApplicationBuilder(String appName, Class source, String... args) {
+ Assert.hasText(appName, "[appName]鏈嶅姟鍚嶄笉鑳戒负绌�");
+ // 璇诲彇鐜鍙橀噺锛屼娇鐢╯pring boot鐨勮鍒�
+ ConfigurableEnvironment environment = new StandardEnvironment();
+ MutablePropertySources propertySources = environment.getPropertySources();
+ propertySources.addFirst(new SimpleCommandLinePropertySource(args));
+ propertySources.addLast(new MapPropertySource(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, environment.getSystemProperties()));
+ propertySources.addLast(new SystemEnvironmentPropertySource(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, environment.getSystemEnvironment()));
+ // 鑾峰彇閰嶇疆鐨勭幆澧冨彉閲�
+ String[] activeProfiles = environment.getActiveProfiles();
+ // 鍒ゆ柇鐜:dev銆乼est銆乸rod
+ List<String> profiles = Arrays.asList(activeProfiles);
+ // 棰勮鐨勭幆澧�
+ List<String> presetProfiles = new ArrayList<>(Arrays.asList(AppConstant.DEV_CODE, AppConstant.TEST_CODE, AppConstant.PROD_CODE));
+ // 浜ら泦
+ presetProfiles.retainAll(profiles);
+ // 褰撳墠浣跨敤
+ List<String> activeProfileList = new ArrayList<>(profiles);
+ Function<Object[], String> joinFun = StringUtils::arrayToCommaDelimitedString;
+ SpringApplicationBuilder builder = new SpringApplicationBuilder(source);
+ String profile;
+ if (activeProfileList.isEmpty()) {
+ // 榛樿dev寮�鍙�
+ profile = AppConstant.DEV_CODE;
+ activeProfileList.add(profile);
+ builder.profiles(profile);
+ } else if (activeProfileList.size() == 1) {
+ profile = activeProfileList.get(0);
+ } else {
+ // 鍚屾椂瀛樺湪dev銆乼est銆乸rod鐜鏃�
+ throw new RuntimeException("鍚屾椂瀛樺湪鐜鍙橀噺:[" + StringUtils.arrayToCommaDelimitedString(activeProfiles) + "]");
+ }
+ String startJarPath = BladeApplication.class.getResource("/").getPath().split("!")[0];
+ String activePros = joinFun.apply(activeProfileList.toArray());
+ System.out.printf("----鍚姩涓紝璇诲彇鍒扮殑鐜鍙橀噺:[%s]锛宩ar鍦板潃:[%s]----%n", activePros, startJarPath);
+ Properties props = System.getProperties();
+ props.setProperty("spring.application.name", appName);
+ props.setProperty("spring.profiles.active", profile);
+ props.setProperty("info.version", AppConstant.APPLICATION_VERSION);
+ props.setProperty("info.desc", appName);
+ props.setProperty("file.encoding", StandardCharsets.UTF_8.name());
+ props.setProperty("blade.env", profile);
+ props.setProperty("blade.name", appName);
+ props.setProperty("blade.is-local", String.valueOf(isLocalDev()));
+ props.setProperty("blade.dev-mode", profile.equals(AppConstant.PROD_CODE) ? "false" : "true");
+ props.setProperty("blade.service.version", AppConstant.APPLICATION_VERSION);
+ props.setProperty("loadbalancer.client.name", appName);
+ Properties defaultProperties = new Properties();
+ defaultProperties.setProperty("spring.main.allow-bean-definition-overriding", "true");
+ defaultProperties.setProperty("spring.sleuth.sampler.percentage", "1.0");
+ defaultProperties.setProperty("spring.cloud.alibaba.seata.tx-service-group", appName.concat(NacosConstant.NACOS_GROUP_SUFFIX));
+ defaultProperties.setProperty("spring.cloud.nacos.config.file-extension", NacosConstant.NACOS_CONFIG_FORMAT);
+ defaultProperties.setProperty("spring.cloud.nacos.config.shared-configs[0].data-id", NacosConstant.sharedDataId());
+ defaultProperties.setProperty("spring.cloud.nacos.config.shared-configs[0].group", NacosConstant.NACOS_CONFIG_GROUP);
+ defaultProperties.setProperty("spring.cloud.nacos.config.shared-configs[0].refresh", NacosConstant.NACOS_CONFIG_REFRESH);
+ defaultProperties.setProperty("spring.cloud.nacos.config.shared-configs[1].data-id", NacosConstant.sharedDataId(profile));
+ defaultProperties.setProperty("spring.cloud.nacos.config.shared-configs[1].group", NacosConstant.NACOS_CONFIG_GROUP);
+ defaultProperties.setProperty("spring.cloud.nacos.config.shared-configs[1].refresh", NacosConstant.NACOS_CONFIG_REFRESH);
+ builder.properties(defaultProperties);
+ // 鍔犺浇鑷畾涔夌粍浠�
+ List<LauncherService> launcherList = new ArrayList<>();
+ ServiceLoader.load(LauncherService.class).forEach(launcherList::add);
+ launcherList.stream().sorted(Comparator.comparing(LauncherService::getOrder)).collect(Collectors.toList())
+ .forEach(launcherService -> launcherService.launcher(builder, appName, profile, isLocalDev()));
+ return builder;
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁涓烘湰鍦板紑鍙戠幆澧�
+ *
+ * @return boolean
+ */
+ public static boolean isLocalDev() {
+ String osName = System.getProperty("os.name");
+ return StringUtils.hasText(osName) && !(AppConstant.OS_NAME_LINUX.equalsIgnoreCase(osName));
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/StartEventListener.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/StartEventListener.java
new file mode 100644
index 0000000..731205c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/StartEventListener.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.launch;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.web.context.WebServerInitializedEvent;
+import org.springframework.context.event.EventListener;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.env.Environment;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.util.StringUtils;
+
+/**
+ * 椤圭洰鍚姩浜嬩欢閫氱煡
+ *
+ * @author Chill
+ */
+@Slf4j
+@AutoConfiguration
+public class StartEventListener {
+
+ @Async
+ @Order
+ @EventListener(WebServerInitializedEvent.class)
+ public void afterStart(WebServerInitializedEvent event) {
+ Environment environment = event.getApplicationContext().getEnvironment();
+ String appName = environment.getProperty("spring.application.name").toUpperCase();
+ int localPort = event.getWebServer().getPort();
+ String profile = StringUtils.arrayToCommaDelimitedString(environment.getActiveProfiles());
+ log.info("---[{}]---鍚姩瀹屾垚锛屽綋鍓嶄娇鐢ㄧ殑绔彛:[{}]锛岀幆澧冨彉閲�:[{}]---", appName, localPort, profile);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/config/BladeLaunchConfiguration.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/config/BladeLaunchConfiguration.java
new file mode 100644
index 0000000..26f38f7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/config/BladeLaunchConfiguration.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.launch.config;
+
+import lombok.AllArgsConstructor;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+
+/**
+ * 閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@AutoConfiguration
+@AllArgsConstructor
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class BladeLaunchConfiguration {
+
+}
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/config/BladePropertyConfiguration.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/config/BladePropertyConfiguration.java
new file mode 100644
index 0000000..bdb7734
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/config/BladePropertyConfiguration.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.launch.config;
+
+import org.springblade.core.launch.props.BladeProperties;
+import org.springblade.core.launch.props.BladePropertySourcePostProcessor;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+
+/**
+ * blade property config
+ *
+ * @author L.cm
+ */
+@AutoConfiguration
+@Order(Ordered.HIGHEST_PRECEDENCE)
+@EnableConfigurationProperties(BladeProperties.class)
+public class BladePropertyConfiguration {
+
+ @Bean
+ public BladePropertySourcePostProcessor bladePropertySourcePostProcessor() {
+ return new BladePropertySourcePostProcessor();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/AppConstant.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/AppConstant.java
new file mode 100644
index 0000000..dad8d3a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/AppConstant.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.launch.constant;
+
+/**
+ * 绯荤粺甯搁噺
+ *
+ * @author Chill
+ */
+public interface AppConstant {
+
+ /**
+ * 搴旂敤鐗堟湰
+ */
+ String APPLICATION_VERSION = "3.0.1.RELEASE";
+
+ /**
+ * 鍩虹鍖�
+ */
+ String BASE_PACKAGES = "org.springblade";
+
+ /**
+ * 搴旂敤鍚嶅墠缂�
+ */
+ String APPLICATION_NAME_PREFIX = "blade-";
+ /**
+ * 缃戝叧妯″潡鍚嶇О
+ */
+ String APPLICATION_GATEWAY_NAME = APPLICATION_NAME_PREFIX + "gateway";
+ /**
+ * 鎺堟潈妯″潡鍚嶇О
+ */
+ String APPLICATION_AUTH_NAME = APPLICATION_NAME_PREFIX + "auth";
+ /**
+ * 鐩戞帶妯″潡鍚嶇О
+ */
+ String APPLICATION_ADMIN_NAME = APPLICATION_NAME_PREFIX + "admin";
+ /**
+ * 鎶ヨ〃绯荤粺鍚嶇О
+ */
+ String APPLICATION_REPORT_NAME = APPLICATION_NAME_PREFIX + "report";
+ /**
+ * 闆嗙兢鐩戞帶鍚嶇О
+ */
+ String APPLICATION_TURBINE_NAME = APPLICATION_NAME_PREFIX + "turbine";
+ /**
+ * 閾捐矾杩借釜鍚嶇О
+ */
+ String APPLICATION_ZIPKIN_NAME = APPLICATION_NAME_PREFIX + "zipkin";
+ /**
+ * websocket鍚嶇О
+ */
+ String APPLICATION_WEBSOCKET_NAME = APPLICATION_NAME_PREFIX + "websocket";
+ /**
+ * 棣栭〉妯″潡鍚嶇О
+ */
+ String APPLICATION_DESK_NAME = APPLICATION_NAME_PREFIX + "desk";
+ /**
+ * 绯荤粺妯″潡鍚嶇О
+ */
+ String APPLICATION_SYSTEM_NAME = APPLICATION_NAME_PREFIX + "system";
+ /**
+ * 鐢ㄦ埛妯″潡鍚嶇О
+ */
+ String APPLICATION_USER_NAME = APPLICATION_NAME_PREFIX + "user";
+ /**
+ * 鏃ュ織妯″潡鍚嶇О
+ */
+ String APPLICATION_LOG_NAME = APPLICATION_NAME_PREFIX + "log";
+ /**
+ * 寮�鍙戞ā鍧楀悕绉�
+ */
+ String APPLICATION_DEVELOP_NAME = APPLICATION_NAME_PREFIX + "develop";
+ /**
+ * 娴佺▼璁捐鍣ㄦā鍧楀悕绉�
+ */
+ String APPLICATION_FLOWDESIGN_NAME = APPLICATION_NAME_PREFIX + "flowdesign";
+ /**
+ * 宸ヤ綔娴佹ā鍧楀悕绉�
+ */
+ String APPLICATION_FLOW_NAME = APPLICATION_NAME_PREFIX + "flow";
+ /**
+ * 璧勬簮妯″潡鍚嶇О
+ */
+ String APPLICATION_RESOURCE_NAME = APPLICATION_NAME_PREFIX + "resource";
+ /**
+ * 鎺ュ彛鏂囨。妯″潡鍚嶇О
+ */
+ String APPLICATION_SWAGGER_NAME = APPLICATION_NAME_PREFIX + "swagger";
+ /**
+ * 娴嬭瘯妯″潡鍚嶇О
+ */
+ String APPLICATION_TEST_NAME = APPLICATION_NAME_PREFIX + "test";
+ /**
+ * 婕旂ず妯″潡鍚嶇О
+ */
+ String APPLICATION_DEMO_NAME = APPLICATION_NAME_PREFIX + "demo";
+
+ /**
+ * 寮�鍙戠幆澧�
+ */
+ String DEV_CODE = "dev";
+ /**
+ * 鐢熶骇鐜
+ */
+ String PROD_CODE = "prod";
+ /**
+ * 娴嬭瘯鐜
+ */
+ String TEST_CODE = "test";
+
+ /**
+ * 浠g爜閮ㄧ讲浜� linux 涓婏紝宸ヤ綔榛樿涓� mac 鍜� Windows
+ */
+ String OS_NAME_LINUX = "LINUX";
+
+}
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/ConsulConstant.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/ConsulConstant.java
new file mode 100644
index 0000000..d389198
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/ConsulConstant.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.launch.constant;
+
+/**
+ * Consul甯搁噺.
+ *
+ * @author Chill
+ */
+public interface ConsulConstant {
+
+ /**
+ * consul dev 鍦板潃
+ */
+ String CONSUL_HOST = "http://localhost";
+
+ /**
+ * consul绔彛
+ */
+ String CONSUL_PORT = "8500";
+
+ /**
+ * consul绔彛
+ */
+ String CONSUL_CONFIG_FORMAT = "yaml";
+
+ /**
+ * consul绔彛
+ */
+ String CONSUL_WATCH_DELAY = "1000";
+
+ /**
+ * consul绔彛
+ */
+ String CONSUL_WATCH_ENABLED = "true";
+}
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/FlowConstant.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/FlowConstant.java
new file mode 100644
index 0000000..218027c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/FlowConstant.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.launch.constant;
+
+/**
+ * 娴佺▼甯搁噺.
+ *
+ * @author Chill
+ */
+public interface FlowConstant {
+
+ /**
+ * 浠诲姟鐢ㄦ埛鏍囪瘑鍓嶇紑
+ */
+ String TASK_USR_PREFIX = "taskUser_";
+
+}
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/NacosConstant.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/NacosConstant.java
new file mode 100644
index 0000000..786e690
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/NacosConstant.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.launch.constant;
+
+/**
+ * Nacos甯搁噺.
+ *
+ * @author Chill
+ */
+public interface NacosConstant {
+
+ /**
+ * nacos 鍦板潃
+ */
+ String NACOS_ADDR = "127.0.0.1:8848";
+
+ /**
+ * nacos 閰嶇疆鍓嶇紑
+ */
+ String NACOS_CONFIG_PREFIX = "blade";
+
+ /**
+ * nacos 缁勯厤缃悗缂�
+ */
+ String NACOS_GROUP_SUFFIX = "-group";
+
+ /**
+ * nacos 閰嶇疆鏂囦欢绫诲瀷
+ */
+ String NACOS_CONFIG_FORMAT = "yaml";
+
+ /**
+ * nacos json閰嶇疆鏂囦欢绫诲瀷
+ */
+ String NACOS_CONFIG_JSON_FORMAT = "json";
+
+ /**
+ * nacos 鏄惁鍒锋柊
+ */
+ String NACOS_CONFIG_REFRESH = "true";
+
+ /**
+ * nacos 鍒嗙粍
+ */
+ String NACOS_CONFIG_GROUP = "DEFAULT_GROUP";
+
+ /**
+ * seata 鍒嗙粍
+ */
+ String NACOS_SEATA_GROUP = "SEATA_GROUP";
+
+ /**
+ * 鏋勫缓鏈嶅姟瀵瑰簲鐨� dataId
+ *
+ * @param appName 鏈嶅姟鍚�
+ * @return dataId
+ */
+ static String dataId(String appName) {
+ return appName + "." + NACOS_CONFIG_FORMAT;
+ }
+
+ /**
+ * 鏋勫缓鏈嶅姟瀵瑰簲鐨� dataId
+ *
+ * @param appName 鏈嶅姟鍚�
+ * @param profile 鐜鍙橀噺
+ * @return dataId
+ */
+ static String dataId(String appName, String profile) {
+ return dataId(appName, profile, NACOS_CONFIG_FORMAT);
+ }
+
+ /**
+ * 鏋勫缓鏈嶅姟瀵瑰簲鐨� dataId
+ *
+ * @param appName 鏈嶅姟鍚�
+ * @param profile 鐜鍙橀噺
+ * @param format 鏂囦欢绫诲瀷
+ * @return dataId
+ */
+ static String dataId(String appName, String profile, String format) {
+ return appName + "-" + profile + "." + format;
+ }
+
+ /**
+ * 鏈嶅姟榛樿鍔犺浇鐨勯厤缃�
+ *
+ * @return sharedDataIds
+ */
+ static String sharedDataId() {
+ return NACOS_CONFIG_PREFIX + "." + NACOS_CONFIG_FORMAT;
+ }
+
+ /**
+ * 鏈嶅姟榛樿鍔犺浇鐨勯厤缃�
+ *
+ * @param profile 鐜鍙橀噺
+ * @return sharedDataIds
+ */
+ static String sharedDataId(String profile) {
+ return NACOS_CONFIG_PREFIX + "-" + profile + "." + NACOS_CONFIG_FORMAT;
+ }
+
+ /**
+ * 鏈嶅姟榛樿鍔犺浇鐨勯厤缃�
+ *
+ * @param profile 鐜鍙橀噺
+ * @return sharedDataIds
+ */
+ static String sharedDataIds(String profile) {
+ return NACOS_CONFIG_PREFIX + "." + NACOS_CONFIG_FORMAT + "," + NACOS_CONFIG_PREFIX + "-" + profile + "." + NACOS_CONFIG_FORMAT;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/SentinelConstant.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/SentinelConstant.java
new file mode 100644
index 0000000..662e988
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/SentinelConstant.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.launch.constant;
+
+/**
+ * sentinel閰嶇疆.
+ *
+ * @author Chill
+ */
+public interface SentinelConstant {
+
+ /**
+ * sentinel 鍦板潃
+ */
+ String SENTINEL_ADDR = "127.0.0.1:8858";
+}
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/TokenConstant.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/TokenConstant.java
new file mode 100644
index 0000000..6664fb7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/TokenConstant.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.launch.constant;
+
+/**
+ * Token閰嶇疆甯搁噺.
+ *
+ * @author Chill
+ */
+public interface TokenConstant {
+
+ String AVATAR = "avatar";
+ String HEADER = "Blade-Auth";
+ String BEARER = "bearer";
+ String ACCESS_TOKEN = "access_token";
+ String REFRESH_TOKEN = "refresh_token";
+ String TOKEN_TYPE = "token_type";
+ String EXPIRES_IN = "expires_in";
+ String ACCOUNT = "account";
+ String USER_NAME = "user_name";
+ String NICK_NAME = "nick_name";
+ String REAL_NAME = "real_name";
+ String USER_ID = "user_id";
+ String DEPT_ID = "dept_id";
+ String POST_ID = "post_id";
+ String ROLE_ID = "role_id";
+ String ROLE_NAME = "role_name";
+ String TENANT_ID = "tenant_id";
+ String OAUTH_ID = "oauth_id";
+ String CLIENT_ID = "client_id";
+ String DETAIL = "detail";
+ String LICENSE = "license";
+ String LICENSE_NAME = "powered by bladex";
+ String DEFAULT_AVATAR = "";
+ Integer AUTH_LENGTH = 7;
+
+}
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/ZookeeperConstant.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/ZookeeperConstant.java
new file mode 100644
index 0000000..3bab713
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/constant/ZookeeperConstant.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.launch.constant;
+
+/**
+ * zookeeper 閰嶇疆.
+ *
+ * @author Chill
+ */
+public interface ZookeeperConstant {
+
+ /**
+ * zookeeper id
+ */
+ String ZOOKEEPER_ID = "zk";
+
+ /**
+ * zookeeper connect string
+ */
+ String ZOOKEEPER_CONNECT_STRING = "127.0.0.1:2181";
+
+ /**
+ * zookeeper address
+ */
+ String ZOOKEEPER_ADDRESS = "zookeeper://" + ZOOKEEPER_CONNECT_STRING;
+
+ /**
+ * zookeeper root
+ */
+ String ZOOKEEPER_ROOT = "/blade-services";
+}
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/log/BladeLogLevel.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/log/BladeLogLevel.java
new file mode 100644
index 0000000..4bc26ea
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/log/BladeLogLevel.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.launch.log;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * 璇锋眰鏃ュ織绾у埆锛屾潵婧� okHttp
+ *
+ * @author L.cm
+ */
+@Getter
+@RequiredArgsConstructor
+public enum BladeLogLevel {
+ /**
+ * No logs.
+ */
+ NONE(0),
+
+ /**
+ * Logs request and response lines.
+ *
+ * <p>Example:
+ * <pre>{@code
+ * --> POST /greeting http/1.1 (3-byte body)
+ *
+ * <-- 200 OK (22ms, 6-byte body)
+ * }</pre>
+ */
+ BASIC(1),
+
+ /**
+ * Logs request and response lines and their respective headers.
+ *
+ * <p>Example:
+ * <pre>{@code
+ * --> POST /greeting http/1.1
+ * Host: example.com
+ * Content-Type: plain/text
+ * Content-Length: 3
+ * --> END POST
+ *
+ * <-- 200 OK (22ms)
+ * Content-Type: plain/text
+ * Content-Length: 6
+ * <-- END HTTP
+ * }</pre>
+ */
+ HEADERS(2),
+
+ /**
+ * Logs request and response lines and their respective headers and bodies (if present).
+ *
+ * <p>Example:
+ * <pre>{@code
+ * --> POST /greeting http/1.1
+ * Host: example.com
+ * Content-Type: plain/text
+ * Content-Length: 3
+ *
+ * Hi?
+ * --> END POST
+ *
+ * <-- 200 OK (22ms)
+ * Content-Type: plain/text
+ * Content-Length: 6
+ *
+ * Hello!
+ * <-- END HTTP
+ * }</pre>
+ */
+ BODY(3);
+
+ /**
+ * 璇锋眰鏃ュ織閰嶇疆鍓嶇紑
+ */
+ public static final String REQ_LOG_PROPS_PREFIX = "blade.log.request";
+ /**
+ * 鎺у埗鍙版棩蹇楁槸鍚﹀惎鐢�
+ */
+ public static final String CONSOLE_LOG_ENABLED_PROP = "blade.log.console.enabled";
+
+ /**
+ * 绾у埆
+ */
+ private final int level;
+
+ /**
+ * 褰撳墠鐗堟湰 灏忎簬鍜岀瓑浜� 姣旇緝鐨勭増鏈�
+ *
+ * @param level LogLevel
+ * @return 鏄惁灏忎簬鍜岀瓑浜�
+ */
+ public boolean lte(BladeLogLevel level) {
+ return this.level <= level.level;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/props/BladeProperties.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/props/BladeProperties.java
new file mode 100644
index 0000000..e86860a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/props/BladeProperties.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.launch.props;
+
+import lombok.Getter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.core.env.Environment;
+import org.springframework.core.env.EnvironmentCapable;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 閰嶇疆鏂囦欢
+ *
+ * @author Chill
+ */
+@ConfigurationProperties("blade")
+public class BladeProperties implements EnvironmentAware, EnvironmentCapable {
+ @Nullable
+ private Environment environment;
+
+ /**
+ * 瑁呰浇鑷畾涔夐厤缃産lade.prop.xxx
+ */
+ @Getter
+ private final Map<String, String> prop = new HashMap<>();
+
+ /**
+ * 鑾峰彇閰嶇疆
+ *
+ * @param key key
+ * @return value
+ */
+ @Nullable
+ public String get(String key) {
+ return get(key, null);
+ }
+
+ /**
+ * 鑾峰彇閰嶇疆
+ *
+ * @param key key
+ * @param defaultValue 榛樿鍊�
+ * @return value
+ */
+ @Nullable
+ public String get(String key, @Nullable String defaultValue) {
+ String value = prop.get(key);
+ if (value == null) {
+ return defaultValue;
+ }
+ return value;
+ }
+
+ /**
+ * 鑾峰彇閰嶇疆
+ *
+ * @param key key
+ * @return int value
+ */
+ @Nullable
+ public Integer getInt(String key) {
+ return getInt(key, null);
+ }
+
+ /**
+ * 鑾峰彇閰嶇疆
+ *
+ * @param key key
+ * @param defaultValue 榛樿鍊�
+ * @return int value
+ */
+ @Nullable
+ public Integer getInt(String key, @Nullable Integer defaultValue) {
+ String value = prop.get(key);
+ if (value != null) {
+ return Integer.valueOf(value.trim());
+ }
+ return defaultValue;
+ }
+
+ /**
+ * 鑾峰彇閰嶇疆
+ *
+ * @param key key
+ * @return long value
+ */
+ @Nullable
+ public Long getLong(String key) {
+ return getLong(key, null);
+ }
+
+ /**
+ * 鑾峰彇閰嶇疆
+ *
+ * @param key key
+ * @param defaultValue 榛樿鍊�
+ * @return long value
+ */
+ @Nullable
+ public Long getLong(String key, @Nullable Long defaultValue) {
+ String value = prop.get(key);
+ if (value != null) {
+ return Long.valueOf(value.trim());
+ }
+ return defaultValue;
+ }
+
+ /**
+ * 鑾峰彇閰嶇疆
+ *
+ * @param key key
+ * @return Boolean value
+ */
+ @Nullable
+ public Boolean getBoolean(String key) {
+ return getBoolean(key, null);
+ }
+
+ /**
+ * 鑾峰彇閰嶇疆
+ *
+ * @param key key
+ * @param defaultValue 榛樿鍊�
+ * @return Boolean value
+ */
+ @Nullable
+ public Boolean getBoolean(String key, @Nullable Boolean defaultValue) {
+ String value = prop.get(key);
+ if (value != null) {
+ value = value.toLowerCase().trim();
+ return Boolean.parseBoolean(value);
+ }
+ return defaultValue;
+ }
+
+ /**
+ * 鑾峰彇閰嶇疆
+ *
+ * @param key key
+ * @return double value
+ */
+ @Nullable
+ public Double getDouble(String key) {
+ return getDouble(key, null);
+ }
+
+ /**
+ * 鑾峰彇閰嶇疆
+ *
+ * @param key key
+ * @param defaultValue 榛樿鍊�
+ * @return double value
+ */
+ @Nullable
+ public Double getDouble(String key, @Nullable Double defaultValue) {
+ String value = prop.get(key);
+ if (value != null) {
+ return Double.parseDouble(value.trim());
+ }
+ return defaultValue;
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁瀛樺湪key
+ *
+ * @param key prop key
+ * @return boolean
+ */
+ public boolean containsKey(String key) {
+ return prop.containsKey(key);
+ }
+
+
+ /**
+ * 鐜锛屾柟渚垮湪浠g爜涓幏鍙�
+ *
+ * @return 鐜 env
+ */
+ public String getEnv() {
+ Objects.requireNonNull(environment, "Spring boot 鐜涓� Environment 涓嶅彲鑳戒负null");
+ String env = environment.getProperty("blade.env");
+ Assert.notNull(env, "璇蜂娇鐢� BladeApplication 鍚姩...");
+ return env;
+ }
+
+ /**
+ * 搴旂敤鍚嶇О${spring.application.name}
+ *
+ * @return 搴旂敤鍚�
+ */
+ public String getName() {
+ Objects.requireNonNull(environment, "Spring boot 鐜涓� Environment 涓嶅彲鑳戒负null");
+ return environment.getProperty("spring.application.name", environment.getProperty("blade.name", ""));
+ }
+
+ @Override
+ public void setEnvironment(Environment environment) {
+ this.environment = environment;
+ }
+
+ @Override
+ public Environment getEnvironment() {
+ Objects.requireNonNull(environment, "Spring boot 鐜涓� Environment 涓嶅彲鑳戒负null");
+ return this.environment;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/props/BladePropertySource.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/props/BladePropertySource.java
new file mode 100644
index 0000000..1e1480b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/props/BladePropertySource.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.launch.props;
+
+import org.springframework.core.Ordered;
+
+import java.lang.annotation.*;
+
+/**
+ * 鑷畾涔夎祫婧愭枃浠惰鍙栵紝浼樺厛绾ф渶浣�
+ *
+ * @author L.cm
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface BladePropertySource {
+
+ /**
+ * Indicate the resource location(s) of the properties file to be loaded.
+ * for example, {@code "classpath:/com/example/app.yml"}
+ *
+ * @return location(s)
+ */
+ String value();
+
+ /**
+ * load app-{activeProfile}.yml
+ *
+ * @return {boolean}
+ */
+ boolean loadActiveProfile() default true;
+
+ /**
+ * Get the order value of this resource.
+ *
+ * @return order
+ */
+ int order() default Ordered.LOWEST_PRECEDENCE;
+
+}
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/props/BladePropertySourcePostProcessor.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/props/BladePropertySourcePostProcessor.java
new file mode 100644
index 0000000..8ba8cca
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/props/BladePropertySourcePostProcessor.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.launch.props;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.boot.env.PropertySourceLoader;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.MutablePropertySources;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.support.SpringFactoriesLoader;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 鑷畾涔夎祫婧愭枃浠惰鍙栵紝浼樺厛绾ф渶浣�
+ *
+ * @author L.cm
+ */
+@Slf4j
+public class BladePropertySourcePostProcessor implements BeanFactoryPostProcessor, InitializingBean, Ordered {
+ private final ResourceLoader resourceLoader;
+ private final List<PropertySourceLoader> propertySourceLoaders;
+
+ public BladePropertySourcePostProcessor() {
+ this.resourceLoader = new DefaultResourceLoader();
+ this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader());
+ }
+
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+ log.info("BladePropertySourcePostProcessor process @BladePropertySource bean.");
+ Map<String, Object> beansWithAnnotation = beanFactory.getBeansWithAnnotation(BladePropertySource.class);
+ Set<Map.Entry<String, Object>> beanEntrySet = beansWithAnnotation.entrySet();
+ // 娌℃湁 @YmlPropertySource 娉ㄨВ锛岃烦鍑�
+ if (beanEntrySet.isEmpty()) {
+ log.warn("Not found @BladePropertySource on spring bean class.");
+ return;
+ }
+ // 缁勮璧勬簮
+ List<PropertyFile> propertyFileList = new ArrayList<>();
+ for (Map.Entry<String, Object> entry : beanEntrySet) {
+ Class<?> beanClass = ClassUtils.getUserClass(entry.getValue());
+ BladePropertySource propertySource = AnnotationUtils.getAnnotation(beanClass, BladePropertySource.class);
+ if (propertySource == null) {
+ continue;
+ }
+ int order = propertySource.order();
+ boolean loadActiveProfile = propertySource.loadActiveProfile();
+ String location = propertySource.value();
+ propertyFileList.add(new PropertyFile(order, location, loadActiveProfile));
+ }
+
+ // 瑁呰浇 PropertySourceLoader
+ Map<String, PropertySourceLoader> loaderMap = new HashMap<>(16);
+ for (PropertySourceLoader loader : propertySourceLoaders) {
+ String[] loaderExtensions = loader.getFileExtensions();
+ for (String extension : loaderExtensions) {
+ loaderMap.put(extension, loader);
+ }
+ }
+ // 鍘婚噸锛屾帓搴�
+ List<PropertyFile> sortedPropertyList = propertyFileList.stream()
+ .distinct()
+ .sorted()
+ .collect(Collectors.toList());
+ ConfigurableEnvironment environment = beanFactory.getBean(ConfigurableEnvironment.class);
+ MutablePropertySources propertySources = environment.getPropertySources();
+
+ // 鍙敮鎸� activeProfiles锛屾病鏈夊繀瑕佹敮鎸� spring.profiles.include銆�
+ String[] activeProfiles = environment.getActiveProfiles();
+ ArrayList<PropertySource> propertySourceList = new ArrayList<>();
+ for (String profile : activeProfiles) {
+ for (PropertyFile propertyFile : sortedPropertyList) {
+ // 涓嶅姞杞� ActiveProfile 鐨勯厤缃枃浠�
+ if (!propertyFile.loadActiveProfile) {
+ continue;
+ }
+ String extension = propertyFile.getExtension();
+ PropertySourceLoader loader = loaderMap.get(extension);
+ if (loader == null) {
+ throw new IllegalArgumentException("Can't find PropertySourceLoader for PropertySource extension:" + extension);
+ }
+ String location = propertyFile.getLocation();
+ String filePath = StringUtils.stripFilenameExtension(location);
+ String profiledLocation = filePath + "-" + profile + "." + extension;
+ Resource resource = resourceLoader.getResource(profiledLocation);
+ loadPropertySource(profiledLocation, resource, loader, propertySourceList);
+ }
+ }
+ // 鏈韩鐨� Resource
+ for (PropertyFile propertyFile : sortedPropertyList) {
+ String extension = propertyFile.getExtension();
+ PropertySourceLoader loader = loaderMap.get(extension);
+ String location = propertyFile.getLocation();
+ Resource resource = resourceLoader.getResource(location);
+ loadPropertySource(location, resource, loader, propertySourceList);
+ }
+ // 杞瓨
+ for (PropertySource propertySource : propertySourceList) {
+ propertySources.addLast(propertySource);
+ }
+ }
+
+ private static void loadPropertySource(String location, Resource resource,
+ PropertySourceLoader loader,
+ List<PropertySource> sourceList) {
+ if (resource.exists()) {
+ String name = "bladePropertySource: [" + location + "]";
+ try {
+ sourceList.addAll(loader.load(name, resource));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ log.info("BladePropertySourcePostProcessor init.");
+ }
+
+ @Override
+ public int getOrder() {
+ return Ordered.LOWEST_PRECEDENCE;
+ }
+
+ @Getter
+ @ToString
+ @EqualsAndHashCode
+ private static class PropertyFile implements Comparable<PropertyFile> {
+ private final int order;
+ private final String location;
+ private final String extension;
+ private final boolean loadActiveProfile;
+
+ PropertyFile(int order, String location, boolean loadActiveProfile) {
+ this.order = order;
+ this.location = location;
+ this.loadActiveProfile = loadActiveProfile;
+ this.extension = Objects.requireNonNull(StringUtils.getFilenameExtension(location));
+ }
+
+ @Override
+ public int compareTo(PropertyFile other) {
+ return Integer.compare(this.order, other.order);
+ }
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/server/ServerInfo.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/server/ServerInfo.java
new file mode 100644
index 0000000..0fae5fe
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/server/ServerInfo.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.launch.server;
+
+import lombok.Getter;
+import org.springblade.core.launch.utils.INetUtil;
+import org.springframework.beans.factory.SmartInitializingSingleton;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+
+/**
+ * 鏈嶅姟鍣ㄤ俊鎭�
+ *
+ * @author Chill
+ */
+@Getter
+@AutoConfiguration
+public class ServerInfo implements SmartInitializingSingleton {
+ private final ServerProperties serverProperties;
+ private String hostName;
+ private String ip;
+ private Integer port;
+ private String ipWithPort;
+
+ @Autowired(required = false)
+ public ServerInfo(ServerProperties serverProperties) {
+ this.serverProperties = serverProperties;
+ }
+
+ @Override
+ public void afterSingletonsInstantiated() {
+ this.hostName = INetUtil.getHostName();
+ this.ip = INetUtil.getHostIp();
+ this.port = serverProperties.getPort();
+ this.ipWithPort = String.format("%s:%d", ip, port);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/service/LauncherService.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/service/LauncherService.java
new file mode 100644
index 0000000..80c8f87
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/service/LauncherService.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.launch.service;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.core.Ordered;
+
+/**
+ * launcher 鎵╁睍 鐢ㄤ簬涓�浜涚粍浠跺彂鐜�
+ *
+ * @author Chill
+ */
+public interface LauncherService extends Ordered, Comparable<LauncherService> {
+
+ /**
+ * 鍚姩鏃� 澶勭悊 SpringApplicationBuilder
+ *
+ * @param builder SpringApplicationBuilder
+ * @param appName SpringApplicationAppName
+ * @param profile SpringApplicationProfile
+ * @param isLocalDev SpringApplicationIsLocalDev
+ */
+ void launcher(SpringApplicationBuilder builder, String appName, String profile, boolean isLocalDev);
+
+ /**
+ * 鑾峰彇鎺掑垪椤哄簭
+ *
+ * @return order
+ */
+ @Override
+ default int getOrder() {
+ return 0;
+ }
+
+ /**
+ * 瀵规瘮鎺掑簭
+ *
+ * @param o LauncherService
+ * @return compare
+ */
+ @Override
+ default int compareTo(LauncherService o) {
+ return Integer.compare(this.getOrder(), o.getOrder());
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/utils/INetUtil.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/utils/INetUtil.java
new file mode 100644
index 0000000..26d1caa
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/utils/INetUtil.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.launch.utils;
+
+import org.springframework.util.StringUtils;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.ServerSocket;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+
+/**
+ * INet 鐩稿叧宸ュ叿
+ *
+ * @author L.cm
+ */
+public class INetUtil {
+ public static final String LOCAL_HOST = "127.0.0.1";
+
+ /**
+ * 鑾峰彇 鏈嶅姟鍣� hostname
+ *
+ * @return hostname
+ */
+ public static String getHostName() {
+ String hostname;
+ try {
+ InetAddress address = InetAddress.getLocalHost();
+ // force a best effort reverse DNS lookup
+ hostname = address.getHostName();
+ if (StringUtils.isEmpty(hostname)) {
+ hostname = address.toString();
+ }
+ } catch (UnknownHostException ignore) {
+ hostname = LOCAL_HOST;
+ }
+ return hostname;
+ }
+
+ /**
+ * 鑾峰彇 鏈嶅姟鍣� HostIp
+ *
+ * @return HostIp
+ */
+ public static String getHostIp() {
+ String hostAddress;
+ try {
+ InetAddress address = INetUtil.getLocalHostLANAddress();
+ // force a best effort reverse DNS lookup
+ hostAddress = address.getHostAddress();
+ if (StringUtils.isEmpty(hostAddress)) {
+ hostAddress = address.toString();
+ }
+ } catch (UnknownHostException ignore) {
+ hostAddress = LOCAL_HOST;
+ }
+ return hostAddress;
+ }
+
+ /**
+ * https://stackoverflow.com/questions/9481865/getting-the-ip-address-of-the-current-machine-using-java
+ *
+ * <p>
+ * Returns an <code>InetAddress</code> object encapsulating what is most likely the machine's LAN IP address.
+ * <p/>
+ * This method is intended for use as a replacement of JDK method <code>InetAddress.getLocalHost</code>, because
+ * that method is ambiguous on Linux systems. Linux systems enumerate the loopback network interface the same
+ * way as regular LAN network interfaces, but the JDK <code>InetAddress.getLocalHost</code> method does not
+ * specify the algorithm used to select the address returned under such circumstances, and will often return the
+ * loopback address, which is not valid for network communication. Details
+ * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4665037">here</a>.
+ * <p/>
+ * This method will scan all IP addresses on all network interfaces on the host machine to determine the IP address
+ * most likely to be the machine's LAN address. If the machine has multiple IP addresses, this method will prefer
+ * a site-local IP address (e.g. 192.168.x.x or 10.10.x.x, usually IPv4) if the machine has one (and will return the
+ * first site-local address if the machine has more than one), but if the machine does not hold a site-local
+ * address, this method will return simply the first non-loopback address found (IPv4 or IPv6).
+ * <p/>
+ * If this method cannot find a non-loopback address using this selection algorithm, it will fall back to
+ * calling and returning the result of JDK method <code>InetAddress.getLocalHost</code>.
+ * <p/>
+ *
+ * @throws UnknownHostException If the LAN address of the machine cannot be found.
+ */
+ private static InetAddress getLocalHostLANAddress() throws UnknownHostException {
+ try {
+ InetAddress candidateAddress = null;
+ // Iterate all NICs (network interface cards)...
+ for (Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements(); ) {
+ NetworkInterface iface = (NetworkInterface) ifaces.nextElement();
+ // Iterate all IP addresses assigned to each card...
+ for (Enumeration inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); ) {
+ InetAddress inetAddr = (InetAddress) inetAddrs.nextElement();
+ if (!inetAddr.isLoopbackAddress()) {
+
+ if (inetAddr.isSiteLocalAddress()) {
+ // Found non-loopback site-local address. Return it immediately...
+ return inetAddr;
+ } else if (candidateAddress == null) {
+ // Found non-loopback address, but not necessarily site-local.
+ // Store it as a candidate to be returned if site-local address is not subsequently found...
+ candidateAddress = inetAddr;
+ // Note that we don't repeatedly assign non-loopback non-site-local addresses as candidates,
+ // only the first. For subsequent iterations, candidate will be non-null.
+ }
+ }
+ }
+ }
+ if (candidateAddress != null) {
+ // We did not find a site-local address, but we found some other non-loopback address.
+ // Server might have a non-site-local address assigned to its NIC (or it might be running
+ // IPv6 which deprecates the "site-local" concept).
+ // Return this non-loopback candidate address...
+ return candidateAddress;
+ }
+ // At this point, we did not find a non-loopback address.
+ // Fall back to returning whatever InetAddress.getLocalHost() returns...
+ InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();
+ if (jdkSuppliedAddress == null) {
+ throw new UnknownHostException("The JDK InetAddress.getLocalHost() method unexpectedly returned null.");
+ }
+ return jdkSuppliedAddress;
+ } catch (Exception e) {
+ UnknownHostException unknownHostException = new UnknownHostException("Failed to determine LAN address: " + e);
+ unknownHostException.initCause(e);
+ throw unknownHostException;
+ }
+ }
+
+ /**
+ * 灏濊瘯绔彛鏃跺�欒鍗犵敤
+ *
+ * @param port 绔彛鍙�
+ * @return 娌℃湁琚崰鐢細true,琚崰鐢細false
+ */
+ public static boolean tryPort(int port) {
+ try (ServerSocket ignore = new ServerSocket(port)) {
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * 灏� ip 杞垚 InetAddress
+ *
+ * @param ip ip
+ * @return InetAddress
+ */
+ public static InetAddress getInetAddress(String ip) {
+ try {
+ return InetAddress.getByName(ip);
+ } catch (UnknownHostException e) {
+ return null;
+ }
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁鍐呯綉 ip
+ *
+ * @param ip ip
+ * @return boolean
+ */
+ public static boolean isInternalIp(String ip) {
+ return isInternalIp(getInetAddress(ip));
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁鍐呯綉 ip
+ *
+ * @param address InetAddress
+ * @return boolean
+ */
+ public static boolean isInternalIp(InetAddress address) {
+ if (isLocalIp(address)) {
+ return true;
+ }
+ return isInternalIp(address.getAddress());
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁鏈湴 ip
+ *
+ * @param address InetAddress
+ * @return boolean
+ */
+ public static boolean isLocalIp(InetAddress address) {
+ return address.isAnyLocalAddress()
+ || address.isLoopbackAddress()
+ || address.isSiteLocalAddress();
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁鍐呯綉 ip
+ *
+ * @param addr ip
+ * @return boolean
+ */
+ public static boolean isInternalIp(byte[] addr) {
+ final byte b0 = addr[0];
+ final byte b1 = addr[1];
+ //10.x.x.x/8
+ final byte section1 = 0x0A;
+ //172.16.x.x/12
+ final byte section2 = (byte) 0xAC;
+ final byte section3 = (byte) 0x10;
+ final byte section4 = (byte) 0x1F;
+ //192.168.x.x/16
+ final byte section5 = (byte) 0xC0;
+ final byte section6 = (byte) 0xA8;
+ switch (b0) {
+ case section1:
+ return true;
+ case section2:
+ if (b1 >= section3 && b1 <= section4) {
+ return true;
+ }
+ case section5:
+ if (b1 == section6) {
+ return true;
+ }
+ default:
+ return false;
+ }
+ }
+
+
+}
diff --git a/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/utils/PropsUtil.java b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/utils/PropsUtil.java
new file mode 100644
index 0000000..830e3ae
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-launch/src/main/java/org/springblade/core/launch/utils/PropsUtil.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.launch.utils;
+
+import org.springframework.util.StringUtils;
+
+import java.util.Properties;
+
+/**
+ * 閰嶇疆宸ュ叿绫�
+ *
+ * @author Chill
+ */
+public class PropsUtil {
+
+ /**
+ * 璁剧疆閰嶇疆鍊硷紝宸插瓨鍦ㄥ垯璺宠繃
+ *
+ * @param props property
+ * @param key key
+ * @param value value
+ */
+ public static void setProperty(Properties props, String key, String value) {
+ if (StringUtils.isEmpty(props.getProperty(key))) {
+ props.setProperty(key, value);
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-log4j2/pom.xml b/Source/BladeX-Tool/blade-core-log4j2/pom.xml
new file mode 100644
index 0000000..9f52fb1
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-log4j2/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-core-log4j2</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-launch</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-log4j2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.lmax</groupId>
+ <artifactId>disruptor</artifactId>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-core-log4j2/src/main/java/org/springblade/core/log4j2/LogLauncherServiceImpl.java b/Source/BladeX-Tool/blade-core-log4j2/src/main/java/org/springblade/core/log4j2/LogLauncherServiceImpl.java
new file mode 100644
index 0000000..c0456ba
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-log4j2/src/main/java/org/springblade/core/log4j2/LogLauncherServiceImpl.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.log4j2;
+
+import org.springblade.core.auto.service.AutoService;
+import org.springblade.core.launch.service.LauncherService;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+
+/**
+ * 鏃ュ織鍚姩鍣�
+ *
+ * @author L.cm
+ */
+@AutoService(LauncherService.class)
+public class LogLauncherServiceImpl implements LauncherService {
+
+ @Override
+ public void launcher(SpringApplicationBuilder builder, String appName, String profile, boolean isLocalDev) {
+ System.setProperty("logging.config", String.format("classpath:log/log4j2_%s.xml", profile));
+ // RocketMQ-Client 4.2.0 Log4j2 閰嶇疆鏂囦欢鍐茬獊闂瑙e喅锛歨ttps://www.jianshu.com/p/b30ae6dd3811
+ System.setProperty("rocketmq.client.log.loadconfig", "false");
+ // RocketMQ-Client 4.3 璁剧疆榛樿涓� slf4j
+ System.setProperty("rocketmq.client.logUseSlf4j", "true");
+ // 闈炴湰鍦� 灏� 鍏ㄩ儴鐨� System.err 鍜� System.out 鏇挎崲涓簂og
+ if (!isLocalDev) {
+ System.setOut(LogPrintStream.out());
+ System.setErr(LogPrintStream.err());
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-log4j2/src/main/java/org/springblade/core/log4j2/LogPrintStream.java b/Source/BladeX-Tool/blade-core-log4j2/src/main/java/org/springblade/core/log4j2/LogPrintStream.java
new file mode 100644
index 0000000..334bef7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-log4j2/src/main/java/org/springblade/core/log4j2/LogPrintStream.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.log4j2;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.PrintStream;
+import java.util.Locale;
+
+/**
+ * 鏇挎崲 绯荤粺 System.err 鍜� System.out 涓簂og
+ *
+ * @author L.cm
+ */
+@Slf4j
+public class LogPrintStream extends PrintStream {
+ private final boolean error;
+
+ private LogPrintStream(boolean error) {
+ super(error ? System.err : System.out);
+ this.error = error;
+ }
+
+ public static LogPrintStream out() {
+ return new LogPrintStream(false);
+ }
+
+ public static LogPrintStream err() {
+ return new LogPrintStream(true);
+ }
+
+ @Override
+ public void print(String s) {
+ if (error) {
+ log.error(s);
+ } else {
+ log.info(s);
+ }
+ }
+
+ /**
+ * 閲嶅啓鎺夊畠锛屽洜涓哄畠浼氭墦鍗板緢澶氭棤鐢ㄧ殑鏂拌
+ */
+ @Override
+ public void println() {
+ }
+
+ @Override
+ public void println(String x) {
+ if (error) {
+ log.error(x);
+ } else {
+ log.info(x);
+ }
+ }
+
+ @Override
+ public PrintStream printf(String format, Object... args) {
+ if (error) {
+ log.error(String.format(format, args));
+ } else {
+ log.info(String.format(format, args));
+ }
+ return this;
+ }
+
+ @Override
+ public PrintStream printf(Locale l, String format, Object... args) {
+ if (error) {
+ log.error(String.format(l, format, args));
+ } else {
+ log.info(String.format(l, format, args));
+ }
+ return this;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_appenders.xml b/Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_appenders.xml
new file mode 100644
index 0000000..2c248c7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_appenders.xml
@@ -0,0 +1,33 @@
+<Appenders>
+ <Console name="Console" target="SYSTEM_OUT" follow="true">
+ <PatternLayout pattern="${sys:CONSOLE_LOG_PATTERN}"/>
+ </Console>
+ <RollingFile name="RollingFile"
+ fileName="${logdir}/${appName}.log"
+ filePattern="${logdir}/${appName}.%d{yyyy-MM-dd}.%i.log.gz">
+ <PatternLayout pattern="[%d] [%thread] ${LOG_LEVEL_PATTERN} ${appName} ${sys:PID} %c %m%n"/>
+ <Filters>
+ <ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL" />
+ <ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY" />
+ </Filters>
+ <Policies>
+ <SizeBasedTriggeringPolicy size="200MB" />
+ <TimeBasedTriggeringPolicy modulate="true" interval="1"/>
+ </Policies>
+ <DefaultRolloverStrategy max="30"/>
+ </RollingFile>
+ <!-- 鍙樉绀篹rror绾у埆鐨勪俊鎭� -->
+ <RollingFile name="RollingFileError"
+ fileName="${logdir}/${appName}-error.log"
+ filePattern="${logdir}/${appName}-error.%d{yyyy-MM-dd}.%i.log.gz">
+ <PatternLayout pattern="[%d] [%thread] ${LOG_LEVEL_PATTERN} ${appName} ${sys:PID} %c %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}"/>
+ <Filters>
+ <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
+ </Filters>
+ <Policies>
+ <SizeBasedTriggeringPolicy size="200MB"/>
+ <TimeBasedTriggeringPolicy modulate="true" interval="1"/>
+ </Policies>
+ <DefaultRolloverStrategy max="30"/>
+ </RollingFile>
+</Appenders>
diff --git a/Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_dev.xml b/Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_dev.xml
new file mode 100644
index 0000000..13c6e48
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_dev.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration status="off" xmlns:xi="http://www.w3.org/2001/XInclude">
+ <Properties>
+ <Property name="appName">${sys:spring.application.name}</Property>
+ <Property name="logdir">${env:LOG_BASE:-logs}/${appName}</Property>
+ <Property name="PID">????</Property>
+ <Property name="LOG_LEVEL_PATTERN">%5p</Property>
+ <Property name="LOG_EXCEPTION_CONVERSION_WORD">%xwEx</Property>
+ <Property name="LOG_DATEFORMAT_PATTERN">yyyy-MM-dd HH:mm:ss.SSS</Property>
+ <Property name="CONSOLE_LOG_PATTERN">%clr{%d{${LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${LOG_LEVEL_PATTERN}} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%C{36}.%M:%L}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
+ </Properties>
+ <xi:include href="log4j2_appenders.xml">
+ <xi:fallback/>
+ </xi:include>
+ <Loggers>
+ <!-- 鍑忓皯閮ㄥ垎debug鏃ュ織 -->
+ <AsyncLogger name="org.springframework.context" level="WARN"/>
+ <AsyncLogger name="org.springframework.beans" level="WARN"/>
+ <AsyncLogger name="springfox.bean.validators" level="ERROR"/>
+ <!-- 鍩虹缁勪欢 -->
+ <AsyncLogger name="RocketmqClient" level="WARN"/>
+ <!-- mongo no sql -->
+ <AsyncLogger name="org.springframework.data.mongodb.core" level="DEBUG"/>
+ <!-- blade鏃ュ織 -->
+ <AsyncLogger name="org.springblade.core" level="INFO"/>
+ <Root level="INFO" additivity="false">
+ <AppenderRef ref="Console"/>
+ <AppenderRef ref="RollingFile"/>
+ <AppenderRef ref="RollingFileError"/>
+ </Root>
+ </Loggers>
+</configuration>
diff --git a/Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_ontest.xml b/Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_ontest.xml
new file mode 100644
index 0000000..aae80aa
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_ontest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration status="off" xmlns:xi="http://www.w3.org/2001/XInclude">
+ <Properties>
+ <Property name="appName">${sys:spring.application.name}</Property>
+ <Property name="logdir">${env:LOG_BASE:-logs}/${appName}</Property>
+ <Property name="PID">????</Property>
+ <Property name="LOG_LEVEL_PATTERN">%5p</Property>
+ <Property name="LOG_EXCEPTION_CONVERSION_WORD">%xwEx</Property>
+ <Property name="LOG_DATEFORMAT_PATTERN">yyyy-MM-dd HH:mm:ss.SSS</Property>
+ <Property name="CONSOLE_LOG_PATTERN">%clr{%d{${LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${LOG_LEVEL_PATTERN}} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
+ </Properties>
+ <xi:include href="log4j2_appenders.xml">
+ <xi:fallback/>
+ </xi:include>
+ <Loggers>
+ <!-- 鍑忓皯閮ㄥ垎debug鏃ュ織 -->
+ <AsyncLogger name="org.springframework.context" level="WARN"/>
+ <AsyncLogger name="org.springframework.beans" level="WARN"/>
+ <AsyncLogger name="springfox.bean.validators" level="ERROR"/>
+ <!-- 鍩虹缁勪欢 -->
+ <AsyncLogger name="RocketmqClient" level="WARN"/>
+ <!-- blade鏃ュ織 -->
+ <AsyncLogger name="org.springblade.core" level="WARN" />
+ <AsyncLogger name="org.springblade.core.http" level="INFO"/>
+ <Root level="INFO" additivity="false">
+ <AppenderRef ref="Console"/>
+ <AppenderRef ref="RollingFile"/>
+ <AppenderRef ref="RollingFileError"/>
+ </Root>
+ </Loggers>
+</configuration>
diff --git a/Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_prod.xml b/Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_prod.xml
new file mode 100644
index 0000000..18151f5
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_prod.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration status="off" xmlns:xi="http://www.w3.org/2001/XInclude">
+ <Properties>
+ <Property name="appName">${sys:spring.application.name}</Property>
+ <Property name="logdir">${env:LOG_BASE:-logs}/${appName}</Property>
+ <Property name="PID">????</Property>
+ <Property name="LOG_LEVEL_PATTERN">%5p</Property>
+ <Property name="LOG_EXCEPTION_CONVERSION_WORD">%xwEx</Property>
+ <Property name="LOG_DATEFORMAT_PATTERN">yyyy-MM-dd HH:mm:ss.SSS</Property>
+ <Property name="CONSOLE_LOG_PATTERN">%clr{%d{${LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${LOG_LEVEL_PATTERN}} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
+ </Properties>
+ <xi:include href="log4j2_appenders.xml">
+ <xi:fallback/>
+ </xi:include>
+ <Loggers>
+ <!-- 鍑忓皯閮ㄥ垎debug鏃ュ織 -->
+ <AsyncLogger name="springfox.bean.validators" level="ERROR"/>
+ <!-- 鍩虹缁勪欢 -->
+ <AsyncLogger name="RocketmqClient" level="ERROR"/>
+ <!-- blade鏃ュ織 -->
+ <AsyncLogger name="org.springblade.core" level="ERROR"/>
+ <AsyncLogger name="org.springblade.core.http" level="INFO"/>
+ <Root level="WARN" additivity="false">
+ <AppenderRef ref="Console" />
+ <AppenderRef ref="RollingFile" />
+ <AppenderRef ref="RollingFileError" />
+ </Root>
+ </Loggers>
+</configuration>
diff --git a/Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_test.xml b/Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_test.xml
new file mode 100644
index 0000000..d077d19
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-log4j2/src/main/resources/log/log4j2_test.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration status="off" xmlns:xi="http://www.w3.org/2001/XInclude">
+ <Properties>
+ <Property name="appName">${sys:spring.application.name}</Property>
+ <Property name="logdir">${env:LOG_BASE:-logs}/${appName}</Property>
+ <Property name="PID">????</Property>
+ <Property name="LOG_LEVEL_PATTERN">%5p</Property>
+ <Property name="LOG_EXCEPTION_CONVERSION_WORD">%xwEx</Property>
+ <Property name="LOG_DATEFORMAT_PATTERN">yyyy-MM-dd HH:mm:ss.SSS</Property>
+ <Property name="CONSOLE_LOG_PATTERN">%clr{%d{${LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${LOG_LEVEL_PATTERN}} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%C{36}.%M:%L}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
+ </Properties>
+ <xi:include href="log4j2_appenders.xml">
+ <xi:fallback/>
+ </xi:include>
+ <Loggers>
+ <!-- 鍑忓皯閮ㄥ垎debug鏃ュ織 -->
+ <AsyncLogger name="org.springframework.context" level="WARN"/>
+ <AsyncLogger name="org.springframework.beans" level="WARN"/>
+ <AsyncLogger name="springfox.bean.validators" level="ERROR"/>
+ <!-- 鍩虹缁勪欢 -->
+ <AsyncLogger name="RocketmqClient" level="WARN"/>
+ <!-- blade鏃ュ織 -->
+ <AsyncLogger name="org.springblade.core" level="INFO"/>
+ <Root level="INFO" additivity="false">
+ <AppenderRef ref="Console"/>
+ <AppenderRef ref="RollingFile"/>
+ <AppenderRef ref="RollingFileError"/>
+ </Root>
+ </Loggers>
+</configuration>
diff --git a/Source/BladeX-Tool/blade-core-secure/pom.xml b/Source/BladeX-Tool/blade-core-secure/pom.xml
new file mode 100644
index 0000000..e37518a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/pom.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-core-secure</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!--Blade-->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-auth</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-cache</artifactId>
+ </dependency>
+ <!--Jdbc-->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-jdbc</artifactId>
+ <exclusions>
+ <exclusion>
+ <artifactId>tomcat-jdbc</artifactId>
+ <groupId>org.apache.tomcat</groupId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/annotation/PreAuth.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/annotation/PreAuth.java
new file mode 100644
index 0000000..0a8cb10
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/annotation/PreAuth.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 鏉冮檺娉ㄨВ 鐢ㄤ簬妫�鏌ユ潈闄� 瑙勫畾璁块棶鏉冮檺
+ *
+ * @example @PreAuth("#userVO.id<10")
+ * @example @PreAuth("hasRole(#test, #test1)")
+ * @example @PreAuth("hasPermission(#test) and @PreAuth.hasPermission(#test)")
+ * @author Chill
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface PreAuth {
+
+ /**
+ * Spring el
+ * 鏂囨。鍦板潃锛歨ttps://docs.spring.io/spring/docs/5.1.6.RELEASE/spring-framework-reference/core.html#expressions
+ */
+ String value();
+
+}
+
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/aspect/AuthAspect.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/aspect/AuthAspect.java
new file mode 100644
index 0000000..34ad152
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/aspect/AuthAspect.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.aspect;
+
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springblade.core.secure.annotation.PreAuth;
+import org.springblade.core.secure.auth.AuthFun;
+import org.springblade.core.secure.exception.SecureException;
+import org.springblade.core.tool.api.ResultCode;
+import org.springblade.core.tool.utils.ClassUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.expression.BeanFactoryResolver;
+import org.springframework.core.MethodParameter;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.lang.NonNull;
+
+import java.lang.reflect.Method;
+
+/**
+ * AOP 閴存潈
+ *
+ * @author Chill
+ */
+@Aspect
+public class AuthAspect implements ApplicationContextAware {
+
+ /**
+ * 琛ㄨ揪寮忓鐞�
+ */
+ private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
+
+ /**
+ * 鍒� 鏂规硶 鍜� 绫讳笂鐨� @PreAuth 娉ㄨВ
+ *
+ * @param point 鍒囩偣
+ * @return Object
+ * @throws Throwable 娌℃湁鏉冮檺鐨勫紓甯�
+ */
+ @Around(
+ "@annotation(org.springblade.core.secure.annotation.PreAuth) || " +
+ "@within(org.springblade.core.secure.annotation.PreAuth)"
+ )
+ public Object preAuth(ProceedingJoinPoint point) throws Throwable {
+ if (handleAuth(point)) {
+ return point.proceed();
+ }
+ throw new SecureException(ResultCode.UN_AUTHORIZED);
+ }
+
+ /**
+ * 澶勭悊鏉冮檺
+ *
+ * @param point 鍒囩偣
+ */
+ private boolean handleAuth(ProceedingJoinPoint point) {
+ MethodSignature ms = (MethodSignature) point.getSignature();
+ Method method = ms.getMethod();
+ // 璇诲彇鏉冮檺娉ㄨВ锛屼紭鍏堟柟娉曚笂锛屾病鏈夊垯璇诲彇绫�
+ PreAuth preAuth = ClassUtil.getAnnotation(method, PreAuth.class);
+ // 鍒ゆ柇琛ㄨ揪寮�
+ String condition = preAuth.value();
+ if (StringUtil.isNotBlank(condition)) {
+ Expression expression = EXPRESSION_PARSER.parseExpression(condition);
+ // 鏂规硶鍙傛暟鍊�
+ Object[] args = point.getArgs();
+ StandardEvaluationContext context = getEvaluationContext(method, args);
+ return expression.getValue(context, Boolean.class);
+ }
+ return false;
+ }
+
+ /**
+ * 鑾峰彇鏂规硶涓婄殑鍙傛暟
+ *
+ * @param method 鏂规硶
+ * @param args 鍙橀噺
+ * @return {SimpleEvaluationContext}
+ */
+ private StandardEvaluationContext getEvaluationContext(Method method, Object[] args) {
+ // 鍒濆鍖朣p el琛ㄨ揪寮忎笂涓嬫枃锛屽苟璁剧疆 AuthFun
+ StandardEvaluationContext context = new StandardEvaluationContext(new AuthFun());
+ // 璁剧疆琛ㄨ揪寮忔敮鎸乻pring bean
+ context.setBeanResolver(new BeanFactoryResolver(applicationContext));
+ for (int i = 0; i < args.length; i++) {
+ // 璇诲彇鏂规硶鍙傛暟
+ MethodParameter methodParam = ClassUtil.getMethodParameter(method, i);
+ // 璁剧疆鏂规硶 鍙傛暟鍚嶅拰鍊� 涓簊p el鍙橀噺
+ context.setVariable(methodParam.getParameterName(), args[i]);
+ }
+ return context;
+ }
+
+ private ApplicationContext applicationContext;
+
+ @Override
+ public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/auth/AuthFun.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/auth/AuthFun.java
new file mode 100644
index 0000000..c711386
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/auth/AuthFun.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.auth;
+
+import org.springblade.core.secure.BladeUser;
+import org.springblade.core.secure.handler.IPermissionHandler;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.constant.RoleConstant;
+import org.springblade.core.tool.utils.*;
+
+/**
+ * 鏉冮檺鍒ゆ柇
+ *
+ * @author Chill
+ */
+public class AuthFun {
+
+ /**
+ * 鏉冮檺鏍¢獙澶勭悊鍣�
+ */
+ private static IPermissionHandler permissionHandler;
+
+ private static IPermissionHandler getPermissionHandler() {
+ if (permissionHandler == null) {
+ permissionHandler = SpringUtil.getBean(IPermissionHandler.class);
+ }
+ return permissionHandler;
+ }
+
+ /**
+ * 鍒ゆ柇瑙掕壊鏄惁鍏锋湁鎺ュ彛鏉冮檺
+ *
+ * @return {boolean}
+ */
+ public boolean permissionAll() {
+ return getPermissionHandler().permissionAll();
+ }
+
+ /**
+ * 鍒ゆ柇瑙掕壊鏄惁鍏锋湁鎺ュ彛鏉冮檺
+ *
+ * @param permission 鏉冮檺缂栧彿
+ * @return {boolean}
+ */
+ public boolean hasPermission(String permission) {
+ return getPermissionHandler().hasPermission(permission);
+ }
+
+ /**
+ * 鏀捐鎵�鏈夎姹�
+ *
+ * @return {boolean}
+ */
+ public boolean permitAll() {
+ return true;
+ }
+
+ /**
+ * 鍙湁瓒呯瑙掕壊鎵嶅彲璁块棶
+ *
+ * @return {boolean}
+ */
+ public boolean denyAll() {
+ return hasRole(RoleConstant.ADMIN);
+ }
+
+ /**
+ * 鏄惁宸叉巿鏉�
+ *
+ * @return {boolean}
+ */
+ public boolean hasAuth() {
+ return Func.isNotEmpty(AuthUtil.getUser());
+ }
+
+ /**
+ * 鏄惁鏈夋椂闂存巿鏉�
+ *
+ * @param start 寮�濮嬫椂闂�
+ * @param end 缁撴潫鏃堕棿
+ * @return {boolean}
+ */
+ public boolean hasTimeAuth(Integer start, Integer end) {
+ Integer hour = DateUtil.hour();
+ return hour >= start && hour <= end;
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁鏈夎瑙掕壊鏉冮檺
+ *
+ * @param role 鍗曡鑹�
+ * @return {boolean}
+ */
+ public boolean hasRole(String role) {
+ return hasAnyRole(role);
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁鍏锋湁鎵�鏈夎鑹叉潈闄�
+ *
+ * @param role 瑙掕壊闆嗗悎
+ * @return {boolean}
+ */
+ public boolean hasAllRole(String... role) {
+ for (String r : role) {
+ if (!hasRole(r)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁鏈夎瑙掕壊鏉冮檺
+ *
+ * @param role 瑙掕壊闆嗗悎
+ * @return {boolean}
+ */
+ public boolean hasAnyRole(String... role) {
+ BladeUser user = AuthUtil.getUser();
+ if (user == null) {
+ return false;
+ }
+ String userRole = user.getRoleName();
+ if (StringUtil.isBlank(userRole)) {
+ return false;
+ }
+ String[] roles = Func.toStrArray(userRole);
+ for (String r : role) {
+ if (CollectionUtil.contains(roles, r)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/config/RegistryConfiguration.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/config/RegistryConfiguration.java
new file mode 100644
index 0000000..7b890b8
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/config/RegistryConfiguration.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.config;
+
+
+import lombok.AllArgsConstructor;
+import org.springblade.core.secure.handler.BladePermissionHandler;
+import org.springblade.core.secure.handler.IPermissionHandler;
+import org.springblade.core.secure.handler.ISecureHandler;
+import org.springblade.core.secure.handler.SecureHandlerHandler;
+import org.springblade.core.secure.registry.SecureRegistry;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.annotation.Order;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+/**
+ * secure娉ㄥ唽榛樿閰嶇疆
+ *
+ * @author Chill
+ */
+@Order
+@AutoConfiguration(before = SecureConfiguration.class)
+@AllArgsConstructor
+public class RegistryConfiguration {
+
+ private final JdbcTemplate jdbcTemplate;
+
+ @Bean
+ @ConditionalOnMissingBean(SecureRegistry.class)
+ public SecureRegistry secureRegistry() {
+ return new SecureRegistry();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(ISecureHandler.class)
+ public ISecureHandler secureHandler() {
+ return new SecureHandlerHandler();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(IPermissionHandler.class)
+ public IPermissionHandler permissionHandler() {
+ return new BladePermissionHandler(jdbcTemplate);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/config/SecureConfiguration.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/config/SecureConfiguration.java
new file mode 100644
index 0000000..0566b8c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/config/SecureConfiguration.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.config;
+
+
+import lombok.AllArgsConstructor;
+import org.springblade.core.secure.aspect.AuthAspect;
+import org.springblade.core.secure.handler.ISecureHandler;
+import org.springblade.core.secure.props.AuthSecure;
+import org.springblade.core.secure.props.BasicSecure;
+import org.springblade.core.secure.props.BladeSecureProperties;
+import org.springblade.core.secure.props.SignSecure;
+import org.springblade.core.secure.provider.ClientDetailsServiceImpl;
+import org.springblade.core.secure.provider.IClientDetailsService;
+import org.springblade.core.secure.registry.SecureRegistry;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.annotation.Order;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.lang.NonNull;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 瀹夊叏閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@Order
+@AutoConfiguration
+@AllArgsConstructor
+@EnableConfigurationProperties({BladeSecureProperties.class})
+public class SecureConfiguration implements WebMvcConfigurer {
+
+ private final SecureRegistry secureRegistry;
+
+ private final BladeSecureProperties secureProperties;
+
+ private final JdbcTemplate jdbcTemplate;
+
+ private final ISecureHandler secureHandler;
+
+ @Override
+ public void addInterceptors(@NonNull InterceptorRegistry registry) {
+ // 璁剧疆璇锋眰鎺堟潈
+ if (secureRegistry.isAuthEnabled() || secureProperties.getAuthEnabled()) {
+ List<AuthSecure> authSecures = this.secureRegistry.addAuthPatterns(secureProperties.getAuth()).getAuthSecures();
+ if (authSecures.size() > 0) {
+ registry.addInterceptor(secureHandler.authInterceptor(authSecures));
+ // 璁剧疆璺緞鏀捐
+ secureRegistry.excludePathPatterns(authSecures.stream().map(AuthSecure::getPattern).collect(Collectors.toList()));
+ }
+ }
+ // 璁剧疆鍩虹璁よ瘉鎺堟潈
+ if (secureRegistry.isBasicEnabled() || secureProperties.getBasicEnabled()) {
+ List<BasicSecure> basicSecures = this.secureRegistry.addBasicPatterns(secureProperties.getBasic()).getBasicSecures();
+ if (basicSecures.size() > 0) {
+ registry.addInterceptor(secureHandler.basicInterceptor(basicSecures));
+ // 璁剧疆璺緞鏀捐
+ secureRegistry.excludePathPatterns(basicSecures.stream().map(BasicSecure::getPattern).collect(Collectors.toList()));
+ }
+ }
+ // 璁剧疆绛惧悕璁よ瘉鎺堟潈
+ if (secureRegistry.isSignEnabled() || secureProperties.getSignEnabled()) {
+ List<SignSecure> signSecures = this.secureRegistry.addSignPatterns(secureProperties.getSign()).getSignSecures();
+ if (signSecures.size() > 0) {
+ registry.addInterceptor(secureHandler.signInterceptor(signSecures));
+ // 璁剧疆璺緞鏀捐
+ secureRegistry.excludePathPatterns(signSecures.stream().map(SignSecure::getPattern).collect(Collectors.toList()));
+ }
+ }
+ // 璁剧疆瀹㈡埛绔巿鏉�
+ if (secureRegistry.isClientEnabled() || secureProperties.getClientEnabled()) {
+ secureProperties.getClient().forEach(
+ clientSecure -> registry.addInterceptor(secureHandler.clientInterceptor(clientSecure.getClientId()))
+ .addPathPatterns(clientSecure.getPathPatterns())
+ );
+ }
+ // 璁剧疆璺緞鏀捐
+ if (secureRegistry.isEnabled() || secureProperties.getEnabled()) {
+ registry.addInterceptor(secureHandler.tokenInterceptor())
+ .excludePathPatterns(secureRegistry.getExcludePatterns())
+ .excludePathPatterns(secureRegistry.getDefaultExcludePatterns())
+ .excludePathPatterns(secureProperties.getSkipUrl());
+ }
+ }
+
+ @Bean
+ public AuthAspect authAspect() {
+ return new AuthAspect();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(IClientDetailsService.class)
+ public IClientDetailsService clientDetailsService() {
+ return new ClientDetailsServiceImpl(jdbcTemplate);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/constant/AuthConstant.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/constant/AuthConstant.java
new file mode 100644
index 0000000..3254e44
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/constant/AuthConstant.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.constant;
+
+/**
+ * PreAuth鏉冮檺琛ㄨ揪寮�
+ *
+ * @author Chill
+ */
+public interface AuthConstant {
+
+ /**
+ * 瓒呯鍒悕
+ */
+ String ADMINISTRATOR = "administrator";
+
+ /**
+ * 鏄湁瓒呯瑙掕壊
+ */
+ String HAS_ROLE_ADMINISTRATOR = "hasRole('" + ADMINISTRATOR + "')";
+
+ /**
+ * 绠$悊鍛樺埆鍚�
+ */
+ String ADMIN = "admin";
+
+ /**
+ * 鏄惁鏈夌鐞嗗憳瑙掕壊
+ */
+ String HAS_ROLE_ADMIN = "hasAnyRole('" + ADMINISTRATOR + "', '" + ADMIN + "')";
+
+ /**
+ * 鐢ㄦ埛鍒悕
+ */
+ String USER = "user";
+
+ /**
+ * 鏄惁鏈夌敤鎴疯鑹�
+ */
+ String HAS_ROLE_USER = "hasRole('" + USER + "')";
+
+ /**
+ * 娴嬭瘯鍒悕
+ */
+ String TEST = "test";
+
+ /**
+ * 鏄惁鏈夋祴璇曡鑹�
+ */
+ String HAS_ROLE_TEST = "hasRole('" + TEST + "')";
+
+ /**
+ * 鏀捐鎵�鏈夎姹�
+ */
+ String PERMIT_ALL = "permitAll()";
+
+ /**
+ * 鍙湁瓒呯鎵嶈兘璁块棶
+ */
+ String DENY_ALL = "denyAll()";
+
+ /**
+ * 瀵规墍鏈夎姹傝繘琛屾帴鍙f潈闄愭牎楠�
+ */
+ String PERMISSION_ALL = "permissionAll()";
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/constant/PermissionConstant.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/constant/PermissionConstant.java
new file mode 100644
index 0000000..3d8c203
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/constant/PermissionConstant.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.constant;
+
+import org.springblade.core.tool.utils.StringUtil;
+
+/**
+ * 鏉冮檺鏍¢獙甯搁噺
+ *
+ * @author Chill
+ */
+public interface PermissionConstant {
+
+ /**
+ * 鑾峰彇瑙掕壊鎵�鏈夌殑鏉冮檺缂栧彿
+ *
+ * @param size 鏁伴噺
+ * @return string
+ */
+ static String permissionAllStatement(int size) {
+ return "select scope_path as path from blade_scope_api where id in (select scope_id from blade_role_scope where scope_category = 2 and role_id in (" + buildHolder(size) + "))";
+ }
+
+ /**
+ * 鑾峰彇瑙掕壊鎸囧畾鐨勬潈闄愮紪鍙�
+ *
+ * @param size 鏁伴噺
+ * @return string
+ */
+ static String permissionStatement(int size) {
+ return "select resource_code as code from blade_scope_api where resource_code = ? and id in (select scope_id from blade_role_scope where scope_category = 2 and role_id in (" + buildHolder(size) + "))";
+ }
+
+ /**
+ * 鑾峰彇Sql鍗犱綅绗�
+ *
+ * @param size 鏁伴噺
+ * @return String
+ */
+ static String buildHolder(int size) {
+ StringBuilder builder = StringUtil.builder();
+ for (int i = 0; i < size; i++) {
+ builder.append("?,");
+ }
+ return StringUtil.removeSuffix(builder.toString(), ",");
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/constant/SecureConstant.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/constant/SecureConstant.java
new file mode 100644
index 0000000..7e1c9e7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/constant/SecureConstant.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.constant;
+
+/**
+ * 鎺堟潈鏍¢獙甯搁噺
+ *
+ * @author Chill
+ */
+public interface SecureConstant {
+
+ /**
+ * 璁よ瘉璇锋眰澶�
+ */
+ String BASIC_HEADER_KEY = "Authorization";
+
+ /**
+ * 璁よ瘉璇锋眰澶村墠缂�
+ */
+ String BASIC_HEADER_PREFIX = "Basic ";
+
+ /**
+ * 璁よ瘉璇锋眰澶村墠缂�
+ */
+ String BASIC_HEADER_PREFIX_EXT = "Basic%20";
+
+ /**
+ * 璁よ瘉璇锋眰澶�
+ */
+ String BASIC_REALM_HEADER_KEY = "WWW-Authenticate";
+
+ /**
+ * 璁よ瘉璇锋眰鍊�
+ */
+ String BASIC_REALM_HEADER_VALUE = "basic realm=\"no auth\"";
+
+ /**
+ * blade_client琛ㄥ瓧娈�
+ */
+ String CLIENT_FIELDS = "client_id, client_secret, access_token_validity, refresh_token_validity";
+
+ /**
+ * blade_client鏌ヨ璇彞
+ */
+ String BASE_STATEMENT = "select " + CLIENT_FIELDS + " from blade_client";
+
+ /**
+ * blade_client鏌ヨ鎺掑簭
+ */
+ String DEFAULT_FIND_STATEMENT = BASE_STATEMENT + " order by client_id";
+
+ /**
+ * 鏌ヨclient_id
+ */
+ String DEFAULT_SELECT_STATEMENT = BASE_STATEMENT + " where client_id = ?";
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/handler/BladePermissionHandler.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/handler/BladePermissionHandler.java
new file mode 100644
index 0000000..fb49285
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/handler/BladePermissionHandler.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.handler;
+
+import lombok.AllArgsConstructor;
+import org.springblade.core.cache.utils.CacheUtil;
+import org.springblade.core.secure.BladeUser;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.WebUtil;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.springblade.core.cache.constant.CacheConstant.SYS_CACHE;
+import static org.springblade.core.secure.constant.PermissionConstant.permissionAllStatement;
+import static org.springblade.core.secure.constant.PermissionConstant.permissionStatement;
+
+/**
+ * 榛樿鎺堟潈鏍¢獙绫�
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+public class BladePermissionHandler implements IPermissionHandler {
+
+ private static final String SCOPE_CACHE_CODE = "apiScope:code:";
+
+ private final JdbcTemplate jdbcTemplate;
+
+ @Override
+ public boolean permissionAll() {
+ HttpServletRequest request = WebUtil.getRequest();
+ BladeUser user = AuthUtil.getUser();
+ if (request == null || user == null) {
+ return false;
+ }
+ String uri = request.getRequestURI();
+ List<String> paths = permissionPath(user.getRoleId());
+ if (paths.size() == 0) {
+ return false;
+ }
+ return paths.stream().anyMatch(uri::contains);
+ }
+
+ @Override
+ public boolean hasPermission(String permission) {
+ HttpServletRequest request = WebUtil.getRequest();
+ BladeUser user = AuthUtil.getUser();
+ if (request == null || user == null) {
+ return false;
+ }
+ List<String> codes = permissionCode(permission, user.getRoleId());
+ return codes.size() != 0;
+ }
+
+ /**
+ * 鑾峰彇鎺ュ彛鏉冮檺鍦板潃
+ *
+ * @param roleId 瑙掕壊id
+ * @return permissions
+ */
+ private List<String> permissionPath(String roleId) {
+ List<String> permissions = CacheUtil.get(SYS_CACHE, SCOPE_CACHE_CODE, roleId, List.class, Boolean.FALSE);
+ if (permissions == null) {
+ List<Long> roleIds = Func.toLongList(roleId);
+ permissions = jdbcTemplate.queryForList(permissionAllStatement(roleIds.size()), roleIds.toArray(), String.class);
+ CacheUtil.put(SYS_CACHE, SCOPE_CACHE_CODE, roleId, permissions, Boolean.FALSE);
+ }
+ return permissions;
+ }
+
+ /**
+ * 鑾峰彇鎺ュ彛鏉冮檺淇℃伅
+ *
+ * @param permission 鏉冮檺缂栧彿
+ * @param roleId 瑙掕壊id
+ * @return permissions
+ */
+ private List<String> permissionCode(String permission, String roleId) {
+ List<String> permissions = CacheUtil.get(SYS_CACHE, SCOPE_CACHE_CODE, permission + StringPool.COLON + roleId, List.class, Boolean.FALSE);
+ if (permissions == null) {
+ List<Object> args = new ArrayList<>(Collections.singletonList(permission));
+ List<Long> roleIds = Func.toLongList(roleId);
+ args.addAll(roleIds);
+ permissions = jdbcTemplate.queryForList(permissionStatement(roleIds.size()), args.toArray(), String.class);
+ CacheUtil.put(SYS_CACHE, SCOPE_CACHE_CODE, permission + StringPool.COLON + roleId, permissions, Boolean.FALSE);
+ }
+ return permissions;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/handler/IPermissionHandler.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/handler/IPermissionHandler.java
new file mode 100644
index 0000000..b4746f2
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/handler/IPermissionHandler.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.handler;
+
+/**
+ * 鏉冮檺鏍¢獙閫氱敤鎺ュ彛
+ *
+ * @author Chill
+ */
+public interface IPermissionHandler {
+
+ /**
+ * 鍒ゆ柇瑙掕壊鏄惁鍏锋湁鎺ュ彛鏉冮檺
+ *
+ * @return {boolean}
+ */
+ boolean permissionAll();
+
+ /**
+ * 鍒ゆ柇瑙掕壊鏄惁鍏锋湁鎺ュ彛鏉冮檺
+ *
+ * @param permission 鏉冮檺缂栧彿
+ * @return {boolean}
+ */
+ boolean hasPermission(String permission);
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/handler/ISecureHandler.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/handler/ISecureHandler.java
new file mode 100644
index 0000000..8a96942
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/handler/ISecureHandler.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.handler;
+
+import org.springblade.core.secure.props.AuthSecure;
+import org.springblade.core.secure.props.BasicSecure;
+import org.springblade.core.secure.props.SignSecure;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import java.util.List;
+
+/**
+ * secure 鎷︽埅鍣ㄩ泦鍚�
+ *
+ * @author Chill
+ */
+public interface ISecureHandler {
+
+ /**
+ * token鎷︽埅鍣�
+ *
+ * @return tokenInterceptor
+ */
+ HandlerInterceptorAdapter tokenInterceptor();
+
+ /**
+ * auth鎷︽埅鍣�
+ *
+ * @param authSecures 鎺堟潈闆嗗悎
+ * @return HandlerInterceptorAdapter
+ */
+ HandlerInterceptorAdapter authInterceptor(List<AuthSecure> authSecures);
+
+ /**
+ * basic鎷︽埅鍣�
+ *
+ * @param basicSecures 鍩虹璁よ瘉闆嗗悎
+ * @return HandlerInterceptorAdapter
+ */
+ HandlerInterceptorAdapter basicInterceptor(List<BasicSecure> basicSecures);
+
+ /**
+ * sign鎷︽埅鍣�
+ *
+ * @param signSecures 绛惧悕璁よ瘉闆嗗悎
+ * @return HandlerInterceptorAdapter
+ */
+ HandlerInterceptorAdapter signInterceptor(List<SignSecure> signSecures);
+
+ /**
+ * client鎷︽埅鍣�
+ *
+ * @param clientId 瀹㈡埛绔痠d
+ * @return clientInterceptor
+ */
+ HandlerInterceptorAdapter clientInterceptor(String clientId);
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/handler/SecureHandlerHandler.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/handler/SecureHandlerHandler.java
new file mode 100644
index 0000000..0c1f82d
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/handler/SecureHandlerHandler.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.handler;
+
+import org.springblade.core.secure.interceptor.*;
+import org.springblade.core.secure.props.AuthSecure;
+import org.springblade.core.secure.props.BasicSecure;
+import org.springblade.core.secure.props.SignSecure;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import java.util.List;
+
+/**
+ * Secure澶勭悊鍣�
+ *
+ * @author Chill
+ */
+public class SecureHandlerHandler implements ISecureHandler {
+
+ @Override
+ public HandlerInterceptorAdapter tokenInterceptor() {
+ return new TokenInterceptor();
+ }
+
+ @Override
+ public HandlerInterceptorAdapter authInterceptor(List<AuthSecure> authSecures) {
+ return new AuthInterceptor(authSecures);
+ }
+
+ @Override
+ public HandlerInterceptorAdapter basicInterceptor(List<BasicSecure> basicSecures) {
+ return new BasicInterceptor(basicSecures);
+ }
+
+ @Override
+ public HandlerInterceptorAdapter signInterceptor(List<SignSecure> signSecures) {
+ return new SignInterceptor(signSecures);
+ }
+
+ @Override
+ public HandlerInterceptorAdapter clientInterceptor(String clientId) {
+ return new ClientInterceptor(clientId);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/AuthInterceptor.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/AuthInterceptor.java
new file mode 100644
index 0000000..225b078
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/AuthInterceptor.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.interceptor;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.secure.auth.AuthFun;
+import org.springblade.core.secure.props.AuthSecure;
+import org.springblade.core.secure.provider.HttpMethod;
+import org.springblade.core.secure.provider.ResponseProvider;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.utils.WebUtil;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.lang.NonNull;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 鑷畾涔夋巿鏉冩嫤鎴櫒鏍¢獙
+ *
+ * @author Chill
+ */
+@Slf4j
+@AllArgsConstructor
+public class AuthInterceptor extends HandlerInterceptorAdapter {
+
+ /**
+ * 琛ㄨ揪寮忓鐞�
+ */
+ private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
+ private static final EvaluationContext EVALUATION_CONTEXT = new StandardEvaluationContext(new AuthFun());
+ private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();
+
+ /**
+ * 鎺堟潈闆嗗悎
+ */
+ private final List<AuthSecure> authSecures;
+
+ @Override
+ public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
+ boolean check = authSecures.stream().filter(authSecure -> checkAuth(request, authSecure)).findFirst().map(
+ authSecure -> checkExpression(authSecure.getExpression())
+ ).orElse(Boolean.TRUE);
+ if (!check) {
+ log.warn("鎺堟潈璁よ瘉澶辫触锛岃姹傛帴鍙o細{}锛岃姹侷P锛歿}锛岃姹傚弬鏁帮細{}", request.getRequestURI(), WebUtil.getIP(request), JsonUtil.toJson(request.getParameterMap()));
+ ResponseProvider.write(response);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 妫�娴嬫巿鏉�
+ */
+ private boolean checkAuth(HttpServletRequest request, AuthSecure authSecure) {
+ return checkMethod(request, authSecure.getMethod()) && checkPath(request, authSecure.getPattern());
+ }
+
+ /**
+ * 妫�娴嬭姹傛柟娉�
+ */
+ private boolean checkMethod(HttpServletRequest request, HttpMethod method) {
+ return method == HttpMethod.ALL || (
+ method != null && method == HttpMethod.of(request.getMethod())
+ );
+ }
+
+ /**
+ * 妫�娴嬭矾寰勫尮閰�
+ */
+ private boolean checkPath(HttpServletRequest request, String pattern) {
+ String servletPath = request.getServletPath();
+ String pathInfo = request.getPathInfo();
+ if (pathInfo != null && pathInfo.length() > 0) {
+ servletPath = servletPath + pathInfo;
+ }
+ return ANT_PATH_MATCHER.match(pattern, servletPath);
+ }
+
+ /**
+ * 妫�娴嬭〃杈惧紡
+ */
+ private boolean checkExpression(String expression) {
+ Boolean result = EXPRESSION_PARSER.parseExpression(expression).getValue(EVALUATION_CONTEXT, Boolean.class);
+ return result != null ? result : false;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/BasicInterceptor.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/BasicInterceptor.java
new file mode 100644
index 0000000..4ed9614
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/BasicInterceptor.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.interceptor;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.secure.props.BasicSecure;
+import org.springblade.core.secure.provider.HttpMethod;
+import org.springblade.core.secure.provider.ResponseProvider;
+import org.springblade.core.secure.utils.SecureUtil;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.utils.WebUtil;
+import org.springframework.lang.NonNull;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+import static org.springblade.core.secure.constant.SecureConstant.BASIC_REALM_HEADER_KEY;
+import static org.springblade.core.secure.constant.SecureConstant.BASIC_REALM_HEADER_VALUE;
+
+/**
+ * 鍩虹璁よ瘉鎷︽埅鍣ㄦ牎楠�
+ *
+ * @author Chill
+ */
+@Slf4j
+@AllArgsConstructor
+public class BasicInterceptor extends HandlerInterceptorAdapter {
+
+ /**
+ * 琛ㄨ揪寮忓尮閰�
+ */
+ private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();
+
+ /**
+ * 鎺堟潈闆嗗悎
+ */
+ private final List<BasicSecure> basicSecures;
+
+ @Override
+ public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
+ boolean check = basicSecures.stream().filter(basicSecure -> checkAuth(request, basicSecure)).findFirst().map(
+ authSecure -> checkBasic(authSecure.getUsername(), authSecure.getPassword())
+ ).orElse(Boolean.TRUE);
+ if (!check) {
+ log.warn("鎺堟潈璁よ瘉澶辫触锛岃姹傛帴鍙o細{}锛岃姹侷P锛歿}锛岃姹傚弬鏁帮細{}", request.getRequestURI(), WebUtil.getIP(request), JsonUtil.toJson(request.getParameterMap()));
+ response.setHeader(BASIC_REALM_HEADER_KEY, BASIC_REALM_HEADER_VALUE);
+ ResponseProvider.write(response);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 妫�娴嬫巿鏉�
+ */
+ private boolean checkAuth(HttpServletRequest request, BasicSecure basicSecure) {
+ return checkMethod(request, basicSecure.getMethod()) && checkPath(request, basicSecure.getPattern());
+ }
+
+ /**
+ * 妫�娴嬭姹傛柟娉�
+ */
+ private boolean checkMethod(HttpServletRequest request, HttpMethod method) {
+ return method == HttpMethod.ALL || (
+ method != null && method == HttpMethod.of(request.getMethod())
+ );
+ }
+
+ /**
+ * 妫�娴嬭矾寰勫尮閰�
+ */
+ private boolean checkPath(HttpServletRequest request, String pattern) {
+ String servletPath = request.getServletPath();
+ String pathInfo = request.getPathInfo();
+ if (pathInfo != null && pathInfo.length() > 0) {
+ servletPath = servletPath + pathInfo;
+ }
+ return ANT_PATH_MATCHER.match(pattern, servletPath);
+ }
+
+ /**
+ * 妫�娴嬭〃杈惧紡
+ */
+ private boolean checkBasic(String username, String password) {
+ try {
+ String[] tokens = SecureUtil.extractAndDecodeHeader();
+ return username.equals(tokens[0]) && password.equals(tokens[1]);
+ } catch (Exception e) {
+ log.warn("鎺堟潈璁よ瘉澶辫触锛岄敊璇俊鎭細{}", e.getMessage());
+ return false;
+ }
+ }
+
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/ClientInterceptor.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/ClientInterceptor.java
new file mode 100644
index 0000000..2e5b623
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/ClientInterceptor.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.interceptor;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.secure.BladeUser;
+import org.springblade.core.secure.provider.ResponseProvider;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.secure.utils.SecureUtil;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.core.tool.utils.WebUtil;
+import org.springframework.lang.NonNull;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 瀹㈡埛绔牎楠屾嫤鎴櫒
+ *
+ * @author Chill
+ */
+@Slf4j
+@AllArgsConstructor
+public class ClientInterceptor extends HandlerInterceptorAdapter {
+
+ private final String clientId;
+
+ @Override
+ public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
+ BladeUser user = AuthUtil.getUser();
+ boolean check = (
+ user != null &&
+ StringUtil.equals(clientId, SecureUtil.getClientIdFromHeader()) &&
+ StringUtil.equals(clientId, user.getClientId())
+ );
+ if (!check) {
+ log.warn("瀹㈡埛绔璇佸け璐ワ紝璇锋眰鎺ュ彛锛歿}锛岃姹侷P锛歿}锛岃姹傚弬鏁帮細{}", request.getRequestURI(), WebUtil.getIP(request), JsonUtil.toJson(request.getParameterMap()));
+ ResponseProvider.write(response);
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/SignInterceptor.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/SignInterceptor.java
new file mode 100644
index 0000000..f8951e2
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/SignInterceptor.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.interceptor;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.secure.props.SignSecure;
+import org.springblade.core.secure.provider.HttpMethod;
+import org.springblade.core.secure.provider.ResponseProvider;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.core.tool.utils.DigestUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.WebUtil;
+import org.springframework.lang.NonNull;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.time.Duration;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 绛惧悕璁よ瘉鎷︽埅鍣ㄦ牎楠�
+ *
+ * @author Chill
+ */
+@Slf4j
+@AllArgsConstructor
+public class SignInterceptor extends HandlerInterceptorAdapter {
+
+ /**
+ * 琛ㄨ揪寮忓尮閰�
+ */
+ private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();
+
+ /**
+ * 鎺堟潈闆嗗悎
+ */
+ private final List<SignSecure> signSecures;
+
+ /**
+ * 璇锋眰鏃堕棿
+ */
+ private final static String TIMESTAMP = "timestamp";
+
+ /**
+ * 闅忔満鏁�
+ */
+ private final static String NONCE = "nonce";
+
+ /**
+ * 鏃堕棿闅忔満鏁扮粍鍚堝姞瀵嗕覆
+ */
+ private final static String SIGNATURE = "signature";
+
+ /**
+ * sha1鍔犲瘑鏂瑰紡
+ */
+ private final static String SHA1 = "sha1";
+
+ /**
+ * md5鍔犲瘑鏂瑰紡
+ */
+ private final static String MD5 = "md5";
+
+ /**
+ * 鏃堕棿宸渶灏忓��
+ */
+ private final static Integer SECOND_MIN = 0;
+
+ /**
+ * 鏃堕棿宸渶澶у��
+ */
+ private final static Integer SECOND_MAX = 10;
+
+ @Override
+ public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
+ boolean check = signSecures.stream().filter(signSecure -> checkAuth(request, signSecure)).findFirst().map(
+ authSecure -> checkSign(authSecure.getCrypto())
+ ).orElse(Boolean.TRUE);
+ if (!check) {
+ log.warn("鎺堟潈璁よ瘉澶辫触锛岃姹傛帴鍙o細{}锛岃姹侷P锛歿}锛岃姹傚弬鏁帮細{}", request.getRequestURI(), WebUtil.getIP(request), JsonUtil.toJson(request.getParameterMap()));
+ ResponseProvider.write(response);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 妫�娴嬫巿鏉�
+ */
+ private boolean checkAuth(HttpServletRequest request, SignSecure signSecure) {
+ return checkMethod(request, signSecure.getMethod()) && checkPath(request, signSecure.getPattern());
+ }
+
+ /**
+ * 妫�娴嬭姹傛柟娉�
+ */
+ private boolean checkMethod(HttpServletRequest request, HttpMethod method) {
+ return method == HttpMethod.ALL || (
+ method != null && method == HttpMethod.of(request.getMethod())
+ );
+ }
+
+ /**
+ * 妫�娴嬭矾寰勫尮閰�
+ */
+ private boolean checkPath(HttpServletRequest request, String pattern) {
+ String servletPath = request.getServletPath();
+ String pathInfo = request.getPathInfo();
+ if (pathInfo != null && pathInfo.length() > 0) {
+ servletPath = servletPath + pathInfo;
+ }
+ return ANT_PATH_MATCHER.match(pattern, servletPath);
+ }
+
+ /**
+ * 妫�娴嬭〃杈惧紡
+ */
+ private boolean checkSign(String crypto) {
+ try {
+ HttpServletRequest request = WebUtil.getRequest();
+ if (request == null) {
+ return false;
+ }
+ // 鑾峰彇澶撮儴鍔ㄦ�佺鍚嶄俊鎭�
+ String timestamp = request.getHeader(TIMESTAMP);
+ // 鍒ゆ柇鏄惁鍦ㄥ悎娉曟椂闂存
+ long seconds = Duration.between(new Date(Func.toLong(timestamp)).toInstant(), DateUtil.now().toInstant()).getSeconds();
+ if (seconds < SECOND_MIN || seconds > SECOND_MAX) {
+ log.warn("鎺堟潈璁よ瘉澶辫触锛岄敊璇俊鎭細{}", "璇锋眰鏃堕棿鎴抽潪娉�");
+ return false;
+ }
+ String nonce = request.getHeader(NONCE);
+ String signature = request.getHeader(SIGNATURE);
+ // 鍔犲瘑绛惧悕姣斿锛屽彲鑷鎷撳睍鍔犲瘑瑙勫垯
+ String sign;
+ if (crypto.equals(MD5)) {
+ sign = DigestUtil.md5Hex(timestamp + nonce);
+ } else if (crypto.equals(SHA1)) {
+ sign = DigestUtil.sha1Hex(timestamp + nonce);
+ } else {
+ sign = DigestUtil.sha1Hex(timestamp + nonce);
+ }
+ return sign.equalsIgnoreCase(signature);
+ } catch (Exception e) {
+ log.warn("鎺堟潈璁よ瘉澶辫触锛岄敊璇俊鎭細{}", e.getMessage());
+ return false;
+ }
+ }
+
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/TokenInterceptor.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/TokenInterceptor.java
new file mode 100644
index 0000000..f88064d
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/interceptor/TokenInterceptor.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.interceptor;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.secure.provider.ResponseProvider;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.utils.WebUtil;
+import org.springframework.lang.NonNull;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 绛惧悕璁よ瘉鎷︽埅鍣�
+ *
+ * @author Chill
+ */
+@Slf4j
+@AllArgsConstructor
+public class TokenInterceptor extends HandlerInterceptorAdapter {
+
+ @Override
+ public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
+ if (null == AuthUtil.getUser()) {
+ log.warn("绛惧悕璁よ瘉澶辫触锛岃姹傛帴鍙o細{}锛岃姹侷P锛歿}锛岃姹傚弬鏁帮細{}", request.getRequestURI(), WebUtil.getIP(request), JsonUtil.toJson(request.getParameterMap()));
+ ResponseProvider.write(response);
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/AuthSecure.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/AuthSecure.java
new file mode 100644
index 0000000..d5f36aa
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/AuthSecure.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.props;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springblade.core.secure.provider.HttpMethod;
+
+/**
+ * 鑷畾涔夋巿鏉冭鍒�
+ *
+ * @author Chill
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class AuthSecure {
+
+ /**
+ * 璇锋眰鏂规硶
+ */
+ private HttpMethod method;
+ /**
+ * 璇锋眰璺緞
+ */
+ private String pattern;
+ /**
+ * 瑙勫垯琛ㄨ揪寮�
+ */
+ private String expression;
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/BasicSecure.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/BasicSecure.java
new file mode 100644
index 0000000..acc3f63
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/BasicSecure.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.props;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springblade.core.secure.provider.HttpMethod;
+
+/**
+ * 鍩虹鎺堟潈瑙勫垯
+ *
+ * @author Chill
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class BasicSecure {
+
+ /**
+ * 璇锋眰鏂规硶
+ */
+ private HttpMethod method;
+ /**
+ * 璇锋眰璺緞
+ */
+ private String pattern;
+ /**
+ * 瀹㈡埛绔痠d
+ */
+ private String username;
+ /**
+ * 瀹㈡埛绔瘑閽�
+ */
+ private String password;
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/BladeSecureProperties.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/BladeSecureProperties.java
new file mode 100644
index 0000000..8d9facc
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/BladeSecureProperties.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.props;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 瀹㈡埛绔牎楠岄厤缃�
+ *
+ * @author Chill
+ */
+@Data
+@ConfigurationProperties("blade.secure")
+public class BladeSecureProperties {
+
+ /**
+ * 寮�鍚壌鏉冭鍒�
+ */
+ private Boolean enabled = false;
+
+ /**
+ * 閴存潈鏀捐璇锋眰
+ */
+ private final List<String> skipUrl = new ArrayList<>();
+
+ /**
+ * 寮�鍚巿鏉冭鍒�
+ */
+ private Boolean authEnabled = true;
+
+ /**
+ * 鎺堟潈閰嶇疆
+ */
+ private final List<AuthSecure> auth = new ArrayList<>();
+
+ /**
+ * 寮�鍚熀纭�璁よ瘉瑙勫垯
+ */
+ private Boolean basicEnabled = true;
+
+ /**
+ * 鍩虹璁よ瘉閰嶇疆
+ */
+ private final List<BasicSecure> basic = new ArrayList<>();
+
+ /**
+ * 寮�鍚鍚嶈璇佽鍒�
+ */
+ private Boolean signEnabled = true;
+
+ /**
+ * 绛惧悕璁よ瘉閰嶇疆
+ */
+ private final List<SignSecure> sign = new ArrayList<>();
+
+ /**
+ * 寮�鍚鎴风瑙勫垯
+ */
+ private Boolean clientEnabled = true;
+
+ /**
+ * 瀹㈡埛绔厤缃�
+ */
+ private final List<ClientSecure> client = new ArrayList<>();
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/ClientSecure.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/ClientSecure.java
new file mode 100644
index 0000000..cb84e42
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/ClientSecure.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.props;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 瀹㈡埛绔护鐗岃璇佷俊鎭�
+ *
+ * @author Chill
+ */
+@Data
+public class ClientSecure {
+
+ /**
+ * 瀹㈡埛绔疘D
+ */
+ private String clientId;
+ /**
+ * 璺緞鍖归厤
+ */
+ private final List<String> pathPatterns = new ArrayList<>();
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/SignSecure.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/SignSecure.java
new file mode 100644
index 0000000..e922b93
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/props/SignSecure.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.props;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springblade.core.secure.provider.HttpMethod;
+
+/**
+ * 绛惧悕鎺堟潈瑙勫垯
+ *
+ * @author Chill
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class SignSecure {
+
+ /**
+ * 璇锋眰鏂规硶
+ */
+ private HttpMethod method;
+ /**
+ * 璇锋眰璺緞
+ */
+ private String pattern;
+ /**
+ * 鍔犲瘑鏂瑰紡
+ */
+ private String crypto;
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/ClientDetails.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/ClientDetails.java
new file mode 100644
index 0000000..fb4080c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/ClientDetails.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.provider;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 瀹㈡埛绔鎯�
+ *
+ * @author Chill
+ */
+@Data
+public class ClientDetails implements IClientDetails {
+
+ /**
+ * 瀹㈡埛绔痠d
+ */
+ @ApiModelProperty(value = "瀹㈡埛绔痠d")
+ private String clientId;
+ /**
+ * 瀹㈡埛绔瘑閽�
+ */
+ @ApiModelProperty(value = "瀹㈡埛绔瘑閽�")
+ private String clientSecret;
+
+ /**
+ * 浠ょ墝杩囨湡绉掓暟
+ */
+ @ApiModelProperty(value = "浠ょ墝杩囨湡绉掓暟")
+ private Integer accessTokenValidity;
+ /**
+ * 鍒锋柊浠ょ墝杩囨湡绉掓暟
+ */
+ @ApiModelProperty(value = "鍒锋柊浠ょ墝杩囨湡绉掓暟")
+ private Integer refreshTokenValidity;
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/ClientDetailsServiceImpl.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/ClientDetailsServiceImpl.java
new file mode 100644
index 0000000..e655f8c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/ClientDetailsServiceImpl.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.provider;
+
+import lombok.AllArgsConstructor;
+import org.springblade.core.secure.constant.SecureConstant;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+/**
+ * 鑾峰彇瀹㈡埛绔鎯�
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+public class ClientDetailsServiceImpl implements IClientDetailsService {
+
+ private final JdbcTemplate jdbcTemplate;
+
+ @Override
+ public IClientDetails loadClientByClientId(String clientId) {
+ try {
+ return jdbcTemplate.queryForObject(SecureConstant.DEFAULT_SELECT_STATEMENT, new String[]{clientId}, new BeanPropertyRowMapper<>(ClientDetails.class));
+ } catch (Exception ex) {
+ return null;
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/HttpMethod.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/HttpMethod.java
new file mode 100644
index 0000000..caa1669
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/HttpMethod.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.provider;
+
+/**
+ * HttpMethod鏋氫妇绫�
+ *
+ * @author Chill
+ */
+public enum HttpMethod {
+
+ /**
+ * 璇锋眰鏂规硶闆嗗悎
+ */
+ GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE, ALL;
+
+ /**
+ * 鍖归厤鏋氫妇
+ */
+ public static HttpMethod of(String method) {
+ try {
+ return valueOf(method);
+ } catch (Exception exception) {
+ return null;
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/IClientDetails.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/IClientDetails.java
new file mode 100644
index 0000000..b7f49de
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/IClientDetails.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.provider;
+
+import java.io.Serializable;
+
+/**
+ * 澶氱粓绔鎯呮帴鍙�
+ *
+ * @author Chill
+ */
+public interface IClientDetails extends Serializable {
+
+ /**
+ * 瀹㈡埛绔痠d.
+ *
+ * @return String.
+ */
+ String getClientId();
+
+ /**
+ * 瀹㈡埛绔瘑閽�.
+ *
+ * @return String.
+ */
+ String getClientSecret();
+
+ /**
+ * 瀹㈡埛绔痶oken杩囨湡鏃堕棿
+ *
+ * @return Integer
+ */
+ Integer getAccessTokenValidity();
+
+ /**
+ * 瀹㈡埛绔埛鏂皌oken杩囨湡鏃堕棿
+ *
+ * @return Integer
+ */
+ Integer getRefreshTokenValidity();
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/IClientDetailsService.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/IClientDetailsService.java
new file mode 100644
index 0000000..1fe5217
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/IClientDetailsService.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.provider;
+
+/**
+ * 澶氱粓绔敞鍐屾帴鍙�
+ *
+ * @author Chill
+ */
+public interface IClientDetailsService {
+
+ /**
+ * 鏍规嵁clientId鑾峰彇Client璇︽儏
+ *
+ * @param clientId 瀹㈡埛绔痠d
+ * @return
+ */
+ IClientDetails loadClientByClientId(String clientId);
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/ResponseProvider.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/ResponseProvider.java
new file mode 100644
index 0000000..b314b13
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/provider/ResponseProvider.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.provider;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.api.ResultCode;
+import org.springblade.core.tool.constant.BladeConstant;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springframework.http.MediaType;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * ResponseProvider
+ *
+ * @author Chill
+ */
+@Slf4j
+public class ResponseProvider {
+
+ public static void write(HttpServletResponse response) {
+ R result = R.fail(ResultCode.UN_AUTHORIZED);
+ response.setCharacterEncoding(BladeConstant.UTF_8);
+ response.addHeader(BladeConstant.CONTENT_TYPE_NAME, MediaType.APPLICATION_JSON_VALUE);
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ try {
+ response.getWriter().write(Objects.requireNonNull(JsonUtil.toJson(result)));
+ } catch (IOException ex) {
+ log.error(ex.getMessage());
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/registry/SecureRegistry.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/registry/SecureRegistry.java
new file mode 100644
index 0000000..bb8ffc4
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/registry/SecureRegistry.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.registry;
+
+import lombok.Data;
+import org.springblade.core.secure.props.AuthSecure;
+import org.springblade.core.secure.props.BasicSecure;
+import org.springblade.core.secure.props.SignSecure;
+import org.springblade.core.secure.provider.HttpMethod;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 瀹夊叏妗嗘灦缁熶竴閰嶇疆
+ *
+ * @author Chill
+ */
+@Data
+public class SecureRegistry {
+
+ /**
+ * 鏄惁寮�鍚壌鏉�
+ */
+ private boolean enabled = false;
+
+ /**
+ * 鏄惁寮�鍚巿鏉�
+ */
+ private boolean authEnabled = true;
+
+ /**
+ * 鏄惁寮�鍚熀纭�璁よ瘉
+ */
+ private boolean basicEnabled = true;
+
+ /**
+ * 鏄惁寮�鍚鍚嶈璇�
+ */
+ private boolean signEnabled = true;
+
+ /**
+ * 鏄惁寮�鍚鎴风璁よ瘉
+ */
+ private boolean clientEnabled = true;
+
+ /**
+ * 榛樿鏀捐瑙勫垯
+ */
+ private final List<String> defaultExcludePatterns = new ArrayList<>();
+
+ /**
+ * 鑷畾涔夋斁琛岃鍒�
+ */
+ private final List<String> excludePatterns = new ArrayList<>();
+
+ /**
+ * 鑷畾涔夋巿鏉冮泦鍚�
+ */
+ private final List<AuthSecure> authSecures = new ArrayList<>();
+
+ /**
+ * 鍩虹璁よ瘉闆嗗悎
+ */
+ private final List<BasicSecure> basicSecures = new ArrayList<>();
+
+ /**
+ * 绛惧悕璁よ瘉闆嗗悎
+ */
+ private final List<SignSecure> signSecures = new ArrayList<>();
+
+ public SecureRegistry() {
+ this.defaultExcludePatterns.add("/actuator/health/**");
+ this.defaultExcludePatterns.add("/v2/api-docs/**");
+ this.defaultExcludePatterns.add("/auth/**");
+ this.defaultExcludePatterns.add("/token/**");
+ this.defaultExcludePatterns.add("/log/**");
+ this.defaultExcludePatterns.add("/menu/routes");
+ this.defaultExcludePatterns.add("/menu/auth-routes");
+ this.defaultExcludePatterns.add("/menu/top-menu");
+ this.defaultExcludePatterns.add("/process/resource-view");
+ this.defaultExcludePatterns.add("/process/diagram-view");
+ this.defaultExcludePatterns.add("/manager/check-upload");
+ this.defaultExcludePatterns.add("/error/**");
+ this.defaultExcludePatterns.add("/assets/**");
+ }
+
+ /**
+ * 璁剧疆鍗曚釜鏀捐api
+ */
+ public SecureRegistry excludePathPattern(String pattern) {
+ this.excludePatterns.add(pattern);
+ return this;
+ }
+
+ /**
+ * 璁剧疆鏀捐api闆嗗悎
+ */
+ public SecureRegistry excludePathPatterns(String... patterns) {
+ this.excludePatterns.addAll(Arrays.asList(patterns));
+ return this;
+ }
+
+ /**
+ * 璁剧疆鏀捐api闆嗗悎
+ */
+ public SecureRegistry excludePathPatterns(List<String> patterns) {
+ this.excludePatterns.addAll(patterns);
+ return this;
+ }
+
+ /**
+ * 璁剧疆鍗曚釜鑷畾涔夋巿鏉�
+ */
+ public SecureRegistry addAuthPattern(HttpMethod method, String pattern, String expression) {
+ this.authSecures.add(new AuthSecure(method, pattern, expression));
+ return this;
+ }
+
+ /**
+ * 璁剧疆鑷畾涔夋巿鏉冮泦鍚�
+ */
+ public SecureRegistry addAuthPatterns(List<AuthSecure> authSecures) {
+ this.authSecures.addAll(authSecures);
+ return this;
+ }
+
+ /**
+ * 杩斿洖鑷畾涔夋巿鏉冮泦鍚�
+ */
+ public List<AuthSecure> getAuthSecures() {
+ return this.authSecures;
+ }
+
+ /**
+ * 璁剧疆鍩虹璁よ瘉
+ */
+ public SecureRegistry addBasicPattern(HttpMethod method, String pattern, String username, String password) {
+ this.basicSecures.add(new BasicSecure(method, pattern, username, password));
+ return this;
+ }
+
+ /**
+ * 璁剧疆鍩虹璁よ瘉闆嗗悎
+ */
+ public SecureRegistry addBasicPatterns(List<BasicSecure> basicSecures) {
+ this.basicSecures.addAll(basicSecures);
+ return this;
+ }
+
+ /**
+ * 杩斿洖鍩虹璁よ瘉闆嗗悎
+ */
+ public List<BasicSecure> getBasicSecures() {
+ return this.basicSecures;
+ }
+
+ /**
+ * 璁剧疆绛惧悕璁よ瘉
+ */
+ public SecureRegistry addSignPattern(HttpMethod method, String pattern, String crypto) {
+ this.signSecures.add(new SignSecure(method, pattern, crypto));
+ return this;
+ }
+
+ /**
+ * 璁剧疆绛惧悕璁よ瘉闆嗗悎
+ */
+ public SecureRegistry addSignPatterns(List<SignSecure> signSecures) {
+ this.signSecures.addAll(signSecures);
+ return this;
+ }
+
+ /**
+ * 杩斿洖绛惧悕璁よ瘉闆嗗悎
+ */
+ public List<SignSecure> getSignSecures() {
+ return this.signSecures;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/utils/SecureUtil.java b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/utils/SecureUtil.java
new file mode 100644
index 0000000..037dbd7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/utils/SecureUtil.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.utils;
+
+import io.jsonwebtoken.JwtBuilder;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import lombok.SneakyThrows;
+import org.springblade.core.jwt.JwtUtil;
+import org.springblade.core.jwt.props.JwtProperties;
+import org.springblade.core.launch.constant.TokenConstant;
+import org.springblade.core.secure.TokenInfo;
+import org.springblade.core.secure.constant.SecureConstant;
+import org.springblade.core.secure.exception.SecureException;
+import org.springblade.core.secure.provider.IClientDetails;
+import org.springblade.core.secure.provider.IClientDetailsService;
+import org.springblade.core.tool.utils.*;
+
+import javax.crypto.spec.SecretKeySpec;
+import java.security.Key;
+import java.util.*;
+
+/**
+ * Secure宸ュ叿绫�
+ *
+ * @author Chill
+ */
+public class SecureUtil extends AuthUtil {
+ private final static String CLIENT_ID = TokenConstant.CLIENT_ID;
+
+ private static IClientDetailsService clientDetailsService;
+
+ private static JwtProperties jwtProperties;
+
+ /**
+ * 鑾峰彇瀹㈡埛绔湇鍔$被
+ *
+ * @return clientDetailsService
+ */
+ private static IClientDetailsService getClientDetailsService() {
+ if (clientDetailsService == null) {
+ clientDetailsService = SpringUtil.getBean(IClientDetailsService.class);
+ }
+ return clientDetailsService;
+ }
+
+ /**
+ * 鑾峰彇閰嶇疆绫�
+ *
+ * @return jwtProperties
+ */
+ private static JwtProperties getJwtProperties() {
+ if (jwtProperties == null) {
+ jwtProperties = SpringUtil.getBean(JwtProperties.class);
+ }
+ return jwtProperties;
+ }
+
+ /**
+ * 鍒涘缓浠ょ墝
+ *
+ * @param user user
+ * @param audience audience
+ * @param issuer issuer
+ * @param tokenType tokenType
+ * @return jwt
+ */
+ public static TokenInfo createJWT(Map<String, Object> user, String audience, String issuer, String tokenType) {
+
+ String[] tokens = extractAndDecodeHeader();
+ String clientId = tokens[0];
+ String clientSecret = tokens[1];
+
+ // 鑾峰彇瀹㈡埛绔俊鎭�
+ IClientDetails clientDetails = clientDetails(clientId);
+
+ // 鏍¢獙瀹㈡埛绔俊鎭�
+ if (!validateClient(clientDetails, clientId, clientSecret)) {
+ throw new SecureException("瀹㈡埛绔璇佸け璐�, 璇锋鏌ヨ姹傚ご [Authorization] 淇℃伅");
+ }
+
+ SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
+
+ long nowMillis = System.currentTimeMillis();
+ Date now = new Date(nowMillis);
+
+ //鐢熸垚绛惧悕瀵嗛挜
+ byte[] apiKeySecretBytes = Base64.getDecoder().decode(JwtUtil.getBase64Security());
+ Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
+
+ //娣诲姞鏋勬垚JWT鐨勭被
+ JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
+ .setIssuer(issuer)
+ .setAudience(audience)
+ .signWith(signingKey);
+
+ //璁剧疆JWT鍙傛暟
+ user.forEach(builder::claim);
+
+ //璁剧疆搴旂敤id
+ builder.claim(CLIENT_ID, clientId);
+
+ //娣诲姞Token杩囨湡鏃堕棿
+ long expireMillis;
+ if (tokenType.equals(TokenConstant.ACCESS_TOKEN)) {
+ expireMillis = clientDetails.getAccessTokenValidity() * 1000L;
+ } else if (tokenType.equals(TokenConstant.REFRESH_TOKEN)) {
+ expireMillis = clientDetails.getRefreshTokenValidity() * 1000L;
+ } else {
+ expireMillis = getExpire();
+ }
+ long expMillis = nowMillis + expireMillis;
+ Date exp = new Date(expMillis);
+ builder.setExpiration(exp).setNotBefore(now);
+
+ //缁勮Token淇℃伅
+ TokenInfo tokenInfo = new TokenInfo();
+ tokenInfo.setToken(builder.compact());
+ tokenInfo.setExpire((int) (expireMillis / 1000L));
+
+ //Token鐘舵�侀厤缃�, 浠呭湪鐢熸垚AccessToken鏃跺�欐墽琛�
+ if (getJwtProperties().getState() && TokenConstant.ACCESS_TOKEN.equals(tokenType)) {
+ String tenantId = String.valueOf(user.get(TokenConstant.TENANT_ID));
+ String userId = String.valueOf(user.get(TokenConstant.USER_ID));
+ JwtUtil.addAccessToken(tenantId, userId, tokenInfo.getToken(), tokenInfo.getExpire());
+ }
+ //Token鐘舵�侀厤缃�, 浠呭湪鐢熸垚RefreshToken鏃跺�欐墽琛�
+ if (getJwtProperties().getState() && getJwtProperties().getSingle() && TokenConstant.REFRESH_TOKEN.equals(tokenType)) {
+ String tenantId = String.valueOf(user.get(TokenConstant.TENANT_ID));
+ String userId = String.valueOf(user.get(TokenConstant.USER_ID));
+ JwtUtil.addRefreshToken(tenantId, userId, tokenInfo.getToken(), tokenInfo.getExpire());
+ }
+ return tokenInfo;
+ }
+
+ /**
+ * 鑾峰彇杩囨湡鏃堕棿(娆℃棩鍑屾櫒3鐐�)
+ *
+ * @return expire
+ */
+ public static long getExpire() {
+ Calendar cal = Calendar.getInstance();
+ cal.add(Calendar.DAY_OF_YEAR, 1);
+ cal.set(Calendar.HOUR_OF_DAY, 3);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ return cal.getTimeInMillis() - System.currentTimeMillis();
+ }
+
+ /**
+ * 瀹㈡埛绔俊鎭В鐮�
+ */
+ @SneakyThrows
+ public static String[] extractAndDecodeHeader() {
+ // 鑾峰彇璇锋眰澶村鎴风淇℃伅
+ String header = Objects.requireNonNull(WebUtil.getRequest()).getHeader(SecureConstant.BASIC_HEADER_KEY);
+ header = Func.toStr(header).replace(SecureConstant.BASIC_HEADER_PREFIX_EXT, SecureConstant.BASIC_HEADER_PREFIX);
+ if (!header.startsWith(SecureConstant.BASIC_HEADER_PREFIX)) {
+ throw new SecureException("鏈幏鍙栧埌璇锋眰澶碵Authorization]鐨勪俊鎭�");
+ }
+ byte[] base64Token = header.substring(6).getBytes(Charsets.UTF_8_NAME);
+
+ byte[] decoded;
+ try {
+ decoded = Base64.getDecoder().decode(base64Token);
+ } catch (IllegalArgumentException var7) {
+ throw new RuntimeException("瀹㈡埛绔护鐗岃В鏋愬け璐�");
+ }
+
+ String token = new String(decoded, Charsets.UTF_8_NAME);
+ int index = token.indexOf(StringPool.COLON);
+ if (index == -1) {
+ throw new RuntimeException("瀹㈡埛绔护鐗屼笉鍚堟硶");
+ } else {
+ return new String[]{token.substring(0, index), token.substring(index + 1)};
+ }
+ }
+
+ /**
+ * 鑾峰彇璇锋眰澶翠腑鐨勫鎴风id
+ */
+ public static String getClientIdFromHeader() {
+ String[] tokens = extractAndDecodeHeader();
+ assert tokens.length == 2;
+ return tokens[0];
+ }
+
+ /**
+ * 鑾峰彇瀹㈡埛绔俊鎭�
+ *
+ * @param clientId 瀹㈡埛绔痠d
+ * @return clientDetails
+ */
+ private static IClientDetails clientDetails(String clientId) {
+ return getClientDetailsService().loadClientByClientId(clientId);
+ }
+
+ /**
+ * 鏍¢獙Client
+ *
+ * @param clientId 瀹㈡埛绔痠d
+ * @param clientSecret 瀹㈡埛绔瘑閽�
+ * @return boolean
+ */
+ private static boolean validateClient(IClientDetails clientDetails, String clientId, String clientSecret) {
+ if (clientDetails != null) {
+ return StringUtil.equals(clientId, clientDetails.getClientId()) && StringUtil.equals(clientSecret, clientDetails.getClientSecret());
+ }
+ return false;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-test/pom.xml b/Source/BladeX-Tool/blade-core-test/pom.xml
new file mode 100644
index 0000000..d75037e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-test/pom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-core-test</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!-- Blade -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-launch</artifactId>
+ </dependency>
+ <!-- Test -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-core-test/src/main/java/org/springblade/core/test/BladeBootTest.java b/Source/BladeX-Tool/blade-core-test/src/main/java/org/springblade/core/test/BladeBootTest.java
new file mode 100644
index 0000000..ef54ed5
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-test/src/main/java/org/springblade/core/test/BladeBootTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.test;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.core.annotation.AliasFor;
+
+import java.lang.annotation.*;
+
+/**
+ * 绠�鍖� 娴嬭瘯
+ *
+ * @author L.cm
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@SpringBootTest
+@ExtendWith(BladeSpringExtension.class)
+public @interface BladeBootTest {
+ /**
+ * 鏈嶅姟鍚嶏細appName
+ * @return appName
+ */
+ @AliasFor("appName")
+ String value() default "blade-test";
+ /**
+ * 鏈嶅姟鍚嶏細appName
+ * @return appName
+ */
+ @AliasFor("value")
+ String appName() default "blade-test";
+ /**
+ * profile
+ * @return profile
+ */
+ String profile() default "dev";
+ /**
+ * 鍚敤 ServiceLoader 鍔犺浇 launcherService
+ * @return 鏄惁鍚敤
+ */
+ boolean enableLoader() default false;
+}
diff --git a/Source/BladeX-Tool/blade-core-test/src/main/java/org/springblade/core/test/BladeBootTestException.java b/Source/BladeX-Tool/blade-core-test/src/main/java/org/springblade/core/test/BladeBootTestException.java
new file mode 100644
index 0000000..1347156
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-test/src/main/java/org/springblade/core/test/BladeBootTestException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.test;
+
+/**
+ * blade test 寮傚父
+ *
+ * @author L.cm
+ */
+class BladeBootTestException extends RuntimeException {
+
+ BladeBootTestException(String message) {
+ super(message);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-test/src/main/java/org/springblade/core/test/BladeSpringExtension.java b/Source/BladeX-Tool/blade-core-test/src/main/java/org/springblade/core/test/BladeSpringExtension.java
new file mode 100644
index 0000000..c57f457
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-test/src/main/java/org/springblade/core/test/BladeSpringExtension.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.test;
+
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.springblade.core.launch.BladeApplication;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.launch.constant.NacosConstant;
+import org.springblade.core.launch.constant.SentinelConstant;
+import org.springblade.core.launch.service.LauncherService;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.lang.NonNull;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 璁剧疆鍚姩鍙傛暟
+ *
+ * @author L.cm
+ */
+public class BladeSpringExtension extends SpringExtension {
+
+ @Override
+ public void beforeAll(@NonNull ExtensionContext context) throws Exception {
+ super.beforeAll(context);
+ setUpTestClass(context);
+ }
+
+ private void setUpTestClass(ExtensionContext context) {
+ Class<?> clazz = context.getRequiredTestClass();
+ BladeBootTest bladeBootTest = AnnotationUtils.getAnnotation(clazz, BladeBootTest.class);
+ if (bladeBootTest == null) {
+ throw new BladeBootTestException(String.format("%s must be @BladeBootTest .", clazz));
+ }
+ String appName = bladeBootTest.appName();
+ String profile = bladeBootTest.profile();
+ boolean isLocalDev = BladeApplication.isLocalDev();
+ Properties props = System.getProperties();
+ props.setProperty("blade.env", profile);
+ props.setProperty("blade.name", appName);
+ props.setProperty("blade.is-local", String.valueOf(isLocalDev));
+ props.setProperty("blade.dev-mode", profile.equals(AppConstant.PROD_CODE) ? "false" : "true");
+ props.setProperty("blade.service.version", AppConstant.APPLICATION_VERSION);
+ props.setProperty("spring.application.name", appName);
+ props.setProperty("spring.profiles.active", profile);
+ props.setProperty("info.version", AppConstant.APPLICATION_VERSION);
+ props.setProperty("info.desc", appName);
+ props.setProperty("loadbalancer.client.name", appName);
+ props.setProperty("spring.cloud.sentinel.transport.dashboard", SentinelConstant.SENTINEL_ADDR);
+ props.setProperty("spring.main.allow-bean-definition-overriding", "true");
+ props.setProperty("spring.cloud.nacos.config.shared-configs[0].data-id", NacosConstant.sharedDataId());
+ props.setProperty("spring.cloud.nacos.config.shared-configs[0].group", NacosConstant.NACOS_CONFIG_GROUP);
+ props.setProperty("spring.cloud.nacos.config.shared-configs[0].refresh", NacosConstant.NACOS_CONFIG_REFRESH);
+ props.setProperty("spring.cloud.nacos.config.file-extension", NacosConstant.NACOS_CONFIG_FORMAT);
+ props.setProperty("spring.cloud.nacos.config.shared-configs[1].data-id", NacosConstant.sharedDataId(profile));
+ props.setProperty("spring.cloud.nacos.config.shared-configs[1].group", NacosConstant.NACOS_CONFIG_GROUP);
+ props.setProperty("spring.cloud.nacos.config.shared-configs[1].refresh", NacosConstant.NACOS_CONFIG_REFRESH);
+ // 鍔犺浇鑷畾涔夌粍浠�
+ if (bladeBootTest.enableLoader()) {
+ SpringApplicationBuilder builder = new SpringApplicationBuilder(clazz);
+ List<LauncherService> launcherList = new ArrayList<>();
+ ServiceLoader.load(LauncherService.class).forEach(launcherList::add);
+ launcherList.stream().sorted(Comparator.comparing(LauncherService::getOrder)).collect(Collectors.toList())
+ .forEach(launcherService -> launcherService.launcher(builder, appName, profile, isLocalDev));
+ }
+ System.err.printf("---[junit.test]:[%s]---鍚姩涓紝璇诲彇鍒扮殑鐜鍙橀噺:[%s]%n", appName, profile);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/pom.xml b/Source/BladeX-Tool/blade-core-tool/pom.xml
new file mode 100644
index 0000000..f181ae3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <parent>
+ <groupId>org.springblade</groupId>
+ <artifactId>BladeX-Tool</artifactId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-core-tool</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!-- Blade -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-launch</artifactId>
+ </dependency>
+ <!-- Jackson -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-jsr310</artifactId>
+ </dependency>
+ <!-- Guava -->
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <!--Swagger-->
+ <dependency>
+ <groupId>io.springfox</groupId>
+ <artifactId>springfox-swagger2</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>io.swagger</groupId>
+ <artifactId>swagger-models</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>io.swagger</groupId>
+ <artifactId>swagger-models</artifactId>
+ </dependency>
+ <!-- protostuff -->
+ <dependency>
+ <groupId>io.protostuff</groupId>
+ <artifactId>protostuff-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.protostuff</groupId>
+ <artifactId>protostuff-runtime</artifactId>
+ </dependency>
+ <!-- jackson -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.module</groupId>
+ <artifactId>jackson-module-jaxb-annotations</artifactId>
+ </dependency>
+ <!-- validation -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-validation</artifactId>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/api/IResultCode.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/api/IResultCode.java
new file mode 100644
index 0000000..cdfa78d
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/api/IResultCode.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.api;
+
+import java.io.Serializable;
+
+/**
+ * 涓氬姟浠g爜鎺ュ彛
+ *
+ * @author Chill
+ */
+public interface IResultCode extends Serializable {
+
+ /**
+ * 鑾峰彇娑堟伅
+ *
+ * @return
+ */
+ String getMessage();
+
+ /**
+ * 鑾峰彇鐘舵�佺爜
+ *
+ * @return
+ */
+ int getCode();
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/api/R.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/api/R.java
new file mode 100644
index 0000000..b30ca40
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/api/R.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.api;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+import org.springblade.core.tool.constant.BladeConstant;
+import org.springblade.core.tool.utils.ObjectUtil;
+import org.springframework.lang.Nullable;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.Serializable;
+import java.util.Optional;
+
+/**
+ * 缁熶竴API鍝嶅簲缁撴灉灏佽
+ *
+ * @author Chill
+ */
+@Getter
+@Setter
+@ToString
+@ApiModel(description = "杩斿洖淇℃伅")
+@NoArgsConstructor
+public class R<T> implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @ApiModelProperty(value = "鐘舵�佺爜", required = true)
+ private int code;
+ @ApiModelProperty(value = "鏄惁鎴愬姛", required = true)
+ private boolean success;
+ @ApiModelProperty(value = "鎵胯浇鏁版嵁")
+ private T data;
+ @ApiModelProperty(value = "杩斿洖娑堟伅", required = true)
+ private String msg;
+
+ private R(IResultCode resultCode) {
+ this(resultCode, null, resultCode.getMessage());
+ }
+
+ private R(IResultCode resultCode, String msg) {
+ this(resultCode, null, msg);
+ }
+
+ private R(IResultCode resultCode, T data) {
+ this(resultCode, data, resultCode.getMessage());
+ }
+
+ private R(IResultCode resultCode, T data, String msg) {
+ this(resultCode.getCode(), data, msg);
+ }
+
+ private R(int code, T data, String msg) {
+ this.code = code;
+ this.data = data;
+ this.msg = msg;
+ this.success = ResultCode.SUCCESS.code == code;
+ }
+
+ /**
+ * 鍒ゆ柇杩斿洖鏄惁涓烘垚鍔�
+ *
+ * @param result Result
+ * @return 鏄惁鎴愬姛
+ */
+ public static boolean isSuccess(@Nullable R<?> result) {
+ return Optional.ofNullable(result)
+ .map(x -> ObjectUtil.nullSafeEquals(ResultCode.SUCCESS.code, x.code))
+ .orElse(Boolean.FALSE);
+ }
+
+ /**
+ * 鍒ゆ柇杩斿洖鏄惁涓烘垚鍔�
+ *
+ * @param result Result
+ * @return 鏄惁鎴愬姛
+ */
+ public static boolean isNotSuccess(@Nullable R<?> result) {
+ return !R.isSuccess(result);
+ }
+
+ /**
+ * 杩斿洖R
+ *
+ * @param data 鏁版嵁
+ * @param <T> T 娉涘瀷鏍囪
+ * @return R
+ */
+ public static <T> R<T> data(T data) {
+ return data(data, BladeConstant.DEFAULT_SUCCESS_MESSAGE);
+ }
+
+ /**
+ * 杩斿洖R
+ *
+ * @param data 鏁版嵁
+ * @param msg 娑堟伅
+ * @param <T> T 娉涘瀷鏍囪
+ * @return R
+ */
+ public static <T> R<T> data(T data, String msg) {
+ return data(HttpServletResponse.SC_OK, data, msg);
+ }
+
+ /**
+ * 杩斿洖R
+ *
+ * @param code 鐘舵�佺爜
+ * @param data 鏁版嵁
+ * @param msg 娑堟伅
+ * @param <T> T 娉涘瀷鏍囪
+ * @return R
+ */
+ public static <T> R<T> data(int code, T data, String msg) {
+ return new R<>(code, data, data == null ? BladeConstant.DEFAULT_NULL_MESSAGE : msg);
+ }
+
+ /**
+ * 杩斿洖R
+ *
+ * @param msg 娑堟伅
+ * @param <T> T 娉涘瀷鏍囪
+ * @return R
+ */
+ public static <T> R<T> success(String msg) {
+ return new R<>(ResultCode.SUCCESS, msg);
+ }
+
+ /**
+ * 杩斿洖R
+ *
+ * @param resultCode 涓氬姟浠g爜
+ * @param <T> T 娉涘瀷鏍囪
+ * @return R
+ */
+ public static <T> R<T> success(IResultCode resultCode) {
+ return new R<>(resultCode);
+ }
+
+ /**
+ * 杩斿洖R
+ *
+ * @param resultCode 涓氬姟浠g爜
+ * @param msg 娑堟伅
+ * @param <T> T 娉涘瀷鏍囪
+ * @return R
+ */
+ public static <T> R<T> success(IResultCode resultCode, String msg) {
+ return new R<>(resultCode, msg);
+ }
+
+ /**
+ * 杩斿洖R
+ *
+ * @param msg 娑堟伅
+ * @param <T> T 娉涘瀷鏍囪
+ * @return R
+ */
+ public static <T> R<T> fail(String msg) {
+ return new R<>(ResultCode.FAILURE, msg);
+ }
+
+
+ /**
+ * 杩斿洖R
+ *
+ * @param code 鐘舵�佺爜
+ * @param msg 娑堟伅
+ * @param <T> T 娉涘瀷鏍囪
+ * @return R
+ */
+ public static <T> R<T> fail(int code, String msg) {
+ return new R<>(code, null, msg);
+ }
+
+ /**
+ * 杩斿洖R
+ *
+ * @param resultCode 涓氬姟浠g爜
+ * @param <T> T 娉涘瀷鏍囪
+ * @return R
+ */
+ public static <T> R<T> fail(IResultCode resultCode) {
+ return new R<>(resultCode);
+ }
+
+ /**
+ * 杩斿洖R
+ *
+ * @param resultCode 涓氬姟浠g爜
+ * @param msg 娑堟伅
+ * @param <T> T 娉涘瀷鏍囪
+ * @return R
+ */
+ public static <T> R<T> fail(IResultCode resultCode, String msg) {
+ return new R<>(resultCode, msg);
+ }
+
+ /**
+ * 杩斿洖R
+ *
+ * @param flag 鎴愬姛鐘舵��
+ * @return R
+ */
+ public static <T> R<T> status(boolean flag) {
+ return flag ? success(BladeConstant.DEFAULT_SUCCESS_MESSAGE) : fail(BladeConstant.DEFAULT_FAILURE_MESSAGE);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/api/ResultCode.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/api/ResultCode.java
new file mode 100644
index 0000000..894fa39
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/api/ResultCode.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.api;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 涓氬姟浠g爜鏋氫妇
+ *
+ * @author Chill
+ */
+@Getter
+@AllArgsConstructor
+public enum ResultCode implements IResultCode {
+
+ /**
+ * 鎿嶄綔鎴愬姛
+ */
+ SUCCESS(HttpServletResponse.SC_OK, "鎿嶄綔鎴愬姛"),
+
+ /**
+ * 涓氬姟寮傚父
+ */
+ FAILURE(HttpServletResponse.SC_BAD_REQUEST, "涓氬姟寮傚父"),
+
+ /**
+ * 璇锋眰鏈巿鏉�
+ */
+ UN_AUTHORIZED(HttpServletResponse.SC_UNAUTHORIZED, "璇锋眰鏈巿鏉�"),
+
+ /**
+ * 瀹㈡埛绔姹傛湭鎺堟潈
+ */
+ CLIENT_UN_AUTHORIZED(HttpServletResponse.SC_UNAUTHORIZED, "瀹㈡埛绔姹傛湭鎺堟潈"),
+
+ /**
+ * 404 娌℃壘鍒拌姹�
+ */
+ NOT_FOUND(HttpServletResponse.SC_NOT_FOUND, "404 娌℃壘鍒拌姹�"),
+
+ /**
+ * 娑堟伅涓嶈兘璇诲彇
+ */
+ MSG_NOT_READABLE(HttpServletResponse.SC_BAD_REQUEST, "娑堟伅涓嶈兘璇诲彇"),
+
+ /**
+ * 涓嶆敮鎸佸綋鍓嶈姹傛柟娉�
+ */
+ METHOD_NOT_SUPPORTED(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "涓嶆敮鎸佸綋鍓嶈姹傛柟娉�"),
+
+ /**
+ * 涓嶆敮鎸佸綋鍓嶅獟浣撶被鍨�
+ */
+ MEDIA_TYPE_NOT_SUPPORTED(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, "涓嶆敮鎸佸綋鍓嶅獟浣撶被鍨�"),
+
+ /**
+ * 璇锋眰琚嫆缁�
+ */
+ REQ_REJECT(HttpServletResponse.SC_FORBIDDEN, "璇锋眰琚嫆缁�"),
+
+ /**
+ * 鏈嶅姟鍣ㄥ紓甯�
+ */
+ INTERNAL_SERVER_ERROR(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "鏈嶅姟鍣ㄥ紓甯�"),
+
+ /**
+ * 缂哄皯蹇呰鐨勮姹傚弬鏁�
+ */
+ PARAM_MISS(HttpServletResponse.SC_BAD_REQUEST, "缂哄皯蹇呰鐨勮姹傚弬鏁�"),
+
+ /**
+ * 璇锋眰鍙傛暟绫诲瀷閿欒
+ */
+ PARAM_TYPE_ERROR(HttpServletResponse.SC_BAD_REQUEST, "璇锋眰鍙傛暟绫诲瀷閿欒"),
+
+ /**
+ * 璇锋眰鍙傛暟缁戝畾閿欒
+ */
+ PARAM_BIND_ERROR(HttpServletResponse.SC_BAD_REQUEST, "璇锋眰鍙傛暟缁戝畾閿欒"),
+
+ /**
+ * 鍙傛暟鏍¢獙澶辫触
+ */
+ PARAM_VALID_ERROR(HttpServletResponse.SC_BAD_REQUEST, "鍙傛暟鏍¢獙澶辫触"),
+ ;
+
+ /**
+ * code缂栫爜
+ */
+ final int code;
+ /**
+ * 涓枃淇℃伅鎻忚堪
+ */
+ final String message;
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BeanProperty.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BeanProperty.java
new file mode 100644
index 0000000..4ca9c0c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BeanProperty.java
@@ -0,0 +1,16 @@
+package org.springblade.core.tool.beans;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Bean灞炴��
+ *
+ * @author Chill
+ */
+@Getter
+@AllArgsConstructor
+public class BeanProperty {
+ private final String name;
+ private final Class<?> type;
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanCopier.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanCopier.java
new file mode 100644
index 0000000..0dc0c33
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanCopier.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.beans;
+
+
+import org.springblade.core.tool.utils.BeanUtil;
+import org.springblade.core.tool.utils.ClassUtil;
+import org.springblade.core.tool.utils.ReflectUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.asm.ClassVisitor;
+import org.springframework.asm.Label;
+import org.springframework.asm.Opcodes;
+import org.springframework.asm.Type;
+import org.springframework.cglib.core.*;
+import org.springframework.lang.Nullable;
+import org.springframework.util.ClassUtils;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.security.ProtectionDomain;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * spring cglib 榄旀敼
+ *
+ * <p>
+ * 1. 鏀寔閾惧紡 bean锛屾敮鎸� map
+ * 2. ClassLoader 璺� target 淇濇寔涓�鑷�
+ * </p>
+ *
+ * @author L.cm
+ */
+public abstract class BladeBeanCopier {
+ private static final Type CONVERTER = TypeUtils.parseType("org.springframework.cglib.core.Converter");
+ private static final Type BEAN_COPIER = TypeUtils.parseType(BladeBeanCopier.class.getName());
+ private static final Type BEAN_MAP = TypeUtils.parseType(Map.class.getName());
+ private static final Signature COPY = new Signature("copy", Type.VOID_TYPE, new Type[]{Constants.TYPE_OBJECT, Constants.TYPE_OBJECT, CONVERTER});
+ private static final Signature CONVERT = TypeUtils.parseSignature("Object convert(Object, Class, Object)");
+ private static final Signature BEAN_MAP_GET = TypeUtils.parseSignature("Object get(Object)");
+ private static final Type CLASS_UTILS = TypeUtils.parseType(ClassUtils.class.getName());
+ private static final Signature IS_ASSIGNABLE_VALUE = TypeUtils.parseSignature("boolean isAssignableValue(Class, Object)");
+ /**
+ * The map to store {@link BladeBeanCopier} of source type and class type for copy.
+ */
+ private static final ConcurrentMap<BladeBeanCopierKey, BladeBeanCopier> BEAN_COPIER_MAP = new ConcurrentHashMap<>();
+
+ public static BladeBeanCopier create(Class source, Class target, boolean useConverter) {
+ return BladeBeanCopier.create(source, target, useConverter, false);
+ }
+
+ public static BladeBeanCopier create(Class source, Class target, boolean useConverter, boolean nonNull) {
+ BladeBeanCopierKey copierKey = new BladeBeanCopierKey(source, target, useConverter, nonNull);
+ // 鍒╃敤 ConcurrentMap 缂撳瓨 鎻愰珮鎬ц兘锛屾帴杩� 鐩存帴 get set
+ return BEAN_COPIER_MAP.computeIfAbsent(copierKey, key -> {
+ Generator gen = new Generator();
+ gen.setSource(key.getSource());
+ gen.setTarget(key.getTarget());
+ gen.setUseConverter(key.isUseConverter());
+ gen.setNonNull(key.isNonNull());
+ return gen.create(key);
+ });
+ }
+
+ /**
+ * Bean copy
+ *
+ * @param from from Bean
+ * @param to to Bean
+ * @param converter Converter
+ */
+ abstract public void copy(Object from, Object to, @Nullable Converter converter);
+
+ public static class Generator extends AbstractClassGenerator {
+ private static final Source SOURCE = new Source(BladeBeanCopier.class.getName());
+ private Class source;
+ private Class target;
+ private boolean useConverter;
+ private boolean nonNull;
+
+ Generator() {
+ super(SOURCE);
+ }
+
+ public void setSource(Class source) {
+ if (!Modifier.isPublic(source.getModifiers())) {
+ setNamePrefix(source.getName());
+ }
+ this.source = source;
+ }
+
+ public void setTarget(Class target) {
+ if (!Modifier.isPublic(target.getModifiers())) {
+ setNamePrefix(target.getName());
+ }
+ this.target = target;
+ }
+
+ public void setUseConverter(boolean useConverter) {
+ this.useConverter = useConverter;
+ }
+
+ public void setNonNull(boolean nonNull) {
+ this.nonNull = nonNull;
+ }
+
+ @Override
+ protected ClassLoader getDefaultClassLoader() {
+ // L.cm 淇濊瘉 鍜� 杩斿洖浣跨敤鍚屼竴涓� ClassLoader
+ return target.getClassLoader();
+ }
+
+ @Override
+ protected ProtectionDomain getProtectionDomain() {
+ return ReflectUtils.getProtectionDomain(source);
+ }
+
+ @Override
+ public BladeBeanCopier create(Object key) {
+ return (BladeBeanCopier) super.create(key);
+ }
+
+ @Override
+ public void generateClass(ClassVisitor v) {
+ Type sourceType = Type.getType(source);
+ Type targetType = Type.getType(target);
+ ClassEmitter ce = new ClassEmitter(v);
+ ce.begin_class(Constants.V1_2,
+ Constants.ACC_PUBLIC,
+ getClassName(),
+ BEAN_COPIER,
+ null,
+ Constants.SOURCE_FILE);
+
+ EmitUtils.null_constructor(ce);
+ CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, COPY, null);
+
+ // map 鍗曠嫭澶勭悊
+ if (Map.class.isAssignableFrom(source)) {
+ generateClassFormMap(ce, e, sourceType, targetType);
+ return;
+ }
+
+ // 2018.12.27 by L.cm 鏀寔閾惧紡 bean
+ // 娉ㄦ剰锛氭澶勯渶鍏煎閾惧紡bean 浣跨敤浜� spring 鐨勬柟娉曪紝姣旇緝鑰楁椂
+ PropertyDescriptor[] getters = ReflectUtil.getBeanGetters(source);
+ PropertyDescriptor[] setters = ReflectUtil.getBeanSetters(target);
+ Map<String, PropertyDescriptor> names = new HashMap<>(16);
+ for (PropertyDescriptor getter : getters) {
+ names.put(getter.getName(), getter);
+ }
+
+ Local targetLocal = e.make_local();
+ Local sourceLocal = e.make_local();
+ e.load_arg(1);
+ e.checkcast(targetType);
+ e.store_local(targetLocal);
+ e.load_arg(0);
+ e.checkcast(sourceType);
+ e.store_local(sourceLocal);
+
+ for (PropertyDescriptor setter : setters) {
+ String propName = setter.getName();
+
+ CopyProperty targetIgnoreCopy = ReflectUtil.getAnnotation(target, propName, CopyProperty.class);
+ // set 涓婃湁蹇界暐鐨� 娉ㄨВ
+ if (targetIgnoreCopy != null) {
+ if (targetIgnoreCopy.ignore()) {
+ continue;
+ }
+ // 娉ㄨВ涓婄殑鍒悕锛屽鏋滃埆鍚嶄笉涓虹┖锛屼娇鐢ㄥ埆鍚�
+ String aliasTargetPropName = targetIgnoreCopy.value();
+ if (StringUtil.isNotBlank(aliasTargetPropName)) {
+ propName = aliasTargetPropName;
+ }
+ }
+ // 鎵惧埌瀵瑰簲鐨� get
+ PropertyDescriptor getter = names.get(propName);
+ // 娌℃湁 get 璺冲嚭
+ if (getter == null) {
+ continue;
+ }
+
+ MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod());
+ Method writeMethod = setter.getWriteMethod();
+ MethodInfo write = ReflectUtils.getMethodInfo(writeMethod);
+ Type returnType = read.getSignature().getReturnType();
+ Type setterType = write.getSignature().getArgumentTypes()[0];
+ Class<?> getterPropertyType = getter.getPropertyType();
+ Class<?> setterPropertyType = setter.getPropertyType();
+
+ // L.cm 2019.01.12 浼樺寲閫昏緫锛屽厛鍒ゆ柇绫诲瀷锛岀被鍨嬩竴鑷寸洿鎺� set锛屼笉鍚屽啀鍒ゆ柇 鏄惁 绫诲瀷杞崲
+ // nonNull Label
+ Label l0 = e.make_label();
+ // 鍒ゆ柇绫诲瀷鏄惁涓�鑷达紝鍖呮嫭 鍖呰绫诲瀷
+ if (ClassUtil.isAssignable(setterPropertyType, getterPropertyType)) {
+ // 2018.12.27 by L.cm 鏀寔閾惧紡 bean
+ e.load_local(targetLocal);
+ e.load_local(sourceLocal);
+ e.invoke(read);
+ boolean getterIsPrimitive = getterPropertyType.isPrimitive();
+ boolean setterIsPrimitive = setterPropertyType.isPrimitive();
+
+ if (nonNull) {
+ // 闇�瑕佽惤鏍堬紝寮哄埗瑁呯
+ e.box(returnType);
+ Local var = e.make_local();
+ e.store_local(var);
+ e.load_local(var);
+ // nonNull Label
+ e.ifnull(l0);
+ e.load_local(targetLocal);
+ e.load_local(var);
+ // 闇�瑕佽惤鏍堬紝寮哄埗鎷嗙
+ e.unbox_or_zero(setterType);
+ } else {
+ // 濡傛灉 get 涓哄師濮嬬被鍨嬶紝闇�瑕佽绠�
+ if (getterIsPrimitive && !setterIsPrimitive) {
+ e.box(returnType);
+ }
+ // 濡傛灉 set 涓哄師濮嬬被鍨嬶紝闇�瑕佹媶绠�
+ if (!getterIsPrimitive && setterIsPrimitive) {
+ e.unbox_or_zero(setterType);
+ }
+ }
+
+ // 鏋勯�� set 鏂规硶
+ invokeWrite(e, write, writeMethod, nonNull, l0);
+ } else if (useConverter) {
+ e.load_local(targetLocal);
+ e.load_arg(2);
+ e.load_local(sourceLocal);
+ e.invoke(read);
+ e.box(returnType);
+
+ if (nonNull) {
+ Local var = e.make_local();
+ e.store_local(var);
+ e.load_local(var);
+ e.ifnull(l0);
+ e.load_local(targetLocal);
+ e.load_arg(2);
+ e.load_local(var);
+ }
+
+ EmitUtils.load_class(e, setterType);
+ // 鏇存敼鎴愪簡灞炴�у悕锛屼箣鍓嶆槸 set 鏂规硶鍚�
+ e.push(propName);
+ e.invoke_interface(CONVERTER, CONVERT);
+ e.unbox_or_zero(setterType);
+
+ // 鏋勯�� set 鏂规硶
+ invokeWrite(e, write, writeMethod, nonNull, l0);
+ }
+ }
+ e.return_value();
+ e.end_method();
+ ce.end_class();
+ }
+
+ private static void invokeWrite(CodeEmitter e, MethodInfo write, Method writeMethod, boolean nonNull, Label l0) {
+ // 杩斿洖鍊硷紝鍒ゆ柇 閾惧紡 bean
+ Class<?> returnType = writeMethod.getReturnType();
+ e.invoke(write);
+ // 閾惧紡 bean锛屾湁杩斿洖鍊奸渶瑕� pop
+ if (!returnType.equals(Void.TYPE)) {
+ e.pop();
+ }
+ if (nonNull) {
+ e.visitLabel(l0);
+ }
+ }
+
+ @Override
+ protected Object firstInstance(Class type) {
+ return BeanUtil.newInstance(type);
+ }
+
+ @Override
+ protected Object nextInstance(Object instance) {
+ return instance;
+ }
+
+ /**
+ * 澶勭悊 map 鐨� copy
+ * @param ce ClassEmitter
+ * @param e CodeEmitter
+ * @param sourceType sourceType
+ * @param targetType targetType
+ */
+ public void generateClassFormMap(ClassEmitter ce, CodeEmitter e, Type sourceType, Type targetType) {
+ // 2018.12.27 by L.cm 鏀寔閾惧紡 bean
+ PropertyDescriptor[] setters = ReflectUtil.getBeanSetters(target);
+
+ // 鍏ュ彛鍙橀噺
+ Local targetLocal = e.make_local();
+ Local sourceLocal = e.make_local();
+ e.load_arg(1);
+ e.checkcast(targetType);
+ e.store_local(targetLocal);
+ e.load_arg(0);
+ e.checkcast(sourceType);
+ e.store_local(sourceLocal);
+ Type mapBox = Type.getType(Object.class);
+
+ for (PropertyDescriptor setter : setters) {
+ String propName = setter.getName();
+
+ // set 涓婃湁蹇界暐鐨� 娉ㄨВ
+ CopyProperty targetIgnoreCopy = ReflectUtil.getAnnotation(target, propName, CopyProperty.class);
+ if (targetIgnoreCopy != null) {
+ if (targetIgnoreCopy.ignore()) {
+ continue;
+ }
+ // 娉ㄨВ涓婄殑鍒悕
+ String aliasTargetPropName = targetIgnoreCopy.value();
+ if (StringUtil.isNotBlank(aliasTargetPropName)) {
+ propName = aliasTargetPropName;
+ }
+ }
+
+ Method writeMethod = setter.getWriteMethod();
+ MethodInfo write = ReflectUtils.getMethodInfo(writeMethod);
+ Type setterType = write.getSignature().getArgumentTypes()[0];
+
+ e.load_local(targetLocal);
+ e.load_local(sourceLocal);
+
+ e.push(propName);
+ // 鎵ц map get
+ e.invoke_interface(BEAN_MAP, BEAN_MAP_GET);
+ // box 瑁呯锛岄伩鍏� array[] 鏁扮粍闂
+ e.box(mapBox);
+
+ // 鐢熸垚鍙橀噺
+ Local var = e.make_local();
+ e.store_local(var);
+ e.load_local(var);
+
+ // 鍏堝垽鏂� 涓嶄负null锛岀劧鍚庡仛绫诲瀷鍒ゆ柇
+ Label l0 = e.make_label();
+ e.ifnull(l0);
+ EmitUtils.load_class(e, setterType);
+ e.load_local(var);
+ // ClassUtils.isAssignableValue(Integer.class, id)
+ e.invoke_static(CLASS_UTILS, IS_ASSIGNABLE_VALUE);
+ Label l1 = new Label();
+ // 杩斿洖鍊硷紝鍒ゆ柇 閾惧紡 bean
+ Class<?> returnType = writeMethod.getReturnType();
+ if (useConverter) {
+ e.if_jump(Opcodes.IFEQ, l1);
+ e.load_local(targetLocal);
+ e.load_local(var);
+ e.unbox_or_zero(setterType);
+ e.invoke(write);
+ if (!returnType.equals(Void.TYPE)) {
+ e.pop();
+ }
+ e.goTo(l0);
+ e.visitLabel(l1);
+ e.load_local(targetLocal);
+ e.load_arg(2);
+ e.load_local(var);
+ EmitUtils.load_class(e, setterType);
+ e.push(propName);
+ e.invoke_interface(CONVERTER, CONVERT);
+ e.unbox_or_zero(setterType);
+ e.invoke(write);
+ } else {
+ e.if_jump(Opcodes.IFEQ, l0);
+ e.load_local(targetLocal);
+ e.load_local(var);
+ e.unbox_or_zero(setterType);
+ e.invoke(write);
+ }
+ // 杩斿洖鍊硷紝鍒ゆ柇 閾惧紡 bean
+ if (!returnType.equals(Void.TYPE)) {
+ e.pop();
+ }
+ e.visitLabel(l0);
+ }
+ e.return_value();
+ e.end_method();
+ ce.end_class();
+ }
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanCopierKey.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanCopierKey.java
new file mode 100644
index 0000000..af10c54
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanCopierKey.java
@@ -0,0 +1,20 @@
+package org.springblade.core.tool.beans;
+
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+
+/**
+ * copy key
+ *
+ * @author L.cm
+ */
+@Getter
+@EqualsAndHashCode
+@AllArgsConstructor
+public class BladeBeanCopierKey {
+ private final Class<?> source;
+ private final Class<?> target;
+ private final boolean useConverter;
+ private final boolean nonNull;
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanMap.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanMap.java
new file mode 100644
index 0000000..f2823fb
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanMap.java
@@ -0,0 +1,125 @@
+package org.springblade.core.tool.beans;
+
+import org.springframework.asm.ClassVisitor;
+import org.springframework.cglib.beans.BeanMap;
+import org.springframework.cglib.core.AbstractClassGenerator;
+import org.springframework.cglib.core.ReflectUtils;
+
+import java.security.ProtectionDomain;
+
+/**
+ * 閲嶅啓 cglib BeanMap锛屾敮鎸侀摼寮廱ean
+ *
+ * @author L.cm
+ */
+public abstract class BladeBeanMap extends BeanMap {
+ protected BladeBeanMap() {
+ }
+
+ protected BladeBeanMap(Object bean) {
+ super(bean);
+ }
+
+ public static BladeBeanMap create(Object bean) {
+ BladeGenerator gen = new BladeGenerator();
+ gen.setBean(bean);
+ return gen.create();
+ }
+
+ /**
+ * newInstance
+ *
+ * @param o Object
+ * @return BladeBeanMap
+ */
+ @Override
+ public abstract BladeBeanMap newInstance(Object o);
+
+ public static class BladeGenerator extends AbstractClassGenerator {
+ private static final Source SOURCE = new Source(BladeBeanMap.class.getName());
+
+ private Object bean;
+ private Class beanClass;
+ private int require;
+
+ public BladeGenerator() {
+ super(SOURCE);
+ }
+
+ /**
+ * Set the bean that the generated map should reflect. The bean may be swapped
+ * out for another bean of the same type using {@link #setBean}.
+ * Calling this method overrides any value previously set using {@link #setBeanClass}.
+ * You must call either this method or {@link #setBeanClass} before {@link #create}.
+ *
+ * @param bean the initial bean
+ */
+ public void setBean(Object bean) {
+ this.bean = bean;
+ if (bean != null) {
+ beanClass = bean.getClass();
+ }
+ }
+
+ /**
+ * Set the class of the bean that the generated map should support.
+ * You must call either this method or {@link #setBeanClass} before {@link #create}.
+ *
+ * @param beanClass the class of the bean
+ */
+ public void setBeanClass(Class beanClass) {
+ this.beanClass = beanClass;
+ }
+
+ /**
+ * Limit the properties reflected by the generated map.
+ *
+ * @param require any combination of {@link #REQUIRE_GETTER} and
+ * {@link #REQUIRE_SETTER}; default is zero (any property allowed)
+ */
+ public void setRequire(int require) {
+ this.require = require;
+ }
+
+ @Override
+ protected ClassLoader getDefaultClassLoader() {
+ return beanClass.getClassLoader();
+ }
+
+ @Override
+ protected ProtectionDomain getProtectionDomain() {
+ return ReflectUtils.getProtectionDomain(beanClass);
+ }
+
+ /**
+ * Create a new instance of the <code>BeanMap</code>. An existing
+ * generated class will be reused if possible.
+ *
+ * @return {BladeBeanMap}
+ */
+ public BladeBeanMap create() {
+ if (beanClass == null) {
+ throw new IllegalArgumentException("Class of bean unknown");
+ }
+ setNamePrefix(beanClass.getName());
+ BladeBeanMapKey key = new BladeBeanMapKey(beanClass, require);
+ return (BladeBeanMap) super.create(key);
+ }
+
+ @Override
+ public void generateClass(ClassVisitor v) throws Exception {
+ new BladeBeanMapEmitter(v, getClassName(), beanClass, require);
+ }
+
+ @Override
+ protected Object firstInstance(Class type) {
+ return ((BeanMap) ReflectUtils.newInstance(type)).newInstance(bean);
+ }
+
+ @Override
+ protected Object nextInstance(Object instance) {
+ return ((BeanMap) instance).newInstance(bean);
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanMapEmitter.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanMapEmitter.java
new file mode 100644
index 0000000..8455905
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanMapEmitter.java
@@ -0,0 +1,192 @@
+package org.springblade.core.tool.beans;
+
+import org.springblade.core.tool.utils.ReflectUtil;
+import org.springframework.asm.ClassVisitor;
+import org.springframework.asm.Label;
+import org.springframework.asm.Type;
+import org.springframework.cglib.core.*;
+
+import java.beans.PropertyDescriptor;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * 閲嶅啓 cglib BeanMap 澶勭悊鍣�
+ *
+ * @author L.cm
+ */
+class BladeBeanMapEmitter extends ClassEmitter {
+ private static final Type BEAN_MAP = TypeUtils.parseType(BladeBeanMap.class.getName());
+ private static final Type FIXED_KEY_SET = TypeUtils.parseType("org.springframework.cglib.beans.FixedKeySet");
+ private static final Signature CSTRUCT_OBJECT = TypeUtils.parseConstructor("Object");
+ private static final Signature CSTRUCT_STRING_ARRAY = TypeUtils.parseConstructor("String[]");
+ private static final Signature BEAN_MAP_GET = TypeUtils.parseSignature("Object get(Object, Object)");
+ private static final Signature BEAN_MAP_PUT = TypeUtils.parseSignature("Object put(Object, Object, Object)");
+ private static final Signature KEY_SET = TypeUtils.parseSignature("java.util.Set keySet()");
+ private static final Signature NEW_INSTANCE = new Signature("newInstance", BEAN_MAP, new Type[]{Constants.TYPE_OBJECT});
+ private static final Signature GET_PROPERTY_TYPE = TypeUtils.parseSignature("Class getPropertyType(String)");
+
+ public BladeBeanMapEmitter(ClassVisitor v, String className, Class type, int require) {
+ super(v);
+
+ begin_class(Constants.V1_2, Constants.ACC_PUBLIC, className, BEAN_MAP, null, Constants.SOURCE_FILE);
+ EmitUtils.null_constructor(this);
+ EmitUtils.factory_method(this, NEW_INSTANCE);
+ generateConstructor();
+
+ Map<String, PropertyDescriptor> getters = makePropertyMap(ReflectUtil.getBeanGetters(type));
+ Map<String, PropertyDescriptor> setters = makePropertyMap(ReflectUtil.getBeanSetters(type));
+ Map<String, PropertyDescriptor> allProps = new HashMap<>(32);
+ allProps.putAll(getters);
+ allProps.putAll(setters);
+
+ if (require != 0) {
+ for (Iterator it = allProps.keySet().iterator(); it.hasNext(); ) {
+ String name = (String) it.next();
+ if ((((require & BladeBeanMap.REQUIRE_GETTER) != 0) && !getters.containsKey(name)) ||
+ (((require & BladeBeanMap.REQUIRE_SETTER) != 0) && !setters.containsKey(name))) {
+ it.remove();
+ getters.remove(name);
+ setters.remove(name);
+ }
+ }
+ }
+ generateGet(type, getters);
+ generatePut(type, setters);
+
+ String[] allNames = getNames(allProps);
+ generateKeySet(allNames);
+ generateGetPropertyType(allProps, allNames);
+ end_class();
+ }
+
+ private Map<String, PropertyDescriptor> makePropertyMap(PropertyDescriptor[] props) {
+ Map<String, PropertyDescriptor> names = new HashMap<>(16);
+ for (PropertyDescriptor prop : props) {
+ String propName = prop.getName();
+ // 杩囨护 getClass锛孲pring 鐨勫伐鍏风被浼氭嬁鍒拌鏂规硶
+ if (!"class".equals(propName)) {
+ names.put(propName, prop);
+ }
+ }
+ return names;
+ }
+
+ private String[] getNames(Map<String, PropertyDescriptor> propertyMap) {
+ return propertyMap.keySet().toArray(new String[0]);
+ }
+
+ private void generateConstructor() {
+ CodeEmitter e = begin_method(Constants.ACC_PUBLIC, CSTRUCT_OBJECT, null);
+ e.load_this();
+ e.load_arg(0);
+ e.super_invoke_constructor(CSTRUCT_OBJECT);
+ e.return_value();
+ e.end_method();
+ }
+
+ private void generateGet(Class type, final Map<String, PropertyDescriptor> getters) {
+ final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, BEAN_MAP_GET, null);
+ e.load_arg(0);
+ e.checkcast(Type.getType(type));
+ e.load_arg(1);
+ e.checkcast(Constants.TYPE_STRING);
+ EmitUtils.string_switch(e, getNames(getters), Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() {
+ @Override
+ public void processCase(Object key, Label end) {
+ PropertyDescriptor pd = getters.get(key);
+ MethodInfo method = ReflectUtils.getMethodInfo(pd.getReadMethod());
+ e.invoke(method);
+ e.box(method.getSignature().getReturnType());
+ e.return_value();
+ }
+
+ @Override
+ public void processDefault() {
+ e.aconst_null();
+ e.return_value();
+ }
+ });
+ e.end_method();
+ }
+
+ private void generatePut(Class type, final Map<String, PropertyDescriptor> setters) {
+ final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, BEAN_MAP_PUT, null);
+ e.load_arg(0);
+ e.checkcast(Type.getType(type));
+ e.load_arg(1);
+ e.checkcast(Constants.TYPE_STRING);
+ EmitUtils.string_switch(e, getNames(setters), Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() {
+ @Override
+ public void processCase(Object key, Label end) {
+ PropertyDescriptor pd = setters.get(key);
+ if (pd.getReadMethod() == null) {
+ e.aconst_null();
+ } else {
+ MethodInfo read = ReflectUtils.getMethodInfo(pd.getReadMethod());
+ e.dup();
+ e.invoke(read);
+ e.box(read.getSignature().getReturnType());
+ }
+ // move old value behind bean
+ e.swap();
+ // new value
+ e.load_arg(2);
+ MethodInfo write = ReflectUtils.getMethodInfo(pd.getWriteMethod());
+ e.unbox(write.getSignature().getArgumentTypes()[0]);
+ e.invoke(write);
+ e.return_value();
+ }
+
+ @Override
+ public void processDefault() {
+ // fall-through
+ }
+ });
+ e.aconst_null();
+ e.return_value();
+ e.end_method();
+ }
+
+ private void generateKeySet(String[] allNames) {
+ // static initializer
+ declare_field(Constants.ACC_STATIC | Constants.ACC_PRIVATE, "keys", FIXED_KEY_SET, null);
+
+ CodeEmitter e = begin_static();
+ e.new_instance(FIXED_KEY_SET);
+ e.dup();
+ EmitUtils.push_array(e, allNames);
+ e.invoke_constructor(FIXED_KEY_SET, CSTRUCT_STRING_ARRAY);
+ e.putfield("keys");
+ e.return_value();
+ e.end_method();
+
+ // keySet
+ e = begin_method(Constants.ACC_PUBLIC, KEY_SET, null);
+ e.load_this();
+ e.getfield("keys");
+ e.return_value();
+ e.end_method();
+ }
+
+ private void generateGetPropertyType(final Map allProps, String[] allNames) {
+ final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, GET_PROPERTY_TYPE, null);
+ e.load_arg(0);
+ EmitUtils.string_switch(e, allNames, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() {
+ @Override
+ public void processCase(Object key, Label end) {
+ PropertyDescriptor pd = (PropertyDescriptor) allProps.get(key);
+ EmitUtils.load_class(e, Type.getType(pd.getPropertyType()));
+ e.return_value();
+ }
+
+ @Override
+ public void processDefault() {
+ e.aconst_null();
+ e.return_value();
+ }
+ });
+ e.end_method();
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanMapKey.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanMapKey.java
new file mode 100644
index 0000000..6ca0fb9
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/BladeBeanMapKey.java
@@ -0,0 +1,16 @@
+package org.springblade.core.tool.beans;
+
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+
+/**
+ * bean map key锛屾彁楂樻�ц兘
+ *
+ * @author L.cm
+ */
+@EqualsAndHashCode
+@AllArgsConstructor
+public class BladeBeanMapKey {
+ private final Class type;
+ private final int require;
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/CopyProperty.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/CopyProperty.java
new file mode 100644
index 0000000..714ee1b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/beans/CopyProperty.java
@@ -0,0 +1,26 @@
+package org.springblade.core.tool.beans;
+
+import java.lang.annotation.*;
+
+/**
+ * copy 瀛楁 閰嶇疆
+ *
+ * @author L.cm
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface CopyProperty {
+
+ /**
+ * 灞炴�у悕锛岀敤浜庢寚瀹氬埆鍚嶏紝榛樿浣跨敤锛歠ield name
+ * @return 灞炴�у悕
+ */
+ String value() default "";
+
+ /**
+ * 蹇界暐锛氶粯璁や负 false
+ * @return 鏄惁蹇界暐
+ */
+ boolean ignore() default false;
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/config/BladeConverterConfiguration.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/config/BladeConverterConfiguration.java
new file mode 100644
index 0000000..4bd88d5
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/config/BladeConverterConfiguration.java
@@ -0,0 +1,23 @@
+package org.springblade.core.tool.config;
+
+import org.springblade.core.tool.convert.EnumToStringConverter;
+import org.springblade.core.tool.convert.StringToEnumConverter;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.format.FormatterRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * blade enum 銆�-銆� String 杞崲閰嶇疆
+ *
+ * @author L.cm
+ */
+@AutoConfiguration
+public class BladeConverterConfiguration implements WebMvcConfigurer {
+
+ @Override
+ public void addFormatters(FormatterRegistry registry) {
+ registry.addConverter(new EnumToStringConverter());
+ registry.addConverter(new StringToEnumConverter());
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/config/JacksonConfiguration.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/config/JacksonConfiguration.java
new file mode 100644
index 0000000..8f47d7d
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/config/JacksonConfiguration.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.config;
+
+import com.fasterxml.jackson.core.json.JsonReadFeature;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import org.springblade.core.tool.jackson.BladeJacksonProperties;
+import org.springblade.core.tool.jackson.BladeJavaTimeModule;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
+
+import java.text.SimpleDateFormat;
+import java.time.ZoneId;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Jackson閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@AutoConfiguration(before = JacksonAutoConfiguration.class)
+@ConditionalOnClass(ObjectMapper.class)
+@EnableConfigurationProperties(BladeJacksonProperties.class)
+public class JacksonConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
+ builder.simpleDateFormat(DateUtil.PATTERN_DATETIME);
+ //鍒涘缓ObjectMapper
+ ObjectMapper objectMapper = builder.createXmlMapper(false).build();
+ //璁剧疆鍦扮偣涓轰腑鍥�
+ objectMapper.setLocale(Locale.CHINA);
+ //鍘绘帀榛樿鐨勬椂闂存埑鏍煎紡
+ objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ //璁剧疆涓轰腑鍥戒笂娴锋椂鍖�
+ objectMapper.setTimeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));
+ //搴忓垪鍖栨椂锛屾棩鏈熺殑缁熶竴鏍煎紡
+ objectMapper.setDateFormat(new SimpleDateFormat(DateUtil.PATTERN_DATETIME, Locale.CHINA));
+ //搴忓垪鍖栧鐞�
+ objectMapper.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true);
+ objectMapper.configure(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER.mappedFeature(), true);
+ //澶辫触澶勭悊
+ objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ //鍗曞紩鍙峰鐞�
+ objectMapper.configure(JsonReadFeature.ALLOW_SINGLE_QUOTES.mappedFeature(), true);
+ //鍙嶅簭鍒楀寲鏃讹紝灞炴�т笉瀛樺湪鐨勫吋瀹瑰鐞�
+ objectMapper.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+ //鏃ユ湡鏍煎紡鍖�
+ objectMapper.configure(MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS, false);
+ objectMapper.registerModule(BladeJavaTimeModule.INSTANCE);
+ objectMapper.configure(MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS, true);
+ objectMapper.findAndRegisterModules();
+ return objectMapper;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/config/MessageConfiguration.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/config/MessageConfiguration.java
new file mode 100644
index 0000000..099210d
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/config/MessageConfiguration.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.config;
+
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.AllArgsConstructor;
+import org.springblade.core.tool.jackson.BladeJacksonProperties;
+import org.springblade.core.tool.jackson.MappingApiJackson2HttpMessageConverter;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.format.FormatterRegistry;
+import org.springframework.format.datetime.DateFormatter;
+import org.springframework.http.converter.*;
+import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+/**
+ * 娑堟伅閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@AutoConfiguration
+@AllArgsConstructor
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class MessageConfiguration implements WebMvcConfigurer {
+
+ private final ObjectMapper objectMapper;
+ private final BladeJacksonProperties properties;
+
+ /**
+ * 浣跨敤 JACKSON 浣滀负JSON MessageConverter
+ * 娑堟伅杞崲锛屽唴缃柇鐐圭画浼狅紝涓嬭浇鍜屽瓧绗︿覆
+ */
+ @Override
+ public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
+ converters.removeIf(x -> x instanceof StringHttpMessageConverter || x instanceof AbstractJackson2HttpMessageConverter);
+ converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
+ converters.add(new ByteArrayHttpMessageConverter());
+ converters.add(new ResourceHttpMessageConverter());
+ converters.add(new ResourceRegionHttpMessageConverter());
+ converters.add(new MappingApiJackson2HttpMessageConverter(objectMapper, properties));
+ }
+
+ /**
+ * 鏃ユ湡鏍煎紡鍖�
+ */
+ @Override
+ public void addFormatters(FormatterRegistry registry) {
+ registry.addFormatter(new DateFormatter(DateUtil.PATTERN_DATE));
+ registry.addFormatter(new DateFormatter(DateUtil.PATTERN_DATETIME));
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/config/ToolConfiguration.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/config/ToolConfiguration.java
new file mode 100644
index 0000000..f829b82
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/config/ToolConfiguration.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.config;
+
+
+import org.springblade.core.tool.support.BinderSupplier;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+
+import java.util.function.Supplier;
+
+/**
+ * 宸ュ叿閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@AutoConfiguration
+public class ToolConfiguration {
+
+ /**
+ * Spring涓婁笅鏂囩紦瀛�
+ */
+ @Bean
+ public SpringUtil springUtil() {
+ return new SpringUtil();
+ }
+
+ /**
+ * Binder鏀寔绫�
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ public Supplier<Object> binderSupplier() {
+ return new BinderSupplier();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/constant/BladeConstant.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/constant/BladeConstant.java
new file mode 100644
index 0000000..37add9d
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/constant/BladeConstant.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.constant;
+
+/**
+ * 绯荤粺甯搁噺
+ *
+ * @author Chill
+ */
+public interface BladeConstant {
+
+ /**
+ * 缂栫爜
+ */
+ String UTF_8 = "UTF-8";
+
+ /**
+ * contentType
+ */
+ String CONTENT_TYPE_NAME = "Content-type";
+
+ /**
+ * JSON 璧勬簮
+ */
+ String CONTENT_TYPE = "application/json;charset=utf-8";
+
+ /**
+ * 涓婁笅鏂囬敭鍊�
+ */
+ String CONTEXT_KEY = "bladeContext";
+
+ /**
+ * mdc request id key
+ */
+ String MDC_REQUEST_ID_KEY = "requestId";
+
+ /**
+ * mdc account id key
+ */
+ String MDC_ACCOUNT_ID_KEY = "accountId";
+
+ /**
+ * mdc tenant id key
+ */
+ String MDC_TENANT_ID_KEY = "tenantId";
+
+ /**
+ * 瑙掕壊鍓嶇紑
+ */
+ String SECURITY_ROLE_PREFIX = "ROLE_";
+
+ /**
+ * 涓婚敭瀛楁鍚�
+ */
+ String DB_PRIMARY_KEY = "id";
+
+ /**
+ * 涓婚敭瀛楁get鏂规硶
+ */
+ String DB_PRIMARY_KEY_METHOD = "getId";
+
+ /**
+ * 绉熸埛瀛楁鍚�
+ */
+ String DB_TENANT_KEY = "tenantId";
+
+ /**
+ * 绉熸埛瀛楁get鏂规硶
+ */
+ String DB_TENANT_KEY_GET_METHOD = "getTenantId";
+
+ /**
+ * 绉熸埛瀛楁set鏂规硶
+ */
+ String DB_TENANT_KEY_SET_METHOD = "setTenantId";
+
+ /**
+ * 涓氬姟鐘舵�乕1:姝e父]
+ */
+ int DB_STATUS_NORMAL = 1;
+
+
+ /**
+ * 鍒犻櫎鐘舵�乕0:姝e父,1:鍒犻櫎]
+ */
+ int DB_NOT_DELETED = 0;
+ int DB_IS_DELETED = 1;
+
+ /**
+ * 鐢ㄦ埛閿佸畾鐘舵��
+ */
+ int DB_ADMIN_NON_LOCKED = 0;
+ int DB_ADMIN_LOCKED = 1;
+
+ /**
+ * 椤剁骇鐖惰妭鐐筰d
+ */
+ Long TOP_PARENT_ID = 0L;
+
+ /**
+ * 椤剁骇鐖惰妭鐐瑰悕绉�
+ */
+ String TOP_PARENT_NAME = "椤剁骇";
+
+ /**
+ * 绠$悊鍛樺搴旂殑绉熸埛ID
+ */
+ String ADMIN_TENANT_ID = "000000";
+
+ /**
+ * 鏃ュ織榛樿鐘舵��
+ */
+ String LOG_NORMAL_TYPE = "1";
+
+ /**
+ * 榛樿涓虹┖娑堟伅
+ */
+ String DEFAULT_NULL_MESSAGE = "鏆傛棤鎵胯浇鏁版嵁";
+ /**
+ * 榛樿鎴愬姛娑堟伅
+ */
+ String DEFAULT_SUCCESS_MESSAGE = "鎿嶄綔鎴愬姛";
+ /**
+ * 榛樿澶辫触娑堟伅
+ */
+ String DEFAULT_FAILURE_MESSAGE = "鎿嶄綔澶辫触";
+ /**
+ * 榛樿鏈巿鏉冩秷鎭�
+ */
+ String DEFAULT_UNAUTHORIZED_MESSAGE = "绛惧悕璁よ瘉澶辫触";
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/constant/RoleConstant.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/constant/RoleConstant.java
new file mode 100644
index 0000000..d7165d3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/constant/RoleConstant.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.constant;
+
+/**
+ * 绯荤粺榛樿瑙掕壊
+ *
+ * @author Chill
+ */
+public class RoleConstant {
+
+ public static final String ADMINISTRATOR = "administrator";
+
+ public static final String HAS_ROLE_ADMINISTRATOR = "hasRole('" + ADMINISTRATOR + "')";
+
+ public static final String ADMIN = "admin";
+
+ public static final String HAS_ROLE_ADMIN = "hasAnyRole('" + ADMINISTRATOR + "', '" + ADMIN + "')";
+
+ public static final String USER = "user";
+
+ public static final String HAS_ROLE_USER = "hasRole('" + USER + "')";
+
+ public static final String TEST = "test";
+
+ public static final String HAS_ROLE_TEST = "hasRole('" + TEST + "')";
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/convert/BladeConversionService.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/convert/BladeConversionService.java
new file mode 100644
index 0000000..f946200
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/convert/BladeConversionService.java
@@ -0,0 +1,50 @@
+package org.springblade.core.tool.convert;
+
+import org.springframework.boot.convert.ApplicationConversionService;
+import org.springframework.core.convert.support.GenericConversionService;
+import org.springframework.lang.Nullable;
+import org.springframework.util.StringValueResolver;
+
+/**
+ * 绫诲瀷 杞崲 鏈嶅姟锛屾坊鍔犱簡 IEnum 杞崲
+ *
+ * @author L.cm
+ */
+public class BladeConversionService extends ApplicationConversionService {
+ @Nullable
+ private static volatile BladeConversionService SHARED_INSTANCE;
+
+ public BladeConversionService() {
+ this(null);
+ }
+
+ public BladeConversionService(@Nullable StringValueResolver embeddedValueResolver) {
+ super(embeddedValueResolver);
+ super.addConverter(new EnumToStringConverter());
+ super.addConverter(new StringToEnumConverter());
+ }
+
+ /**
+ * Return a shared default application {@code ConversionService} instance, lazily
+ * building it once needed.
+ * <p>
+ * Note: This method actually returns an {@link BladeConversionService}
+ * instance. However, the {@code ConversionService} signature has been preserved for
+ * binary compatibility.
+ * @return the shared {@code BladeConversionService} instance (never{@code null})
+ */
+ public static GenericConversionService getInstance() {
+ BladeConversionService sharedInstance = BladeConversionService.SHARED_INSTANCE;
+ if (sharedInstance == null) {
+ synchronized (BladeConversionService.class) {
+ sharedInstance = BladeConversionService.SHARED_INSTANCE;
+ if (sharedInstance == null) {
+ sharedInstance = new BladeConversionService();
+ BladeConversionService.SHARED_INSTANCE = sharedInstance;
+ }
+ }
+ }
+ return sharedInstance;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/convert/BladeConverter.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/convert/BladeConverter.java
new file mode 100644
index 0000000..08ba13f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/convert/BladeConverter.java
@@ -0,0 +1,77 @@
+package org.springblade.core.tool.convert;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.tool.function.CheckedFunction;
+import org.springblade.core.tool.utils.ClassUtil;
+import org.springblade.core.tool.utils.ConvertUtil;
+import org.springblade.core.tool.utils.ReflectUtil;
+import org.springblade.core.tool.utils.Unchecked;
+import org.springframework.cglib.core.Converter;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.lang.Nullable;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * 缁勫悎 spring cglib Converter 鍜� spring ConversionService
+ *
+ * @author L.cm
+ */
+@Slf4j
+@AllArgsConstructor
+public class BladeConverter implements Converter {
+ private static final ConcurrentMap<String, TypeDescriptor> TYPE_CACHE = new ConcurrentHashMap<>();
+ private final Class<?> sourceClazz;
+ private final Class<?> targetClazz;
+
+ /**
+ * cglib convert
+ *
+ * @param value 婧愬璞″睘鎬�
+ * @param target 鐩爣瀵硅薄灞炴�х被
+ * @param fieldName 鐩爣鐨刦ield鍚嶏紝鍘熶负 set 鏂规硶鍚嶏紝BladeBeanCopier 閲屽仛浜嗘洿鏀�
+ * @return {Object}
+ */
+ @Override
+ @Nullable
+ public Object convert(Object value, Class target, final Object fieldName) {
+ if (value == null) {
+ return null;
+ }
+ // 绫诲瀷涓�鏍凤紝涓嶉渶瑕佽浆鎹�
+ if (ClassUtil.isAssignableValue(target, value)) {
+ return value;
+ }
+ try {
+ TypeDescriptor targetDescriptor = BladeConverter.getTypeDescriptor(targetClazz, (String) fieldName);
+ // 1. 鍒ゆ柇 sourceClazz 涓� Map
+ if (Map.class.isAssignableFrom(sourceClazz)) {
+ return ConvertUtil.convert(value, targetDescriptor);
+ } else {
+ TypeDescriptor sourceDescriptor = BladeConverter.getTypeDescriptor(sourceClazz, (String) fieldName);
+ return ConvertUtil.convert(value, sourceDescriptor, targetDescriptor);
+ }
+ } catch (Throwable e) {
+ log.warn("BladeConverter error", e);
+ return null;
+ }
+ }
+
+ private static TypeDescriptor getTypeDescriptor(final Class<?> clazz, final String fieldName) {
+ String srcCacheKey = clazz.getName() + fieldName;
+ // 蹇界暐鎶涘嚭寮傚父鐨勫嚱鏁帮紝瀹氫箟瀹屾暣娉涘瀷锛岄伩鍏嶇紪璇戦棶棰�
+ CheckedFunction<String, TypeDescriptor> uncheckedFunction = (key) -> {
+ // 杩欓噷 property 鐞嗚涓婁笉浼氫负 null
+ Field field = ReflectUtil.getField(clazz, fieldName);
+ if (field == null) {
+ throw new NoSuchFieldException(fieldName);
+ }
+ return new TypeDescriptor(field);
+ };
+ return TYPE_CACHE.computeIfAbsent(srcCacheKey, Unchecked.function(uncheckedFunction));
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/convert/EnumToStringConverter.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/convert/EnumToStringConverter.java
new file mode 100644
index 0000000..4e7aac7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/convert/EnumToStringConverter.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.convert;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.tool.utils.ConvertUtil;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+import org.springframework.lang.Nullable;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * 鎺ユ敹鍙傛暟 鍚� jackson Enum -銆� String 杞崲
+ *
+ * @author L.cm
+ */
+@Slf4j
+public class EnumToStringConverter implements ConditionalGenericConverter {
+ /**
+ * 缂撳瓨 Enum 绫讳俊鎭紝鎻愪緵鎬ц兘
+ */
+ private static final ConcurrentMap<Class<?>, AccessibleObject> ENUM_CACHE_MAP = new ConcurrentHashMap<>(8);
+
+ @Nullable
+ private static AccessibleObject getAnnotation(Class<?> clazz) {
+ Set<AccessibleObject> accessibleObjects = new HashSet<>();
+ // JsonValue METHOD, FIELD
+ Field[] fields = clazz.getDeclaredFields();
+ Collections.addAll(accessibleObjects, fields);
+ // methods
+ Method[] methods = clazz.getDeclaredMethods();
+ Collections.addAll(accessibleObjects, methods);
+ for (AccessibleObject accessibleObject : accessibleObjects) {
+ // 澶嶇敤 jackson 鐨� JsonValue 娉ㄨВ
+ JsonValue jsonValue = accessibleObject.getAnnotation(JsonValue.class);
+ if (jsonValue != null && jsonValue.value()) {
+ accessibleObject.setAccessible(true);
+ return accessibleObject;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return true;
+ }
+
+ @Override
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ Set<ConvertiblePair> pairSet = new HashSet<>(3);
+ pairSet.add(new ConvertiblePair(Enum.class, String.class));
+ pairSet.add(new ConvertiblePair(Enum.class, Integer.class));
+ pairSet.add(new ConvertiblePair(Enum.class, Long.class));
+ return Collections.unmodifiableSet(pairSet);
+ }
+
+ @Override
+ public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source == null) {
+ return null;
+ }
+ Class<?> sourceClazz = sourceType.getType();
+ AccessibleObject accessibleObject = ENUM_CACHE_MAP.computeIfAbsent(sourceClazz, EnumToStringConverter::getAnnotation);
+ Class<?> targetClazz = targetType.getType();
+ // 濡傛灉涓簄ull锛岃蛋榛樿鐨勮浆鎹�
+ if (accessibleObject == null) {
+ if (String.class == targetClazz) {
+ return ((Enum) source).name();
+ }
+ int ordinal = ((Enum) source).ordinal();
+ return ConvertUtil.convert(ordinal, targetClazz);
+ }
+ try {
+ return EnumToStringConverter.invoke(sourceClazz, accessibleObject, source, targetClazz);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ return null;
+ }
+
+ @Nullable
+ private static Object invoke(Class<?> clazz, AccessibleObject accessibleObject, Object source, Class<?> targetClazz)
+ throws IllegalAccessException, InvocationTargetException {
+ Object value = null;
+ if (accessibleObject instanceof Field) {
+ Field field = (Field) accessibleObject;
+ value = field.get(source);
+ } else if (accessibleObject instanceof Method) {
+ Method method = (Method) accessibleObject;
+ Class<?> paramType = method.getParameterTypes()[0];
+ // 绫诲瀷杞崲
+ Object object = ConvertUtil.convert(source, paramType);
+ value = method.invoke(clazz, object);
+ }
+ if (value == null) {
+ return null;
+ }
+ return ConvertUtil.convert(value, targetClazz);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/convert/StringToEnumConverter.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/convert/StringToEnumConverter.java
new file mode 100644
index 0000000..4f94359
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/convert/StringToEnumConverter.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.convert;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.tool.utils.ConvertUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+import org.springframework.lang.Nullable;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * 鎺ユ敹鍙傛暟 鍚� jackson String -銆� Enum 杞崲
+ *
+ * @author L.cm
+ */
+@Slf4j
+public class StringToEnumConverter implements ConditionalGenericConverter {
+ /**
+ * 缂撳瓨 Enum 绫讳俊鎭紝鎻愪緵鎬ц兘
+ */
+ private static final ConcurrentMap<Class<?>, AccessibleObject> ENUM_CACHE_MAP = new ConcurrentHashMap<>(8);
+
+ @Nullable
+ private static AccessibleObject getAnnotation(Class<?> clazz) {
+ Set<AccessibleObject> accessibleObjects = new HashSet<>();
+ // JsonCreator METHOD, CONSTRUCTOR
+ Constructor<?>[] constructors = clazz.getConstructors();
+ Collections.addAll(accessibleObjects, constructors);
+ // methods
+ Method[] methods = clazz.getDeclaredMethods();
+ Collections.addAll(accessibleObjects, methods);
+ for (AccessibleObject accessibleObject : accessibleObjects) {
+ // 澶嶇敤 jackson 鐨� JsonCreator娉ㄨВ
+ JsonCreator jsonCreator = accessibleObject.getAnnotation(JsonCreator.class);
+ if (jsonCreator != null && JsonCreator.Mode.DISABLED != jsonCreator.mode()) {
+ accessibleObject.setAccessible(true);
+ return accessibleObject;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return true;
+ }
+
+ @Override
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(new ConvertiblePair(String.class, Enum.class));
+ }
+
+ @Nullable
+ @Override
+ public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (StringUtil.isBlank((String) source)) {
+ return null;
+ }
+ Class<?> clazz = targetType.getType();
+ AccessibleObject accessibleObject = ENUM_CACHE_MAP.computeIfAbsent(clazz, StringToEnumConverter::getAnnotation);
+ String value = ((String) source).trim();
+ // 濡傛灉涓簄ull锛岃蛋榛樿鐨勮浆鎹�
+ if (accessibleObject == null) {
+ return valueOf(clazz, value);
+ }
+ try {
+ return StringToEnumConverter.invoke(clazz, accessibleObject, value);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T extends Enum<T>> T valueOf(Class<?> clazz, String value){
+ return Enum.valueOf((Class<T>) clazz, value);
+ }
+
+ @Nullable
+ private static Object invoke(Class<?> clazz, AccessibleObject accessibleObject, String value)
+ throws IllegalAccessException, InvocationTargetException, InstantiationException {
+ if (accessibleObject instanceof Constructor) {
+ Constructor constructor = (Constructor) accessibleObject;
+ Class<?> paramType = constructor.getParameterTypes()[0];
+ // 绫诲瀷杞崲
+ Object object = ConvertUtil.convert(value, paramType);
+ return constructor.newInstance(object);
+ }
+ if (accessibleObject instanceof Method) {
+ Method method = (Method) accessibleObject;
+ Class<?> paramType = method.getParameterTypes()[0];
+ // 绫诲瀷杞崲
+ Object object = ConvertUtil.convert(value, paramType);
+ return method.invoke(clazz, object);
+ }
+ return null;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedCallable.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedCallable.java
new file mode 100644
index 0000000..c78e220
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedCallable.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.function;
+
+import org.springframework.lang.Nullable;
+
+/**
+ * 鍙楁鐨� Callable
+ *
+ * @author L.cm
+ */
+@FunctionalInterface
+public interface CheckedCallable<T> {
+
+ /**
+ * Run this callable.
+ *
+ * @return result
+ * @throws Throwable CheckedException
+ */
+ @Nullable
+ T call() throws Throwable;
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedComparator.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedComparator.java
new file mode 100644
index 0000000..fe978ef
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedComparator.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.function;
+
+/**
+ * 鍙楁鐨� Comparator
+ *
+ * @author L.cm
+ */
+@FunctionalInterface
+public interface CheckedComparator<T> {
+
+ /**
+ * Compares its two arguments for order.
+ *
+ * @param o1 o1
+ * @param o2 o2
+ * @return int
+ * @throws Throwable CheckedException
+ */
+ int compare(T o1, T o2) throws Throwable;
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedConsumer.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedConsumer.java
new file mode 100644
index 0000000..02cca4e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedConsumer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.function;
+
+import org.springframework.lang.Nullable;
+
+/**
+ * 鍙楁鐨� Consumer
+ *
+ * @author L.cm
+ */
+@FunctionalInterface
+public interface CheckedConsumer<T> {
+
+ /**
+ * Run the Consumer
+ *
+ * @param t T
+ * @throws Throwable UncheckedException
+ */
+ @Nullable
+ void accept(@Nullable T t) throws Throwable;
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedFunction.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedFunction.java
new file mode 100644
index 0000000..cca8163
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedFunction.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.function;
+
+import org.springframework.lang.Nullable;
+
+/**
+ * 鍙楁鐨� function
+ *
+ * @author L.cm
+ */
+@FunctionalInterface
+public interface CheckedFunction<T, R> {
+
+ /**
+ * Run the Function
+ *
+ * @param t T
+ * @return R R
+ * @throws Throwable CheckedException
+ */
+ @Nullable
+ R apply(@Nullable T t) throws Throwable;
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedRunnable.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedRunnable.java
new file mode 100644
index 0000000..751a4bf
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedRunnable.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.function;
+
+/**
+ * 鍙楁鐨� runnable
+ *
+ * @author L.cm
+ */
+@FunctionalInterface
+public interface CheckedRunnable {
+
+ /**
+ * Run this runnable.
+ *
+ * @throws Throwable CheckedException
+ */
+ void run() throws Throwable;
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedSupplier.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedSupplier.java
new file mode 100644
index 0000000..cb554bf
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/function/CheckedSupplier.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.function;
+
+import org.springframework.lang.Nullable;
+
+/**
+ * 鍙楁鐨� Supplier
+ *
+ * @author L.cm
+ */
+@FunctionalInterface
+public interface CheckedSupplier<T> {
+
+ /**
+ * Run the Supplier
+ *
+ * @return T
+ * @throws Throwable CheckedException
+ */
+ @Nullable
+ T get() throws Throwable;
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/AbstractReadWriteJackson2HttpMessageConverter.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/AbstractReadWriteJackson2HttpMessageConverter.java
new file mode 100644
index 0000000..553edcd
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/AbstractReadWriteJackson2HttpMessageConverter.java
@@ -0,0 +1,127 @@
+package org.springblade.core.tool.jackson;
+
+import com.fasterxml.jackson.core.JsonEncoding;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.PrettyPrinter;
+import com.fasterxml.jackson.core.util.DefaultIndenter;
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
+import com.fasterxml.jackson.databind.ser.FilterProvider;
+import org.springblade.core.tool.utils.Charsets;
+import org.springframework.http.HttpOutputMessage;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConversionException;
+import org.springframework.http.converter.HttpMessageNotWritableException;
+import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
+import org.springframework.http.converter.json.MappingJacksonValue;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+import org.springframework.util.TypeUtils;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * 鍒嗚鍐欑殑 json 娑堟伅 澶勭悊鍣�
+ *
+ * @author L.cm
+ */
+public abstract class AbstractReadWriteJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
+ private static final java.nio.charset.Charset DEFAULT_CHARSET = Charsets.UTF_8;
+
+ private final ObjectMapper writeObjectMapper;
+ @Nullable
+ private PrettyPrinter ssePrettyPrinter;
+
+ public AbstractReadWriteJackson2HttpMessageConverter(ObjectMapper readObjectMapper, ObjectMapper writeObjectMapper) {
+ super(readObjectMapper);
+ this.writeObjectMapper = writeObjectMapper;
+ initSsePrettyPrinter();
+ }
+
+ public AbstractReadWriteJackson2HttpMessageConverter(ObjectMapper readObjectMapper, ObjectMapper writeObjectMapper, MediaType supportedMediaType) {
+ this(readObjectMapper, writeObjectMapper);
+ setSupportedMediaTypes(Collections.singletonList(supportedMediaType));
+ initSsePrettyPrinter();
+ }
+
+ public AbstractReadWriteJackson2HttpMessageConverter(ObjectMapper readObjectMapper, ObjectMapper writeObjectMapper, List<MediaType> supportedMediaTypes) {
+ this(readObjectMapper, writeObjectMapper);
+ setSupportedMediaTypes(supportedMediaTypes);
+ }
+
+ private void initSsePrettyPrinter() {
+ setDefaultCharset(DEFAULT_CHARSET);
+ DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
+ prettyPrinter.indentObjectsWith(new DefaultIndenter(" ", "\ndata:"));
+ this.ssePrettyPrinter = prettyPrinter;
+ }
+
+ @Override
+ public boolean canWrite(@NonNull Class<?> clazz, @Nullable MediaType mediaType) {
+ if (!canWrite(mediaType)) {
+ return false;
+ }
+ AtomicReference<Throwable> causeRef = new AtomicReference<>();
+ if (this.defaultObjectMapper.canSerialize(clazz, causeRef)) {
+ return true;
+ }
+ logWarningIfNecessary(clazz, causeRef.get());
+ return false;
+ }
+
+ @Override
+ protected void writeInternal(@NonNull Object object, @Nullable Type type, HttpOutputMessage outputMessage)
+ throws IOException, HttpMessageNotWritableException {
+
+ MediaType contentType = outputMessage.getHeaders().getContentType();
+ JsonEncoding encoding = getJsonEncoding(contentType);
+ JsonGenerator generator = this.writeObjectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
+ try {
+ writePrefix(generator, object);
+
+ Object value = object;
+ Class<?> serializationView = null;
+ FilterProvider filters = null;
+ JavaType javaType = null;
+
+ if (object instanceof MappingJacksonValue) {
+ MappingJacksonValue container = (MappingJacksonValue) object;
+ value = container.getValue();
+ serializationView = container.getSerializationView();
+ filters = container.getFilters();
+ }
+ if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
+ javaType = getJavaType(type, null);
+ }
+
+ ObjectWriter objectWriter = (serializationView != null ?
+ this.writeObjectMapper.writerWithView(serializationView) : this.writeObjectMapper.writer());
+ if (filters != null) {
+ objectWriter = objectWriter.with(filters);
+ }
+ if (javaType != null && javaType.isContainerType()) {
+ objectWriter = objectWriter.forType(javaType);
+ }
+ SerializationConfig config = objectWriter.getConfig();
+ if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&
+ config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
+ objectWriter = objectWriter.with(this.ssePrettyPrinter);
+ }
+ objectWriter.writeValue(generator, value);
+
+ writeSuffix(generator, object);
+ generator.flush();
+ } catch (InvalidDefinitionException ex) {
+ throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
+ } catch (JsonProcessingException ex) {
+ throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BigNumberSerializer.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BigNumberSerializer.java
new file mode 100644
index 0000000..c573c21
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BigNumberSerializer.java
@@ -0,0 +1,44 @@
+package org.springblade.core.tool.jackson;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
+import com.fasterxml.jackson.databind.ser.std.NumberSerializer;
+
+import java.io.IOException;
+
+/**
+ * 澶ф暟鍊煎簭鍒楀寲锛岄伩鍏嶈秴杩噅s鐨勭簿搴︼紝閫犳垚绮惧害涓㈠け
+ *
+ * @author L.cm
+ */
+@JacksonStdImpl
+public class BigNumberSerializer extends NumberSerializer {
+
+ /**
+ * js 鏈�澶у�间负 Math.pow(2, 53)锛屽崄杩涘埗涓猴細9007199254740992
+ */
+ private static final long JS_NUM_MAX = 0x20000000000000L;
+ /**
+ * js 鏈�灏忓�间负 -Math.pow(2, 53)锛屽崄杩涘埗涓猴細-9007199254740992
+ */
+ private static final long JS_NUM_MIN = -0x20000000000000L;
+ /**
+ * Static instance that is only to be used for {@link java.lang.Number}.
+ */
+ public final static BigNumberSerializer instance = new BigNumberSerializer(Number.class);
+
+ public BigNumberSerializer(Class<? extends Number> rawType) {
+ super(rawType);
+ }
+
+ @Override
+ public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+ long longValue = value.longValue();
+ if (longValue < JS_NUM_MIN || longValue > JS_NUM_MAX) {
+ gen.writeString(value.toString());
+ } else {
+ super.serialize(value, gen, provider);
+ }
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeBeanSerializerModifier.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeBeanSerializerModifier.java
new file mode 100644
index 0000000..cbc2f63
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeBeanSerializerModifier.java
@@ -0,0 +1,107 @@
+package org.springblade.core.tool.jackson;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
+import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+
+import java.io.IOException;
+import java.time.OffsetDateTime;
+import java.time.temporal.TemporalAccessor;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * jackson 榛樿鍊间负 null 鏃剁殑澶勭悊
+ * <p>
+ * 涓昏鏄负浜嗛伩鍏� app 绔嚭鐜皀ull瀵艰嚧闂��
+ * <p>
+ * 瑙勫垯锛�
+ * number -1
+ * string ""
+ * date ""
+ * boolean false
+ * array []
+ * Object {}
+ *
+ * @author L.cm
+ */
+public class BladeBeanSerializerModifier extends BeanSerializerModifier {
+ @Override
+ public List<BeanPropertyWriter> changeProperties(
+ SerializationConfig config, BeanDescription beanDesc,
+ List<BeanPropertyWriter> beanProperties) {
+ // 寰幆鎵�鏈夌殑beanPropertyWriter
+ beanProperties.forEach(writer -> {
+ // 濡傛灉宸茬粡鏈� null 搴忓垪鍖栧鐞嗗娉ㄨВ锛欯JsonSerialize(nullsUsing = xxx) 璺宠繃
+ if (writer.hasNullSerializer()) {
+ return;
+ }
+ JavaType type = writer.getType();
+ Class<?> clazz = type.getRawClass();
+ if (type.isTypeOrSubTypeOf(Number.class)) {
+// writer.assignNullSerializer(NullJsonSerializers.NUMBER_JSON_SERIALIZER);
+ } else if (type.isTypeOrSubTypeOf(Boolean.class)) {
+ writer.assignNullSerializer(NullJsonSerializers.BOOLEAN_JSON_SERIALIZER);
+ } else if (type.isTypeOrSubTypeOf(Character.class)) {
+ writer.assignNullSerializer(NullJsonSerializers.STRING_JSON_SERIALIZER);
+ } else if (type.isTypeOrSubTypeOf(String.class)) {
+ writer.assignNullSerializer(NullJsonSerializers.STRING_JSON_SERIALIZER);
+ } else if (type.isArrayType() || clazz.isArray() || type.isTypeOrSubTypeOf(Collection.class)) {
+ writer.assignNullSerializer(NullJsonSerializers.ARRAY_JSON_SERIALIZER);
+ } else if (type.isTypeOrSubTypeOf(OffsetDateTime.class)) {
+ writer.assignNullSerializer(NullJsonSerializers.STRING_JSON_SERIALIZER);
+ } else if (type.isTypeOrSubTypeOf(Date.class) || type.isTypeOrSubTypeOf(TemporalAccessor.class)) {
+ writer.assignNullSerializer(NullJsonSerializers.STRING_JSON_SERIALIZER);
+ } else {
+ writer.assignNullSerializer(NullJsonSerializers.OBJECT_JSON_SERIALIZER);
+ }
+ });
+ return super.changeProperties(config, beanDesc, beanProperties);
+ }
+
+ public interface NullJsonSerializers {
+
+ JsonSerializer<Object> STRING_JSON_SERIALIZER = new JsonSerializer<Object>() {
+ @Override
+ public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ gen.writeString(StringPool.EMPTY);
+ }
+ };
+
+ JsonSerializer<Object> NUMBER_JSON_SERIALIZER = new JsonSerializer<Object>() {
+ @Override
+ public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ gen.writeNumber(StringUtil.INDEX_NOT_FOUND);
+ }
+ };
+
+ JsonSerializer<Object> BOOLEAN_JSON_SERIALIZER = new JsonSerializer<Object>() {
+ @Override
+ public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ gen.writeObject(Boolean.FALSE);
+ }
+ };
+
+ JsonSerializer<Object> ARRAY_JSON_SERIALIZER = new JsonSerializer<Object>() {
+ @Override
+ public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ gen.writeStartArray();
+ gen.writeEndArray();
+ }
+ };
+
+ JsonSerializer<Object> OBJECT_JSON_SERIALIZER = new JsonSerializer<Object>() {
+ @Override
+ public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ gen.writeStartObject();
+ gen.writeEndObject();
+ }
+ };
+
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeJacksonProperties.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeJacksonProperties.java
new file mode 100644
index 0000000..4c208a5
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeJacksonProperties.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.jackson;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * jackson 閰嶇疆
+ *
+ * @author L.cm
+ */
+@Getter
+@Setter
+@ConfigurationProperties("blade.jackson")
+public class BladeJacksonProperties {
+
+ /**
+ * null 杞负 绌猴紝瀛楃涓茶浆鎴�""锛屾暟缁勮浆涓篬]锛屽璞¤浆涓簕}锛屾暟瀛楄浆涓�-1
+ */
+ private Boolean nullToEmpty = Boolean.TRUE;
+ /**
+ * 鍝嶅簲鍒板墠绔紝澶ф暟鍊艰嚜鍔ㄥ啓鍑轰负 String锛岄伩鍏嶇簿搴︿涪澶�
+ */
+ private Boolean bigNumToString = Boolean.TRUE;
+ /**
+ * 鏀寔 MediaType text/plain锛岀敤浜庡拰 blade-api-crypto 涓�璧蜂娇鐢�
+ */
+ private Boolean supportTextPlain = Boolean.FALSE;
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeJavaTimeModule.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeJavaTimeModule.java
new file mode 100644
index 0000000..a6c0af3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeJavaTimeModule.java
@@ -0,0 +1,35 @@
+package org.springblade.core.tool.jackson;
+
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.datatype.jsr310.PackageVersion;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
+import org.springblade.core.tool.utils.DateTimeUtil;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+
+/**
+ * java 8 鏃堕棿榛樿搴忓垪鍖�
+ *
+ * @author L.cm
+ */
+public class BladeJavaTimeModule extends SimpleModule {
+ public static final BladeJavaTimeModule INSTANCE = new BladeJavaTimeModule();
+
+ public BladeJavaTimeModule() {
+ super(PackageVersion.VERSION);
+ this.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeUtil.DATETIME_FORMAT));
+ this.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeUtil.DATE_FORMAT));
+ this.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeUtil.TIME_FORMAT));
+ this.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeUtil.DATETIME_FORMAT));
+ this.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeUtil.DATE_FORMAT));
+ this.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeUtil.TIME_FORMAT));
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeNumberModule.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeNumberModule.java
new file mode 100644
index 0000000..ff89e5f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeNumberModule.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.jackson;
+
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+/**
+ * 澶ф暣鏁板簭鍒楀寲涓� String 瀛楃涓诧紝閬垮厤娴忚鍣ㄤ涪澶辩簿搴�
+ *
+ * <p>
+ * 鍓嶇寤鸿閲囩敤锛�
+ * bignumber 搴擄細 https://github.com/MikeMcl/bignumber.js
+ * decimal.js 搴擄細 https://github.com/MikeMcl/decimal.js
+ * </p>
+ *
+ * @author L.cm
+ */
+public class BladeNumberModule extends SimpleModule {
+ public static final BladeNumberModule INSTANCE = new BladeNumberModule();
+
+ public BladeNumberModule() {
+ super(BladeNumberModule.class.getName());
+ // Long 鍜� BigInteger 閲囩敤瀹氬埗鐨勯�昏緫搴忓垪鍖栵紝閬垮厤瓒呰繃js鐨勭簿搴�
+ this.addSerializer(Long.class, BigNumberSerializer.instance);
+ this.addSerializer(Long.TYPE, BigNumberSerializer.instance);
+ this.addSerializer(BigInteger.class, BigNumberSerializer.instance);
+ // BigDecimal 閲囩敤 toString 閬垮厤绮惧害涓㈠け锛屽墠绔噰鐢� decimal.js 鏉ヨ绠椼��
+ this.addSerializer(BigDecimal.class, ToStringSerializer.instance);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/JsonUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/JsonUtil.java
new file mode 100644
index 0000000..89a9e84
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/JsonUtil.java
@@ -0,0 +1,705 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.jackson;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.TreeNode;
+import com.fasterxml.jackson.core.json.JsonReadFeature;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.type.CollectionLikeType;
+import com.fasterxml.jackson.databind.type.MapType;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.tool.utils.*;
+import org.springframework.lang.Nullable;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.SimpleDateFormat;
+import java.time.ZoneId;
+import java.util.*;
+
+/**
+ * Jackson宸ュ叿绫�
+ *
+ * @author Chill
+ */
+@Slf4j
+public class JsonUtil {
+
+ /**
+ * 灏嗗璞″簭鍒楀寲鎴恓son瀛楃涓�
+ *
+ * @param value javaBean
+ * @return jsonString json瀛楃涓�
+ */
+ public static <T> String toJson(T value) {
+ try {
+ return getInstance().writeValueAsString(value);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * 灏嗗璞″簭鍒楀寲鎴� json byte 鏁扮粍
+ *
+ * @param object javaBean
+ * @return jsonString json瀛楃涓�
+ */
+ public static byte[] toJsonAsBytes(Object object) {
+ try {
+ return getInstance().writeValueAsBytes(object);
+ } catch (JsonProcessingException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 灏唈son鍙嶅簭鍒楀寲鎴愬璞�
+ *
+ * @param content content
+ * @param valueType class
+ * @param <T> T 娉涘瀷鏍囪
+ * @return Bean
+ */
+ public static <T> T parse(String content, Class<T> valueType) {
+ try {
+ return getInstance().readValue(content, valueType);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * 灏唈son鍙嶅簭鍒楀寲鎴愬璞�
+ *
+ * @param content content
+ * @param typeReference 娉涘瀷绫诲瀷
+ * @param <T> T 娉涘瀷鏍囪
+ * @return Bean
+ */
+ public static <T> T parse(String content, TypeReference<T> typeReference) {
+ try {
+ return getInstance().readValue(content, typeReference);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 灏唈son byte 鏁扮粍鍙嶅簭鍒楀寲鎴愬璞�
+ *
+ * @param bytes json bytes
+ * @param valueType class
+ * @param <T> T 娉涘瀷鏍囪
+ * @return Bean
+ */
+ public static <T> T parse(byte[] bytes, Class<T> valueType) {
+ try {
+ return getInstance().readValue(bytes, valueType);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+
+ /**
+ * 灏唈son鍙嶅簭鍒楀寲鎴愬璞�
+ *
+ * @param bytes bytes
+ * @param typeReference 娉涘瀷绫诲瀷
+ * @param <T> T 娉涘瀷鏍囪
+ * @return Bean
+ */
+ public static <T> T parse(byte[] bytes, TypeReference<T> typeReference) {
+ try {
+ return getInstance().readValue(bytes, typeReference);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 灏唈son鍙嶅簭鍒楀寲鎴愬璞�
+ *
+ * @param in InputStream
+ * @param valueType class
+ * @param <T> T 娉涘瀷鏍囪
+ * @return Bean
+ */
+ public static <T> T parse(InputStream in, Class<T> valueType) {
+ try {
+ return getInstance().readValue(in, valueType);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 灏唈son鍙嶅簭鍒楀寲鎴愬璞�
+ *
+ * @param in InputStream
+ * @param typeReference 娉涘瀷绫诲瀷
+ * @param <T> T 娉涘瀷鏍囪
+ * @return Bean
+ */
+ public static <T> T parse(InputStream in, TypeReference<T> typeReference) {
+ try {
+ return getInstance().readValue(in, typeReference);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 灏唈son鍙嶅簭鍒楀寲鎴怢ist瀵硅薄
+ *
+ * @param content content
+ * @param valueTypeRef class
+ * @param <T> T 娉涘瀷鏍囪
+ * @return List<T>
+ */
+ public static <T> List<T> parseArray(String content, Class<T> valueTypeRef) {
+ try {
+
+ if (!StringUtil.startsWithIgnoreCase(content, StringPool.LEFT_SQ_BRACKET)) {
+ content = StringPool.LEFT_SQ_BRACKET + content + StringPool.RIGHT_SQ_BRACKET;
+ }
+
+ List<Map<String, Object>> list = getInstance().readValue(content, new TypeReference<List<Map<String, Object>>>() {
+ });
+
+ List<T> result = new ArrayList<>();
+ for (Map<String, Object> map : list) {
+ result.add(toPojo(map, valueTypeRef));
+ }
+ return result;
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * 灏唈son瀛楃涓茶浆鎴� JsonNode
+ *
+ * @param jsonString jsonString
+ * @return jsonString json瀛楃涓�
+ */
+ public static JsonNode readTree(String jsonString) {
+ Objects.requireNonNull(jsonString, "jsonString is null");
+ try {
+ return getInstance().readTree(jsonString);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 灏唈son瀛楃涓茶浆鎴� JsonNode
+ *
+ * @param in InputStream
+ * @return jsonString json瀛楃涓�
+ */
+ public static JsonNode readTree(InputStream in) {
+ Objects.requireNonNull(in, "InputStream in is null");
+ try {
+ return getInstance().readTree(in);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 灏唈son瀛楃涓茶浆鎴� JsonNode
+ *
+ * @param content content
+ * @return jsonString json瀛楃涓�
+ */
+ public static JsonNode readTree(byte[] content) {
+ Objects.requireNonNull(content, "byte[] content is null");
+ try {
+ return getInstance().readTree(content);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 灏唈son瀛楃涓茶浆鎴� JsonNode
+ *
+ * @param jsonParser JsonParser
+ * @return jsonString json瀛楃涓�
+ */
+ public static JsonNode readTree(JsonParser jsonParser) {
+ Objects.requireNonNull(jsonParser, "jsonParser is null");
+ try {
+ return getInstance().readTree(jsonParser);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+
+ /**
+ * 灏唈son byte 鏁扮粍鍙嶅簭鍒楀寲鎴愬璞�
+ *
+ * @param content json bytes
+ * @param valueType class
+ * @param <T> T 娉涘瀷鏍囪
+ * @return Bean
+ */
+ @Nullable
+ public static <T> T readValue(@Nullable byte[] content, Class<T> valueType) {
+ if (ObjectUtil.isEmpty(content)) {
+ return null;
+ }
+ try {
+ return getInstance().readValue(content, valueType);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 灏唈son鍙嶅簭鍒楀寲鎴愬璞�
+ *
+ * @param jsonString jsonString
+ * @param valueType class
+ * @param <T> T 娉涘瀷鏍囪
+ * @return Bean
+ */
+ @Nullable
+ public static <T> T readValue(@Nullable String jsonString, Class<T> valueType) {
+ if (StringUtil.isBlank(jsonString)) {
+ return null;
+ }
+ try {
+ return getInstance().readValue(jsonString, valueType);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 灏唈son鍙嶅簭鍒楀寲鎴愬璞�
+ *
+ * @param in InputStream
+ * @param valueType class
+ * @param <T> T 娉涘瀷鏍囪
+ * @return Bean
+ */
+ @Nullable
+ public static <T> T readValue(@Nullable InputStream in, Class<T> valueType) {
+ if (in == null) {
+ return null;
+ }
+ try {
+ return getInstance().readValue(in, valueType);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 灏唈son鍙嶅簭鍒楀寲鎴愬璞�
+ *
+ * @param content bytes
+ * @param typeReference 娉涘瀷绫诲瀷
+ * @param <T> T 娉涘瀷鏍囪
+ * @return Bean
+ */
+ @Nullable
+ public static <T> T readValue(@Nullable byte[] content, TypeReference<T> typeReference) {
+ if (ObjectUtil.isEmpty(content)) {
+ return null;
+ }
+ try {
+ return getInstance().readValue(content, typeReference);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 灏唈son鍙嶅簭鍒楀寲鎴愬璞�
+ *
+ * @param jsonString jsonString
+ * @param typeReference 娉涘瀷绫诲瀷
+ * @param <T> T 娉涘瀷鏍囪
+ * @return Bean
+ */
+ @Nullable
+ public static <T> T readValue(@Nullable String jsonString, TypeReference<T> typeReference) {
+ if (StringUtil.isBlank(jsonString)) {
+ return null;
+ }
+ try {
+ return getInstance().readValue(jsonString, typeReference);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 灏唈son鍙嶅簭鍒楀寲鎴愬璞�
+ *
+ * @param in InputStream
+ * @param typeReference 娉涘瀷绫诲瀷
+ * @param <T> T 娉涘瀷鏍囪
+ * @return Bean
+ */
+ @Nullable
+ public static <T> T readValue(@Nullable InputStream in, TypeReference<T> typeReference) {
+ if (in == null) {
+ return null;
+ }
+ try {
+ return getInstance().readValue(in, typeReference);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 灏佽 map type
+ *
+ * @param keyClass key 绫诲瀷
+ * @param valueClass value 绫诲瀷
+ * @return MapType
+ */
+ public static MapType getMapType(Class<?> keyClass, Class<?> valueClass) {
+ return getInstance().getTypeFactory().constructMapType(Map.class, keyClass, valueClass);
+ }
+
+ /**
+ * 灏佽 map type
+ *
+ * @param elementClass 闆嗗悎鍊肩被鍨�
+ * @return CollectionLikeType
+ */
+ public static CollectionLikeType getListType(Class<?> elementClass) {
+ return getInstance().getTypeFactory().constructCollectionLikeType(List.class, elementClass);
+ }
+
+ /**
+ * 璇诲彇闆嗗悎
+ *
+ * @param content bytes
+ * @param elementClass elementClass
+ * @param <T> 娉涘瀷
+ * @return 闆嗗悎
+ */
+ public static <T> List<T> readList(@Nullable byte[] content, Class<T> elementClass) {
+ if (ObjectUtil.isEmpty(content)) {
+ return Collections.emptyList();
+ }
+ try {
+ return getInstance().readValue(content, getListType(elementClass));
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 璇诲彇闆嗗悎
+ *
+ * @param content InputStream
+ * @param elementClass elementClass
+ * @param <T> 娉涘瀷
+ * @return 闆嗗悎
+ */
+ public static <T> List<T> readList(@Nullable InputStream content, Class<T> elementClass) {
+ if (content == null) {
+ return Collections.emptyList();
+ }
+ try {
+ return getInstance().readValue(content, getListType(elementClass));
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 璇诲彇闆嗗悎
+ *
+ * @param content bytes
+ * @param elementClass elementClass
+ * @param <T> 娉涘瀷
+ * @return 闆嗗悎
+ */
+ public static <T> List<T> readList(@Nullable String content, Class<T> elementClass) {
+ if (ObjectUtil.isEmpty(content)) {
+ return Collections.emptyList();
+ }
+ try {
+ return getInstance().readValue(content, getListType(elementClass));
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 璇诲彇闆嗗悎
+ *
+ * @param content bytes
+ * @param keyClass key绫诲瀷
+ * @param valueClass 鍊肩被鍨�
+ * @param <K> 娉涘瀷
+ * @param <V> 娉涘瀷
+ * @return 闆嗗悎
+ */
+ public static <K, V> Map<K, V> readMap(@Nullable byte[] content, Class<?> keyClass, Class<?> valueClass) {
+ if (ObjectUtil.isEmpty(content)) {
+ return Collections.emptyMap();
+ }
+ try {
+ return getInstance().readValue(content, getMapType(keyClass, valueClass));
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 璇诲彇闆嗗悎
+ *
+ * @param content InputStream
+ * @param keyClass key绫诲瀷
+ * @param valueClass 鍊肩被鍨�
+ * @param <K> 娉涘瀷
+ * @param <V> 娉涘瀷
+ * @return 闆嗗悎
+ */
+ public static <K, V> Map<K, V> readMap(@Nullable InputStream content, Class<?> keyClass, Class<?> valueClass) {
+ if (ObjectUtil.isEmpty(content)) {
+ return Collections.emptyMap();
+ }
+ try {
+ return getInstance().readValue(content, getMapType(keyClass, valueClass));
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 璇诲彇闆嗗悎
+ *
+ * @param content bytes
+ * @param keyClass key绫诲瀷
+ * @param valueClass 鍊肩被鍨�
+ * @param <K> 娉涘瀷
+ * @param <V> 娉涘瀷
+ * @return 闆嗗悎
+ */
+ public static <K, V> Map<K, V> readMap(@Nullable String content, Class<?> keyClass, Class<?> valueClass) {
+ if (ObjectUtil.isEmpty(content)) {
+ return Collections.emptyMap();
+ }
+ try {
+ return getInstance().readValue(content, getMapType(keyClass, valueClass));
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 璇诲彇闆嗗悎
+ *
+ * @param content bytes
+ * @return 闆嗗悎
+ */
+ public static Map<String, Object> readMap(@Nullable String content) {
+ return readMap(content, String.class, Object.class);
+ }
+
+ /**
+ * 璇诲彇闆嗗悎
+ *
+ * @param content bytes
+ * @return 闆嗗悎
+ */
+ public static List<Map<String, Object>> readListMap(@Nullable String content) {
+ if (ObjectUtil.isEmpty(content)) {
+ return Collections.emptyList();
+ }
+ try {
+ return getInstance().readValue(content, new TypeReference<List<Map<String, Object>>>() {
+ });
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * jackson 鐨勭被鍨嬭浆鎹�
+ *
+ * @param fromValue 鏉ユ簮瀵硅薄
+ * @param toValueType 杞崲鐨勭被鍨�
+ * @param <T> 娉涘瀷鏍囪
+ * @return 杞崲缁撴灉
+ */
+ public static <T> T convertValue(Object fromValue, Class<T> toValueType) {
+ return getInstance().convertValue(fromValue, toValueType);
+ }
+
+ /**
+ * jackson 鐨勭被鍨嬭浆鎹�
+ *
+ * @param fromValue 鏉ユ簮瀵硅薄
+ * @param toValueType 杞崲鐨勭被鍨�
+ * @param <T> 娉涘瀷鏍囪
+ * @return 杞崲缁撴灉
+ */
+ public static <T> T convertValue(Object fromValue, JavaType toValueType) {
+ return getInstance().convertValue(fromValue, toValueType);
+ }
+
+ /**
+ * jackson 鐨勭被鍨嬭浆鎹�
+ *
+ * @param fromValue 鏉ユ簮瀵硅薄
+ * @param toValueTypeRef 娉涘瀷绫诲瀷
+ * @param <T> 娉涘瀷鏍囪
+ * @return 杞崲缁撴灉
+ */
+ public static <T> T convertValue(Object fromValue, TypeReference<T> toValueTypeRef) {
+ return getInstance().convertValue(fromValue, toValueTypeRef);
+ }
+
+ /**
+ * tree 杞璞�
+ *
+ * @param treeNode TreeNode
+ * @param valueType valueType
+ * @param <T> 娉涘瀷鏍囪
+ * @return 杞崲缁撴灉
+ */
+ public static <T> T treeToValue(TreeNode treeNode, Class<T> valueType) {
+ try {
+ return getInstance().treeToValue(treeNode, valueType);
+ } catch (JsonProcessingException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 瀵硅薄杞负 json node
+ *
+ * @param value 瀵硅薄
+ * @return JsonNode
+ */
+ public static JsonNode valueToTree(@Nullable Object value) {
+ return getInstance().valueToTree(value);
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁鍙互搴忓垪鍖�
+ *
+ * @param value 瀵硅薄
+ * @return 鏄惁鍙互搴忓垪鍖�
+ */
+ public static boolean canSerialize(@Nullable Object value) {
+ if (value == null) {
+ return true;
+ }
+ return getInstance().canSerialize(value.getClass());
+ }
+
+ public static Map<String, Object> toMap(String content) {
+ try {
+ return getInstance().readValue(content, Map.class);
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ }
+ return null;
+ }
+
+ public static <T> Map<String, T> toMap(String content, Class<T> valueTypeRef) {
+ try {
+ Map<String, Map<String, Object>> map = getInstance().readValue(content, new TypeReference<Map<String, Map<String, Object>>>() {
+ });
+ Map<String, T> result = new HashMap<>(16);
+ for (Map.Entry<String, Map<String, Object>> entry : map.entrySet()) {
+ result.put(entry.getKey(), toPojo(entry.getValue(), valueTypeRef));
+ }
+ return result;
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ }
+ return null;
+ }
+
+ public static <T> T toPojo(Map fromValue, Class<T> toValueType) {
+ return getInstance().convertValue(fromValue, toValueType);
+ }
+
+ public static ObjectMapper getInstance() {
+ return JacksonHolder.INSTANCE;
+ }
+
+ private static class JacksonHolder {
+ private static final ObjectMapper INSTANCE = new JacksonObjectMapper();
+ }
+
+ private static class JacksonObjectMapper extends ObjectMapper {
+ private static final long serialVersionUID = 4288193147502386170L;
+
+ private static final Locale CHINA = Locale.CHINA;
+
+ public JacksonObjectMapper(ObjectMapper src) {
+ super(src);
+ }
+
+ public JacksonObjectMapper() {
+ super();
+ //璁剧疆鍦扮偣涓轰腑鍥�
+ super.setLocale(CHINA);
+ //鍘绘帀榛樿鐨勬椂闂存埑鏍煎紡
+ super.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ //璁剧疆涓轰腑鍥戒笂娴锋椂鍖�
+ super.setTimeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));
+ //搴忓垪鍖栨椂锛屾棩鏈熺殑缁熶竴鏍煎紡
+ super.setDateFormat(new SimpleDateFormat(DateUtil.PATTERN_DATETIME, Locale.CHINA));
+ // 鍗曞紩鍙�
+ super.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
+ // 鍏佽JSON瀛楃涓插寘鍚潪寮曞彿鎺у埗瀛楃锛堝�煎皬浜�32鐨凙SCII瀛楃锛屽寘鍚埗琛ㄧ鍜屾崲琛岀锛�
+ super.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true);
+ super.configure(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER.mappedFeature(), true);
+ super.findAndRegisterModules();
+ //澶辫触澶勭悊
+ super.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+ super.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ //鍗曞紩鍙峰鐞�
+ super.configure(JsonReadFeature.ALLOW_SINGLE_QUOTES.mappedFeature(), true);
+ //鍙嶅簭鍒楀寲鏃讹紝灞炴�т笉瀛樺湪鐨勫吋瀹瑰鐞唖
+ super.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+ //鏃ユ湡鏍煎紡鍖�
+ super.registerModule(new BladeJavaTimeModule());
+ super.findAndRegisterModules();
+ }
+
+ @Override
+ public ObjectMapper copy() {
+ return new JacksonObjectMapper(this);
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/MappingApiJackson2HttpMessageConverter.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/MappingApiJackson2HttpMessageConverter.java
new file mode 100644
index 0000000..d73f7ba
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/MappingApiJackson2HttpMessageConverter.java
@@ -0,0 +1,108 @@
+package org.springblade.core.tool.jackson;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springblade.core.tool.utils.Charsets;
+import org.springframework.http.HttpOutputMessage;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageNotWritableException;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+import org.springframework.util.StreamUtils;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 閽堝 api 鏈嶅姟瀵� android 鍜� ios 澶勭悊鐨� 鍒嗚鍐欑殑 jackson 澶勭悊
+ *
+ * <p>
+ * 1. app 绔笂鎶ユ暟鎹槸 浣跨敤 readObjectMapper
+ * 2. 杩斿洖缁� app 绔殑鏁版嵁浣跨敤 writeObjectMapper
+ * 3. 濡傛灉鏄繑鍥炲瓧绗︿覆锛岀洿鎺ョ浉搴旓紝涓嶅仛 json 澶勭悊
+ * </p>
+ *
+ * @author L.cm
+ */
+public class MappingApiJackson2HttpMessageConverter extends AbstractReadWriteJackson2HttpMessageConverter {
+
+ @Nullable
+ private String jsonPrefix;
+
+ public MappingApiJackson2HttpMessageConverter(ObjectMapper objectMapper, BladeJacksonProperties properties) {
+ super(objectMapper, initWriteObjectMapper(objectMapper, properties), initMediaType(properties));
+ }
+
+ private static List<MediaType> initMediaType(BladeJacksonProperties properties) {
+ List<MediaType> supportedMediaTypes = new ArrayList<>();
+ supportedMediaTypes.add(MediaType.APPLICATION_JSON);
+ supportedMediaTypes.add(new MediaType("application", "*+json"));
+ // 鏀寔 text 鏂囨湰锛岀敤浜庢姤鏂囩鍚�
+ if (Boolean.TRUE.equals(properties.getSupportTextPlain())) {
+ supportedMediaTypes.add(MediaType.TEXT_PLAIN);
+ }
+ return supportedMediaTypes;
+ }
+
+ private static ObjectMapper initWriteObjectMapper(ObjectMapper readObjectMapper, BladeJacksonProperties properties) {
+ // 鎷疯礉 readObjectMapper
+ ObjectMapper writeObjectMapper = readObjectMapper.copy();
+ // 澶ф暟瀛� 杞� 瀛楃涓�
+ if (Boolean.TRUE.equals(properties.getBigNumToString())) {
+ writeObjectMapper.registerModules(BladeNumberModule.INSTANCE);
+ }
+ // null 澶勭悊
+ if (Boolean.TRUE.equals(properties.getNullToEmpty())) {
+ writeObjectMapper.setSerializerFactory(writeObjectMapper.getSerializerFactory().withSerializerModifier(new BladeBeanSerializerModifier()));
+ writeObjectMapper.getSerializerProvider().setNullValueSerializer(BladeBeanSerializerModifier.NullJsonSerializers.STRING_JSON_SERIALIZER);
+ }
+ return writeObjectMapper;
+ }
+
+ @Override
+ protected void writeInternal(@NonNull Object object, @Nullable Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
+ // 濡傛灉鏄瓧绗︿覆锛岀洿鎺ュ啓鍑�
+ if (object instanceof String) {
+ Charset defaultCharset = this.getDefaultCharset();
+ Charset charset = defaultCharset == null ? Charsets.UTF_8 : defaultCharset;
+ StreamUtils.copy((String) object, charset, outputMessage.getBody());
+ } else {
+ super.writeInternal(object, type, outputMessage);
+ }
+ }
+
+ /**
+ * Specify a custom prefix to use for this view's JSON output.
+ * Default is none.
+ *
+ * @param jsonPrefix jsonPrefix
+ * @see #setPrefixJson
+ */
+ public void setJsonPrefix(@Nullable String jsonPrefix) {
+ this.jsonPrefix = jsonPrefix;
+ }
+
+ /**
+ * Indicate whether the JSON output by this view should be prefixed with ")]}', ". Default is false.
+ * <p>Prefixing the JSON string in this manner is used to help prevent JSON Hijacking.
+ * The prefix renders the string syntactically invalid as a script so that it cannot be hijacked.
+ * This prefix should be stripped before parsing the string as JSON.
+ *
+ * @param prefixJson prefixJson
+ * @see #setJsonPrefix
+ */
+ public void setPrefixJson(boolean prefixJson) {
+ this.jsonPrefix = (prefixJson ? ")]}', " : null);
+ }
+
+ @Override
+ protected void writePrefix(@NonNull JsonGenerator generator, @NonNull Object object) throws IOException {
+ if (this.jsonPrefix != null) {
+ generator.writeRaw(this.jsonPrefix);
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/BaseNode.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/BaseNode.java
new file mode 100644
index 0000000..d1531fa
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/BaseNode.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.node;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 鑺傜偣鍩虹被
+ *
+ * @author smallchill
+ */
+@Data
+public class BaseNode<T> implements INode<T> {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 涓婚敭ID
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ protected Long id;
+
+ /**
+ * 鐖惰妭鐐笽D
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ protected Long parentId;
+
+ /**
+ * 瀛愬瓩鑺傜偣
+ */
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ protected List<T> children = new ArrayList<T>();
+
+ /**
+ * 鏄惁鏈夊瓙瀛欒妭鐐�
+ */
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ private Boolean hasChildren;
+
+ /**
+ * 鏄惁鏈夊瓙瀛欒妭鐐�
+ *
+ * @return Boolean
+ */
+ @Override
+ public Boolean getHasChildren() {
+ if (children.size() > 0) {
+ return true;
+ } else {
+ return this.hasChildren;
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/ForestNode.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/ForestNode.java
new file mode 100644
index 0000000..5987dc1
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/ForestNode.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.node;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+
+/**
+ * 妫灄鑺傜偣绫�
+ *
+ * @author smallchill
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class ForestNode extends BaseNode<ForestNode> {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鑺傜偣鍐呭
+ */
+ private Object content;
+
+ public ForestNode(Long id, Long parentId, Object content) {
+ this.id = id;
+ this.parentId = parentId;
+ this.content = content;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/ForestNodeManager.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/ForestNodeManager.java
new file mode 100644
index 0000000..f1ff224
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/ForestNodeManager.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.node;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import org.springblade.core.tool.utils.StringPool;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 妫灄绠$悊绫�
+ *
+ * @author smallchill
+ */
+public class ForestNodeManager<T extends INode<T>> {
+
+ /**
+ * 妫灄鐨勬墍鏈夎妭鐐�
+ */
+ private final ImmutableMap<Long, T> nodeMap;
+
+ /**
+ * 妫灄鐨勭埗鑺傜偣ID
+ */
+ private final Map<Long, Object> parentIdMap = Maps.newHashMap();
+
+ public ForestNodeManager(List<T> nodes) {
+ nodeMap = Maps.uniqueIndex(nodes, INode::getId);
+ }
+
+ /**
+ * 鏍规嵁鑺傜偣ID鑾峰彇涓�涓妭鐐�
+ *
+ * @param id 鑺傜偣ID
+ * @return 瀵瑰簲鐨勮妭鐐瑰璞�
+ */
+ public INode<T> getTreeNodeAt(Long id) {
+ if (nodeMap.containsKey(id)) {
+ return nodeMap.get(id);
+ }
+ return null;
+ }
+
+ /**
+ * 澧炲姞鐖惰妭鐐笽D
+ *
+ * @param parentId 鐖惰妭鐐笽D
+ */
+ public void addParentId(Long parentId) {
+ parentIdMap.put(parentId, StringPool.EMPTY);
+ }
+
+ /**
+ * 鑾峰彇鏍戠殑鏍硅妭鐐�(涓�涓.鏋楀搴斿棰楁爲)
+ *
+ * @return 鏍戠殑鏍硅妭鐐归泦鍚�
+ */
+ public List<T> getRoot() {
+ List<T> roots = new ArrayList<>();
+ nodeMap.forEach((key, node) -> {
+ if (node.getParentId() == 0 || parentIdMap.containsKey(node.getId())) {
+ roots.add(node);
+ }
+ });
+ return roots;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/ForestNodeMerger.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/ForestNodeMerger.java
new file mode 100644
index 0000000..33f9286
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/ForestNodeMerger.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.node;
+
+import java.util.List;
+
+/**
+ * 妫灄鑺傜偣褰掑苟绫�
+ *
+ * @author smallchill
+ */
+public class ForestNodeMerger {
+
+ /**
+ * 灏嗚妭鐐规暟缁勫綊骞朵负涓�涓.鏋楋紙澶氭5鏍戯級锛堝~鍏呰妭鐐圭殑children鍩燂級
+ * 鏃堕棿澶嶆潅搴︿负O(n^2)
+ *
+ * @param items 鑺傜偣鍩�
+ * @return 澶氭5鏍戠殑鏍硅妭鐐归泦鍚�
+ */
+ public static <T extends INode<T>> List<T> merge(List<T> items) {
+ ForestNodeManager<T> forestNodeManager = new ForestNodeManager<>(items);
+ items.forEach(forestNode -> {
+ if (forestNode.getParentId() != 0) {
+ INode<T> node = forestNodeManager.getTreeNodeAt(forestNode.getParentId());
+ if (node != null) {
+ node.getChildren().add(forestNode);
+ } else {
+ forestNodeManager.addParentId(forestNode.getId());
+ }
+ }
+ });
+ return forestNodeManager.getRoot();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/INode.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/INode.java
new file mode 100644
index 0000000..53968b9
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/INode.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.node;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Created by Blade.
+ *
+ * @author smallchill
+ */
+public interface INode<T> extends Serializable {
+
+ /**
+ * 涓婚敭
+ *
+ * @return Long
+ */
+ Long getId();
+
+ /**
+ * 鐖朵富閿�
+ *
+ * @return Long
+ */
+ Long getParentId();
+
+ /**
+ * 瀛愬瓩鑺傜偣
+ *
+ * @return List<T>
+ */
+ List<T> getChildren();
+
+ /**
+ * 鏄惁鏈夊瓙瀛欒妭鐐�
+ *
+ * @return Boolean
+ */
+ default Boolean getHasChildren() {
+ return false;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/NodeTest.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/NodeTest.java
new file mode 100644
index 0000000..8c98b5e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/NodeTest.java
@@ -0,0 +1,33 @@
+package org.springblade.core.tool.node;
+
+import org.springblade.core.tool.jackson.JsonUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by Blade.
+ *
+ * @author smallchill
+ */
+public class NodeTest {
+
+ public static void main(String[] args) {
+ List<ForestNode> list = new ArrayList<>();
+ list.add(new ForestNode(1L, 0L, "1"));
+ list.add(new ForestNode(2L, 0L, "2"));
+ list.add(new ForestNode(3L, 1L, "3"));
+ list.add(new ForestNode(4L, 2L, "4"));
+ list.add(new ForestNode(5L, 3L, "5"));
+ list.add(new ForestNode(6L, 4L, "6"));
+ list.add(new ForestNode(7L, 3L, "7"));
+ list.add(new ForestNode(8L, 5L, "8"));
+ list.add(new ForestNode(9L, 6L, "9"));
+ list.add(new ForestNode(10L, 9L, "10"));
+ List<ForestNode> tns = ForestNodeMerger.merge(list);
+ tns.forEach(node ->
+ System.out.println(JsonUtil.toJson(node))
+ );
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/TreeNode.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/TreeNode.java
new file mode 100644
index 0000000..846377a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/node/TreeNode.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.node;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import lombok.Data;
+import org.springblade.core.tool.utils.Func;
+
+import java.util.Objects;
+
+/**
+ * 鏍戝瀷鑺傜偣绫�
+ *
+ * @author smallchill
+ */
+@Data
+public class TreeNode extends BaseNode<TreeNode> {
+
+ private static final long serialVersionUID = 1L;
+
+ private String title;
+
+ @JsonSerialize(using = ToStringSerializer.class)
+ private Long key;
+
+ @JsonSerialize(using = ToStringSerializer.class)
+ private Long value;
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ TreeNode other = (TreeNode) obj;
+ return Func.equals(this.getId(), other.getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, parentId);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/spel/BladeExpressionEvaluator.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/spel/BladeExpressionEvaluator.java
new file mode 100644
index 0000000..da2ab51
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/spel/BladeExpressionEvaluator.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.spel;
+
+import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.context.expression.AnnotatedElementKey;
+import org.springframework.context.expression.BeanFactoryResolver;
+import org.springframework.context.expression.CachedExpressionEvaluator;
+import org.springframework.context.expression.MethodBasedEvaluationContext;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.lang.Nullable;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 缂撳瓨 spEl 鎻愰珮鎬ц兘
+ *
+ * @author L.cm
+ */
+public class BladeExpressionEvaluator extends CachedExpressionEvaluator {
+ private final Map<ExpressionKey, Expression> expressionCache = new ConcurrentHashMap<>(64);
+ private final Map<AnnotatedElementKey, Method> methodCache = new ConcurrentHashMap<>(64);
+
+ /**
+ * Create an {@link EvaluationContext}.
+ *
+ * @param method the method
+ * @param args the method arguments
+ * @param target the target object
+ * @param targetClass the target class
+ * @return the evaluation context
+ */
+ public EvaluationContext createContext(Method method, Object[] args, Object target, Class<?> targetClass, @Nullable BeanFactory beanFactory) {
+ Method targetMethod = getTargetMethod(targetClass, method);
+ BladeExpressionRootObject rootObject = new BladeExpressionRootObject(method, args, target, targetClass, targetMethod);
+ MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext(rootObject, targetMethod, args, getParameterNameDiscoverer());
+ if (beanFactory != null) {
+ evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
+ }
+ return evaluationContext;
+ }
+
+ /**
+ * Create an {@link EvaluationContext}.
+ *
+ * @param method the method
+ * @param args the method arguments
+ * @param rootObject rootObject
+ * @param targetClass the target class
+ * @return the evaluation context
+ */
+ public EvaluationContext createContext(Method method, Object[] args, Class<?> targetClass, Object rootObject, @Nullable BeanFactory beanFactory) {
+ Method targetMethod = getTargetMethod(targetClass, method);
+ MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext(rootObject, targetMethod, args, getParameterNameDiscoverer());
+ if (beanFactory != null) {
+ evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
+ }
+ return evaluationContext;
+ }
+
+ @Nullable
+ public Object eval(String expression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
+ return eval(expression, methodKey, evalContext, null);
+ }
+
+ @Nullable
+ public <T> T eval(String expression, AnnotatedElementKey methodKey, EvaluationContext evalContext, @Nullable Class<T> valueType) {
+ return getExpression(this.expressionCache, methodKey, expression).getValue(evalContext, valueType);
+ }
+
+ @Nullable
+ public String evalAsText(String expression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
+ return eval(expression, methodKey, evalContext, String.class);
+ }
+
+ public boolean evalAsBool(String expression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
+ return Boolean.TRUE.equals(eval(expression, methodKey, evalContext, Boolean.class));
+ }
+
+ private Method getTargetMethod(Class<?> targetClass, Method method) {
+ AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
+ return methodCache.computeIfAbsent(methodKey, (key) -> AopUtils.getMostSpecificMethod(method, targetClass));
+ }
+
+ /**
+ * Clear all caches.
+ */
+ public void clear() {
+ this.expressionCache.clear();
+ this.methodCache.clear();
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/spel/BladeExpressionRootObject.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/spel/BladeExpressionRootObject.java
new file mode 100644
index 0000000..8994d27
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/spel/BladeExpressionRootObject.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.spel;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.lang.reflect.Method;
+
+/**
+ * ExpressionRootObject
+ *
+ * @author L.cm
+ */
+@Getter
+@AllArgsConstructor
+public class BladeExpressionRootObject {
+ private final Method method;
+
+ private final Object[] args;
+
+ private final Object target;
+
+ private final Class<?> targetClass;
+
+ private final Method targetMethod;
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/ssl/DisableValidationTrustManager.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/ssl/DisableValidationTrustManager.java
new file mode 100644
index 0000000..e8ebb62
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/ssl/DisableValidationTrustManager.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.ssl;
+
+import javax.net.ssl.X509TrustManager;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+/**
+ * 涓嶈繘琛岃瘉涔︽牎楠�
+ *
+ * @author L.cm
+ */
+public class DisableValidationTrustManager implements X509TrustManager {
+
+ public static final X509TrustManager INSTANCE = new DisableValidationTrustManager();
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/ssl/TrustAllHostNames.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/ssl/TrustAllHostNames.java
new file mode 100644
index 0000000..0e138cf
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/ssl/TrustAllHostNames.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.ssl;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSession;
+
+/**
+ * 淇′换鎵�鏈� host name
+ *
+ * @author L.cm
+ */
+public class TrustAllHostNames implements HostnameVerifier {
+ public static final TrustAllHostNames INSTANCE = new TrustAllHostNames();
+
+ @Override
+ public boolean verify(String s, SSLSession sslSession) {
+ return true;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/BeanDiff.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/BeanDiff.java
new file mode 100644
index 0000000..c4354cc
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/BeanDiff.java
@@ -0,0 +1,31 @@
+package org.springblade.core.tool.support;
+
+import lombok.Getter;
+import lombok.ToString;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 璺熻釜绫诲彉鍔ㄦ瘮杈�
+ *
+ * @author L.cm
+ */
+@Getter
+@ToString
+public class BeanDiff {
+ /**
+ * 鍙樻洿瀛楁
+ */
+ private final Set<String> fields = new HashSet<>();
+ /**
+ * 鏃у��
+ */
+ private final Map<String, Object> oldValues = new HashMap<>();
+ /**
+ * 鏂板��
+ */
+ private final Map<String, Object> newValues = new HashMap<>();
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/BinderSupplier.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/BinderSupplier.java
new file mode 100644
index 0000000..20469ad
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/BinderSupplier.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.support;
+
+import java.util.function.Supplier;
+
+/**
+ * 瑙e喅 no binder available 闂
+ *
+ * @author Chill
+ */
+public class BinderSupplier implements Supplier<Object> {
+
+ @Override
+ public Object get() {
+ return null;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/CoreMain.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/CoreMain.java
new file mode 100644
index 0000000..75c6c1d
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/CoreMain.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.support;
+
+/**
+ * Created by Blade.
+ *
+ * @author Chill
+ */
+public class CoreMain {
+
+ public static void main(String[] args) {
+ System.out.println("init core module");
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/FastStringWriter.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/FastStringWriter.java
new file mode 100644
index 0000000..3adcada
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/FastStringWriter.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.support;
+
+
+import org.springblade.core.tool.utils.StringPool;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Arrays;
+
+/**
+ * FastStringWriter锛屾洿鏀逛簬 jdk CharArrayWriter
+ *
+ * <p>
+ * 1. 鍘绘帀浜嗛攣
+ * 2. 鍒濆瀹归噺鐢� 32 鏀逛负 64
+ * </p>
+ *
+ * @author L.cm
+ */
+public class FastStringWriter extends Writer {
+ /**
+ * The buffer where data is stored.
+ */
+ private char[] buf;
+ /**
+ * The number of chars in the buffer.
+ */
+ private int count;
+
+ /**
+ * Creates a new CharArrayWriter.
+ */
+ public FastStringWriter() {
+ this(64);
+ }
+
+ /**
+ * Creates a new CharArrayWriter with the specified initial size.
+ *
+ * @param initialSize an int specifying the initial buffer size.
+ * @throws IllegalArgumentException if initialSize is negative
+ */
+ public FastStringWriter(int initialSize) {
+ if (initialSize < 0) {
+ throw new IllegalArgumentException("Negative initial size: " + initialSize);
+ }
+ buf = new char[initialSize];
+ }
+
+ /**
+ * Writes a character to the buffer.
+ */
+ @Override
+ public void write(int c) {
+ int newCount = count + 1;
+ if (newCount > buf.length) {
+ buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newCount));
+ }
+ buf[count] = (char) c;
+ count = newCount;
+ }
+
+ /**
+ * Writes characters to the buffer.
+ *
+ * @param c the data to be written
+ * @param off the start offset in the data
+ * @param len the number of chars that are written
+ */
+ @Override
+ public void write(char[] c, int off, int len) {
+ if ((off < 0) || (off > c.length) || (len < 0) ||
+ ((off + len) > c.length) || ((off + len) < 0)) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return;
+ }
+ int newCount = count + len;
+ if (newCount > buf.length) {
+ buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newCount));
+ }
+ System.arraycopy(c, off, buf, count, len);
+ count = newCount;
+ }
+
+ /**
+ * Write a portion of a string to the buffer.
+ *
+ * @param str String to be written from
+ * @param off Offset from which to start reading characters
+ * @param len Number of characters to be written
+ */
+ @Override
+ public void write(String str, int off, int len) {
+ int newCount = count + len;
+ if (newCount > buf.length) {
+ buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newCount));
+ }
+ str.getChars(off, off + len, buf, count);
+ count = newCount;
+ }
+
+ /**
+ * Writes the contents of the buffer to another character stream.
+ *
+ * @param out the output stream to write to
+ * @throws IOException If an I/O error occurs.
+ */
+ public void writeTo(Writer out) throws IOException {
+ out.write(buf, 0, count);
+ }
+
+ /**
+ * Appends the specified character sequence to this writer.
+ *
+ * <p> An invocation of this method of the form <tt>out.append(csq)</tt>
+ * behaves in exactly the same way as the invocation
+ *
+ * <pre>
+ * out.write(csq.toString()) </pre>
+ *
+ * <p> Depending on the specification of <tt>toString</tt> for the
+ * character sequence <tt>csq</tt>, the entire sequence may not be
+ * appended. For instance, invoking the <tt>toString</tt> method of a
+ * character buffer will return a subsequence whose content depends upon
+ * the buffer's position and limit.
+ *
+ * @param csq The character sequence to append. If <tt>csq</tt> is
+ * <tt>null</tt>, then the four characters <tt>"null"</tt> are
+ * appended to this writer.
+ * @return This writer
+ */
+ @Override
+ public FastStringWriter append(CharSequence csq) {
+ String s = (csq == null ? StringPool.NULL : csq.toString());
+ write(s, 0, s.length());
+ return this;
+ }
+
+ /**
+ * Appends a subsequence of the specified character sequence to this writer.
+ *
+ * <p> An invocation of this method of the form <tt>out.append(csq, start,
+ * end)</tt> when <tt>csq</tt> is not <tt>null</tt>, behaves in
+ * exactly the same way as the invocation
+ *
+ * <pre>
+ * out.write(csq.subSequence(start, end).toString()) </pre>
+ *
+ * @param csq The character sequence from which a subsequence will be
+ * appended. If <tt>csq</tt> is <tt>null</tt>, then characters
+ * will be appended as if <tt>csq</tt> contained the four
+ * characters <tt>"null"</tt>.
+ * @param start The index of the first character in the subsequence
+ * @param end The index of the character following the last character in the
+ * subsequence
+ * @return This writer
+ * @throws IndexOutOfBoundsException If <tt>start</tt> or <tt>end</tt> are negative, <tt>start</tt>
+ * is greater than <tt>end</tt>, or <tt>end</tt> is greater than
+ * <tt>csq.length()</tt>
+ */
+ @Override
+ public FastStringWriter append(CharSequence csq, int start, int end) {
+ String s = (csq == null ? StringPool.NULL : csq).subSequence(start, end).toString();
+ write(s, 0, s.length());
+ return this;
+ }
+
+ /**
+ * Appends the specified character to this writer.
+ *
+ * <p> An invocation of this method of the form <tt>out.append(c)</tt>
+ * behaves in exactly the same way as the invocation
+ *
+ * <pre>
+ * out.write(c) </pre>
+ *
+ * @param c The 16-bit character to append
+ * @return This writer
+ */
+ @Override
+ public FastStringWriter append(char c) {
+ write(c);
+ return this;
+ }
+
+ /**
+ * Resets the buffer so that you can use it again without
+ * throwing away the already allocated buffer.
+ */
+ public void reset() {
+ count = 0;
+ }
+
+ /**
+ * Returns a copy of the input data.
+ *
+ * @return an array of chars copied from the input data.
+ */
+ public char[] toCharArray() {
+ return Arrays.copyOf(buf, count);
+ }
+
+ /**
+ * Returns the current size of the buffer.
+ *
+ * @return an int representing the current size of the buffer.
+ */
+ public int size() {
+ return count;
+ }
+
+ /**
+ * Converts input data to a string.
+ *
+ * @return the string.
+ */
+ @Override
+ public String toString() {
+ return new String(buf, 0, count);
+ }
+
+ /**
+ * Flush the stream.
+ */
+ @Override
+ public void flush() {
+ }
+
+ /**
+ * Close the stream. This method does not release the buffer, since its
+ * contents might still be required. Note: Invoking this method in this class
+ * will have no effect.
+ */
+ @Override
+ public void close() {
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/IMultiOutputStream.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/IMultiOutputStream.java
new file mode 100644
index 0000000..6f907c3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/IMultiOutputStream.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.support;
+
+import java.io.OutputStream;
+
+/**
+ * A factory for creating MultiOutputStream objects.
+ *
+ * @author Chill
+ */
+public interface IMultiOutputStream {
+
+ /**
+ * Builds the output stream.
+ *
+ * @param params the params
+ * @return the output stream
+ */
+ OutputStream buildOutputStream(Integer... params);
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/ImagePosition.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/ImagePosition.java
new file mode 100644
index 0000000..f96211e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/ImagePosition.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.support;
+
+/**
+ * 鍥剧墖鎿嶄綔绫�
+ *
+ * @author Chill
+ */
+public class ImagePosition {
+
+ /**
+ * 鍥剧墖椤堕儴.
+ */
+ public static final int TOP = 32;
+
+ /**
+ * 鍥剧墖涓儴.
+ */
+ public static final int MIDDLE = 16;
+
+ /**
+ * 鍥剧墖搴曢儴.
+ */
+ public static final int BOTTOM = 8;
+
+ /**
+ * 鍥剧墖宸︿晶.
+ */
+ public static final int LEFT = 4;
+
+ /**
+ * 鍥剧墖灞呬腑.
+ */
+ public static final int CENTER = 2;
+
+ /**
+ * 鍥剧墖鍙充晶.
+ */
+ public static final int RIGHT = 1;
+
+ /**
+ * 妯悜杈硅窛锛岄潬宸︽垨闈犲彸鏃跺拰杈圭晫鐨勮窛绂�.
+ */
+ private static final int PADDING_HORI = 6;
+
+ /**
+ * 绾靛悜杈硅窛锛岄潬涓婃垨闈犲簳鏃跺拰杈圭晫鐨勮窛绂�.
+ */
+ private static final int PADDING_VERT = 6;
+
+
+ /**
+ * 鍥剧墖涓洅[宸︿笂瑙抅鐨剎鍧愭爣.
+ */
+ private int boxPosX;
+
+ /**
+ * 鍥剧墖涓洅[宸︿笂瑙抅鐨剏鍧愭爣.
+ */
+ private int boxPosY;
+
+ /**
+ * Instantiates a new image position.
+ *
+ * @param width the width
+ * @param height the height
+ * @param boxWidth the box width
+ * @param boxHeight the box height
+ * @param style the style
+ */
+ public ImagePosition(int width, int height, int boxWidth, int boxHeight, int style) {
+ switch (style & 7) {
+ case LEFT:
+ boxPosX = PADDING_HORI;
+ break;
+ case RIGHT:
+ boxPosX = width - boxWidth - PADDING_HORI;
+ break;
+ case CENTER:
+ default:
+ boxPosX = (width - boxWidth) / 2;
+ }
+ switch (style >> 3 << 3) {
+ case TOP:
+ boxPosY = PADDING_VERT;
+ break;
+ case MIDDLE:
+ boxPosY = (height - boxHeight) / 2;
+ break;
+ case BOTTOM:
+ default:
+ boxPosY = height - boxHeight - PADDING_VERT;
+ }
+ }
+
+
+ /**
+ * Gets the x.
+ *
+ * @return the x
+ */
+ public int getX() {
+ return getX(0);
+ }
+
+ /**
+ * Gets the x.
+ *
+ * @param x 妯悜鍋忕Щ
+ * @return the x
+ */
+ public int getX(int x) {
+ return this.boxPosX + x;
+ }
+
+ /**
+ * Gets the y.
+ *
+ * @return the y
+ */
+ public int getY() {
+ return getY(0);
+ }
+
+ /**
+ * Gets the y.
+ *
+ * @param y 绾靛悜鍋忕Щ
+ * @return the y
+ */
+ public int getY(int y) {
+ return this.boxPosY + y;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/Kv.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/Kv.java
new file mode 100644
index 0000000..1c9a4e1
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/Kv.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.support;
+
+import org.springblade.core.tool.utils.Func;
+import org.springframework.util.LinkedCaseInsensitiveMap;
+
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 閾惧紡map
+ *
+ * @author Chill
+ */
+public class Kv extends LinkedCaseInsensitiveMap<Object> {
+
+ private Kv() {
+ super();
+ }
+
+ /**
+ * 鍒涘缓Kv
+ *
+ * @return Kv
+ */
+ public static Kv create() {
+ return new Kv();
+ }
+
+ public static <K, V> HashMap<K, V> newMap() {
+ return new HashMap<>(16);
+ }
+
+ /**
+ * 璁剧疆鍒�
+ *
+ * @param attr 灞炴��
+ * @param value 鍊�
+ * @return 鏈韩
+ */
+ public Kv set(String attr, Object value) {
+ this.put(attr, value);
+ return this;
+ }
+
+ /**
+ * 璁剧疆鍏ㄩ儴
+ *
+ * @param map 灞炴��
+ * @return 鏈韩
+ */
+ public Kv setAll(Map<? extends String, ?> map) {
+ if (map != null) {
+ this.putAll(map);
+ }
+ return this;
+ }
+
+ /**
+ * 璁剧疆鍒楋紝褰撻敭鎴栧�间负null鏃跺拷鐣�
+ *
+ * @param attr 灞炴��
+ * @param value 鍊�
+ * @return 鏈韩
+ */
+ public Kv setIgnoreNull(String attr, Object value) {
+ if (attr != null && value != null) {
+ set(attr, value);
+ }
+ return this;
+ }
+
+ public Object getObj(String key) {
+ return super.get(key);
+ }
+
+ /**
+ * 鑾峰緱鐗瑰畾绫诲瀷鍊�
+ *
+ * @param <T> 鍊肩被鍨�
+ * @param attr 瀛楁鍚�
+ * @param defaultValue 榛樿鍊�
+ * @return 瀛楁鍊�
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T get(String attr, T defaultValue) {
+ final Object result = get(attr);
+ return (T) (result != null ? result : defaultValue);
+ }
+
+ /**
+ * 鑾峰緱鐗瑰畾绫诲瀷鍊�
+ *
+ * @param attr 瀛楁鍚�
+ * @return 瀛楁鍊�
+ */
+ public String getStr(String attr) {
+ return Func.toStr(get(attr), null);
+ }
+
+ /**
+ * 鑾峰緱鐗瑰畾绫诲瀷鍊�
+ *
+ * @param attr 瀛楁鍚�
+ * @return 瀛楁鍊�
+ */
+ public Integer getInt(String attr) {
+ return Func.toInt(get(attr), -1);
+ }
+
+ /**
+ * 鑾峰緱鐗瑰畾绫诲瀷鍊�
+ *
+ * @param attr 瀛楁鍚�
+ * @return 瀛楁鍊�
+ */
+ public Long getLong(String attr) {
+ return Func.toLong(get(attr), -1L);
+ }
+
+ /**
+ * 鑾峰緱鐗瑰畾绫诲瀷鍊�
+ *
+ * @param attr 瀛楁鍚�
+ * @return 瀛楁鍊�
+ */
+ public Float getFloat(String attr) {
+ return Func.toFloat(get(attr), null);
+ }
+
+ public Double getDouble(String attr) {
+ return Func.toDouble(get(attr), null);
+ }
+
+
+ /**
+ * 鑾峰緱鐗瑰畾绫诲瀷鍊�
+ *
+ * @param attr 瀛楁鍚�
+ * @return 瀛楁鍊�
+ */
+ public Boolean getBool(String attr) {
+ return Func.toBoolean(get(attr), null);
+ }
+
+ /**
+ * 鑾峰緱鐗瑰畾绫诲瀷鍊�
+ *
+ * @param attr 瀛楁鍚�
+ * @return 瀛楁鍊�
+ */
+ public byte[] getBytes(String attr) {
+ return get(attr, null);
+ }
+
+ /**
+ * 鑾峰緱鐗瑰畾绫诲瀷鍊�
+ *
+ * @param attr 瀛楁鍚�
+ * @return 瀛楁鍊�
+ */
+ public Date getDate(String attr) {
+ return get(attr, null);
+ }
+
+ /**
+ * 鑾峰緱鐗瑰畾绫诲瀷鍊�
+ *
+ * @param attr 瀛楁鍚�
+ * @return 瀛楁鍊�
+ */
+ public Time getTime(String attr) {
+ return get(attr, null);
+ }
+
+ /**
+ * 鑾峰緱鐗瑰畾绫诲瀷鍊�
+ *
+ * @param attr 瀛楁鍚�
+ * @return 瀛楁鍊�
+ */
+ public Timestamp getTimestamp(String attr) {
+ return get(attr, null);
+ }
+
+ /**
+ * 鑾峰緱鐗瑰畾绫诲瀷鍊�
+ *
+ * @param attr 瀛楁鍚�
+ * @return 瀛楁鍊�
+ */
+ public Number getNumber(String attr) {
+ return get(attr, null);
+ }
+
+ @Override
+ public Kv clone() {
+ Kv clone = new Kv();
+ clone.putAll(this);
+ return clone;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/StrSpliter.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/StrSpliter.java
new file mode 100644
index 0000000..377e9b5
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/StrSpliter.java
@@ -0,0 +1,501 @@
+package org.springblade.core.tool.support;
+
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 瀛楃涓插垏鍒嗗櫒
+ *
+ * @author Looly
+ */
+public class StrSpliter {
+
+ //---------------------------------------------------------------------------------------------- Split by char
+
+ /**
+ * 鍒囧垎瀛楃涓茶矾寰勶紝浠呮敮鎸乁nix鍒嗙晫绗︼細/
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.0.8
+ */
+ public static List<String> splitPath(String str) {
+ return splitPath(str, 0);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓茶矾寰勶紝浠呮敮鎸乁nix鍒嗙晫绗︼細/
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.0.8
+ */
+ public static String[] splitPathToArray(String str) {
+ return toArray(splitPath(str));
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓茶矾寰勶紝浠呮敮鎸乁nix鍒嗙晫绗︼細/
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param limit 闄愬埗鍒嗙墖鏁�
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.0.8
+ */
+ public static List<String> splitPath(String str, int limit) {
+ return split(str, StringPool.SLASH, limit, true, true);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓茶矾寰勶紝浠呮敮鎸乁nix鍒嗙晫绗︼細/
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param limit 闄愬埗鍒嗙墖鏁�
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.0.8
+ */
+ public static String[] splitPathToArray(String str, int limit) {
+ return toArray(splitPath(str, limit));
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.2.1
+ */
+ public static List<String> splitTrim(String str, char separator, boolean ignoreEmpty) {
+ return split(str, separator, 0, true, ignoreEmpty);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @param isTrim 鏄惁鍘婚櫎鍒囧垎瀛楃涓插悗姣忎釜鍏冪礌涓よ竟鐨勭┖鏍�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.0.8
+ */
+ public static List<String> split(String str, char separator, boolean isTrim, boolean ignoreEmpty) {
+ return split(str, separator, 0, isTrim, ignoreEmpty);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓诧紝澶у皬鍐欐晱鎰燂紝鍘婚櫎姣忎釜鍏冪礌涓よ竟绌虹櫧绗�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @param limit 闄愬埗鍒嗙墖鏁帮紝-1涓嶉檺鍒�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.0.8
+ */
+ public static List<String> splitTrim(String str, char separator, int limit, boolean ignoreEmpty) {
+ return split(str, separator, limit, true, ignoreEmpty, false);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓诧紝澶у皬鍐欐晱鎰�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @param limit 闄愬埗鍒嗙墖鏁帮紝-1涓嶉檺鍒�
+ * @param isTrim 鏄惁鍘婚櫎鍒囧垎瀛楃涓插悗姣忎釜鍏冪礌涓よ竟鐨勭┖鏍�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.0.8
+ */
+ public static List<String> split(String str, char separator, int limit, boolean isTrim, boolean ignoreEmpty) {
+ return split(str, separator, limit, isTrim, ignoreEmpty, false);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓诧紝蹇界暐澶у皬鍐�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @param limit 闄愬埗鍒嗙墖鏁帮紝-1涓嶉檺鍒�
+ * @param isTrim 鏄惁鍘婚櫎鍒囧垎瀛楃涓插悗姣忎釜鍏冪礌涓よ竟鐨勭┖鏍�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.2.1
+ */
+ public static List<String> splitIgnoreCase(String str, char separator, int limit, boolean isTrim, boolean ignoreEmpty) {
+ return split(str, separator, limit, isTrim, ignoreEmpty, true);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @param limit 闄愬埗鍒嗙墖鏁帮紝-1涓嶉檺鍒�
+ * @param isTrim 鏄惁鍘婚櫎鍒囧垎瀛楃涓插悗姣忎釜鍏冪礌涓よ竟鐨勭┖鏍�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @param ignoreCase 鏄惁蹇界暐澶у皬鍐�
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.2.1
+ */
+ public static List<String> split(String str, char separator, int limit, boolean isTrim, boolean ignoreEmpty, boolean ignoreCase) {
+ if (StringUtil.isEmpty(str)) {
+ return new ArrayList<String>(0);
+ }
+ if (limit == 1) {
+ return addToList(new ArrayList<String>(1), str, isTrim, ignoreEmpty);
+ }
+
+ final ArrayList<String> list = new ArrayList<>(limit > 0 ? limit : 16);
+ int len = str.length();
+ int start = 0;
+ for (int i = 0; i < len; i++) {
+ if (Func.equals(separator, str.charAt(i))) {
+ addToList(list, str.substring(start, i), isTrim, ignoreEmpty);
+ start = i + 1;
+
+ //妫�鏌ユ槸鍚﹁秴鍑鸿寖鍥达紙鏈�澶у厑璁竘imit-1涓紝鍓╀笅涓�涓暀缁欐湯灏惧瓧绗︿覆锛�
+ if (limit > 0 && list.size() > limit - 2) {
+ break;
+ }
+ }
+ }
+ return addToList(list, str.substring(start, len), isTrim, ignoreEmpty);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓蹭负瀛楃涓叉暟缁�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @param limit 闄愬埗鍒嗙墖鏁�
+ * @param isTrim 鏄惁鍘婚櫎鍒囧垎瀛楃涓插悗姣忎釜鍏冪礌涓よ竟鐨勭┖鏍�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.0.8
+ */
+ public static String[] splitToArray(String str, char separator, int limit, boolean isTrim, boolean ignoreEmpty) {
+ return toArray(split(str, separator, limit, isTrim, ignoreEmpty));
+ }
+
+ //---------------------------------------------------------------------------------------------- Split by String
+
+ /**
+ * 鍒囧垎瀛楃涓诧紝涓嶅拷鐣ュぇ灏忓啓
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗︿覆
+ * @param isTrim 鏄惁鍘婚櫎鍒囧垎瀛楃涓插悗姣忎釜鍏冪礌涓よ竟鐨勭┖鏍�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.0.8
+ */
+ public static List<String> split(String str, String separator, boolean isTrim, boolean ignoreEmpty) {
+ return split(str, separator, -1, isTrim, ignoreEmpty, false);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓诧紝鍘婚櫎姣忎釜鍏冪礌涓よ竟绌烘牸锛屽拷鐣ュぇ灏忓啓
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗︿覆
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.2.1
+ */
+ public static List<String> splitTrim(String str, String separator, boolean ignoreEmpty) {
+ return split(str, separator, true, ignoreEmpty);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓诧紝涓嶅拷鐣ュぇ灏忓啓
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗︿覆
+ * @param limit 闄愬埗鍒嗙墖鏁�
+ * @param isTrim 鏄惁鍘婚櫎鍒囧垎瀛楃涓插悗姣忎釜鍏冪礌涓よ竟鐨勭┖鏍�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.0.8
+ */
+ public static List<String> split(String str, String separator, int limit, boolean isTrim, boolean ignoreEmpty) {
+ return split(str, separator, limit, isTrim, ignoreEmpty, false);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓诧紝鍘婚櫎姣忎釜鍏冪礌涓よ竟绌烘牸锛屽拷鐣ュぇ灏忓啓
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗︿覆
+ * @param limit 闄愬埗鍒嗙墖鏁�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.2.1
+ */
+ public static List<String> splitTrim(String str, String separator, int limit, boolean ignoreEmpty) {
+ return split(str, separator, limit, true, ignoreEmpty);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓诧紝蹇界暐澶у皬鍐�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗︿覆
+ * @param limit 闄愬埗鍒嗙墖鏁�
+ * @param isTrim 鏄惁鍘婚櫎鍒囧垎瀛楃涓插悗姣忎釜鍏冪礌涓よ竟鐨勭┖鏍�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.2.1
+ */
+ public static List<String> splitIgnoreCase(String str, String separator, int limit, boolean isTrim, boolean ignoreEmpty) {
+ return split(str, separator, limit, isTrim, ignoreEmpty, true);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓诧紝鍘婚櫎姣忎釜鍏冪礌涓よ竟绌烘牸锛屽拷鐣ュぇ灏忓啓
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗︿覆
+ * @param limit 闄愬埗鍒嗙墖鏁�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.2.1
+ */
+ public static List<String> splitTrimIgnoreCase(String str, String separator, int limit, boolean ignoreEmpty) {
+ return split(str, separator, limit, true, ignoreEmpty, true);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗︿覆
+ * @param limit 闄愬埗鍒嗙墖鏁�
+ * @param isTrim 鏄惁鍘婚櫎鍒囧垎瀛楃涓插悗姣忎釜鍏冪礌涓よ竟鐨勭┖鏍�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @param ignoreCase 鏄惁蹇界暐澶у皬鍐�
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.2.1
+ */
+ public static List<String> split(String str, String separator, int limit, boolean isTrim, boolean ignoreEmpty, boolean ignoreCase) {
+ if (StringUtil.isEmpty(str)) {
+ return new ArrayList<String>(0);
+ }
+ if (limit == 1) {
+ return addToList(new ArrayList<String>(1), str, isTrim, ignoreEmpty);
+ }
+
+ if (StringUtil.isEmpty(separator)) {
+ return split(str, limit);
+ } else if (separator.length() == 1) {
+ return split(str, separator.charAt(0), limit, isTrim, ignoreEmpty, ignoreCase);
+ }
+
+ final ArrayList<String> list = new ArrayList<>();
+ int len = str.length();
+ int separatorLen = separator.length();
+ int start = 0;
+ int i = 0;
+ while (i < len) {
+ i = StringUtil.indexOf(str, separator, start, ignoreCase);
+ if (i > -1) {
+ addToList(list, str.substring(start, i), isTrim, ignoreEmpty);
+ start = i + separatorLen;
+
+ //妫�鏌ユ槸鍚﹁秴鍑鸿寖鍥达紙鏈�澶у厑璁竘imit-1涓紝鍓╀笅涓�涓暀缁欐湯灏惧瓧绗︿覆锛�
+ if (limit > 0 && list.size() > limit - 2) {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ return addToList(list, str.substring(start, len), isTrim, ignoreEmpty);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓蹭负瀛楃涓叉暟缁�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @param limit 闄愬埗鍒嗙墖鏁�
+ * @param isTrim 鏄惁鍘婚櫎鍒囧垎瀛楃涓插悗姣忎釜鍏冪礌涓よ竟鐨勭┖鏍�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.0.8
+ */
+ public static String[] splitToArray(String str, String separator, int limit, boolean isTrim, boolean ignoreEmpty) {
+ return toArray(split(str, separator, limit, isTrim, ignoreEmpty));
+ }
+
+ //---------------------------------------------------------------------------------------------- Split by Whitespace
+
+ /**
+ * 浣跨敤绌虹櫧绗﹀垏鍒嗗瓧绗︿覆<br>
+ * 鍒囧垎鍚庣殑瀛楃涓蹭袱杈逛笉鍖呭惈绌虹櫧绗︼紝绌轰覆鎴栫┖鐧界涓插苟涓嶅仛涓哄厓绱犱箣涓�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param limit 闄愬埗鍒嗙墖鏁�
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.0.8
+ */
+ public static List<String> split(String str, int limit) {
+ if (StringUtil.isEmpty(str)) {
+ return new ArrayList<String>(0);
+ }
+ if (limit == 1) {
+ return addToList(new ArrayList<String>(1), str, true, true);
+ }
+
+ final ArrayList<String> list = new ArrayList<>();
+ int len = str.length();
+ int start = 0;
+ for (int i = 0; i < len; i++) {
+ if (Func.isEmpty(str.charAt(i))) {
+ addToList(list, str.substring(start, i), true, true);
+ start = i + 1;
+ if (limit > 0 && list.size() > limit - 2) {
+ break;
+ }
+ }
+ }
+ return addToList(list, str.substring(start, len), true, true);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓蹭负瀛楃涓叉暟缁�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param limit 闄愬埗鍒嗙墖鏁�
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.0.8
+ */
+ public static String[] splitToArray(String str, int limit) {
+ return toArray(split(str, limit));
+ }
+
+ //---------------------------------------------------------------------------------------------- Split by regex
+
+ /**
+ * 閫氳繃姝e垯鍒囧垎瀛楃涓�
+ *
+ * @param str 瀛楃涓�
+ * @param separatorPattern 鍒嗛殧绗︽鍒檣@link Pattern}
+ * @param limit 闄愬埗鍒嗙墖鏁�
+ * @param isTrim 鏄惁鍘婚櫎鍒囧垎瀛楃涓插悗姣忎釜鍏冪礌涓よ竟鐨勭┖鏍�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.0.8
+ */
+ public static List<String> split(String str, Pattern separatorPattern, int limit, boolean isTrim, boolean ignoreEmpty) {
+ if (StringUtil.isEmpty(str)) {
+ return new ArrayList<String>(0);
+ }
+ if (limit == 1) {
+ return addToList(new ArrayList<String>(1), str, isTrim, ignoreEmpty);
+ }
+
+ if (null == separatorPattern) {
+ return split(str, limit);
+ }
+
+ final Matcher matcher = separatorPattern.matcher(str);
+ final ArrayList<String> list = new ArrayList<>();
+ int len = str.length();
+ int start = 0;
+ while (matcher.find()) {
+ addToList(list, str.substring(start, matcher.start()), isTrim, ignoreEmpty);
+ start = matcher.end();
+
+ if (limit > 0 && list.size() > limit - 2) {
+ break;
+ }
+ }
+ return addToList(list, str.substring(start, len), isTrim, ignoreEmpty);
+ }
+
+ /**
+ * 閫氳繃姝e垯鍒囧垎瀛楃涓蹭负瀛楃涓叉暟缁�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separatorPattern 鍒嗛殧绗︽鍒檣@link Pattern}
+ * @param limit 闄愬埗鍒嗙墖鏁�
+ * @param isTrim 鏄惁鍘婚櫎鍒囧垎瀛楃涓插悗姣忎釜鍏冪礌涓よ竟鐨勭┖鏍�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.0.8
+ */
+ public static String[] splitToArray(String str, Pattern separatorPattern, int limit, boolean isTrim, boolean ignoreEmpty) {
+ return toArray(split(str, separatorPattern, limit, isTrim, ignoreEmpty));
+ }
+
+ //---------------------------------------------------------------------------------------------- Split by length
+
+ /**
+ * 鏍规嵁缁欏畾闀垮害锛屽皢缁欏畾瀛楃涓叉埅鍙栦负澶氫釜閮ㄥ垎
+ *
+ * @param str 瀛楃涓�
+ * @param len 姣忎竴涓皬鑺傜殑闀垮害
+ * @return 鎴彇鍚庣殑瀛楃涓叉暟缁�
+ */
+ public static String[] splitByLength(String str, int len) {
+ int partCount = str.length() / len;
+ int lastPartCount = str.length() % len;
+ int fixPart = 0;
+ if (lastPartCount != 0) {
+ fixPart = 1;
+ }
+
+ final String[] strs = new String[partCount + fixPart];
+ for (int i = 0; i < partCount + fixPart; i++) {
+ if (i == partCount + fixPart - 1 && lastPartCount != 0) {
+ strs[i] = str.substring(i * len, i * len + lastPartCount);
+ } else {
+ strs[i] = str.substring(i * len, i * len + len);
+ }
+ }
+ return strs;
+ }
+
+ //---------------------------------------------------------------------------------------------------------- Private method start
+
+ /**
+ * 灏嗗瓧绗︿覆鍔犲叆List涓�
+ *
+ * @param list 鍒楄〃
+ * @param part 琚姞鍏ョ殑閮ㄥ垎
+ * @param isTrim 鏄惁鍘婚櫎涓ょ绌虹櫧绗�
+ * @param ignoreEmpty 鏄惁鐣ヨ繃绌哄瓧绗︿覆锛堢┖瀛楃涓蹭笉鍋氫负涓�涓厓绱狅級
+ * @return 鍒楄〃
+ */
+ private static List<String> addToList(List<String> list, String part, boolean isTrim, boolean ignoreEmpty) {
+ part = part.toString();
+ if (isTrim) {
+ part = part.trim();
+ }
+ if (false == ignoreEmpty || false == part.isEmpty()) {
+ list.add(part);
+ }
+ return list;
+ }
+
+ /**
+ * List杞珹rray
+ *
+ * @param list List
+ * @return Array
+ */
+ private static String[] toArray(List<String> list) {
+ return list.toArray(new String[list.size()]);
+ }
+ //---------------------------------------------------------------------------------------------------------- Private method end
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/Try.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/Try.java
new file mode 100644
index 0000000..c453b50
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/support/Try.java
@@ -0,0 +1,88 @@
+package org.springblade.core.tool.support;
+
+import org.springblade.core.tool.utils.Exceptions;
+import org.springframework.lang.Nullable;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * Lambda 鍙楁寮傚父澶勭悊
+ * https://segmentfault.com/a/1190000007832130
+ *
+ * @author Chill
+ */
+public class Try {
+
+ public static <T, R> Function<T, R> of(UncheckedFunction<T, R> mapper) {
+ Objects.requireNonNull(mapper);
+ return t -> {
+ try {
+ return mapper.apply(t);
+ } catch (Exception e) {
+ throw Exceptions.unchecked(e);
+ }
+ };
+ }
+
+ public static <T> Consumer<T> of(UncheckedConsumer<T> mapper) {
+ Objects.requireNonNull(mapper);
+ return t -> {
+ try {
+ mapper.accept(t);
+ } catch (Exception e) {
+ throw Exceptions.unchecked(e);
+ }
+ };
+ }
+
+ public static <T> Supplier<T> of(UncheckedSupplier<T> mapper) {
+ Objects.requireNonNull(mapper);
+ return () -> {
+ try {
+ return mapper.get();
+ } catch (Exception e) {
+ throw Exceptions.unchecked(e);
+ }
+ };
+ }
+
+ @FunctionalInterface
+ public interface UncheckedFunction<T, R> {
+ /**
+ * apply
+ *
+ * @param t
+ * @return
+ * @throws Exception
+ */
+ @Nullable
+ R apply(@Nullable T t) throws Exception;
+ }
+
+ @FunctionalInterface
+ public interface UncheckedConsumer<T> {
+ /**
+ * accept
+ *
+ * @param t
+ * @throws Exception
+ */
+ @Nullable
+ void accept(@Nullable T t) throws Exception;
+ }
+
+ @FunctionalInterface
+ public interface UncheckedSupplier<T> {
+ /**
+ * get
+ *
+ * @return
+ * @throws Exception
+ */
+ @Nullable
+ T get() throws Exception;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/tuple/KeyPair.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/tuple/KeyPair.java
new file mode 100644
index 0000000..ecc525f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/tuple/KeyPair.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.tuple;
+
+import lombok.RequiredArgsConstructor;
+import org.springblade.core.tool.utils.RsaUtil;
+
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+/**
+ * rsa 鐨� key pair 灏佽
+ *
+ * @author L.cm
+ */
+@RequiredArgsConstructor
+public class KeyPair {
+ private final java.security.KeyPair keyPair;
+
+ public PublicKey getPublic() {
+ return keyPair.getPublic();
+ }
+
+ public PrivateKey getPrivate() {
+ return keyPair.getPrivate();
+ }
+
+ public byte[] getPublicBytes() {
+ return this.getPublic().getEncoded();
+ }
+
+ public byte[] getPrivateBytes() {
+ return this.getPrivate().getEncoded();
+ }
+
+ public String getPublicBase64() {
+ return RsaUtil.getKeyString(this.getPublic());
+ }
+
+ public String getPrivateBase64() {
+ return RsaUtil.getKeyString(this.getPrivate());
+ }
+
+ @Override
+ public String toString() {
+ return "PublicKey=" + this.getPublicBase64() + '\n' + "PrivateKey=" + this.getPrivateBase64();
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/tuple/Pair.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/tuple/Pair.java
new file mode 100644
index 0000000..846c0ed
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/tuple/Pair.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.tuple;
+
+import lombok.*;
+
+/**
+ * tuple Pair
+ *
+ * @author L.cm
+ **/
+@Getter
+@ToString
+@EqualsAndHashCode
+public class Pair<L, R> {
+ private static final Pair<Object, Object> EMPTY = new Pair<>(null, null);
+
+ private final L left;
+ private final R right;
+
+ /**
+ * Returns an empty pair.
+ */
+ @SuppressWarnings("unchecked")
+ public static <L, R> Pair<L, R> empty() {
+ return (Pair<L, R>) EMPTY;
+ }
+
+ /**
+ * Constructs a pair with its left value being {@code left}, or returns an empty pair if
+ * {@code left} is null.
+ *
+ * @return the constructed pair or an empty pair if {@code left} is null.
+ */
+ public static <L, R> Pair<L, R> createLeft(L left) {
+ if (left == null) {
+ return empty();
+ } else {
+ return new Pair<>(left, null);
+ }
+ }
+
+ /**
+ * Constructs a pair with its right value being {@code right}, or returns an empty pair if
+ * {@code right} is null.
+ *
+ * @return the constructed pair or an empty pair if {@code right} is null.
+ */
+ public static <L, R> Pair<L, R> createRight(R right) {
+ if (right == null) {
+ return empty();
+ } else {
+ return new Pair<>(null, right);
+ }
+ }
+
+ public static <L, R> Pair<L, R> create(L left, R right) {
+ if (right == null && left == null) {
+ return empty();
+ } else {
+ return new Pair<>(left, right);
+ }
+ }
+
+ private Pair(L left, R right) {
+ this.left = left;
+ this.right = right;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/AesUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/AesUtil.java
new file mode 100644
index 0000000..69152ff
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/AesUtil.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * 瀹屽叏鍏煎寰俊鎵�浣跨敤鐨凙ES鍔犲瘑宸ュ叿绫�
+ * aes鐨刱ey蹇呴』鏄�256byte闀匡紙姣斿32涓瓧绗︼級锛屽彲浠ヤ娇鐢ˋesKit.genAesKey()鏉ョ敓鎴愪竴缁刱ey
+ *
+ * @author L.cm
+ */
+public class AesUtil {
+
+ public static final Charset DEFAULT_CHARSET = Charsets.UTF_8;
+
+ /**
+ * 鑾峰彇瀵嗛挜
+ *
+ * @return {String}
+ */
+ public static String genAesKey() {
+ return StringUtil.random(32);
+ }
+
+ /**
+ * 鍔犲瘑
+ *
+ * @param content 鏂囨湰鍐呭
+ * @param aesTextKey 鏂囨湰瀵嗛挜
+ * @return byte[]
+ */
+ public static byte[] encrypt(String content, String aesTextKey) {
+ return encrypt(content.getBytes(DEFAULT_CHARSET), aesTextKey);
+ }
+
+ /**
+ * 鍔犲瘑
+ *
+ * @param content 鏂囨湰鍐呭
+ * @param charset 缂栫爜
+ * @param aesTextKey 鏂囨湰瀵嗛挜
+ * @return byte[]
+ */
+ public static byte[] encrypt(String content, Charset charset, String aesTextKey) {
+ return encrypt(content.getBytes(charset), aesTextKey);
+ }
+
+ /**
+ * 鍔犲瘑
+ *
+ * @param content 鍐呭
+ * @param aesTextKey 鏂囨湰瀵嗛挜
+ * @return byte[]
+ */
+ public static byte[] encrypt(byte[] content, String aesTextKey) {
+ return encrypt(content, Objects.requireNonNull(aesTextKey).getBytes(DEFAULT_CHARSET));
+ }
+
+ /**
+ * hex鍔犲瘑
+ *
+ * @param content 鏂囨湰鍐呭
+ * @param aesTextKey 鏂囨湰瀵嗛挜
+ * @return {String}
+ */
+ public static String encryptToHex(String content, String aesTextKey) {
+ return HexUtil.encodeToString(encrypt(content, aesTextKey));
+ }
+
+ /**
+ * hex鍔犲瘑
+ *
+ * @param content 鍐呭
+ * @param aesTextKey 鏂囨湰瀵嗛挜
+ * @return {String}
+ */
+ public static String encryptToHex(byte[] content, String aesTextKey) {
+ return HexUtil.encodeToString(encrypt(content, aesTextKey));
+ }
+
+ /**
+ * Base64鍔犲瘑
+ *
+ * @param content 鏂囨湰鍐呭
+ * @param aesTextKey 鏂囨湰瀵嗛挜
+ * @return {String}
+ */
+ public static String encryptToBase64(String content, String aesTextKey) {
+ return Base64Util.encodeToString(encrypt(content, aesTextKey));
+ }
+
+ /**
+ * Base64鍔犲瘑
+ *
+ * @param content 鍐呭
+ * @param aesTextKey 鏂囨湰瀵嗛挜
+ * @return {String}
+ */
+ public static String encryptToBase64(byte[] content, String aesTextKey) {
+ return Base64Util.encodeToString(encrypt(content, aesTextKey));
+ }
+
+ /**
+ * hex瑙e瘑
+ *
+ * @param content 鏂囨湰鍐呭
+ * @param aesTextKey 鏂囨湰瀵嗛挜
+ * @return {String}
+ */
+ @Nullable
+ public static String decryptFormHexToString(@Nullable String content, String aesTextKey) {
+ byte[] hexBytes = decryptFormHex(content, aesTextKey);
+ if (hexBytes == null) {
+ return null;
+ }
+ return new String(hexBytes, DEFAULT_CHARSET);
+ }
+
+ /**
+ * hex瑙e瘑
+ *
+ * @param content 鏂囨湰鍐呭
+ * @param aesTextKey 鏂囨湰瀵嗛挜
+ * @return byte[]
+ */
+ @Nullable
+ public static byte[] decryptFormHex(@Nullable String content, String aesTextKey) {
+ if (StringUtil.isBlank(content)) {
+ return null;
+ }
+ return decryptFormHex(content.getBytes(DEFAULT_CHARSET), aesTextKey);
+ }
+
+ /**
+ * hex瑙e瘑
+ *
+ * @param content 鍐呭
+ * @param aesTextKey 鏂囨湰瀵嗛挜
+ * @return byte[]
+ */
+ public static byte[] decryptFormHex(byte[] content, String aesTextKey) {
+ return decrypt(HexUtil.decode(content), aesTextKey);
+ }
+
+ /**
+ * Base64瑙e瘑
+ *
+ * @param content 鏂囨湰鍐呭
+ * @param aesTextKey 鏂囨湰瀵嗛挜
+ * @return {String}
+ */
+ @Nullable
+ public static String decryptFormBase64ToString(@Nullable String content, String aesTextKey) {
+ byte[] hexBytes = decryptFormBase64(content, aesTextKey);
+ if (hexBytes == null) {
+ return null;
+ }
+ return new String(hexBytes, DEFAULT_CHARSET);
+ }
+
+ /**
+ * Base64瑙e瘑
+ *
+ * @param content 鏂囨湰鍐呭
+ * @param aesTextKey 鏂囨湰瀵嗛挜
+ * @return byte[]
+ */
+ @Nullable
+ public static byte[] decryptFormBase64(@Nullable String content, String aesTextKey) {
+ if (StringUtil.isBlank(content)) {
+ return null;
+ }
+ return decryptFormBase64(content.getBytes(DEFAULT_CHARSET), aesTextKey);
+ }
+
+ /**
+ * Base64瑙e瘑
+ *
+ * @param content 鍐呭
+ * @param aesTextKey 鏂囨湰瀵嗛挜
+ * @return byte[]
+ */
+ public static byte[] decryptFormBase64(byte[] content, String aesTextKey) {
+ return decrypt(Base64Util.decode(content), aesTextKey);
+ }
+
+ /**
+ * 瑙e瘑
+ *
+ * @param content 鍐呭
+ * @param aesTextKey 鏂囨湰瀵嗛挜
+ * @return {String}
+ */
+ public static String decryptToString(byte[] content, String aesTextKey) {
+ return new String(decrypt(content, aesTextKey), DEFAULT_CHARSET);
+ }
+
+ /**
+ * 瑙e瘑
+ *
+ * @param content 鍐呭
+ * @param aesTextKey 鏂囨湰瀵嗛挜
+ * @return byte[]
+ */
+ public static byte[] decrypt(byte[] content, String aesTextKey) {
+ return decrypt(content, Objects.requireNonNull(aesTextKey).getBytes(DEFAULT_CHARSET));
+ }
+
+ /**
+ * 瑙e瘑
+ *
+ * @param content 鍐呭
+ * @param aesKey 瀵嗛挜
+ * @return byte[]
+ */
+ public static byte[] encrypt(byte[] content, byte[] aesKey) {
+ return aes(Pkcs7Encoder.encode(content), aesKey, Cipher.ENCRYPT_MODE);
+ }
+
+ /**
+ * 鍔犲瘑
+ *
+ * @param encrypted 鍐呭
+ * @param aesKey 瀵嗛挜
+ * @return byte[]
+ */
+ public static byte[] decrypt(byte[] encrypted, byte[] aesKey) {
+ return Pkcs7Encoder.decode(aes(encrypted, aesKey, Cipher.DECRYPT_MODE));
+ }
+
+ /**
+ * ase鍔犲瘑
+ *
+ * @param encrypted 鍐呭
+ * @param aesKey 瀵嗛挜
+ * @param mode 妯″紡
+ * @return byte[]
+ */
+ private static byte[] aes(byte[] encrypted, byte[] aesKey, int mode) {
+ Assert.isTrue(aesKey.length == 32, "IllegalAesKey, aesKey's length must be 32");
+ try {
+ Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
+ SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
+ IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
+ cipher.init(mode, keySpec, iv);
+ return cipher.doFinal(encrypted);
+ } catch (Exception e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 鎻愪緵鍩轰簬PKCS7绠楁硶鐨勫姞瑙e瘑鎺ュ彛.
+ */
+ private static class Pkcs7Encoder {
+ private static final int BLOCK_SIZE = 32;
+
+ private static byte[] encode(byte[] src) {
+ int count = src.length;
+ // 璁$畻闇�瑕佸~鍏呯殑浣嶆暟
+ int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
+ // 鑾峰緱琛ヤ綅鎵�鐢ㄧ殑瀛楃
+ byte pad = (byte) (amountToPad & 0xFF);
+ byte[] pads = new byte[amountToPad];
+ for (int index = 0; index < amountToPad; index++) {
+ pads[index] = pad;
+ }
+ int length = count + amountToPad;
+ byte[] dest = new byte[length];
+ System.arraycopy(src, 0, dest, 0, count);
+ System.arraycopy(pads, 0, dest, count, amountToPad);
+ return dest;
+ }
+
+ private static byte[] decode(byte[] decrypted) {
+ int pad = decrypted[decrypted.length - 1];
+ if (pad < 1 || pad > BLOCK_SIZE) {
+ pad = 0;
+ }
+ if (pad > 0) {
+ return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
+ }
+ return decrypted;
+ }
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/AntPathFilter.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/AntPathFilter.java
new file mode 100644
index 0000000..612cea5
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/AntPathFilter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.utils;
+
+import lombok.AllArgsConstructor;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.PathMatcher;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.Serializable;
+
+/**
+ * Spring AntPath 瑙勫垯鏂囦欢杩囨护
+ *
+ * @author L.cm
+ */
+@AllArgsConstructor
+public class AntPathFilter implements FileFilter, Serializable {
+ private static final long serialVersionUID = 812598009067554612L;
+ private static final PathMatcher PATH_MATCHER = new AntPathMatcher();
+
+ private final String pattern;
+
+ /**
+ * 杩囨护瑙勫垯
+ *
+ * @param pathname 璺緞
+ * @return boolean
+ */
+ @Override
+ public boolean accept(File pathname) {
+ String filePath = pathname.getAbsolutePath();
+ return PATH_MATCHER.match(pattern, filePath);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Base64Util.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Base64Util.java
new file mode 100644
index 0000000..cfd4520
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Base64Util.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+/**
+ * Base64宸ュ叿
+ *
+ * @author L.cm
+ */
+public class Base64Util extends org.springframework.util.Base64Utils {
+
+ /**
+ * 缂栫爜
+ *
+ * @param value 瀛楃涓�
+ * @return {String}
+ */
+ public static String encode(String value) {
+ return Base64Util.encode(value, Charsets.UTF_8);
+ }
+
+ /**
+ * 缂栫爜
+ *
+ * @param value 瀛楃涓�
+ * @param charset 瀛楃闆�
+ * @return {String}
+ */
+ public static String encode(String value, java.nio.charset.Charset charset) {
+ byte[] val = value.getBytes(charset);
+ return new String(Base64Util.encode(val), charset);
+ }
+
+ /**
+ * 缂栫爜URL瀹夊叏
+ *
+ * @param value 瀛楃涓�
+ * @return {String}
+ */
+ public static String encodeUrlSafe(String value) {
+ return Base64Util.encodeUrlSafe(value, Charsets.UTF_8);
+ }
+
+ /**
+ * 缂栫爜URL瀹夊叏
+ *
+ * @param value 瀛楃涓�
+ * @param charset 瀛楃闆�
+ * @return {String}
+ */
+ public static String encodeUrlSafe(String value, java.nio.charset.Charset charset) {
+ byte[] val = value.getBytes(charset);
+ return new String(Base64Util.encodeUrlSafe(val), charset);
+ }
+
+ /**
+ * 瑙g爜
+ *
+ * @param value 瀛楃涓�
+ * @return {String}
+ */
+ public static String decode(String value) {
+ return Base64Util.decode(value, Charsets.UTF_8);
+ }
+
+ /**
+ * 瑙g爜
+ *
+ * @param value 瀛楃涓�
+ * @param charset 瀛楃闆�
+ * @return {String}
+ */
+ public static String decode(String value, java.nio.charset.Charset charset) {
+ byte[] val = value.getBytes(charset);
+ byte[] decodedValue = Base64Util.decode(val);
+ return new String(decodedValue, charset);
+ }
+
+ /**
+ * 瑙g爜URL瀹夊叏
+ *
+ * @param value 瀛楃涓�
+ * @return {String}
+ */
+ public static String decodeUrlSafe(String value) {
+ return Base64Util.decodeUrlSafe(value, Charsets.UTF_8);
+ }
+
+ /**
+ * 瑙g爜URL瀹夊叏
+ *
+ * @param value 瀛楃涓�
+ * @param charset 瀛楃闆�
+ * @return {String}
+ */
+ public static String decodeUrlSafe(String value, java.nio.charset.Charset charset) {
+ byte[] val = value.getBytes(charset);
+ byte[] decodedValue = Base64Util.decodeUrlSafe(val);
+ return new String(decodedValue, charset);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/BeanUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/BeanUtil.java
new file mode 100644
index 0000000..26abd4c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/BeanUtil.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+
+import org.springblade.core.tool.beans.BeanProperty;
+import org.springblade.core.tool.beans.BladeBeanCopier;
+import org.springblade.core.tool.beans.BladeBeanMap;
+import org.springblade.core.tool.convert.BladeConverter;
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.PropertyAccessorFactory;
+import org.springframework.cglib.beans.BeanGenerator;
+import org.springframework.lang.Nullable;
+
+import java.util.*;
+
+/**
+ * 瀹炰綋宸ュ叿绫�
+ *
+ * @author L.cm
+ */
+public class BeanUtil extends org.springframework.beans.BeanUtils {
+
+ /**
+ * 瀹炰緥鍖栧璞�
+ *
+ * @param clazz 绫�
+ * @param <T> 娉涘瀷鏍囪
+ * @return 瀵硅薄
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T newInstance(Class<?> clazz) {
+ return (T) instantiateClass(clazz);
+ }
+
+ /**
+ * 瀹炰緥鍖栧璞�
+ *
+ * @param clazzStr 绫诲悕
+ * @param <T> 娉涘瀷鏍囪
+ * @return 瀵硅薄
+ */
+ public static <T> T newInstance(String clazzStr) {
+ try {
+ Class<?> clazz = ClassUtil.forName(clazzStr, null);
+ return newInstance(clazz);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 鑾峰彇Bean鐨勫睘鎬�, 鏀寔 propertyName 澶氱骇 锛歵est.user.name
+ *
+ * @param bean bean
+ * @param propertyName 灞炴�у悕
+ * @return 灞炴�у��
+ */
+ @Nullable
+ public static Object getProperty(@Nullable Object bean, String propertyName) {
+ if (bean == null) {
+ return null;
+ }
+ BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean);
+ return beanWrapper.getPropertyValue(propertyName);
+ }
+
+ /**
+ * 璁剧疆Bean灞炴��, 鏀寔 propertyName 澶氱骇 锛歵est.user.name
+ *
+ * @param bean bean
+ * @param propertyName 灞炴�у悕
+ * @param value 灞炴�у��
+ */
+ public static void setProperty(Object bean, String propertyName, Object value) {
+ Objects.requireNonNull(bean, "bean Could not null");
+ BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean);
+ beanWrapper.setPropertyValue(propertyName, value);
+ }
+
+ /**
+ * 娣卞鍒�
+ *
+ * <p>
+ * 鏀寔 map bean
+ * </p>
+ *
+ * @param source 婧愬璞�
+ * @param <T> 娉涘瀷鏍囪
+ * @return T
+ */
+ @SuppressWarnings("unchecked")
+ @Nullable
+ public static <T> T clone(@Nullable T source) {
+ if (source == null) {
+ return null;
+ }
+ return (T) BeanUtil.copy(source, source.getClass());
+ }
+
+ /**
+ * copy 瀵硅薄灞炴�э紝榛樿涓嶄娇鐢–onvert
+ *
+ * <p>
+ * 鏀寔 map bean copy
+ * </p>
+ *
+ * @param source 婧愬璞�
+ * @param clazz 绫诲悕
+ * @param <T> 娉涘瀷鏍囪
+ * @return T
+ */
+ @Nullable
+ public static <T> T copy(@Nullable Object source, Class<T> clazz) {
+ if (source == null) {
+ return null;
+ }
+ return BeanUtil.copy(source, source.getClass(), clazz);
+ }
+
+ /**
+ * copy 瀵硅薄灞炴�э紝榛樿涓嶄娇鐢–onvert
+ *
+ * <p>
+ * 鏀寔 map bean copy
+ * </p>
+ *
+ * @param source 婧愬璞�
+ * @param sourceClazz 婧愮被鍨�
+ * @param targetClazz 杞崲鎴愮殑绫诲瀷
+ * @param <T> 娉涘瀷鏍囪
+ * @return T
+ */
+ @Nullable
+ public static <T> T copy(@Nullable Object source, Class sourceClazz, Class<T> targetClazz) {
+ if (source == null) {
+ return null;
+ }
+ BladeBeanCopier copier = BladeBeanCopier.create(sourceClazz, targetClazz, false);
+ T to = newInstance(targetClazz);
+ copier.copy(source, to, null);
+ return to;
+ }
+
+ /**
+ * copy 鍒楄〃瀵硅薄锛岄粯璁や笉浣跨敤Convert
+ *
+ * <p>
+ * 鏀寔 map bean copy
+ * </p>
+ *
+ * @param sourceList 婧愬垪琛�
+ * @param targetClazz 杞崲鎴愮殑绫诲瀷
+ * @param <T> 娉涘瀷鏍囪
+ * @return T
+ */
+ public static <T> List<T> copy(@Nullable Collection<?> sourceList, Class<T> targetClazz) {
+ if (sourceList == null || sourceList.isEmpty()) {
+ return Collections.emptyList();
+ }
+ List<T> outList = new ArrayList<>(sourceList.size());
+ Class<?> sourceClazz = null;
+ for (Object source : sourceList) {
+ if (source == null) {
+ continue;
+ }
+ if (sourceClazz == null) {
+ sourceClazz = source.getClass();
+ }
+ T bean = BeanUtil.copy(source, sourceClazz, targetClazz);
+ outList.add(bean);
+ }
+ return outList;
+ }
+
+ /**
+ * 鎷疯礉瀵硅薄
+ *
+ * <p>
+ * 鏀寔 map bean copy
+ * </p>
+ *
+ * @param source 婧愬璞�
+ * @param targetBean 闇�瑕佽祴鍊肩殑瀵硅薄
+ */
+ public static void copy(@Nullable Object source, @Nullable Object targetBean) {
+ if (source == null || targetBean == null) {
+ return;
+ }
+ BladeBeanCopier copier = BladeBeanCopier
+ .create(source.getClass(), targetBean.getClass(), false);
+
+ copier.copy(source, targetBean, null);
+ }
+
+ /**
+ * 鎷疯礉瀵硅薄锛宻ource 灞炴�у仛 null 鍒ゆ柇锛孧ap 涓嶆敮鎸侊紝map 浼氬仛 instanceof 鍒ゆ柇锛屼笉浼�
+ *
+ * <p>
+ * 鏀寔 bean copy
+ * </p>
+ *
+ * @param source 婧愬璞�
+ * @param targetBean 闇�瑕佽祴鍊肩殑瀵硅薄
+ */
+ public static void copyNonNull(@Nullable Object source, @Nullable Object targetBean) {
+ if (source == null || targetBean == null) {
+ return;
+ }
+ BladeBeanCopier copier = BladeBeanCopier
+ .create(source.getClass(), targetBean.getClass(), false, true);
+
+ copier.copy(source, targetBean, null);
+ }
+
+ /**
+ * 鎷疯礉瀵硅薄骞跺涓嶅悓绫诲瀷灞炴�ц繘琛岃浆鎹�
+ *
+ * <p>
+ * 鏀寔 map bean copy
+ * </p>
+ *
+ * @param source 婧愬璞�
+ * @param targetClazz 杞崲鎴愮殑绫�
+ * @param <T> 娉涘瀷鏍囪
+ * @return T
+ */
+ @Nullable
+ public static <T> T copyWithConvert(@Nullable Object source, Class<T> targetClazz) {
+ if (source == null) {
+ return null;
+ }
+ return BeanUtil.copyWithConvert(source, source.getClass(), targetClazz);
+ }
+
+ /**
+ * 鎷疯礉瀵硅薄骞跺涓嶅悓绫诲瀷灞炴�ц繘琛岃浆鎹�
+ *
+ * <p>
+ * 鏀寔 map bean copy
+ * </p>
+ *
+ * @param source 婧愬璞�
+ * @param sourceClazz 婧愮被
+ * @param targetClazz 杞崲鎴愮殑绫�
+ * @param <T> 娉涘瀷鏍囪
+ * @return T
+ */
+ @Nullable
+ public static <T> T copyWithConvert(@Nullable Object source, Class<?> sourceClazz, Class<T> targetClazz) {
+ if (source == null) {
+ return null;
+ }
+ BladeBeanCopier copier = BladeBeanCopier.create(sourceClazz, targetClazz, true);
+ T to = newInstance(targetClazz);
+ copier.copy(source, to, new BladeConverter(sourceClazz, targetClazz));
+ return to;
+ }
+
+ /**
+ * 鎷疯礉鍒楄〃骞跺涓嶅悓绫诲瀷灞炴�ц繘琛岃浆鎹�
+ *
+ * <p>
+ * 鏀寔 map bean copy
+ * </p>
+ *
+ * @param sourceList 婧愬璞″垪琛�
+ * @param targetClazz 杞崲鎴愮殑绫�
+ * @param <T> 娉涘瀷鏍囪
+ * @return List
+ */
+ public static <T> List<T> copyWithConvert(@Nullable Collection<?> sourceList, Class<T> targetClazz) {
+ if (sourceList == null || sourceList.isEmpty()) {
+ return Collections.emptyList();
+ }
+ List<T> outList = new ArrayList<>(sourceList.size());
+ Class<?> sourceClazz = null;
+ for (Object source : sourceList) {
+ if (source == null) {
+ continue;
+ }
+ if (sourceClazz == null) {
+ sourceClazz = source.getClass();
+ }
+ T bean = BeanUtil.copyWithConvert(source, sourceClazz, targetClazz);
+ outList.add(bean);
+ }
+ return outList;
+ }
+
+ /**
+ * Copy the property values of the given source bean into the target class.
+ * <p>Note: The source and target classes do not have to match or even be derived
+ * from each other, as long as the properties match. Any bean properties that the
+ * source bean exposes but the target bean does not will silently be ignored.
+ * <p>This is just a convenience method. For more complex transfer needs,
+ *
+ * @param source the source bean
+ * @param targetClazz the target bean class
+ * @param <T> 娉涘瀷鏍囪
+ * @return T
+ * @throws BeansException if the copying failed
+ */
+ @Nullable
+ public static <T> T copyProperties(@Nullable Object source, Class<T> targetClazz) throws BeansException {
+ if (source == null) {
+ return null;
+ }
+ T to = newInstance(targetClazz);
+ BeanUtil.copyProperties(source, to);
+ return to;
+ }
+
+ /**
+ * Copy the property values of the given source bean into the target class.
+ * <p>Note: The source and target classes do not have to match or even be derived
+ * from each other, as long as the properties match. Any bean properties that the
+ * source bean exposes but the target bean does not will silently be ignored.
+ * <p>This is just a convenience method. For more complex transfer needs,
+ *
+ * @param sourceList the source list bean
+ * @param targetClazz the target bean class
+ * @param <T> 娉涘瀷鏍囪
+ * @return List
+ * @throws BeansException if the copying failed
+ */
+ public static <T> List<T> copyProperties(@Nullable Collection<?> sourceList, Class<T> targetClazz) throws BeansException {
+ if (sourceList == null || sourceList.isEmpty()) {
+ return Collections.emptyList();
+ }
+ List<T> outList = new ArrayList<>(sourceList.size());
+ for (Object source : sourceList) {
+ if (source == null) {
+ continue;
+ }
+ T bean = BeanUtil.copyProperties(source, targetClazz);
+ outList.add(bean);
+ }
+ return outList;
+ }
+
+ /**
+ * 灏嗗璞¤鎴恗ap褰㈠紡
+ *
+ * @param bean 婧愬璞�
+ * @return {Map}
+ */
+ @SuppressWarnings("unchecked")
+ public static Map<String, Object> toMap(@Nullable Object bean) {
+ if (bean == null) {
+ return new HashMap<>(0);
+ }
+ return BladeBeanMap.create(bean);
+ }
+
+ /**
+ * 灏唌ap 杞负 bean
+ *
+ * @param beanMap map
+ * @param valueType 瀵硅薄绫诲瀷
+ * @param <T> 娉涘瀷鏍囪
+ * @return {T}
+ */
+ public static <T> T toBean(Map<String, Object> beanMap, Class<T> valueType) {
+ Objects.requireNonNull(beanMap, "beanMap Could not null");
+ T to = newInstance(valueType);
+ if (beanMap.isEmpty()) {
+ return to;
+ }
+ BeanUtil.copy(beanMap, to);
+ return to;
+ }
+
+ /**
+ * 缁欎竴涓狟ean娣诲姞瀛楁
+ *
+ * @param superBean 鐖剁骇Bean
+ * @param props 鏂板灞炴��
+ * @return {Object}
+ */
+ @Nullable
+ public static Object generator(@Nullable Object superBean, BeanProperty... props) {
+ if (superBean == null) {
+ return null;
+ }
+ Class<?> superclass = superBean.getClass();
+ Object genBean = generator(superclass, props);
+ BeanUtil.copy(superBean, genBean);
+ return genBean;
+ }
+
+ /**
+ * 缁欎竴涓猚lass娣诲姞瀛楁
+ *
+ * @param superclass 鐖剁骇
+ * @param props 鏂板灞炴��
+ * @return {Object}
+ */
+ public static Object generator(Class<?> superclass, BeanProperty... props) {
+ BeanGenerator generator = new BeanGenerator();
+ generator.setSuperclass(superclass);
+ generator.setUseCache(true);
+ for (BeanProperty prop : props) {
+ generator.addProperty(prop.getName(), prop.getType());
+ }
+ return generator.create();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/CharPool.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/CharPool.java
new file mode 100644
index 0000000..036f6ea
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/CharPool.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+/**
+ * char 甯搁噺姹�
+ *
+ * @author L.cm
+ */
+public interface CharPool {
+
+ // @formatter:off
+ char UPPER_A = 'A';
+ char LOWER_A = 'a';
+ char UPPER_Z = 'Z';
+ char LOWER_Z = 'z';
+ char DOT = '.';
+ char AT = '@';
+ char LEFT_BRACE = '{';
+ char RIGHT_BRACE = '}';
+ char LEFT_BRACKET = '(';
+ char RIGHT_BRACKET = ')';
+ char DASH = '-';
+ char PERCENT = '%';
+ char PIPE = '|';
+ char PLUS = '+';
+ char QUESTION_MARK = '?';
+ char EXCLAMATION_MARK = '!';
+ char EQUALS = '=';
+ char AMPERSAND = '&';
+ char ASTERISK = '*';
+ char STAR = ASTERISK;
+ char BACK_SLASH = '\\';
+ char COLON = ':';
+ char COMMA = ',';
+ char DOLLAR = '$';
+ char SLASH = '/';
+ char HASH = '#';
+ char HAT = '^';
+ char LEFT_CHEV = '<';
+ char NEWLINE = '\n';
+ char N = 'n';
+ char Y = 'y';
+ char QUOTE = '\"';
+ char RETURN = '\r';
+ char TAB = '\t';
+ char RIGHT_CHEV = '>';
+ char SEMICOLON = ';';
+ char SINGLE_QUOTE = '\'';
+ char BACKTICK = '`';
+ char SPACE = ' ';
+ char TILDA = '~';
+ char LEFT_SQ_BRACKET = '[';
+ char RIGHT_SQ_BRACKET = ']';
+ char UNDERSCORE = '_';
+ char ONE = '1';
+ char ZERO = '0';
+ // @formatter:on
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Charsets.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Charsets.java
new file mode 100644
index 0000000..ca383d1
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Charsets.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
+
+/**
+ * 瀛楃闆嗗伐鍏风被
+ *
+ * @author L.cm
+ */
+public class Charsets {
+
+ /**
+ * 瀛楃闆咺SO-8859-1
+ */
+ public static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;
+ public static final String ISO_8859_1_NAME = ISO_8859_1.name();
+
+ /**
+ * 瀛楃闆咷BK
+ */
+ public static final Charset GBK = Charset.forName(StringPool.GBK);
+ public static final String GBK_NAME = GBK.name();
+
+ /**
+ * 瀛楃闆唘tf-8
+ */
+ public static final Charset UTF_8 = StandardCharsets.UTF_8;
+ public static final String UTF_8_NAME = UTF_8.name();
+
+ /**
+ * 杞崲涓篊harset瀵硅薄
+ *
+ * @param charsetName 瀛楃闆嗭紝涓虹┖鍒欒繑鍥為粯璁ゅ瓧绗﹂泦
+ * @return Charsets
+ * @throws UnsupportedCharsetException 缂栫爜涓嶆敮鎸�
+ */
+ public static Charset charset(String charsetName) throws UnsupportedCharsetException {
+ return StringUtil.isBlank(charsetName) ? Charset.defaultCharset() : Charset.forName(charsetName);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ClassUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ClassUtil.java
new file mode 100644
index 0000000..27a8096
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ClassUtil.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import org.springframework.core.BridgeMethodResolver;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.annotation.SynthesizingMethodParameter;
+import org.springframework.web.method.HandlerMethod;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/**
+ * 绫绘搷浣滃伐鍏�
+ *
+ * @author L.cm
+ */
+public class ClassUtil extends org.springframework.util.ClassUtils {
+
+ private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
+
+ /**
+ * 鑾峰彇鏂规硶鍙傛暟淇℃伅
+ *
+ * @param constructor 鏋勯�犲櫒
+ * @param parameterIndex 鍙傛暟搴忓彿
+ * @return {MethodParameter}
+ */
+ public static MethodParameter getMethodParameter(Constructor<?> constructor, int parameterIndex) {
+ MethodParameter methodParameter = new SynthesizingMethodParameter(constructor, parameterIndex);
+ methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER);
+ return methodParameter;
+ }
+
+ /**
+ * 鑾峰彇鏂规硶鍙傛暟淇℃伅
+ *
+ * @param method 鏂规硶
+ * @param parameterIndex 鍙傛暟搴忓彿
+ * @return {MethodParameter}
+ */
+ public static MethodParameter getMethodParameter(Method method, int parameterIndex) {
+ MethodParameter methodParameter = new SynthesizingMethodParameter(method, parameterIndex);
+ methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER);
+ return methodParameter;
+ }
+
+ /**
+ * 鑾峰彇Annotation
+ *
+ * @param method Method
+ * @param annotationType 娉ㄨВ绫�
+ * @param <A> 娉涘瀷鏍囪
+ * @return {Annotation}
+ */
+ public static <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) {
+ Class<?> targetClass = method.getDeclaringClass();
+ // The method may be on an interface, but we need attributes from the target class.
+ // If the target class is null, the method will be unchanged.
+ Method specificMethod = ClassUtil.getMostSpecificMethod(method, targetClass);
+ // If we are dealing with method with generic parameters, find the original method.
+ specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
+ // 鍏堟壘鏂规硶锛屽啀鎵炬柟娉曚笂鐨勭被
+ A annotation = AnnotatedElementUtils.findMergedAnnotation(specificMethod, annotationType);
+ ;
+ if (null != annotation) {
+ return annotation;
+ }
+ // 鑾峰彇绫讳笂闈㈢殑Annotation锛屽彲鑳藉寘鍚粍鍚堟敞瑙o紝鏁呴噰鐢╯pring鐨勫伐鍏风被
+ return AnnotatedElementUtils.findMergedAnnotation(specificMethod.getDeclaringClass(), annotationType);
+ }
+
+ /**
+ * 鑾峰彇Annotation
+ *
+ * @param handlerMethod HandlerMethod
+ * @param annotationType 娉ㄨВ绫�
+ * @param <A> 娉涘瀷鏍囪
+ * @return {Annotation}
+ */
+ public static <A extends Annotation> A getAnnotation(HandlerMethod handlerMethod, Class<A> annotationType) {
+ // 鍏堟壘鏂规硶锛屽啀鎵炬柟娉曚笂鐨勭被
+ A annotation = handlerMethod.getMethodAnnotation(annotationType);
+ if (null != annotation) {
+ return annotation;
+ }
+ // 鑾峰彇绫讳笂闈㈢殑Annotation锛屽彲鑳藉寘鍚粍鍚堟敞瑙o紝鏁呴噰鐢╯pring鐨勫伐鍏风被
+ Class<?> beanType = handlerMethod.getBeanType();
+ return AnnotatedElementUtils.findMergedAnnotation(beanType, annotationType);
+ }
+
+
+ /**
+ * 鍒ゆ柇鏄惁鏈夋敞瑙� Annotation
+ *
+ * @param method Method
+ * @param annotationType 娉ㄨВ绫�
+ * @param <A> 娉涘瀷鏍囪
+ * @return {boolean}
+ */
+ public static <A extends Annotation> boolean isAnnotated(Method method, Class<A> annotationType) {
+ // 鍏堟壘鏂规硶锛屽啀鎵炬柟娉曚笂鐨勭被
+ boolean isMethodAnnotated = AnnotatedElementUtils.isAnnotated(method, annotationType);
+ if (isMethodAnnotated) {
+ return true;
+ }
+ // 鑾峰彇绫讳笂闈㈢殑Annotation锛屽彲鑳藉寘鍚粍鍚堟敞瑙o紝鏁呴噰鐢╯pring鐨勫伐鍏风被
+ Class<?> targetClass = method.getDeclaringClass();
+ return AnnotatedElementUtils.isAnnotated(targetClass, annotationType);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/CollectionUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/CollectionUtil.java
new file mode 100644
index 0000000..e678bbb
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/CollectionUtil.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import org.springframework.lang.Nullable;
+import org.springframework.util.CollectionUtils;
+
+import java.lang.reflect.Array;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 闆嗗悎宸ュ叿绫�
+ *
+ * @author L.cm
+ */
+public class CollectionUtil extends CollectionUtils {
+
+ /**
+ * Return {@code true} if the supplied Collection is not {@code null} or empty.
+ * Otherwise, return {@code false}.
+ *
+ * @param collection the Collection to check
+ * @return whether the given Collection is not empty
+ */
+ public static boolean isNotEmpty(@Nullable Collection<?> collection) {
+ return !CollectionUtil.isEmpty(collection);
+ }
+
+ /**
+ * Return {@code true} if the supplied Map is not {@code null} or empty.
+ * Otherwise, return {@code false}.
+ *
+ * @param map the Map to check
+ * @return whether the given Map is not empty
+ */
+ public static boolean isNotEmpty(@Nullable Map<?, ?> map) {
+ return !CollectionUtil.isEmpty(map);
+ }
+
+ /**
+ * Check whether the given Array contains the given element.
+ *
+ * @param array the Array to check
+ * @param element the element to look for
+ * @param <T> The generic tag
+ * @return {@code true} if found, {@code false} else
+ */
+ public static <T> boolean contains(@Nullable T[] array, final T element) {
+ if (array == null) {
+ return false;
+ }
+ return Arrays.stream(array).anyMatch(x -> ObjectUtil.nullSafeEquals(x, element));
+ }
+
+ /**
+ * Concatenates 2 arrays
+ *
+ * @param one 鏁扮粍1
+ * @param other 鏁扮粍2
+ * @return 鏂版暟缁�
+ */
+ public static String[] concat(String[] one, String[] other) {
+ return concat(one, other, String.class);
+ }
+
+ /**
+ * Concatenates 2 arrays
+ *
+ * @param one 鏁扮粍1
+ * @param other 鏁扮粍2
+ * @param clazz 鏁扮粍绫�
+ * @return 鏂版暟缁�
+ */
+ public static <T> T[] concat(T[] one, T[] other, Class<T> clazz) {
+ T[] target = (T[]) Array.newInstance(clazz, one.length + other.length);
+ System.arraycopy(one, 0, target, 0, one.length);
+ System.arraycopy(other, 0, target, one.length, other.length);
+ return target;
+ }
+
+ /**
+ * 瀵硅薄鏄惁涓烘暟缁勫璞�
+ *
+ * @param obj 瀵硅薄
+ * @return 鏄惁涓烘暟缁勫璞★紝濡傛灉涓簕@code null} 杩斿洖false
+ */
+ public static boolean isArray(Object obj) {
+ if (null == obj) {
+ return false;
+ }
+ return obj.getClass().isArray();
+ }
+
+ /**
+ * 涓嶅彲鍙� Set
+ *
+ * @param es 瀵硅薄
+ * @param <E> 娉涘瀷
+ * @return 闆嗗悎
+ */
+ @SafeVarargs
+ public static <E> Set<E> ofImmutableSet(E... es) {
+ Objects.requireNonNull(es, "args es is null.");
+ return Arrays.stream(es).collect(Collectors.toSet());
+ }
+
+ /**
+ * 涓嶅彲鍙� List
+ *
+ * @param es 瀵硅薄
+ * @param <E> 娉涘瀷
+ * @return 闆嗗悎
+ */
+ @SafeVarargs
+ public static <E> List<E> ofImmutableList(E... es) {
+ Objects.requireNonNull(es, "args es is null.");
+ return Arrays.stream(es).collect(Collectors.toList());
+ }
+
+ /**
+ * Iterable 杞崲涓篖ist闆嗗悎
+ *
+ * @param elements Iterable
+ * @param <E> 娉涘瀷
+ * @return 闆嗗悎
+ */
+ public static <E> List<E> toList(Iterable<E> elements) {
+ Objects.requireNonNull(elements, "elements es is null.");
+ if (elements instanceof Collection) {
+ return new ArrayList((Collection) elements);
+ }
+ Iterator<E> iterator = elements.iterator();
+ List<E> list = new ArrayList<>();
+ while (iterator.hasNext()) {
+ list.add(iterator.next());
+ }
+ return list;
+ }
+
+ /**
+ * 灏唊ey value 鏁扮粍杞负 map
+ *
+ * @param keysValues key value 鏁扮粍
+ * @param <K> key
+ * @param <V> value
+ * @return map 闆嗗悎
+ */
+ public static <K, V> Map<K, V> toMap(Object... keysValues) {
+ int kvLength = keysValues.length;
+ if (kvLength % 2 != 0) {
+ throw new IllegalArgumentException("wrong number of arguments for met, keysValues length can not be odd");
+ }
+ Map<K, V> keyValueMap = new HashMap<>(kvLength);
+ for (int i = kvLength - 2; i >= 0; i -= 2) {
+ Object key = keysValues[i];
+ Object value = keysValues[i + 1];
+ keyValueMap.put((K) key, (V) value);
+ }
+ return keyValueMap;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ConcurrentDateFormat.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ConcurrentDateFormat.java
new file mode 100644
index 0000000..16da23e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ConcurrentDateFormat.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Queue;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * 鍙傝�僼omcat8涓殑骞跺彂DateFormat
+ * <p>
+ * {@link SimpleDateFormat}鐨勭嚎绋嬪畨鍏ㄥ寘瑁呭櫒銆�
+ * 涓嶄娇鐢═hreadLocal锛屽垱寤鸿冻澶熺殑SimpleDateFormat瀵硅薄鏉ユ弧瓒冲苟鍙戞�ц姹傘��
+ * </p>
+ *
+ * @author L.cm
+ */
+public class ConcurrentDateFormat {
+ private final String format;
+ private final Locale locale;
+ private final TimeZone timezone;
+ private final Queue<SimpleDateFormat> queue = new ConcurrentLinkedQueue<>();
+
+ private ConcurrentDateFormat(String format, Locale locale, TimeZone timezone) {
+ this.format = format;
+ this.locale = locale;
+ this.timezone = timezone;
+ SimpleDateFormat initial = createInstance();
+ queue.add(initial);
+ }
+
+ public static ConcurrentDateFormat of(String format) {
+ return new ConcurrentDateFormat(format, Locale.getDefault(), TimeZone.getDefault());
+ }
+
+ public static ConcurrentDateFormat of(String format, TimeZone timezone) {
+ return new ConcurrentDateFormat(format, Locale.getDefault(), timezone);
+ }
+
+ public static ConcurrentDateFormat of(String format, Locale locale, TimeZone timezone) {
+ return new ConcurrentDateFormat(format, locale, timezone);
+ }
+
+ public String format(Date date) {
+ SimpleDateFormat sdf = queue.poll();
+ if (sdf == null) {
+ sdf = createInstance();
+ }
+ String result = sdf.format(date);
+ queue.add(sdf);
+ return result;
+ }
+
+ public Date parse(String source) throws ParseException {
+ SimpleDateFormat sdf = queue.poll();
+ if (sdf == null) {
+ sdf = createInstance();
+ }
+ Date result = sdf.parse(source);
+ queue.add(sdf);
+ return result;
+ }
+
+ private SimpleDateFormat createInstance() {
+ SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
+ sdf.setTimeZone(timezone);
+ return sdf;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ConvertUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ConvertUtil.java
new file mode 100644
index 0000000..2772560
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ConvertUtil.java
@@ -0,0 +1,81 @@
+package org.springblade.core.tool.utils;
+
+import org.springblade.core.tool.convert.BladeConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.support.GenericConversionService;
+import org.springframework.lang.Nullable;
+
+/**
+ * 鍩轰簬 spring ConversionService 绫诲瀷杞崲
+ *
+ * @author L.cm
+ */
+@SuppressWarnings("unchecked")
+public class ConvertUtil {
+
+ /**
+ * Convenience operation for converting a source object to the specified targetType.
+ * {@link TypeDescriptor#forObject(Object)}.
+ * @param source the source object
+ * @param targetType the target type
+ * @param <T> 娉涘瀷鏍囪
+ * @return the converted value
+ * @throws IllegalArgumentException if targetType is {@code null},
+ * or sourceType is {@code null} but source is not {@code null}
+ */
+ @Nullable
+ public static <T> T convert(@Nullable Object source, Class<T> targetType) {
+ if (source == null) {
+ return null;
+ }
+ if (ClassUtil.isAssignableValue(targetType, source)) {
+ return (T) source;
+ }
+ GenericConversionService conversionService = BladeConversionService.getInstance();
+ return conversionService.convert(source, targetType);
+ }
+
+ /**
+ * Convenience operation for converting a source object to the specified targetType,
+ * where the target type is a descriptor that provides additional conversion context.
+ * {@link TypeDescriptor#forObject(Object)}.
+ * @param source the source object
+ * @param sourceType the source type
+ * @param targetType the target type
+ * @param <T> 娉涘瀷鏍囪
+ * @return the converted value
+ * @throws IllegalArgumentException if targetType is {@code null},
+ * or sourceType is {@code null} but source is not {@code null}
+ */
+ @Nullable
+ public static <T> T convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source == null) {
+ return null;
+ }
+ GenericConversionService conversionService = BladeConversionService.getInstance();
+ return (T) conversionService.convert(source, sourceType, targetType);
+ }
+
+ /**
+ * Convenience operation for converting a source object to the specified targetType,
+ * where the target type is a descriptor that provides additional conversion context.
+ * Simply delegates to {@link #convert(Object, TypeDescriptor, TypeDescriptor)} and
+ * encapsulates the construction of the source type descriptor using
+ * {@link TypeDescriptor#forObject(Object)}.
+ * @param source the source object
+ * @param targetType the target type
+ * @param <T> 娉涘瀷鏍囪
+ * @return the converted value
+ * @throws IllegalArgumentException if targetType is {@code null},
+ * or sourceType is {@code null} but source is not {@code null}
+ */
+ @Nullable
+ public static <T> T convert(@Nullable Object source, TypeDescriptor targetType) {
+ if (source == null) {
+ return null;
+ }
+ GenericConversionService conversionService = BladeConversionService.getInstance();
+ return (T) conversionService.convert(source, targetType);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DatatypeConverterUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DatatypeConverterUtil.java
new file mode 100644
index 0000000..1b88ace
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DatatypeConverterUtil.java
@@ -0,0 +1,57 @@
+package org.springblade.core.tool.utils;
+
+/**
+ * 鏁版嵁绫诲瀷杞崲宸ュ叿绫�
+ *
+ * @author Chill
+ */
+public class DatatypeConverterUtil {
+
+ /**
+ * hex鏂囨湰杞崲涓轰簩杩涘埗
+ *
+ * @param hexStr hex鏂囨湰
+ * @return byte[]
+ */
+ public static byte[] parseHexBinary(String hexStr) {
+ final int len = hexStr.length();
+
+ if (len % 2 != 0) {
+ throw new IllegalArgumentException("hexBinary needs to be even-length: " + hexStr);
+ }
+
+ byte[] out = new byte[len / 2];
+
+ for (int i = 0; i < len; i += 2) {
+ int h = hexToBin(hexStr.charAt(i));
+ int l = hexToBin(hexStr.charAt(i + 1));
+ if (h == -1 || l == -1) {
+ throw new IllegalArgumentException("contains illegal character for hexBinary: " + hexStr);
+ }
+
+ out[i / 2] = (byte) (h * 16 + l);
+ }
+
+ return out;
+ }
+
+ /**
+ * hex鏂囨湰杞崲涓篿nt
+ *
+ * @param ch hex鏂囨湰
+ * @return int
+ */
+ private static int hexToBin(char ch) {
+ if ('0' <= ch && ch <= '9') {
+ return ch - '0';
+ }
+ if ('A' <= ch && ch <= 'F') {
+ return ch - 'A' + 10;
+ }
+ if ('a' <= ch && ch <= 'f') {
+ return ch - 'a' + 10;
+ }
+ return -1;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DateTimeUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DateTimeUtil.java
new file mode 100644
index 0000000..a5d9c79
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DateTimeUtil.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAccessor;
+import java.util.Date;
+
+/**
+ * DateTime 宸ュ叿绫�
+ *
+ * @author L.cm
+ */
+public class DateTimeUtil {
+ public static final DateTimeFormatter DATETIME_FORMAT = DateTimeFormatter.ofPattern(DateUtil.PATTERN_DATETIME);
+ public static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern(DateUtil.PATTERN_DATE);
+ public static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern(DateUtil.PATTERN_TIME);
+
+ /**
+ * 鏃ユ湡鏃堕棿鏍煎紡鍖�
+ *
+ * @param temporal 鏃堕棿
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String formatDateTime(TemporalAccessor temporal) {
+ return DATETIME_FORMAT.format(temporal);
+ }
+
+ /**
+ * 鏃ユ湡鏃堕棿鏍煎紡鍖�
+ *
+ * @param temporal 鏃堕棿
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String formatDate(TemporalAccessor temporal) {
+ return DATE_FORMAT.format(temporal);
+ }
+
+ /**
+ * 鏃堕棿鏍煎紡鍖�
+ *
+ * @param temporal 鏃堕棿
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String formatTime(TemporalAccessor temporal) {
+ return TIME_FORMAT.format(temporal);
+ }
+
+ /**
+ * 鏃ユ湡鏍煎紡鍖�
+ *
+ * @param temporal 鏃堕棿
+ * @param pattern 琛ㄨ揪寮�
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String format(TemporalAccessor temporal, String pattern) {
+ return DateTimeFormatter.ofPattern(pattern).format(temporal);
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘椂闂�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @param pattern 琛ㄨ揪寮�
+ * @return 鏃堕棿
+ */
+ public static LocalDateTime parseDateTime(String dateStr, String pattern) {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
+ return DateTimeUtil.parseDateTime(dateStr, formatter);
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘椂闂�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @param formatter DateTimeFormatter
+ * @return 鏃堕棿
+ */
+ public static LocalDateTime parseDateTime(String dateStr, DateTimeFormatter formatter) {
+ return LocalDateTime.parse(dateStr, formatter);
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘椂闂�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @return 鏃堕棿
+ */
+ public static LocalDateTime parseDateTime(String dateStr) {
+ return DateTimeUtil.parseDateTime(dateStr, DateTimeUtil.DATETIME_FORMAT);
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘椂闂�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @param pattern 琛ㄨ揪寮�
+ * @return 鏃堕棿
+ */
+ public static LocalDate parseDate(String dateStr, String pattern) {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
+ return DateTimeUtil.parseDate(dateStr, formatter);
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘椂闂�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @param formatter DateTimeFormatter
+ * @return 鏃堕棿
+ */
+ public static LocalDate parseDate(String dateStr, DateTimeFormatter formatter) {
+ return LocalDate.parse(dateStr, formatter);
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘棩鏈�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @return 鏃堕棿
+ */
+ public static LocalDate parseDate(String dateStr) {
+ return DateTimeUtil.parseDate(dateStr, DateTimeUtil.DATE_FORMAT);
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘椂闂�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @param pattern 鏃堕棿姝e垯
+ * @return 鏃堕棿
+ */
+ public static LocalTime parseTime(String dateStr, String pattern) {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
+ return DateTimeUtil.parseTime(dateStr, formatter);
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘椂闂�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @param formatter DateTimeFormatter
+ * @return 鏃堕棿
+ */
+ public static LocalTime parseTime(String dateStr, DateTimeFormatter formatter) {
+ return LocalTime.parse(dateStr, formatter);
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘椂闂�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @return 鏃堕棿
+ */
+ public static LocalTime parseTime(String dateStr) {
+ return DateTimeUtil.parseTime(dateStr, DateTimeUtil.TIME_FORMAT);
+ }
+
+ /**
+ * 鏃堕棿杞� Instant
+ *
+ * @param dateTime 鏃堕棿
+ * @return Instant
+ */
+ public static Instant toInstant(LocalDateTime dateTime) {
+ return dateTime.atZone(ZoneId.systemDefault()).toInstant();
+ }
+
+ /**
+ * Instant 杞� 鏃堕棿
+ *
+ * @param instant Instant
+ * @return Instant
+ */
+ public static LocalDateTime toDateTime(Instant instant) {
+ return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
+ }
+
+ /**
+ * 杞崲鎴� date
+ *
+ * @param dateTime LocalDateTime
+ * @return Date
+ */
+ public static Date toDate(LocalDateTime dateTime) {
+ return Date.from(DateTimeUtil.toInstant(dateTime));
+ }
+
+ /**
+ * 姣旇緝2涓椂闂村樊锛岃法搴︽瘮杈冨皬
+ *
+ * @param startInclusive 寮�濮嬫椂闂�
+ * @param endExclusive 缁撴潫鏃堕棿
+ * @return 鏃堕棿闂撮殧
+ */
+ public static Duration between(Temporal startInclusive, Temporal endExclusive) {
+ return Duration.between(startInclusive, endExclusive);
+ }
+
+ /**
+ * 姣旇緝2涓椂闂村樊锛岃法搴︽瘮杈冨ぇ锛屽勾鏈堟棩涓哄崟浣�
+ *
+ * @param startDate 寮�濮嬫椂闂�
+ * @param endDate 缁撴潫鏃堕棿
+ * @return 鏃堕棿闂撮殧
+ */
+ public static Period between(LocalDate startDate, LocalDate endDate) {
+ return Period.between(startDate, endDate);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DateUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DateUtil.java
new file mode 100644
index 0000000..0a34c08
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DateUtil.java
@@ -0,0 +1,634 @@
+package org.springblade.core.tool.utils;
+
+import org.springframework.util.Assert;
+
+import java.text.ParseException;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalAmount;
+import java.time.temporal.TemporalQuery;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+/**
+ * 鏃ユ湡宸ュ叿绫�
+ *
+ * @author L.cm
+ */
+public class DateUtil {
+
+ public static final String PATTERN_DATETIME = "yyyy-MM-dd HH:mm:ss";
+ public static final String PATTERN_DATETIME_MINI = "yyyyMMddHHmmss";
+ public static final String PATTERN_DATE = "yyyy-MM-dd";
+ public static final String PATTERN_TIME = "HH:mm:ss";
+ /**
+ * 鑰� date 鏍煎紡鍖�
+ */
+ public static final ConcurrentDateFormat DATETIME_FORMAT = ConcurrentDateFormat.of(PATTERN_DATETIME);
+ public static final ConcurrentDateFormat DATETIME_MINI_FORMAT = ConcurrentDateFormat.of(PATTERN_DATETIME_MINI);
+ public static final ConcurrentDateFormat DATE_FORMAT = ConcurrentDateFormat.of(PATTERN_DATE);
+ public static final ConcurrentDateFormat TIME_FORMAT = ConcurrentDateFormat.of(PATTERN_TIME);
+ /**
+ * java 8 鏃堕棿鏍煎紡鍖�
+ */
+ public static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern(DateUtil.PATTERN_DATETIME);
+ public static final DateTimeFormatter DATETIME_MINI_FORMATTER = DateTimeFormatter.ofPattern(DateUtil.PATTERN_DATETIME_MINI);
+ public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DateUtil.PATTERN_DATE);
+ public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(DateUtil.PATTERN_TIME);
+
+ /**
+ * 鑾峰彇褰撳墠鏃ユ湡
+ *
+ * @return 褰撳墠鏃ユ湡
+ */
+ public static Date now() {
+ return new Date();
+ }
+
+ /**
+ * 娣诲姞骞�
+ *
+ * @param date 鏃堕棿
+ * @param yearsToAdd 娣诲姞鐨勫勾鏁�
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date plusYears(Date date, int yearsToAdd) {
+ return DateUtil.set(date, Calendar.YEAR, yearsToAdd);
+ }
+
+ /**
+ * 娣诲姞鏈�
+ *
+ * @param date 鏃堕棿
+ * @param monthsToAdd 娣诲姞鐨勬湀鏁�
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date plusMonths(Date date, int monthsToAdd) {
+ return DateUtil.set(date, Calendar.MONTH, monthsToAdd);
+ }
+
+ /**
+ * 娣诲姞鍛�
+ *
+ * @param date 鏃堕棿
+ * @param weeksToAdd 娣诲姞鐨勫懆鏁�
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date plusWeeks(Date date, int weeksToAdd) {
+ return DateUtil.plus(date, Period.ofWeeks(weeksToAdd));
+ }
+
+ /**
+ * 娣诲姞澶�
+ *
+ * @param date 鏃堕棿
+ * @param daysToAdd 娣诲姞鐨勫ぉ鏁�
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date plusDays(Date date, long daysToAdd) {
+ return DateUtil.plus(date, Duration.ofDays(daysToAdd));
+ }
+
+ /**
+ * 娣诲姞灏忔椂
+ *
+ * @param date 鏃堕棿
+ * @param hoursToAdd 娣诲姞鐨勫皬鏃舵暟
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date plusHours(Date date, long hoursToAdd) {
+ return DateUtil.plus(date, Duration.ofHours(hoursToAdd));
+ }
+
+ /**
+ * 娣诲姞鍒嗛挓
+ *
+ * @param date 鏃堕棿
+ * @param minutesToAdd 娣诲姞鐨勫垎閽熸暟
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date plusMinutes(Date date, long minutesToAdd) {
+ return DateUtil.plus(date, Duration.ofMinutes(minutesToAdd));
+ }
+
+ /**
+ * 娣诲姞绉�
+ *
+ * @param date 鏃堕棿
+ * @param secondsToAdd 娣诲姞鐨勭鏁�
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date plusSeconds(Date date, long secondsToAdd) {
+ return DateUtil.plus(date, Duration.ofSeconds(secondsToAdd));
+ }
+
+ /**
+ * 娣诲姞姣
+ *
+ * @param date 鏃堕棿
+ * @param millisToAdd 娣诲姞鐨勬绉掓暟
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date plusMillis(Date date, long millisToAdd) {
+ return DateUtil.plus(date, Duration.ofMillis(millisToAdd));
+ }
+
+ /**
+ * 娣诲姞绾崇
+ *
+ * @param date 鏃堕棿
+ * @param nanosToAdd 娣诲姞鐨勭撼绉掓暟
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date plusNanos(Date date, long nanosToAdd) {
+ return DateUtil.plus(date, Duration.ofNanos(nanosToAdd));
+ }
+
+ /**
+ * 鏃ユ湡娣诲姞鏃堕棿閲�
+ *
+ * @param date 鏃堕棿
+ * @param amount 鏃堕棿閲�
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date plus(Date date, TemporalAmount amount) {
+ Instant instant = date.toInstant();
+ return Date.from(instant.plus(amount));
+ }
+
+ /**
+ * 鍑忓皯骞�
+ *
+ * @param date 鏃堕棿
+ * @param years 鍑忓皯鐨勫勾鏁�
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date minusYears(Date date, int years) {
+ return DateUtil.set(date, Calendar.YEAR, -years);
+ }
+
+ /**
+ * 鍑忓皯鏈�
+ *
+ * @param date 鏃堕棿
+ * @param months 鍑忓皯鐨勬湀鏁�
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date minusMonths(Date date, int months) {
+ return DateUtil.set(date, Calendar.MONTH, -months);
+ }
+
+ /**
+ * 鍑忓皯鍛�
+ *
+ * @param date 鏃堕棿
+ * @param weeks 鍑忓皯鐨勫懆鏁�
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date minusWeeks(Date date, int weeks) {
+ return DateUtil.minus(date, Period.ofWeeks(weeks));
+ }
+
+ /**
+ * 鍑忓皯澶�
+ *
+ * @param date 鏃堕棿
+ * @param days 鍑忓皯鐨勫ぉ鏁�
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date minusDays(Date date, long days) {
+ return DateUtil.minus(date, Duration.ofDays(days));
+ }
+
+ /**
+ * 鍑忓皯灏忔椂
+ *
+ * @param date 鏃堕棿
+ * @param hours 鍑忓皯鐨勫皬鏃舵暟
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date minusHours(Date date, long hours) {
+ return DateUtil.minus(date, Duration.ofHours(hours));
+ }
+
+ /**
+ * 鍑忓皯鍒嗛挓
+ *
+ * @param date 鏃堕棿
+ * @param minutes 鍑忓皯鐨勫垎閽熸暟
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date minusMinutes(Date date, long minutes) {
+ return DateUtil.minus(date, Duration.ofMinutes(minutes));
+ }
+
+ /**
+ * 鍑忓皯绉�
+ *
+ * @param date 鏃堕棿
+ * @param seconds 鍑忓皯鐨勭鏁�
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date minusSeconds(Date date, long seconds) {
+ return DateUtil.minus(date, Duration.ofSeconds(seconds));
+ }
+
+ /**
+ * 鍑忓皯姣
+ *
+ * @param date 鏃堕棿
+ * @param millis 鍑忓皯鐨勬绉掓暟
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date minusMillis(Date date, long millis) {
+ return DateUtil.minus(date, Duration.ofMillis(millis));
+ }
+
+ /**
+ * 鍑忓皯绾崇
+ *
+ * @param date 鏃堕棿
+ * @param nanos 鍑忓皯鐨勭撼绉掓暟
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date minusNanos(Date date, long nanos) {
+ return DateUtil.minus(date, Duration.ofNanos(nanos));
+ }
+
+ /**
+ * 鏃ユ湡鍑忓皯鏃堕棿閲�
+ *
+ * @param date 鏃堕棿
+ * @param amount 鏃堕棿閲�
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ public static Date minus(Date date, TemporalAmount amount) {
+ Instant instant = date.toInstant();
+ return Date.from(instant.minus(amount));
+ }
+
+ /**
+ * 璁剧疆鏃ユ湡灞炴��
+ *
+ * @param date 鏃堕棿
+ * @param calendarField 鏇存敼鐨勫睘鎬�
+ * @param amount 鏇存敼鏁帮紝-1琛ㄧず鍑忓皯
+ * @return 璁剧疆鍚庣殑鏃堕棿
+ */
+ private static Date set(Date date, int calendarField, int amount) {
+ Assert.notNull(date, "The date must not be null");
+ Calendar c = Calendar.getInstance();
+ c.setLenient(false);
+ c.setTime(date);
+ c.add(calendarField, amount);
+ return c.getTime();
+ }
+
+ /**
+ * 鏃ユ湡鏃堕棿鏍煎紡鍖�
+ *
+ * @param date 鏃堕棿
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String formatDateTime(Date date) {
+ return DATETIME_FORMAT.format(date);
+ }
+
+ /**
+ * 鏃ユ湡鏃堕棿鏍煎紡鍖�
+ *
+ * @param date 鏃堕棿
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String formatDateTimeMini(Date date) {
+ return DATETIME_MINI_FORMAT.format(date);
+ }
+
+ /**
+ * 鏃ユ湡鏍煎紡鍖�
+ *
+ * @param date 鏃堕棿
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String formatDate(Date date) {
+ return DATE_FORMAT.format(date);
+ }
+
+ /**
+ * 鏃堕棿鏍煎紡鍖�
+ *
+ * @param date 鏃堕棿
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String formatTime(Date date) {
+ return TIME_FORMAT.format(date);
+ }
+
+ /**
+ * 鏃ユ湡鏍煎紡鍖�
+ *
+ * @param date 鏃堕棿
+ * @param pattern 琛ㄨ揪寮�
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String format(Date date, String pattern) {
+ return ConcurrentDateFormat.of(pattern).format(date);
+ }
+
+ /**
+ * java8 鏃ユ湡鏃堕棿鏍煎紡鍖�
+ *
+ * @param temporal 鏃堕棿
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String formatDateTime(TemporalAccessor temporal) {
+ return DATETIME_FORMATTER.format(temporal);
+ }
+
+ /**
+ * java8 鏃ユ湡鏃堕棿鏍煎紡鍖�
+ *
+ * @param temporal 鏃堕棿
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String formatDateTimeMini(TemporalAccessor temporal) {
+ return DATETIME_MINI_FORMATTER.format(temporal);
+ }
+
+ /**
+ * java8 鏃ユ湡鏃堕棿鏍煎紡鍖�
+ *
+ * @param temporal 鏃堕棿
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String formatDate(TemporalAccessor temporal) {
+ return DATE_FORMATTER.format(temporal);
+ }
+
+ /**
+ * java8 鏃堕棿鏍煎紡鍖�
+ *
+ * @param temporal 鏃堕棿
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String formatTime(TemporalAccessor temporal) {
+ return TIME_FORMATTER.format(temporal);
+ }
+
+ /**
+ * java8 鏃ユ湡鏍煎紡鍖�
+ *
+ * @param temporal 鏃堕棿
+ * @param pattern 琛ㄨ揪寮�
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String format(TemporalAccessor temporal, String pattern) {
+ return DateTimeFormatter.ofPattern(pattern).format(temporal);
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘椂闂�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @param pattern 琛ㄨ揪寮�
+ * @return 鏃堕棿
+ */
+ public static Date parse(String dateStr, String pattern) {
+ ConcurrentDateFormat format = ConcurrentDateFormat.of(pattern);
+ try {
+ return format.parse(dateStr);
+ } catch (ParseException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘椂闂�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @param format ConcurrentDateFormat
+ * @return 鏃堕棿
+ */
+ public static Date parse(String dateStr, ConcurrentDateFormat format) {
+ try {
+ return format.parse(dateStr);
+ } catch (ParseException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘椂闂�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @param pattern 琛ㄨ揪寮�
+ * @return 鏃堕棿
+ */
+ public static <T> T parse(String dateStr, String pattern, TemporalQuery<T> query) {
+ return DateTimeFormatter.ofPattern(pattern).parse(dateStr, query);
+ }
+
+ /**
+ * 鏃堕棿杞� Instant
+ *
+ * @param dateTime 鏃堕棿
+ * @return Instant
+ */
+ public static Instant toInstant(LocalDateTime dateTime) {
+ return dateTime.atZone(ZoneId.systemDefault()).toInstant();
+ }
+
+ /**
+ * Instant 杞� 鏃堕棿
+ *
+ * @param instant Instant
+ * @return Instant
+ */
+ public static LocalDateTime toDateTime(Instant instant) {
+ return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
+ }
+
+ /**
+ * 杞崲鎴� date
+ *
+ * @param dateTime LocalDateTime
+ * @return Date
+ */
+ public static Date toDate(LocalDateTime dateTime) {
+ return Date.from(DateUtil.toInstant(dateTime));
+ }
+
+ /**
+ * 杞崲鎴� date
+ *
+ * @param localDate LocalDate
+ * @return Date
+ */
+ public static Date toDate(final LocalDate localDate) {
+ return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
+ }
+
+ /**
+ * Converts local date time to Calendar.
+ */
+ public static Calendar toCalendar(final LocalDateTime localDateTime) {
+ return GregorianCalendar.from(ZonedDateTime.of(localDateTime, ZoneId.systemDefault()));
+ }
+
+ /**
+ * localDateTime 杞崲鎴愭绉掓暟
+ *
+ * @param localDateTime LocalDateTime
+ * @return long
+ */
+ public static long toMilliseconds(final LocalDateTime localDateTime) {
+ return localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+ }
+
+ /**
+ * localDate 杞崲鎴愭绉掓暟
+ *
+ * @param localDate LocalDate
+ * @return long
+ */
+ public static long toMilliseconds(LocalDate localDate) {
+ return toMilliseconds(localDate.atStartOfDay());
+ }
+
+ /**
+ * 杞崲鎴恓ava8 鏃堕棿
+ *
+ * @param calendar 鏃ュ巻
+ * @return LocalDateTime
+ */
+ public static LocalDateTime fromCalendar(final Calendar calendar) {
+ TimeZone tz = calendar.getTimeZone();
+ ZoneId zid = tz == null ? ZoneId.systemDefault() : tz.toZoneId();
+ return LocalDateTime.ofInstant(calendar.toInstant(), zid);
+ }
+
+ /**
+ * 杞崲鎴恓ava8 鏃堕棿
+ *
+ * @param instant Instant
+ * @return LocalDateTime
+ */
+ public static LocalDateTime fromInstant(final Instant instant) {
+ return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
+ }
+
+ /**
+ * 杞崲鎴恓ava8 鏃堕棿
+ *
+ * @param date Date
+ * @return LocalDateTime
+ */
+ public static LocalDateTime fromDate(final Date date) {
+ return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
+ }
+
+ /**
+ * 杞崲鎴恓ava8 鏃堕棿
+ *
+ * @param milliseconds 姣鏁�
+ * @return LocalDateTime
+ */
+ public static LocalDateTime fromMilliseconds(final long milliseconds) {
+ return LocalDateTime.ofInstant(Instant.ofEpochMilli(milliseconds), ZoneId.systemDefault());
+ }
+
+ /**
+ * 姣旇緝2涓椂闂村樊锛岃法搴︽瘮杈冨皬
+ *
+ * @param startInclusive 寮�濮嬫椂闂�
+ * @param endExclusive 缁撴潫鏃堕棿
+ * @return 鏃堕棿闂撮殧
+ */
+ public static Duration between(Temporal startInclusive, Temporal endExclusive) {
+ return Duration.between(startInclusive, endExclusive);
+ }
+
+ /**
+ * 姣旇緝2涓椂闂村樊锛岃法搴︽瘮杈冨ぇ锛屽勾鏈堟棩涓哄崟浣�
+ *
+ * @param startDate 寮�濮嬫椂闂�
+ * @param endDate 缁撴潫鏃堕棿
+ * @return 鏃堕棿闂撮殧
+ */
+ public static Period between(LocalDate startDate, LocalDate endDate) {
+ return Period.between(startDate, endDate);
+ }
+
+ /**
+ * 姣旇緝2涓� 鏃堕棿宸�
+ *
+ * @param startDate 寮�濮嬫椂闂�
+ * @param endDate 缁撴潫鏃堕棿
+ * @return 鏃堕棿闂撮殧
+ */
+ public static Duration between(Date startDate, Date endDate) {
+ return Duration.between(startDate.toInstant(), endDate.toInstant());
+ }
+
+ /**
+ * 灏嗙鏁拌浆鎹负鏃ユ椂鍒嗙
+ *
+ * @param second 绉掓暟
+ * @return 鏃堕棿
+ */
+ public static String secondToTime(Long second) {
+ // 鍒ゆ柇鏄惁涓虹┖
+ if (second == null || second == 0L) {
+ return StringPool.EMPTY;
+ }
+ //杞崲澶╂暟
+ long days = second / 86400;
+ //鍓╀綑绉掓暟
+ second = second % 86400;
+ //杞崲灏忔椂
+ long hours = second / 3600;
+ //鍓╀綑绉掓暟
+ second = second % 3600;
+ //杞崲鍒嗛挓
+ long minutes = second / 60;
+ //鍓╀綑绉掓暟
+ second = second % 60;
+ if (days > 0) {
+ return StringUtil.format("{}澶﹞}灏忔椂{}鍒唟}绉�", days, hours, minutes, second);
+ } else {
+ return StringUtil.format("{}灏忔椂{}鍒唟}绉�", hours, minutes, second);
+ }
+ }
+
+ /**
+ * 鑾峰彇浠婂ぉ鐨勬棩鏈�
+ *
+ * @return 鏃堕棿
+ */
+ public static String today() {
+ return format(new Date(), "yyyyMMdd");
+ }
+
+ /**
+ * 鑾峰彇浠婂ぉ鐨勬椂闂�
+ *
+ * @return 鏃堕棿
+ */
+ public static String time() {
+ return format(new Date(), PATTERN_DATETIME_MINI);
+ }
+
+ /**
+ * 鑾峰彇浠婂ぉ鐨勫皬鏃舵暟
+ *
+ * @return 鏃堕棿
+ */
+ public static Integer hour() {
+ return NumberUtil.toInt(format(new Date(), "HH"));
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DesUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DesUtil.java
new file mode 100644
index 0000000..b5eb28c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DesUtil.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import org.springframework.lang.Nullable;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESKeySpec;
+import java.util.Objects;
+
+/**
+ * DES鍔犺В瀵嗗鐞嗗伐鍏�
+ *
+ * @author L.cm
+ */
+public class DesUtil {
+ /**
+ * 鏁板瓧绛惧悕锛屽瘑閽ョ畻娉�
+ */
+ public static final String DES_ALGORITHM = "DES";
+
+ /**
+ * 鐢熸垚 des 瀵嗛挜
+ *
+ * @return 瀵嗛挜
+ */
+ public static String genDesKey() {
+ return StringUtil.random(16);
+ }
+
+ /**
+ * DES鍔犲瘑
+ *
+ * @param data byte array
+ * @param password 瀵嗛挜
+ * @return des hex
+ */
+ public static String encryptToHex(byte[] data, String password) {
+ return HexUtil.encodeToString(encrypt(data, password));
+ }
+
+ /**
+ * DES鍔犲瘑
+ *
+ * @param data 瀛楃涓插唴瀹�
+ * @param password 瀵嗛挜
+ * @return des hex
+ */
+ @Nullable
+ public static String encryptToHex(@Nullable String data, String password) {
+ if (StringUtil.isBlank(data)) {
+ return null;
+ }
+ byte[] dataBytes = data.getBytes(Charsets.UTF_8);
+ return encryptToHex(dataBytes, password);
+ }
+
+ /**
+ * DES瑙e瘑
+ *
+ * @param data 瀛楃涓插唴瀹�
+ * @param password 瀵嗛挜
+ * @return des context
+ */
+ @Nullable
+ public static String decryptFormHex(@Nullable String data, String password) {
+ if (StringUtil.isBlank(data)) {
+ return null;
+ }
+ byte[] hexBytes = HexUtil.decode(data);
+ return new String(decrypt(hexBytes, password), Charsets.UTF_8);
+ }
+
+ /**
+ * DES鍔犲瘑
+ *
+ * @param data byte array
+ * @param password 瀵嗛挜
+ * @return des hex
+ */
+ public static String encryptToBase64(byte[] data, String password) {
+ return Base64Util.encodeToString(encrypt(data, password));
+ }
+
+ /**
+ * DES鍔犲瘑
+ *
+ * @param data 瀛楃涓插唴瀹�
+ * @param password 瀵嗛挜
+ * @return des hex
+ */
+ @Nullable
+ public static String encryptToBase64(@Nullable String data, String password) {
+ if (StringUtil.isBlank(data)) {
+ return null;
+ }
+ byte[] dataBytes = data.getBytes(Charsets.UTF_8);
+ return encryptToBase64(dataBytes, password);
+ }
+
+ /**
+ * DES瑙e瘑
+ *
+ * @param data 瀛楃涓插唴瀹�
+ * @param password 瀵嗛挜
+ * @return des context
+ */
+ public static byte[] decryptFormBase64(byte[] data, String password) {
+ byte[] dataBytes = Base64Util.decode(data);
+ return decrypt(dataBytes, password);
+ }
+
+ /**
+ * DES瑙e瘑
+ *
+ * @param data 瀛楃涓插唴瀹�
+ * @param password 瀵嗛挜
+ * @return des context
+ */
+ @Nullable
+ public static String decryptFormBase64(@Nullable String data, String password) {
+ if (StringUtil.isBlank(data)) {
+ return null;
+ }
+ byte[] dataBytes = Base64Util.decodeFromString(data);
+ return new String(decrypt(dataBytes, password), Charsets.UTF_8);
+ }
+
+ /**
+ * DES鍔犲瘑
+ *
+ * @param data 鍐呭
+ * @param desKey 瀵嗛挜
+ * @return byte array
+ */
+ public static byte[] encrypt(byte[] data, byte[] desKey) {
+ return des(data, desKey, Cipher.ENCRYPT_MODE);
+ }
+
+ /**
+ * DES鍔犲瘑
+ *
+ * @param data 鍐呭
+ * @param desKey 瀵嗛挜
+ * @return byte array
+ */
+ public static byte[] encrypt(byte[] data, String desKey) {
+ return encrypt(data, Objects.requireNonNull(desKey).getBytes(Charsets.UTF_8));
+ }
+
+ /**
+ * DES瑙e瘑
+ *
+ * @param data 鍐呭
+ * @param desKey 瀵嗛挜
+ * @return byte array
+ */
+ public static byte[] decrypt(byte[] data, byte[] desKey) {
+ return des(data, desKey, Cipher.DECRYPT_MODE);
+ }
+
+ /**
+ * DES瑙e瘑
+ *
+ * @param data 鍐呭
+ * @param desKey 瀵嗛挜
+ * @return byte array
+ */
+ public static byte[] decrypt(byte[] data, String desKey) {
+ return decrypt(data, Objects.requireNonNull(desKey).getBytes(Charsets.UTF_8));
+ }
+
+ /**
+ * DES鍔犲瘑/瑙e瘑鍏叡鏂规硶
+ *
+ * @param data byte鏁扮粍
+ * @param desKey 瀵嗛挜
+ * @param mode 鍔犲瘑锛歿@link Cipher#ENCRYPT_MODE}锛岃В瀵嗭細{@link Cipher#DECRYPT_MODE}
+ * @return des
+ */
+ private static byte[] des(byte[] data, byte[] desKey, int mode) {
+ try {
+ SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES_ALGORITHM);
+ Cipher cipher = Cipher.getInstance(DES_ALGORITHM);
+ DESKeySpec desKeySpec = new DESKeySpec(desKey);
+ cipher.init(mode, keyFactory.generateSecret(desKeySpec), Holder.SECURE_RANDOM);
+ return cipher.doFinal(data);
+ } catch (Exception e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DigestUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DigestUtil.java
new file mode 100644
index 0000000..dbc46b7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DigestUtil.java
@@ -0,0 +1,450 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.utils;
+
+import org.springframework.lang.Nullable;
+import org.springframework.util.DigestUtils;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * 鍔犲瘑鐩稿叧宸ュ叿绫荤洿鎺ヤ娇鐢⊿pring util灏佽锛屽噺灏慾ar渚濊禆
+ *
+ * @author L.cm
+ */
+public class DigestUtil extends org.springframework.util.DigestUtils {
+ private static final char[] HEX_CODE = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+ /**
+ * Calculates the MD5 digest and returns the value as a 32 character hex string.
+ *
+ * @param data Data to digest
+ * @return MD5 digest as a hex string
+ */
+ public static String md5Hex(final String data) {
+ return DigestUtils.md5DigestAsHex(data.getBytes(Charsets.UTF_8));
+ }
+
+ /**
+ * Return a hexadecimal string representation of the MD5 digest of the given bytes.
+ *
+ * @param bytes the bytes to calculate the digest over
+ * @return a hexadecimal digest string
+ */
+ public static String md5Hex(final byte[] bytes) {
+ return DigestUtils.md5DigestAsHex(bytes);
+ }
+
+ /**
+ * sha1Hex
+ *
+ * @param data Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha1Hex(String data) {
+ return DigestUtil.sha1Hex(data.getBytes(Charsets.UTF_8));
+ }
+
+ /**
+ * sha1Hex
+ *
+ * @param bytes Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha1Hex(final byte[] bytes) {
+ return DigestUtil.digestHex("SHA-1", bytes);
+ }
+
+ /**
+ * SHA224Hex
+ *
+ * @param data Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha224Hex(String data) {
+ return DigestUtil.sha224Hex(data.getBytes(Charsets.UTF_8));
+ }
+
+ /**
+ * SHA224Hex
+ *
+ * @param bytes Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha224Hex(final byte[] bytes) {
+ return DigestUtil.digestHex("SHA-224", bytes);
+ }
+
+ /**
+ * sha256Hex
+ *
+ * @param data Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha256Hex(String data) {
+ return DigestUtil.sha256Hex(data.getBytes(Charsets.UTF_8));
+ }
+
+ /**
+ * sha256Hex
+ *
+ * @param bytes Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha256Hex(final byte[] bytes) {
+ return DigestUtil.digestHex("SHA-256", bytes);
+ }
+
+ /**
+ * sha384Hex
+ *
+ * @param data Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha384Hex(String data) {
+ return DigestUtil.sha384Hex(data.getBytes(Charsets.UTF_8));
+ }
+
+ /**
+ * sha384Hex
+ *
+ * @param bytes Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha384Hex(final byte[] bytes) {
+ return DigestUtil.digestHex("SHA-384", bytes);
+ }
+
+ /**
+ * sha512Hex
+ *
+ * @param data Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha512Hex(String data) {
+ return DigestUtil.sha512Hex(data.getBytes(Charsets.UTF_8));
+ }
+
+ /**
+ * sha512Hex
+ *
+ * @param bytes Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha512Hex(final byte[] bytes) {
+ return DigestUtil.digestHex("SHA-512", bytes);
+ }
+
+ /**
+ * digest Hex
+ *
+ * @param algorithm 绠楁硶
+ * @param bytes Data to digest
+ * @return digest as a hex string
+ */
+ public static String digestHex(String algorithm, byte[] bytes) {
+ try {
+ MessageDigest md = MessageDigest.getInstance(algorithm);
+ return encodeHex(md.digest(bytes));
+ } catch (NoSuchAlgorithmException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * hmacMd5 Hex
+ *
+ * @param data Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacMd5Hex(String data, String key) {
+ return DigestUtil.hmacMd5Hex(data.getBytes(Charsets.UTF_8), key);
+ }
+
+ /**
+ * hmacMd5 Hex
+ *
+ * @param bytes Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacMd5Hex(final byte[] bytes, String key) {
+ return DigestUtil.digestHMacHex("HmacMD5", bytes, key);
+ }
+
+ /**
+ * hmacSha1 Hex
+ *
+ * @param data Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha1Hex(String data, String key) {
+ return DigestUtil.hmacSha1Hex(data.getBytes(Charsets.UTF_8), key);
+ }
+
+ /**
+ * hmacSha1 Hex
+ *
+ * @param bytes Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha1Hex(final byte[] bytes, String key) {
+ return DigestUtil.digestHMacHex("HmacSHA1", bytes, key);
+ }
+
+ /**
+ * hmacSha224 Hex
+ *
+ * @param data Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha224Hex(String data, String key) {
+ return DigestUtil.hmacSha224Hex(data.getBytes(Charsets.UTF_8), key);
+ }
+
+ /**
+ * hmacSha224 Hex
+ *
+ * @param bytes Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha224Hex(final byte[] bytes, String key) {
+ return DigestUtil.digestHMacHex("HmacSHA224", bytes, key);
+ }
+
+ /**
+ * hmacSha256
+ *
+ * @param data Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static byte[] hmacSha256(String data, String key) {
+ return DigestUtil.hmacSha256(data.getBytes(Charsets.UTF_8), key);
+ }
+
+ /**
+ * hmacSha256
+ *
+ * @param bytes Data to digest
+ * @param key key
+ * @return digest as a byte array
+ */
+ public static byte[] hmacSha256(final byte[] bytes, String key) {
+ return DigestUtil.digestHMac("HmacSHA256", bytes, key);
+ }
+
+ /**
+ * hmacSha256 Hex
+ *
+ * @param data Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha256Hex(String data, String key) {
+ return DigestUtil.hmacSha256Hex(data.getBytes(Charsets.UTF_8), key);
+ }
+
+ /**
+ * hmacSha256 Hex
+ *
+ * @param bytes Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha256Hex(final byte[] bytes, String key) {
+ return DigestUtil.digestHMacHex("HmacSHA256", bytes, key);
+ }
+
+ /**
+ * hmacSha384 Hex
+ *
+ * @param data Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha384Hex(String data, String key) {
+ return DigestUtil.hmacSha384Hex(data.getBytes(Charsets.UTF_8), key);
+ }
+
+ /**
+ * hmacSha384 Hex
+ *
+ * @param bytes Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha384Hex(final byte[] bytes, String key) {
+ return DigestUtil.digestHMacHex("HmacSHA384", bytes, key);
+ }
+
+ /**
+ * hmacSha512 Hex
+ *
+ * @param data Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha512Hex(String data, String key) {
+ return DigestUtil.hmacSha512Hex(data.getBytes(Charsets.UTF_8), key);
+ }
+
+ /**
+ * hmacSha512 Hex
+ *
+ * @param bytes Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha512Hex(final byte[] bytes, String key) {
+ return DigestUtil.digestHMacHex("HmacSHA512", bytes, key);
+ }
+
+ /**
+ * digest HMac Hex
+ *
+ * @param algorithm 绠楁硶
+ * @param bytes Data to digest
+ * @return digest as a hex string
+ */
+ public static String digestHMacHex(String algorithm, final byte[] bytes, String key) {
+ SecretKey secretKey = new SecretKeySpec(key.getBytes(Charsets.UTF_8), algorithm);
+ try {
+ Mac mac = Mac.getInstance(secretKey.getAlgorithm());
+ mac.init(secretKey);
+ return DigestUtil.encodeHex(mac.doFinal(bytes));
+ } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * digest HMac
+ *
+ * @param algorithm 绠楁硶
+ * @param bytes Data to digest
+ * @return digest as a byte array
+ */
+ public static byte[] digestHMac(String algorithm, final byte[] bytes, String key) {
+ SecretKey secretKey = new SecretKeySpec(key.getBytes(Charsets.UTF_8), algorithm);
+ try {
+ Mac mac = Mac.getInstance(secretKey.getAlgorithm());
+ mac.init(secretKey);
+ return mac.doFinal(bytes);
+ } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * encode Hex
+ *
+ * @param bytes Data to Hex
+ * @return bytes as a hex string
+ */
+ public static String encodeHex(byte[] bytes) {
+ StringBuilder r = new StringBuilder(bytes.length * 2);
+ for (byte b : bytes) {
+ r.append(HEX_CODE[(b >> 4) & 0xF]);
+ r.append(HEX_CODE[(b & 0xF)]);
+ }
+ return r.toString();
+ }
+
+ /**
+ * decode Hex
+ *
+ * @param hexStr Hex string
+ * @return decode hex to bytes
+ */
+ public static byte[] decodeHex(final String hexStr) {
+ return DatatypeConverterUtil.parseHexBinary(hexStr);
+ }
+
+ /**
+ * 姣旇緝瀛楃涓诧紝閬垮厤瀛楃涓插洜涓鸿繃闀匡紝浜х敓鑰楁椂
+ *
+ * @param a String
+ * @param b String
+ * @return 鏄惁鐩稿悓
+ */
+ public static boolean slowEquals(@Nullable String a, @Nullable String b) {
+ if (a == null || b == null) {
+ return false;
+ }
+ return DigestUtil.slowEquals(a.getBytes(Charsets.UTF_8), b.getBytes(Charsets.UTF_8));
+ }
+
+ /**
+ * 姣旇緝 byte 鏁扮粍锛岄伩鍏嶅瓧绗︿覆鍥犱负杩囬暱锛屼骇鐢熻�楁椂
+ *
+ * @param a byte array
+ * @param b byte array
+ * @return 鏄惁鐩稿悓
+ */
+ public static boolean slowEquals(@Nullable byte[] a, @Nullable byte[] b) {
+ if (a == null || b == null) {
+ return false;
+ }
+ if (a.length != b.length) {
+ return false;
+ }
+ int diff = a.length ^ b.length;
+ for (int i = 0; i < a.length; i++) {
+ diff |= a[i] ^ b[i];
+ }
+ return diff == 0;
+ }
+
+ /**
+ * 鑷畾涔夊姞瀵� 灏嗗墠绔紶閫掔殑瀵嗙爜鍐嶆鍔犲瘑
+ *
+ * @param data 鏁版嵁
+ * @return {String}
+ */
+ public static String hex(String data) {
+ if (StringUtil.isBlank(data)) {
+ return StringPool.EMPTY;
+ }
+ return sha1Hex(data);
+ }
+
+ /**
+ * 鐢ㄦ埛瀵嗙爜鍔犲瘑瑙勫垯 鍏圡D5鍐峉HA1
+ *
+ * @param data 鏁版嵁
+ * @return {String}
+ */
+ public static String encrypt(String data) {
+ if (StringUtil.isBlank(data)) {
+ return StringPool.EMPTY;
+ }
+ return sha1Hex(md5Hex(data));
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Exceptions.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Exceptions.java
new file mode 100644
index 0000000..23d4a16
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Exceptions.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.utils;
+
+import org.springblade.core.tool.support.FastStringWriter;
+
+import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.UndeclaredThrowableException;
+
+/**
+ * 寮傚父澶勭悊宸ュ叿绫�
+ *
+ * @author L.cm
+ */
+public class Exceptions {
+
+ /**
+ * 灏咰heckedException杞崲涓篣ncheckedException.
+ *
+ * @param e Throwable
+ * @return {RuntimeException}
+ */
+ public static RuntimeException unchecked(Throwable e) {
+ if (e instanceof Error) {
+ throw (Error) e;
+ } else if (e instanceof IllegalAccessException ||
+ e instanceof IllegalArgumentException ||
+ e instanceof NoSuchMethodException) {
+ return new IllegalArgumentException(e);
+ } else if (e instanceof InvocationTargetException) {
+ return new RuntimeException(((InvocationTargetException) e).getTargetException());
+ } else if (e instanceof RuntimeException) {
+ return (RuntimeException) e;
+ } else if (e instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ }
+ return Exceptions.runtime(e);
+ }
+
+ /**
+ * 涓嶉噰鐢� RuntimeException 鍖呰锛岀洿鎺ユ姏鍑猴紝浣垮紓甯告洿鍔犵簿鍑�
+ *
+ * @param throwable Throwable
+ * @param <T> 娉涘瀷鏍囪
+ * @return Throwable
+ * @throws T 娉涘瀷
+ */
+ @SuppressWarnings("unchecked")
+ private static <T extends Throwable> T runtime(Throwable throwable) throws T {
+ throw (T) throwable;
+ }
+
+ /**
+ * 浠g悊寮傚父瑙e寘
+ *
+ * @param wrapped 鍖呰杩囧緱寮傚父
+ * @return 瑙e寘鍚庣殑寮傚父
+ */
+ public static Throwable unwrap(Throwable wrapped) {
+ Throwable unwrapped = wrapped;
+ while (true) {
+ if (unwrapped instanceof InvocationTargetException) {
+ unwrapped = ((InvocationTargetException) unwrapped).getTargetException();
+ } else if (unwrapped instanceof UndeclaredThrowableException) {
+ unwrapped = ((UndeclaredThrowableException) unwrapped).getUndeclaredThrowable();
+ } else {
+ return unwrapped;
+ }
+ }
+ }
+
+ /**
+ * 灏咵rrorStack杞寲涓篠tring.
+ *
+ * @param ex Throwable
+ * @return {String}
+ */
+ public static String getStackTraceAsString(Throwable ex) {
+ FastStringWriter stringWriter = new FastStringWriter();
+ ex.printStackTrace(new PrintWriter(stringWriter));
+ return stringWriter.toString();
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/FileUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/FileUtil.java
new file mode 100644
index 0000000..9b5eed8
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/FileUtil.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.utils;
+
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.FileSystemUtils;
+import org.springframework.util.PatternMatchUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 鏂囦欢宸ュ叿绫�
+ *
+ * @author L.cm
+ */
+public class FileUtil extends org.springframework.util.FileCopyUtils {
+
+ /**
+ * 榛樿涓簍rue
+ *
+ * @author L.cm
+ */
+ public static class TrueFilter implements FileFilter, Serializable {
+ private static final long serialVersionUID = -6420452043795072619L;
+
+ public final static TrueFilter TRUE = new TrueFilter();
+
+ @Override
+ public boolean accept(File pathname) {
+ return true;
+ }
+ }
+
+ /**
+ * 鎵弿鐩綍涓嬬殑鏂囦欢
+ *
+ * @param path 璺緞
+ * @return 鏂囦欢闆嗗悎
+ */
+ public static List<File> list(String path) {
+ File file = new File(path);
+ return list(file, TrueFilter.TRUE);
+ }
+
+ /**
+ * 鎵弿鐩綍涓嬬殑鏂囦欢
+ *
+ * @param path 璺緞
+ * @param fileNamePattern 鏂囦欢鍚� * 鍙�
+ * @return 鏂囦欢闆嗗悎
+ */
+ public static List<File> list(String path, final String fileNamePattern) {
+ File file = new File(path);
+ return list(file, pathname -> {
+ String fileName = pathname.getName();
+ return PatternMatchUtils.simpleMatch(fileNamePattern, fileName);
+ });
+ }
+
+ /**
+ * 鎵弿鐩綍涓嬬殑鏂囦欢
+ *
+ * @param path 璺緞
+ * @param filter 鏂囦欢杩囨护
+ * @return 鏂囦欢闆嗗悎
+ */
+ public static List<File> list(String path, FileFilter filter) {
+ File file = new File(path);
+ return list(file, filter);
+ }
+
+ /**
+ * 鎵弿鐩綍涓嬬殑鏂囦欢
+ *
+ * @param file 鏂囦欢
+ * @return 鏂囦欢闆嗗悎
+ */
+ public static List<File> list(File file) {
+ List<File> fileList = new ArrayList<>();
+ return list(file, fileList, TrueFilter.TRUE);
+ }
+
+ /**
+ * 鎵弿鐩綍涓嬬殑鏂囦欢
+ *
+ * @param file 鏂囦欢
+ * @param fileNamePattern Spring AntPathMatcher 瑙勫垯
+ * @return 鏂囦欢闆嗗悎
+ */
+ public static List<File> list(File file, final String fileNamePattern) {
+ List<File> fileList = new ArrayList<>();
+ return list(file, fileList, pathname -> {
+ String fileName = pathname.getName();
+ return PatternMatchUtils.simpleMatch(fileNamePattern, fileName);
+ });
+ }
+
+ /**
+ * 鎵弿鐩綍涓嬬殑鏂囦欢
+ *
+ * @param file 鏂囦欢
+ * @param filter 鏂囦欢杩囨护
+ * @return 鏂囦欢闆嗗悎
+ */
+ public static List<File> list(File file, FileFilter filter) {
+ List<File> fileList = new ArrayList<>();
+ return list(file, fileList, filter);
+ }
+
+ /**
+ * 鎵弿鐩綍涓嬬殑鏂囦欢
+ *
+ * @param file 鏂囦欢
+ * @param filter 鏂囦欢杩囨护
+ * @return 鏂囦欢闆嗗悎
+ */
+ private static List<File> list(File file, List<File> fileList, FileFilter filter) {
+ if (file.isDirectory()) {
+ File[] files = file.listFiles();
+ if (files != null) {
+ for (File f : files) {
+ list(f, fileList, filter);
+ }
+ }
+ } else {
+ // 杩囨护鏂囦欢
+ boolean accept = filter.accept(file);
+ if (file.exists() && accept) {
+ fileList.add(file);
+ }
+ }
+ return fileList;
+ }
+
+ /**
+ * 鑾峰彇鏂囦欢鍚庣紑鍚�
+ * @param fullName 鏂囦欢鍏ㄥ悕
+ * @return {String}
+ */
+ public static String getFileExtension(String fullName) {
+ if (StringUtil.isBlank(fullName)) return StringPool.EMPTY;
+ String fileName = new File(fullName).getName();
+ int dotIndex = fileName.lastIndexOf(CharPool.DOT);
+ return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1);
+ }
+
+ /**
+ * 鑾峰彇鏂囦欢鍚嶏紝鍘婚櫎鍚庣紑鍚�
+ * @param fullName 鏂囦欢鍏ㄥ悕
+ * @return {String}
+ */
+ public static String getNameWithoutExtension(String fullName) {
+ if (StringUtil.isBlank(fullName)) return StringPool.EMPTY;
+ String fileName = new File(fullName).getName();
+ int dotIndex = fileName.lastIndexOf(CharPool.DOT);
+ return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex);
+ }
+
+ /**
+ * Returns the path to the system temporary directory.
+ *
+ * @return the path to the system temporary directory.
+ */
+ public static String getTempDirPath() {
+ return System.getProperty("java.io.tmpdir");
+ }
+
+ /**
+ * Returns a {@link File} representing the system temporary directory.
+ *
+ * @return the system temporary directory.
+ */
+ public static File getTempDir() {
+ return new File(getTempDirPath());
+ }
+
+ /**
+ * Reads the contents of a file into a String.
+ * The file is always closed.
+ *
+ * @param file the file to read, must not be {@code null}
+ * @return the file contents, never {@code null}
+ */
+ public static String readToString(final File file) {
+ return readToString(file, Charsets.UTF_8);
+ }
+
+ /**
+ * Reads the contents of a file into a String.
+ * The file is always closed.
+ *
+ * @param file the file to read, must not be {@code null}
+ * @param encoding the encoding to use, {@code null} means platform default
+ * @return the file contents, never {@code null}
+ */
+ public static String readToString(final File file, final Charset encoding) {
+ try (InputStream in = Files.newInputStream(file.toPath())) {
+ return IoUtil.readToString(in, encoding);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * Reads the contents of a file into a String.
+ * The file is always closed.
+ *
+ * @param file the file to read, must not be {@code null}
+ * @return the file contents, never {@code null}
+ */
+ public static byte[] readToByteArray(final File file) {
+ try (InputStream in = Files.newInputStream(file.toPath())) {
+ return IoUtil.readToByteArray(in);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * Writes a String to a file creating the file if it does not exist.
+ *
+ * @param file the file to write
+ * @param data the content to write to the file
+ */
+ public static void writeToFile(final File file, final String data) {
+ writeToFile(file, data, Charsets.UTF_8, false);
+ }
+
+ /**
+ * Writes a String to a file creating the file if it does not exist.
+ *
+ * @param file the file to write
+ * @param data the content to write to the file
+ * @param append if {@code true}, then the String will be added to the
+ * end of the file rather than overwriting
+ */
+ public static void writeToFile(final File file, final String data, final boolean append){
+ writeToFile(file, data, Charsets.UTF_8, append);
+ }
+
+ /**
+ * Writes a String to a file creating the file if it does not exist.
+ *
+ * @param file the file to write
+ * @param data the content to write to the file
+ * @param encoding the encoding to use, {@code null} means platform default
+ */
+ public static void writeToFile(final File file, final String data, final Charset encoding) {
+ writeToFile(file, data, encoding, false);
+ }
+
+ /**
+ * Writes a String to a file creating the file if it does not exist.
+ *
+ * @param file the file to write
+ * @param data the content to write to the file
+ * @param encoding the encoding to use, {@code null} means platform default
+ * @param append if {@code true}, then the String will be added to the
+ * end of the file rather than overwriting
+ */
+ public static void writeToFile(final File file, final String data, final Charset encoding, final boolean append) {
+ try (OutputStream out = new FileOutputStream(file, append)) {
+ IoUtil.write(data, out, encoding);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 杞垚file
+ * @param multipartFile MultipartFile
+ * @param file File
+ */
+ public static void toFile(MultipartFile multipartFile, final File file) {
+ try {
+ FileUtil.toFile(multipartFile.getInputStream(), file);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 杞垚file
+ * @param in InputStream
+ * @param file File
+ */
+ public static void toFile(InputStream in, final File file) {
+ try (OutputStream out = new FileOutputStream(file)) {
+ FileUtil.copy(in, out);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * Moves a file.
+ * <p>
+ * When the destination file is on another file system, do a "copy and delete".
+ *
+ * @param srcFile the file to be moved
+ * @param destFile the destination file
+ * @throws NullPointerException if source or destination is {@code null}
+ * @throws IOException if source or destination is invalid
+ * @throws IOException if an IO error occurs moving the file
+ */
+ public static void moveFile(final File srcFile, final File destFile) throws IOException {
+ Assert.notNull(srcFile, "Source must not be null");
+ Assert.notNull(destFile, "Destination must not be null");
+ if (!srcFile.exists()) {
+ throw new FileNotFoundException("Source '" + srcFile + "' does not exist");
+ }
+ if (srcFile.isDirectory()) {
+ throw new IOException("Source '" + srcFile + "' is a directory");
+ }
+ if (destFile.exists()) {
+ throw new IOException("Destination '" + destFile + "' already exists");
+ }
+ if (destFile.isDirectory()) {
+ throw new IOException("Destination '" + destFile + "' is a directory");
+ }
+ final boolean rename = srcFile.renameTo(destFile);
+ if (!rename) {
+ FileUtil.copy(srcFile, destFile);
+ if (!srcFile.delete()) {
+ FileUtil.deleteQuietly(destFile);
+ throw new IOException("Failed to delete original file '" + srcFile + "' after copy to '" + destFile + "'");
+ }
+ }
+ }
+
+ /**
+ * Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories.
+ * <p>
+ * The difference between File.delete() and this method are:
+ * <ul>
+ * <li>A directory to be deleted does not have to be empty.</li>
+ * <li>No exceptions are thrown when a file or directory cannot be deleted.</li>
+ * </ul>
+ *
+ * @param file file or directory to delete, can be {@code null}
+ * @return {@code true} if the file or directory was deleted, otherwise
+ * {@code false}
+ */
+ public static boolean deleteQuietly(@Nullable final File file) {
+ if (file == null) {
+ return false;
+ }
+ try {
+ if (file.isDirectory()) {
+ FileSystemUtils.deleteRecursively(file);
+ }
+ } catch (final Exception ignored) {
+ }
+
+ try {
+ return file.delete();
+ } catch (final Exception ignored) {
+ return false;
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Func.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Func.java
new file mode 100644
index 0000000..e6cac27
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Func.java
@@ -0,0 +1,2156 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springframework.beans.BeansException;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.lang.Nullable;
+import org.springframework.util.PatternMatchUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.method.HandlerMethod;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.nio.charset.Charset;
+import java.text.DecimalFormat;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAccessor;
+import java.util.*;
+import java.util.function.Supplier;
+
+/**
+ * 宸ュ叿鍖呴泦鍚堬紝宸ュ叿绫诲揩鎹锋柟寮�
+ *
+ * @author L.cm
+ */
+public class Func {
+
+ /**
+ * 鏂█锛屽繀椤讳笉鑳戒负 null
+ * <blockquote><pre>
+ * public Foo(Bar bar) {
+ * this.bar = $.requireNotNull(bar);
+ * }
+ * </pre></blockquote>
+ *
+ * @param obj the object reference to check for nullity
+ * @param <T> the type of the reference
+ * @return {@code obj} if not {@code null}
+ * @throws NullPointerException if {@code obj} is {@code null}
+ */
+ public static <T> T requireNotNull(T obj) {
+ return Objects.requireNonNull(obj);
+ }
+
+ /**
+ * 鏂█锛屽繀椤讳笉鑳戒负 null
+ * <blockquote><pre>
+ * public Foo(Bar bar, Baz baz) {
+ * this.bar = $.requireNotNull(bar, "bar must not be null");
+ * this.baz = $.requireNotNull(baz, "baz must not be null");
+ * }
+ * </pre></blockquote>
+ *
+ * @param obj the object reference to check for nullity
+ * @param message detail message to be used in the event that a {@code
+ * NullPointerException} is thrown
+ * @param <T> the type of the reference
+ * @return {@code obj} if not {@code null}
+ * @throws NullPointerException if {@code obj} is {@code null}
+ */
+ public static <T> T requireNotNull(T obj, String message) {
+ return Objects.requireNonNull(obj, message);
+ }
+
+ /**
+ * 鏂█锛屽繀椤讳笉鑳戒负 null
+ * <blockquote><pre>
+ * public Foo(Bar bar, Baz baz) {
+ * this.bar = $.requireNotNull(bar, () -> "bar must not be null");
+ * }
+ * </pre></blockquote>
+ *
+ * @param obj the object reference to check for nullity
+ * @param messageSupplier supplier of the detail message to be
+ * used in the event that a {@code NullPointerException} is thrown
+ * @param <T> the type of the reference
+ * @return {@code obj} if not {@code null}
+ * @throws NullPointerException if {@code obj} is {@code null}
+ */
+ public static <T> T requireNotNull(T obj, Supplier<String> messageSupplier) {
+ return Objects.requireNonNull(obj, messageSupplier);
+ }
+
+ /**
+ * 鍒ゆ柇瀵硅薄鏄惁涓簄ull
+ * <p>
+ * This method exists to be used as a
+ * {@link java.util.function.Predicate}, {@code filter($::isNull)}
+ * </p>
+ *
+ * @param obj a reference to be checked against {@code null}
+ * @return {@code true} if the provided reference is {@code null} otherwise
+ * {@code false}
+ * @see java.util.function.Predicate
+ */
+ public static boolean isNull(@Nullable Object obj) {
+ return Objects.isNull(obj);
+ }
+
+ /**
+ * 鍒ゆ柇瀵硅薄鏄惁 not null
+ * <p>
+ * This method exists to be used as a
+ * {@link java.util.function.Predicate}, {@code filter($::notNull)}
+ * </p>
+ *
+ * @param obj a reference to be checked against {@code null}
+ * @return {@code true} if the provided reference is non-{@code null}
+ * otherwise {@code false}
+ * @see java.util.function.Predicate
+ */
+ public static boolean notNull(@Nullable Object obj) {
+ return Objects.nonNull(obj);
+ }
+
+ /**
+ * 棣栧瓧姣嶅彉灏忓啓
+ *
+ * @param str 瀛楃涓�
+ * @return {String}
+ */
+ public static String firstCharToLower(String str) {
+ return StringUtil.firstCharToLower(str);
+ }
+
+ /**
+ * 棣栧瓧姣嶅彉澶у啓
+ *
+ * @param str 瀛楃涓�
+ * @return {String}
+ */
+ public static String firstCharToUpper(String str) {
+ return StringUtil.firstCharToUpper(str);
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁涓虹┖瀛楃涓�
+ * <pre class="code">
+ * $.isBlank(null) = true
+ * $.isBlank("") = true
+ * $.isBlank(" ") = true
+ * $.isBlank("12345") = false
+ * $.isBlank(" 12345 ") = false
+ * </pre>
+ *
+ * @param cs the {@code CharSequence} to check (may be {@code null})
+ * @return {@code true} if the {@code CharSequence} is not {@code null},
+ * its length is greater than 0, and it does not contain whitespace only
+ * @see Character#isWhitespace
+ */
+ public static boolean isBlank(@Nullable final CharSequence cs) {
+ return StringUtil.isBlank(cs);
+ }
+
+ /**
+ * 鍒ゆ柇涓嶄负绌哄瓧绗︿覆
+ * <pre>
+ * $.isNotBlank(null) = false
+ * $.isNotBlank("") = false
+ * $.isNotBlank(" ") = false
+ * $.isNotBlank("bob") = true
+ * $.isNotBlank(" bob ") = true
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if the CharSequence is
+ * not empty and not null and not whitespace
+ * @see Character#isWhitespace
+ */
+ public static boolean isNotBlank(@Nullable final CharSequence cs) {
+ return StringUtil.isNotBlank(cs);
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁鏈変换鎰忎竴涓� 绌哄瓧绗︿覆
+ *
+ * @param css CharSequence
+ * @return boolean
+ */
+ public static boolean isAnyBlank(final CharSequence... css) {
+ return StringUtil.isAnyBlank(css);
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁鍏ㄤ负闈炵┖瀛楃涓�
+ *
+ * @param css CharSequence
+ * @return boolean
+ */
+ public static boolean isNoneBlank(final CharSequence... css) {
+ return StringUtil.isNoneBlank(css);
+ }
+
+ /**
+ * 鍒ゆ柇瀵硅薄鏄暟缁�
+ *
+ * @param obj the object to check
+ * @return 鏄惁鏁扮粍
+ */
+ public static boolean isArray(@Nullable Object obj) {
+ return ObjectUtil.isArray(obj);
+ }
+
+ /**
+ * 鍒ゆ柇绌哄璞� object銆乵ap銆乴ist銆乻et銆佸瓧绗︿覆銆佹暟缁�
+ *
+ * @param obj the object to check
+ * @return 鏁扮粍鏄惁涓虹┖
+ */
+ public static boolean isEmpty(@Nullable Object obj) {
+ return ObjectUtil.isEmpty(obj);
+ }
+
+ /**
+ * 瀵硅薄涓嶄负绌� object銆乵ap銆乴ist銆乻et銆佸瓧绗︿覆銆佹暟缁�
+ *
+ * @param obj the object to check
+ * @return 鏄惁涓嶄负绌�
+ */
+ public static boolean isNotEmpty(@Nullable Object obj) {
+ return !ObjectUtil.isEmpty(obj);
+ }
+
+ /**
+ * 鍒ゆ柇鏁扮粍涓虹┖
+ *
+ * @param array the array to check
+ * @return 鏁扮粍鏄惁涓虹┖
+ */
+ public static boolean isEmpty(@Nullable Object[] array) {
+ return ObjectUtil.isEmpty(array);
+ }
+
+ /**
+ * 鍒ゆ柇鏁扮粍涓嶄负绌�
+ *
+ * @param array 鏁扮粍
+ * @return 鏁扮粍鏄惁涓嶄负绌�
+ */
+ public static boolean isNotEmpty(@Nullable Object[] array) {
+ return ObjectUtil.isNotEmpty(array);
+ }
+
+ /**
+ * 瀵硅薄缁勪腑鏄惁瀛樺湪 Empty Object
+ *
+ * @param os 瀵硅薄缁�
+ * @return boolean
+ */
+ public static boolean hasEmpty(Object... os) {
+ for (Object o : os) {
+ if (isEmpty(o)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 瀵硅薄缁勪腑鏄惁鍏ㄩ儴涓� Empty Object
+ *
+ * @param os 瀵硅薄缁�
+ * @return boolean
+ */
+ public static boolean isAllEmpty(Object... os) {
+ for (Object o : os) {
+ if (isNotEmpty(o)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆涓壒瀹氭ā寮忕殑瀛楃杞崲鎴恗ap涓搴旂殑鍊�
+ * <p>
+ * use: format("my name is ${name}, and i like ${like}!", {"name":"L.cm", "like": "Java"})
+ *
+ * @param message 闇�瑕佽浆鎹㈢殑瀛楃涓�
+ * @param params 杞崲鎵�闇�鐨勯敭鍊煎闆嗗悎
+ * @return 杞崲鍚庣殑瀛楃涓�
+ */
+ public static String format(@Nullable String message, @Nullable Map<String, ?> params) {
+ return StringUtil.format(message, params);
+ }
+
+ /**
+ * 鍚� log 鏍煎紡鐨� format 瑙勫垯
+ * <p>
+ * use: format("my name is {}, and i like {}!", "L.cm", "Java")
+ *
+ * @param message 闇�瑕佽浆鎹㈢殑瀛楃涓�
+ * @param arguments 闇�瑕佹浛鎹㈢殑鍙橀噺
+ * @return 杞崲鍚庣殑瀛楃涓�
+ */
+ public static String format(@Nullable String message, @Nullable Object... arguments) {
+ return StringUtil.format(message, arguments);
+ }
+
+ /**
+ * 鏍煎紡鍖栨墽琛屾椂闂达紝鍗曚綅涓� ms 鍜� s锛屼繚鐣欎笁浣嶅皬鏁�
+ *
+ * @param nanos 绾崇
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String format(long nanos) {
+ return StringUtil.format(nanos);
+ }
+
+ /**
+ * 姣旇緝涓や釜瀵硅薄鏄惁鐩哥瓑銆�<br>
+ * 鐩稿悓鐨勬潯浠舵湁涓や釜锛屾弧瓒冲叾涓�鍗冲彲锛�<br>
+ *
+ * @param obj1 瀵硅薄1
+ * @param obj2 瀵硅薄2
+ * @return 鏄惁鐩哥瓑
+ */
+ public static boolean equals(Object obj1, Object obj2) {
+ return Objects.equals(obj1, obj2);
+ }
+
+ /**
+ * 瀹夊叏鐨� equals
+ *
+ * @param o1 first Object to compare
+ * @param o2 second Object to compare
+ * @return whether the given objects are equal
+ * @see Object#equals(Object)
+ * @see java.util.Arrays#equals
+ */
+ public static boolean equalsSafe(@Nullable Object o1, @Nullable Object o2) {
+ return ObjectUtil.nullSafeEquals(o1, o2);
+ }
+
+ /**
+ * 鍒ゆ柇鏁扮粍涓槸鍚﹀寘鍚厓绱�
+ *
+ * @param array the Array to check
+ * @param element the element to look for
+ * @param <T> The generic tag
+ * @return {@code true} if found, {@code false} else
+ */
+ public static <T> boolean contains(@Nullable T[] array, final T element) {
+ return CollectionUtil.contains(array, element);
+ }
+
+ /**
+ * 鍒ゆ柇杩唬鍣ㄤ腑鏄惁鍖呭惈鍏冪礌
+ *
+ * @param iterator the Iterator to check
+ * @param element the element to look for
+ * @return {@code true} if found, {@code false} otherwise
+ */
+ public static boolean contains(@Nullable Iterator<?> iterator, Object element) {
+ return CollectionUtil.contains(iterator, element);
+ }
+
+ /**
+ * 鍒ゆ柇鏋氫妇鏄惁鍖呭惈璇ュ厓绱�
+ *
+ * @param enumeration the Enumeration to check
+ * @param element the element to look for
+ * @return {@code true} if found, {@code false} otherwise
+ */
+ public static boolean contains(@Nullable Enumeration<?> enumeration, Object element) {
+ return CollectionUtil.contains(enumeration, element);
+ }
+
+ /**
+ * 涓嶅彲鍙� Set
+ *
+ * @param es 瀵硅薄
+ * @param <E> 娉涘瀷
+ * @return 闆嗗悎
+ */
+ @SafeVarargs
+ public static <E> Set<E> ofImmutableSet(E... es) {
+ return CollectionUtil.ofImmutableSet(es);
+ }
+
+ /**
+ * 涓嶅彲鍙� List
+ *
+ * @param es 瀵硅薄
+ * @param <E> 娉涘瀷
+ * @return 闆嗗悎
+ */
+ @SafeVarargs
+ public static <E> List<E> ofImmutableList(E... es) {
+ return CollectionUtil.ofImmutableList(es);
+ }
+
+ /**
+ * 寮鸿浆string,骞跺幓鎺夊浣欑┖鏍�
+ *
+ * @param str 瀛楃涓�
+ * @return {String}
+ */
+ public static String toStr(Object str) {
+ return toStr(str, "");
+ }
+
+ /**
+ * 寮鸿浆string,骞跺幓鎺夊浣欑┖鏍�
+ *
+ * @param str 瀛楃涓�
+ * @param defaultValue 榛樿鍊�
+ * @return {String}
+ */
+ public static String toStr(Object str, String defaultValue) {
+ if (null == str || str.equals(StringPool.NULL)) {
+ return defaultValue;
+ }
+ return String.valueOf(str);
+ }
+
+ /**
+ * 寮鸿浆string(鍖呭惈绌哄瓧绗︿覆),骞跺幓鎺夊浣欑┖鏍�
+ *
+ * @param str 瀛楃涓�
+ * @param defaultValue 榛樿鍊�
+ * @return {String}
+ */
+ public static String toStrWithEmpty(Object str, String defaultValue) {
+ if (null == str || str.equals(StringPool.NULL) || str.equals(StringPool.EMPTY)) {
+ return defaultValue;
+ }
+ return String.valueOf(str);
+ }
+
+
+ /**
+ * 鍒ゆ柇涓�涓瓧绗︿覆鏄惁鏄暟瀛�
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {boolean}
+ */
+ public static boolean isNumeric(final CharSequence cs) {
+ return StringUtil.isNumeric(cs);
+ }
+
+ /**
+ * 瀛楃涓茶浆 int锛屼负绌哄垯杩斿洖0
+ *
+ * <pre>
+ * $.toInt(null) = 0
+ * $.toInt("") = 0
+ * $.toInt("1") = 1
+ * </pre>
+ *
+ * @param str the string to convert, may be null
+ * @return the int represented by the string, or <code>zero</code> if
+ * conversion fails
+ */
+ public static int toInt(final Object str) {
+ return NumberUtil.toInt(String.valueOf(str));
+ }
+
+ /**
+ * 瀛楃涓茶浆 int锛屼负绌哄垯杩斿洖榛樿鍊�
+ *
+ * <pre>
+ * $.toInt(null, 1) = 1
+ * $.toInt("", 1) = 1
+ * $.toInt("1", 0) = 1
+ * </pre>
+ *
+ * @param str the string to convert, may be null
+ * @param defaultValue the default value
+ * @return the int represented by the string, or the default if conversion fails
+ */
+ public static int toInt(@Nullable final Object str, final int defaultValue) {
+ return NumberUtil.toInt(String.valueOf(str), defaultValue);
+ }
+
+ /**
+ * 瀛楃涓茶浆 long锛屼负绌哄垯杩斿洖0
+ *
+ * <pre>
+ * $.toLong(null) = 0L
+ * $.toLong("") = 0L
+ * $.toLong("1") = 1L
+ * </pre>
+ *
+ * @param str the string to convert, may be null
+ * @return the long represented by the string, or <code>0</code> if
+ * conversion fails
+ */
+ public static long toLong(final Object str) {
+ return NumberUtil.toLong(String.valueOf(str));
+ }
+
+ /**
+ * 瀛楃涓茶浆 long锛屼负绌哄垯杩斿洖榛樿鍊�
+ *
+ * <pre>
+ * $.toLong(null, 1L) = 1L
+ * $.toLong("", 1L) = 1L
+ * $.toLong("1", 0L) = 1L
+ * </pre>
+ *
+ * @param str the string to convert, may be null
+ * @param defaultValue the default value
+ * @return the long represented by the string, or the default if conversion fails
+ */
+ public static long toLong(@Nullable final Object str, final long defaultValue) {
+ return NumberUtil.toLong(String.valueOf(str), defaultValue);
+ }
+
+ /**
+ * <p>Convert a <code>String</code> to an <code>Double</code>, returning a
+ * default value if the conversion fails.</p>
+ *
+ * <p>If the string is <code>null</code>, the default value is returned.</p>
+ *
+ * <pre>
+ * $.toDouble(null, 1) = 1.0
+ * $.toDouble("", 1) = 1.0
+ * $.toDouble("1", 0) = 1.0
+ * </pre>
+ *
+ * @param value the string to convert, may be null
+ * @return the int represented by the string, or the default if conversion fails
+ */
+ public static Double toDouble(Object value) {
+ return toDouble(String.valueOf(value), -1.00);
+ }
+
+ /**
+ * <p>Convert a <code>String</code> to an <code>Double</code>, returning a
+ * default value if the conversion fails.</p>
+ *
+ * <p>If the string is <code>null</code>, the default value is returned.</p>
+ *
+ * <pre>
+ * $.toDouble(null, 1) = 1.0
+ * $.toDouble("", 1) = 1.0
+ * $.toDouble("1", 0) = 1.0
+ * </pre>
+ *
+ * @param value the string to convert, may be null
+ * @param defaultValue the default value
+ * @return the int represented by the string, or the default if conversion fails
+ */
+ public static Double toDouble(Object value, Double defaultValue) {
+ return NumberUtil.toDouble(String.valueOf(value), defaultValue);
+ }
+
+ /**
+ * <p>Convert a <code>String</code> to an <code>Float</code>, returning a
+ * default value if the conversion fails.</p>
+ *
+ * <p>If the string is <code>null</code>, the default value is returned.</p>
+ *
+ * <pre>
+ * $.toFloat(null, 1) = 1.00f
+ * $.toFloat("", 1) = 1.00f
+ * $.toFloat("1", 0) = 1.00f
+ * </pre>
+ *
+ * @param value the string to convert, may be null
+ * @return the int represented by the string, or the default if conversion fails
+ */
+ public static Float toFloat(Object value) {
+ return toFloat(String.valueOf(value), -1.0f);
+ }
+
+ /**
+ * <p>Convert a <code>String</code> to an <code>Float</code>, returning a
+ * default value if the conversion fails.</p>
+ *
+ * <p>If the string is <code>null</code>, the default value is returned.</p>
+ *
+ * <pre>
+ * $.toFloat(null, 1) = 1.00f
+ * $.toFloat("", 1) = 1.00f
+ * $.toFloat("1", 0) = 1.00f
+ * </pre>
+ *
+ * @param value the string to convert, may be null
+ * @param defaultValue the default value
+ * @return the int represented by the string, or the default if conversion fails
+ */
+ public static Float toFloat(Object value, Float defaultValue) {
+ return NumberUtil.toFloat(String.valueOf(value), defaultValue);
+ }
+
+ /**
+ * <p>Convert a <code>String</code> to an <code>Boolean</code>, returning a
+ * default value if the conversion fails.</p>
+ *
+ * <p>If the string is <code>null</code>, the default value is returned.</p>
+ *
+ * <pre>
+ * $.toBoolean("true", true) = true
+ * $.toBoolean("false") = false
+ * $.toBoolean("", false) = false
+ * </pre>
+ *
+ * @param value the string to convert, may be null
+ * @return the int represented by the string, or the default if conversion fails
+ */
+ public static Boolean toBoolean(Object value) {
+ return toBoolean(value, null);
+ }
+
+ /**
+ * <p>Convert a <code>String</code> to an <code>Boolean</code>, returning a
+ * default value if the conversion fails.</p>
+ *
+ * <p>If the string is <code>null</code>, the default value is returned.</p>
+ *
+ * <pre>
+ * $.toBoolean("true", true) = true
+ * $.toBoolean("false") = false
+ * $.toBoolean("", false) = false
+ * </pre>
+ *
+ * @param value the string to convert, may be null
+ * @param defaultValue the default value
+ * @return the int represented by the string, or the default if conversion fails
+ */
+ public static Boolean toBoolean(Object value, Boolean defaultValue) {
+ if (value != null) {
+ String val = String.valueOf(value);
+ val = val.toLowerCase().trim();
+ return Boolean.parseBoolean(val);
+ }
+ return defaultValue;
+ }
+
+ /**
+ * 杞崲涓篒nteger鏁扮粍<br>
+ *
+ * @param str 琚浆鎹㈢殑鍊�
+ * @return 缁撴灉
+ */
+ public static Integer[] toIntArray(String str) {
+ return toIntArray(",", str);
+ }
+
+ /**
+ * 杞崲涓篒nteger鏁扮粍<br>
+ *
+ * @param split 鍒嗛殧绗�
+ * @param str 琚浆鎹㈢殑鍊�
+ * @return 缁撴灉
+ */
+ public static Integer[] toIntArray(String split, String str) {
+ if (StringUtil.isEmpty(str)) {
+ return new Integer[]{};
+ }
+ String[] arr = str.split(split);
+ final Integer[] ints = new Integer[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ final Integer v = toInt(arr[i], 0);
+ ints[i] = v;
+ }
+ return ints;
+ }
+
+ /**
+ * 杞崲涓篒nteger闆嗗悎<br>
+ *
+ * @param str 缁撴灉琚浆鎹㈢殑鍊�
+ * @return 缁撴灉
+ */
+ public static List<Integer> toIntList(String str) {
+ return Arrays.asList(toIntArray(str));
+ }
+
+ /**
+ * 杞崲涓篒nteger闆嗗悎<br>
+ *
+ * @param split 鍒嗛殧绗�
+ * @param str 琚浆鎹㈢殑鍊�
+ * @return 缁撴灉
+ */
+ public static List<Integer> toIntList(String split, String str) {
+ return Arrays.asList(toIntArray(split, str));
+ }
+
+ /**
+ * 鑾峰彇绗竴浣岻nteger鏁板��
+ *
+ * @param str 琚浆鎹㈢殑鍊�
+ * @return 缁撴灉
+ */
+ public static Integer firstInt(String str) {
+ return firstInt(",", str);
+ }
+
+ /**
+ * 鑾峰彇绗竴浣岻nteger鏁板��
+ *
+ * @param split 鍒嗛殧绗�
+ * @param str 琚浆鎹㈢殑鍊�
+ * @return 缁撴灉
+ */
+ public static Integer firstInt(String split, String str) {
+ List<Integer> ints = toIntList(split, str);
+ if (isEmpty(ints)) {
+ return null;
+ } else {
+ return ints.get(0);
+ }
+ }
+
+ /**
+ * 杞崲涓篖ong鏁扮粍<br>
+ *
+ * @param str 琚浆鎹㈢殑鍊�
+ * @return 缁撴灉
+ */
+ public static Long[] toLongArray(String str) {
+ return toLongArray(",", str);
+ }
+
+ /**
+ * 杞崲涓篖ong鏁扮粍<br>
+ *
+ * @param split 鍒嗛殧绗�
+ * @param str 琚浆鎹㈢殑鍊�
+ * @return 缁撴灉
+ */
+ public static Long[] toLongArray(String split, String str) {
+ if (StringUtil.isEmpty(str)) {
+ return new Long[]{};
+ }
+ String[] arr = str.split(split);
+ final Long[] longs = new Long[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ final Long v = toLong(arr[i], 0);
+ longs[i] = v;
+ }
+ return longs;
+ }
+
+ /**
+ * 杞崲涓篖ong闆嗗悎<br>
+ *
+ * @param str 缁撴灉琚浆鎹㈢殑鍊�
+ * @return 缁撴灉
+ */
+ public static List<Long> toLongList(String str) {
+ return Arrays.asList(toLongArray(str));
+ }
+
+ /**
+ * 杞崲涓篖ong闆嗗悎<br>
+ *
+ * @param split 鍒嗛殧绗�
+ * @param str 琚浆鎹㈢殑鍊�
+ * @return 缁撴灉
+ */
+ public static List<Long> toLongList(String split, String str) {
+ return Arrays.asList(toLongArray(split, str));
+ }
+
+ /**
+ * 鑾峰彇绗竴浣峀ong鏁板��
+ *
+ * @param str 琚浆鎹㈢殑鍊�
+ * @return 缁撴灉
+ */
+ public static Long firstLong(String str) {
+ return firstLong(",", str);
+ }
+
+ /**
+ * 鑾峰彇绗竴浣峀ong鏁板��
+ *
+ * @param split 鍒嗛殧绗�
+ * @param str 琚浆鎹㈢殑鍊�
+ * @return 缁撴灉
+ */
+ public static Long firstLong(String split, String str) {
+ List<Long> longs = toLongList(split, str);
+ if (isEmpty(longs)) {
+ return null;
+ } else {
+ return longs.get(0);
+ }
+ }
+
+ /**
+ * 杞崲涓篠tring鏁扮粍<br>
+ *
+ * @param str 琚浆鎹㈢殑鍊�
+ * @return 缁撴灉
+ */
+ public static String[] toStrArray(String str) {
+ return toStrArray(",", str);
+ }
+
+ /**
+ * 杞崲涓篠tring鏁扮粍<br>
+ *
+ * @param split 鍒嗛殧绗�
+ * @param str 琚浆鎹㈢殑鍊�
+ * @return 缁撴灉
+ */
+ public static String[] toStrArray(String split, String str) {
+ if (isBlank(str)) {
+ return new String[]{};
+ }
+ return str.split(split);
+ }
+
+ /**
+ * 杞崲涓篠tring闆嗗悎<br>
+ *
+ * @param str 缁撴灉琚浆鎹㈢殑鍊�
+ * @return 缁撴灉
+ */
+ public static List<String> toStrList(String str) {
+ return Arrays.asList(toStrArray(str));
+ }
+
+ /**
+ * 杞崲涓篠tring闆嗗悎<br>
+ *
+ * @param split 鍒嗛殧绗�
+ * @param str 琚浆鎹㈢殑鍊�
+ * @return 缁撴灉
+ */
+ public static List<String> toStrList(String split, String str) {
+ return Arrays.asList(toStrArray(split, str));
+ }
+
+ /**
+ * 鑾峰彇绗竴浣峉tring鏁板��
+ *
+ * @param str 琚浆鎹㈢殑鍊�
+ * @return 缁撴灉
+ */
+ public static String firstStr(String str) {
+ return firstStr(",", str);
+ }
+
+ /**
+ * 鑾峰彇绗竴浣峉tring鏁板��
+ *
+ * @param split 鍒嗛殧绗�
+ * @param str 琚浆鎹㈢殑鍊�
+ * @return 缁撴灉
+ */
+ public static String firstStr(String split, String str) {
+ List<String> strs = toStrList(split, str);
+ if (isEmpty(strs)) {
+ return null;
+ } else {
+ return strs.get(0);
+ }
+ }
+
+ /**
+ * 灏� long 杞煭瀛楃涓� 涓� 62 杩涘埗
+ *
+ * @param num 鏁板瓧
+ * @return 鐭瓧绗︿覆
+ */
+ public static String to62String(long num) {
+ return NumberUtil.to62String(num);
+ }
+
+ /**
+ * 灏嗛泦鍚堟嫾鎺ユ垚瀛楃涓诧紝榛樿浣跨敤`,`鎷兼帴
+ *
+ * @param coll the {@code Collection} to convert
+ * @return the delimited {@code String}
+ */
+ public static String join(Collection<?> coll) {
+ return StringUtil.join(coll);
+ }
+
+ /**
+ * 灏嗛泦鍚堟嫾鎺ユ垚瀛楃涓诧紝榛樿鎸囧畾鍒嗛殧绗�
+ *
+ * @param coll the {@code Collection} to convert
+ * @param delim the delimiter to use (typically a ",")
+ * @return the delimited {@code String}
+ */
+ public static String join(Collection<?> coll, String delim) {
+ return StringUtil.join(coll, delim);
+ }
+
+ /**
+ * 灏嗘暟缁勬嫾鎺ユ垚瀛楃涓诧紝榛樿浣跨敤`,`鎷兼帴
+ *
+ * @param arr the array to display
+ * @return the delimited {@code String}
+ */
+ public static String join(Object[] arr) {
+ return StringUtil.join(arr);
+ }
+
+ /**
+ * 灏嗘暟缁勬嫾鎺ユ垚瀛楃涓诧紝榛樿鎸囧畾鍒嗛殧绗�
+ *
+ * @param arr the array to display
+ * @param delim the delimiter to use (typically a ",")
+ * @return the delimited {@code String}
+ */
+ public static String join(Object[] arr, String delim) {
+ return StringUtil.join(arr, delim);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓诧紝涓嶅幓闄ゅ垏鍒嗗悗姣忎釜鍏冪礌涓よ竟鐨勭┖鐧界锛屼笉鍘婚櫎绌虹櫧椤�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ */
+ public static List<String> split(CharSequence str, char separator) {
+ return StringUtil.split(str, separator, -1);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓诧紝鍘婚櫎鍒囧垎鍚庢瘡涓厓绱犱袱杈圭殑绌虹櫧绗︼紝鍘婚櫎绌虹櫧椤�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ */
+ public static List<String> splitTrim(CharSequence str, char separator) {
+ return StringUtil.splitTrim(str, separator);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓诧紝鍘婚櫎鍒囧垎鍚庢瘡涓厓绱犱袱杈圭殑绌虹櫧绗︼紝鍘婚櫎绌虹櫧椤�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ */
+ public static List<String> splitTrim(CharSequence str, CharSequence separator) {
+ return StringUtil.splitTrim(str, separator);
+ }
+
+ /**
+ * 鍒嗗壊 瀛楃涓�
+ *
+ * @param str 瀛楃涓�
+ * @param delimiter 鍒嗗壊绗�
+ * @return 瀛楃涓叉暟缁�
+ */
+ public static String[] split(@Nullable String str, @Nullable String delimiter) {
+ return StringUtil.delimitedListToStringArray(str, delimiter);
+ }
+
+ /**
+ * 鍒嗗壊 瀛楃涓� 鍒犻櫎甯歌 绌虹櫧绗�
+ *
+ * @param str 瀛楃涓�
+ * @param delimiter 鍒嗗壊绗�
+ * @return 瀛楃涓叉暟缁�
+ */
+ public static String[] splitTrim(@Nullable String str, @Nullable String delimiter) {
+ return StringUtil.delimitedListToStringArray(str, delimiter, " \t\n\n\f");
+ }
+
+ /**
+ * 瀛楃涓叉槸鍚︾鍚堟寚瀹氱殑 琛ㄨ揪寮�
+ *
+ * <p>
+ * pattern styles: "xxx*", "*xxx", "*xxx*" and "xxx*yyy"
+ * </p>
+ *
+ * @param pattern 琛ㄨ揪寮�
+ * @param str 瀛楃涓�
+ * @return 鏄惁鍖归厤
+ */
+ public static boolean simpleMatch(@Nullable String pattern, @Nullable String str) {
+ return PatternMatchUtils.simpleMatch(pattern, str);
+ }
+
+ /**
+ * 瀛楃涓叉槸鍚︾鍚堟寚瀹氱殑 琛ㄨ揪寮�
+ *
+ * <p>
+ * pattern styles: "xxx*", "*xxx", "*xxx*" and "xxx*yyy"
+ * </p>
+ *
+ * @param patterns 琛ㄨ揪寮� 鏁扮粍
+ * @param str 瀛楃涓�
+ * @return 鏄惁鍖归厤
+ */
+ public static boolean simpleMatch(@Nullable String[] patterns, String str) {
+ return PatternMatchUtils.simpleMatch(patterns, str);
+ }
+
+ /**
+ * 鐢熸垚uuid
+ *
+ * @return UUID
+ */
+ public static String randomUUID() {
+ return StringUtil.randomUUID();
+ }
+
+ /**
+ * 杞箟HTML鐢ㄤ簬瀹夊叏杩囨护
+ *
+ * @param html html
+ * @return {String}
+ */
+ public static String escapeHtml(String html) {
+ return StringUtil.escapeHtml(html);
+ }
+
+ /**
+ * 闅忔満鏁扮敓鎴�
+ *
+ * @param count 瀛楃闀垮害
+ * @return 闅忔満鏁�
+ */
+ public static String random(int count) {
+ return StringUtil.random(count);
+ }
+
+ /**
+ * 闅忔満鏁扮敓鎴�
+ *
+ * @param count 瀛楃闀垮害
+ * @param randomType 闅忔満鏁扮被鍒�
+ * @return 闅忔満鏁�
+ */
+ public static String random(int count, RandomType randomType) {
+ return StringUtil.random(count, randomType);
+ }
+
+ /**
+ * 瀛楃涓插簭鍒楀寲鎴� md5
+ *
+ * @param data Data to digest
+ * @return MD5 digest as a hex string
+ */
+ public static String md5Hex(final String data) {
+ return DigestUtil.md5Hex(data);
+ }
+
+ /**
+ * 鏁扮粍搴忓垪鍖栨垚 md5
+ *
+ * @param bytes the bytes to calculate the digest over
+ * @return md5 digest string
+ */
+ public static String md5Hex(final byte[] bytes) {
+ return DigestUtil.md5Hex(bytes);
+ }
+
+
+ /**
+ * sha1Hex
+ *
+ * @param data Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha1Hex(String data) {
+ return DigestUtil.sha1Hex(data);
+ }
+
+ /**
+ * sha1Hex
+ *
+ * @param bytes Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha1Hex(final byte[] bytes) {
+ return DigestUtil.sha1Hex(bytes);
+ }
+
+ /**
+ * SHA224Hex
+ *
+ * @param data Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha224Hex(String data) {
+ return DigestUtil.sha224Hex(data);
+ }
+
+ /**
+ * SHA224Hex
+ *
+ * @param bytes Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha224Hex(final byte[] bytes) {
+ return DigestUtil.sha224Hex(bytes);
+ }
+
+ /**
+ * sha256Hex
+ *
+ * @param data Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha256Hex(String data) {
+ return DigestUtil.sha256Hex(data);
+ }
+
+ /**
+ * sha256Hex
+ *
+ * @param bytes Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha256Hex(final byte[] bytes) {
+ return DigestUtil.sha256Hex(bytes);
+ }
+
+ /**
+ * sha384Hex
+ *
+ * @param data Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha384Hex(String data) {
+ return DigestUtil.sha384Hex(data);
+ }
+
+ /**
+ * sha384Hex
+ *
+ * @param bytes Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha384Hex(final byte[] bytes) {
+ return DigestUtil.sha384Hex(bytes);
+ }
+
+ /**
+ * sha512Hex
+ *
+ * @param data Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha512Hex(String data) {
+ return DigestUtil.sha512Hex(data);
+ }
+
+ /**
+ * sha512Hex
+ *
+ * @param bytes Data to digest
+ * @return digest as a hex string
+ */
+ public static String sha512Hex(final byte[] bytes) {
+ return DigestUtil.sha512Hex(bytes);
+ }
+
+ /**
+ * hmacMd5 Hex
+ *
+ * @param data Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacMd5Hex(String data, String key) {
+ return DigestUtil.hmacMd5Hex(data, key);
+ }
+
+ /**
+ * hmacMd5 Hex
+ *
+ * @param bytes Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacMd5Hex(final byte[] bytes, String key) {
+ return DigestUtil.hmacMd5Hex(bytes, key);
+ }
+
+ /**
+ * hmacSha1 Hex
+ *
+ * @param data Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha1Hex(String data, String key) {
+ return DigestUtil.hmacSha1Hex(data, key);
+ }
+
+ /**
+ * hmacSha1 Hex
+ *
+ * @param bytes Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha1Hex(final byte[] bytes, String key) {
+ return DigestUtil.hmacSha1Hex(bytes, key);
+ }
+
+ /**
+ * hmacSha224 Hex
+ *
+ * @param data Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha224Hex(String data, String key) {
+ return DigestUtil.hmacSha224Hex(data, key);
+ }
+
+ /**
+ * hmacSha224 Hex
+ *
+ * @param bytes Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha224Hex(final byte[] bytes, String key) {
+ return DigestUtil.hmacSha224Hex(bytes, key);
+ }
+
+ /**
+ * hmacSha256 Hex
+ *
+ * @param data Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha256Hex(String data, String key) {
+ return DigestUtil.hmacSha256Hex(data, key);
+ }
+
+ /**
+ * hmacSha256 Hex
+ *
+ * @param bytes Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha256Hex(final byte[] bytes, String key) {
+ return DigestUtil.hmacSha256Hex(bytes, key);
+ }
+
+ /**
+ * hmacSha384 Hex
+ *
+ * @param data Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha384Hex(String data, String key) {
+ return DigestUtil.hmacSha384Hex(data, key);
+ }
+
+ /**
+ * hmacSha384 Hex
+ *
+ * @param bytes Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha384Hex(final byte[] bytes, String key) {
+ return DigestUtil.hmacSha384Hex(bytes, key);
+ }
+
+ /**
+ * hmacSha512 Hex
+ *
+ * @param data Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha512Hex(String data, String key) {
+ return DigestUtil.hmacSha512Hex(data, key);
+ }
+
+ /**
+ * hmacSha512 Hex
+ *
+ * @param bytes Data to digest
+ * @param key key
+ * @return digest as a hex string
+ */
+ public static String hmacSha512Hex(final byte[] bytes, String key) {
+ return DigestUtil.hmacSha512Hex(bytes, key);
+ }
+
+ /**
+ * byte 鏁扮粍搴忓垪鍖栨垚 hex
+ *
+ * @param bytes bytes to encode
+ * @return MD5 digest as a hex string
+ */
+ public static String encodeHex(byte[] bytes) {
+ return DigestUtil.encodeHex(bytes);
+ }
+
+ /**
+ * 瀛楃涓插弽搴忓垪鍖栨垚 hex
+ *
+ * @param hexString String to decode
+ * @return MD5 digest as a hex string
+ */
+ public static byte[] decodeHex(final String hexString) {
+ return DigestUtil.decodeHex(hexString);
+ }
+
+ /**
+ * Base64缂栫爜
+ *
+ * @param value 瀛楃涓�
+ * @return {String}
+ */
+ public static String encodeBase64(String value) {
+ return Base64Util.encode(value);
+ }
+
+ /**
+ * Base64缂栫爜
+ *
+ * @param value 瀛楃涓�
+ * @param charset 瀛楃闆�
+ * @return {String}
+ */
+ public static String encodeBase64(String value, Charset charset) {
+ return Base64Util.encode(value, charset);
+ }
+
+ /**
+ * Base64缂栫爜涓篣RL瀹夊叏
+ *
+ * @param value 瀛楃涓�
+ * @return {String}
+ */
+ public static String encodeBase64UrlSafe(String value) {
+ return Base64Util.encodeUrlSafe(value);
+ }
+
+ /**
+ * Base64缂栫爜涓篣RL瀹夊叏
+ *
+ * @param value 瀛楃涓�
+ * @param charset 瀛楃闆�
+ * @return {String}
+ */
+ public static String encodeBase64UrlSafe(String value, Charset charset) {
+ return Base64Util.encodeUrlSafe(value, charset);
+ }
+
+ /**
+ * Base64瑙g爜
+ *
+ * @param value 瀛楃涓�
+ * @return {String}
+ */
+ public static String decodeBase64(String value) {
+ return Base64Util.decode(value);
+ }
+
+ /**
+ * Base64瑙g爜
+ *
+ * @param value 瀛楃涓�
+ * @param charset 瀛楃闆�
+ * @return {String}
+ */
+ public static String decodeBase64(String value, Charset charset) {
+ return Base64Util.decode(value, charset);
+ }
+
+ /**
+ * Base64URL瀹夊叏瑙g爜
+ *
+ * @param value 瀛楃涓�
+ * @return {String}
+ */
+ public static String decodeBase64UrlSafe(String value) {
+ return Base64Util.decodeUrlSafe(value);
+ }
+
+ /**
+ * Base64URL瀹夊叏瑙g爜
+ *
+ * @param value 瀛楃涓�
+ * @param charset 瀛楃闆�
+ * @return {String}
+ */
+ public static String decodeBase64UrlSafe(String value, Charset charset) {
+ return Base64Util.decodeUrlSafe(value, charset);
+ }
+
+ /**
+ * 鍏抽棴 Closeable
+ *
+ * @param closeable 鑷姩鍏抽棴
+ */
+ public static void closeQuietly(@Nullable Closeable closeable) {
+ IoUtil.closeQuietly(closeable);
+ }
+
+ /**
+ * InputStream to String utf-8
+ *
+ * @param input the <code>InputStream</code> to read from
+ * @return the requested String
+ * @throws NullPointerException if the input is null
+ */
+ public static String readToString(InputStream input) {
+ return IoUtil.readToString(input);
+ }
+
+ /**
+ * InputStream to String
+ *
+ * @param input the <code>InputStream</code> to read from
+ * @param charset the <code>Charset</code>
+ * @return the requested String
+ * @throws NullPointerException if the input is null
+ */
+ public static String readToString(@Nullable InputStream input, Charset charset) {
+ return IoUtil.readToString(input, charset);
+ }
+
+ /**
+ * InputStream to bytes 鏁扮粍
+ *
+ * @param input InputStream
+ * @return the requested byte array
+ */
+ public static byte[] readToByteArray(@Nullable InputStream input) {
+ return IoUtil.readToByteArray(input);
+ }
+
+ /**
+ * 璇诲彇鏂囦欢涓哄瓧绗︿覆
+ *
+ * @param file the file to read, must not be {@code null}
+ * @return the file contents, never {@code null}
+ */
+ public static String readToString(final File file) {
+ return FileUtil.readToString(file);
+ }
+
+ /**
+ * 璇诲彇鏂囦欢涓哄瓧绗︿覆
+ *
+ * @param file the file to read, must not be {@code null}
+ * @param encoding the encoding to use, {@code null} means platform default
+ * @return the file contents, never {@code null}
+ */
+ public static String readToString(File file, Charset encoding) {
+ return FileUtil.readToString(file, encoding);
+ }
+
+ /**
+ * 璇诲彇鏂囦欢涓� byte 鏁扮粍
+ *
+ * @param file the file to read, must not be {@code null}
+ * @return the file contents, never {@code null}
+ */
+ public static byte[] readToByteArray(File file) {
+ return FileUtil.readToByteArray(file);
+ }
+
+ /**
+ * 灏嗗璞″簭鍒楀寲鎴恓son瀛楃涓�
+ *
+ * @param object javaBean
+ * @return jsonString json瀛楃涓�
+ */
+ public static String toJson(Object object) {
+ return JsonUtil.toJson(object);
+ }
+
+ /**
+ * 灏嗗璞″簭鍒楀寲鎴� json byte 鏁扮粍
+ *
+ * @param object javaBean
+ * @return jsonString json瀛楃涓�
+ */
+ public static byte[] toJsonAsBytes(Object object) {
+ return JsonUtil.toJsonAsBytes(object);
+ }
+
+ /**
+ * 灏唈son瀛楃涓茶浆鎴� JsonNode
+ *
+ * @param jsonString jsonString
+ * @return jsonString json瀛楃涓�
+ */
+ public static JsonNode readTree(String jsonString) {
+ return JsonUtil.readTree(jsonString);
+ }
+
+ /**
+ * 灏唈son瀛楃涓茶浆鎴� JsonNode
+ *
+ * @param in InputStream
+ * @return jsonString json瀛楃涓�
+ */
+ public static JsonNode readTree(InputStream in) {
+ return JsonUtil.readTree(in);
+ }
+
+ /**
+ * 灏唈son瀛楃涓茶浆鎴� JsonNode
+ *
+ * @param content content
+ * @return jsonString json瀛楃涓�
+ */
+ public static JsonNode readTree(byte[] content) {
+ return JsonUtil.readTree(content);
+ }
+
+ /**
+ * 灏唈son瀛楃涓茶浆鎴� JsonNode
+ *
+ * @param jsonParser JsonParser
+ * @return jsonString json瀛楃涓�
+ */
+ public static JsonNode readTree(JsonParser jsonParser) {
+ return JsonUtil.readTree(jsonParser);
+ }
+
+ /**
+ * 灏唈son byte 鏁扮粍鍙嶅簭鍒楀寲鎴愬璞�
+ *
+ * @param bytes json bytes
+ * @param valueType class
+ * @param <T> T 娉涘瀷鏍囪
+ * @return Bean
+ */
+ public static <T> T readJson(byte[] bytes, Class<T> valueType) {
+ return JsonUtil.parse(bytes, valueType);
+ }
+
+ /**
+ * 灏唈son鍙嶅簭鍒楀寲鎴愬璞�
+ *
+ * @param jsonString jsonString
+ * @param valueType class
+ * @param <T> T 娉涘瀷鏍囪
+ * @return Bean
+ */
+ public static <T> T readJson(String jsonString, Class<T> valueType) {
+ return JsonUtil.parse(jsonString, valueType);
+ }
+
+ /**
+ * 灏唈son鍙嶅簭鍒楀寲鎴愬璞�
+ *
+ * @param in InputStream
+ * @param valueType class
+ * @param <T> T 娉涘瀷鏍囪
+ * @return Bean
+ */
+ public static <T> T readJson(InputStream in, Class<T> valueType) {
+ return JsonUtil.parse(in, valueType);
+ }
+
+ /**
+ * 灏唈son鍙嶅簭鍒楀寲鎴愬璞�
+ *
+ * @param bytes bytes
+ * @param typeReference 娉涘瀷绫诲瀷
+ * @param <T> T 娉涘瀷鏍囪
+ * @return Bean
+ */
+ public static <T> T readJson(byte[] bytes, TypeReference<T> typeReference) {
+ return JsonUtil.parse(bytes, typeReference);
+ }
+
+ /**
+ * 灏唈son鍙嶅簭鍒楀寲鎴愬璞�
+ *
+ * @param jsonString jsonString
+ * @param typeReference 娉涘瀷绫诲瀷
+ * @param <T> T 娉涘瀷鏍囪
+ * @return Bean
+ */
+ public static <T> T readJson(String jsonString, TypeReference<T> typeReference) {
+ return JsonUtil.parse(jsonString, typeReference);
+ }
+
+ /**
+ * 灏唈son鍙嶅簭鍒楀寲鎴愬璞�
+ *
+ * @param in InputStream
+ * @param typeReference 娉涘瀷绫诲瀷
+ * @param <T> T 娉涘瀷鏍囪
+ * @return Bean
+ */
+ public static <T> T readJson(InputStream in, TypeReference<T> typeReference) {
+ return JsonUtil.parse(in, typeReference);
+ }
+
+ /**
+ * url 缂栫爜
+ *
+ * @param source the String to be encoded
+ * @return the encoded String
+ */
+ public static String urlEncode(String source) {
+ return UrlUtil.encode(source, Charsets.UTF_8);
+ }
+
+ /**
+ * url 缂栫爜
+ *
+ * @param source the String to be encoded
+ * @param charset the character encoding to encode to
+ * @return the encoded String
+ */
+ public static String urlEncode(String source, Charset charset) {
+ return UrlUtil.encode(source, charset);
+ }
+
+ /**
+ * url 瑙g爜
+ *
+ * @param source the encoded String
+ * @return the decoded value
+ * @throws IllegalArgumentException when the given source contains invalid encoded sequences
+ * @see StringUtils#uriDecode(String, Charset)
+ * @see java.net.URLDecoder#decode(String, String)
+ */
+ public static String urlDecode(String source) {
+ return StringUtils.uriDecode(source, Charsets.UTF_8);
+ }
+
+ /**
+ * url 瑙g爜
+ *
+ * @param source the encoded String
+ * @param charset the character encoding to use
+ * @return the decoded value
+ * @throws IllegalArgumentException when the given source contains invalid encoded sequences
+ * @see StringUtils#uriDecode(String, Charset)
+ * @see java.net.URLDecoder#decode(String, String)
+ */
+ public static String urlDecode(String source, Charset charset) {
+ return StringUtils.uriDecode(source, charset);
+ }
+
+ /**
+ * 鏃ユ湡鏃堕棿鏍煎紡鍖�
+ *
+ * @param date 鏃堕棿
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String formatDateTime(Date date) {
+ return DateUtil.formatDateTime(date);
+ }
+
+ /**
+ * 鏃ユ湡鏍煎紡鍖�
+ *
+ * @param date 鏃堕棿
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String formatDate(Date date) {
+ return DateUtil.formatDate(date);
+ }
+
+ /**
+ * 鏃堕棿鏍煎紡鍖�
+ *
+ * @param date 鏃堕棿
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String formatTime(Date date) {
+ return DateUtil.formatTime(date);
+ }
+
+ /**
+ * 瀵硅薄鏍煎紡鍖� 鏀寔鏁板瓧锛宒ate锛宩ava8鏃堕棿
+ *
+ * @param object 鏍煎紡鍖栧璞�
+ * @param pattern 琛ㄨ揪寮�
+ * @return 鏍煎紡鍖栧悗鐨勫瓧绗︿覆
+ */
+ public static String format(Object object, String pattern) {
+ if (object instanceof Number) {
+ DecimalFormat decimalFormat = new DecimalFormat(pattern);
+ return decimalFormat.format(object);
+ } else if (object instanceof Date) {
+ return DateUtil.format((Date) object, pattern);
+ } else if (object instanceof TemporalAccessor) {
+ return DateTimeUtil.format((TemporalAccessor) object, pattern);
+ }
+ throw new IllegalArgumentException("鏈敮鎸佺殑瀵硅薄:" + object + ",鏍煎紡:" + object);
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘椂闂�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @param pattern 琛ㄨ揪寮�
+ * @return 鏃堕棿
+ */
+ public static Date parseDate(String dateStr, String pattern) {
+ return DateUtil.parse(dateStr, pattern);
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘椂闂�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @param format ConcurrentDateFormat
+ * @return 鏃堕棿
+ */
+ public static Date parse(String dateStr, ConcurrentDateFormat format) {
+ return DateUtil.parse(dateStr, format);
+ }
+
+ /**
+ * 鏃ユ湡鏃堕棿鏍煎紡鍖�
+ *
+ * @param temporal 鏃堕棿
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String formatDateTime(TemporalAccessor temporal) {
+ return DateTimeUtil.formatDateTime(temporal);
+ }
+
+ /**
+ * 鏃ユ湡鏃堕棿鏍煎紡鍖�
+ *
+ * @param temporal 鏃堕棿
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String formatDate(TemporalAccessor temporal) {
+ return DateTimeUtil.formatDate(temporal);
+ }
+
+ /**
+ * 鏃堕棿鏍煎紡鍖�
+ *
+ * @param temporal 鏃堕棿
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String formatTime(TemporalAccessor temporal) {
+ return DateTimeUtil.formatTime(temporal);
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘椂闂�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @param formatter DateTimeFormatter
+ * @return 鏃堕棿
+ */
+ public static LocalDateTime parseDateTime(String dateStr, DateTimeFormatter formatter) {
+ return DateTimeUtil.parseDateTime(dateStr, formatter);
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘椂闂�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @return 鏃堕棿
+ */
+ public static LocalDateTime parseDateTime(String dateStr) {
+ return DateTimeUtil.parseDateTime(dateStr);
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘椂闂�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @param formatter DateTimeFormatter
+ * @return 鏃堕棿
+ */
+ public static LocalDate parseDate(String dateStr, DateTimeFormatter formatter) {
+ return DateTimeUtil.parseDate(dateStr, formatter);
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘棩鏈�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @return 鏃堕棿
+ */
+ public static LocalDate parseDate(String dateStr) {
+ return DateTimeUtil.parseDate(dateStr, DateTimeUtil.DATE_FORMAT);
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘椂闂�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @param formatter DateTimeFormatter
+ * @return 鏃堕棿
+ */
+ public static LocalTime parseTime(String dateStr, DateTimeFormatter formatter) {
+ return DateTimeUtil.parseTime(dateStr, formatter);
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆杞崲涓烘椂闂�
+ *
+ * @param dateStr 鏃堕棿瀛楃涓�
+ * @return 鏃堕棿
+ */
+ public static LocalTime parseTime(String dateStr) {
+ return DateTimeUtil.parseTime(dateStr);
+ }
+
+ /**
+ * 鏃堕棿姣旇緝
+ *
+ * @param startInclusive the start instant, inclusive, not null
+ * @param endExclusive the end instant, exclusive, not null
+ * @return a {@code Duration}, not null
+ */
+ public static Duration between(Temporal startInclusive, Temporal endExclusive) {
+ return Duration.between(startInclusive, endExclusive);
+ }
+
+ /**
+ * 姣旇緝2涓� 鏃堕棿宸�
+ *
+ * @param startDate 寮�濮嬫椂闂�
+ * @param endDate 缁撴潫鏃堕棿
+ * @return 鏃堕棿闂撮殧
+ */
+ public static Duration between(Date startDate, Date endDate) {
+ return DateUtil.between(startDate, endDate);
+ }
+
+ /**
+ * 瀵硅薄绫诲瀷杞崲
+ *
+ * @param source the source object
+ * @param targetType the target type
+ * @param <T> 娉涘瀷鏍囪
+ * @return the converted value
+ * @throws IllegalArgumentException if targetType is {@code null},
+ * or sourceType is {@code null} but source is not {@code null}
+ */
+ @Nullable
+ public static <T> T convert(@Nullable Object source, Class<T> targetType) {
+ return ConvertUtil.convert(source, targetType);
+ }
+
+ /**
+ * 瀵硅薄绫诲瀷杞崲
+ *
+ * @param source the source object
+ * @param sourceType the source type
+ * @param targetType the target type
+ * @param <T> 娉涘瀷鏍囪
+ * @return the converted value
+ * @throws IllegalArgumentException if targetType is {@code null},
+ * or sourceType is {@code null} but source is not {@code null}
+ */
+ @Nullable
+ public static <T> T convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return ConvertUtil.convert(source, sourceType, targetType);
+ }
+
+ /**
+ * 瀵硅薄绫诲瀷杞崲
+ *
+ * @param source the source object
+ * @param targetType the target type
+ * @param <T> 娉涘瀷鏍囪
+ * @return the converted value
+ * @throws IllegalArgumentException if targetType is {@code null},
+ * or sourceType is {@code null} but source is not {@code null}
+ */
+ @Nullable
+ public static <T> T convert(@Nullable Object source, TypeDescriptor targetType) {
+ return ConvertUtil.convert(source, targetType);
+ }
+
+ /**
+ * 鑾峰彇鏂规硶鍙傛暟淇℃伅
+ *
+ * @param constructor 鏋勯�犲櫒
+ * @param parameterIndex 鍙傛暟搴忓彿
+ * @return {MethodParameter}
+ */
+ public static MethodParameter getMethodParameter(Constructor<?> constructor, int parameterIndex) {
+ return ClassUtil.getMethodParameter(constructor, parameterIndex);
+ }
+
+ /**
+ * 鑾峰彇鏂规硶鍙傛暟淇℃伅
+ *
+ * @param method 鏂规硶
+ * @param parameterIndex 鍙傛暟搴忓彿
+ * @return {MethodParameter}
+ */
+ public static MethodParameter getMethodParameter(Method method, int parameterIndex) {
+ return ClassUtil.getMethodParameter(method, parameterIndex);
+ }
+
+ /**
+ * 鑾峰彇Annotation娉ㄨВ
+ *
+ * @param annotatedElement AnnotatedElement
+ * @param annotationType 娉ㄨВ绫�
+ * @param <A> 娉涘瀷鏍囪
+ * @return {Annotation}
+ */
+ @Nullable
+ public static <A extends Annotation> A getAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
+ return AnnotatedElementUtils.findMergedAnnotation(annotatedElement, annotationType);
+ }
+
+ /**
+ * 鑾峰彇Annotation锛屽厛鎵炬柟娉曪紝娌℃湁鍒欏啀鎵炬柟娉曚笂鐨勭被
+ *
+ * @param method Method
+ * @param annotationType 娉ㄨВ绫�
+ * @param <A> 娉涘瀷鏍囪
+ * @return {Annotation}
+ */
+ @Nullable
+ public static <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) {
+ return ClassUtil.getAnnotation(method, annotationType);
+ }
+
+ /**
+ * 鑾峰彇Annotation锛屽厛鎵綡andlerMethod锛屾病鏈夊垯鍐嶆壘瀵瑰簲鐨勭被
+ *
+ * @param handlerMethod HandlerMethod
+ * @param annotationType 娉ㄨВ绫�
+ * @param <A> 娉涘瀷鏍囪
+ * @return {Annotation}
+ */
+ @Nullable
+ public static <A extends Annotation> A getAnnotation(HandlerMethod handlerMethod, Class<A> annotationType) {
+ return ClassUtil.getAnnotation(handlerMethod, annotationType);
+ }
+
+ /**
+ * 瀹炰緥鍖栧璞�
+ *
+ * @param clazz 绫�
+ * @param <T> 娉涘瀷鏍囪
+ * @return 瀵硅薄
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T newInstance(Class<?> clazz) {
+ return (T) BeanUtil.instantiateClass(clazz);
+ }
+
+ /**
+ * 瀹炰緥鍖栧璞�
+ *
+ * @param clazzStr 绫诲悕
+ * @param <T> 娉涘瀷鏍囪
+ * @return 瀵硅薄
+ */
+ public static <T> T newInstance(String clazzStr) {
+ return BeanUtil.newInstance(clazzStr);
+ }
+
+ /**
+ * 鑾峰彇Bean鐨勫睘鎬�
+ *
+ * @param bean bean
+ * @param propertyName 灞炴�у悕
+ * @return 灞炴�у��
+ */
+ @Nullable
+ public static Object getProperty(@Nullable Object bean, String propertyName) {
+ return BeanUtil.getProperty(bean, propertyName);
+ }
+
+ /**
+ * 璁剧疆Bean灞炴��
+ *
+ * @param bean bean
+ * @param propertyName 灞炴�у悕
+ * @param value 灞炴�у��
+ */
+ public static void setProperty(Object bean, String propertyName, Object value) {
+ BeanUtil.setProperty(bean, propertyName, value);
+ }
+
+ /**
+ * 娴呭鍒�
+ *
+ * @param source 婧愬璞�
+ * @param <T> 娉涘瀷鏍囪
+ * @return T
+ */
+ @Nullable
+ public static <T> T clone(@Nullable T source) {
+ return BeanUtil.clone(source);
+ }
+
+ /**
+ * 鎷疯礉瀵硅薄锛屾敮鎸� Map 鍜� Bean
+ *
+ * @param source 婧愬璞�
+ * @param clazz 绫诲悕
+ * @param <T> 娉涘瀷鏍囪
+ * @return T
+ */
+ @Nullable
+ public static <T> T copy(@Nullable Object source, Class<T> clazz) {
+ return BeanUtil.copy(source, clazz);
+ }
+
+ /**
+ * 鎷疯礉瀵硅薄锛屾敮鎸� Map 鍜� Bean
+ *
+ * @param source 婧愬璞�
+ * @param targetBean 闇�瑕佽祴鍊肩殑瀵硅薄
+ */
+ public static void copy(@Nullable Object source, @Nullable Object targetBean) {
+ BeanUtil.copy(source, targetBean);
+ }
+
+ /**
+ * 鎷疯礉瀵硅薄锛宻ource 瀵硅薄灞炴�у仛闈� null 鍒ゆ柇
+ *
+ * <p>
+ * 鏀寔 map bean copy
+ * </p>
+ *
+ * @param source 婧愬璞�
+ * @param targetBean 闇�瑕佽祴鍊肩殑瀵硅薄
+ */
+ public static void copyNonNull(@Nullable Object source, @Nullable Object targetBean) {
+ BeanUtil.copyNonNull(source, targetBean);
+ }
+
+ /**
+ * 鎷疯礉瀵硅薄锛屽苟瀵逛笉鍚岀被鍨嬪睘鎬ц繘琛岃浆鎹�
+ *
+ * @param source 婧愬璞�
+ * @param clazz 绫诲悕
+ * @param <T> 娉涘瀷鏍囪
+ * @return T
+ */
+ @Nullable
+ public static <T> T copyWithConvert(@Nullable Object source, Class<T> clazz) {
+ return BeanUtil.copyWithConvert(source, clazz);
+ }
+
+ /**
+ * 鎷疯礉鍒楄〃瀵硅薄
+ *
+ * <p>
+ * 鏀寔 map bean copy
+ * </p>
+ *
+ * @param sourceList 婧愬垪琛�
+ * @param targetClazz 杞崲鎴愮殑绫诲瀷
+ * @param <T> 娉涘瀷鏍囪
+ * @return T
+ */
+ public static <T> List<T> copy(@Nullable Collection<?> sourceList, Class<T> targetClazz) {
+ return BeanUtil.copy(sourceList, targetClazz);
+ }
+
+ /**
+ * 鎷疯礉鍒楄〃瀵硅薄锛屽苟瀵逛笉鍚岀被鍨嬪睘鎬ц繘琛岃浆鎹�
+ *
+ * <p>
+ * 鏀寔 map bean copy
+ * </p>
+ *
+ * @param sourceList 婧愬璞″垪琛�
+ * @param targetClazz 杞崲鎴愮殑绫�
+ * @param <T> 娉涘瀷鏍囪
+ * @return List
+ */
+ public static <T> List<T> copyWithConvert(@Nullable Collection<?> sourceList, Class<T> targetClazz) {
+ return BeanUtil.copyWithConvert(sourceList, targetClazz);
+ }
+
+ /**
+ * 鎷疯礉瀵硅薄锛屾墿灞� Spring 鐨勬嫹璐濇柟娉�
+ *
+ * @param source the source bean
+ * @param clazz the target bean class
+ * @param <T> 娉涘瀷鏍囪
+ * @return T
+ * @throws BeansException if the copying failed
+ */
+ @Nullable
+ public static <T> T copyProperties(@Nullable Object source, Class<T> clazz) throws BeansException {
+ return BeanUtil.copyProperties(source, clazz);
+ }
+
+ /**
+ * 鎷疯礉鍒楄〃瀵硅薄锛屾墿灞� Spring 鐨勬嫹璐濇柟娉�
+ *
+ * @param sourceList the source list bean
+ * @param targetClazz the target bean class
+ * @param <T> 娉涘瀷鏍囪
+ * @return List
+ * @throws BeansException if the copying failed
+ */
+ public static <T> List<T> copyProperties(@Nullable Collection<?> sourceList, Class<T> targetClazz) throws BeansException {
+ return BeanUtil.copyProperties(sourceList, targetClazz);
+ }
+
+ /**
+ * 灏嗗璞¤鎴恗ap褰㈠紡
+ *
+ * @param bean 婧愬璞�
+ * @return {Map}
+ */
+ public static Map<String, Object> toMap(@Nullable Object bean) {
+ return BeanUtil.toMap(bean);
+ }
+
+ /**
+ * 灏唌ap 杞负 bean
+ *
+ * @param beanMap map
+ * @param valueType 瀵硅薄绫诲瀷
+ * @param <T> 娉涘瀷鏍囪
+ * @return {T}
+ */
+ public static <T> T toBean(Map<String, Object> beanMap, Class<T> valueType) {
+ return BeanUtil.toBean(beanMap, valueType);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/HexUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/HexUtil.java
new file mode 100644
index 0000000..da3f452
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/HexUtil.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import org.springframework.lang.Nullable;
+
+import java.nio.charset.Charset;
+
+/**
+ * hex 宸ュ叿锛岀紪瑙g爜鍏ㄧ敤 byte
+ *
+ * @author L.cm
+ */
+public class HexUtil {
+ public static final Charset DEFAULT_CHARSET = Charsets.UTF_8;
+ private static final byte[] DIGITS_LOWER = new byte[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+ private static final byte[] DIGITS_UPPER = new byte[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+ /**
+ * encode Hex
+ *
+ * @param data data to hex
+ * @return hex bytes
+ */
+ public static byte[] encode(byte[] data) {
+ return encode(data, true);
+ }
+
+ /**
+ * encode Hex
+ *
+ * @param data data to hex
+ * @param toLowerCase 鏄惁灏忓啓
+ * @return hex bytes
+ */
+ public static byte[] encode(byte[] data, boolean toLowerCase) {
+ return encode(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
+ }
+
+ /**
+ * encode Hex
+ *
+ * @param data Data to Hex
+ * @return bytes as a hex string
+ */
+ private static byte[] encode(byte[] data, byte[] digits) {
+ int len = data.length;
+ byte[] out = new byte[len << 1];
+ for (int i = 0, j = 0; i < len; i++) {
+ out[j++] = digits[(0xF0 & data[i]) >>> 4];
+ out[j++] = digits[0xF & data[i]];
+ }
+ return out;
+ }
+
+ /**
+ * encode Hex
+ *
+ * @param data Data to Hex
+ * @param toLowerCase 鏄惁灏忓啓
+ * @return bytes as a hex string
+ */
+ public static String encodeToString(byte[] data, boolean toLowerCase) {
+ return new String(encode(data, toLowerCase), DEFAULT_CHARSET);
+ }
+
+ /**
+ * encode Hex
+ *
+ * @param data Data to Hex
+ * @return bytes as a hex string
+ */
+ public static String encodeToString(byte[] data) {
+ return new String(encode(data), DEFAULT_CHARSET);
+ }
+
+ /**
+ * encode Hex
+ *
+ * @param data Data to Hex
+ * @return bytes as a hex string
+ */
+ @Nullable
+ public static String encodeToString(@Nullable String data) {
+ if (StringUtil.isBlank(data)) {
+ return null;
+ }
+ return encodeToString(data.getBytes(DEFAULT_CHARSET));
+ }
+
+ /**
+ * decode Hex
+ *
+ * @param data Hex data
+ * @return decode hex to bytes
+ */
+ @Nullable
+ public static byte[] decode(@Nullable String data) {
+ if (StringUtil.isBlank(data)) {
+ return null;
+ }
+ return decode(data.getBytes(DEFAULT_CHARSET));
+ }
+
+ /**
+ * encode Hex
+ *
+ * @param data Data to Hex
+ * @return bytes as a hex string
+ */
+ @Nullable
+ public static String decodeToString(@Nullable String data) {
+ byte[] decodeBytes = decode(data);
+ if (decodeBytes == null) {
+ return null;
+ }
+ return new String(decodeBytes, DEFAULT_CHARSET);
+ }
+
+ /**
+ * decode Hex
+ *
+ * @param data Hex data
+ * @return decode hex to bytes
+ */
+ public static byte[] decode(byte[] data) {
+ int len = data.length;
+ if ((len & 0x01) != 0) {
+ throw new IllegalArgumentException("hexBinary needs to be even-length: " + len);
+ }
+ byte[] out = new byte[len >> 1];
+ for (int i = 0, j = 0; j < len; i++) {
+ int f = toDigit(data[j], j) << 4;
+ j++;
+ f |= toDigit(data[j], j);
+ j++;
+ out[i] = (byte) (f & 0xFF);
+ }
+ return out;
+ }
+
+ private static int toDigit(byte b, int index) {
+ int digit = Character.digit(b, 16);
+ if (digit == -1) {
+ throw new IllegalArgumentException("Illegal hexadecimal byte " + b + " at index " + index);
+ }
+ return digit;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Holder.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Holder.java
new file mode 100644
index 0000000..cd5a27c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Holder.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.utils;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+/**
+ * 涓�浜涘父鐢ㄧ殑鍗曞埄瀵硅薄
+ *
+ * @author L.cm
+ */
+public class Holder {
+
+ /**
+ * RANDOM
+ */
+ public final static Random RANDOM = new Random();
+
+ /**
+ * SECURE_RANDOM
+ */
+ public final static SecureRandom SECURE_RANDOM = new SecureRandom();
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ImageUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ImageUtil.java
new file mode 100644
index 0000000..4dd7183
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ImageUtil.java
@@ -0,0 +1,489 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.utils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springblade.core.tool.support.IMultiOutputStream;
+import org.springblade.core.tool.support.ImagePosition;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.color.ColorSpace;
+import java.awt.geom.AffineTransform;
+import java.awt.image.*;
+import java.io.*;
+import java.net.URL;
+
+/**
+ * 鍥剧墖宸ュ叿绫�
+ *
+ * @author Chill
+ */
+public final class ImageUtil {
+
+ /**
+ * Logger for this class
+ */
+ private static Logger LOGGER = LoggerFactory.getLogger(ImageUtil.class);
+
+ /**
+ * 榛樿杈撳嚭鍥剧墖绫诲瀷
+ */
+ public static final String DEFAULT_IMG_TYPE = "JPEG";
+
+ private ImageUtil() {
+
+ }
+
+ /**
+ * 杞崲杈撳叆娴佸埌byte
+ *
+ * @param src 婧�
+ * @param type 绫诲瀷
+ * @return byte[]
+ * @throws IOException 寮傚父
+ */
+ public static byte[] toByteArray(BufferedImage src, String type) throws IOException {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ ImageIO.write(src, defaultString(type, DEFAULT_IMG_TYPE), os);
+ return os.toByteArray();
+ }
+
+ /**
+ * 鑾峰彇鍥惧儚鍐呭
+ *
+ * @param srcImageFile 鏂囦欢璺緞
+ * @return BufferedImage
+ */
+ public static BufferedImage readImage(String srcImageFile) {
+ try {
+ return ImageIO.read(new File(srcImageFile));
+ } catch (IOException e) {
+ LOGGER.error("Error readImage", e);
+ }
+ return null;
+ }
+
+ /**
+ * 鑾峰彇鍥惧儚鍐呭
+ *
+ * @param srcImageFile 鏂囦欢
+ * @return BufferedImage
+ */
+ public static BufferedImage readImage(File srcImageFile) {
+ try {
+ return ImageIO.read(srcImageFile);
+ } catch (IOException e) {
+ LOGGER.error("Error readImage", e);
+ }
+ return null;
+ }
+
+ /**
+ * 鑾峰彇鍥惧儚鍐呭
+ *
+ * @param srcInputStream 杈撳叆娴�
+ * @return BufferedImage
+ */
+ public static BufferedImage readImage(InputStream srcInputStream) {
+ try {
+ return ImageIO.read(srcInputStream);
+ } catch (IOException e) {
+ LOGGER.error("Error readImage", e);
+ }
+ return null;
+ }
+
+ /**
+ * 鑾峰彇鍥惧儚鍐呭
+ *
+ * @param url URL鍦板潃
+ * @return BufferedImage
+ */
+ public static BufferedImage readImage(URL url) {
+ try {
+ return ImageIO.read(url);
+ } catch (IOException e) {
+ LOGGER.error("Error readImage", e);
+ }
+ return null;
+ }
+
+
+ /**
+ * 缂╂斁鍥惧儚锛堟寜姣斾緥缂╂斁锛�
+ *
+ * @param src 婧愬浘鍍�
+ * @param output 杈撳嚭娴�
+ * @param type 绫诲瀷
+ * @param scale 缂╂斁姣斾緥
+ * @param flag 缂╂斁閫夋嫨:true 鏀惧ぇ; false 缂╁皬;
+ */
+ public final static void zoomScale(BufferedImage src, OutputStream output, String type, double scale, boolean flag) {
+ try {
+ // 寰楀埌婧愬浘瀹�
+ int width = src.getWidth();
+ // 寰楀埌婧愬浘闀�
+ int height = src.getHeight();
+ if (flag) {
+ // 鏀惧ぇ
+ width = Long.valueOf(Math.round(width * scale)).intValue();
+ height = Long.valueOf(Math.round(height * scale)).intValue();
+ } else {
+ // 缂╁皬
+ width = Long.valueOf(Math.round(width / scale)).intValue();
+ height = Long.valueOf(Math.round(height / scale)).intValue();
+ }
+ Image image = src.getScaledInstance(width, height, Image.SCALE_DEFAULT);
+ BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ Graphics g = tag.getGraphics();
+
+ g.drawImage(image, 0, 0, null);
+ g.dispose();
+
+ ImageIO.write(tag, defaultString(type, DEFAULT_IMG_TYPE), output);
+
+ output.close();
+ } catch (IOException e) {
+ LOGGER.error("Error in zoom image", e);
+ }
+ }
+
+ /**
+ * 缂╂斁鍥惧儚锛堟寜楂樺害鍜屽搴︾缉鏀撅級
+ *
+ * @param src 婧愬浘鍍�
+ * @param output 杈撳嚭娴�
+ * @param type 绫诲瀷
+ * @param height 缂╂斁鍚庣殑楂樺害
+ * @param width 缂╂斁鍚庣殑瀹藉害
+ * @param bb 姣斾緥涓嶅鏃舵槸鍚﹂渶瑕佽ˉ鐧斤細true涓鸿ˉ鐧�; false涓轰笉琛ョ櫧;
+ * @param fillColor 濉厖鑹诧紝null鏃朵负Color.WHITE
+ */
+ public final static void zoomFixed(BufferedImage src, OutputStream output, String type, int height, int width, boolean bb, Color fillColor) {
+ try {
+ double ratio = 0.0;
+ Image itemp = src.getScaledInstance(width, height, BufferedImage.SCALE_SMOOTH);
+ // 璁$畻姣斾緥
+ if (src.getHeight() > src.getWidth()) {
+ ratio = Integer.valueOf(height).doubleValue() / src.getHeight();
+ } else {
+ ratio = Integer.valueOf(width).doubleValue() / src.getWidth();
+ }
+ AffineTransformOp op = new AffineTransformOp(AffineTransform.getScaleInstance(ratio, ratio), null);
+ itemp = op.filter(src, null);
+
+ if (bb) {
+ //琛ョ櫧
+ BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ Graphics2D g = image.createGraphics();
+ Color fill = fillColor == null ? Color.white : fillColor;
+ g.setColor(fill);
+ g.fillRect(0, 0, width, height);
+ if (width == itemp.getWidth(null)) {
+ g.drawImage(itemp, 0, (height - itemp.getHeight(null)) / 2, itemp.getWidth(null), itemp.getHeight(null), fill, null);
+ } else {
+ g.drawImage(itemp, (width - itemp.getWidth(null)) / 2, 0, itemp.getWidth(null), itemp.getHeight(null), fill, null);
+ }
+ g.dispose();
+ itemp = image;
+ }
+ // 杈撳嚭涓烘枃浠�
+ ImageIO.write((BufferedImage) itemp, defaultString(type, DEFAULT_IMG_TYPE), output);
+ // 鍏抽棴娴�
+ output.close();
+ } catch (IOException e) {
+ LOGGER.error("Error in zoom image", e);
+ }
+ }
+
+ /**
+ * 鍥惧儚瑁佸壀(鎸夋寚瀹氳捣鐐瑰潗鏍囧拰瀹介珮鍒囧壊)
+ *
+ * @param src 婧愬浘鍍�
+ * @param output 鍒囩墖鍚庣殑鍥惧儚鍦板潃
+ * @param type 绫诲瀷
+ * @param x 鐩爣鍒囩墖璧风偣鍧愭爣X
+ * @param y 鐩爣鍒囩墖璧风偣鍧愭爣Y
+ * @param width 鐩爣鍒囩墖瀹藉害
+ * @param height 鐩爣鍒囩墖楂樺害
+ */
+ public final static void crop(BufferedImage src, OutputStream output, String type, int x, int y, int width, int height) {
+ try {
+ // 婧愬浘瀹藉害
+ int srcWidth = src.getWidth();
+ // 婧愬浘楂樺害
+ int srcHeight = src.getHeight();
+ if (srcWidth > 0 && srcHeight > 0) {
+ Image image = src.getScaledInstance(srcWidth, srcHeight, Image.SCALE_DEFAULT);
+ // 鍥涗釜鍙傛暟鍒嗗埆涓哄浘鍍忚捣鐐瑰潗鏍囧拰瀹介珮
+ ImageFilter cropFilter = new CropImageFilter(x, y, width, height);
+ Image img = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(image.getSource(), cropFilter));
+ BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ Graphics g = tag.getGraphics();
+ g.drawImage(img, 0, 0, width, height, null);
+ g.dispose();
+ // 杈撳嚭涓烘枃浠�
+ ImageIO.write(tag, defaultString(type, DEFAULT_IMG_TYPE), output);
+ // 鍏抽棴娴�
+ output.close();
+ }
+ } catch (Exception e) {
+ LOGGER.error("Error in cut image", e);
+ }
+ }
+
+ /**
+ * 鍥惧儚鍒囧壊锛堟寚瀹氬垏鐗囩殑琛屾暟鍜屽垪鏁帮級
+ *
+ * @param src 婧愬浘鍍忓湴鍧�
+ * @param mos 鍒囩墖鐩爣鏂囦欢澶�
+ * @param type 绫诲瀷
+ * @param prows 鐩爣鍒囩墖琛屾暟銆傞粯璁�2锛屽繀椤绘槸鑼冨洿 [1, 20] 涔嬪唴
+ * @param pcols 鐩爣鍒囩墖鍒楁暟銆傞粯璁�2锛屽繀椤绘槸鑼冨洿 [1, 20] 涔嬪唴
+ */
+ public final static void sliceWithNumber(BufferedImage src, IMultiOutputStream mos, String type, int prows, int pcols) {
+ try {
+ int rows = prows <= 0 || prows > 20 ? 2 : prows;
+ int cols = pcols <= 0 || pcols > 20 ? 2 : pcols;
+ // 婧愬浘瀹藉害
+ int srcWidth = src.getWidth();
+ // 婧愬浘楂樺害
+ int srcHeight = src.getHeight();
+ if (srcWidth > 0 && srcHeight > 0) {
+ Image img;
+ ImageFilter cropFilter;
+ Image image = src.getScaledInstance(srcWidth, srcHeight, Image.SCALE_DEFAULT);
+ // 姣忓紶鍒囩墖鐨勫搴�
+ int destWidth = (srcWidth % cols == 0) ? (srcWidth / cols) : (srcWidth / cols + 1);
+ // 姣忓紶鍒囩墖鐨勯珮搴�
+ int destHeight = (srcHeight % rows == 0) ? (srcHeight / rows) : (srcHeight / rows + 1);
+ // 寰幆寤虹珛鍒囩墖
+ // 鏀硅繘鐨勬兂娉�:鏄惁鍙敤澶氱嚎绋嬪姞蹇垏鍓查�熷害
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ // 鍥涗釜鍙傛暟鍒嗗埆涓哄浘鍍忚捣鐐瑰潗鏍囧拰瀹介珮
+ cropFilter = new CropImageFilter(j * destWidth, i * destHeight, destWidth, destHeight);
+ img = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(image.getSource(), cropFilter));
+ BufferedImage tag = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_INT_RGB);
+ Graphics g = tag.getGraphics();
+ // 缁樺埗缂╁皬鍚庣殑鍥�
+ g.drawImage(img, 0, 0, null);
+ g.dispose();
+ // 杈撳嚭涓烘枃浠�
+ ImageIO.write(tag, defaultString(type, DEFAULT_IMG_TYPE), mos.buildOutputStream(i, j));
+ }
+ }
+ }
+ } catch (Exception e) {
+ LOGGER.error("Error in slice image", e);
+ }
+ }
+
+ /**
+ * 鍥惧儚鍒囧壊锛堟寚瀹氬垏鐗囩殑瀹藉害鍜岄珮搴︼級
+ *
+ * @param src 婧愬浘鍍忓湴鍧�
+ * @param mos 鍒囩墖鐩爣鏂囦欢澶�
+ * @param type 绫诲瀷
+ * @param pdestWidth 鐩爣鍒囩墖瀹藉害銆傞粯璁�200
+ * @param pdestHeight 鐩爣鍒囩墖楂樺害銆傞粯璁�150
+ */
+ public final static void sliceWithSize(BufferedImage src, IMultiOutputStream mos, String type, int pdestWidth, int pdestHeight) {
+ try {
+ int destWidth = pdestWidth <= 0 ? 200 : pdestWidth;
+ int destHeight = pdestHeight <= 0 ? 150 : pdestHeight;
+ // 婧愬浘瀹藉害
+ int srcWidth = src.getWidth();
+ // 婧愬浘楂樺害
+ int srcHeight = src.getHeight();
+ if (srcWidth > destWidth && srcHeight > destHeight) {
+ Image img;
+ ImageFilter cropFilter;
+ Image image = src.getScaledInstance(srcWidth, srcHeight, Image.SCALE_DEFAULT);
+ // 鍒囩墖妯悜鏁伴噺
+ int cols = (srcWidth % destWidth == 0) ? (srcWidth / destWidth) : (srcWidth / destWidth + 1);
+ // 鍒囩墖绾靛悜鏁伴噺
+ int rows = (srcHeight % destHeight == 0) ? (srcHeight / destHeight) : (srcHeight / destHeight + 1);
+ // 寰幆寤虹珛鍒囩墖
+ // 鏀硅繘鐨勬兂娉�:鏄惁鍙敤澶氱嚎绋嬪姞蹇垏鍓查�熷害
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ // 鍥涗釜鍙傛暟鍒嗗埆涓哄浘鍍忚捣鐐瑰潗鏍囧拰瀹介珮
+ cropFilter = new CropImageFilter(j * destWidth, i * destHeight, destWidth, destHeight);
+ img = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(image.getSource(), cropFilter));
+ BufferedImage tag = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_INT_RGB);
+ Graphics g = tag.getGraphics();
+ // 缁樺埗缂╁皬鍚庣殑鍥�
+ g.drawImage(img, 0, 0, null);
+ g.dispose();
+ // 杈撳嚭涓烘枃浠�
+ ImageIO.write(tag, defaultString(type, DEFAULT_IMG_TYPE), mos.buildOutputStream(i, j));
+ }
+ }
+ }
+ } catch (Exception e) {
+ LOGGER.error("Error in slice image", e);
+ }
+ }
+
+ /**
+ * 鍥惧儚绫诲瀷杞崲锛欸IF-JPG銆丟IF-PNG銆丳NG-JPG銆丳NG-GIF(X)銆丅MP-PNG
+ *
+ * @param src 婧愬浘鍍忓湴鍧�
+ * @param formatName 鍖呭惈鏍煎紡闈炴寮忓悕绉扮殑 String锛氬JPG銆丣PEG銆丟IF绛�
+ * @param output 鐩爣鍥惧儚鍦板潃
+ */
+ public final static void convert(BufferedImage src, OutputStream output, String formatName) {
+ try {
+ // 杈撳嚭涓烘枃浠�
+ ImageIO.write(src, formatName, output);
+ // 鍏抽棴娴�
+ output.close();
+ } catch (Exception e) {
+ LOGGER.error("Error in convert image", e);
+ }
+ }
+
+ /**
+ * 褰╄壊杞负榛戠櫧
+ *
+ * @param src 婧愬浘鍍忓湴鍧�
+ * @param output 鐩爣鍥惧儚鍦板潃
+ * @param type 绫诲瀷
+ */
+ public final static void gray(BufferedImage src, OutputStream output, String type) {
+ try {
+ ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
+ ColorConvertOp op = new ColorConvertOp(cs, null);
+ src = op.filter(src, null);
+ // 杈撳嚭涓烘枃浠�
+ ImageIO.write(src, defaultString(type, DEFAULT_IMG_TYPE), output);
+ // 鍏抽棴娴�
+ output.close();
+ } catch (IOException e) {
+ LOGGER.error("Error in gray image", e);
+ }
+ }
+
+ /**
+ * 缁欏浘鐗囨坊鍔犳枃瀛楁按鍗�
+ *
+ * @param src 婧愬浘鍍�
+ * @param output 杈撳嚭娴�
+ * @param type 绫诲瀷
+ * @param text 姘村嵃鏂囧瓧
+ * @param font 姘村嵃鐨勫瓧浣�
+ * @param color 姘村嵃鐨勫瓧浣撻鑹�
+ * @param position 姘村嵃浣嶇疆 {@link ImagePosition}
+ * @param x 淇鍊�
+ * @param y 淇鍊�
+ * @param alpha 閫忔槑搴︼細alpha 蹇呴』鏄寖鍥� [0.0, 1.0] 涔嬪唴锛堝寘鍚竟鐣屽�硷級鐨勪竴涓诞鐐规暟瀛�
+ */
+ public final static void textStamp(BufferedImage src, OutputStream output, String type, String text, Font font, Color color
+ , int position, int x, int y, float alpha) {
+ try {
+ int width = src.getWidth(null);
+ int height = src.getHeight(null);
+ BufferedImage image = new BufferedImage(width, height,
+ BufferedImage.TYPE_INT_RGB);
+ Graphics2D g = image.createGraphics();
+ g.drawImage(src, 0, 0, width, height, null);
+ g.setColor(color);
+ g.setFont(font);
+ g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
+ // 鍦ㄦ寚瀹氬潗鏍囩粯鍒舵按鍗版枃瀛�
+ ImagePosition boxPos = new ImagePosition(width, height, calcTextWidth(text) * font.getSize(), font.getSize(), position);
+ g.drawString(text, boxPos.getX(x), boxPos.getY(y));
+ g.dispose();
+ // 杈撳嚭涓烘枃浠�
+ ImageIO.write((BufferedImage) image, defaultString(type, DEFAULT_IMG_TYPE), output);
+ // 鍏抽棴娴�
+ output.close();
+ } catch (Exception e) {
+ LOGGER.error("Error in textStamp image", e);
+ }
+ }
+
+ /**
+ * 缁欏浘鐗囨坊鍔犲浘鐗囨按鍗�
+ *
+ * @param src 婧愬浘鍍�
+ * @param output 杈撳嚭娴�
+ * @param type 绫诲瀷
+ * @param stamp 姘村嵃鍥剧墖
+ * @param position 姘村嵃浣嶇疆 {@link ImagePosition}
+ * @param x 淇鍊�
+ * @param y 淇鍊�
+ * @param alpha 閫忔槑搴︼細alpha 蹇呴』鏄寖鍥� [0.0, 1.0] 涔嬪唴锛堝寘鍚竟鐣屽�硷級鐨勪竴涓诞鐐规暟瀛�
+ */
+ public final static void imageStamp(BufferedImage src, OutputStream output, String type, BufferedImage stamp
+ , int position, int x, int y, float alpha) {
+ try {
+ int width = src.getWidth();
+ int height = src.getHeight();
+ BufferedImage image = new BufferedImage(width, height,
+ BufferedImage.TYPE_INT_RGB);
+ Graphics2D g = image.createGraphics();
+ g.drawImage(src, 0, 0, width, height, null);
+ // 姘村嵃鏂囦欢
+ int stampWidth = stamp.getWidth();
+ int stampHeight = stamp.getHeight();
+ g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
+ ImagePosition boxPos = new ImagePosition(width, height, stampWidth, stampHeight, position);
+ g.drawImage(stamp, boxPos.getX(x), boxPos.getY(y), stampWidth, stampHeight, null);
+ // 姘村嵃鏂囦欢缁撴潫
+ g.dispose();
+ // 杈撳嚭涓烘枃浠�
+ ImageIO.write((BufferedImage) image, defaultString(type, DEFAULT_IMG_TYPE), output);
+ // 鍏抽棴娴�
+ output.close();
+ } catch (Exception e) {
+ LOGGER.error("Error imageStamp", e);
+ }
+ }
+
+ /**
+ * 璁$畻text鐨勯暱搴︼紙涓�涓腑鏂囩畻涓や釜瀛楃锛�
+ *
+ * @param text text
+ * @return int
+ */
+ public final static int calcTextWidth(String text) {
+ int length = 0;
+ for (int i = 0; i < text.length(); i++) {
+ if (new String(text.charAt(i) + "").getBytes().length > 1) {
+ length += 2;
+ } else {
+ length += 1;
+ }
+ }
+ return length / 2;
+ }
+
+ /**
+ * 榛樿瀛楃涓�
+ * @param str 瀛楃涓�
+ * @param defaultStr 榛樿鍊�
+ * @return
+ */
+ public static String defaultString(String str, String defaultStr) {
+ return ((str == null) ? defaultStr : str);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/IntegerPool.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/IntegerPool.java
new file mode 100644
index 0000000..1ce1156
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/IntegerPool.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tool.utils;
+
+/**
+ * 闈欐�� Integer 姹�.
+ *
+ * @author Chill
+ */
+public interface IntegerPool {
+
+ Integer INT_1024 = 1024;
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/IoUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/IoUtil.java
new file mode 100644
index 0000000..f3dc7b0
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/IoUtil.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.utils;
+
+import org.springframework.lang.Nullable;
+
+import java.io.*;
+import java.nio.charset.Charset;
+
+/**
+ * 娴佸伐鍏风被
+ *
+ * @author L.cm
+ */
+public class IoUtil extends org.springframework.util.StreamUtils {
+
+ /**
+ * closeQuietly
+ *
+ * @param closeable 鑷姩鍏抽棴
+ */
+ public static void closeQuietly(@Nullable Closeable closeable) {
+ if (closeable == null) {
+ return;
+ }
+ if (closeable instanceof Flushable) {
+ try {
+ ((Flushable) closeable).flush();
+ } catch (IOException ignored) {
+ // ignore
+ }
+ }
+ try {
+ closeable.close();
+ } catch (IOException ignored) {
+ // ignore
+ }
+ }
+
+ /**
+ * InputStream to String utf-8
+ *
+ * @param input the <code>InputStream</code> to read from
+ * @return the requested String
+ */
+ public static String readToString(InputStream input) {
+ return readToString(input, Charsets.UTF_8);
+ }
+
+ /**
+ * InputStream to String
+ *
+ * @param input the <code>InputStream</code> to read from
+ * @param charset the <code>Charset</code>
+ * @return the requested String
+ */
+ public static String readToString(@Nullable InputStream input, Charset charset) {
+ try {
+ return IoUtil.copyToString(input, charset);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ } finally {
+ IoUtil.closeQuietly(input);
+ }
+ }
+
+ public static byte[] readToByteArray(@Nullable InputStream input) {
+ try {
+ return IoUtil.copyToByteArray(input);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ } finally {
+ IoUtil.closeQuietly(input);
+ }
+ }
+
+ /**
+ * Writes chars from a <code>String</code> to bytes on an
+ * <code>OutputStream</code> using the specified character encoding.
+ * <p>
+ * This method uses {@link String#getBytes(String)}.
+ * </p>
+ * @param data the <code>String</code> to write, null ignored
+ * @param output the <code>OutputStream</code> to write to
+ * @param encoding the encoding to use, null means platform default
+ * @throws NullPointerException if output is null
+ * @throws IOException if an I/O error occurs
+ */
+ public static void write(@Nullable final String data, final OutputStream output, final Charset encoding) throws IOException {
+ if (data != null) {
+ output.write(data.getBytes(encoding));
+ }
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Lazy.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Lazy.java
new file mode 100644
index 0000000..0e62408
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Lazy.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import org.springframework.lang.Nullable;
+
+import java.io.Serializable;
+import java.util.function.Supplier;
+
+/**
+ * Holder of a value that is computed lazy.
+ *
+ * @author L.cm
+ */
+public class Lazy<T> implements Supplier<T>, Serializable {
+
+ @Nullable
+ private transient volatile Supplier<? extends T> supplier;
+ @Nullable
+ private T value;
+
+ /**
+ * Creates new instance of Lazy.
+ *
+ * @param supplier Supplier
+ * @param <T> 娉涘瀷鏍囪
+ * @return Lazy
+ */
+ public static <T> Lazy<T> of(final Supplier<T> supplier) {
+ return new Lazy<>(supplier);
+ }
+
+ private Lazy(final Supplier<T> supplier) {
+ this.supplier = supplier;
+ }
+
+ /**
+ * Returns the value. Value will be computed on first call.
+ *
+ * @return lazy value
+ */
+ @Nullable
+ @Override
+ public T get() {
+ return (supplier == null) ? value : computeValue();
+ }
+
+ @Nullable
+ private synchronized T computeValue() {
+ final Supplier<? extends T> s = supplier;
+ if (s != null) {
+ value = s.get();
+ supplier = null;
+ }
+ return value;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/NumberUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/NumberUtil.java
new file mode 100644
index 0000000..3977967
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/NumberUtil.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+
+import org.springframework.lang.Nullable;
+
+/**
+ * 鏁板瓧绫诲瀷宸ュ叿绫�
+ *
+ * @author L.cm
+ */
+public class NumberUtil extends org.springframework.util.NumberUtils {
+
+ //-----------------------------------------------------------------------
+
+ /**
+ * <p>Convert a <code>String</code> to an <code>int</code>, returning
+ * <code>zero</code> if the conversion fails.</p>
+ *
+ * <p>If the string is <code>null</code>, <code>zero</code> is returned.</p>
+ *
+ * <pre>
+ * NumberUtil.toInt(null) = 0
+ * NumberUtil.toInt("") = 0
+ * NumberUtil.toInt("1") = 1
+ * </pre>
+ *
+ * @param str the string to convert, may be null
+ * @return the int represented by the string, or <code>zero</code> if
+ * conversion fails
+ */
+ public static int toInt(final String str) {
+ return toInt(str, -1);
+ }
+
+ /**
+ * <p>Convert a <code>String</code> to an <code>int</code>, returning a
+ * default value if the conversion fails.</p>
+ *
+ * <p>If the string is <code>null</code>, the default value is returned.</p>
+ *
+ * <pre>
+ * NumberUtil.toInt(null, 1) = 1
+ * NumberUtil.toInt("", 1) = 1
+ * NumberUtil.toInt("1", 0) = 1
+ * </pre>
+ *
+ * @param str the string to convert, may be null
+ * @param defaultValue the default value
+ * @return the int represented by the string, or the default if conversion fails
+ */
+ public static int toInt(@Nullable final String str, final int defaultValue) {
+ if (str == null) {
+ return defaultValue;
+ }
+ try {
+ return Integer.valueOf(str);
+ } catch (final NumberFormatException nfe) {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * <p>Convert a <code>String</code> to a <code>long</code>, returning
+ * <code>zero</code> if the conversion fails.</p>
+ *
+ * <p>If the string is <code>null</code>, <code>zero</code> is returned.</p>
+ *
+ * <pre>
+ * NumberUtil.toLong(null) = 0L
+ * NumberUtil.toLong("") = 0L
+ * NumberUtil.toLong("1") = 1L
+ * </pre>
+ *
+ * @param str the string to convert, may be null
+ * @return the long represented by the string, or <code>0</code> if
+ * conversion fails
+ */
+ public static long toLong(final String str) {
+ return toLong(str, 0L);
+ }
+
+ /**
+ * <p>Convert a <code>String</code> to a <code>long</code>, returning a
+ * default value if the conversion fails.</p>
+ *
+ * <p>If the string is <code>null</code>, the default value is returned.</p>
+ *
+ * <pre>
+ * NumberUtil.toLong(null, 1L) = 1L
+ * NumberUtil.toLong("", 1L) = 1L
+ * NumberUtil.toLong("1", 0L) = 1L
+ * </pre>
+ *
+ * @param str the string to convert, may be null
+ * @param defaultValue the default value
+ * @return the long represented by the string, or the default if conversion fails
+ */
+ public static long toLong(@Nullable final String str, final long defaultValue) {
+ if (str == null) {
+ return defaultValue;
+ }
+ try {
+ return Long.valueOf(str);
+ } catch (final NumberFormatException nfe) {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * <p>Convert a <code>String</code> to a <code>Double</code>
+ *
+ * @param value value
+ * @return double value
+ */
+ public static Double toDouble(String value) {
+ return toDouble(value, null);
+ }
+
+ /**
+ * <p>Convert a <code>String</code> to a <code>Double</code>
+ *
+ * @param value value
+ * @param defaultValue 榛樿鍊�
+ * @return double value
+ */
+ public static Double toDouble(@Nullable String value, Double defaultValue) {
+ if (value != null) {
+ return Double.valueOf(value.trim());
+ }
+ return defaultValue;
+ }
+
+ /**
+ * <p>Convert a <code>String</code> to a <code>Double</code>
+ *
+ * @param value value
+ * @return double value
+ */
+ public static Float toFloat(String value) {
+ return toFloat(value, null);
+ }
+
+ /**
+ * <p>Convert a <code>String</code> to a <code>Double</code>
+ *
+ * @param value value
+ * @param defaultValue 榛樿鍊�
+ * @return double value
+ */
+ public static Float toFloat(@Nullable String value, Float defaultValue) {
+ if (value != null) {
+ return Float.valueOf(value.trim());
+ }
+ return defaultValue;
+ }
+
+ /**
+ * All possible chars for representing a number as a String
+ */
+ private final static char[] DIGITS = {
+ '0', '1', '2', '3', '4', '5',
+ '6', '7', '8', '9', 'a', 'b',
+ 'c', 'd', 'e', 'f', 'g', 'h',
+ 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't',
+ 'u', 'v', 'w', 'x', 'y', 'z',
+ 'A', 'B', 'C', 'D', 'E', 'F',
+ 'G', 'H', 'I', 'J', 'K', 'L',
+ 'M', 'N', 'O', 'P', 'Q', 'R',
+ 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z'
+ };
+
+ /**
+ * 灏� long 杞煭瀛楃涓� 涓� 62 杩涘埗
+ *
+ * @param i 鏁板瓧
+ * @return 鐭瓧绗︿覆
+ */
+ public static String to62String(long i) {
+ int radix = DIGITS.length;
+ char[] buf = new char[65];
+ int charPos = 64;
+ i = -i;
+ while (i <= -radix) {
+ buf[charPos--] = DIGITS[(int) (-(i % radix))];
+ i = i / radix;
+ }
+ buf[charPos] = DIGITS[(int) (-i)];
+
+ return new String(buf, charPos, (65 - charPos));
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ObjectUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ObjectUtil.java
new file mode 100644
index 0000000..bb8bb54
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ObjectUtil.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import org.springframework.lang.Nullable;
+
+/**
+ * 瀵硅薄宸ュ叿绫�
+ *
+ * @author L.cm
+ */
+public class ObjectUtil extends org.springframework.util.ObjectUtils {
+
+ /**
+ * 鍒ゆ柇鍏冪礌涓嶄负绌�
+ * @param obj object
+ * @return boolean
+ */
+ public static boolean isNotEmpty(@Nullable Object obj) {
+ return !ObjectUtil.isEmpty(obj);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/PathUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/PathUtil.java
new file mode 100644
index 0000000..3dcfe74
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/PathUtil.java
@@ -0,0 +1,64 @@
+package org.springblade.core.tool.utils;
+
+import org.springframework.lang.Nullable;
+
+import java.io.File;
+import java.net.URL;
+
+/**
+ * 鐢ㄦ潵鑾峰彇鍚勭鐩綍
+ *
+ * @author L.cm
+ */
+public class PathUtil {
+ public static final String FILE_PROTOCOL = "file";
+ public static final String JAR_PROTOCOL = "jar";
+ public static final String ZIP_PROTOCOL = "zip";
+ public static final String FILE_PROTOCOL_PREFIX = "file:";
+ public static final String JAR_FILE_SEPARATOR = "!/";
+
+ /**
+ * 鑾峰彇jar鍖呰繍琛屾椂鐨勫綋鍓嶇洰褰�
+ *
+ * @return {String}
+ */
+ @Nullable
+ public static String getJarPath() {
+ try {
+ URL url = PathUtil.class.getResource(StringPool.SLASH).toURI().toURL();
+ return PathUtil.toFilePath(url);
+ } catch (Exception e) {
+ String path = PathUtil.class.getResource(StringPool.EMPTY).getPath();
+ return new File(path).getParentFile().getParentFile().getAbsolutePath();
+ }
+ }
+
+ /**
+ * 杞崲涓烘枃浠惰矾寰�
+ *
+ * @param url 璺緞
+ * @return {String}
+ */
+ @Nullable
+ public static String toFilePath(@Nullable URL url) {
+ if (url == null) {
+ return null;
+ }
+ String protocol = url.getProtocol();
+ String file = UrlUtil.decode(url.getPath(), Charsets.UTF_8);
+ if (FILE_PROTOCOL.equals(protocol)) {
+ return new File(file).getParentFile().getParentFile().getAbsolutePath();
+ } else if (JAR_PROTOCOL.equals(protocol) || ZIP_PROTOCOL.equals(protocol)) {
+ int ipos = file.indexOf(JAR_FILE_SEPARATOR);
+ if (ipos > 0) {
+ file = file.substring(0, ipos);
+ }
+ if (file.startsWith(FILE_PROTOCOL_PREFIX)) {
+ file = file.substring(FILE_PROTOCOL_PREFIX.length());
+ }
+ return new File(file).getParentFile().getAbsolutePath();
+ }
+ return file;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/PlaceholderUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/PlaceholderUtil.java
new file mode 100644
index 0000000..9c55108
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/PlaceholderUtil.java
@@ -0,0 +1,152 @@
+package org.springblade.core.tool.utils;
+
+import java.util.Map;
+import java.util.Properties;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+/**
+ * 鍗犱綅绗﹁В鏋愬櫒
+ *
+ * @author meilin.huang, chill
+ */
+public class PlaceholderUtil {
+ /**
+ * 榛樿鍓嶇紑鍗犱綅绗�
+ */
+ public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";
+
+ /**
+ * 榛樿鍚庣紑鍗犱綅绗�
+ */
+ public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";
+
+ /**
+ * 榛樿鍗曚緥瑙f瀽鍣�
+ */
+ private static final PlaceholderUtil DEFAULT_RESOLVER = new PlaceholderUtil();
+
+ /**
+ * 鍗犱綅绗﹀墠缂�
+ */
+ private String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;
+
+ /**
+ * 鍗犱綅绗﹀悗缂�
+ */
+ private String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX;
+
+
+ private PlaceholderUtil() {
+ }
+
+ private PlaceholderUtil(String placeholderPrefix, String placeholderSuffix) {
+ this.placeholderPrefix = placeholderPrefix;
+ this.placeholderSuffix = placeholderSuffix;
+ }
+
+ /**
+ * 鑾峰彇榛樿鐨勫崰浣嶇瑙f瀽鍣紝鍗冲崰浣嶇鍓嶇紑涓�"${", 鍚庣紑涓�"}"
+ *
+ * @return PlaceholderUtil
+ */
+ public static PlaceholderUtil getDefaultResolver() {
+ return DEFAULT_RESOLVER;
+ }
+
+ public static PlaceholderUtil getResolver(String placeholderPrefix, String placeholderSuffix) {
+ return new PlaceholderUtil(placeholderPrefix, placeholderSuffix);
+ }
+
+ /**
+ * 瑙f瀽甯︽湁鎸囧畾鍗犱綅绗︾殑妯℃澘瀛楃涓诧紝榛樿鍗犱綅绗︿负鍓嶇紑锛�${ 鍚庣紑锛殅<br/><br/>
+ * 濡傦細template = category:${}:product:${}<br/>
+ * values = {"1", "2"}<br/>
+ * 杩斿洖 category:1:product:2<br/>
+ *
+ * @param content 瑕佽В鏋愮殑甯︽湁鍗犱綅绗︾殑妯℃澘瀛楃涓�
+ * @param values 鎸夌収妯℃澘鍗犱綅绗︾储寮曚綅缃缃搴旂殑鍊�
+ * @return {String}
+ */
+ public String resolve(String content, String... values) {
+ int start = content.indexOf(this.placeholderPrefix);
+ if (start == -1) {
+ return content;
+ }
+ //鍊肩储寮�
+ int valueIndex = 0;
+ StringBuilder result = new StringBuilder(content);
+ while (start != -1) {
+ int end = result.indexOf(this.placeholderSuffix);
+ String replaceContent = values[valueIndex++];
+ result.replace(start, end + this.placeholderSuffix.length(), replaceContent);
+ start = result.indexOf(this.placeholderPrefix, start + replaceContent.length());
+ }
+ return result.toString();
+ }
+
+ /**
+ * 瑙f瀽甯︽湁鎸囧畾鍗犱綅绗︾殑妯℃澘瀛楃涓诧紝榛樿鍗犱綅绗︿负鍓嶇紑锛�${ 鍚庣紑锛殅<br/><br/>
+ * 濡傦細template = category:${}:product:${}<br/>
+ * values = {"1", "2"}<br/>
+ * 杩斿洖 category:1:product:2<br/>
+ *
+ * @param content 瑕佽В鏋愮殑甯︽湁鍗犱綅绗︾殑妯℃澘瀛楃涓�
+ * @param values 鎸夌収妯℃澘鍗犱綅绗︾储寮曚綅缃缃搴旂殑鍊�
+ * @return {String}
+ */
+ public String resolve(String content, Object[] values) {
+ return resolve(content, Stream.of(values).map(String::valueOf).toArray(String[]::new));
+ }
+
+ /**
+ * 鏍规嵁鏇挎崲瑙勫垯鏉ユ浛鎹㈡寚瀹氭ā鏉夸腑鐨勫崰浣嶇鍊�
+ *
+ * @param content 瑕佽В鏋愮殑瀛楃涓�
+ * @param rule 瑙f瀽瑙勫垯鍥炶皟
+ * @return {String}
+ */
+ public String resolveByRule(String content, Function<String, String> rule) {
+ int start = content.indexOf(this.placeholderPrefix);
+ if (start == -1) {
+ return content;
+ }
+ StringBuilder result = new StringBuilder(content);
+ while (start != -1) {
+ int end = result.indexOf(this.placeholderSuffix, start + 1);
+ //鑾峰彇鍗犱綅绗﹀睘鎬у�硷紝濡�${id}, 鍗宠幏鍙杋d
+ String placeholder = result.substring(start + this.placeholderPrefix.length(), end);
+ //鏇挎崲鏁翠釜鍗犱綅绗﹀唴瀹癸紝鍗冲皢${id}鍊兼浛鎹负鏇挎崲瑙勫垯鍥炶皟涓殑鍐呭
+ String replaceContent = placeholder.trim().isEmpty() ? "" : rule.apply(placeholder);
+ result.replace(start, end + this.placeholderSuffix.length(), replaceContent);
+ start = result.indexOf(this.placeholderPrefix, start + replaceContent.length());
+ }
+ return result.toString();
+ }
+
+ /**
+ * 鏇挎崲妯℃澘涓崰浣嶇鍐呭锛屽崰浣嶇鐨勫唴瀹瑰嵆涓簃ap key瀵瑰簲鐨勫�硷紝key涓哄崰浣嶇涓殑鍐呭銆�<br/><br/>
+ * 濡傦細content = product:${id}:detail:${did}<br/>
+ * valueMap = id -> 1; pid -> 2<br/>
+ * 缁忚繃瑙f瀽杩斿洖 product:1:detail:2<br/>
+ *
+ * @param content 妯℃澘鍐呭
+ * @param valueMap 鍊兼槧灏�
+ * @return 鏇挎崲瀹屾垚鍚庣殑瀛楃涓�
+ */
+ public String resolveByMap(String content, final Map<String, Object> valueMap) {
+ return resolveByRule(content, placeholderValue -> String.valueOf(valueMap.get(placeholderValue)));
+ }
+
+ /**
+ * 鏍规嵁properties鏂囦欢鏇挎崲鍗犱綅绗﹀唴瀹�
+ *
+ * @param content 妯℃澘鍐呭
+ * @param properties 閰嶇疆
+ * @return {String}
+ */
+ public String resolveByProperties(String content, final Properties properties) {
+ return resolveByRule(content, properties::getProperty);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ProtostuffUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ProtostuffUtil.java
new file mode 100644
index 0000000..79fd5e5
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ProtostuffUtil.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import io.protostuff.LinkedBuffer;
+import io.protostuff.ProtostuffIOUtil;
+import io.protostuff.Schema;
+import io.protostuff.runtime.RuntimeSchema;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Protostuff 宸ュ叿绫�
+ *
+ * @author L.cm
+ */
+public class ProtostuffUtil {
+
+ /**
+ * 閬垮厤姣忔搴忓垪鍖栭兘閲嶆柊鐢宠Buffer绌洪棿
+ */
+ private static final LinkedBuffer BUFFER = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
+ /**
+ * 缂撳瓨Schema
+ */
+ private static final Map<Class<?>, Schema<?>> SCHEMA_CACHE = new ConcurrentHashMap<>();
+
+ /**
+ * 搴忓垪鍖栨柟娉曪紝鎶婃寚瀹氬璞″簭鍒楀寲鎴愬瓧鑺傛暟缁�
+ *
+ * @param obj obj
+ * @param <T> T
+ * @return byte[]
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> byte[] serialize(T obj) {
+ Class<T> clazz = (Class<T>) obj.getClass();
+ Schema<T> schema = getSchema(clazz);
+ byte[] data;
+ try {
+ data = ProtostuffIOUtil.toByteArray(obj, schema, BUFFER);
+ } finally {
+ BUFFER.clear();
+ }
+ return data;
+ }
+
+ /**
+ * 鍙嶅簭鍒楀寲鏂规硶锛屽皢瀛楄妭鏁扮粍鍙嶅簭鍒楀寲鎴愭寚瀹欳lass绫诲瀷
+ *
+ * @param data data
+ * @param clazz clazz
+ * @param <T> T
+ * @return T
+ */
+ public static <T> T deserialize(byte[] data, Class<T> clazz) {
+ Schema<T> schema = getSchema(clazz);
+ T obj = schema.newMessage();
+ ProtostuffIOUtil.mergeFrom(data, obj, schema);
+ return obj;
+ }
+
+ /**
+ * 鑾峰彇Schema
+ * @param clazz clazz
+ * @param <T> T
+ * @return T
+ */
+ @SuppressWarnings("unchecked")
+ private static <T> Schema<T> getSchema(Class<T> clazz) {
+ Schema<T> schema = (Schema<T>) SCHEMA_CACHE.get(clazz);
+ if (Objects.isNull(schema)) {
+ //杩欎釜schema閫氳繃RuntimeSchema杩涜鎳掑垱寤哄苟缂撳瓨
+ //鎵�浠ュ彲浠ヤ竴鐩磋皟鐢≧untimeSchema.getSchema(),杩欎釜鏂规硶鏄嚎绋嬪畨鍏ㄧ殑
+ schema = RuntimeSchema.getSchema(clazz);
+ if (Objects.nonNull(schema)) {
+ SCHEMA_CACHE.put(clazz, schema);
+ }
+ }
+ return schema;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RandomType.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RandomType.java
new file mode 100644
index 0000000..cab8c12
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RandomType.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * 鐢熸垚鐨勯殢鏈烘暟绫诲瀷
+ *
+ * @author L.cm
+ */
+@Getter
+@RequiredArgsConstructor
+public enum RandomType {
+ /**
+ * INT STRING ALL
+ */
+ INT(RandomType.INT_STR),
+ STRING(RandomType.STR_STR),
+ ALL(RandomType.ALL_STR);
+
+ private final String factor;
+
+ /**
+ * 闅忔満瀛楃涓插洜瀛�
+ */
+ private static final String INT_STR = "0123456789";
+ private static final String STR_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ private static final String ALL_STR = INT_STR + STR_STR;
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ReflectUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ReflectUtil.java
new file mode 100644
index 0000000..68b0fe2
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ReflectUtil.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import org.springframework.beans.BeansException;
+import org.springframework.cglib.core.CodeGenerationException;
+import org.springframework.core.convert.Property;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.lang.Nullable;
+import org.springframework.util.ReflectionUtils;
+
+import java.beans.PropertyDescriptor;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 鍙嶅皠宸ュ叿绫�
+ *
+ * @author L.cm
+ */
+public class ReflectUtil extends ReflectionUtils {
+
+ /**
+ * 鑾峰彇 Bean 鐨勬墍鏈� get鏂规硶
+ *
+ * @param type 绫�
+ * @return PropertyDescriptor鏁扮粍
+ */
+ public static PropertyDescriptor[] getBeanGetters(Class type) {
+ return getPropertiesHelper(type, true, false);
+ }
+
+ /**
+ * 鑾峰彇 Bean 鐨勬墍鏈� set鏂规硶
+ *
+ * @param type 绫�
+ * @return PropertyDescriptor鏁扮粍
+ */
+ public static PropertyDescriptor[] getBeanSetters(Class type) {
+ return getPropertiesHelper(type, false, true);
+ }
+
+ /**
+ * 鑾峰彇 Bean 鐨勬墍鏈� PropertyDescriptor
+ *
+ * @param type 绫�
+ * @param read 璇诲彇鏂规硶
+ * @param write 鍐欐柟娉�
+ * @return PropertyDescriptor鏁扮粍
+ */
+ public static PropertyDescriptor[] getPropertiesHelper(Class type, boolean read, boolean write) {
+ try {
+ PropertyDescriptor[] all = BeanUtil.getPropertyDescriptors(type);
+ if (read && write) {
+ return all;
+ } else {
+ List<PropertyDescriptor> properties = new ArrayList<>(all.length);
+ for (PropertyDescriptor pd : all) {
+ if (read && pd.getReadMethod() != null) {
+ properties.add(pd);
+ } else if (write && pd.getWriteMethod() != null) {
+ properties.add(pd);
+ }
+ }
+ return properties.toArray(new PropertyDescriptor[0]);
+ }
+ } catch (BeansException ex) {
+ throw new CodeGenerationException(ex);
+ }
+ }
+
+ /**
+ * 鑾峰彇 bean 鐨勫睘鎬т俊鎭�
+ * @param propertyType 绫诲瀷
+ * @param propertyName 灞炴�у悕
+ * @return {Property}
+ */
+ @Nullable
+ public static Property getProperty(Class<?> propertyType, String propertyName) {
+ PropertyDescriptor propertyDescriptor = BeanUtil.getPropertyDescriptor(propertyType, propertyName);
+ if (propertyDescriptor == null) {
+ return null;
+ }
+ return ReflectUtil.getProperty(propertyType, propertyDescriptor, propertyName);
+ }
+
+ /**
+ * 鑾峰彇 bean 鐨勫睘鎬т俊鎭�
+ * @param propertyType 绫诲瀷
+ * @param propertyDescriptor PropertyDescriptor
+ * @param propertyName 灞炴�у悕
+ * @return {Property}
+ */
+ public static Property getProperty(Class<?> propertyType, PropertyDescriptor propertyDescriptor, String propertyName) {
+ Method readMethod = propertyDescriptor.getReadMethod();
+ Method writeMethod = propertyDescriptor.getWriteMethod();
+ return new Property(propertyType, readMethod, writeMethod, propertyName);
+ }
+
+ /**
+ * 鑾峰彇 bean 鐨勫睘鎬т俊鎭�
+ * @param propertyType 绫诲瀷
+ * @param propertyName 灞炴�у悕
+ * @return {Property}
+ */
+ @Nullable
+ public static TypeDescriptor getTypeDescriptor(Class<?> propertyType, String propertyName) {
+ Property property = ReflectUtil.getProperty(propertyType, propertyName);
+ if (property == null) {
+ return null;
+ }
+ return new TypeDescriptor(property);
+ }
+
+ /**
+ * 鑾峰彇 绫诲睘鎬т俊鎭�
+ * @param propertyType 绫诲瀷
+ * @param propertyDescriptor PropertyDescriptor
+ * @param propertyName 灞炴�у悕
+ * @return {Property}
+ */
+ public static TypeDescriptor getTypeDescriptor(Class<?> propertyType, PropertyDescriptor propertyDescriptor, String propertyName) {
+ Method readMethod = propertyDescriptor.getReadMethod();
+ Method writeMethod = propertyDescriptor.getWriteMethod();
+ Property property = new Property(propertyType, readMethod, writeMethod, propertyName);
+ return new TypeDescriptor(property);
+ }
+
+ /**
+ * 鑾峰彇 绫诲睘鎬�
+ * @param clazz 绫讳俊鎭�
+ * @param fieldName 灞炴�у悕
+ * @return Field
+ */
+ @Nullable
+ public static Field getField(Class<?> clazz, String fieldName) {
+ while (clazz != Object.class) {
+ try {
+ return clazz.getDeclaredField(fieldName);
+ } catch (NoSuchFieldException e) {
+ clazz = clazz.getSuperclass();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 鑾峰彇 鎵�鏈� field 灞炴�т笂鐨勬敞瑙�
+ * @param clazz 绫�
+ * @param fieldName 灞炴�у悕
+ * @param annotationClass 娉ㄨВ
+ * @param <T> 娉ㄨВ娉涘瀷
+ * @return 娉ㄨВ
+ */
+ @Nullable
+ public static <T extends Annotation> T getAnnotation(Class<?> clazz, String fieldName, Class<T> annotationClass) {
+ Field field = ReflectUtil.getField(clazz, fieldName);
+ if (field == null) {
+ return null;
+ }
+ return field.getAnnotation(annotationClass);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RegexUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RegexUtil.java
new file mode 100644
index 0000000..03052f1
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RegexUtil.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import org.springframework.lang.Nullable;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 姝e垯琛ㄨ揪寮忓伐鍏�
+ *
+ * @author L.cm
+ */
+public class RegexUtil {
+ /**
+ * 鐢ㄦ埛鍚�
+ */
+ public static final String USER_NAME = "^[a-zA-Z\\u4E00-\\u9FA5][a-zA-Z0-9_\\u4E00-\\u9FA5]{1,11}$";
+
+ /**
+ * 瀵嗙爜
+ */
+ public static final String USER_PASSWORD = "^.{6,32}$";
+
+ /**
+ * 閭
+ */
+ public static final String EMAIL = "^\\w+([-+.]*\\w+)*@([\\da-z](-[\\da-z])?)+(\\.{1,2}[a-z]+)+$";
+
+ /**
+ * 鎵嬫満鍙�
+ */
+ public static final String PHONE = "^1[3456789]\\d{9}$";
+
+ /**
+ * 鎵嬫満鍙锋垨鑰呴偖绠�
+ */
+ public static final String EMAIL_OR_PHONE = EMAIL + "|" + PHONE;
+
+ /**
+ * URL璺緞
+ */
+ public static final String URL = "^(https?:\\/\\/)?([\\da-z\\.-]+)\\.([a-z\\.]{2,6})(:[\\d]+)?([\\/\\w\\.-]*)*\\/?$";
+
+ /**
+ * 韬唤璇佹牎楠岋紝鍒濈骇鏍¢獙锛屽叿浣撹鍒欐湁涓�濂楃畻娉�
+ */
+ public static final String ID_CARD = "^\\d{15}$|^\\d{17}([0-9]|X)$";
+
+ /**
+ * 鍩熷悕鏍¢獙
+ */
+ public static final String DOMAIN = "^[0-9a-zA-Z]+[0-9a-zA-Z\\.-]*\\.[a-zA-Z]{2,4}$";
+
+ /**
+ * 缂栬瘧浼犲叆姝e垯琛ㄨ揪寮忓拰瀛楃涓插幓鍖归厤,蹇界暐澶у皬鍐�
+ *
+ * @param regex 姝e垯
+ * @param beTestString 瀛楃涓�
+ * @return {boolean}
+ */
+ public static boolean match(String regex, String beTestString) {
+ Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
+ Matcher matcher = pattern.matcher(beTestString);
+ return matcher.matches();
+ }
+
+ /**
+ * 缂栬瘧浼犲叆姝e垯琛ㄨ揪寮忓湪瀛楃涓蹭腑瀵绘壘锛屽鏋滃尮閰嶅埌鍒欎负true
+ *
+ * @param regex 姝e垯
+ * @param beTestString 瀛楃涓�
+ * @return {boolean}
+ */
+ public static boolean find(String regex, String beTestString) {
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(beTestString);
+ return matcher.find();
+ }
+
+ /**
+ * 缂栬瘧浼犲叆姝e垯琛ㄨ揪寮忓湪瀛楃涓蹭腑瀵绘壘锛屽鏋滄壘鍒拌繑鍥炵涓�涓粨鏋�
+ * 鎵句笉鍒拌繑鍥瀗ull
+ *
+ * @param regex 姝e垯
+ * @param beFoundString 瀛楃涓�
+ * @return {boolean}
+ */
+ @Nullable
+ public static String findResult(String regex, String beFoundString) {
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(beFoundString);
+ if (matcher.find()) {
+ return matcher.group();
+ }
+ return null;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ResourceUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ResourceUtil.java
new file mode 100644
index 0000000..eb1078e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ResourceUtil.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.UrlResource;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.util.Assert;
+
+import java.io.IOException;
+
+/**
+ * 璧勬簮宸ュ叿绫�
+ *
+ * @author L.cm
+ */
+public class ResourceUtil extends org.springframework.util.ResourceUtils {
+ public static final String HTTP_REGEX = "^https?:.+$";
+ public static final String FTP_URL_PREFIX = "ftp:";
+
+ /**
+ * 鑾峰彇璧勬簮
+ * <p>
+ * 鏀寔涓�涓嬪崗璁細
+ * <p>
+ * 1. classpath:
+ * 2. file:
+ * 3. ftp:
+ * 4. http: and https:
+ * 5. classpath*:
+ * 6. C:/dir1/ and /Users/lcm
+ * </p>
+ *
+ * @param resourceLocation 璧勬簮璺緞
+ * @return {Resource}
+ * @throws IOException IOException
+ */
+ public static Resource getResource(String resourceLocation) throws IOException {
+ Assert.notNull(resourceLocation, "Resource location must not be null");
+ if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
+ return new ClassPathResource(resourceLocation);
+ }
+ if (resourceLocation.startsWith(FTP_URL_PREFIX)) {
+ return new UrlResource(resourceLocation);
+ }
+ if (resourceLocation.matches(HTTP_REGEX)) {
+ return new UrlResource(resourceLocation);
+ }
+ if (resourceLocation.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX)) {
+ return SpringUtil.getContext().getResource(resourceLocation);
+ }
+ return new FileSystemResource(resourceLocation);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RsaUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RsaUtil.java
new file mode 100644
index 0000000..e89471c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RsaUtil.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import org.springframework.lang.Nullable;
+import org.springframework.util.Base64Utils;
+
+import org.springblade.core.tool.tuple.KeyPair;
+import javax.crypto.Cipher;
+import java.math.BigInteger;
+import java.security.*;
+import java.security.spec.*;
+import java.util.Objects;
+
+/**
+ * RSA鍔犮�佽В瀵嗗伐鍏�
+ *
+ * <p>
+ * 1. 鍏挜璐熻矗鍔犲瘑锛岀閽ヨ礋璐hВ瀵嗭紱
+ * 2. 绉侀挜璐熻矗绛惧悕锛屽叕閽ヨ礋璐i獙璇併��
+ * </p>
+ *
+ * @author L.cm
+ */
+public class RsaUtil {
+ /**
+ * 鏁板瓧绛惧悕锛屽瘑閽ョ畻娉�
+ */
+ public static final String RSA_ALGORITHM = "RSA";
+ public static final String RSA_PADDING = "RSA/ECB/PKCS1Padding";
+
+ /**
+ * 鑾峰彇 KeyPair
+ *
+ * @return KeyPair
+ */
+ public static KeyPair genKeyPair() {
+ return genKeyPair(1024);
+ }
+
+ /**
+ * 鑾峰彇 KeyPair
+ *
+ * @param keySize key size
+ * @return KeyPair
+ */
+ public static KeyPair genKeyPair(int keySize) {
+ try {
+ KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(RSA_ALGORITHM);
+ // 瀵嗛挜浣嶆暟
+ keyPairGen.initialize(keySize);
+ // 瀵嗛挜瀵�
+ return new KeyPair(keyPairGen.generateKeyPair());
+ } catch (NoSuchAlgorithmException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 鐢熸垚RSA绉侀挜
+ *
+ * @param modulus N鐗瑰緛鍊�
+ * @param exponent d鐗瑰緛鍊�
+ * @return {@link PrivateKey}
+ */
+ public static PrivateKey generatePrivateKey(String modulus, String exponent) {
+ return generatePrivateKey(new BigInteger(modulus), new BigInteger(exponent));
+ }
+
+ /**
+ * 鐢熸垚RSA绉侀挜
+ *
+ * @param modulus N鐗瑰緛鍊�
+ * @param exponent d鐗瑰緛鍊�
+ * @return {@link PrivateKey}
+ */
+ public static PrivateKey generatePrivateKey(BigInteger modulus, BigInteger exponent) {
+ RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(modulus, exponent);
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
+ return keyFactory.generatePrivate(keySpec);
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 鐢熸垚RSA鍏挜
+ *
+ * @param modulus N鐗瑰緛鍊�
+ * @param exponent e鐗瑰緛鍊�
+ * @return {@link PublicKey}
+ */
+ public static PublicKey generatePublicKey(String modulus, String exponent) {
+ return generatePublicKey(new BigInteger(modulus), new BigInteger(exponent));
+ }
+
+ /**
+ * 鐢熸垚RSA鍏挜
+ *
+ * @param modulus N鐗瑰緛鍊�
+ * @param exponent e鐗瑰緛鍊�
+ * @return {@link PublicKey}
+ */
+ public static PublicKey generatePublicKey(BigInteger modulus, BigInteger exponent) {
+ RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, exponent);
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
+ return keyFactory.generatePublic(keySpec);
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 寰楀埌鍏挜
+ *
+ * @param base64PubKey 瀵嗛挜瀛楃涓诧紙缁忚繃base64缂栫爜锛�
+ * @return PublicKey
+ */
+ public static PublicKey getPublicKey(String base64PubKey) {
+ Objects.requireNonNull(base64PubKey, "base64 public key is null.");
+ byte[] keyBytes = Base64Utils.decodeFromString(base64PubKey);
+ X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
+ return keyFactory.generatePublic(keySpec);
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 寰楀埌鍏挜瀛楃涓�
+ *
+ * @param base64PubKey 瀵嗛挜瀛楃涓诧紙缁忚繃base64缂栫爜锛�
+ * @return PublicKey String
+ */
+ public static String getPublicKeyToBase64(String base64PubKey) {
+ PublicKey publicKey = getPublicKey(base64PubKey);
+ return getKeyString(publicKey);
+ }
+
+ /**
+ * 寰楀埌绉侀挜
+ *
+ * @param base64PriKey 瀵嗛挜瀛楃涓诧紙缁忚繃base64缂栫爜锛�
+ * @return PrivateKey
+ */
+ public static PrivateKey getPrivateKey(String base64PriKey) {
+ Objects.requireNonNull(base64PriKey, "base64 private key is null.");
+ byte[] keyBytes = Base64Utils.decodeFromString(base64PriKey);
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
+ return keyFactory.generatePrivate(keySpec);
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 寰楀埌瀵嗛挜瀛楃涓诧紙缁忚繃base64缂栫爜锛�
+ *
+ * @param key key
+ * @return base 64 缂栫爜鍚庣殑 key
+ */
+ public static String getKeyString(Key key) {
+ return Base64Utils.encodeToString(key.getEncoded());
+ }
+
+ /**
+ * 寰楀埌绉侀挜 base64
+ *
+ * @param base64PriKey 瀵嗛挜瀛楃涓诧紙缁忚繃base64缂栫爜锛�
+ * @return PrivateKey String
+ */
+ public static String getPrivateKeyToBase64(String base64PriKey) {
+ PrivateKey privateKey = getPrivateKey(base64PriKey);
+ return getKeyString(privateKey);
+ }
+
+ /**
+ * 鍏辫鍔犲瘑
+ *
+ * @param base64PublicKey base64 鐨勫叕閽�
+ * @param data 寰呭姞瀵嗙殑鍐呭
+ * @return 鍔犲瘑鍚庣殑鍐呭
+ */
+ public static byte[] encrypt(String base64PublicKey, byte[] data) {
+ return encrypt(getPublicKey(base64PublicKey), data);
+ }
+
+ /**
+ * 鍏辫鍔犲瘑
+ *
+ * @param publicKey 鍏挜
+ * @param data 寰呭姞瀵嗙殑鍐呭
+ * @return 鍔犲瘑鍚庣殑鍐呭
+ */
+ public static byte[] encrypt(PublicKey publicKey, byte[] data) {
+ return rsa(publicKey, data, Cipher.ENCRYPT_MODE);
+ }
+
+ /**
+ * 绉侀挜鍔犲瘑锛岀敤浜� qpp 鍐咃紝鍏挜瑙e瘑
+ *
+ * @param base64PrivateKey base64 鐨勭閽�
+ * @param data 寰呭姞瀵嗙殑鍐呭
+ * @return 鍔犲瘑鍚庣殑鍐呭
+ */
+ public static byte[] encryptByPrivateKey(String base64PrivateKey, byte[] data) {
+ return encryptByPrivateKey(getPrivateKey(base64PrivateKey), data);
+ }
+
+ /**
+ * 绉侀挜鍔犲瘑锛屽姞瀵嗘垚 base64 瀛楃涓诧紝鐢ㄤ簬 qpp 鍐咃紝鍏挜瑙e瘑
+ *
+ * @param base64PrivateKey base64 鐨勭閽�
+ * @param data 寰呭姞瀵嗙殑鍐呭
+ * @return 鍔犲瘑鍚庣殑鍐呭
+ */
+ public static String encryptByPrivateKeyToBase64(String base64PrivateKey, byte[] data) {
+ return Base64Util.encodeToString(encryptByPrivateKey(base64PrivateKey, data));
+ }
+
+ /**
+ * 绉侀挜鍔犲瘑锛岀敤浜� qpp 鍐咃紝鍏挜瑙e瘑
+ *
+ * @param privateKey 绉侀挜
+ * @param data 寰呭姞瀵嗙殑鍐呭
+ * @return 鍔犲瘑鍚庣殑鍐呭
+ */
+ public static byte[] encryptByPrivateKey(PrivateKey privateKey, byte[] data) {
+ return rsa(privateKey, data, Cipher.ENCRYPT_MODE);
+ }
+
+ /**
+ * 鍏挜鍔犲瘑
+ *
+ * @param base64PublicKey base64 鍏挜
+ * @param data 寰呭姞瀵嗙殑鍐呭
+ * @return 鍔犲瘑鍚庣殑鍐呭
+ */
+ @Nullable
+ public static String encryptToBase64(String base64PublicKey, @Nullable String data) {
+ if (StringUtil.isBlank(data)) {
+ return null;
+ }
+ return Base64Utils.encodeToString(encrypt(base64PublicKey, data.getBytes(Charsets.UTF_8)));
+ }
+
+ /**
+ * 瑙e瘑
+ *
+ * @param base64PrivateKey base64 绉侀挜
+ * @param data 鏁版嵁
+ * @return 瑙e瘑鍚庣殑鏁版嵁
+ */
+ public static byte[] decrypt(String base64PrivateKey, byte[] data) {
+ return decrypt(getPrivateKey(base64PrivateKey), data);
+ }
+
+ /**
+ * 瑙e瘑
+ *
+ * @param base64publicKey base64 鍏挜
+ * @param data 鏁版嵁
+ * @return 瑙e瘑鍚庣殑鏁版嵁
+ */
+ public static byte[] decryptByPublicKey(String base64publicKey, byte[] data) {
+ return decryptByPublicKey(getPublicKey(base64publicKey), data);
+ }
+
+ /**
+ * 瑙e瘑
+ *
+ * @param privateKey privateKey
+ * @param data 鏁版嵁
+ * @return 瑙e瘑鍚庣殑鏁版嵁
+ */
+ public static byte[] decrypt(PrivateKey privateKey, byte[] data) {
+ return rsa(privateKey, data, Cipher.DECRYPT_MODE);
+ }
+
+ /**
+ * 瑙e瘑
+ *
+ * @param publicKey PublicKey
+ * @param data 鏁版嵁
+ * @return 瑙e瘑鍚庣殑鏁版嵁
+ */
+ public static byte[] decryptByPublicKey(PublicKey publicKey, byte[] data) {
+ return rsa(publicKey, data, Cipher.DECRYPT_MODE);
+ }
+
+ /**
+ * rsa 鍔犮�佽В瀵�
+ *
+ * @param key key
+ * @param data 鏁版嵁
+ * @param mode 妯″紡
+ * @return 瑙e瘑鍚庣殑鏁版嵁
+ */
+ private static byte[] rsa(Key key, byte[] data, int mode) {
+ try {
+ Cipher cipher = Cipher.getInstance(RSA_PADDING);
+ cipher.init(mode, key);
+ return cipher.doFinal(data);
+ } catch (Exception e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * base64 鏁版嵁瑙e瘑
+ *
+ * @param base64PublicKey base64 鍏挜
+ * @param base64Data base64鏁版嵁
+ * @return 瑙e瘑鍚庣殑鏁版嵁
+ */
+ public static byte[] decryptByPublicKeyFromBase64(String base64PublicKey, byte[] base64Data) {
+ return decryptByPublicKey(getPublicKey(base64PublicKey), base64Data);
+ }
+
+ /**
+ * base64 鏁版嵁瑙e瘑
+ *
+ * @param base64PrivateKey base64 绉侀挜
+ * @param base64Data base64鏁版嵁
+ * @return 瑙e瘑鍚庣殑鏁版嵁
+ */
+ @Nullable
+ public static String decryptFromBase64(String base64PrivateKey, @Nullable String base64Data) {
+ if (StringUtil.isBlank(base64Data)) {
+ return null;
+ }
+ return new String(decrypt(base64PrivateKey, Base64Utils.decodeFromString(base64Data)), Charsets.UTF_8);
+ }
+
+ /**
+ * base64 鏁版嵁瑙e瘑
+ *
+ * @param base64PrivateKey base64 绉侀挜
+ * @param base64Data base64鏁版嵁
+ * @return 瑙e瘑鍚庣殑鏁版嵁
+ */
+ public static byte[] decryptFromBase64(String base64PrivateKey, byte[] base64Data) {
+ return decrypt(base64PrivateKey, Base64Utils.decode(base64Data));
+ }
+
+ /**
+ * base64 鏁版嵁瑙e瘑
+ *
+ * @param base64PublicKey base64 鍏挜
+ * @param base64Data base64鏁版嵁
+ * @return 瑙e瘑鍚庣殑鏁版嵁
+ */
+ @Nullable
+ public static String decryptByPublicKeyFromBase64(String base64PublicKey, @Nullable String base64Data) {
+ if (StringUtil.isBlank(base64Data)) {
+ return null;
+ }
+ return new String(decryptByPublicKeyFromBase64(base64PublicKey, Base64Utils.decodeFromString(base64Data)), Charsets.UTF_8);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RuntimeUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RuntimeUtil.java
new file mode 100644
index 0000000..385261b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RuntimeUtil.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+
+import java.lang.management.ManagementFactory;
+import java.time.Duration;
+import java.util.List;
+
+/**
+ * 杩愯鏃跺伐鍏风被
+ *
+ * @author L.cm
+ */
+public class RuntimeUtil {
+
+ /**
+ * 鑾峰緱褰撳墠杩涚▼鐨凱ID
+ * <p>
+ * 褰撳け璐ユ椂杩斿洖-1
+ *
+ * @return pid
+ */
+ public static int getPid() {
+ // something like '<pid>@<hostname>', at least in SUN / Oracle JVMs
+ final String jvmName = ManagementFactory.getRuntimeMXBean().getName();
+ final int index = jvmName.indexOf(CharPool.AT);
+ if (index > 0) {
+ return NumberUtil.toInt(jvmName.substring(0, index), -1);
+ }
+ return -1;
+ }
+
+ /**
+ * 杩斿洖搴旂敤鍚姩鍒扮幇鍦ㄧ殑鏃堕棿
+ *
+ * @return {Duration}
+ */
+ public static Duration getUpTime() {
+ long upTime = ManagementFactory.getRuntimeMXBean().getUptime();
+ return Duration.ofMillis(upTime);
+ }
+
+ /**
+ * 杩斿洖杈撳叆鐨凧VM鍙傛暟鍒楄〃
+ *
+ * @return jvm鍙傛暟
+ */
+ public static String getJvmArguments() {
+ List<String> vmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
+ return StringUtil.join(vmArguments, StringPool.SPACE);
+ }
+
+ /**
+ * 鑾峰彇CPU鏍告暟
+ *
+ * @return cpu count
+ */
+ public static int getCpuNum() {
+ return Runtime.getRuntime().availableProcessors();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/SpringUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/SpringUtil.java
new file mode 100644
index 0000000..73f1237
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/SpringUtil.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.lang.Nullable;
+
+/**
+ * spring 宸ュ叿绫�
+ *
+ * @author Chill
+ */
+@Slf4j
+public class SpringUtil implements ApplicationContextAware {
+
+ private static ApplicationContext context;
+
+ @Override
+ public void setApplicationContext(@Nullable ApplicationContext context) throws BeansException {
+ SpringUtil.context = context;
+ }
+
+ /**
+ * 鑾峰彇bean
+ *
+ * @param clazz class绫�
+ * @param <T> 娉涘瀷
+ * @return T
+ */
+ public static <T> T getBean(Class<T> clazz) {
+ if (clazz == null) {
+ return null;
+ }
+ return context.getBean(clazz);
+ }
+
+ /**
+ * 鑾峰彇bean
+ *
+ * @param beanId beanId
+ * @param <T> 娉涘瀷
+ * @return T
+ */
+ public static <T> T getBean(String beanId) {
+ if (beanId == null) {
+ return null;
+ }
+ return (T) context.getBean(beanId);
+ }
+
+ /**
+ * 鑾峰彇bean
+ *
+ * @param beanName bean鍚嶇О
+ * @param clazz class绫�
+ * @param <T> 娉涘瀷
+ * @return T
+ */
+ public static <T> T getBean(String beanName, Class<T> clazz) {
+ if (null == beanName || "".equals(beanName.trim())) {
+ return null;
+ }
+ if (clazz == null) {
+ return null;
+ }
+ return (T) context.getBean(beanName, clazz);
+ }
+
+ /**
+ * 鑾峰彇 ApplicationContext
+ *
+ * @return ApplicationContext
+ */
+ public static ApplicationContext getContext() {
+ if (context == null) {
+ return null;
+ }
+ return context;
+ }
+
+ /**
+ * 鍙戝竷浜嬩欢
+ *
+ * @param event 浜嬩欢
+ */
+ public static void publishEvent(ApplicationEvent event) {
+ if (context == null) {
+ return;
+ }
+ try {
+ context.publishEvent(event);
+ } catch (Exception ex) {
+ log.error(ex.getMessage());
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/StringPool.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/StringPool.java
new file mode 100644
index 0000000..10d5b16
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/StringPool.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+/**
+ * 闈欐�� String 姹�
+ *
+ * @author L.cm
+ */
+public interface StringPool {
+
+ String AMPERSAND = "&";
+ String AND = "and";
+ String AT = "@";
+ String ASTERISK = "*";
+ String STAR = ASTERISK;
+ String SLASH = "/";
+ String BACK_SLASH = "\\";
+ String DOUBLE_SLASH = "#//";
+ String COLON = ":";
+ String COMMA = ",";
+ String DASH = "-";
+ String DOLLAR = "$";
+ String DOT = ".";
+ String EMPTY = "";
+ String EMPTY_JSON = "{}";
+ String EQUALS = "=";
+ String FALSE = "false";
+ String HASH = "#";
+ String HAT = "^";
+ String LEFT_BRACE = "{";
+ String LEFT_BRACKET = "(";
+ String LEFT_CHEV = "<";
+ String NEWLINE = "\n";
+ String N = "n";
+ String NO = "no";
+ String NULL = "null";
+ String OFF = "off";
+ String ON = "on";
+ String PERCENT = "%";
+ String PIPE = "|";
+ String PLUS = "+";
+ String QUESTION_MARK = "?";
+ String EXCLAMATION_MARK = "!";
+ String QUOTE = "\"";
+ String RETURN = "\r";
+ String TAB = "\t";
+ String RIGHT_BRACE = "}";
+ String RIGHT_BRACKET = ")";
+ String RIGHT_CHEV = ">";
+ String SEMICOLON = ";";
+ String SINGLE_QUOTE = "'";
+ String BACKTICK = "`";
+ String SPACE = " ";
+ String TILDA = "~";
+ String LEFT_SQ_BRACKET = "[";
+ String RIGHT_SQ_BRACKET = "]";
+ String TRUE = "true";
+ String UNDERSCORE = "_";
+ String UTF_8 = "UTF-8";
+ String GBK = "GBK";
+ String ISO_8859_1 = "ISO-8859-1";
+ String Y = "y";
+ String YES = "yes";
+ String ONE = "1";
+ String ZERO = "0";
+ String MINUS_ONE = "-1";
+ String DOLLAR_LEFT_BRACE= "${";
+ String UNKNOWN = "unknown";
+ String GET = "GET";
+ String POST = "POST";
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/StringUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/StringUtil.java
new file mode 100644
index 0000000..31b0b0a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/StringUtil.java
@@ -0,0 +1,1577 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import org.springblade.core.tool.support.StrSpliter;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.PatternMatchUtils;
+import org.springframework.web.util.HtmlUtils;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+/**
+ * 缁ф壙鑷猄pring util鐨勫伐鍏风被锛屽噺灏慾ar渚濊禆
+ *
+ * @author L.cm
+ */
+public class StringUtil extends org.springframework.util.StringUtils {
+
+ public static final int INDEX_NOT_FOUND = -1;
+
+ /**
+ * Check whether the given {@code CharSequence} contains actual <em>text</em>.
+ * <p>More specifically, this method returns {@code true} if the
+ * {@code CharSequence} is not {@code null}, its length is greater than
+ * 0, and it contains at least one non-whitespace character.
+ * <pre class="code">
+ * StringUtil.isBlank(null) = true
+ * StringUtil.isBlank("") = true
+ * StringUtil.isBlank(" ") = true
+ * StringUtil.isBlank("12345") = false
+ * StringUtil.isBlank(" 12345 ") = false
+ * </pre>
+ *
+ * @param cs the {@code CharSequence} to check (may be {@code null})
+ * @return {@code true} if the {@code CharSequence} is not {@code null},
+ * its length is greater than 0, and it does not contain whitespace only
+ * @see Character#isWhitespace
+ */
+ public static boolean isBlank(final CharSequence cs) {
+ return !StringUtil.hasText(cs);
+ }
+
+ /**
+ * <p>Checks if a CharSequence is not empty (""), not null and not whitespace only.</p>
+ * <pre>
+ * StringUtil.isNotBlank(null) = false
+ * StringUtil.isNotBlank("") = false
+ * StringUtil.isNotBlank(" ") = false
+ * StringUtil.isNotBlank("bob") = true
+ * StringUtil.isNotBlank(" bob ") = true
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if the CharSequence is
+ * not empty and not null and not whitespace
+ * @see Character#isWhitespace
+ */
+ public static boolean isNotBlank(final CharSequence cs) {
+ return StringUtil.hasText(cs);
+ }
+
+ /**
+ * 鏈� 浠绘剰 涓�涓� Blank
+ *
+ * @param css CharSequence
+ * @return boolean
+ */
+ public static boolean isAnyBlank(final CharSequence... css) {
+ if (ObjectUtil.isEmpty(css)) {
+ return true;
+ }
+ return Stream.of(css).anyMatch(StringUtil::isBlank);
+ }
+
+ /**
+ * 鏄惁鍏ㄩ潪 Blank
+ *
+ * @param css CharSequence
+ * @return boolean
+ */
+ public static boolean isNoneBlank(final CharSequence... css) {
+ if (ObjectUtil.isEmpty(css)) {
+ return false;
+ }
+ return Stream.of(css).allMatch(StringUtil::isNotBlank);
+ }
+
+ /**
+ * 鏄惁鍏ㄤ负 Blank
+ *
+ * @param css CharSequence
+ * @return boolean
+ */
+ public static boolean isAllBlank(final CharSequence... css) {
+ return Stream.of(css).allMatch(StringUtil::isBlank);
+ }
+
+ /**
+ * 鍒ゆ柇涓�涓瓧绗︿覆鏄惁鏄暟瀛�
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {boolean}
+ */
+ public static boolean isNumeric(final CharSequence cs) {
+ if (isBlank(cs)) {
+ return false;
+ }
+ for (int i = cs.length(); --i >= 0; ) {
+ int chr = cs.charAt(i);
+ if (chr < 48 || chr > 57) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆涓壒瀹氭ā寮忕殑瀛楃杞崲鎴恗ap涓搴旂殑鍊�
+ * <p>
+ * use: format("my name is ${name}, and i like ${like}!", {"name":"L.cm", "like": "Java"})
+ *
+ * @param message 闇�瑕佽浆鎹㈢殑瀛楃涓�
+ * @param params 杞崲鎵�闇�鐨勯敭鍊煎闆嗗悎
+ * @return 杞崲鍚庣殑瀛楃涓�
+ */
+ public static String format(@Nullable String message, @Nullable Map<String, ?> params) {
+ // message 涓� null 杩斿洖绌哄瓧绗︿覆
+ if (message == null) {
+ return StringPool.EMPTY;
+ }
+ // 鍙傛暟涓� null 鎴栬�呬负绌�
+ if (params == null || params.isEmpty()) {
+ return message;
+ }
+ // 鏇挎崲鍙橀噺
+ StringBuilder sb = new StringBuilder((int) (message.length() * 1.5));
+ int cursor = 0;
+ for (int start, end; (start = message.indexOf(StringPool.DOLLAR_LEFT_BRACE, cursor)) != -1 && (end = message.indexOf(StringPool.RIGHT_BRACE, start)) != -1; ) {
+ sb.append(message, cursor, start);
+ String key = message.substring(start + 2, end);
+ Object value = params.get(StringUtil.trimWhitespace(key));
+ sb.append(value == null ? StringPool.EMPTY : value);
+ cursor = end + 1;
+ }
+ sb.append(message.substring(cursor));
+ return sb.toString();
+ }
+
+ /**
+ * 鍚� log 鏍煎紡鐨� format 瑙勫垯
+ * <p>
+ * use: format("my name is {}, and i like {}!", "L.cm", "Java")
+ *
+ * @param message 闇�瑕佽浆鎹㈢殑瀛楃涓�
+ * @param arguments 闇�瑕佹浛鎹㈢殑鍙橀噺
+ * @return 杞崲鍚庣殑瀛楃涓�
+ */
+ public static String format(@Nullable String message, @Nullable Object... arguments) {
+ // message 涓� null 杩斿洖绌哄瓧绗︿覆
+ if (message == null) {
+ return StringPool.EMPTY;
+ }
+ // 鍙傛暟涓� null 鎴栬�呬负绌�
+ if (arguments == null || arguments.length == 0) {
+ return message;
+ }
+ StringBuilder sb = new StringBuilder((int) (message.length() * 1.5));
+ int cursor = 0;
+ int index = 0;
+ int argsLength = arguments.length;
+ for (int start, end; (start = message.indexOf('{', cursor)) != -1 && (end = message.indexOf('}', start)) != -1 && index < argsLength; ) {
+ sb.append(message, cursor, start);
+ sb.append(arguments[index]);
+ cursor = end + 1;
+ index++;
+ }
+ sb.append(message.substring(cursor));
+ return sb.toString();
+ }
+
+ /**
+ * 鏍煎紡鍖栨墽琛屾椂闂达紝鍗曚綅涓� ms 鍜� s锛屼繚鐣欎笁浣嶅皬鏁�
+ *
+ * @param nanos 绾崇
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂�
+ */
+ public static String format(long nanos) {
+ if (nanos < 1) {
+ return "0ms";
+ }
+ double millis = (double) nanos / (1000 * 1000);
+ // 涓嶅 1 ms锛屾渶灏忓崟浣嶄负 ms
+ if (millis > 1000) {
+ return String.format("%.3fs", millis / 1000);
+ } else {
+ return String.format("%.3fms", millis);
+ }
+ }
+
+ /**
+ * Convert a {@code Collection} into a delimited {@code String} (e.g., CSV).
+ * <p>Useful for {@code toString()} implementations.
+ *
+ * @param coll the {@code Collection} to convert
+ * @return the delimited {@code String}
+ */
+ public static String join(Collection<?> coll) {
+ return StringUtil.collectionToCommaDelimitedString(coll);
+ }
+
+ /**
+ * Convert a {@code Collection} into a delimited {@code String} (e.g. CSV).
+ * <p>Useful for {@code toString()} implementations.
+ *
+ * @param coll the {@code Collection} to convert
+ * @param delim the delimiter to use (typically a ",")
+ * @return the delimited {@code String}
+ */
+ public static String join(Collection<?> coll, String delim) {
+ return StringUtil.collectionToDelimitedString(coll, delim);
+ }
+
+ /**
+ * Convert a {@code String} array into a comma delimited {@code String}
+ * (i.e., CSV).
+ * <p>Useful for {@code toString()} implementations.
+ *
+ * @param arr the array to display
+ * @return the delimited {@code String}
+ */
+ public static String join(Object[] arr) {
+ return StringUtil.arrayToCommaDelimitedString(arr);
+ }
+
+ /**
+ * Convert a {@code String} array into a delimited {@code String} (e.g. CSV).
+ * <p>Useful for {@code toString()} implementations.
+ *
+ * @param arr the array to display
+ * @param delim the delimiter to use (typically a ",")
+ * @return the delimited {@code String}
+ */
+ public static String join(Object[] arr, String delim) {
+ return StringUtil.arrayToDelimitedString(arr, delim);
+ }
+
+ /**
+ * 瀛楃涓叉槸鍚︾鍚堟寚瀹氱殑 琛ㄨ揪寮�
+ *
+ * <p>
+ * pattern styles: "xxx*", "*xxx", "*xxx*" and "xxx*yyy"
+ * </p>
+ *
+ * @param pattern 琛ㄨ揪寮�
+ * @param str 瀛楃涓�
+ * @return 鏄惁鍖归厤
+ */
+ public static boolean simpleMatch(@Nullable String pattern, @Nullable String str) {
+ return PatternMatchUtils.simpleMatch(pattern, str);
+ }
+
+ /**
+ * 瀛楃涓叉槸鍚︾鍚堟寚瀹氱殑 琛ㄨ揪寮�
+ *
+ * <p>
+ * pattern styles: "xxx*", "*xxx", "*xxx*" and "xxx*yyy"
+ * </p>
+ *
+ * @param patterns 琛ㄨ揪寮� 鏁扮粍
+ * @param str 瀛楃涓�
+ * @return 鏄惁鍖归厤
+ */
+ public static boolean simpleMatch(@Nullable String[] patterns, String str) {
+ return PatternMatchUtils.simpleMatch(patterns, str);
+ }
+
+ /**
+ * 鐢熸垚uuid
+ *
+ * @return UUID
+ */
+ public static String randomUUID() {
+ ThreadLocalRandom random = ThreadLocalRandom.current();
+ return new UUID(random.nextLong(), random.nextLong()).toString().replace(StringPool.DASH, StringPool.EMPTY);
+ }
+
+ /**
+ * 杞箟HTML鐢ㄤ簬瀹夊叏杩囨护
+ *
+ * @param html html
+ * @return {String}
+ */
+ public static String escapeHtml(String html) {
+ return StringUtil.isBlank(html) ? StringPool.EMPTY : HtmlUtils.htmlEscape(html);
+ }
+
+ /**
+ * 娓呯悊瀛楃涓诧紝娓呯悊鍑烘煇浜涗笉鍙瀛楃
+ *
+ * @param txt 瀛楃涓�
+ * @return {String}
+ */
+ public static String cleanChars(String txt) {
+ return txt.replaceAll("[ 銆�`路鈥拷\\f\\t\\v\\s]", "");
+ }
+
+ /**
+ * 鐗规畩瀛楃姝e垯锛宻ql鐗规畩瀛楃鍜岀┖鐧界
+ */
+ private final static Pattern SPECIAL_CHARS_REGEX = Pattern.compile("[`'\"|/,;()-+*%#路鈥拷銆�\\s]");
+
+ /**
+ * 娓呯悊瀛楃涓诧紝娓呯悊鍑烘煇浜涗笉鍙瀛楃鍜屼竴浜泂ql鐗规畩瀛楃
+ *
+ * @param txt 鏂囨湰
+ * @return {String}
+ */
+ @Nullable
+ public static String cleanText(@Nullable String txt) {
+ if (txt == null) {
+ return null;
+ }
+ return SPECIAL_CHARS_REGEX.matcher(txt).replaceAll(StringPool.EMPTY);
+ }
+
+ /**
+ * 鑾峰彇鏍囪瘑绗︼紝鐢ㄤ簬鍙傛暟娓呯悊
+ *
+ * @param param 鍙傛暟
+ * @return 娓呯悊鍚庣殑鏍囪瘑绗�
+ */
+ @Nullable
+ public static String cleanIdentifier(@Nullable String param) {
+ if (param == null) {
+ return null;
+ }
+ StringBuilder paramBuilder = new StringBuilder();
+ for (int i = 0; i < param.length(); i++) {
+ char c = param.charAt(i);
+ if (Character.isJavaIdentifierPart(c)) {
+ paramBuilder.append(c);
+ }
+ }
+ return paramBuilder.toString();
+ }
+
+ /**
+ * 闅忔満鏁扮敓鎴�
+ *
+ * @param count 瀛楃闀垮害
+ * @return 闅忔満鏁�
+ */
+ public static String random(int count) {
+ return StringUtil.random(count, RandomType.ALL);
+ }
+
+ /**
+ * 闅忔満鏁扮敓鎴�
+ *
+ * @param count 瀛楃闀垮害
+ * @param randomType 闅忔満鏁扮被鍒�
+ * @return 闅忔満鏁�
+ */
+ public static String random(int count, RandomType randomType) {
+ if (count == 0) {
+ return StringPool.EMPTY;
+ }
+ Assert.isTrue(count > 0, "Requested random string length " + count + " is less than 0.");
+ final Random random = Holder.SECURE_RANDOM;
+ char[] buffer = new char[count];
+ for (int i = 0; i < count; i++) {
+ String factor = randomType.getFactor();
+ buffer[i] = factor.charAt(random.nextInt(factor.length()));
+ }
+ return new String(buffer);
+ }
+
+ /**
+ * 鏈夊簭鐨勬牸寮忓寲鏂囨湰锛屼娇鐢▄number}鍋氫负鍗犱綅绗�<br>
+ * 渚嬶細<br>
+ * 閫氬父浣跨敤锛歠ormat("this is {0} for {1}", "a", "b") =銆� this is a for b<br>
+ *
+ * @param pattern 鏂囨湰鏍煎紡
+ * @param arguments 鍙傛暟
+ * @return 鏍煎紡鍖栧悗鐨勬枃鏈�
+ */
+ public static String indexedFormat(CharSequence pattern, Object... arguments) {
+ return MessageFormat.format(pattern.toString(), arguments);
+ }
+
+ /**
+ * 鏍煎紡鍖栨枃鏈紝浣跨敤 {varName} 鍗犱綅<br>
+ * map = {a: "aValue", b: "bValue"} format("{a} and {b}", map) ---=銆� aValue and bValue
+ *
+ * @param template 鏂囨湰妯℃澘锛岃鏇挎崲鐨勯儴鍒嗙敤 {key} 琛ㄧず
+ * @param map 鍙傛暟鍊煎
+ * @return 鏍煎紡鍖栧悗鐨勬枃鏈�
+ */
+ public static String format(CharSequence template, Map<?, ?> map) {
+ if (null == template) {
+ return null;
+ }
+ if (null == map || map.isEmpty()) {
+ return template.toString();
+ }
+
+ String template2 = template.toString();
+ for (Map.Entry<?, ?> entry : map.entrySet()) {
+ template2 = template2.replace("{" + entry.getKey() + "}", Func.toStr(entry.getValue()));
+ }
+ return template2;
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓诧紝涓嶅幓闄ゅ垏鍒嗗悗姣忎釜鍏冪礌涓よ竟鐨勭┖鐧界锛屼笉鍘婚櫎绌虹櫧椤�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @param limit 闄愬埗鍒嗙墖鏁帮紝-1涓嶉檺鍒�
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ */
+ public static List<String> split(CharSequence str, char separator, int limit) {
+ return split(str, separator, limit, false, false);
+ }
+
+ /**
+ * 鍒嗗壊 瀛楃涓� 鍒犻櫎甯歌 绌虹櫧绗�
+ *
+ * @param str 瀛楃涓�
+ * @param delimiter 鍒嗗壊绗�
+ * @return 瀛楃涓叉暟缁�
+ */
+ public static String[] splitTrim(@Nullable String str, @Nullable String delimiter) {
+ return StringUtil.delimitedListToStringArray(str, delimiter, " \t\n\n\f");
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓诧紝鍘婚櫎鍒囧垎鍚庢瘡涓厓绱犱袱杈圭殑绌虹櫧绗︼紝鍘婚櫎绌虹櫧椤�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.1.2
+ */
+ public static List<String> splitTrim(CharSequence str, char separator) {
+ return splitTrim(str, separator, -1);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓诧紝鍘婚櫎鍒囧垎鍚庢瘡涓厓绱犱袱杈圭殑绌虹櫧绗︼紝鍘婚櫎绌虹櫧椤�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.2.0
+ */
+ public static List<String> splitTrim(CharSequence str, CharSequence separator) {
+ return splitTrim(str, separator, -1);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓诧紝鍘婚櫎鍒囧垎鍚庢瘡涓厓绱犱袱杈圭殑绌虹櫧绗︼紝鍘婚櫎绌虹櫧椤�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @param limit 闄愬埗鍒嗙墖鏁帮紝-1涓嶉檺鍒�
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.1.0
+ */
+ public static List<String> splitTrim(CharSequence str, char separator, int limit) {
+ return split(str, separator, limit, true, true);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓诧紝鍘婚櫎鍒囧垎鍚庢瘡涓厓绱犱袱杈圭殑绌虹櫧绗︼紝鍘婚櫎绌虹櫧椤�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @param limit 闄愬埗鍒嗙墖鏁帮紝-1涓嶉檺鍒�
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.2.0
+ */
+ public static List<String> splitTrim(CharSequence str, CharSequence separator, int limit) {
+ return split(str, separator, limit, true, true);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓诧紝涓嶉檺鍒跺垎鐗囨暟閲�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @param isTrim 鏄惁鍘婚櫎鍒囧垎瀛楃涓插悗姣忎釜鍏冪礌涓よ竟鐨勭┖鏍�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.0.8
+ */
+ public static List<String> split(CharSequence str, char separator, boolean isTrim, boolean ignoreEmpty) {
+ return split(str, separator, 0, isTrim, ignoreEmpty);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @param limit 闄愬埗鍒嗙墖鏁帮紝-1涓嶉檺鍒�
+ * @param isTrim 鏄惁鍘婚櫎鍒囧垎瀛楃涓插悗姣忎釜鍏冪礌涓よ竟鐨勭┖鏍�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.0.8
+ */
+ public static List<String> split(CharSequence str, char separator, int limit, boolean isTrim, boolean ignoreEmpty) {
+ if (null == str) {
+ return new ArrayList<>(0);
+ }
+ return StrSpliter.split(str.toString(), separator, limit, isTrim, ignoreEmpty);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗﹀瓧绗�
+ * @param limit 闄愬埗鍒嗙墖鏁帮紝-1涓嶉檺鍒�
+ * @param isTrim 鏄惁鍘婚櫎鍒囧垎瀛楃涓插悗姣忎釜鍏冪礌涓よ竟鐨勭┖鏍�
+ * @param ignoreEmpty 鏄惁蹇界暐绌轰覆
+ * @return 鍒囧垎鍚庣殑闆嗗悎
+ * @since 3.2.0
+ */
+ public static List<String> split(CharSequence str, CharSequence separator, int limit, boolean isTrim, boolean ignoreEmpty) {
+ if (null == str) {
+ return new ArrayList<>(0);
+ }
+ final String separatorStr = (null == separator) ? null : separator.toString();
+ return StrSpliter.split(str.toString(), separatorStr, limit, isTrim, ignoreEmpty);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗�
+ * @return 瀛楃涓�
+ */
+ public static String[] split(CharSequence str, CharSequence separator) {
+ if (str == null) {
+ return new String[]{};
+ }
+
+ final String separatorStr = (null == separator) ? null : separator.toString();
+ return StrSpliter.splitToArray(str.toString(), separatorStr, 0, false, false);
+ }
+
+ /**
+ * 鏍规嵁缁欏畾闀垮害锛屽皢缁欏畾瀛楃涓叉埅鍙栦负澶氫釜閮ㄥ垎
+ *
+ * @param str 瀛楃涓�
+ * @param len 姣忎竴涓皬鑺傜殑闀垮害
+ * @return 鎴彇鍚庣殑瀛楃涓叉暟缁�
+ * @see StrSpliter#splitByLength(String, int)
+ */
+ public static String[] split(CharSequence str, int len) {
+ if (null == str) {
+ return new String[]{};
+ }
+ return StrSpliter.splitByLength(str.toString(), len);
+ }
+
+ /**
+ * 鎸囧畾瀛楃鏄惁鍦ㄥ瓧绗︿覆涓嚭鐜拌繃
+ *
+ * @param str 瀛楃涓�
+ * @param searchChar 琚煡鎵剧殑瀛楃
+ * @return 鏄惁鍖呭惈
+ * @since 3.1.2
+ */
+ public static boolean contains(CharSequence str, char searchChar) {
+ return indexOf(str, searchChar) > -1;
+ }
+
+ /**
+ * 鏌ユ壘鎸囧畾瀛楃涓叉槸鍚﹀寘鍚寚瀹氬瓧绗︿覆鍒楄〃涓殑浠绘剰涓�涓瓧绗︿覆
+ *
+ * @param str 鎸囧畾瀛楃涓�
+ * @param testStrs 闇�瑕佹鏌ョ殑瀛楃涓叉暟缁�
+ * @return 鏄惁鍖呭惈浠绘剰涓�涓瓧绗︿覆
+ * @since 3.2.0
+ */
+ public static boolean containsAny(CharSequence str, CharSequence... testStrs) {
+ return null != getContainsStr(str, testStrs);
+ }
+
+ /**
+ * 鏌ユ壘鎸囧畾瀛楃涓叉槸鍚﹀寘鍚寚瀹氬瓧绗︿覆鍒楄〃涓殑浠绘剰涓�涓瓧绗︿覆锛屽鏋滃寘鍚繑鍥炴壘鍒扮殑绗竴涓瓧绗︿覆
+ *
+ * @param str 鎸囧畾瀛楃涓�
+ * @param testStrs 闇�瑕佹鏌ョ殑瀛楃涓叉暟缁�
+ * @return 琚寘鍚殑绗竴涓瓧绗︿覆
+ * @since 3.2.0
+ */
+ public static String getContainsStr(CharSequence str, CharSequence... testStrs) {
+ if (isEmpty(str) || Func.isEmpty(testStrs)) {
+ return null;
+ }
+ for (CharSequence checkStr : testStrs) {
+ if (str.toString().contains(checkStr)) {
+ return checkStr.toString();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 鏄惁鍖呭惈鐗瑰畾瀛楃锛屽拷鐣ュぇ灏忓啓锛屽鏋滅粰瀹氫袱涓弬鏁伴兘涓�<code>null</code>锛岃繑鍥瀟rue
+ *
+ * @param str 琚娴嬪瓧绗︿覆
+ * @param testStr 琚祴璇曟槸鍚﹀寘鍚殑瀛楃涓�
+ * @return 鏄惁鍖呭惈
+ */
+ public static boolean containsIgnoreCase(CharSequence str, CharSequence testStr) {
+ if (null == str) {
+ // 濡傛灉琚洃娴嬪瓧绗︿覆鍜�
+ return null == testStr;
+ }
+ return str.toString().toLowerCase().contains(testStr.toString().toLowerCase());
+ }
+
+ /**
+ * 鏌ユ壘鎸囧畾瀛楃涓叉槸鍚﹀寘鍚寚瀹氬瓧绗︿覆鍒楄〃涓殑浠绘剰涓�涓瓧绗︿覆<br>
+ * 蹇界暐澶у皬鍐�
+ *
+ * @param str 鎸囧畾瀛楃涓�
+ * @param testStrs 闇�瑕佹鏌ョ殑瀛楃涓叉暟缁�
+ * @return 鏄惁鍖呭惈浠绘剰涓�涓瓧绗︿覆
+ * @since 3.2.0
+ */
+ public static boolean containsAnyIgnoreCase(CharSequence str, CharSequence... testStrs) {
+ return null != getContainsStrIgnoreCase(str, testStrs);
+ }
+
+ /**
+ * 鏌ユ壘鎸囧畾瀛楃涓叉槸鍚﹀寘鍚寚瀹氬瓧绗︿覆鍒楄〃涓殑浠绘剰涓�涓瓧绗︿覆锛屽鏋滃寘鍚繑鍥炴壘鍒扮殑绗竴涓瓧绗︿覆<br>
+ * 蹇界暐澶у皬鍐�
+ *
+ * @param str 鎸囧畾瀛楃涓�
+ * @param testStrs 闇�瑕佹鏌ョ殑瀛楃涓叉暟缁�
+ * @return 琚寘鍚殑绗竴涓瓧绗︿覆
+ * @since 3.2.0
+ */
+ public static String getContainsStrIgnoreCase(CharSequence str, CharSequence... testStrs) {
+ if (isEmpty(str) || Func.isEmpty(testStrs)) {
+ return null;
+ }
+ for (CharSequence testStr : testStrs) {
+ if (containsIgnoreCase(str, testStr)) {
+ return testStr.toString();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 鏀硅繘JDK subString<br>
+ * index浠�0寮�濮嬭绠楋紝鏈�鍚庝竴涓瓧绗︿负-1<br>
+ * 濡傛灉from鍜宼o浣嶇疆涓�鏍凤紝杩斿洖 "" <br>
+ * 濡傛灉from鎴杢o涓鸿礋鏁帮紝鍒欐寜鐓ength浠庡悗鍚戝墠鏁颁綅缃紝濡傛灉缁濆鍊煎ぇ浜庡瓧绗︿覆闀垮害锛屽垯from褰掑埌0锛宼o褰掑埌length<br>
+ * 濡傛灉缁忚繃淇鐨刬ndex涓璮rom澶т簬to锛屽垯浜掓崲from鍜宼o example: <br>
+ * abcdefgh 2 3 =銆� c <br>
+ * abcdefgh 2 -3 =銆� cde <br>
+ *
+ * @param str String
+ * @param fromIndex 寮�濮嬬殑index锛堝寘鎷級
+ * @param toIndex 缁撴潫鐨刬ndex锛堜笉鍖呮嫭锛�
+ * @return 瀛椾覆
+ */
+ public static String sub(CharSequence str, int fromIndex, int toIndex) {
+ if (isEmpty(str)) {
+ return StringPool.EMPTY;
+ }
+ int len = str.length();
+
+ if (fromIndex < 0) {
+ fromIndex = len + fromIndex;
+ if (fromIndex < 0) {
+ fromIndex = 0;
+ }
+ } else if (fromIndex > len) {
+ fromIndex = len;
+ }
+
+ if (toIndex < 0) {
+ toIndex = len + toIndex;
+ if (toIndex < 0) {
+ toIndex = len;
+ }
+ } else if (toIndex > len) {
+ toIndex = len;
+ }
+
+ if (toIndex < fromIndex) {
+ int tmp = fromIndex;
+ fromIndex = toIndex;
+ toIndex = tmp;
+ }
+
+ if (fromIndex == toIndex) {
+ return StringPool.EMPTY;
+ }
+
+ return str.toString().substring(fromIndex, toIndex);
+ }
+
+
+ /**
+ * 鎴彇鍒嗛殧瀛楃涓蹭箣鍓嶇殑瀛楃涓诧紝涓嶅寘鎷垎闅斿瓧绗︿覆<br>
+ * 濡傛灉缁欏畾鐨勫瓧绗︿覆涓虹┖涓诧紙null鎴�""锛夋垨鑰呭垎闅斿瓧绗︿覆涓簄ull锛岃繑鍥炲師瀛楃涓�<br>
+ * 濡傛灉鍒嗛殧瀛楃涓蹭负绌轰覆""锛屽垯杩斿洖绌轰覆锛屽鏋滃垎闅斿瓧绗︿覆鏈壘鍒帮紝杩斿洖鍘熷瓧绗︿覆
+ * <p>
+ * 鏍楀瓙锛�
+ *
+ * <pre>
+ * StringUtil.subBefore(null, *) = null
+ * StringUtil.subBefore("", *) = ""
+ * StringUtil.subBefore("abc", "a") = ""
+ * StringUtil.subBefore("abcba", "b") = "a"
+ * StringUtil.subBefore("abc", "c") = "ab"
+ * StringUtil.subBefore("abc", "d") = "abc"
+ * StringUtil.subBefore("abc", "") = ""
+ * StringUtil.subBefore("abc", null) = "abc"
+ * </pre>
+ *
+ * @param string 琚煡鎵剧殑瀛楃涓�
+ * @param separator 鍒嗛殧瀛楃涓诧紙涓嶅寘鎷級
+ * @param isLastSeparator 鏄惁鏌ユ壘鏈�鍚庝竴涓垎闅斿瓧绗︿覆锛堝娆″嚭鐜板垎闅斿瓧绗︿覆鏃堕�夊彇鏈�鍚庝竴涓級锛宼rue涓洪�夊彇鏈�鍚庝竴涓�
+ * @return 鍒囧壊鍚庣殑瀛楃涓�
+ * @since 3.1.1
+ */
+ public static String subBefore(CharSequence string, CharSequence separator, boolean isLastSeparator) {
+ if (isEmpty(string) || separator == null) {
+ return null == string ? null : string.toString();
+ }
+
+ final String str = string.toString();
+ final String sep = separator.toString();
+ if (sep.isEmpty()) {
+ return StringPool.EMPTY;
+ }
+ final int pos = isLastSeparator ? str.lastIndexOf(sep) : str.indexOf(sep);
+ if (pos == INDEX_NOT_FOUND) {
+ return str;
+ }
+ return str.substring(0, pos);
+ }
+
+ /**
+ * 鎴彇鍒嗛殧瀛楃涓蹭箣鍚庣殑瀛楃涓诧紝涓嶅寘鎷垎闅斿瓧绗︿覆<br>
+ * 濡傛灉缁欏畾鐨勫瓧绗︿覆涓虹┖涓诧紙null鎴�""锛夛紝杩斿洖鍘熷瓧绗︿覆<br>
+ * 濡傛灉鍒嗛殧瀛楃涓蹭负绌轰覆锛坣ull鎴�""锛夛紝鍒欒繑鍥炵┖涓诧紝濡傛灉鍒嗛殧瀛楃涓叉湭鎵惧埌锛岃繑鍥炵┖涓�
+ * <p>
+ * 鏍楀瓙锛�
+ *
+ * <pre>
+ * StringUtil.subAfter(null, *) = null
+ * StringUtil.subAfter("", *) = ""
+ * StringUtil.subAfter(*, null) = ""
+ * StringUtil.subAfter("abc", "a") = "bc"
+ * StringUtil.subAfter("abcba", "b") = "cba"
+ * StringUtil.subAfter("abc", "c") = ""
+ * StringUtil.subAfter("abc", "d") = ""
+ * StringUtil.subAfter("abc", "") = "abc"
+ * </pre>
+ *
+ * @param string 琚煡鎵剧殑瀛楃涓�
+ * @param separator 鍒嗛殧瀛楃涓诧紙涓嶅寘鎷級
+ * @param isLastSeparator 鏄惁鏌ユ壘鏈�鍚庝竴涓垎闅斿瓧绗︿覆锛堝娆″嚭鐜板垎闅斿瓧绗︿覆鏃堕�夊彇鏈�鍚庝竴涓級锛宼rue涓洪�夊彇鏈�鍚庝竴涓�
+ * @return 鍒囧壊鍚庣殑瀛楃涓�
+ * @since 3.1.1
+ */
+ public static String subAfter(CharSequence string, CharSequence separator, boolean isLastSeparator) {
+ if (isEmpty(string)) {
+ return null == string ? null : string.toString();
+ }
+ if (separator == null) {
+ return StringPool.EMPTY;
+ }
+ final String str = string.toString();
+ final String sep = separator.toString();
+ final int pos = isLastSeparator ? str.lastIndexOf(sep) : str.indexOf(sep);
+ if (pos == INDEX_NOT_FOUND) {
+ return StringPool.EMPTY;
+ }
+ return str.substring(pos + separator.length());
+ }
+
+ /**
+ * 鎴彇鎸囧畾瀛楃涓蹭腑闂撮儴鍒嗭紝涓嶅寘鎷爣璇嗗瓧绗︿覆<br>
+ * <p>
+ * 鏍楀瓙锛�
+ *
+ * <pre>
+ * StringUtil.subBetween("wx[b]yz", "[", "]") = "b"
+ * StringUtil.subBetween(null, *, *) = null
+ * StringUtil.subBetween(*, null, *) = null
+ * StringUtil.subBetween(*, *, null) = null
+ * StringUtil.subBetween("", "", "") = ""
+ * StringUtil.subBetween("", "", "]") = null
+ * StringUtil.subBetween("", "[", "]") = null
+ * StringUtil.subBetween("yabcz", "", "") = ""
+ * StringUtil.subBetween("yabcz", "y", "z") = "abc"
+ * StringUtil.subBetween("yabczyabcz", "y", "z") = "abc"
+ * </pre>
+ *
+ * @param str 琚垏鍓茬殑瀛楃涓�
+ * @param before 鎴彇寮�濮嬬殑瀛楃涓叉爣璇�
+ * @param after 鎴彇鍒扮殑瀛楃涓叉爣璇�
+ * @return 鎴彇鍚庣殑瀛楃涓�
+ * @since 3.1.1
+ */
+ public static String subBetween(CharSequence str, CharSequence before, CharSequence after) {
+ if (str == null || before == null || after == null) {
+ return null;
+ }
+
+ final String str2 = str.toString();
+ final String before2 = before.toString();
+ final String after2 = after.toString();
+
+ final int start = str2.indexOf(before2);
+ if (start != INDEX_NOT_FOUND) {
+ final int end = str2.indexOf(after2, start + before2.length());
+ if (end != INDEX_NOT_FOUND) {
+ return str2.substring(start + before2.length(), end);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 鎴彇鎸囧畾瀛楃涓蹭腑闂撮儴鍒嗭紝涓嶅寘鎷爣璇嗗瓧绗︿覆<br>
+ * <p>
+ * 鏍楀瓙锛�
+ *
+ * <pre>
+ * StringUtil.subBetween(null, *) = null
+ * StringUtil.subBetween("", "") = ""
+ * StringUtil.subBetween("", "tag") = null
+ * StringUtil.subBetween("tagabctag", null) = null
+ * StringUtil.subBetween("tagabctag", "") = ""
+ * StringUtil.subBetween("tagabctag", "tag") = "abc"
+ * </pre>
+ *
+ * @param str 琚垏鍓茬殑瀛楃涓�
+ * @param beforeAndAfter 鎴彇寮�濮嬪拰缁撴潫鐨勫瓧绗︿覆鏍囪瘑
+ * @return 鎴彇鍚庣殑瀛楃涓�
+ * @since 3.1.1
+ */
+ public static String subBetween(CharSequence str, CharSequence beforeAndAfter) {
+ return subBetween(str, beforeAndAfter, beforeAndAfter);
+ }
+
+ /**
+ * 鍘绘帀鎸囧畾鍓嶇紑
+ *
+ * @param str 瀛楃涓�
+ * @param prefix 鍓嶇紑
+ * @return 鍒囨帀鍚庣殑瀛楃涓诧紝鑻ュ墠缂�涓嶆槸 preffix锛� 杩斿洖鍘熷瓧绗︿覆
+ */
+ public static String removePrefix(CharSequence str, CharSequence prefix) {
+ if (isEmpty(str) || isEmpty(prefix)) {
+ return StringPool.EMPTY;
+ }
+
+ final String str2 = str.toString();
+ if (str2.startsWith(prefix.toString())) {
+ return subSuf(str2, prefix.length());
+ }
+ return str2;
+ }
+
+ /**
+ * 蹇界暐澶у皬鍐欏幓鎺夋寚瀹氬墠缂�
+ *
+ * @param str 瀛楃涓�
+ * @param prefix 鍓嶇紑
+ * @return 鍒囨帀鍚庣殑瀛楃涓诧紝鑻ュ墠缂�涓嶆槸 prefix锛� 杩斿洖鍘熷瓧绗︿覆
+ */
+ public static String removePrefixIgnoreCase(CharSequence str, CharSequence prefix) {
+ if (isEmpty(str) || isEmpty(prefix)) {
+ return StringPool.EMPTY;
+ }
+
+ final String str2 = str.toString();
+ if (str2.toLowerCase().startsWith(prefix.toString().toLowerCase())) {
+ return subSuf(str2, prefix.length());
+ }
+ return str2;
+ }
+
+ /**
+ * 鍘绘帀鎸囧畾鍚庣紑
+ *
+ * @param str 瀛楃涓�
+ * @param suffix 鍚庣紑
+ * @return 鍒囨帀鍚庣殑瀛楃涓诧紝鑻ュ悗缂�涓嶆槸 suffix锛� 杩斿洖鍘熷瓧绗︿覆
+ */
+ public static String removeSuffix(CharSequence str, CharSequence suffix) {
+ if (isEmpty(str) || isEmpty(suffix)) {
+ return StringPool.EMPTY;
+ }
+
+ final String str2 = str.toString();
+ if (str2.endsWith(suffix.toString())) {
+ return subPre(str2, str2.length() - suffix.length());
+ }
+ return str2;
+ }
+
+ /**
+ * 鍘绘帀鎸囧畾鍚庣紑锛屽苟灏忓啓棣栧瓧姣�
+ *
+ * @param str 瀛楃涓�
+ * @param suffix 鍚庣紑
+ * @return 鍒囨帀鍚庣殑瀛楃涓诧紝鑻ュ悗缂�涓嶆槸 suffix锛� 杩斿洖鍘熷瓧绗︿覆
+ */
+ public static String removeSufAndLowerFirst(CharSequence str, CharSequence suffix) {
+ return firstCharToLower(removeSuffix(str, suffix));
+ }
+
+ /**
+ * 蹇界暐澶у皬鍐欏幓鎺夋寚瀹氬悗缂�
+ *
+ * @param str 瀛楃涓�
+ * @param suffix 鍚庣紑
+ * @return 鍒囨帀鍚庣殑瀛楃涓诧紝鑻ュ悗缂�涓嶆槸 suffix锛� 杩斿洖鍘熷瓧绗︿覆
+ */
+ public static String removeSuffixIgnoreCase(CharSequence str, CharSequence suffix) {
+ if (isEmpty(str) || isEmpty(suffix)) {
+ return StringPool.EMPTY;
+ }
+
+ final String str2 = str.toString();
+ if (str2.toLowerCase().endsWith(suffix.toString().toLowerCase())) {
+ return subPre(str2, str2.length() - suffix.length());
+ }
+ return str2;
+ }
+
+ /**
+ * 棣栧瓧姣嶅彉灏忓啓
+ *
+ * @param str 瀛楃涓�
+ * @return {String}
+ */
+ public static String firstCharToLower(String str) {
+ char firstChar = str.charAt(0);
+ if (firstChar >= CharPool.UPPER_A && firstChar <= CharPool.UPPER_Z) {
+ char[] arr = str.toCharArray();
+ arr[0] += (CharPool.LOWER_A - CharPool.UPPER_A);
+ return new String(arr);
+ }
+ return str;
+ }
+
+ /**
+ * 棣栧瓧姣嶅彉澶у啓
+ *
+ * @param str 瀛楃涓�
+ * @return {String}
+ */
+ public static String firstCharToUpper(String str) {
+ char firstChar = str.charAt(0);
+ if (firstChar >= CharPool.LOWER_A && firstChar <= CharPool.LOWER_Z) {
+ char[] arr = str.toCharArray();
+ arr[0] -= (CharPool.LOWER_A - CharPool.UPPER_A);
+ return new String(arr);
+ }
+ return str;
+ }
+
+ /**
+ * 鍒囧壊鎸囧畾浣嶇疆涔嬪墠閮ㄥ垎鐨勫瓧绗︿覆
+ *
+ * @param string 瀛楃涓�
+ * @param toIndex 鍒囧壊鍒扮殑浣嶇疆锛堜笉鍖呮嫭锛�
+ * @return 鍒囧壊鍚庣殑鍓╀綑鐨勫墠鍗婇儴鍒嗗瓧绗︿覆
+ */
+ public static String subPre(CharSequence string, int toIndex) {
+ return sub(string, 0, toIndex);
+ }
+
+ /**
+ * 鍒囧壊鎸囧畾浣嶇疆涔嬪悗閮ㄥ垎鐨勫瓧绗︿覆
+ *
+ * @param string 瀛楃涓�
+ * @param fromIndex 鍒囧壊寮�濮嬬殑浣嶇疆锛堝寘鎷級
+ * @return 鍒囧壊鍚庡悗鍓╀綑鐨勫悗鍗婇儴鍒嗗瓧绗︿覆
+ */
+ public static String subSuf(CharSequence string, int fromIndex) {
+ if (isEmpty(string)) {
+ return null;
+ }
+ return sub(string, fromIndex, string.length());
+ }
+
+ /**
+ * 鎸囧畾鑼冨洿鍐呮煡鎵炬寚瀹氬瓧绗�
+ *
+ * @param str 瀛楃涓�
+ * @param searchChar 琚煡鎵剧殑瀛楃
+ * @return 浣嶇疆
+ */
+ public static int indexOf(final CharSequence str, char searchChar) {
+ return indexOf(str, searchChar, 0);
+ }
+
+ /**
+ * 鎸囧畾鑼冨洿鍐呮煡鎵炬寚瀹氬瓧绗�
+ *
+ * @param str 瀛楃涓�
+ * @param searchChar 琚煡鎵剧殑瀛楃
+ * @param start 璧峰浣嶇疆锛屽鏋滃皬浜�0锛屼粠0寮�濮嬫煡鎵�
+ * @return 浣嶇疆
+ */
+ public static int indexOf(final CharSequence str, char searchChar, int start) {
+ if (str instanceof String) {
+ return ((String) str).indexOf(searchChar, start);
+ } else {
+ return indexOf(str, searchChar, start, -1);
+ }
+ }
+
+ /**
+ * 鎸囧畾鑼冨洿鍐呮煡鎵炬寚瀹氬瓧绗�
+ *
+ * @param str 瀛楃涓�
+ * @param searchChar 琚煡鎵剧殑瀛楃
+ * @param start 璧峰浣嶇疆锛屽鏋滃皬浜�0锛屼粠0寮�濮嬫煡鎵�
+ * @param end 缁堟浣嶇疆锛屽鏋滆秴杩噑tr.length()鍒欓粯璁ゆ煡鎵惧埌瀛楃涓叉湯灏�
+ * @return 浣嶇疆
+ */
+ public static int indexOf(final CharSequence str, char searchChar, int start, int end) {
+ final int len = str.length();
+ if (start < 0 || start > len) {
+ start = 0;
+ }
+ if (end > len || end < 0) {
+ end = len;
+ }
+ for (int i = start; i < end; i++) {
+ if (str.charAt(i) == searchChar) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * 鎸囧畾鑼冨洿鍐呮煡鎵惧瓧绗︿覆锛屽拷鐣ュぇ灏忓啓<br>
+ *
+ * <pre>
+ * StringUtil.indexOfIgnoreCase(null, *, *) = -1
+ * StringUtil.indexOfIgnoreCase(*, null, *) = -1
+ * StringUtil.indexOfIgnoreCase("", "", 0) = 0
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "A", 0) = 0
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "B", 0) = 2
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "B", 3) = 5
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "B", 9) = -1
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "B", -1) = 2
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "", 2) = 2
+ * StringUtil.indexOfIgnoreCase("abc", "", 9) = -1
+ * </pre>
+ *
+ * @param str 瀛楃涓�
+ * @param searchStr 闇�瑕佹煡鎵句綅缃殑瀛楃涓�
+ * @return 浣嶇疆
+ * @since 3.2.1
+ */
+ public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) {
+ return indexOfIgnoreCase(str, searchStr, 0);
+ }
+
+ /**
+ * 鎸囧畾鑼冨洿鍐呮煡鎵惧瓧绗︿覆
+ *
+ * <pre>
+ * StringUtil.indexOfIgnoreCase(null, *, *) = -1
+ * StringUtil.indexOfIgnoreCase(*, null, *) = -1
+ * StringUtil.indexOfIgnoreCase("", "", 0) = 0
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "A", 0) = 0
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "B", 0) = 2
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "B", 3) = 5
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "B", 9) = -1
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "B", -1) = 2
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "", 2) = 2
+ * StringUtil.indexOfIgnoreCase("abc", "", 9) = -1
+ * </pre>
+ *
+ * @param str 瀛楃涓�
+ * @param searchStr 闇�瑕佹煡鎵句綅缃殑瀛楃涓�
+ * @param fromIndex 璧峰浣嶇疆
+ * @return 浣嶇疆
+ * @since 3.2.1
+ */
+ public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, int fromIndex) {
+ return indexOf(str, searchStr, fromIndex, true);
+ }
+
+ /**
+ * 鎸囧畾鑼冨洿鍐呭弽鍚戞煡鎵惧瓧绗︿覆
+ *
+ * @param str 瀛楃涓�
+ * @param searchStr 闇�瑕佹煡鎵句綅缃殑瀛楃涓�
+ * @param fromIndex 璧峰浣嶇疆
+ * @param ignoreCase 鏄惁蹇界暐澶у皬鍐�
+ * @return 浣嶇疆
+ * @since 3.2.1
+ */
+ public static int indexOf(final CharSequence str, CharSequence searchStr, int fromIndex, boolean ignoreCase) {
+ if (str == null || searchStr == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (fromIndex < 0) {
+ fromIndex = 0;
+ }
+
+ final int endLimit = str.length() - searchStr.length() + 1;
+ if (fromIndex > endLimit) {
+ return INDEX_NOT_FOUND;
+ }
+ if (searchStr.length() == 0) {
+ return fromIndex;
+ }
+
+ if (false == ignoreCase) {
+ // 涓嶅拷鐣ュぇ灏忓啓璋冪敤JDK鏂规硶
+ return str.toString().indexOf(searchStr.toString(), fromIndex);
+ }
+
+ for (int i = fromIndex; i < endLimit; i++) {
+ if (isSubEquals(str, i, searchStr, 0, searchStr.length(), true)) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * 鎸囧畾鑼冨洿鍐呮煡鎵惧瓧绗︿覆锛屽拷鐣ュぇ灏忓啓<br>
+ *
+ * @param str 瀛楃涓�
+ * @param searchStr 闇�瑕佹煡鎵句綅缃殑瀛楃涓�
+ * @return 浣嶇疆
+ * @since 3.2.1
+ */
+ public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) {
+ return lastIndexOfIgnoreCase(str, searchStr, str.length());
+ }
+
+ /**
+ * 鎸囧畾鑼冨洿鍐呮煡鎵惧瓧绗︿覆锛屽拷鐣ュぇ灏忓啓<br>
+ *
+ * @param str 瀛楃涓�
+ * @param searchStr 闇�瑕佹煡鎵句綅缃殑瀛楃涓�
+ * @param fromIndex 璧峰浣嶇疆锛屼粠鍚庡線鍓嶈鏁�
+ * @return 浣嶇疆
+ * @since 3.2.1
+ */
+ public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, int fromIndex) {
+ return lastIndexOf(str, searchStr, fromIndex, true);
+ }
+
+ /**
+ * 鎸囧畾鑼冨洿鍐呮煡鎵惧瓧绗︿覆<br>
+ *
+ * @param str 瀛楃涓�
+ * @param searchStr 闇�瑕佹煡鎵句綅缃殑瀛楃涓�
+ * @param fromIndex 璧峰浣嶇疆锛屼粠鍚庡線鍓嶈鏁�
+ * @param ignoreCase 鏄惁蹇界暐澶у皬鍐�
+ * @return 浣嶇疆
+ * @since 3.2.1
+ */
+ public static int lastIndexOf(final CharSequence str, final CharSequence searchStr, int fromIndex, boolean ignoreCase) {
+ if (str == null || searchStr == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (fromIndex < 0) {
+ fromIndex = 0;
+ }
+ fromIndex = Math.min(fromIndex, str.length());
+
+ if (searchStr.length() == 0) {
+ return fromIndex;
+ }
+
+ if (false == ignoreCase) {
+ // 涓嶅拷鐣ュぇ灏忓啓璋冪敤JDK鏂规硶
+ return str.toString().lastIndexOf(searchStr.toString(), fromIndex);
+ }
+
+ for (int i = fromIndex; i > 0; i--) {
+ if (isSubEquals(str, i, searchStr, 0, searchStr.length(), true)) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * 杩斿洖瀛楃涓� searchStr 鍦ㄥ瓧绗︿覆 str 涓 ordinal 娆″嚭鐜扮殑浣嶇疆銆�<br>
+ * 姝ゆ柟娉曟潵鑷細Apache-Commons-Lang
+ * <p>
+ * 鏍楀瓙锛�*浠h〃浠绘剰瀛楃锛夛細
+ *
+ * <pre>
+ * StringUtil.ordinalIndexOf(null, *, *) = -1
+ * StringUtil.ordinalIndexOf(*, null, *) = -1
+ * StringUtil.ordinalIndexOf("", "", *) = 0
+ * StringUtil.ordinalIndexOf("aabaabaa", "a", 1) = 0
+ * StringUtil.ordinalIndexOf("aabaabaa", "a", 2) = 1
+ * StringUtil.ordinalIndexOf("aabaabaa", "b", 1) = 2
+ * StringUtil.ordinalIndexOf("aabaabaa", "b", 2) = 5
+ * StringUtil.ordinalIndexOf("aabaabaa", "ab", 1) = 1
+ * StringUtil.ordinalIndexOf("aabaabaa", "ab", 2) = 4
+ * StringUtil.ordinalIndexOf("aabaabaa", "", 1) = 0
+ * StringUtil.ordinalIndexOf("aabaabaa", "", 2) = 0
+ * </pre>
+ *
+ * @param str 琚鏌ョ殑瀛楃涓诧紝鍙互涓簄ull
+ * @param searchStr 琚煡鎵剧殑瀛楃涓诧紝鍙互涓簄ull
+ * @param ordinal 绗嚑娆″嚭鐜扮殑浣嶇疆
+ * @return 鏌ユ壘鍒扮殑浣嶇疆
+ * @since 3.2.3
+ */
+ public static int ordinalIndexOf(String str, String searchStr, int ordinal) {
+ if (str == null || searchStr == null || ordinal <= 0) {
+ return INDEX_NOT_FOUND;
+ }
+ if (searchStr.length() == 0) {
+ return 0;
+ }
+ int found = 0;
+ int index = INDEX_NOT_FOUND;
+ do {
+ index = str.indexOf(searchStr, index + 1);
+ if (index < 0) {
+ return index;
+ }
+ found++;
+ } while (found < ordinal);
+ return index;
+ }
+
+ /**
+ * 鎴彇涓や釜瀛楃涓茬殑涓嶅悓閮ㄥ垎锛堥暱搴︿竴鑷达級锛屽垽鏂埅鍙栫殑瀛愪覆鏄惁鐩稿悓<br>
+ * 浠绘剰涓�涓瓧绗︿覆涓簄ull杩斿洖false
+ *
+ * @param str1 绗竴涓瓧绗︿覆
+ * @param start1 绗竴涓瓧绗︿覆寮�濮嬬殑浣嶇疆
+ * @param str2 绗簩涓瓧绗︿覆
+ * @param start2 绗簩涓瓧绗︿覆寮�濮嬬殑浣嶇疆
+ * @param length 鎴彇闀垮害
+ * @param ignoreCase 鏄惁蹇界暐澶у皬鍐�
+ * @return 瀛愪覆鏄惁鐩稿悓
+ * @since 3.2.1
+ */
+ public static boolean isSubEquals(CharSequence str1, int start1, CharSequence str2, int start2, int length, boolean ignoreCase) {
+ if (null == str1 || null == str2) {
+ return false;
+ }
+
+ return str1.toString().regionMatches(ignoreCase, start1, str2.toString(), start2, length);
+ }
+
+ /**
+ * 姣旇緝涓や釜瀛楃涓诧紙澶у皬鍐欐晱鎰燂級銆�
+ *
+ * <pre>
+ * equalsIgnoreCase(null, null) = true
+ * equalsIgnoreCase(null, "abc") = false
+ * equalsIgnoreCase("abc", null) = false
+ * equalsIgnoreCase("abc", "abc") = true
+ * equalsIgnoreCase("abc", "ABC") = true
+ * </pre>
+ *
+ * @param str1 瑕佹瘮杈冪殑瀛楃涓�1
+ * @param str2 瑕佹瘮杈冪殑瀛楃涓�2
+ * @return 濡傛灉涓や釜瀛楃涓茬浉鍚岋紝鎴栬�呴兘鏄�<code>null</code>锛屽垯杩斿洖<code>true</code>
+ */
+ public static boolean equals(CharSequence str1, CharSequence str2) {
+ return equals(str1, str2, false);
+ }
+
+ /**
+ * 姣旇緝涓や釜瀛楃涓诧紙澶у皬鍐欎笉鏁忔劅锛夈��
+ *
+ * <pre>
+ * equalsIgnoreCase(null, null) = true
+ * equalsIgnoreCase(null, "abc") = false
+ * equalsIgnoreCase("abc", null) = false
+ * equalsIgnoreCase("abc", "abc") = true
+ * equalsIgnoreCase("abc", "ABC") = true
+ * </pre>
+ *
+ * @param str1 瑕佹瘮杈冪殑瀛楃涓�1
+ * @param str2 瑕佹瘮杈冪殑瀛楃涓�2
+ * @return 濡傛灉涓や釜瀛楃涓茬浉鍚岋紝鎴栬�呴兘鏄�<code>null</code>锛屽垯杩斿洖<code>true</code>
+ */
+ public static boolean equalsIgnoreCase(CharSequence str1, CharSequence str2) {
+ return equals(str1, str2, true);
+ }
+
+ /**
+ * 姣旇緝涓や釜瀛楃涓叉槸鍚︾浉绛夈��
+ *
+ * @param str1 瑕佹瘮杈冪殑瀛楃涓�1
+ * @param str2 瑕佹瘮杈冪殑瀛楃涓�2
+ * @param ignoreCase 鏄惁蹇界暐澶у皬鍐�
+ * @return 濡傛灉涓や釜瀛楃涓茬浉鍚岋紝鎴栬�呴兘鏄�<code>null</code>锛屽垯杩斿洖<code>true</code>
+ * @since 3.2.0
+ */
+ public static boolean equals(CharSequence str1, CharSequence str2, boolean ignoreCase) {
+ if (null == str1) {
+ // 鍙湁涓や釜閮戒负null鎵嶅垽鏂浉绛�
+ return str2 == null;
+ }
+ if (null == str2) {
+ // 瀛楃涓�2绌猴紝瀛楃涓�1闈炵┖锛岀洿鎺alse
+ return false;
+ }
+
+ if (ignoreCase) {
+ return str1.toString().equalsIgnoreCase(str2.toString());
+ } else {
+ return str1.equals(str2);
+ }
+ }
+
+ /**
+ * 鍒涘缓StringBuilder瀵硅薄
+ *
+ * @return {String}Builder瀵硅薄
+ */
+ public static StringBuilder builder() {
+ return new StringBuilder();
+ }
+
+ /**
+ * 鍒涘缓StringBuilder瀵硅薄
+ *
+ * @param capacity 鍒濆澶у皬
+ * @return {String}Builder瀵硅薄
+ */
+ public static StringBuilder builder(int capacity) {
+ return new StringBuilder(capacity);
+ }
+
+ /**
+ * 鍒涘缓StringBuilder瀵硅薄
+ *
+ * @param strs 鍒濆瀛楃涓插垪琛�
+ * @return {String}Builder瀵硅薄
+ */
+ public static StringBuilder builder(CharSequence... strs) {
+ final StringBuilder sb = new StringBuilder();
+ for (CharSequence str : strs) {
+ sb.append(str);
+ }
+ return sb;
+ }
+
+ /**
+ * 鍒涘缓StringBuilder瀵硅薄
+ *
+ * @param sb 鍒濆StringBuilder
+ * @param strs 鍒濆瀛楃涓插垪琛�
+ * @return {String}Builder瀵硅薄
+ */
+ public static StringBuilder appendBuilder(StringBuilder sb, CharSequence... strs) {
+ for (CharSequence str : strs) {
+ sb.append(str);
+ }
+ return sb;
+ }
+
+ /**
+ * 鑾峰緱StringReader
+ *
+ * @param str 瀛楃涓�
+ * @return {String}Reader
+ */
+ public static StringReader getReader(CharSequence str) {
+ if (null == str) {
+ return null;
+ }
+ return new StringReader(str.toString());
+ }
+
+ /**
+ * 鑾峰緱StringWriter
+ *
+ * @return {String}Writer
+ */
+ public static StringWriter getWriter() {
+ return new StringWriter();
+ }
+
+ /**
+ * 缁熻鎸囧畾鍐呭涓寘鍚寚瀹氬瓧绗︿覆鐨勬暟閲�<br>
+ * 鍙傛暟涓� {@code null} 鎴栬�� "" 杩斿洖 {@code 0}.
+ *
+ * <pre>
+ * StringUtil.count(null, *) = 0
+ * StringUtil.count("", *) = 0
+ * StringUtil.count("abba", null) = 0
+ * StringUtil.count("abba", "") = 0
+ * StringUtil.count("abba", "a") = 2
+ * StringUtil.count("abba", "ab") = 1
+ * StringUtil.count("abba", "xxx") = 0
+ * </pre>
+ *
+ * @param content 琚煡鎵剧殑瀛楃涓�
+ * @param strForSearch 闇�瑕佹煡鎵剧殑瀛楃涓�
+ * @return 鏌ユ壘鍒扮殑涓暟
+ */
+ public static int count(CharSequence content, CharSequence strForSearch) {
+ if (Func.hasEmpty(content, strForSearch) || strForSearch.length() > content.length()) {
+ return 0;
+ }
+
+ int count = 0;
+ int idx = 0;
+ final String content2 = content.toString();
+ final String strForSearch2 = strForSearch.toString();
+ while ((idx = content2.indexOf(strForSearch2, idx)) > -1) {
+ count++;
+ idx += strForSearch.length();
+ }
+ return count;
+ }
+
+ /**
+ * 缁熻鎸囧畾鍐呭涓寘鍚寚瀹氬瓧绗︾殑鏁伴噺
+ *
+ * @param content 鍐呭
+ * @param charForSearch 琚粺璁$殑瀛楃
+ * @return 鍖呭惈鏁伴噺
+ */
+ public static int count(CharSequence content, char charForSearch) {
+ int count = 0;
+ if (isEmpty(content)) {
+ return 0;
+ }
+ int contentLength = content.length();
+ for (int i = 0; i < contentLength; i++) {
+ if (charForSearch == content.charAt(i)) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * 涓嬪垝绾胯浆椹煎嘲
+ *
+ * @param para 瀛楃涓�
+ * @return {String}
+ */
+ public static String underlineToHump(String para) {
+ if (isBlank(para)) {
+ return StringPool.EMPTY;
+ }
+ StringBuilder result = new StringBuilder();
+ String[] a = para.split("_");
+ for (String s : a) {
+ if (result.length() == 0) {
+ result.append(s.toLowerCase());
+ } else {
+ result.append(s.substring(0, 1).toUpperCase());
+ result.append(s.substring(1).toLowerCase());
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * 椹煎嘲杞笅鍒掔嚎
+ *
+ * @param para 瀛楃涓�
+ * @return {String}
+ */
+ public static String humpToUnderline(String para) {
+ if (isBlank(para)) {
+ return StringPool.EMPTY;
+ }
+ para = firstCharToLower(para);
+ StringBuilder sb = new StringBuilder(para);
+ int temp = 0;
+ for (int i = 0; i < para.length(); i++) {
+ if (Character.isUpperCase(para.charAt(i))) {
+ sb.insert(i + temp, "_");
+ temp += 1;
+ }
+ }
+ return sb.toString().toLowerCase();
+ }
+
+ /**
+ * 妯嚎杞┘宄�
+ *
+ * @param para 瀛楃涓�
+ * @return {String}
+ */
+ public static String lineToHump(String para) {
+ if (isBlank(para)) {
+ return StringPool.EMPTY;
+ }
+ StringBuilder result = new StringBuilder();
+ String[] a = para.split("-");
+ for (String s : a) {
+ if (result.length() == 0) {
+ result.append(s.toLowerCase());
+ } else {
+ result.append(s.substring(0, 1).toUpperCase());
+ result.append(s.substring(1).toLowerCase());
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * 椹煎嘲杞í绾�
+ *
+ * @param para 瀛楃涓�
+ * @return {String}
+ */
+ public static String humpToLine(String para) {
+ if (isBlank(para)) {
+ return StringPool.EMPTY;
+ }
+ para = firstCharToLower(para);
+ StringBuilder sb = new StringBuilder(para);
+ int temp = 0;
+ for (int i = 0; i < para.length(); i++) {
+ if (Character.isUpperCase(para.charAt(i))) {
+ sb.insert(i + temp, "-");
+ temp += 1;
+ }
+ }
+ return sb.toString().toLowerCase();
+ }
+
+
+}
+
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/SuffixFileFilter.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/SuffixFileFilter.java
new file mode 100644
index 0000000..24c6c3b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/SuffixFileFilter.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.utils;
+
+import org.springframework.util.Assert;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.Serializable;
+
+/**
+ * 鏂囦欢鍚庣紑杩囨护鍣�
+ *
+ * @author L.cm
+ */
+public class SuffixFileFilter implements FileFilter, Serializable {
+
+ private static final long serialVersionUID = -3389157631240246157L;
+
+ private final String[] suffixes;
+
+ public SuffixFileFilter(final String suffix) {
+ Assert.notNull(suffix, "The suffix must not be null");
+ this.suffixes = new String[]{suffix};
+ }
+
+ public SuffixFileFilter(final String[] suffixes) {
+ Assert.notNull(suffixes, "The suffix must not be null");
+ this.suffixes = new String[suffixes.length];
+ System.arraycopy(suffixes, 0, this.suffixes, 0, suffixes.length);
+ }
+
+ @Override
+ public boolean accept(File pathname) {
+ final String name = pathname.getName();
+ for (final String suffix : this.suffixes) {
+ if (checkEndsWith(name, suffix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkEndsWith(final String str, final String end) {
+ final int endLen = end.length();
+ return str.regionMatches(true, str.length() - endLen, end, 0, endLen);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ThreadLocalUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ThreadLocalUtil.java
new file mode 100644
index 0000000..580b223
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ThreadLocalUtil.java
@@ -0,0 +1,136 @@
+/*
+ *
+ * Copyright 2019 http://www.hswebframework.org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.springblade.core.tool.utils;
+
+import org.springframework.lang.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+
+/**
+ * ThreadLocal 宸ュ叿绫�,閫氳繃鍦═hreadLocal瀛樺偍map淇℃伅,鏉ュ疄鐜板湪ThreadLocal涓淮鎶ゅ涓俊鎭�
+ * <br>e.g.<code>
+ * ThreadLocalUtils.put("key",value);<br>
+ * ThreadLocalUtils.get("key");<br>
+ * ThreadLocalUtils.remove("key");<br>
+ * ThreadLocalUtils.getAndRemove("key");<br>
+ * ThreadLocalUtils.get("key",()->defaultValue);<br>
+ * ThreadLocalUtils.clear();<br>
+ * </code>
+ *
+ * @author zhouhao
+ * @since 2.0
+ */
+@SuppressWarnings("unchecked")
+public class ThreadLocalUtil {
+ private static final ThreadLocal<Map<String, Object>> LOCAL = ThreadLocal.withInitial(HashMap::new);
+
+ /**
+ * @return threadLocal涓殑鍏ㄩ儴鍊�
+ */
+ public static Map<String, Object> getAll() {
+ return new HashMap<>(LOCAL.get());
+ }
+
+ /**
+ * 璁剧疆涓�涓�煎埌ThreadLocal
+ *
+ * @param key 閿�
+ * @param value 鍊�
+ * @param <T> 鍊肩殑绫诲瀷
+ * @return 琚斁鍏ョ殑鍊�
+ * @see Map#put(Object, Object)
+ */
+ public static <T> T put(String key, T value) {
+ LOCAL.get().put(key, value);
+ return value;
+ }
+
+ /**
+ * 璁剧疆涓�涓�煎埌ThreadLocal
+ *
+ * @param map map
+ * @return 琚斁鍏ョ殑鍊�
+ * @see Map#putAll(Map)
+ */
+ public static void put(Map<String, Object> map) {
+ LOCAL.get().putAll(map);
+ }
+
+ /**
+ * 鍒犻櫎鍙傛暟瀵瑰簲鐨勫��
+ *
+ * @param key
+ * @see Map#remove(Object)
+ */
+ public static void remove(String key) {
+ LOCAL.get().remove(key);
+ }
+
+ /**
+ * 娓呯┖ThreadLocal
+ *
+ * @see Map#clear()
+ */
+ public static void clear() {
+ LOCAL.remove();
+ }
+
+ /**
+ * 浠嶵hreadLocal涓幏鍙栧��
+ *
+ * @param key 閿�
+ * @param <T> 鍊兼硾鍨�
+ * @return 鍊�, 涓嶅瓨鍦ㄥ垯杩斿洖null, 濡傛灉绫诲瀷涓庢硾鍨嬩笉涓�鑷�, 鍙兘鎶涘嚭{@link ClassCastException}
+ * @see Map#get(Object)
+ * @see ClassCastException
+ */
+ @Nullable
+ public static <T> T get(String key) {
+ return ((T) LOCAL.get().get(key));
+ }
+
+ /**
+ * 浠嶵hreadLocal涓幏鍙栧��,骞舵寚瀹氫竴涓綋鍊间笉瀛樺湪鐨勬彁渚涜��
+ *
+ * @see Supplier
+ */
+ @Nullable
+ public static <T> T getIfAbsent(String key, Supplier<T> supplierOnNull) {
+ return ((T) LOCAL.get().computeIfAbsent(key, k -> supplierOnNull.get()));
+ }
+
+ /**
+ * 鑾峰彇涓�涓�煎悗鐒跺悗鍒犻櫎鎺�
+ *
+ * @param key 閿�
+ * @param <T> 鍊肩被鍨�
+ * @return 鍊�, 涓嶅瓨鍦ㄥ垯杩斿洖null
+ * @see this#get(String)
+ * @see this#remove(String)
+ */
+ public static <T> T getAndRemove(String key) {
+ try {
+ return get(key);
+ } finally {
+ remove(key);
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ThreadUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ThreadUtil.java
new file mode 100644
index 0000000..b62aa38
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ThreadUtil.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 澶氱嚎绋嬪伐鍏风被
+ *
+ * @author L.cm
+ */
+public class ThreadUtil {
+
+ /**
+ * Thread sleep
+ *
+ * @param millis 鏃堕暱
+ */
+ public static void sleep(long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ /**
+ * Thread sleep
+ *
+ * @param timeUnit TimeUnit
+ * @param timeout timeout
+ */
+ public static void sleep(TimeUnit timeUnit, long timeout) {
+ try {
+ timeUnit.sleep(timeout);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Unchecked.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Unchecked.java
new file mode 100644
index 0000000..c6da9c1
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Unchecked.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.utils;
+
+import org.springblade.core.tool.function.*;
+
+import java.util.Comparator;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * Lambda 鍙楁寮傚父澶勭悊
+ *
+ * <p>
+ * https://segmentfault.com/a/1190000007832130
+ * https://github.com/jOOQ/jOOL
+ * </p>
+ *
+ * @author L.cm
+ */
+public class Unchecked {
+
+ public static <T, R> Function<T, R> function(CheckedFunction<T, R> mapper) {
+ Objects.requireNonNull(mapper);
+ return t -> {
+ try {
+ return mapper.apply(t);
+ } catch (Throwable e) {
+ throw Exceptions.unchecked(e);
+ }
+ };
+ }
+
+ public static <T> Consumer<T> consumer(CheckedConsumer<T> mapper) {
+ Objects.requireNonNull(mapper);
+ return t -> {
+ try {
+ mapper.accept(t);
+ } catch (Throwable e) {
+ throw Exceptions.unchecked(e);
+ }
+ };
+ }
+
+ public static <T> Supplier<T> supplier(CheckedSupplier<T> mapper) {
+ Objects.requireNonNull(mapper);
+ return () -> {
+ try {
+ return mapper.get();
+ } catch (Throwable e) {
+ throw Exceptions.unchecked(e);
+ }
+ };
+ }
+
+ public static Runnable runnable(CheckedRunnable runnable) {
+ Objects.requireNonNull(runnable);
+ return () -> {
+ try {
+ runnable.run();
+ } catch (Throwable e) {
+ throw Exceptions.unchecked(e);
+ }
+ };
+ }
+
+ public static <T> Callable<T> callable(CheckedCallable<T> callable) {
+ Objects.requireNonNull(callable);
+ return () -> {
+ try {
+ return callable.call();
+ } catch (Throwable e) {
+ throw Exceptions.unchecked(e);
+ }
+ };
+ }
+
+ public static <T> Comparator<T> comparator(CheckedComparator<T> comparator) {
+ Objects.requireNonNull(comparator);
+ return (T o1, T o2) -> {
+ try {
+ return comparator.compare(o1, o2);
+ } catch (Throwable e) {
+ throw Exceptions.unchecked(e);
+ }
+ };
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/UrlUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/UrlUtil.java
new file mode 100644
index 0000000..b65311a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/UrlUtil.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.Charset;
+
+/**
+ * url澶勭悊宸ュ叿绫�
+ *
+ * @author L.cm
+ */
+public class UrlUtil extends org.springframework.web.util.UriUtils {
+
+ /**
+ * url 缂栫爜
+ *
+ * @param source source
+ * @return sourced String
+ */
+ public static String encode(String source) {
+ return UrlUtil.encode(source, Charsets.UTF_8);
+ }
+
+ /**
+ * url 瑙g爜
+ *
+ * @param source source
+ * @return decoded String
+ */
+ public static String decode(String source) {
+ return UrlUtil.decode(source, Charsets.UTF_8);
+ }
+
+ /**
+ * url 缂栫爜
+ *
+ * @param source url
+ * @param charset 瀛楃闆�
+ * @return 缂栫爜鍚庣殑url
+ */
+ @Deprecated
+ public static String encodeURL(String source, Charset charset) {
+ return UrlUtil.encode(source, charset.name());
+ }
+
+ /**
+ * url 瑙g爜
+ *
+ * @param source url
+ * @param charset 瀛楃闆�
+ * @return 瑙g爜url
+ */
+ @Deprecated
+ public static String decodeURL(String source, Charset charset) {
+ return UrlUtil.decode(source, charset.name());
+ }
+
+ /**
+ * 鑾峰彇url璺緞
+ *
+ * @param uriStr 璺緞
+ * @return url璺緞
+ */
+ public static String getPath(String uriStr) {
+ URI uri;
+
+ try {
+ uri = new URI(uriStr);
+ } catch (URISyntaxException var3) {
+ throw new RuntimeException(var3);
+ }
+
+ return uri.getPath();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Version.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Version.java
new file mode 100644
index 0000000..fc42bca
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Version.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.tool.utils;
+
+import org.springframework.lang.Nullable;
+
+/**
+ * 鐗堟湰鍙锋瘮杈冨伐鍏�
+ * <p>
+ * 鎬濊矾鏉ユ簮浜庯細 https://github.com/hotoo/versioning/blob/master/versioning.js
+ * <p>
+ * example
+ * * ##瀹屾暣妯″紡
+ * Version.of("v0.1.1").eq("v0.1.2"); // false
+ * <p>
+ * ##涓嶅畬鏁存ā寮�
+ * Version.of("v0.1").incomplete().eq("v0.1.2"); // true
+ *
+ * @author L.cm
+ * email: 596392912@qq.com
+ * site:http://www.dreamlu.net
+ * date 2015骞�7鏈�9鏃ヤ笅鍗�10:48:39
+ */
+public class Version {
+ private static final String DELIMITER = "\\.";
+
+ /**
+ * 鐗堟湰鍙�
+ */
+ @Nullable
+ private String version;
+ /**
+ * 鏄惁瀹屾暣妯″紡锛岄粯璁や娇鐢ㄥ畬鏁存ā寮�
+ */
+ private boolean complete = true;
+
+ /**
+ * 绉佹湁瀹炰緥鍖栨瀯閫犳柟娉�
+ */
+ private Version() {
+ }
+
+ private Version(@Nullable String version) {
+ this.version = version;
+ }
+
+ /**
+ * 涓嶅畬鏁存ā寮�
+ *
+ * @return {Version}
+ */
+ public Version incomplete() {
+ this.complete = false;
+ return this;
+ }
+
+ /**
+ * 鏋勯�犲櫒
+ *
+ * @param version 鐗堟湰
+ * @return {Version}
+ */
+ public static Version of(@Nullable String version) {
+ return new Version(version);
+ }
+
+ /**
+ * 姣旇緝鐗堟湰鍙锋槸鍚︾浉鍚�
+ * <p>
+ * example:
+ * * Version.of("v0.3").eq("v0.4")
+ *
+ * @param version 瀛楃涓茬増鏈彿
+ * @return {boolean}
+ */
+ public boolean eq(@Nullable String version) {
+ return compare(version) == 0;
+ }
+
+ /**
+ * 涓嶇浉鍚�
+ * <p>
+ * example:
+ * * Version.of("v0.3").ne("v0.4")
+ *
+ * @param version 瀛楃涓茬増鏈彿
+ * @return {boolean}
+ */
+ public boolean ne(@Nullable String version) {
+ return compare(version) != 0;
+ }
+
+ /**
+ * 澶т簬
+ *
+ * @param version 鐗堟湰鍙�
+ * @return 鏄惁澶т簬
+ */
+ public boolean gt(@Nullable String version) {
+ return compare(version) > 0;
+ }
+
+ /**
+ * 澶т簬鍜岀瓑浜�
+ *
+ * @param version 鐗堟湰鍙�
+ * @return 鏄惁澶т簬鍜岀瓑浜�
+ */
+ public boolean gte(@Nullable String version) {
+ return compare(version) >= 0;
+ }
+
+ /**
+ * 灏忎簬
+ *
+ * @param version 鐗堟湰鍙�
+ * @return 鏄惁灏忎簬
+ */
+ public boolean lt(@Nullable String version) {
+ return compare(version) < 0;
+ }
+
+ /**
+ * 灏忎簬鍜岀瓑浜�
+ *
+ * @param version 鐗堟湰鍙�
+ * @return 鏄惁灏忎簬鍜岀瓑浜�
+ */
+ public boolean lte(@Nullable String version) {
+ return compare(version) <= 0;
+ }
+
+ /**
+ * 鍜屽彟澶栦竴涓増鏈彿姣旇緝
+ *
+ * @param version 鐗堟湰鍙�
+ * @return {int}
+ */
+ private int compare(@Nullable String version) {
+ return Version.compare(this.version, version, complete);
+ }
+
+ /**
+ * 姣旇緝2涓増鏈彿
+ *
+ * @param v1 v1
+ * @param v2 v2
+ * @param complete 鏄惁瀹屾暣鐨勬瘮杈冧袱涓増鏈�
+ * @return (v1 < v2) ? -1 : ((v1 == v2) ? 0 : 1)
+ */
+ private static int compare(@Nullable String v1, @Nullable String v2, boolean complete) {
+ // v1 null瑙嗕负鏈�灏忕増鏈紝鎺掑湪鍓�
+ if (v1 == v2) {
+ return 0;
+ } else if (v1 == null) {
+ return -1;
+ } else if (v2 == null) {
+ return 1;
+ }
+ // 鍘婚櫎绌烘牸
+ v1 = v1.trim();
+ v2 = v2.trim();
+ if (v1.equals(v2)) {
+ return 0;
+ }
+ String[] v1s = v1.split(DELIMITER);
+ String[] v2s = v2.split(DELIMITER);
+ int v1sLen = v1s.length;
+ int v2sLen = v2s.length;
+ int len = complete
+ ? Math.max(v1sLen, v2sLen)
+ : Math.min(v1sLen, v2sLen);
+
+ for (int i = 0; i < len; i++) {
+ String c1 = len > v1sLen || null == v1s[i] ? "" : v1s[i];
+ String c2 = len > v2sLen || null == v2s[i] ? "" : v2s[i];
+
+ int result = c1.compareTo(c2);
+ if (result != 0) {
+ return result;
+ }
+ }
+
+ return 0;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/WebUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/WebUtil.java
new file mode 100644
index 0000000..6860848
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/WebUtil.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springframework.http.MediaType;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.method.HandlerMethod;
+
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.Objects;
+import java.util.function.Predicate;
+
+
+/**
+ * Miscellaneous utilities for web applications.
+ *
+ * @author L.cm
+ */
+@Slf4j
+public class WebUtil extends org.springframework.web.util.WebUtils {
+
+ public static final String USER_AGENT_HEADER = "user-agent";
+
+ /**
+ * 鍒ゆ柇鏄惁ajax璇锋眰
+ * spring ajax 杩斿洖鍚湁 ResponseBody 鎴栬�� RestController娉ㄨВ
+ *
+ * @param handlerMethod HandlerMethod
+ * @return 鏄惁ajax璇锋眰
+ */
+ public static boolean isBody(HandlerMethod handlerMethod) {
+ ResponseBody responseBody = ClassUtil.getAnnotation(handlerMethod, ResponseBody.class);
+ return responseBody != null;
+ }
+
+ /**
+ * 璇诲彇cookie
+ *
+ * @param name cookie name
+ * @return cookie value
+ */
+ @Nullable
+ public static String getCookieVal(String name) {
+ HttpServletRequest request = WebUtil.getRequest();
+ Assert.notNull(request, "request from RequestContextHolder is null");
+ return getCookieVal(request, name);
+ }
+
+ /**
+ * 璇诲彇cookie
+ *
+ * @param request HttpServletRequest
+ * @param name cookie name
+ * @return cookie value
+ */
+ @Nullable
+ public static String getCookieVal(HttpServletRequest request, String name) {
+ Cookie cookie = getCookie(request, name);
+ return cookie != null ? cookie.getValue() : null;
+ }
+
+ /**
+ * 娓呴櫎 鏌愪釜鎸囧畾鐨刢ookie
+ *
+ * @param response HttpServletResponse
+ * @param key cookie key
+ */
+ public static void removeCookie(HttpServletResponse response, String key) {
+ setCookie(response, key, null, 0);
+ }
+
+ /**
+ * 璁剧疆cookie
+ *
+ * @param response HttpServletResponse
+ * @param name cookie name
+ * @param value cookie value
+ * @param maxAgeInSeconds maxage
+ */
+ public static void setCookie(HttpServletResponse response, String name, @Nullable String value, int maxAgeInSeconds) {
+ Cookie cookie = new Cookie(name, value);
+ cookie.setPath(StringPool.SLASH);
+ cookie.setMaxAge(maxAgeInSeconds);
+ cookie.setHttpOnly(true);
+ response.addCookie(cookie);
+ }
+
+ /**
+ * 鑾峰彇 HttpServletRequest
+ *
+ * @return {HttpServletRequest}
+ */
+ public static HttpServletRequest getRequest() {
+ RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
+ return (requestAttributes == null) ? null : ((ServletRequestAttributes) requestAttributes).getRequest();
+ }
+
+ /**
+ * 杩斿洖json
+ *
+ * @param response HttpServletResponse
+ * @param result 缁撴灉瀵硅薄
+ */
+ public static void renderJson(HttpServletResponse response, Object result) {
+ renderJson(response, result, MediaType.APPLICATION_JSON_VALUE);
+ }
+
+ /**
+ * 杩斿洖json
+ *
+ * @param response HttpServletResponse
+ * @param result 缁撴灉瀵硅薄
+ * @param contentType contentType
+ */
+ public static void renderJson(HttpServletResponse response, Object result, String contentType) {
+ response.setCharacterEncoding("UTF-8");
+ response.setContentType(contentType);
+ try (PrintWriter out = response.getWriter()) {
+ out.append(JsonUtil.toJson(result));
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 鑾峰彇ip
+ *
+ * @return {String}
+ */
+ public static String getIP() {
+ return getIP(WebUtil.getRequest());
+ }
+
+ private static final String[] IP_HEADER_NAMES = new String[]{
+ "x-forwarded-for",
+ "Proxy-Client-IP",
+ "WL-Proxy-Client-IP",
+ "HTTP_CLIENT_IP",
+ "HTTP_X_FORWARDED_FOR"
+ };
+
+ private static final Predicate<String> IP_PREDICATE = (ip) -> StringUtil.isBlank(ip) || StringPool.UNKNOWN.equalsIgnoreCase(ip);
+
+ /**
+ * 鑾峰彇ip
+ *
+ * @param request HttpServletRequest
+ * @return {String}
+ */
+ @Nullable
+ public static String getIP(@Nullable HttpServletRequest request) {
+ if (request == null) {
+ return StringPool.EMPTY;
+ }
+ String ip = null;
+ for (String ipHeader : IP_HEADER_NAMES) {
+ ip = request.getHeader(ipHeader);
+ if (!IP_PREDICATE.test(ip)) {
+ break;
+ }
+ }
+ if (IP_PREDICATE.test(ip)) {
+ ip = request.getRemoteAddr();
+ }
+ return StringUtil.isBlank(ip) ? null : StringUtil.splitTrim(ip, StringPool.COMMA)[0];
+ }
+
+ /**
+ * 鑾峰彇璇锋眰澶寸殑鍊�
+ *
+ * @param name 璇锋眰澶村悕绉�
+ * @return 璇锋眰澶�
+ */
+ public static String getHeader(String name) {
+ HttpServletRequest request = getRequest();
+ return Objects.requireNonNull(request).getHeader(name);
+ }
+
+ /**
+ * 鑾峰彇璇锋眰澶寸殑鍊�
+ *
+ * @param name 璇锋眰澶村悕绉�
+ * @return 璇锋眰澶�
+ */
+ public static Enumeration<String> getHeaders(String name) {
+ HttpServletRequest request = getRequest();
+ return Objects.requireNonNull(request).getHeaders(name);
+ }
+
+ /**
+ * 鑾峰彇鎵�鏈夌殑璇锋眰澶�
+ *
+ * @return 璇锋眰澶撮泦鍚�
+ */
+ public static Enumeration<String> getHeaderNames() {
+ HttpServletRequest request = getRequest();
+ return Objects.requireNonNull(request).getHeaderNames();
+ }
+
+ /**
+ * 鑾峰彇璇锋眰鍙傛暟
+ *
+ * @param name 璇锋眰鍙傛暟鍚�
+ * @return 璇锋眰鍙傛暟
+ */
+ public static String getParameter(String name) {
+ HttpServletRequest request = getRequest();
+ return Objects.requireNonNull(request).getParameter(name);
+ }
+
+ /**
+ * 鑾峰彇 request 璇锋眰浣�
+ *
+ * @param servletInputStream servletInputStream
+ * @return body
+ */
+ public static String getRequestBody(ServletInputStream servletInputStream) {
+ StringBuilder sb = new StringBuilder();
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(servletInputStream, StandardCharsets.UTF_8));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (servletInputStream != null) {
+ try {
+ servletInputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 鑾峰彇 request 璇锋眰鍐呭
+ *
+ * @param request request
+ * @return {String}
+ */
+ public static String getRequestContent(HttpServletRequest request) {
+ try {
+ String queryString = request.getQueryString();
+ if (StringUtil.isNotBlank(queryString)) {
+ return new String(queryString.getBytes(Charsets.ISO_8859_1), Charsets.UTF_8).replaceAll("&", "&").replaceAll("%22", "\"");
+ }
+ String charEncoding = request.getCharacterEncoding();
+ if (charEncoding == null) {
+ charEncoding = StringPool.UTF_8;
+ }
+ byte[] buffer = getRequestBody(request.getInputStream()).getBytes();
+ String str = new String(buffer, charEncoding).trim();
+ if (StringUtil.isBlank(str)) {
+ StringBuilder sb = new StringBuilder();
+ Enumeration<String> parameterNames = request.getParameterNames();
+ while (parameterNames.hasMoreElements()) {
+ String key = parameterNames.nextElement();
+ String value = request.getParameter(key);
+ StringUtil.appendBuilder(sb, key, "=", value, "&");
+ }
+ str = StringUtil.removeSuffix(sb.toString(), "&");
+ }
+ return str.replaceAll("&", "&");
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ return StringPool.EMPTY;
+ }
+ }
+
+
+}
+
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/XmlUtil.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/XmlUtil.java
new file mode 100644
index 0000000..cc624bf
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/utils/XmlUtil.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.utils;
+
+import org.springframework.lang.Nullable;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * xpath瑙f瀽xml
+ *
+ * <pre>
+ * 鏂囨。鍦板潃锛�
+ * http://www.w3school.com.cn/xpath/index.asp
+ * </pre>
+ *
+ * @author L.cm
+ */
+public class XmlUtil {
+ private final XPath path;
+ private final Document doc;
+
+ private XmlUtil(InputSource inputSource) throws ParserConfigurationException, SAXException, IOException {
+ DocumentBuilderFactory dbf = getDocumentBuilderFactory();
+ DocumentBuilder db = dbf.newDocumentBuilder();
+ doc = db.parse(inputSource);
+ path = getXPathFactory().newXPath();
+ }
+
+ /**
+ * 鍒涘缓宸ュ叿绫�
+ *
+ * @param inputSource inputSource
+ * @return XmlUtil
+ */
+ private static XmlUtil create(InputSource inputSource) {
+ try {
+ return new XmlUtil(inputSource);
+ } catch (ParserConfigurationException | SAXException | IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 杞崲宸ュ叿绫�
+ *
+ * @param inputStream inputStream
+ * @return XmlUtil
+ */
+ public static XmlUtil of(InputStream inputStream) {
+ InputSource inputSource = new InputSource(inputStream);
+ return create(inputSource);
+ }
+
+ /**
+ * 杞崲宸ュ叿绫�
+ *
+ * @param xmlStr xmlStr
+ * @return XmlUtil
+ */
+ public static XmlUtil of(String xmlStr) {
+ StringReader sr = new StringReader(xmlStr.trim());
+ InputSource inputSource = new InputSource(sr);
+ XmlUtil xmlUtil = create(inputSource);
+ IoUtil.closeQuietly(sr);
+ return xmlUtil;
+ }
+
+ /**
+ * 杞崲璺緞
+ *
+ * @param expression 琛ㄨ揪寮�
+ * @param item 瀹炰綋
+ * @param returnType 杩斿洖绫诲瀷
+ * @return Object
+ */
+ private Object evalXPath(String expression, @Nullable Object item, QName returnType) {
+ item = null == item ? doc : item;
+ try {
+ return path.evaluate(expression, item, returnType);
+ } catch (XPathExpressionException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 鑾峰彇String
+ *
+ * @param expression 璺緞
+ * @return {String}
+ */
+ public String getString(String expression) {
+ return (String) evalXPath(expression, null, XPathConstants.STRING);
+ }
+
+ /**
+ * 鑾峰彇Boolean
+ *
+ * @param expression 璺緞
+ * @return {String}
+ */
+ public Boolean getBoolean(String expression) {
+ return (Boolean) evalXPath(expression, null, XPathConstants.BOOLEAN);
+ }
+
+ /**
+ * 鑾峰彇Number
+ *
+ * @param expression 璺緞
+ * @return {Number}
+ */
+ public Number getNumber(String expression) {
+ return (Number) evalXPath(expression, null, XPathConstants.NUMBER);
+ }
+
+ /**
+ * 鑾峰彇鏌愪釜鑺傜偣
+ *
+ * @param expression 璺緞
+ * @return {Node}
+ */
+ public Node getNode(String expression) {
+ return (Node) evalXPath(expression, null, XPathConstants.NODE);
+ }
+
+ /**
+ * 鑾峰彇瀛愯妭鐐�
+ *
+ * @param expression 璺緞
+ * @return NodeList
+ */
+ public NodeList getNodeList(String expression) {
+ return (NodeList) evalXPath(expression, null, XPathConstants.NODESET);
+ }
+
+
+ /**
+ * 鑾峰彇String
+ *
+ * @param node 鑺傜偣
+ * @param expression 鐩稿浜巒ode鐨勮矾寰�
+ * @return {String}
+ */
+ public String getString(Object node, String expression) {
+ return (String) evalXPath(expression, node, XPathConstants.STRING);
+ }
+
+ /**
+ * 鑾峰彇
+ *
+ * @param node 鑺傜偣
+ * @param expression 鐩稿浜巒ode鐨勮矾寰�
+ * @return {String}
+ */
+ public Boolean getBoolean(Object node, String expression) {
+ return (Boolean) evalXPath(expression, node, XPathConstants.BOOLEAN);
+ }
+
+ /**
+ * 鑾峰彇
+ *
+ * @param node 鑺傜偣
+ * @param expression 鐩稿浜巒ode鐨勮矾寰�
+ * @return {Number}
+ */
+ public Number getNumber(Object node, String expression) {
+ return (Number) evalXPath(expression, node, XPathConstants.NUMBER);
+ }
+
+ /**
+ * 鑾峰彇鏌愪釜鑺傜偣
+ *
+ * @param node 鑺傜偣
+ * @param expression 璺緞
+ * @return {Node}
+ */
+ public Node getNode(Object node, String expression) {
+ return (Node) evalXPath(expression, node, XPathConstants.NODE);
+ }
+
+ /**
+ * 鑾峰彇瀛愯妭鐐�
+ *
+ * @param node 鑺傜偣
+ * @param expression 鐩稿浜巒ode鐨勮矾寰�
+ * @return NodeList
+ */
+ public NodeList getNodeList(Object node, String expression) {
+ return (NodeList) evalXPath(expression, node, XPathConstants.NODESET);
+ }
+
+ /**
+ * 閽堝娌℃湁宓屽鑺傜偣鐨勭畝鍗曞鐞�
+ *
+ * @return map闆嗗悎
+ */
+ public Map<String, String> toMap() {
+ Element root = doc.getDocumentElement();
+ Map<String, String> params = new HashMap<>(16);
+
+ // 灏嗚妭鐐瑰皝瑁呮垚map褰㈠紡
+ NodeList list = root.getChildNodes();
+ for (int i = 0; i < list.getLength(); i++) {
+ Node node = list.item(i);
+ if (node instanceof Element) {
+ params.put(node.getNodeName(), node.getTextContent());
+ }
+ }
+ return params;
+ }
+
+ private static volatile boolean preventedXXE = false;
+
+ private static DocumentBuilderFactory getDocumentBuilderFactory() throws ParserConfigurationException {
+ DocumentBuilderFactory dbf = XmlUtil.XmlHelperHolder.documentBuilderFactory;
+ if (!preventedXXE) {
+ preventXXE(dbf);
+ }
+ return dbf;
+ }
+
+ /**
+ * preventXXE
+ *
+ * @param dbf
+ * @throws ParserConfigurationException
+ */
+ private static void preventXXE(DocumentBuilderFactory dbf) throws ParserConfigurationException {
+ // This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity attacks are prevented
+ // Xerces 2 only - http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl
+ dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+
+ // If you can't completely disable DTDs, then at least do the following:
+ // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
+ // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities
+
+ // JDK7+ - http://xml.org/sax/features/external-general-entities
+ dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
+
+ // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities
+ // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities
+
+ // JDK7+ - http://xml.org/sax/features/external-parameter-entities
+ dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+
+ // Disable external DTDs as well
+ dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+
+ // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
+ dbf.setXIncludeAware(false);
+ dbf.setExpandEntityReferences(false);
+ preventedXXE = true;
+ }
+
+ private static XPathFactory getXPathFactory() {
+ return XmlUtil.XmlHelperHolder.xPathFactory;
+ }
+
+ /**
+ * 鍐呴儴绫诲崟渚�
+ */
+ private static class XmlHelperHolder {
+ private static DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+ private static XPathFactory xPathFactory = XPathFactory.newInstance();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/yml/YmlPropertyLoaderFactory.java b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/yml/YmlPropertyLoaderFactory.java
new file mode 100644
index 0000000..fcd94a5
--- /dev/null
+++ b/Source/BladeX-Tool/blade-core-tool/src/main/java/org/springblade/core/tool/yml/YmlPropertyLoaderFactory.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.tool.yml;
+
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.boot.env.OriginTrackedMapPropertySource;
+import org.springframework.boot.env.YamlPropertySourceLoader;
+import org.springframework.core.env.MapPropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.DefaultPropertySourceFactory;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.lang.Nullable;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+/**
+ * yml閰嶇疆鍔犺浇
+ *
+ * @author lcm
+ */
+public class YmlPropertyLoaderFactory extends DefaultPropertySourceFactory {
+
+ @Override
+ public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource encodedResource) throws IOException {
+ if (encodedResource == null) {
+ return emptyPropertySource(name);
+ }
+ Resource resource = encodedResource.getResource();
+ String fileName = resource.getFilename();
+ List<PropertySource<?>> sources = new YamlPropertySourceLoader().load(fileName, resource);
+ if (sources.isEmpty()) {
+ return emptyPropertySource(fileName);
+ }
+ // yml 鏁版嵁瀛樺偍锛屽悎鎴愪竴涓� PropertySource
+ Map<String, Object> ymlDataMap = new HashMap<>(32);
+ for (PropertySource<?> source : sources) {
+ ymlDataMap.putAll(((MapPropertySource) source).getSource());
+ }
+ return new OriginTrackedMapPropertySource(getSourceName(fileName, name), ymlDataMap);
+ }
+
+ private static PropertySource<?> emptyPropertySource(@Nullable String name) {
+ return new MapPropertySource(getSourceName(name), Collections.emptyMap());
+ }
+
+ private static String getSourceName(String... names) {
+ return Stream.of(names)
+ .filter(StringUtil::isNotBlank)
+ .findFirst()
+ .orElse("BladeYmlPropertySource");
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-actuate/README.md b/Source/BladeX-Tool/blade-starter-actuate/README.md
new file mode 100644
index 0000000..57d34d5
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-actuate/README.md
@@ -0,0 +1,34 @@
+## 鎯虫硶
+鏆撮湶涓�浜涚鐐癸紝鎻愪緵涓�浜涘姛鑳姐��
+
+1. http-cache
+
+2. RateLimiter
+
+3. ... ...
+
+### 涓嶆槸鐢ㄧ綉鍏筹紝鍗曚綋搴旂敤
+鎷︽埅鍣ㄥ鐞嗭紝鍩轰簬 redis 鐨� cache 鏃堕棿鎴栬�� RateLimiter澶勭悊銆�
+
+缁撴瀯锛歴erviceName:http-cache:/user/1?queryString If-Modified-Since
+缁撴瀯锛歴erviceName:RateLimiter:/user/1 99
+
+### 浣跨敤缃戝叧
+灏嗙鐐逛俊鎭瓨鍌ㄥ埌 redis 閲岋紝渚� 缃戝叧浣跨敤銆�
+缁撴瀯锛歴erviceName:http-cache:endpoint:/user/{id} 100s
+
+缁撴瀯锛歴erviceName:RateLimiter:endpoint:/user/{id} 100/s
+
+## RateLimiter Headers
+```text
+#=============================#===================================================#
+# HTTP Header # Description #
+#=============================#===================================================#
+| X-RateLimit-Limit | Request limit per day / per 5 minutes |
++-----------------------------+---------------------------------------------------+
+| X-RateLimit-Remaining | The number of requests left for the time window |
++-----------------------------+---------------------------------------------------+
+| X-RateLimit-Reset | The remaining window before the rate limit resets |
+| | in UTC epoch seconds |
++-----------------------------+---------------------------------------------------+
+```
\ No newline at end of file
diff --git a/Source/BladeX-Tool/blade-starter-actuate/pom.xml b/Source/BladeX-Tool/blade-starter-actuate/pom.xml
new file mode 100644
index 0000000..86e9aee
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-actuate/pom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-actuate</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-tool</artifactId>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/BladeHttpCacheProperties.java b/Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/BladeHttpCacheProperties.java
new file mode 100644
index 0000000..1c57049
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/BladeHttpCacheProperties.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.http.cache;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Http Cache 閰嶇疆
+ *
+ * @author L.cm
+ */
+@ConfigurationProperties("blade.http.cache")
+public class BladeHttpCacheProperties {
+ /**
+ * Http-cache 鐨� spring cache鍚嶏紝榛樿锛歜ladeHttpCache
+ */
+ @Getter
+ @Setter
+ private String cacheName = "bladeHttpCache";
+ /**
+ * 榛樿鎷︽埅/**
+ */
+ @Getter
+ private final List<String> includePatterns = new ArrayList<String>() {{
+ add("/**");
+ }};
+ /**
+ * 榛樿鎺掗櫎闈欐�佹枃浠剁洰褰�
+ */
+ @Getter
+ private final List<String> excludePatterns = new ArrayList<>();
+}
diff --git a/Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/HttpCacheAble.java b/Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/HttpCacheAble.java
new file mode 100644
index 0000000..2ebee89
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/HttpCacheAble.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.http.cache;
+
+import org.springframework.core.annotation.AliasFor;
+
+import java.lang.annotation.*;
+
+/**
+ * Http cache
+ * cache-control
+ * <p>
+ * max-age 澶т簬0 鏃� 鐩存帴浠庢父瑙堝櫒缂撳瓨涓� 鎻愬彇
+ * max-age 灏忎簬鎴栫瓑浜�0 鏃� 鍚憇erver 鍙戦�乭ttp 璇锋眰纭 ,璇ヨ祫婧愭槸鍚︽湁淇敼
+ *
+ * @author L.cm
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface HttpCacheAble {
+
+ /**
+ * 缂撳瓨鐨勬椂闂�,榛樿0,鍗曚綅绉�
+ *
+ * @return {long}
+ */
+ @AliasFor("maxAge")
+ long value();
+
+ /**
+ * 缂撳瓨鐨勬椂闂�,榛樿0,鍗曚綅绉�
+ *
+ * @return {long}
+ */
+ @AliasFor("value")
+ long maxAge() default 0;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/HttpCacheConfiguration.java b/Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/HttpCacheConfiguration.java
new file mode 100644
index 0000000..cd48de8
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/HttpCacheConfiguration.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.http.cache;
+
+import lombok.AllArgsConstructor;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cache.CacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.lang.NonNull;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Http Cache 閰嶇疆
+ *
+ * @author L.cm
+ */
+@AutoConfiguration
+@AllArgsConstructor
+@EnableConfigurationProperties(BladeHttpCacheProperties.class)
+@ConditionalOnProperty(value = "blade.http.cache.enabled", havingValue = "true")
+public class HttpCacheConfiguration implements WebMvcConfigurer {
+ private static final String DEFAULT_STATIC_PATH_PATTERN = "/**";
+ private final WebMvcProperties webMvcProperties;
+ private final BladeHttpCacheProperties properties;
+ private final CacheManager cacheManager;
+
+ @Bean
+ public HttpCacheService httpCacheService() {
+ return new HttpCacheService(properties, cacheManager);
+ }
+
+ @Override
+ public void addInterceptors(@NonNull InterceptorRegistry registry) {
+ Set<String> excludePatterns = new HashSet<>(properties.getExcludePatterns());
+ String staticPathPattern = webMvcProperties.getStaticPathPattern();
+ // 濡傛灉闈欐�� 鐩綍 涓嶄负 /**
+ if (!DEFAULT_STATIC_PATH_PATTERN.equals(staticPathPattern.trim())) {
+ excludePatterns.add(staticPathPattern);
+ }
+ HttpCacheInterceptor httpCacheInterceptor = new HttpCacheInterceptor(httpCacheService());
+ registry.addInterceptor(httpCacheInterceptor)
+ .addPathPatterns(properties.getIncludePatterns().toArray(new String[0]))
+ .excludePathPatterns(excludePatterns.toArray(new String[0]));
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/HttpCacheInterceptor.java b/Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/HttpCacheInterceptor.java
new file mode 100644
index 0000000..3531529
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/HttpCacheInterceptor.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.http.cache;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.tool.utils.ClassUtil;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.lang.NonNull;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.time.Clock;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Http cache鎷︽埅鍣�
+ *
+ * @author L.cm
+ */
+@Slf4j
+@AllArgsConstructor
+public class HttpCacheInterceptor extends HandlerInterceptorAdapter {
+ private final HttpCacheService httpCacheService;
+
+ @Override
+ public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) throws Exception {
+ // 闈炴帶鍒跺櫒璇锋眰鐩存帴璺冲嚭
+ if (!(handler instanceof HandlerMethod)) {
+ return true;
+ }
+ // http cache 閽堝 HEAD 鍜� GET 璇锋眰
+ String method = request.getMethod();
+ HttpMethod httpMethod = HttpMethod.resolve(method);
+ if (httpMethod == null) {
+ return true;
+ }
+ List<HttpMethod> allowList = Arrays.asList(HttpMethod.HEAD, HttpMethod.GET);
+ if (!allowList.contains(httpMethod)) {
+ return true;
+ }
+ // 澶勭悊HttpCacheAble
+ HandlerMethod handlerMethod = (HandlerMethod) handler;
+ HttpCacheAble cacheAble = ClassUtil.getAnnotation(handlerMethod, HttpCacheAble.class);
+ if (cacheAble == null) {
+ return true;
+ }
+
+ // 鏈�鍚庝慨鏀规椂闂�
+ long ims = request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
+ long now = Clock.systemUTC().millis();
+ // 缂撳瓨鏃堕棿,绉�
+ long maxAge = cacheAble.maxAge();
+ // 缂撳瓨鏃堕棿,姣
+ long maxAgeMicros = TimeUnit.SECONDS.toMillis(maxAge);
+ String cacheKey = request.getRequestURI() + "?" + request.getQueryString();
+ // 鍚庣鍙帶鍒秇ttp-cache瓒呮椂
+ boolean hasCache = httpCacheService.get(cacheKey);
+ // 濡傛灉header澶存病鏈夎繃鏈�
+ if (hasCache && ims + maxAgeMicros > now) {
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ response.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=" + maxAge);
+ response.addDateHeader(HttpHeaders.EXPIRES, ims + maxAgeMicros);
+ response.addDateHeader(HttpHeaders.LAST_MODIFIED, ims);
+ log.info("{} 304 {}", method, request.getRequestURI());
+ return false;
+ }
+ response.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=" + maxAge);
+ response.addDateHeader(HttpHeaders.EXPIRES, now + maxAgeMicros);
+ response.addDateHeader(HttpHeaders.LAST_MODIFIED, now);
+ httpCacheService.set(cacheKey);
+ return super.preHandle(request, response, handler);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/HttpCacheService.java b/Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/HttpCacheService.java
new file mode 100644
index 0000000..999bfd5
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-actuate/src/main/java/org/springblade/core/http/cache/HttpCacheService.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.http.cache;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.util.Assert;
+
+/**
+ * Http Cache 鏈嶅姟
+ *
+ * @author L.cm
+ */
+public class HttpCacheService implements InitializingBean {
+ private final BladeHttpCacheProperties properties;
+ private final CacheManager cacheManager;
+ private Cache cache;
+
+ public HttpCacheService(BladeHttpCacheProperties properties, CacheManager cacheManager) {
+ this.properties = properties;
+ this.cacheManager = cacheManager;
+ }
+
+ public boolean get(String key) {
+ Boolean result = cache.get(key, Boolean.class);
+ return Boolean.TRUE.equals(result);
+ }
+
+ public void set(String key) {
+ cache.put(key, Boolean.TRUE);
+ }
+
+ public void remove(String key) {
+ cache.evict(key);
+ }
+
+ public void clear() {
+ cache.clear();
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ Assert.notNull(cacheManager, "cacheManager must not be null!");
+ String cacheName = properties.getCacheName();
+ this.cache = cacheManager.getCache(cacheName);
+ Assert.notNull(this.cache, "HttpCacheCache cacheName: " + cacheName + " is not config.");
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/README.md b/Source/BladeX-Tool/blade-starter-api-crypto/README.md
new file mode 100644
index 0000000..1d0eac3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/README.md
@@ -0,0 +1,4 @@
+## 鍙傝��
+encrypt-body-spring-boot-starter: https://github.com/Licoy/encrypt-body-spring-boot-starter
+
+mica锛歨ttps://github.com/lets-mica/mica
\ No newline at end of file
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/pom.xml b/Source/BladeX-Tool/blade-starter-api-crypto/pom.xml
new file mode 100644
index 0000000..b3cf006
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/pom.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-api-crypto</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-tool</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-context</artifactId>
+ </dependency>
+ </dependencies>
+
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/crypto/ApiCrypto.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/crypto/ApiCrypto.java
new file mode 100644
index 0000000..3ad6617
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/crypto/ApiCrypto.java
@@ -0,0 +1,22 @@
+package org.springblade.core.api.crypto.annotation.crypto;
+
+import org.springblade.core.api.crypto.annotation.decrypt.ApiDecrypt;
+import org.springblade.core.api.crypto.annotation.encrypt.ApiEncrypt;
+import org.springblade.core.api.crypto.enums.CryptoType;
+
+import java.lang.annotation.*;
+
+/**
+ * <p>AES鍔犲瘑瑙e瘑鍚湁{@link org.springframework.web.bind.annotation.RequestBody}娉ㄨВ鐨勫弬鏁拌姹傛暟鎹�</p>
+ *
+ * @author Chill
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@ApiEncrypt(CryptoType.AES)
+@ApiDecrypt(CryptoType.AES)
+public @interface ApiCrypto {
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/crypto/ApiCryptoAes.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/crypto/ApiCryptoAes.java
new file mode 100644
index 0000000..c7aa98f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/crypto/ApiCryptoAes.java
@@ -0,0 +1,22 @@
+package org.springblade.core.api.crypto.annotation.crypto;
+
+import org.springblade.core.api.crypto.annotation.decrypt.ApiDecrypt;
+import org.springblade.core.api.crypto.annotation.encrypt.ApiEncrypt;
+import org.springblade.core.api.crypto.enums.CryptoType;
+
+import java.lang.annotation.*;
+
+/**
+ * <p>AES鍔犲瘑瑙e瘑鍚湁{@link org.springframework.web.bind.annotation.RequestBody}娉ㄨВ鐨勫弬鏁拌姹傛暟鎹�</p>
+ *
+ * @author Chill
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@ApiEncrypt(CryptoType.AES)
+@ApiDecrypt(CryptoType.AES)
+public @interface ApiCryptoAes {
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/crypto/ApiCryptoDes.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/crypto/ApiCryptoDes.java
new file mode 100644
index 0000000..6ea127a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/crypto/ApiCryptoDes.java
@@ -0,0 +1,22 @@
+package org.springblade.core.api.crypto.annotation.crypto;
+
+import org.springblade.core.api.crypto.annotation.decrypt.ApiDecrypt;
+import org.springblade.core.api.crypto.annotation.encrypt.ApiEncrypt;
+import org.springblade.core.api.crypto.enums.CryptoType;
+
+import java.lang.annotation.*;
+
+/**
+ * <p>DES鍔犲瘑瑙e瘑鍚湁{@link org.springframework.web.bind.annotation.RequestBody}娉ㄨВ鐨勫弬鏁拌姹傛暟鎹�</p>
+ *
+ * @author Chill
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@ApiEncrypt(CryptoType.DES)
+@ApiDecrypt(CryptoType.DES)
+public @interface ApiCryptoDes {
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/crypto/ApiCryptoRsa.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/crypto/ApiCryptoRsa.java
new file mode 100644
index 0000000..6f4ba55
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/crypto/ApiCryptoRsa.java
@@ -0,0 +1,22 @@
+package org.springblade.core.api.crypto.annotation.crypto;
+
+import org.springblade.core.api.crypto.annotation.decrypt.ApiDecrypt;
+import org.springblade.core.api.crypto.annotation.encrypt.ApiEncrypt;
+import org.springblade.core.api.crypto.enums.CryptoType;
+
+import java.lang.annotation.*;
+
+/**
+ * <p>RSA鍔犲瘑瑙e瘑鍚湁{@link org.springframework.web.bind.annotation.RequestBody}娉ㄨВ鐨勫弬鏁拌姹傛暟鎹�</p>
+ *
+ * @author Chill
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@ApiEncrypt(CryptoType.RSA)
+@ApiDecrypt(CryptoType.RSA)
+public @interface ApiCryptoRsa {
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecrypt.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecrypt.java
new file mode 100644
index 0000000..6defbd0
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecrypt.java
@@ -0,0 +1,32 @@
+package org.springblade.core.api.crypto.annotation.decrypt;
+
+import org.springblade.core.api.crypto.enums.CryptoType;
+
+import java.lang.annotation.*;
+
+/**
+ * <p>瑙e瘑鍚湁{@link org.springframework.web.bind.annotation.RequestBody}娉ㄨВ鐨勫弬鏁拌姹傛暟鎹紝鍙敤浜庢暣涓帶鍒剁被鎴栬�呮煇涓帶鍒跺櫒涓�</p>
+ *
+ * @author licoy.cn, L.cm
+ */
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface ApiDecrypt {
+
+ /**
+ * 瑙e瘑绫诲瀷
+ *
+ * @return 绫诲瀷
+ */
+ CryptoType value();
+
+ /**
+ * 绉侀挜锛岀敤浜庢煇浜涢渶瑕佸崟鐙厤缃閽ョ殑鏂规硶锛屾病鏈夋椂璇诲彇鍏ㄥ眬閰嶇疆鐨勭閽�
+ *
+ * @return 绉侀挜
+ */
+ String secretKey() default "";
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecryptAes.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecryptAes.java
new file mode 100644
index 0000000..7dfcbb4
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecryptAes.java
@@ -0,0 +1,28 @@
+package org.springblade.core.api.crypto.annotation.decrypt;
+
+import org.springblade.core.api.crypto.enums.CryptoType;
+import org.springframework.core.annotation.AliasFor;
+
+import java.lang.annotation.*;
+
+/**
+ * aes 瑙e瘑
+ *
+ * @author licoy.cn, L.cm
+ * @see ApiDecrypt
+ */
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@ApiDecrypt(CryptoType.AES)
+public @interface ApiDecryptAes {
+
+ /**
+ * Alias for {@link ApiDecrypt#secretKey()}.
+ *
+ * @return {String}
+ */
+ @AliasFor(annotation = ApiDecrypt.class)
+ String secretKey() default "";
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecryptDes.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecryptDes.java
new file mode 100644
index 0000000..d2efc7b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecryptDes.java
@@ -0,0 +1,28 @@
+package org.springblade.core.api.crypto.annotation.decrypt;
+
+import org.springblade.core.api.crypto.enums.CryptoType;
+import org.springframework.core.annotation.AliasFor;
+
+import java.lang.annotation.*;
+
+/**
+ * des 瑙e瘑
+ *
+ * @author licoy.cn
+ * @see ApiDecrypt
+ */
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@ApiDecrypt(CryptoType.DES)
+public @interface ApiDecryptDes {
+
+ /**
+ * Alias for {@link ApiDecrypt#secretKey()}.
+ *
+ * @return {String}
+ */
+ @AliasFor(annotation = ApiDecrypt.class)
+ String secretKey() default "";
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecryptRsa.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecryptRsa.java
new file mode 100644
index 0000000..73a486d
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecryptRsa.java
@@ -0,0 +1,18 @@
+package org.springblade.core.api.crypto.annotation.decrypt;
+
+import org.springblade.core.api.crypto.enums.CryptoType;
+
+import java.lang.annotation.*;
+
+/**
+ * rsa 瑙e瘑
+ *
+ * @author licoy.cn
+ * @see ApiDecrypt
+ */
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@ApiDecrypt(CryptoType.RSA)
+public @interface ApiDecryptRsa {
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncrypt.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncrypt.java
new file mode 100644
index 0000000..fb50bd3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncrypt.java
@@ -0,0 +1,33 @@
+package org.springblade.core.api.crypto.annotation.encrypt;
+
+
+import org.springblade.core.api.crypto.enums.CryptoType;
+
+import java.lang.annotation.*;
+
+/**
+ * <p>鍔犲瘑{@link org.springframework.web.bind.annotation.ResponseBody}鍝嶅簲鏁版嵁锛屽彲鐢ㄤ簬鏁翠釜鎺у埗绫绘垨鑰呮煇涓帶鍒跺櫒涓�</p>
+ *
+ * @author licoy.cn, L.cm
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface ApiEncrypt {
+
+ /**
+ * 鍔犲瘑绫诲瀷
+ *
+ * @return 绫诲瀷
+ */
+ CryptoType value();
+
+ /**
+ * 绉侀挜锛岀敤浜庢煇浜涢渶瑕佸崟鐙厤缃閽ョ殑鏂规硶锛屾病鏈夋椂璇诲彇鍏ㄥ眬閰嶇疆鐨勭閽�
+ *
+ * @return 绉侀挜
+ */
+ String secretKey() default "";
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncryptAes.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncryptAes.java
new file mode 100644
index 0000000..2b45790
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncryptAes.java
@@ -0,0 +1,28 @@
+package org.springblade.core.api.crypto.annotation.encrypt;
+
+import org.springblade.core.api.crypto.enums.CryptoType;
+import org.springframework.core.annotation.AliasFor;
+
+import java.lang.annotation.*;
+
+/**
+ * aes 鍔犲瘑
+ *
+ * @author licoy.cn, L.cm
+ * @see ApiEncrypt
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@ApiEncrypt(CryptoType.AES)
+public @interface ApiEncryptAes {
+
+ /**
+ * Alias for {@link ApiEncrypt#secretKey()}.
+ *
+ * @return {String}
+ */
+ @AliasFor(annotation = ApiEncrypt.class)
+ String secretKey() default "";
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncryptDes.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncryptDes.java
new file mode 100644
index 0000000..036de8a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncryptDes.java
@@ -0,0 +1,28 @@
+package org.springblade.core.api.crypto.annotation.encrypt;
+
+import org.springblade.core.api.crypto.enums.CryptoType;
+import org.springframework.core.annotation.AliasFor;
+
+import java.lang.annotation.*;
+
+/**
+ * des 鍔犲瘑
+ *
+ * @author licoy.cn, L.cm
+ * @see ApiEncrypt
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@ApiEncrypt(CryptoType.DES)
+public @interface ApiEncryptDes {
+
+ /**
+ * Alias for {@link ApiEncrypt#secretKey()}.
+ *
+ * @return {String}
+ */
+ @AliasFor(annotation = ApiEncrypt.class)
+ String secretKey() default "";
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncryptRsa.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncryptRsa.java
new file mode 100644
index 0000000..e36d9d8
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncryptRsa.java
@@ -0,0 +1,18 @@
+package org.springblade.core.api.crypto.annotation.encrypt;
+
+import org.springblade.core.api.crypto.enums.CryptoType;
+
+import java.lang.annotation.*;
+
+/**
+ * rsa 鍔犲瘑
+ *
+ * @author licoy.cn, L.cm
+ * @see ApiEncrypt
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@ApiEncrypt(CryptoType.RSA)
+public @interface ApiEncryptRsa {
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/bean/CryptoInfoBean.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/bean/CryptoInfoBean.java
new file mode 100644
index 0000000..e1e6d3f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/bean/CryptoInfoBean.java
@@ -0,0 +1,25 @@
+package org.springblade.core.api.crypto.bean;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.springblade.core.api.crypto.enums.CryptoType;
+
+/**
+ * <p>鍔犲瘑娉ㄨВ淇℃伅</p>
+ *
+ * @author licoy.cn, L.cm
+ */
+@Getter
+@RequiredArgsConstructor
+public class CryptoInfoBean {
+
+ /**
+ * 鍔犲瘑绫诲瀷
+ */
+ private final CryptoType type;
+ /**
+ * 绉侀挜
+ */
+ private final String secretKey;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/bean/DecryptHttpInputMessage.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/bean/DecryptHttpInputMessage.java
new file mode 100644
index 0000000..8c23fb3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/bean/DecryptHttpInputMessage.java
@@ -0,0 +1,20 @@
+package org.springblade.core.api.crypto.bean;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpInputMessage;
+
+import java.io.InputStream;
+
+/**
+ * <p>瑙e瘑淇℃伅杈撳叆娴�</p>
+ *
+ * @author licoy.cn, L.cm
+ */
+@Getter
+@RequiredArgsConstructor
+public class DecryptHttpInputMessage implements HttpInputMessage {
+ private final InputStream body;
+ private final HttpHeaders headers;
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/config/ApiCryptoConfiguration.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/config/ApiCryptoConfiguration.java
new file mode 100644
index 0000000..c683f44
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/config/ApiCryptoConfiguration.java
@@ -0,0 +1,30 @@
+package org.springblade.core.api.crypto.config;
+
+import lombok.RequiredArgsConstructor;
+import org.springblade.core.api.crypto.core.ApiDecryptParamResolver;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.util.List;
+
+/**
+ * api 绛惧悕鑷姩閰嶇疆
+ *
+ * @author L.cm
+ */
+@AutoConfiguration
+@RequiredArgsConstructor
+@EnableConfigurationProperties(ApiCryptoProperties.class)
+@ConditionalOnProperty(value = ApiCryptoProperties.PREFIX + ".enabled", havingValue = "true", matchIfMissing = true)
+public class ApiCryptoConfiguration implements WebMvcConfigurer {
+ private final ApiCryptoProperties apiCryptoProperties;
+
+ @Override
+ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
+ argumentResolvers.add(new ApiDecryptParamResolver(apiCryptoProperties));
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/config/ApiCryptoProperties.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/config/ApiCryptoProperties.java
new file mode 100644
index 0000000..e548359
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/config/ApiCryptoProperties.java
@@ -0,0 +1,46 @@
+package org.springblade.core.api.crypto.config;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * api 绛惧悕閰嶇疆绫�
+ *
+ * @author licoy.cn, L.cm
+ */
+@Getter
+@Setter
+@ConfigurationProperties(ApiCryptoProperties.PREFIX)
+public class ApiCryptoProperties {
+ /**
+ * 鍓嶇紑
+ */
+ public static final String PREFIX = "blade.api.crypto";
+
+ /**
+ * 鏄惁寮�鍚� api 绛惧悕
+ */
+ private Boolean enabled = Boolean.TRUE;
+
+ /**
+ * url鐨勫弬鏁扮鍚嶏紝浼犻�掔殑鍙傛暟鍚嶃�備緥濡傦細/user?data=绛惧悕鍚庣殑鏁版嵁
+ */
+ private String paramName = "data";
+
+ /**
+ * aes 瀵嗛挜
+ */
+ private String aesKey;
+
+ /**
+ * des 瀵嗛挜
+ */
+ private String desKey;
+
+ /**
+ * rsa 绉侀挜
+ */
+ private String rsaPrivateKey;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiDecryptParamResolver.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiDecryptParamResolver.java
new file mode 100644
index 0000000..b208380
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiDecryptParamResolver.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.api.crypto.core;
+
+import lombok.RequiredArgsConstructor;
+import org.springblade.core.api.crypto.annotation.decrypt.ApiDecrypt;
+import org.springblade.core.api.crypto.bean.CryptoInfoBean;
+import org.springblade.core.api.crypto.config.ApiCryptoProperties;
+import org.springblade.core.api.crypto.util.ApiCryptoUtil;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.utils.Charsets;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.lang.Nullable;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.ModelAndViewContainer;
+
+import java.lang.reflect.Parameter;
+
+/**
+ * param 鍙傛暟 瑙f瀽
+ *
+ * @author L.cm
+ */
+@RequiredArgsConstructor
+public class ApiDecryptParamResolver implements HandlerMethodArgumentResolver {
+ private final ApiCryptoProperties properties;
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return AnnotatedElementUtils.hasAnnotation(parameter.getParameter(), ApiDecrypt.class);
+ }
+
+ @Nullable
+ @Override
+ public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer,
+ NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
+ Parameter parameter = methodParameter.getParameter();
+ ApiDecrypt apiDecrypt = AnnotatedElementUtils.getMergedAnnotation(parameter, ApiDecrypt.class);
+ String text = webRequest.getParameter(properties.getParamName());
+ if (StringUtil.isBlank(text)) {
+ return null;
+ }
+ CryptoInfoBean infoBean = new CryptoInfoBean(apiDecrypt.value(), apiDecrypt.secretKey());
+ byte[] textBytes = text.getBytes(Charsets.UTF_8);
+ byte[] decryptData = ApiCryptoUtil.decryptData(properties, textBytes, infoBean);
+ return JsonUtil.readValue(decryptData, parameter.getType());
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiDecryptRequestBodyAdvice.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiDecryptRequestBodyAdvice.java
new file mode 100644
index 0000000..28075c0
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiDecryptRequestBodyAdvice.java
@@ -0,0 +1,87 @@
+package org.springblade.core.api.crypto.core;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.api.crypto.annotation.decrypt.ApiDecrypt;
+import org.springblade.core.api.crypto.bean.CryptoInfoBean;
+import org.springblade.core.api.crypto.bean.DecryptHttpInputMessage;
+import org.springblade.core.api.crypto.config.ApiCryptoProperties;
+import org.springblade.core.api.crypto.exception.DecryptBodyFailException;
+import org.springblade.core.api.crypto.util.ApiCryptoUtil;
+import org.springblade.core.tool.utils.ClassUtil;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpInputMessage;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.lang.NonNull;
+import org.springframework.util.StreamUtils;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Type;
+
+/**
+ * 璇锋眰鏁版嵁鐨勫姞瀵嗕俊鎭В瀵嗗鐞�<br>
+ * 鏈被鍙鎺у埗鍣ㄥ弬鏁颁腑鍚湁<strong>{@link org.springframework.web.bind.annotation.RequestBody}</strong>
+ * 浠ュ強package涓�<strong><code>org.springblade.core.api.signature.annotation.decrypt</code></strong>涓嬬殑娉ㄨВ鏈夋晥
+ *
+ * @author licoy.cn, L.cm
+ * @see RequestBodyAdvice
+ */
+@Slf4j
+@Order(1)
+@AutoConfiguration
+@ControllerAdvice
+@RequiredArgsConstructor
+@ConditionalOnProperty(value = ApiCryptoProperties.PREFIX + ".enabled", havingValue = "true", matchIfMissing = true)
+public class ApiDecryptRequestBodyAdvice implements RequestBodyAdvice {
+ private final ApiCryptoProperties properties;
+
+ @Override
+ public boolean supports(MethodParameter methodParameter, @NonNull Type targetType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
+ return ClassUtil.isAnnotated(methodParameter.getMethod(), ApiDecrypt.class);
+ }
+
+ @Override
+ public Object handleEmptyBody(Object body, @NonNull HttpInputMessage inputMessage, @NonNull MethodParameter parameter,
+ @NonNull Type targetType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
+ return body;
+ }
+
+ @NonNull
+ @Override
+ public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, @NonNull MethodParameter parameter,
+ @NonNull Type targetType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
+ // 鍒ゆ柇 body 鏄惁涓虹┖
+ InputStream messageBody = inputMessage.getBody();
+ if (messageBody.available() <= 0) {
+ return inputMessage;
+ }
+ byte[] decryptedBody = null;
+ CryptoInfoBean cryptoInfoBean = ApiCryptoUtil.getDecryptInfo(parameter);
+ if (cryptoInfoBean != null) {
+ // base64 byte array
+ byte[] bodyByteArray = StreamUtils.copyToByteArray(messageBody);
+ decryptedBody = ApiCryptoUtil.decryptData(properties, bodyByteArray, cryptoInfoBean);
+ }
+ if (decryptedBody == null) {
+ throw new DecryptBodyFailException("Decryption error, " +
+ "please check if the selected source data is encrypted correctly." +
+ " (瑙e瘑閿欒锛岃妫�鏌ラ�夋嫨鐨勬簮鏁版嵁鐨勫姞瀵嗘柟寮忔槸鍚︽纭��)");
+ }
+ InputStream inputStream = new ByteArrayInputStream(decryptedBody);
+ return new DecryptHttpInputMessage(inputStream, inputMessage.getHeaders());
+ }
+
+ @NonNull
+ @Override
+ public Object afterBodyRead(@NonNull Object body, @NonNull HttpInputMessage inputMessage, @NonNull MethodParameter parameter, @NonNull Type targetType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
+ return body;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiEncryptResponseBodyAdvice.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiEncryptResponseBodyAdvice.java
new file mode 100644
index 0000000..a491d54
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiEncryptResponseBodyAdvice.java
@@ -0,0 +1,64 @@
+package org.springblade.core.api.crypto.core;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.api.crypto.annotation.encrypt.ApiEncrypt;
+import org.springblade.core.api.crypto.bean.CryptoInfoBean;
+import org.springblade.core.api.crypto.config.ApiCryptoProperties;
+import org.springblade.core.api.crypto.exception.EncryptBodyFailException;
+import org.springblade.core.api.crypto.util.ApiCryptoUtil;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.utils.ClassUtil;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
+
+
+/**
+ * 鍝嶅簲鏁版嵁鐨勫姞瀵嗗鐞�<br>
+ * 鏈被鍙鎺у埗鍣ㄥ弬鏁颁腑鍚湁<strong>{@link org.springframework.web.bind.annotation.ResponseBody}</strong>
+ * 鎴栬�呮帶鍒剁被涓婂惈鏈�<strong>{@link org.springframework.web.bind.annotation.RestController}</strong>
+ * 浠ュ強package涓�<strong><code>org.springblade.core.api.signature.annotation.encrypt</code></strong>涓嬬殑娉ㄨВ鏈夋晥
+ *
+ * @author licoy.cn, L.cm
+ * @see ResponseBodyAdvice
+ */
+@Slf4j
+@Order(1)
+@AutoConfiguration
+@ControllerAdvice
+@RequiredArgsConstructor
+@ConditionalOnProperty(value = ApiCryptoProperties.PREFIX + ".enabled", havingValue = "true", matchIfMissing = true)
+public class ApiEncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
+ private final ApiCryptoProperties properties;
+
+ @Override
+ public boolean supports(MethodParameter returnType, @NonNull Class converterType) {
+ return ClassUtil.isAnnotated(returnType.getMethod(), ApiEncrypt.class);
+ }
+
+ @Nullable
+ @Override
+ public Object beforeBodyWrite(Object body, @NonNull MethodParameter returnType, @NonNull MediaType selectedContentType,
+ @NonNull Class selectedConverterType, @NonNull ServerHttpRequest request, @NonNull ServerHttpResponse response) {
+ if (body == null) {
+ return null;
+ }
+ response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
+ CryptoInfoBean cryptoInfoBean = ApiCryptoUtil.getEncryptInfo(returnType);
+ if (cryptoInfoBean != null) {
+ byte[] bodyJsonBytes = JsonUtil.toJsonAsBytes(body);
+ return ApiCryptoUtil.encryptData(properties, bodyJsonBytes, cryptoInfoBean);
+ }
+ throw new EncryptBodyFailException();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/enums/CryptoType.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/enums/CryptoType.java
new file mode 100644
index 0000000..1fe017d
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/enums/CryptoType.java
@@ -0,0 +1,25 @@
+package org.springblade.core.api.crypto.enums;
+
+/**
+ * <p>鍔犲瘑鏂瑰紡</p>
+ *
+ * @author licoy.cn, L.cm
+ */
+public enum CryptoType {
+
+ /**
+ * des
+ */
+ DES,
+
+ /**
+ * aes
+ */
+ AES,
+
+ /**
+ * rsa
+ */
+ RSA
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/exception/DecryptBodyFailException.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/exception/DecryptBodyFailException.java
new file mode 100644
index 0000000..a103dbf
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/exception/DecryptBodyFailException.java
@@ -0,0 +1,13 @@
+package org.springblade.core.api.crypto.exception;
+
+/**
+ * <p>瑙e瘑鏁版嵁澶辫触寮傚父</p>
+ *
+ * @author licoy.cn
+ */
+public class DecryptBodyFailException extends RuntimeException {
+
+ public DecryptBodyFailException(String message) {
+ super(message);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/exception/EncryptBodyFailException.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/exception/EncryptBodyFailException.java
new file mode 100644
index 0000000..eb3bf70
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/exception/EncryptBodyFailException.java
@@ -0,0 +1,17 @@
+package org.springblade.core.api.crypto.exception;
+
+/**
+ * <p>鍔犲瘑鏁版嵁澶辫触寮傚父</p>
+ *
+ * @author licoy.cn
+ */
+public class EncryptBodyFailException extends RuntimeException {
+
+ public EncryptBodyFailException() {
+ super("Encrypted data failed. (鍔犲瘑鏁版嵁澶辫触)");
+ }
+
+ public EncryptBodyFailException(String message) {
+ super(message);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/exception/EncryptMethodNotFoundException.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/exception/EncryptMethodNotFoundException.java
new file mode 100644
index 0000000..f148fe8
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/exception/EncryptMethodNotFoundException.java
@@ -0,0 +1,14 @@
+package org.springblade.core.api.crypto.exception;
+
+/**
+ * <p>鍔犲瘑鏂瑰紡鏈壘鍒版垨鏈畾涔夊紓甯�</p>
+ *
+ * @author licoy.cn
+ */
+public class EncryptMethodNotFoundException extends RuntimeException {
+
+ public EncryptMethodNotFoundException() {
+ super("Encryption method is not defined. (鍔犲瘑鏂瑰紡鏈畾涔�)");
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/exception/KeyNotConfiguredException.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/exception/KeyNotConfiguredException.java
new file mode 100644
index 0000000..4fc05f5
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/exception/KeyNotConfiguredException.java
@@ -0,0 +1,14 @@
+package org.springblade.core.api.crypto.exception;
+
+
+/**
+ * <p>鏈厤缃甂EY杩愯鏃跺紓甯�</p>
+ *
+ * @author licoy.cn, L.cm
+ */
+public class KeyNotConfiguredException extends RuntimeException {
+
+ public KeyNotConfiguredException(String message) {
+ super(message);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/util/ApiCryptoUtil.java b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/util/ApiCryptoUtil.java
new file mode 100644
index 0000000..fa66b67
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-api-crypto/src/main/java/org/springblade/core/api/crypto/util/ApiCryptoUtil.java
@@ -0,0 +1,124 @@
+package org.springblade.core.api.crypto.util;
+
+import org.springblade.core.api.crypto.annotation.decrypt.ApiDecrypt;
+import org.springblade.core.api.crypto.annotation.encrypt.ApiEncrypt;
+import org.springblade.core.api.crypto.bean.CryptoInfoBean;
+import org.springblade.core.api.crypto.config.ApiCryptoProperties;
+import org.springblade.core.api.crypto.enums.CryptoType;
+import org.springblade.core.api.crypto.exception.EncryptBodyFailException;
+import org.springblade.core.api.crypto.exception.EncryptMethodNotFoundException;
+import org.springblade.core.api.crypto.exception.KeyNotConfiguredException;
+import org.springblade.core.tool.utils.*;
+import org.springframework.core.MethodParameter;
+import org.springframework.lang.Nullable;
+
+import java.util.Objects;
+
+/**
+ * <p>杈呭姪妫�娴嬪伐鍏风被</p>
+ *
+ * @author licoy.cn, L.cm
+ */
+public class ApiCryptoUtil {
+
+ /**
+ * 鑾峰彇鏂规硶鎺у埗鍣ㄤ笂鐨勫姞瀵嗘敞瑙d俊鎭�
+ *
+ * @param methodParameter 鎺у埗鍣ㄦ柟娉�
+ * @return 鍔犲瘑娉ㄨВ淇℃伅
+ */
+ @Nullable
+ public static CryptoInfoBean getEncryptInfo(MethodParameter methodParameter) {
+ ApiEncrypt encryptBody = ClassUtil.getAnnotation(methodParameter.getMethod(), ApiEncrypt.class);
+ if (encryptBody == null) {
+ return null;
+ }
+ return new CryptoInfoBean(encryptBody.value(), encryptBody.secretKey());
+ }
+
+ /**
+ * 鑾峰彇鏂规硶鎺у埗鍣ㄤ笂鐨勮В瀵嗘敞瑙d俊鎭�
+ *
+ * @param methodParameter 鎺у埗鍣ㄦ柟娉�
+ * @return 鍔犲瘑娉ㄨВ淇℃伅
+ */
+ @Nullable
+ public static CryptoInfoBean getDecryptInfo(MethodParameter methodParameter) {
+ ApiDecrypt decryptBody = ClassUtil.getAnnotation(methodParameter.getMethod(), ApiDecrypt.class);
+ if (decryptBody == null) {
+ return null;
+ }
+ return new CryptoInfoBean(decryptBody.value(), decryptBody.secretKey());
+ }
+
+ /**
+ * 閫夋嫨鍔犲瘑鏂瑰紡骞惰繘琛屽姞瀵�
+ *
+ * @param jsonData json 鏁版嵁
+ * @param infoBean 鍔犲瘑淇℃伅
+ * @return 鍔犲瘑缁撴灉
+ */
+ public static String encryptData(ApiCryptoProperties properties, byte[] jsonData, CryptoInfoBean infoBean) {
+ CryptoType type = infoBean.getType();
+ if (type == null) {
+ throw new EncryptMethodNotFoundException();
+ }
+ String secretKey = infoBean.getSecretKey();
+ if (type == CryptoType.DES) {
+ secretKey = ApiCryptoUtil.checkSecretKey(properties.getDesKey(), secretKey, "DES");
+ return DesUtil.encryptToBase64(jsonData, secretKey);
+ }
+ if (type == CryptoType.AES) {
+ secretKey = ApiCryptoUtil.checkSecretKey(properties.getAesKey(), secretKey, "AES");
+ return AesUtil.encryptToBase64(jsonData, secretKey);
+ }
+ if (type == CryptoType.RSA) {
+ String privateKey = Objects.requireNonNull(properties.getRsaPrivateKey());
+ return RsaUtil.encryptByPrivateKeyToBase64(privateKey, jsonData);
+ }
+ throw new EncryptBodyFailException();
+ }
+
+ /**
+ * 閫夋嫨鍔犲瘑鏂瑰紡骞惰繘琛岃В瀵�
+ *
+ * @param bodyData byte array
+ * @param infoBean 鍔犲瘑淇℃伅
+ * @return 瑙e瘑缁撴灉
+ */
+ public static byte[] decryptData(ApiCryptoProperties properties, byte[] bodyData, CryptoInfoBean infoBean) {
+ CryptoType type = infoBean.getType();
+ if (type == null) {
+ throw new EncryptMethodNotFoundException();
+ }
+ String secretKey = infoBean.getSecretKey();
+ if (type == CryptoType.AES) {
+ secretKey = ApiCryptoUtil.checkSecretKey(properties.getAesKey(), secretKey, "AES");
+ return AesUtil.decryptFormBase64(bodyData, secretKey);
+ }
+ if (type == CryptoType.DES) {
+ secretKey = ApiCryptoUtil.checkSecretKey(properties.getDesKey(), secretKey, "DES");
+ return DesUtil.decryptFormBase64(bodyData, secretKey);
+ }
+ if (type == CryptoType.RSA) {
+ String privateKey = Objects.requireNonNull(properties.getRsaPrivateKey());
+ return RsaUtil.decryptFromBase64(privateKey, bodyData);
+ }
+ throw new EncryptMethodNotFoundException();
+ }
+
+ /**
+ * 妫�楠岀閽�
+ *
+ * @param k1 閰嶇疆鐨勭閽�
+ * @param k2 娉ㄨВ涓婄殑绉侀挜
+ * @param keyName key鍚嶇О
+ * @return 绉侀挜
+ */
+ private static String checkSecretKey(String k1, String k2, String keyName) {
+ if (StringUtil.isBlank(k1) && StringUtil.isBlank(k2)) {
+ throw new KeyNotConfiguredException(String.format("%s key is not configured (鏈厤缃�%s)", keyName, keyName));
+ }
+ return StringUtil.isBlank(k2) ? k1 : k2;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-auth/pom.xml b/Source/BladeX-Tool/blade-starter-auth/pom.xml
new file mode 100644
index 0000000..bb1611b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-auth/pom.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-auth</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!--Blade-->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-tool</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-jwt</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/AuthInfo.java b/Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/AuthInfo.java
new file mode 100644
index 0000000..945bcca
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/AuthInfo.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * AuthInfo
+ *
+ * @author Chill
+ */
+@Data
+@ApiModel(description = "璁よ瘉淇℃伅")
+public class AuthInfo {
+ @ApiModelProperty(value = "浠ょ墝")
+ private String accessToken;
+ @ApiModelProperty(value = "浠ょ墝绫诲瀷")
+ private String tokenType;
+ @ApiModelProperty(value = "澶村儚")
+ private String avatar = "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png";
+ @ApiModelProperty(value = "瑙掕壊鍚�")
+ private String authority;
+ @ApiModelProperty(value = "鐢ㄦ埛鍚�")
+ private String userName;
+ @ApiModelProperty(value = "璐﹀彿鍚�")
+ private String account;
+ @ApiModelProperty(value = "杩囨湡鏃堕棿")
+ private long expiresIn;
+ @ApiModelProperty(value = "璁稿彲璇�")
+ private String license = "made by blade";
+}
diff --git a/Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/BladeUser.java b/Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/BladeUser.java
new file mode 100644
index 0000000..d2451db
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/BladeUser.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springblade.core.tool.support.Kv;
+
+import java.io.Serializable;
+
+/**
+ * 鐢ㄦ埛瀹炰綋
+ *
+ * @author Chill
+ */
+@Data
+public class BladeUser implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ /**
+ * 瀹㈡埛绔痠d
+ */
+ @ApiModelProperty(hidden = true)
+ private String clientId;
+
+ /**
+ * 鐢ㄦ埛id
+ */
+ @ApiModelProperty(hidden = true)
+ private Long userId;
+ /**
+ * 璐﹀彿
+ */
+ @ApiModelProperty(hidden = true)
+ private String account;
+ /**
+ * 鐢ㄦ埛鍚�
+ */
+ @ApiModelProperty(hidden = true)
+ private String userName;
+ /**
+ * 鏄电О
+ */
+ @ApiModelProperty(hidden = true)
+ private String nickName;
+ /**
+ * 绉熸埛ID
+ */
+ @ApiModelProperty(hidden = true)
+ private String tenantId;
+ /**
+ * 绗笁鏂硅璇両D
+ */
+ @ApiModelProperty(hidden = true)
+ private String oauthId;
+ /**
+ * 閮ㄩ棬id
+ */
+ @ApiModelProperty(hidden = true)
+ private String deptId;
+ /**
+ * 宀椾綅id
+ */
+ @ApiModelProperty(hidden = true)
+ private String postId;
+ /**
+ * 瑙掕壊id
+ */
+ @ApiModelProperty(hidden = true)
+ private String roleId;
+ /**
+ * 瑙掕壊鍚�
+ */
+ @ApiModelProperty(hidden = true)
+ private String roleName;
+ /**
+ * 鐢ㄦ埛璇︽儏
+ */
+ @ApiModelProperty(hidden = true)
+ private Kv detail;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/TokenInfo.java b/Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/TokenInfo.java
new file mode 100644
index 0000000..a9dba2e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/TokenInfo.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure;
+
+import lombok.Data;
+
+/**
+ * TokenInfo
+ *
+ * @author Chill
+ */
+@Data
+public class TokenInfo {
+
+ /**
+ * 浠ょ墝鍊�
+ */
+ private String token;
+
+ /**
+ * 杩囨湡绉掓暟
+ */
+ private int expire;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/exception/SecureException.java b/Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/exception/SecureException.java
new file mode 100644
index 0000000..cc034c6
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/exception/SecureException.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.exception;
+
+import lombok.Getter;
+import org.springblade.core.tool.api.IResultCode;
+import org.springblade.core.tool.api.ResultCode;
+
+/**
+ * Secure寮傚父
+ *
+ * @author Chill
+ */
+public class SecureException extends RuntimeException {
+ private static final long serialVersionUID = 2359767895161832954L;
+
+ @Getter
+ private final IResultCode resultCode;
+
+ public SecureException(String message) {
+ super(message);
+ this.resultCode = ResultCode.UN_AUTHORIZED;
+ }
+
+ public SecureException(IResultCode resultCode) {
+ super(resultCode.getMessage());
+ this.resultCode = resultCode;
+ }
+
+ public SecureException(IResultCode resultCode, Throwable cause) {
+ super(cause);
+ this.resultCode = resultCode;
+ }
+
+ @Override
+ public Throwable fillInStackTrace() {
+ return this;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/utils/AuthUtil.java b/Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/utils/AuthUtil.java
new file mode 100644
index 0000000..58d9a9b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-auth/src/main/java/org/springblade/core/secure/utils/AuthUtil.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.secure.utils;
+
+import io.jsonwebtoken.Claims;
+import org.springblade.core.jwt.JwtUtil;
+import org.springblade.core.jwt.props.JwtProperties;
+import org.springblade.core.launch.constant.TokenConstant;
+import org.springblade.core.secure.BladeUser;
+import org.springblade.core.tool.constant.RoleConstant;
+import org.springblade.core.tool.support.Kv;
+import org.springblade.core.tool.utils.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Auth宸ュ叿绫�
+ *
+ * @author Chill
+ */
+public class AuthUtil {
+ private static final String BLADE_USER_REQUEST_ATTR = "_BLADE_USER_REQUEST_ATTR_";
+
+ private final static String HEADER = TokenConstant.HEADER;
+ private final static String ACCOUNT = TokenConstant.ACCOUNT;
+ private final static String USER_NAME = TokenConstant.USER_NAME;
+ private final static String NICK_NAME = TokenConstant.NICK_NAME;
+ private final static String USER_ID = TokenConstant.USER_ID;
+ private final static String DEPT_ID = TokenConstant.DEPT_ID;
+ private final static String POST_ID = TokenConstant.POST_ID;
+ private final static String ROLE_ID = TokenConstant.ROLE_ID;
+ private final static String ROLE_NAME = TokenConstant.ROLE_NAME;
+ private final static String TENANT_ID = TokenConstant.TENANT_ID;
+ private final static String OAUTH_ID = TokenConstant.OAUTH_ID;
+ private final static String CLIENT_ID = TokenConstant.CLIENT_ID;
+ private final static String DETAIL = TokenConstant.DETAIL;
+
+ private static JwtProperties jwtProperties;
+
+ /**
+ * 鑾峰彇閰嶇疆绫�
+ *
+ * @return jwtProperties
+ */
+ private static JwtProperties getJwtProperties() {
+ if (jwtProperties == null) {
+ jwtProperties = SpringUtil.getBean(JwtProperties.class);
+ }
+ return jwtProperties;
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛淇℃伅
+ *
+ * @return BladeUser
+ */
+ public static BladeUser getUser() {
+ HttpServletRequest request = WebUtil.getRequest();
+ if (request == null) {
+ return null;
+ }
+ // 浼樺厛浠� request 涓幏鍙�
+ Object bladeUser = request.getAttribute(BLADE_USER_REQUEST_ATTR);
+ if (bladeUser == null) {
+ bladeUser = getUser(request);
+ if (bladeUser != null) {
+ // 璁剧疆鍒� request 涓�
+ request.setAttribute(BLADE_USER_REQUEST_ATTR, bladeUser);
+ }
+ }
+ return (BladeUser) bladeUser;
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛淇℃伅
+ *
+ * @param request request
+ * @return BladeUser
+ */
+ @SuppressWarnings("unchecked")
+ public static BladeUser getUser(HttpServletRequest request) {
+ Claims claims = getClaims(request);
+ if (claims == null) {
+ return null;
+ }
+ String clientId = Func.toStr(claims.get(AuthUtil.CLIENT_ID));
+ Long userId = Func.toLong(claims.get(AuthUtil.USER_ID));
+ String tenantId = Func.toStr(claims.get(AuthUtil.TENANT_ID));
+ String oauthId = Func.toStr(claims.get(AuthUtil.OAUTH_ID));
+ String deptId = Func.toStrWithEmpty(claims.get(AuthUtil.DEPT_ID), StringPool.MINUS_ONE);
+ String postId = Func.toStrWithEmpty(claims.get(AuthUtil.POST_ID), StringPool.MINUS_ONE);
+ String roleId = Func.toStrWithEmpty(claims.get(AuthUtil.ROLE_ID), StringPool.MINUS_ONE);
+ String account = Func.toStr(claims.get(AuthUtil.ACCOUNT));
+ String roleName = Func.toStr(claims.get(AuthUtil.ROLE_NAME));
+ String userName = Func.toStr(claims.get(AuthUtil.USER_NAME));
+ String nickName = Func.toStr(claims.get(AuthUtil.NICK_NAME));
+ String tenantName = Func.toStr(claims.get("tenantName"));
+ String email = Func.toStr(claims.get("email"));
+ String deptName = Func.toStr(claims.get("deptName"));
+ String secretGrade = Func.toStr(claims.get("secretGrade"));
+ Kv detail = Kv.create().setAll((Map<? extends String, ?>) claims.get(AuthUtil.DETAIL));
+ BladeUser bladeUser = new BladeUser();
+ bladeUser.setClientId(clientId);
+ bladeUser.setUserId(userId);
+ bladeUser.setTenantId(tenantId);
+ bladeUser.setOauthId(oauthId);
+ bladeUser.setAccount(account);
+ bladeUser.setDeptId(deptId);
+ bladeUser.setPostId(postId);
+ bladeUser.setRoleId(roleId);
+ bladeUser.setRoleName(roleName);
+ bladeUser.setUserName(userName);
+ bladeUser.setNickName(nickName);
+ detail.put("tenantName",tenantName);
+ detail.put("deptName",deptName);
+ detail.put("email",email);
+ detail.put("secretGrade",secretGrade);
+ bladeUser.setDetail(detail);
+ return bladeUser;
+ }
+
+ /**
+ * 鏄惁涓鸿秴绠�
+ *
+ * @return boolean
+ */
+ public static boolean isAdministrator() {
+ return StringUtil.containsAny(getUserRole(), RoleConstant.ADMINISTRATOR);
+ }
+
+ /**
+ * 鏄惁涓虹鐞嗗憳
+ *
+ * @return boolean
+ */
+ public static boolean isAdmin() {
+ return StringUtil.containsAny(getUserRole(), RoleConstant.ADMIN);
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛id
+ *
+ * @return userId
+ */
+ public static Long getUserId() {
+ BladeUser user = getUser();
+ return (null == user) ? -1 : user.getUserId();
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛id
+ *
+ * @param request request
+ * @return userId
+ */
+ public static Long getUserId(HttpServletRequest request) {
+ BladeUser user = getUser(request);
+ return (null == user) ? -1 : user.getUserId();
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛璐﹀彿
+ *
+ * @return userAccount
+ */
+ public static String getUserAccount() {
+ BladeUser user = getUser();
+ return (null == user) ? StringPool.EMPTY : user.getAccount();
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛璐﹀彿
+ *
+ * @param request request
+ * @return userAccount
+ */
+ public static String getUserAccount(HttpServletRequest request) {
+ BladeUser user = getUser(request);
+ return (null == user) ? StringPool.EMPTY : user.getAccount();
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛鍚�
+ *
+ * @return userName
+ */
+ public static String getUserName() {
+ BladeUser user = getUser();
+ return (null == user) ? StringPool.EMPTY : user.getUserName();
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛鍚�
+ *
+ * @param request request
+ * @return userName
+ */
+ public static String getUserName(HttpServletRequest request) {
+ BladeUser user = getUser(request);
+ return (null == user) ? StringPool.EMPTY : user.getUserName();
+ }
+
+ /**
+ * 鑾峰彇鏄电О
+ *
+ * @return userName
+ */
+ public static String getNickName() {
+ BladeUser user = getUser();
+ return (null == user) ? StringPool.EMPTY : user.getNickName();
+ }
+
+ /**
+ * 鑾峰彇鏄电О
+ *
+ * @param request request
+ * @return userName
+ */
+ public static String getNickName(HttpServletRequest request) {
+ BladeUser user = getUser(request);
+ return (null == user) ? StringPool.EMPTY : user.getNickName();
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛閮ㄩ棬
+ *
+ * @return userName
+ */
+ public static String getDeptId() {
+ BladeUser user = getUser();
+ return (null == user) ? StringPool.EMPTY : user.getDeptId();
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛閮ㄩ棬
+ *
+ * @param request request
+ * @return userName
+ */
+ public static String getDeptId(HttpServletRequest request) {
+ BladeUser user = getUser(request);
+ return (null == user) ? StringPool.EMPTY : user.getDeptId();
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛宀椾綅
+ *
+ * @return userName
+ */
+ public static String getPostId() {
+ BladeUser user = getUser();
+ return (null == user) ? StringPool.EMPTY : user.getPostId();
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛宀椾綅
+ *
+ * @param request request
+ * @return userName
+ */
+ public static String getPostId(HttpServletRequest request) {
+ BladeUser user = getUser(request);
+ return (null == user) ? StringPool.EMPTY : user.getPostId();
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛瑙掕壊
+ *
+ * @return userName
+ */
+ public static String getUserRole() {
+ BladeUser user = getUser();
+ return (null == user) ? StringPool.EMPTY : user.getRoleName();
+ }
+
+ /**
+ * 鑾峰彇鐢ㄨ鑹�
+ *
+ * @param request request
+ * @return userName
+ */
+ public static String getUserRole(HttpServletRequest request) {
+ BladeUser user = getUser(request);
+ return (null == user) ? StringPool.EMPTY : user.getRoleName();
+ }
+
+ /**
+ * 鑾峰彇绉熸埛ID
+ *
+ * @return tenantId
+ */
+ public static String getTenantId() {
+ BladeUser user = getUser();
+ return (null == user) ? StringPool.EMPTY : user.getTenantId();
+ }
+
+ /**
+ * 鑾峰彇绉熸埛ID
+ *
+ * @param request request
+ * @return tenantId
+ */
+ public static String getTenantId(HttpServletRequest request) {
+ BladeUser user = getUser(request);
+ return (null == user) ? StringPool.EMPTY : user.getTenantId();
+ }
+
+ /**
+ * 鑾峰彇绗笁鏂硅璇両D
+ *
+ * @return tenantId
+ */
+ public static String getOauthId() {
+ BladeUser user = getUser();
+ return (null == user) ? StringPool.EMPTY : user.getOauthId();
+ }
+
+ /**
+ * 鑾峰彇绗笁鏂硅璇両D
+ *
+ * @param request request
+ * @return tenantId
+ */
+ public static String getOauthId(HttpServletRequest request) {
+ BladeUser user = getUser(request);
+ return (null == user) ? StringPool.EMPTY : user.getOauthId();
+ }
+
+ /**
+ * 鑾峰彇瀹㈡埛绔痠d
+ *
+ * @return clientId
+ */
+ public static String getClientId() {
+ BladeUser user = getUser();
+ return (null == user) ? StringPool.EMPTY : user.getClientId();
+ }
+
+ /**
+ * 鑾峰彇瀹㈡埛绔痠d
+ *
+ * @param request request
+ * @return clientId
+ */
+ public static String getClientId(HttpServletRequest request) {
+ BladeUser user = getUser(request);
+ return (null == user) ? StringPool.EMPTY : user.getClientId();
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛璇︽儏
+ *
+ * @return clientId
+ */
+ public static Kv getDetail() {
+ BladeUser user = getUser();
+ return (null == user) ? Kv.create() : user.getDetail();
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛璇︽儏
+ *
+ * @param request request
+ * @return clientId
+ */
+ public static Kv getDetail(HttpServletRequest request) {
+ BladeUser user = getUser(request);
+ return (null == user) ? Kv.create() : user.getDetail();
+ }
+
+ /**
+ * 鑾峰彇Claims
+ *
+ * @param request request
+ * @return Claims
+ */
+ public static Claims getClaims(HttpServletRequest request) {
+ String auth = request.getHeader(AuthUtil.HEADER);
+ Claims claims = null;
+ String token;
+ // 鑾峰彇 Token 鍙傛暟
+ if (StringUtil.isNotBlank(auth)) {
+ token = JwtUtil.getToken(auth);
+ } else {
+ String parameter = request.getParameter(AuthUtil.HEADER);
+ token = JwtUtil.getToken(parameter);
+ }
+ // 鑾峰彇 Token 鍊�
+ if (StringUtil.isNotBlank(token)) {
+ claims = AuthUtil.parseJWT(token);
+ }
+ // 鍒ゆ柇 Token 鐘舵��
+ if (ObjectUtil.isNotEmpty(claims) && getJwtProperties().getState()) {
+ String tenantId = Func.toStr(claims.get(AuthUtil.TENANT_ID));
+ String userId = Func.toStr(claims.get(AuthUtil.USER_ID));
+ String accessToken = JwtUtil.getAccessToken(tenantId, userId, token);
+ if (!token.equalsIgnoreCase(accessToken)) {
+ return null;
+ }
+ }
+ return claims;
+ }
+
+ /**
+ * 鑾峰彇璇锋眰澶�
+ *
+ * @return header
+ */
+ public static String getHeader() {
+ return getHeader(Objects.requireNonNull(WebUtil.getRequest()));
+ }
+
+ /**
+ * 鑾峰彇璇锋眰澶�
+ *
+ * @param request request
+ * @return header
+ */
+ public static String getHeader(HttpServletRequest request) {
+ return request.getHeader(HEADER);
+ }
+
+ /**
+ * 瑙f瀽jsonWebToken
+ *
+ * @param jsonWebToken jsonWebToken
+ * @return Claims
+ */
+ public static Claims parseJWT(String jsonWebToken) {
+ return JwtUtil.parseJWT(jsonWebToken);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-cache/pom.xml b/Source/BladeX-Tool/blade-starter-cache/pom.xml
new file mode 100644
index 0000000..596c977
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-cache/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-cache</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!-- Blade -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-tool</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-auth</artifactId>
+ </dependency>
+ <!-- Ehcache -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-cache</artifactId>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-cache/src/main/java/org/springblade/core/cache/config/CacheConfiguration.java b/Source/BladeX-Tool/blade-starter-cache/src/main/java/org/springblade/core/cache/config/CacheConfiguration.java
new file mode 100644
index 0000000..cb3d0c1
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-cache/src/main/java/org/springblade/core/cache/config/CacheConfiguration.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.cache.config;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.cache.annotation.EnableCaching;
+
+/**
+ * Cache閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@EnableCaching
+@AutoConfiguration
+public class CacheConfiguration {
+}
diff --git a/Source/BladeX-Tool/blade-starter-cache/src/main/java/org/springblade/core/cache/constant/CacheConstant.java b/Source/BladeX-Tool/blade-starter-cache/src/main/java/org/springblade/core/cache/constant/CacheConstant.java
new file mode 100644
index 0000000..e4396b1
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-cache/src/main/java/org/springblade/core/cache/constant/CacheConstant.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.cache.constant;
+
+/**
+ * 缂撳瓨鍚�
+ *
+ * @author Chill
+ */
+public interface CacheConstant {
+
+ String BIZ_CACHE = "blade:biz";
+
+ String MENU_CACHE = "blade:menu";
+
+ String USER_CACHE = "blade:user";
+
+ String DICT_CACHE = "blade:dict";
+
+ String FLOW_CACHE = "blade:flow";
+
+ String SYS_CACHE = "blade:sys";
+
+ String RESOURCE_CACHE = "blade:resource";
+
+ String PARAM_CACHE = "blade:param";
+
+ String DEFAULT_CACHE = "default:cache";
+
+ String RETRY_LIMIT_CACHE = "retry:limit:cache";
+
+ String HALF_HOUR = "half:hour";
+
+ String HOUR = "hour";
+
+ String ONE_DAY = "one:day";
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-cache/src/main/java/org/springblade/core/cache/utils/CacheUtil.java b/Source/BladeX-Tool/blade-starter-cache/src/main/java/org/springblade/core/cache/utils/CacheUtil.java
new file mode 100644
index 0000000..9bd3dbb
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-cache/src/main/java/org/springblade/core/cache/utils/CacheUtil.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.cache.utils;
+
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.constant.BladeConstant;
+import org.springblade.core.tool.utils.*;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.lang.Nullable;
+
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * 缂撳瓨宸ュ叿绫�
+ *
+ * @author Chill
+ */
+public class CacheUtil {
+
+ private static CacheManager cacheManager;
+
+ private static final Boolean TENANT_MODE = Boolean.TRUE;
+
+ /**
+ * 鑾峰彇缂撳瓨宸ュ叿
+ *
+ * @return CacheManager
+ */
+ private static CacheManager getCacheManager() {
+ if (cacheManager == null) {
+ cacheManager = SpringUtil.getBean(CacheManager.class);
+ }
+ return cacheManager;
+ }
+
+ /**
+ * 鑾峰彇缂撳瓨瀵硅薄
+ *
+ * @param cacheName 缂撳瓨鍚�
+ * @return Cache
+ */
+ public static Cache getCache(String cacheName) {
+ return getCache(cacheName, TENANT_MODE);
+ }
+
+ /**
+ * 鑾峰彇缂撳瓨瀵硅薄
+ *
+ * @param cacheName 缂撳瓨鍚�
+ * @param tenantMode 绉熸埛妯″紡
+ * @return Cache
+ */
+ public static Cache getCache(String cacheName, Boolean tenantMode) {
+ return getCacheManager().getCache(formatCacheName(cacheName, tenantMode));
+ }
+
+ /**
+ * 鑾峰彇缂撳瓨瀵硅薄
+ *
+ * @param cacheName 缂撳瓨鍚�
+ * @param tenantId 绉熸埛ID
+ * @return Cache
+ */
+ public static Cache getCache(String cacheName, String tenantId) {
+ return getCacheManager().getCache(formatCacheName(cacheName, tenantId));
+ }
+
+ /**
+ * 鏍规嵁绉熸埛淇℃伅鏍煎紡鍖栫紦瀛樺悕
+ *
+ * @param cacheName 缂撳瓨鍚�
+ * @param tenantMode 绉熸埛妯″紡
+ * @return String
+ */
+ public static String formatCacheName(String cacheName, Boolean tenantMode) {
+ if (!tenantMode) {
+ return cacheName;
+ }
+ return formatCacheName(cacheName, AuthUtil.getTenantId());
+ }
+
+ /**
+ * 鏍规嵁绉熸埛淇℃伅鏍煎紡鍖栫紦瀛樺悕
+ *
+ * @param cacheName 缂撳瓨鍚�
+ * @param tenantId 绉熸埛ID
+ * @return String
+ */
+ public static String formatCacheName(String cacheName, String tenantId) {
+ return (StringUtil.isBlank(tenantId) ? cacheName : tenantId.concat(StringPool.COLON).concat(cacheName));
+ }
+
+ /**
+ * 鑾峰彇缂撳瓨
+ *
+ * @param cacheName 缂撳瓨鍚�
+ * @param keyPrefix 缂撳瓨閿墠缂�
+ * @param key 缂撳瓨閿��
+ * @return Object
+ */
+ @Nullable
+ public static Object get(String cacheName, String keyPrefix, Object key) {
+ return get(cacheName, keyPrefix, key, TENANT_MODE);
+ }
+
+ /**
+ * 鑾峰彇缂撳瓨
+ *
+ * @param cacheName 缂撳瓨鍚�
+ * @param keyPrefix 缂撳瓨閿墠缂�
+ * @param key 缂撳瓨閿��
+ * @param tenantMode 绉熸埛妯″紡
+ * @return Object
+ */
+ @Nullable
+ public static Object get(String cacheName, String keyPrefix, Object key, Boolean tenantMode) {
+ if (Func.hasEmpty(cacheName, keyPrefix, key)) {
+ return null;
+ }
+ Cache.ValueWrapper valueWrapper = getCache(cacheName, tenantMode).get(keyPrefix.concat(String.valueOf(key)));
+ if (Func.isEmpty(valueWrapper)) {
+ return null;
+ }
+ return valueWrapper.get();
+ }
+
+ /**
+ * 鑾峰彇缂撳瓨
+ *
+ * @param cacheName 缂撳瓨鍚�
+ * @param keyPrefix 缂撳瓨閿墠缂�
+ * @param key 缂撳瓨閿��
+ * @param type 绫诲瀷
+ * @param <T> 娉涘瀷
+ * @return T
+ */
+ @Nullable
+ public static <T> T get(String cacheName, String keyPrefix, Object key, @Nullable Class<T> type) {
+ return get(cacheName, keyPrefix, key, type, TENANT_MODE);
+ }
+
+ /**
+ * 鑾峰彇缂撳瓨
+ *
+ * @param cacheName 缂撳瓨鍚�
+ * @param keyPrefix 缂撳瓨閿墠缂�
+ * @param key 缂撳瓨閿��
+ * @param type 绫诲瀷
+ * @param tenantMode 绉熸埛妯″紡
+ * @param <T> 娉涘瀷
+ * @return T
+ */
+ @Nullable
+ public static <T> T get(String cacheName, String keyPrefix, Object key, @Nullable Class<T> type, Boolean tenantMode) {
+ if (Func.hasEmpty(cacheName, keyPrefix, key)) {
+ return null;
+ }
+ return getCache(cacheName, tenantMode).get(keyPrefix.concat(String.valueOf(key)), type);
+ }
+
+ /**
+ * 鑾峰彇缂撳瓨
+ *
+ * @param cacheName 缂撳瓨鍚�
+ * @param keyPrefix 缂撳瓨閿墠缂�
+ * @param key 缂撳瓨閿��
+ * @param valueLoader 閲嶈浇瀵硅薄
+ * @param <T> 娉涘瀷
+ * @return T
+ */
+ @Nullable
+ public static <T> T get(String cacheName, String keyPrefix, Object key, Callable<T> valueLoader) {
+ return get(cacheName, keyPrefix, key, valueLoader, TENANT_MODE);
+ }
+
+ /**
+ * 鑾峰彇缂撳瓨
+ *
+ * @param cacheName 缂撳瓨鍚�
+ * @param keyPrefix 缂撳瓨閿墠缂�
+ * @param key 缂撳瓨閿��
+ * @param valueLoader 閲嶈浇瀵硅薄
+ * @param tenantMode 绉熸埛妯″紡
+ * @param <T> 娉涘瀷
+ * @return T
+ */
+ @Nullable
+ public static <T> T get(String cacheName, String keyPrefix, Object key, Callable<T> valueLoader, Boolean tenantMode) {
+ if (Func.hasEmpty(cacheName, keyPrefix, key)) {
+ return null;
+ }
+ try {
+ Cache.ValueWrapper valueWrapper = getCache(cacheName, tenantMode).get(keyPrefix.concat(String.valueOf(key)));
+ Object value = null;
+ if (valueWrapper == null) {
+ T call = valueLoader.call();
+ if (ObjectUtil.isNotEmpty(call)) {
+ Field field = ReflectUtil.getField(call.getClass(), BladeConstant.DB_PRIMARY_KEY);
+ if (ObjectUtil.isNotEmpty(field) && ObjectUtil.isEmpty(ClassUtil.getMethod(call.getClass(), BladeConstant.DB_PRIMARY_KEY_METHOD).invoke(call))) {
+ return null;
+ }
+ getCache(cacheName, tenantMode).put(keyPrefix.concat(String.valueOf(key)), call);
+ value = call;
+ }
+ } else {
+ value = valueWrapper.get();
+ }
+ return (T) value;
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * 璁剧疆缂撳瓨
+ *
+ * @param cacheName 缂撳瓨鍚�
+ * @param keyPrefix 缂撳瓨閿墠缂�
+ * @param key 缂撳瓨閿��
+ * @param value 缂撳瓨鍊�
+ */
+ public static void put(String cacheName, String keyPrefix, Object key, @Nullable Object value) {
+ put(cacheName, keyPrefix, key, value, TENANT_MODE);
+ }
+
+ /**
+ * 璁剧疆缂撳瓨
+ *
+ * @param cacheName 缂撳瓨鍚�
+ * @param keyPrefix 缂撳瓨閿墠缂�
+ * @param key 缂撳瓨閿��
+ * @param value 缂撳瓨鍊�
+ * @param tenantMode 绉熸埛妯″紡
+ */
+ public static void put(String cacheName, String keyPrefix, Object key, @Nullable Object value, Boolean tenantMode) {
+ getCache(cacheName, tenantMode).put(keyPrefix.concat(String.valueOf(key)), value);
+ }
+
+ /**
+ * 娓呴櫎缂撳瓨
+ *
+ * @param cacheName 缂撳瓨鍚�
+ * @param keyPrefix 缂撳瓨閿墠缂�
+ * @param key 缂撳瓨閿��
+ */
+ public static void evict(String cacheName, String keyPrefix, Object key) {
+ evict(cacheName, keyPrefix, key, TENANT_MODE);
+ }
+
+ /**
+ * 娓呴櫎缂撳瓨
+ *
+ * @param cacheName 缂撳瓨鍚�
+ * @param keyPrefix 缂撳瓨閿墠缂�
+ * @param key 缂撳瓨閿��
+ * @param tenantMode 绉熸埛妯″紡
+ */
+ public static void evict(String cacheName, String keyPrefix, Object key, Boolean tenantMode) {
+ if (Func.hasEmpty(cacheName, keyPrefix, key)) {
+ return;
+ }
+ getCache(cacheName, tenantMode).evict(keyPrefix.concat(String.valueOf(key)));
+ }
+
+ /**
+ * 娓呯┖缂撳瓨
+ *
+ * @param cacheName 缂撳瓨鍚�
+ */
+ public static void clear(String cacheName) {
+ clear(cacheName, TENANT_MODE);
+ }
+
+ /**
+ * 娓呯┖缂撳瓨
+ *
+ * @param cacheName 缂撳瓨鍚�
+ * @param tenantMode 绉熸埛妯″紡
+ */
+ public static void clear(String cacheName, Boolean tenantMode) {
+ if (Func.isEmpty(cacheName)) {
+ return;
+ }
+ getCache(cacheName, tenantMode).clear();
+ }
+
+ /**
+ * 娓呯┖缂撳瓨
+ *
+ * @param cacheName 缂撳瓨鍚�
+ * @param tenantId 绉熸埛ID
+ */
+ public static void clear(String cacheName, String tenantId) {
+ if (Func.isEmpty(cacheName)) {
+ return;
+ }
+ getCache(cacheName, tenantId).clear();
+ }
+
+ /**
+ * 娓呯┖缂撳瓨
+ *
+ * @param cacheName 缂撳瓨鍚�
+ * @param tenantIds 绉熸埛ID闆嗗悎
+ */
+ public static void clear(String cacheName, List<String> tenantIds) {
+ if (Func.isEmpty(cacheName)) {
+ return;
+ }
+ tenantIds.forEach(tenantId -> getCache(cacheName, tenantId).clear());
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-datascope/pom.xml b/Source/BladeX-Tool/blade-starter-datascope/pom.xml
new file mode 100644
index 0000000..fbb2ea3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-datascope/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-datascope</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-cache</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-mybatis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/annotation/DataAuth.java b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/annotation/DataAuth.java
new file mode 100644
index 0000000..4824193
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/annotation/DataAuth.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.datascope.annotation;
+
+import org.springblade.core.datascope.constant.DataScopeConstant;
+import org.springblade.core.datascope.enums.DataScopeEnum;
+
+import java.lang.annotation.*;
+
+/**
+ * 鏁版嵁鏉冮檺瀹氫箟
+ *
+ * @author Chill
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface DataAuth {
+
+ /**
+ * 璧勬簮缂栧彿
+ */
+ String code() default "";
+
+ /**
+ * 鏁版嵁鏉冮檺瀵瑰簲瀛楁
+ */
+ String column() default DataScopeConstant.DEFAULT_COLUMN;
+
+ /**
+ * 鏁版嵁鏉冮檺瑙勫垯
+ */
+ DataScopeEnum type() default DataScopeEnum.ALL;
+
+ /**
+ * 鍙瀛楁
+ */
+ String field() default "*";
+
+ /**
+ * 鏁版嵁鏉冮檺瑙勫垯鍊煎煙
+ */
+ String value() default "";
+
+}
+
diff --git a/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/config/DataScopeConfiguration.java b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/config/DataScopeConfiguration.java
new file mode 100644
index 0000000..c41a28c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/config/DataScopeConfiguration.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.datascope.config;
+
+import lombok.AllArgsConstructor;
+import org.springblade.core.datascope.handler.BladeDataScopeHandler;
+import org.springblade.core.datascope.handler.BladeScopeModelHandler;
+import org.springblade.core.datascope.handler.DataScopeHandler;
+import org.springblade.core.datascope.handler.ScopeModelHandler;
+import org.springblade.core.datascope.interceptor.DataScopeInterceptor;
+import org.springblade.core.datascope.props.DataScopeProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+/**
+ * 鏁版嵁鏉冮檺閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@AutoConfiguration
+@AllArgsConstructor
+@EnableConfigurationProperties(DataScopeProperties.class)
+public class DataScopeConfiguration {
+
+ private final JdbcTemplate jdbcTemplate;
+
+ @Bean
+ @ConditionalOnMissingBean(ScopeModelHandler.class)
+ public ScopeModelHandler scopeModelHandler() {
+ return new BladeScopeModelHandler(jdbcTemplate);
+ }
+
+ @Bean
+ @ConditionalOnBean(ScopeModelHandler.class)
+ @ConditionalOnMissingBean(DataScopeHandler.class)
+ public DataScopeHandler dataScopeHandler(ScopeModelHandler scopeModelHandler) {
+ return new BladeDataScopeHandler(scopeModelHandler);
+ }
+
+ @Bean
+ @ConditionalOnBean(DataScopeHandler.class)
+ @ConditionalOnMissingBean(DataScopeInterceptor.class)
+ public DataScopeInterceptor interceptor(DataScopeHandler dataScopeHandler, DataScopeProperties dataScopeProperties) {
+ return new DataScopeInterceptor(dataScopeHandler, dataScopeProperties);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/constant/DataScopeConstant.java b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/constant/DataScopeConstant.java
new file mode 100644
index 0000000..b2ac236
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/constant/DataScopeConstant.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.datascope.constant;
+
+import org.springblade.core.tool.utils.StringUtil;
+
+/**
+ * 鏁版嵁鏉冮檺甯搁噺
+ *
+ * @author Chill
+ */
+public interface DataScopeConstant {
+
+ String DEFAULT_COLUMN = "create_dept";
+
+ /**
+ * 鑾峰彇閮ㄩ棬鏁版嵁
+ */
+ String DATA_BY_DEPT = "select id from blade_dept where ancestors like concat(concat('%', ?),'%') and is_deleted = 0";
+
+ /**
+ * 鏍规嵁resourceCode鑾峰彇鏁版嵁鏉冮檺閰嶇疆
+ */
+ String DATA_BY_CODE = "select resource_code, scope_column, scope_field, scope_type, scope_value from blade_scope_data where resource_code = ?";
+
+ /**
+ * 鏍规嵁mapperId鑾峰彇鏁版嵁鏉冮檺閰嶇疆
+ *
+ * @param size 鏁伴噺
+ * @return String
+ */
+ static String dataByMapper(int size) {
+ return "select resource_code, scope_column, scope_field, scope_type, scope_value from blade_scope_data where scope_class = ? and id in (select scope_id from blade_role_scope where scope_category = 1 and role_id in (" + buildHolder(size) + "))";
+ }
+
+ /**
+ * 鑾峰彇Sql鍗犱綅绗�
+ *
+ * @param size 鏁伴噺
+ * @return String
+ */
+ static String buildHolder(int size) {
+ StringBuilder builder = StringUtil.builder();
+ for (int i = 0; i < size; i++) {
+ builder.append("?,");
+ }
+ return StringUtil.removeSuffix(builder.toString(), ",");
+ }
+
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/enums/DataScopeEnum.java b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/enums/DataScopeEnum.java
new file mode 100644
index 0000000..5f3e82d
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/enums/DataScopeEnum.java
@@ -0,0 +1,78 @@
+/*
+ *
+ * Copyright (c) 2018-2025, lengleng All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: lengleng (wangiegie@gmail.com)
+ *
+ */
+package org.springblade.core.datascope.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 鏁版嵁鏉冮檺绫诲瀷
+ *
+ * @author lengleng, Chill
+ */
+@Getter
+@AllArgsConstructor
+public enum DataScopeEnum {
+ /**
+ * 鍏ㄩ儴鏁版嵁
+ */
+ ALL(1, "鍏ㄩ儴"),
+
+ /**
+ * 鏈汉鍙
+ */
+ OWN(2, "鏈汉鍙"),
+
+ /**
+ * 鎵�鍦ㄦ満鏋勫彲瑙�
+ */
+ OWN_DEPT(3, "鎵�鍦ㄦ満鏋勫彲瑙�"),
+
+ /**
+ * 鎵�鍦ㄦ満鏋勫強瀛愮骇鍙
+ */
+ OWN_DEPT_CHILD(4, "鎵�鍦ㄦ満鏋勫強瀛愮骇鍙"),
+
+ /**
+ * 鑷畾涔�
+ */
+ CUSTOM(5, "鑷畾涔�");
+
+ /**
+ * 绫诲瀷
+ */
+ private final int type;
+ /**
+ * 鎻忚堪
+ */
+ private final String description;
+
+ public static DataScopeEnum of(Integer dataScopeType) {
+ if (dataScopeType == null) {
+ return null;
+ }
+ DataScopeEnum[] values = DataScopeEnum.values();
+ for (DataScopeEnum scopeTypeEnum : values) {
+ if (scopeTypeEnum.type == dataScopeType) {
+ return scopeTypeEnum;
+ }
+ }
+ return null;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/exception/DataScopeException.java b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/exception/DataScopeException.java
new file mode 100644
index 0000000..e3afb63
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/exception/DataScopeException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.datascope.exception;
+
+/**
+ * 鏁版嵁鏉冮檺寮傚父
+ *
+ * @author L.cm
+ */
+public class DataScopeException extends RuntimeException {
+
+ public DataScopeException() {
+ }
+
+ public DataScopeException(String message) {
+ super(message);
+ }
+
+ public DataScopeException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/handler/BladeDataScopeHandler.java b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/handler/BladeDataScopeHandler.java
new file mode 100644
index 0000000..b51e7ca
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/handler/BladeDataScopeHandler.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.datascope.handler;
+
+import lombok.RequiredArgsConstructor;
+import org.springblade.core.datascope.enums.DataScopeEnum;
+import org.springblade.core.datascope.model.DataScopeModel;
+import org.springblade.core.secure.BladeUser;
+import org.springblade.core.tool.constant.RoleConstant;
+import org.springblade.core.tool.utils.BeanUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.PlaceholderUtil;
+import org.springblade.core.tool.utils.StringUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 榛樿鏁版嵁鏉冮檺瑙勫垯
+ *
+ * @author Chill
+ */
+@RequiredArgsConstructor
+public class BladeDataScopeHandler implements DataScopeHandler {
+
+ private final ScopeModelHandler scopeModelHandler;
+
+ @Override
+ public String sqlCondition(String mapperId, DataScopeModel dataScope, BladeUser bladeUser, String originalSql) {
+
+ //鏁版嵁鏉冮檺璧勬簮缂栧彿
+ String code = dataScope.getResourceCode();
+
+ //鏍规嵁mapperId浠庢暟鎹簱涓幏鍙栧搴旀ā鍨�
+ DataScopeModel dataScopeDb = scopeModelHandler.getDataScopeByMapper(mapperId, bladeUser.getRoleId());
+
+ //mapperId閰嶇疆鏈彇鍒板垯浠庢暟鎹簱涓牴鎹祫婧愮紪鍙疯幏鍙�
+ if (dataScopeDb == null && StringUtil.isNotBlank(code)) {
+ dataScopeDb = scopeModelHandler.getDataScopeByCode(code);
+ }
+
+ //鏈粠鏁版嵁搴撴壘鍒板搴旈厤缃垯閲囩敤榛樿
+ dataScope = (dataScopeDb != null) ? dataScopeDb : dataScope;
+
+ //鍒ゆ柇鏁版嵁鏉冮檺绫诲瀷骞剁粍瑁呭搴擲ql
+ Integer scopeRule = Objects.requireNonNull(dataScope).getScopeType();
+ DataScopeEnum scopeTypeEnum = DataScopeEnum.of(scopeRule);
+ List<Long> ids = new ArrayList<>();
+ String whereSql = "where scope.{} in ({})";
+ if (DataScopeEnum.ALL == scopeTypeEnum || StringUtil.containsAny(bladeUser.getRoleName(), RoleConstant.ADMINISTRATOR)) {
+ return null;
+ } else if (DataScopeEnum.CUSTOM == scopeTypeEnum) {
+ whereSql = PlaceholderUtil.getDefaultResolver().resolveByMap(dataScope.getScopeValue(), BeanUtil.toMap(bladeUser));
+ } else if (DataScopeEnum.OWN == scopeTypeEnum) {
+ ids.add(bladeUser.getUserId());
+ } else if (DataScopeEnum.OWN_DEPT == scopeTypeEnum) {
+ ids.addAll(Func.toLongList(bladeUser.getDeptId()));
+ } else if (DataScopeEnum.OWN_DEPT_CHILD == scopeTypeEnum) {
+ List<Long> deptIds = Func.toLongList(bladeUser.getDeptId());
+ ids.addAll(deptIds);
+ deptIds.forEach(deptId -> {
+ List<Long> deptIdList = scopeModelHandler.getDeptAncestors(deptId);
+ ids.addAll(deptIdList);
+ });
+ }
+ return StringUtil.format("select {} from ({}) scope " + whereSql, Func.toStr(dataScope.getScopeField(), "*"), originalSql, dataScope.getScopeColumn(), StringUtil.join(ids));
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/handler/BladeScopeModelHandler.java b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/handler/BladeScopeModelHandler.java
new file mode 100644
index 0000000..86bf7f2
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/handler/BladeScopeModelHandler.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.datascope.handler;
+
+import lombok.RequiredArgsConstructor;
+import org.springblade.core.cache.utils.CacheUtil;
+import org.springblade.core.datascope.constant.DataScopeConstant;
+import org.springblade.core.datascope.model.DataScopeModel;
+import org.springblade.core.tool.utils.CollectionUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.springblade.core.cache.constant.CacheConstant.SYS_CACHE;
+
+/**
+ * BladeScopeModelHandler
+ *
+ * @author Chill
+ */
+@RequiredArgsConstructor
+public class BladeScopeModelHandler implements ScopeModelHandler {
+
+ private static final String SCOPE_CACHE_CODE = "dataScope:code:";
+ private static final String SCOPE_CACHE_CLASS = "dataScope:class:";
+ private static final String DEPT_CACHE_ANCESTORS = "dept:ancestors:";
+ private static final DataScopeModel SEARCHED_DATA_SCOPE_MODEL = new DataScopeModel(Boolean.TRUE);
+
+ private final JdbcTemplate jdbcTemplate;
+
+ /**
+ * 鑾峰彇鏁版嵁鏉冮檺
+ *
+ * @param mapperId 鏁版嵁鏉冮檺mapperId
+ * @param roleId 鐢ㄦ埛瑙掕壊闆嗗悎
+ * @return DataScopeModel
+ */
+ @Override
+ public DataScopeModel getDataScopeByMapper(String mapperId, String roleId) {
+ List<Object> args = new ArrayList<>(Collections.singletonList(mapperId));
+ List<Long> roleIds = Func.toLongList(roleId);
+ args.addAll(roleIds);
+ // 澧炲姞searched瀛楁闃叉鏈厤缃殑鍙傛暟閲嶅璇诲簱瀵艰嚧缂撳瓨鍑荤┛
+ // 鍚庣画鑻ユ湁鏂板閰嶇疆鍒欎細娓呯┖缂撳瓨閲嶆柊鍔犺浇
+ DataScopeModel dataScope = CacheUtil.get(SYS_CACHE, SCOPE_CACHE_CLASS, mapperId + StringPool.COLON + roleId, DataScopeModel.class, Boolean.FALSE);
+ if (dataScope == null || !dataScope.getSearched()) {
+ List<DataScopeModel> list = jdbcTemplate.query(DataScopeConstant.dataByMapper(roleIds.size()), args.toArray(), new BeanPropertyRowMapper<>(DataScopeModel.class));
+ if (CollectionUtil.isNotEmpty(list)) {
+ dataScope = list.iterator().next();
+ dataScope.setSearched(Boolean.TRUE);
+ } else {
+ dataScope = SEARCHED_DATA_SCOPE_MODEL;
+ }
+ CacheUtil.put(SYS_CACHE, SCOPE_CACHE_CLASS, mapperId + StringPool.COLON + roleId, dataScope, Boolean.FALSE);
+ }
+ return StringUtil.isNotBlank(dataScope.getResourceCode()) ? dataScope : null;
+ }
+
+ /**
+ * 鑾峰彇鏁版嵁鏉冮檺
+ *
+ * @param code 鏁版嵁鏉冮檺璧勬簮缂栧彿
+ * @return DataScopeModel
+ */
+ @Override
+ public DataScopeModel getDataScopeByCode(String code) {
+ DataScopeModel dataScope = CacheUtil.get(SYS_CACHE, SCOPE_CACHE_CODE, code, DataScopeModel.class, Boolean.FALSE);
+ // 澧炲姞searched瀛楁闃叉鏈厤缃殑鍙傛暟閲嶅璇诲簱瀵艰嚧缂撳瓨鍑荤┛
+ // 鍚庣画鑻ユ湁鏂板閰嶇疆鍒欎細娓呯┖缂撳瓨閲嶆柊鍔犺浇
+ if (dataScope == null || !dataScope.getSearched()) {
+ List<DataScopeModel> list = jdbcTemplate.query(DataScopeConstant.DATA_BY_CODE, new Object[]{code}, new BeanPropertyRowMapper<>(DataScopeModel.class));
+ if (CollectionUtil.isNotEmpty(list)) {
+ dataScope = list.iterator().next();
+ dataScope.setSearched(Boolean.TRUE);
+ } else {
+ dataScope = SEARCHED_DATA_SCOPE_MODEL;
+ }
+ CacheUtil.put(SYS_CACHE, SCOPE_CACHE_CODE, code, dataScope, Boolean.FALSE);
+ }
+ return StringUtil.isNotBlank(dataScope.getResourceCode()) ? dataScope : null;
+ }
+
+ /**
+ * 鑾峰彇閮ㄩ棬瀛愮骇
+ *
+ * @param deptId 閮ㄩ棬id
+ * @return deptIds
+ */
+ @Override
+ public List<Long> getDeptAncestors(Long deptId) {
+ List ancestors = CacheUtil.get(SYS_CACHE, DEPT_CACHE_ANCESTORS, deptId, List.class);
+ if (CollectionUtil.isEmpty(ancestors)) {
+ ancestors = jdbcTemplate.queryForList(DataScopeConstant.DATA_BY_DEPT, new Object[]{deptId}, Long.class);
+ CacheUtil.put(SYS_CACHE, DEPT_CACHE_ANCESTORS, deptId, ancestors);
+ }
+ return ancestors;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/handler/DataScopeHandler.java b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/handler/DataScopeHandler.java
new file mode 100644
index 0000000..181c619
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/handler/DataScopeHandler.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.datascope.handler;
+
+import org.springblade.core.datascope.model.DataScopeModel;
+import org.springblade.core.secure.BladeUser;
+
+/**
+ * 鏁版嵁鏉冮檺瑙勫垯
+ *
+ * @author Chill
+ */
+public interface DataScopeHandler {
+
+ /**
+ * 鑾峰彇杩囨护sql
+ *
+ * @param mapperId 鏁版嵁鏌ヨ绫�
+ * @param dataScope 鏁版嵁鏉冮檺绫�
+ * @param bladeUser 褰撳墠鐢ㄦ埛淇℃伅
+ * @param originalSql 鍘熷Sql
+ * @return sql
+ */
+ String sqlCondition(String mapperId, DataScopeModel dataScope, BladeUser bladeUser, String originalSql);
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/handler/ScopeModelHandler.java b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/handler/ScopeModelHandler.java
new file mode 100644
index 0000000..388ddb2
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/handler/ScopeModelHandler.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.datascope.handler;
+
+import org.springblade.core.datascope.model.DataScopeModel;
+
+import java.util.List;
+
+/**
+ * 鑾峰彇鏁版嵁鏉冮檺妯″瀷缁熶竴鎺ュ彛
+ *
+ * @author Chill
+ */
+public interface ScopeModelHandler {
+
+ /**
+ * 鑾峰彇鏁版嵁鏉冮檺
+ *
+ * @param mapperId 鏁版嵁鏉冮檺mapperId
+ * @param roleId 鐢ㄦ埛瑙掕壊闆嗗悎
+ * @return DataScopeModel
+ */
+ DataScopeModel getDataScopeByMapper(String mapperId, String roleId);
+
+ /**
+ * 鑾峰彇鏁版嵁鏉冮檺
+ *
+ * @param code 鏁版嵁鏉冮檺璧勬簮缂栧彿
+ * @return DataScopeModel
+ */
+ DataScopeModel getDataScopeByCode(String code);
+
+ /**
+ * 鑾峰彇閮ㄩ棬瀛愮骇
+ *
+ * @param deptId 閮ㄩ棬id
+ * @return deptIds
+ */
+ List<Long> getDeptAncestors(Long deptId);
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/interceptor/DataScopeInterceptor.java b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/interceptor/DataScopeInterceptor.java
new file mode 100644
index 0000000..992f4a3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/interceptor/DataScopeInterceptor.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.datascope.interceptor;
+
+import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
+import com.baomidou.mybatisplus.core.toolkit.StringPool;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.SqlCommandType;
+import org.apache.ibatis.mapping.StatementType;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
+import org.springblade.core.datascope.annotation.DataAuth;
+import org.springblade.core.datascope.handler.DataScopeHandler;
+import org.springblade.core.datascope.model.DataScopeModel;
+import org.springblade.core.datascope.props.DataScopeProperties;
+import org.springblade.core.mp.intercept.QueryInterceptor;
+import org.springblade.core.secure.BladeUser;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.utils.ClassUtil;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.core.tool.utils.StringUtil;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+
+/**
+ * mybatis 鏁版嵁鏉冮檺鎷︽埅鍣�
+ *
+ * @author L.cm, Chill
+ */
+@Slf4j
+@RequiredArgsConstructor
+@SuppressWarnings({"rawtypes"})
+public class DataScopeInterceptor implements QueryInterceptor {
+
+ private final ConcurrentMap<String, DataAuth> dataAuthMap = new ConcurrentHashMap<>(8);
+
+ private final DataScopeHandler dataScopeHandler;
+ private final DataScopeProperties dataScopeProperties;
+
+ @Override
+ public void intercept(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
+ //鏈惎鐢ㄥ垯鏀捐
+ if (!dataScopeProperties.getEnabled()) {
+ return;
+ }
+
+ //鏈彇鍒扮敤鎴峰垯鏀捐
+ BladeUser bladeUser = AuthUtil.getUser();
+ if (bladeUser == null) {
+ return;
+ }
+
+ if (SqlCommandType.SELECT != ms.getSqlCommandType() || StatementType.CALLABLE == ms.getStatementType()) {
+ return;
+ }
+
+ String originalSql = boundSql.getSql();
+
+ //鏌ユ壘娉ㄨВ涓寘鍚獶ataAuth绫诲瀷鐨勫弬鏁�
+ DataAuth dataAuth = findDataAuthAnnotation(ms);
+
+ //娉ㄨВ涓虹┖骞朵笖鏁版嵁鏉冮檺鏂规硶鍚嶆湭鍖归厤鍒�,鍒欐斁琛�
+ String mapperId = ms.getId();
+ String className = mapperId.substring(0, mapperId.lastIndexOf(StringPool.DOT));
+ String mapperName = ClassUtil.getShortName(className);
+ String methodName = mapperId.substring(mapperId.lastIndexOf(StringPool.DOT) + 1);
+ boolean mapperSkip = dataScopeProperties.getMapperKey().stream().noneMatch(methodName::contains)
+ || dataScopeProperties.getMapperExclude().stream().anyMatch(mapperName::contains);
+ if (dataAuth == null && mapperSkip) {
+ return;
+ }
+
+ //鍒涘缓鏁版嵁鏉冮檺妯″瀷
+ DataScopeModel dataScope = new DataScopeModel();
+
+ //鑻ユ敞瑙d笉涓虹┖,鍒欓厤缃敞瑙i」
+ if (dataAuth != null) {
+ dataScope.setResourceCode(dataAuth.code());
+ dataScope.setScopeColumn(dataAuth.column());
+ dataScope.setScopeType(dataAuth.type().getType());
+ dataScope.setScopeField(dataAuth.field());
+ dataScope.setScopeValue(dataAuth.value());
+ }
+
+ //鑾峰彇鏁版嵁鏉冮檺瑙勫垯瀵瑰簲鐨勭瓫閫塖ql
+ String sqlCondition = dataScopeHandler.sqlCondition(mapperId, dataScope, bladeUser, originalSql);
+ if (!StringUtil.isBlank(sqlCondition)) {
+ PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
+ mpBoundSql.sql(sqlCondition);
+ }
+ }
+
+ /**
+ * 鑾峰彇鏁版嵁鏉冮檺娉ㄨВ淇℃伅
+ *
+ * @param mappedStatement mappedStatement
+ * @return DataAuth
+ */
+ private DataAuth findDataAuthAnnotation(MappedStatement mappedStatement) {
+ String id = mappedStatement.getId();
+ return dataAuthMap.computeIfAbsent(id, (key) -> {
+ String className = key.substring(0, key.lastIndexOf(StringPool.DOT));
+ String mapperBean = StringUtil.firstCharToLower(ClassUtil.getShortName(className));
+ Object mapper = SpringUtil.getBean(mapperBean);
+ String methodName = key.substring(key.lastIndexOf(StringPool.DOT) + 1);
+ Class<?>[] interfaces = ClassUtil.getAllInterfaces(mapper);
+ for (Class<?> mapperInterface : interfaces) {
+ for (Method method : mapperInterface.getDeclaredMethods()) {
+ if (methodName.equals(method.getName()) && method.isAnnotationPresent(DataAuth.class)) {
+ return method.getAnnotation(DataAuth.class);
+ }
+ }
+ }
+ return null;
+ });
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/model/DataScopeModel.java b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/model/DataScopeModel.java
new file mode 100644
index 0000000..d37aa2f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/model/DataScopeModel.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.datascope.model;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springblade.core.datascope.constant.DataScopeConstant;
+import org.springblade.core.datascope.enums.DataScopeEnum;
+
+import java.io.Serializable;
+
+/**
+ * 鏁版嵁鏉冮檺瀹炰綋绫�
+ *
+ * @author Chill
+ */
+@Data
+@NoArgsConstructor
+public class DataScopeModel implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏋勯�犲櫒鍒涘缓
+ */
+ public DataScopeModel(Boolean searched) {
+ this.searched = searched;
+ }
+
+ /**
+ * 鏄惁宸叉煡璇�
+ */
+ private Boolean searched = Boolean.FALSE;
+ /**
+ * 璧勬簮缂栧彿
+ */
+ private String resourceCode;
+ /**
+ * 鏁版嵁鏉冮檺瀛楁
+ */
+ private String scopeColumn = DataScopeConstant.DEFAULT_COLUMN;
+ /**
+ * 鏁版嵁鏉冮檺瑙勫垯
+ */
+ private Integer scopeType = DataScopeEnum.ALL.getType();
+ /**
+ * 鍙瀛楁
+ */
+ private String scopeField;
+ /**
+ * 鏁版嵁鏉冮檺瑙勫垯鍊�
+ */
+ private String scopeValue;
+}
diff --git a/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/props/DataScopeProperties.java b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/props/DataScopeProperties.java
new file mode 100644
index 0000000..ea2c459
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-datascope/src/main/java/org/springblade/core/datascope/props/DataScopeProperties.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.datascope.props;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 鏁版嵁鏉冮檺鍙傛暟閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@Data
+@ConfigurationProperties(prefix = "blade.data-scope")
+public class DataScopeProperties {
+
+ /**
+ * 寮�鍚暟鎹潈闄�
+ */
+ private Boolean enabled = true;
+ /**
+ * mapper鏂规硶鍖归厤鍏抽敭瀛�
+ */
+ private List<String> mapperKey = Arrays.asList("page", "Page", "list", "List");
+
+ /**
+ * mapper杩囨护
+ */
+ private List<String> mapperExclude = Collections.singletonList("FlowMapper");
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/pom.xml b/Source/BladeX-Tool/blade-starter-develop/pom.xml
new file mode 100644
index 0000000..55aaec2
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-develop</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!--Blade-->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-tool</artifactId>
+ </dependency>
+ <!--Mybatis-Plus-Generator-->
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-generator</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-extension</artifactId>
+ </dependency>
+ <!--Beetl-->
+ <dependency>
+ <groupId>com.ibeetl</groupId>
+ <artifactId>beetl</artifactId>
+ <version>3.10.0.Antlr4.5-RELEASE</version>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/java/org/springblade/develop/CodeGenerator.java b/Source/BladeX-Tool/blade-starter-develop/src/main/java/org/springblade/develop/CodeGenerator.java
new file mode 100644
index 0000000..fdc5811
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/java/org/springblade/develop/CodeGenerator.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.develop;
+
+
+import org.springblade.develop.constant.DevelopConstant;
+import org.springblade.develop.support.BladeCodeGenerator;
+
+/**
+ * 浠g爜鐢熸垚鍣�
+ *
+ * @author Chill
+ */
+public class CodeGenerator {
+
+ /**
+ * 浠g爜鐢熸垚鐨勬ā鍧楀悕
+ */
+ public static String CODE_NAME = "搴旂敤绠$悊";
+ /**
+ * 浠g爜鎵�鍦ㄦ湇鍔″悕
+ */
+ public static String SERVICE_NAME = "blade-system";
+ /**
+ * 浠g爜鐢熸垚鐨勫寘鍚�
+ */
+ public static String PACKAGE_NAME = "org.springblade.system";
+ /**
+ * 鍓嶇浠g爜鐢熸垚椋庢牸
+ */
+ public static String CODE_STYLE = DevelopConstant.SABER_NAME;
+ /**
+ * 鍓嶇浠g爜鐢熸垚鍦板潃
+ */
+ public static String PACKAGE_WEB_DIR = "/Users/chill/Workspaces/product/Saber";
+ /**
+ * 闇�瑕佸幓鎺夌殑琛ㄥ墠缂�
+ */
+ public static String[] TABLE_PREFIX = {"blade_"};
+ /**
+ * 闇�瑕佺敓鎴愮殑琛ㄥ悕(涓よ�呭彧鑳藉彇鍏朵竴)
+ */
+ public static String[] INCLUDE_TABLES = {"blade_client"};
+ /**
+ * 闇�瑕佹帓闄ょ殑琛ㄥ悕(涓よ�呭彧鑳藉彇鍏朵竴)
+ */
+ public static String[] EXCLUDE_TABLES = {};
+ /**
+ * 鏄惁鍖呭惈鍩虹涓氬姟瀛楁
+ */
+ public static Boolean HAS_SUPER_ENTITY = Boolean.TRUE;
+ /**
+ * 鍩虹涓氬姟瀛楁
+ */
+ public static String[] SUPER_ENTITY_COLUMNS = {"id", "create_time", "create_user", "create_dept", "update_time", "update_user", "status", "is_deleted"};
+ /**
+ * 鏄惁鍖呭惈鍖呰鍣�
+ */
+ public static Boolean HAS_WRAPPER = Boolean.TRUE;
+ /**
+ * 鏄惁鍖呭惈杩滅▼璋冪敤
+ */
+ public static Boolean HAS_FEIGN = Boolean.FALSE;
+
+
+ /**
+ * RUN THIS
+ */
+ public static void run() {
+ BladeCodeGenerator generator = new BladeCodeGenerator();
+ generator.setCodeName(CODE_NAME);
+ generator.setServiceName(SERVICE_NAME);
+ generator.setCodeStyle(CODE_STYLE);
+ generator.setPackageName(PACKAGE_NAME);
+ generator.setPackageWebDir(PACKAGE_WEB_DIR);
+ generator.setTablePrefix(TABLE_PREFIX);
+ generator.setIncludeTables(INCLUDE_TABLES);
+ generator.setExcludeTables(EXCLUDE_TABLES);
+ generator.setHasSuperEntity(HAS_SUPER_ENTITY);
+ generator.setSuperEntityColumns(SUPER_ENTITY_COLUMNS);
+ generator.setHasWrapper(HAS_WRAPPER);
+ generator.setHasFeign(HAS_FEIGN);
+ generator.run();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/java/org/springblade/develop/constant/DevelopConstant.java b/Source/BladeX-Tool/blade-starter-develop/src/main/java/org/springblade/develop/constant/DevelopConstant.java
new file mode 100644
index 0000000..99bc2f1
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/java/org/springblade/develop/constant/DevelopConstant.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.develop.constant;
+
+/**
+ * 浠g爜鐢熸垚绯荤粺甯搁噺.
+ *
+ * @author Chill
+ */
+public interface DevelopConstant {
+ /**
+ * sword 绯荤粺鍚�
+ */
+ String SWORD_NAME = "sword";
+
+ /**
+ * saber 绯荤粺鍚�
+ */
+ String SABER_NAME = "saber";
+
+ /**
+ * lemon 绯荤粺鍚�
+ */
+ String LEMON_NAME = "lemon";
+
+ /**
+ * element 绯荤粺鍚�
+ */
+ String ELEMENT_NAME = "element";
+
+ /**
+ * 鍗曡〃妯″紡
+ */
+ String TEMPLATE_CRUD = "crud";
+
+ /**
+ * 鏍戣〃妯″紡
+ */
+ String TEMPLATE_TREE = "tree";
+
+ /**
+ * 涓诲瓙琛ㄦā寮�
+ */
+ String TEMPLATE_SUB = "sub";
+
+ /**
+ * 涓绘ā鍧�
+ */
+ String TEMPLATE_MAIN = "main";
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/java/org/springblade/develop/support/BladeCodeGenerator.java b/Source/BladeX-Tool/blade-starter-develop/src/main/java/org/springblade/develop/support/BladeCodeGenerator.java
new file mode 100644
index 0000000..d56a569
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/java/org/springblade/develop/support/BladeCodeGenerator.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.develop.support;
+
+import com.baomidou.mybatisplus.generator.FastAutoGenerator;
+import com.baomidou.mybatisplus.generator.config.TemplateType;
+import com.baomidou.mybatisplus.generator.config.rules.DateType;
+import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.annotations.Mapper;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PropertiesLoaderUtils;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static org.springblade.develop.constant.DevelopConstant.*;
+
+/**
+ * 浠g爜鐢熸垚鍣ㄩ厤缃被
+ *
+ * @author Chill
+ */
+@Data
+@Slf4j
+public class BladeCodeGenerator {
+ /**
+ * 浠g爜椋庢牸
+ */
+ private String codeStyle = SABER_NAME;
+ /**
+ * 浠g爜妯″潡鍚嶇О
+ */
+ private String codeName;
+ /**
+ * 妯″瀷缂栧彿
+ */
+ private String modelCode;
+ /**
+ * 妯″瀷瀹炰綋绫�
+ */
+ private String modelClass;
+ /**
+ * 浠g爜鎵�鍦ㄦ湇鍔″悕
+ */
+ private String serviceName = "blade-service";
+ /**
+ * 浠g爜鐢熸垚鐨勫寘鍚�
+ */
+ private String packageName = "org.springblade.test";
+ /**
+ * 妯$増绫诲瀷
+ */
+ private String templateType;
+ /**
+ * 浣滆�呬俊鎭�
+ */
+ private String author;
+ /**
+ * 瀛愯〃妯″瀷涓婚敭
+ */
+ private String subModelId;
+ /**
+ * 瀛愯〃缁戝畾澶栭敭
+ */
+ private String subFkId;
+ /**
+ * 鏍戜富閿瓧娈�
+ */
+ private String treeId;
+ /**
+ * 鏍戠埗涓婚敭瀛楁
+ */
+ private String treePid;
+ /**
+ * 鏍戝悕绉板瓧娈�
+ */
+ private String treeName;
+ /**
+ * 浠g爜鍚庣鐢熸垚鐨勫湴鍧�
+ */
+ private String packageDir;
+ /**
+ * 浠g爜鍓嶇鐢熸垚鐨勫湴鍧�
+ */
+ private String packageWebDir;
+ /**
+ * 闇�瑕佸幓鎺夌殑琛ㄥ墠缂�
+ */
+ private String[] tablePrefix = {"blade_"};
+ /**
+ * 闇�瑕佺敓鎴愮殑琛ㄥ悕(涓よ�呭彧鑳藉彇鍏朵竴)
+ */
+ private String[] includeTables = {"blade_test"};
+ /**
+ * 闇�瑕佹帓闄ょ殑琛ㄥ悕(涓よ�呭彧鑳藉彇鍏朵竴)
+ */
+ private String[] excludeTables = {};
+ /**
+ * 鏄惁鍖呭惈鍩虹涓氬姟瀛楁
+ */
+ private Boolean hasSuperEntity = Boolean.TRUE;
+ /**
+ * 鏄惁鍖呭惈鍖呰鍣�
+ */
+ private Boolean hasWrapper = Boolean.TRUE;
+ /**
+ * 鏄惁鍖呭惈杩滅▼璋冪敤
+ */
+ private Boolean hasFeign = Boolean.FALSE;
+ /**
+ * 鏄惁鍖呭惈鏈嶅姟鍚�
+ */
+ private Boolean hasServiceName = Boolean.FALSE;
+ /**
+ * 鍩虹涓氬姟瀛楁
+ */
+ private String[] superEntityColumns = {"create_time", "create_user", "create_dept", "update_time", "update_user", "status", "is_deleted"};
+ /**
+ * 绉熸埛瀛楁
+ */
+ private String tenantColumn = "tenant_id";
+ /**
+ * 鏁版嵁搴撻┍鍔ㄥ悕
+ */
+ private String driverName;
+ /**
+ * 鏁版嵁搴撻摼鎺ュ湴鍧�
+ */
+ private String url;
+ /**
+ * 鏁版嵁搴撶敤鎴峰悕
+ */
+ private String username;
+ /**
+ * 鏁版嵁搴撳瘑鐮�
+ */
+ private String password;
+ /**
+ * 鏁版嵁妯″瀷
+ */
+ private Map<String, Object> model;
+ /**
+ * 鏁版嵁鍘熷瀷
+ */
+ private List<Map<String, Object>> prototypes;
+ /**
+ * 瀛愭暟鎹ā鍨�
+ */
+ private Map<String, Object> subModel;
+ /**
+ * 瀛愭暟鎹師鍨�
+ */
+ private List<Map<String, Object>> subPrototypes;
+
+ /**
+ * 浠g爜鐢熸垚鎵ц
+ */
+ public void run() {
+ // 涓绘ā鍧椾唬鐮佺敓鎴�
+ getAutoGenerator(getCustomMap(TEMPLATE_MAIN), getCustomFile(TEMPLATE_MAIN)).templateEngine(new BladeTemplateEngine(getOutputDir(), getOutputWebDir())).execute();
+ // 瀛愭ā鍧椾唬鐮佺敓鎴�
+ if (Func.equals(templateType, TEMPLATE_SUB) && StringUtil.isNotBlank(subModelId)) {
+ getAutoGenerator(getCustomMap(TEMPLATE_SUB), getCustomFile(TEMPLATE_SUB)).templateEngine(new BladeTemplateEngine(getOutputDir(), getOutputWebDir())).execute();
+ }
+ }
+
+ /**
+ * 璁剧疆 customMap
+ */
+ private Map<String, Object> getCustomMap(String generateType) {
+ List<Map<String, Object>> prototypeList;
+ String[] split = packageName.split("\\.");
+ String serviceCode = split[split.length - 1];
+ Map<String, Object> customMap = new HashMap<>(11);
+ customMap.put("generateType", generateType);
+ customMap.put("codeName", codeName);
+ customMap.put("serviceName", serviceName);
+ customMap.put("serviceCode", serviceCode);
+ customMap.put("packageName", packageName);
+ customMap.put("tenantColumn", tenantColumn);
+ customMap.put("hasWrapper", hasWrapper);
+ customMap.put("hasServiceName", hasServiceName);
+ customMap.put("templateType", templateType);
+ customMap.put("author", author);
+ customMap.put("subModelId", subModelId);
+ customMap.put("subFkId", subFkId);
+ customMap.put("treeId", treeId);
+ customMap.put("treePid", treePid);
+ customMap.put("treeName", treeName);
+ customMap.put("subFkIdHump", StringUtil.underlineToHump(subFkId));
+ customMap.put("treeIdHump", StringUtil.underlineToHump(treeId));
+ customMap.put("treePidHump", StringUtil.underlineToHump(treePid));
+ if (Func.equals(generateType, TEMPLATE_SUB)) {
+ prototypeList = subPrototypes;
+ customMap.put("model", subModel);
+ customMap.put("prototypes", subPrototypes);
+ customMap.put("modelCode", subModel.get("modelCode"));
+ customMap.put("modelClass", subModel.get("modelClass"));
+ customMap.put("modelTable", subModel.get("modelTable"));
+ } else {
+ prototypeList = prototypes;
+ customMap.put("model", model);
+ customMap.put("prototypes", prototypes);
+ customMap.put("subModel", subModel);
+ customMap.put("subPrototypes", subPrototypes);
+ customMap.put("modelCode", model.get("modelCode"));
+ customMap.put("modelClass", model.get("modelClass"));
+ customMap.put("modelTable", model.get("modelTable"));
+ }
+ List<String> propertyImport = prototypeList.stream().filter(prototype -> {
+ String propertyType = String.valueOf(prototype.get("propertyType"));
+ return !"String".equals(propertyType) && !"Integer".equals(propertyType) && !"Long".equals(propertyType);
+ }).map(prototype -> String.valueOf(prototype.get("propertyEntity"))).distinct().collect(Collectors.toList());
+ customMap.put("propertyImport", propertyImport);
+ return customMap;
+ }
+
+ /**
+ * 璁剧疆 customFile
+ */
+ private Map<String, String> getCustomFile(String type) {
+ Map<String, String> customFile = new HashMap<>(15);
+ if (!Func.equals(type, TEMPLATE_SUB)) {
+ customFile.put("menu.sql", "/templates/sql/menu.sql.btl");
+ }
+ customFile.put("entityVO.java", "/templates/api/entityVO.java.btl");
+ customFile.put("entityDTO.java", "/templates/api/entityDTO.java.btl");
+ if (hasWrapper) {
+ customFile.put("wrapper.java", "/templates/api/wrapper.java.btl");
+ }
+ if (hasFeign) {
+ customFile.put("feign.java", "/templates/api/feign.java.btl");
+ customFile.put("feignclient.java", "/templates/api/feignclient.java.btl");
+ }
+ if (Func.isNotBlank(packageWebDir)) {
+ if (Func.equals(codeStyle, SWORD_NAME)) {
+ customFile.put("action.js", "/templates/sword/action.js.btl");
+ customFile.put("model.js", "/templates/sword/model.js.btl");
+ customFile.put("service.js", "/templates/sword/service.js.btl");
+ customFile.put("list.js", "/templates/sword/list.js.btl");
+ customFile.put("add.js", "/templates/sword/add.js.btl");
+ customFile.put("edit.js", "/templates/sword/edit.js.btl");
+ customFile.put("view.js", "/templates/sword/view.js.btl");
+ } else if (Func.equals(codeStyle, SABER_NAME)) {
+ customFile.put("api.js", "/templates/saber/" + templateType + "/api.js.btl");
+ customFile.put("const.js", "/templates/saber/" + templateType + "/const.js.btl");
+ if (!Func.equals(type, TEMPLATE_SUB)) {
+ customFile.put("crud.vue", "/templates/saber/" + templateType + "/crud.vue.btl");
+ }
+ } else if (Func.equals(codeStyle, ELEMENT_NAME)) {
+ customFile.put("api.js", "/templates/element/" + templateType + "/api.js.btl");
+ customFile.put("const.js", "/templates/element/" + templateType + "/const.js.btl");
+ if (!Func.equals(type, TEMPLATE_SUB)) {
+ customFile.put("crud.vue", "/templates/element/" + templateType + "/crud.vue.btl");
+ } else {
+ customFile.put("sub.vue", "/templates/element/" + templateType + "/sub.vue.btl");
+ }
+ } else if (Func.equals(codeStyle, LEMON_NAME)) {
+ customFile.put("data.ts", "/templates/lemon/" + templateType + "/data.ts.btl");
+ customFile.put("Modal.vue", "/templates/lemon/" + templateType + "/Modal.vue.btl");
+ customFile.put("data.data.ts", "/templates/lemon/" + templateType + "/data.data.ts.btl");
+ if (!Func.equals(type, TEMPLATE_SUB)) {
+ customFile.put("index.vue", "/templates/lemon/" + templateType + "/index.vue.btl");
+ } else {
+ customFile.put("lemonSub.vue", "/templates/lemon/" + templateType + "/sub.vue.btl");
+ }
+
+ }
+ }
+ return customFile;
+ }
+
+ private FastAutoGenerator getAutoGenerator(Map<String, Object> customMap, Map<String, String> customFile) {
+ Properties props = getProperties();
+ String url = Func.toStr(this.url, props.getProperty("spring.datasource.url"));
+ String username = Func.toStr(this.username, props.getProperty("spring.datasource.username"));
+ String password = Func.toStr(this.password, props.getProperty("spring.datasource.password"));
+ return FastAutoGenerator.create(url, username, password)
+ .globalConfig(builder -> builder.author(StringUtil.isBlank(author) ? props.getProperty("author") : author).dateType(DateType.TIME_PACK).enableSwagger().outputDir(getOutputDir()).disableOpenDir())
+ .packageConfig(builder -> builder.parent(packageName).controller("controller").entity("entity").service("service").serviceImpl("service.impl").mapper("mapper").xml("mapper"))
+ .strategyConfig(builder -> builder.addTablePrefix(tablePrefix).addInclude(Func.toStrArray(String.valueOf(customMap.get("modelTable")))).addExclude(excludeTables)
+ .entityBuilder().naming(NamingStrategy.underline_to_camel).columnNaming(NamingStrategy.underline_to_camel).enableLombok().superClass("org.springblade.core.mp.base.BaseEntity").formatFileName("%sEntity").addSuperEntityColumns(superEntityColumns).enableFileOverride()
+ .serviceBuilder().superServiceClass("org.springblade.core.mp.base.BaseService").superServiceImplClass("org.springblade.core.mp.base.BaseServiceImpl").formatServiceFileName("I%sService").formatServiceImplFileName("%sServiceImpl").enableFileOverride()
+ .mapperBuilder().mapperAnnotation(Mapper.class).enableBaseResultMap().enableBaseColumnList().formatMapperFileName("%sMapper").formatXmlFileName("%sMapper").enableFileOverride()
+ .controllerBuilder().superClass("org.springblade.core.boot.ctrl.BladeController").formatFileName("%sController").enableRestStyle().enableHyphenStyle().enableFileOverride()
+ )
+ .templateConfig(builder -> builder.disable(TemplateType.ENTITY)
+ .entity("/templates/api/entity.java")
+ .service("/templates/api/service.java")
+ .serviceImpl("/templates/api/serviceImpl.java")
+ .mapper("/templates/api/mapper.java")
+ .xml("/templates/api/mapper.xml")
+ .controller("/templates/api/controller.java"))
+ .injectionConfig(builder -> builder.beforeOutputFile(
+ (tableInfo, objectMap) -> System.out.println("tableInfo: " + tableInfo.getEntityName() + " objectMap: " + objectMap.size())
+ ).customMap(customMap).customFile(customFile)
+ );
+ }
+
+ /**
+ * 鑾峰彇閰嶇疆鏂囦欢
+ *
+ * @return 閰嶇疆Props
+ */
+ private Properties getProperties() {
+ // 璇诲彇閰嶇疆鏂囦欢
+ Resource resource = new ClassPathResource("/templates/code.properties");
+ Properties props = new Properties();
+ try {
+ props = PropertiesLoaderUtils.loadProperties(resource);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return props;
+ }
+
+ /**
+ * 鐢熸垚鍒伴」鐩腑
+ *
+ * @return outputDir
+ */
+ public String getOutputDir() {
+ return (Func.isBlank(packageDir) ? System.getProperty("user.dir") + "/blade-ops/blade-develop" : packageDir) + "/src/main/java";
+ }
+
+
+ /**
+ * 鐢熸垚鍒癢eb椤圭洰涓�
+ *
+ * @return outputDir
+ */
+ public String getOutputWebDir() {
+ return (Func.isBlank(packageWebDir) ? System.getProperty("user.dir") : packageWebDir) + "/src";
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/java/org/springblade/develop/support/BladeTemplateEngine.java b/Source/BladeX-Tool/blade-starter-develop/src/main/java/org/springblade/develop/support/BladeTemplateEngine.java
new file mode 100644
index 0000000..6477b60
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/java/org/springblade/develop/support/BladeTemplateEngine.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.develop.support;
+
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import com.baomidou.mybatisplus.core.toolkit.StringPool;
+import com.baomidou.mybatisplus.generator.config.OutputFile;
+import com.baomidou.mybatisplus.generator.config.builder.CustomFile;
+import com.baomidou.mybatisplus.generator.config.po.TableInfo;
+import com.baomidou.mybatisplus.generator.engine.BeetlTemplateEngine;
+import lombok.AllArgsConstructor;
+import org.springblade.core.tool.utils.StringUtil;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 浠g爜妯$増鐢熸垚瀹炵幇绫�
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+public class BladeTemplateEngine extends BeetlTemplateEngine {
+
+ private String outputDir;
+ private String outputWebDir;
+
+ @Override
+ protected void outputCustomFile(List<CustomFile> customFiles, TableInfo tableInfo, Map<String, Object> objectMap) {
+ String packageName = String.valueOf(objectMap.get("packageName"));
+ String serviceCode = String.valueOf(objectMap.get("serviceCode"));
+ String modelCode = String.valueOf(objectMap.get("modelCode"));
+ String entityName = String.valueOf(objectMap.get("modelClass"));
+ String entityNameLower = entityName.toLowerCase();
+
+ customFiles.forEach(customFile -> {
+ String key = customFile.getFileName();
+ String value = customFile.getTemplatePath();
+ String outputPath = getPathInfo(OutputFile.parent);
+ objectMap.put("entityKey", entityNameLower);
+ if (StringUtil.equals(key, "menu.sql")) {
+ objectMap.put("menuId", IdWorker.getId());
+ objectMap.put("addMenuId", IdWorker.getId());
+ objectMap.put("editMenuId", IdWorker.getId());
+ objectMap.put("removeMenuId", IdWorker.getId());
+ objectMap.put("viewMenuId", IdWorker.getId());
+ outputPath = outputDir + StringPool.SLASH + "sql" + StringPool.SLASH + entityNameLower + ".menu.sql";
+ }
+ if (StringUtil.equals(key, "entityVO.java")) {
+ outputPath = outputDir + StringPool.SLASH + packageName.replace(StringPool.DOT, StringPool.SLASH) + StringPool.SLASH + "vo" + StringPool.SLASH + entityName + "VO" + StringPool.DOT_JAVA;
+ }
+
+ if (StringUtil.equals(key, "entityDTO.java")) {
+ outputPath = outputDir + StringPool.SLASH + packageName.replace(StringPool.DOT, StringPool.SLASH) + StringPool.SLASH + "dto" + StringPool.SLASH + entityName + "DTO" + StringPool.DOT_JAVA;
+ }
+
+ if (StringUtil.equals(key, "wrapper.java")) {
+ outputPath = outputDir + StringPool.SLASH + packageName.replace(StringPool.DOT, StringPool.SLASH) + StringPool.SLASH + "wrapper" + StringPool.SLASH + entityName + "Wrapper" + StringPool.DOT_JAVA;
+ }
+
+ if (StringUtil.equals(key, "feign.java")) {
+ outputPath = outputDir + StringPool.SLASH + packageName.replace(StringPool.DOT, StringPool.SLASH) + StringPool.SLASH + "feign" + StringPool.SLASH + "I" + entityName + "Client" + StringPool.DOT_JAVA;
+ }
+
+ if (StringUtil.equals(key, "feignclient.java")) {
+ outputPath = outputDir + StringPool.SLASH + packageName.replace(StringPool.DOT, StringPool.SLASH) + StringPool.SLASH + "feign" + StringPool.SLASH + entityName + "Client" + StringPool.DOT_JAVA;
+ }
+
+ if (StringUtil.equals(key, "action.js")) {
+ outputPath = outputWebDir + StringPool.SLASH + "actions" + StringPool.SLASH + entityNameLower + ".js";
+ }
+
+ if (StringUtil.equals(key, "model.js")) {
+ outputPath = outputWebDir + StringPool.SLASH + "models" + StringPool.SLASH + entityNameLower + ".js";
+ }
+
+ if (StringUtil.equals(key, "service.js")) {
+ outputPath = outputWebDir + StringPool.SLASH + "services" + StringPool.SLASH + entityNameLower + ".js";
+ }
+
+ if (StringUtil.equals(key, "list.js")) {
+ outputPath = outputWebDir + StringPool.SLASH + "pages" + StringPool.SLASH + StringUtil.firstCharToUpper(modelCode) + StringPool.SLASH + entityName + StringPool.SLASH + entityName + ".js";
+ }
+
+ if (StringUtil.equals(key, "add.js")) {
+ outputPath = outputWebDir + StringPool.SLASH + "pages" + StringPool.SLASH + StringUtil.firstCharToUpper(modelCode) + StringPool.SLASH + entityName + StringPool.SLASH + entityName + "Add.js";
+ }
+
+ if (StringUtil.equals(key, "edit.js")) {
+ outputPath = outputWebDir + StringPool.SLASH + "pages" + StringPool.SLASH + StringUtil.firstCharToUpper(modelCode) + StringPool.SLASH + entityName + StringPool.SLASH + entityName + "Edit.js";
+ }
+
+ if (StringUtil.equals(key, "view.js")) {
+ outputPath = outputWebDir + StringPool.SLASH + "pages" + StringPool.SLASH + StringUtil.firstCharToUpper(modelCode) + StringPool.SLASH + entityName + StringPool.SLASH + entityName + "View.js";
+ }
+
+ if (StringUtil.equals(key, "api.js")) {
+ outputPath = outputWebDir + StringPool.SLASH + "api" + StringPool.SLASH + serviceCode + StringPool.SLASH + modelCode + ".js";
+ }
+
+ if (StringUtil.equals(key, "const.js")) {
+ outputPath = outputWebDir + StringPool.SLASH + "const" + StringPool.SLASH + serviceCode + StringPool.SLASH + modelCode + ".js";
+ }
+
+ if (StringUtil.equals(key, "crud.vue")) {
+ outputPath = outputWebDir + StringPool.SLASH + "views" + StringPool.SLASH + serviceCode + StringPool.SLASH + modelCode + ".vue";
+ }
+
+ if (StringUtil.equals(key, "sub.vue")) {
+ outputPath = outputWebDir + StringPool.SLASH + "views" + StringPool.SLASH + serviceCode + StringPool.SLASH + modelCode + "Sub.vue";
+ }
+
+ if (StringUtil.equals(key, "data.ts")) {
+ outputPath = outputWebDir + StringPool.SLASH + "api" + StringPool.SLASH + serviceCode + StringPool.SLASH + modelCode + ".ts";
+ }
+
+ if (StringUtil.equals(key, "data.data.ts")) {
+
+ outputPath = outputWebDir + StringPool.SLASH + "views" + StringPool.SLASH + serviceCode + StringPool.SLASH + modelCode + StringPool.SLASH + modelCode + ".data.ts";
+ }
+
+ if (StringUtil.equals(key, "index.vue")) {
+ outputPath = outputWebDir + StringPool.SLASH + "views" + StringPool.SLASH + serviceCode + StringPool.SLASH + modelCode + StringPool.SLASH + "index.vue";
+ }
+
+ if (StringUtil.equals(key, "Modal.vue")) {
+ outputPath = outputWebDir + StringPool.SLASH + "views" + StringPool.SLASH + serviceCode + StringPool.SLASH + modelCode + StringPool.SLASH + entityName + "Modal.vue";
+ }
+
+ if (StringUtil.equals(key, "lemonSub.vue")) {
+ outputPath = outputWebDir + StringPool.SLASH + "views" + StringPool.SLASH + serviceCode + StringPool.SLASH + modelCode + StringPool.SLASH + entityName + "Sub.vue";
+ }
+ outputFile(new File(String.valueOf(outputPath)), objectMap, value, Boolean.TRUE);
+ });
+ }
+
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/beetl.properties b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/beetl.properties
new file mode 100644
index 0000000..0d2e6d1
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/beetl.properties
@@ -0,0 +1,10 @@
+#榛樿閰嶇疆
+ENGINE = org.beetl.core.engine.FastRuntimeEngine
+
+#寮�濮嬬粨鏉熷崰浣嶇
+DELIMITER_PLACEHOLDER_START = ${
+DELIMITER_PLACEHOLDER_END = }
+
+#寮�濮嬬粨鏉熸爣绛�
+DELIMITER_STATEMENT_START = #
+DELIMITER_STATEMENT_END = null
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/controller.java.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/controller.java.btl
new file mode 100644
index 0000000..de11f16
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/controller.java.btl
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package ${package.Controller};
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import lombok.AllArgsConstructor;
+import javax.validation.Valid;
+
+import org.springblade.core.secure.BladeUser;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.Func;
+import org.springframework.web.bind.annotation.*;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import ${packageName!}.entity.${modelClass!}Entity;
+import ${packageName!}.vo.${modelClass!}VO;
+#if(hasWrapper) {
+import ${packageName!}.wrapper.${modelClass!}Wrapper;
+#}
+import ${packageName!}.service.I${modelClass!}Service;
+#if(isNotEmpty(superControllerClassPackage)){
+import ${superControllerClassPackage!};
+#}
+#if(templateType=="tree"){
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import org.springblade.core.tool.constant.BladeConstant;
+import java.util.List;
+#}
+#if(templateType=="tree"&&!hasWrapper){
+import ${packageName!}.wrapper.${modelClass!}Wrapper;
+#}
+
+/**
+ * ${codeName!} 鎺у埗鍣�
+ *
+ * @author ${author!}
+ * @since ${date!}
+ */
+@RestController
+@AllArgsConstructor
+#if(hasServiceName) {
+@RequestMapping("${serviceName!}/${modelCode!}")
+#}else{
+@RequestMapping("/${modelCode!}")
+#}
+@Api(value = "${codeName!}", tags = "${codeName!}鎺ュ彛")
+#if(isNotEmpty(superControllerClass)){
+public class ${modelClass!}Controller extends ${superControllerClass!} {
+#}
+#else{
+public class ${modelClass!}Controller {
+#}
+
+ private final I${modelClass!}Service ${modelCode!}Service;
+
+#if(hasWrapper){
+ /**
+ * ${codeName!} 璇︽儏
+ */
+ @GetMapping("/detail")
+ @ApiOperationSupport(order = 1)
+ @ApiOperation(value = "璇︽儏", notes = "浼犲叆${modelCode!}")
+ public R<${modelClass!}VO> detail(${modelClass!}Entity ${modelCode!}) {
+ ${modelClass!}Entity detail = ${modelCode!}Service.getOne(Condition.getQueryWrapper(${modelCode!}));
+ return R.data(${modelClass!}Wrapper.build().entityVO(detail));
+ }
+#if(templateType=="tree"){
+ /**
+ * ${codeName!} 鏍戝垪琛�
+ */
+ @GetMapping("/list")
+ @ApiOperationSupport(order = 2)
+ @ApiOperation(value = "鍒嗛〉", notes = "浼犲叆notice")
+ public R<List<${modelClass!}VO>> list(${modelClass!}Entity ${modelCode!}, BladeUser bladeUser) {
+ QueryWrapper<${modelClass!}Entity> queryWrapper = Condition.getQueryWrapper(${modelCode!});
+ List<${modelClass!}Entity> list = ${modelCode!}Service.list((!bladeUser.getTenantId().equals(BladeConstant.ADMIN_TENANT_ID)) ? queryWrapper.lambda().eq(${modelClass!}Entity::getTenantId, bladeUser.getTenantId()) : queryWrapper);
+ return R.data(${modelClass!}Wrapper.build().treeNodeVO(list));
+ }
+#}else{
+ /**
+ * ${codeName!} 鍒嗛〉
+ */
+ @GetMapping("/list")
+ @ApiOperationSupport(order = 2)
+ @ApiOperation(value = "鍒嗛〉", notes = "浼犲叆${modelCode!}")
+ public R<IPage<${modelClass!}VO>> list(${modelClass!}Entity ${modelCode!}, Query query) {
+ IPage<${modelClass!}Entity> pages = ${modelCode!}Service.page(Condition.getPage(query), Condition.getQueryWrapper(${modelCode!}));
+ return R.data(${modelClass!}Wrapper.build().pageVO(pages));
+ }
+#}
+#}else{
+ /**
+ * ${codeName!} 璇︽儏
+ */
+ @GetMapping("/detail")
+ @ApiOperationSupport(order = 1)
+ @ApiOperation(value = "璇︽儏", notes = "浼犲叆${modelCode!}")
+ public R<${modelClass!}Entity> detail(${modelClass!}Entity ${modelCode!}) {
+ ${modelClass!}Entity detail = ${modelCode!}Service.getOne(Condition.getQueryWrapper(${modelCode!}));
+ return R.data(detail);
+ }
+#if(templateType=="tree"){
+ /**
+ * ${codeName!} 鏍戝垪琛�
+ */
+ @GetMapping("/list")
+ @ApiOperationSupport(order = 2)
+ @ApiOperation(value = "鍒嗛〉", notes = "浼犲叆notice")
+ public R<List<${modelClass!}VO>> list(${modelClass!}Entity ${modelCode!}, BladeUser bladeUser) {
+ QueryWrapper<${modelClass!}Entity> queryWrapper = Condition.getQueryWrapper(${modelCode!});
+ List<${modelClass!}Entity> list = ${modelCode!}Service.list((!bladeUser.getTenantId().equals(BladeConstant.ADMIN_TENANT_ID)) ? queryWrapper.lambda().eq(${modelClass!}Entity::getTenantId, bladeUser.getTenantId()) : queryWrapper);
+ return R.data(${modelClass!}Wrapper.build().treeNodeVO(list));
+ }
+#}else{
+ /**
+ * ${codeName!} 鍒嗛〉
+ */
+ @GetMapping("/list")
+ @ApiOperationSupport(order = 2)
+ @ApiOperation(value = "鍒嗛〉", notes = "浼犲叆${modelCode!}")
+ public R<IPage<${modelClass!}Entity>> list(${modelClass!}Entity ${modelCode!}, Query query) {
+ IPage<${modelClass!}Entity> pages = ${modelCode!}Service.page(Condition.getPage(query), Condition.getQueryWrapper(${modelCode!}));
+ return R.data(pages);
+ }
+#}
+#}
+
+ /**
+ * ${codeName!} 鑷畾涔夊垎椤�
+ */
+ @GetMapping("/page")
+ @ApiOperationSupport(order = 3)
+ @ApiOperation(value = "鍒嗛〉", notes = "浼犲叆${modelCode!}")
+ public R<IPage<${modelClass!}VO>> page(${modelClass!}VO ${modelCode!}, Query query) {
+ IPage<${modelClass!}VO> pages = ${modelCode!}Service.select${modelClass!}Page(Condition.getPage(query), ${modelCode!});
+ return R.data(pages);
+ }
+
+ /**
+ * ${codeName!} 鏂板
+ */
+ @PostMapping("/save")
+ @ApiOperationSupport(order = 4)
+ @ApiOperation(value = "鏂板", notes = "浼犲叆${modelCode!}")
+ public R save(@Valid @RequestBody ${modelClass!}Entity ${modelCode!}) {
+ return R.status(${modelCode!}Service.save(${modelCode!}));
+ }
+
+ /**
+ * ${codeName!} 淇敼
+ */
+ @PostMapping("/update")
+ @ApiOperationSupport(order = 5)
+ @ApiOperation(value = "淇敼", notes = "浼犲叆${modelCode!}")
+ public R update(@Valid @RequestBody ${modelClass!}Entity ${modelCode!}) {
+ return R.status(${modelCode!}Service.updateById(${modelCode!}));
+ }
+
+ /**
+ * ${codeName!} 鏂板鎴栦慨鏀�
+ */
+ @PostMapping("/submit")
+ @ApiOperationSupport(order = 6)
+ @ApiOperation(value = "鏂板鎴栦慨鏀�", notes = "浼犲叆${modelCode!}")
+ public R submit(@Valid @RequestBody ${modelClass!}Entity ${modelCode!}) {
+ return R.status(${modelCode!}Service.saveOrUpdate(${modelCode!}));
+ }
+
+#if(isNotEmpty(superEntityClass)){
+ /**
+ * ${codeName!} 鍒犻櫎
+ */
+ @PostMapping("/remove")
+ @ApiOperationSupport(order = 7)
+ @ApiOperation(value = "閫昏緫鍒犻櫎", notes = "浼犲叆ids")
+ public R remove(@ApiParam(value = "涓婚敭闆嗗悎", required = true) @RequestParam String ids) {
+ return R.status(${modelCode!}Service.deleteLogic(Func.toLongList(ids)));
+ }
+#}else{
+ /**
+ * ${codeName!} 鍒犻櫎
+ */
+ @PostMapping("/remove")
+ @ApiOperationSupport(order = 7)
+ @ApiOperation(value = "鍒犻櫎", notes = "浼犲叆ids")
+ public R remove(@ApiParam(value = "涓婚敭闆嗗悎", required = true) @RequestParam String ids) {
+ return R.status(${modelCode!}Service.removeByIds(Func.toLongList(ids)));
+ }
+#}
+
+#if(templateType=="tree"){
+ /**
+ * ${codeName!} 鏍戝舰缁撴瀯
+ */
+ @GetMapping("/tree")
+ @ApiOperationSupport(order = 8)
+ @ApiOperation(value = "鏍戝舰缁撴瀯", notes = "鏍戝舰缁撴瀯")
+ public R<List<${modelClass!}VO>> tree(String tenantId, BladeUser bladeUser) {
+ List<${modelClass!}VO> tree = ${modelCode!}Service.tree(Func.toStrWithEmpty(tenantId, bladeUser.getTenantId()));
+ return R.data(tree);
+ }
+#}
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/entity.java.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/entity.java.btl
new file mode 100644
index 0000000..c0ef4d3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/entity.java.btl
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package ${package.Entity!};
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+#for(x in propertyImport){
+import ${x!};
+#}
+#if(isNotEmpty(superEntityClass)){
+import lombok.EqualsAndHashCode;
+import org.springblade.core.tenant.mp.TenantEntity;
+#}else{
+import java.io.Serializable;
+#}
+
+/**
+ * ${codeName!} 瀹炰綋绫�
+ *
+ * @author ${author!}
+ * @since ${date!}
+ */
+@Data
+@TableName("${model.modelTable!}")
+@ApiModel(value = "${modelClass!}瀵硅薄", description = "${codeName!}")
+#if(isNotEmpty(superEntityClass)){
+@EqualsAndHashCode(callSuper = true)
+public class ${modelClass!}Entity extends TenantEntity {
+#}else{
+public class ${modelClass!}Entity implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+#}
+
+ #for(x in prototypes) {
+ #if(isNotEmpty(superEntityClass)&&x.propertyName!="id"&&x.propertyName!="createUser"&&x.propertyName!="createDept"&&x.propertyName!="createTime"&&x.propertyName!="updateUser"&&x.propertyName!="updateTime"&&x.propertyName!="status"&&x.propertyName!="isDeleted"&&x.propertyName!="tenantId"){
+ /**
+ * ${x.comment!}
+ */
+ @ApiModelProperty(value = "${x.comment!}")
+ private ${x.propertyType!} ${x.propertyName!};
+ #}
+ #}
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/entityDTO.java.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/entityDTO.java.btl
new file mode 100644
index 0000000..1a08fe6
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/entityDTO.java.btl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package ${strutil.replace(package.Entity,"entity","dto")};
+
+import ${packageName!}.entity.${modelClass!}Entity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * ${codeName!} 鏁版嵁浼犺緭瀵硅薄瀹炰綋绫�
+ *
+ * @author ${author!}
+ * @since ${date!}
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ${modelClass!}DTO extends ${modelClass!}Entity {
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/entityVO.java.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/entityVO.java.btl
new file mode 100644
index 0000000..4f66dcd
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/entityVO.java.btl
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package ${strutil.replace(package.Entity,"entity","vo")};
+
+import ${packageName!}.entity.${modelClass!}Entity;
+import org.springblade.core.tool.node.INode;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+#if(templateType=="tree"){
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+
+import java.util.ArrayList;
+import java.util.List;
+#}
+
+/**
+ * ${codeName!} 瑙嗗浘瀹炰綋绫�
+ *
+ * @author ${author!}
+ * @since ${date!}
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+#if(templateType=="tree"){
+public class ${modelClass!}VO extends ${modelClass!}Entity implements INode<${modelClass!}VO> {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 涓婚敭ID
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ private Long id;
+
+ /**
+ * 鐖惰妭鐐笽D
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ private Long parentId;
+
+ /**
+ * 鐖惰妭鐐瑰悕绉�
+ */
+ private String parentName;
+
+ /**
+ * 瀛愬瓩鑺傜偣
+ */
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ private List<${modelClass!}VO> children;
+
+ /**
+ * 鏄惁鏈夊瓙瀛欒妭鐐�
+ */
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ private Boolean hasChildren;
+
+ @Override
+ public List<${modelClass!}VO> getChildren() {
+ if (this.children == null) {
+ this.children = new ArrayList<>();
+ }
+ return this.children;
+ }
+
+}
+#}else{
+public class ${modelClass!}VO extends ${modelClass!}Entity {
+ private static final long serialVersionUID = 1L;
+
+}
+#}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/feign.java.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/feign.java.btl
new file mode 100644
index 0000000..145ff5a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/feign.java.btl
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package ${strutil.replace(package.Entity,"entity","feign")};
+
+import org.springblade.core.mp.support.BladePage;
+import ${packageName!}.entity.${modelClass!}Entity;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+/**
+ * ${codeName!} Feign鎺ュ彛绫�
+ *
+ * @author ${author!}
+ * @since ${date!}
+ */
+@FeignClient(
+ value = "${serviceName!}"
+)
+public interface I${modelClass!}Client {
+
+ String API_PREFIX = "/client";
+ String TOP = API_PREFIX + "/top";
+
+ /**
+ * 鑾峰彇${codeName!}鍒楄〃
+ *
+ * @param current 椤靛彿
+ * @param size 椤垫暟
+ * @return BladePage
+ */
+ @GetMapping(TOP)
+ BladePage<${modelClass!}Entity> top(@RequestParam("current") Integer current, @RequestParam("size") Integer size);
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/feignclient.java.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/feignclient.java.btl
new file mode 100644
index 0000000..593d12d
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/feignclient.java.btl
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package ${strutil.replace(package.Entity,"entity","feign")};
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.AllArgsConstructor;
+import org.springblade.core.mp.support.BladePage;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import ${packageName!}.entity.${modelClass!}Entity;
+import ${packageName!}.service.I${modelClass!}Service;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import springfox.documentation.annotations.ApiIgnore;
+
+/**
+ * ${codeName!} Feign瀹炵幇绫�
+ *
+ * @author ${author!}
+ * @since ${date!}
+ */
+@ApiIgnore()
+@RestController
+@AllArgsConstructor
+public class ${modelClass!}Client implements I${modelClass!}Client {
+
+ private final I${modelClass!}Service ${modelCode!}Service;
+
+ @Override
+ @GetMapping(TOP)
+ public BladePage<${modelClass!}Entity> top(Integer current, Integer size) {
+ Query query = new Query();
+ query.setCurrent(current);
+ query.setSize(size);
+ IPage<${modelClass!}Entity> page = service.page(Condition.getPage(query));
+ return BladePage.of(page);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/mapper.java.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/mapper.java.btl
new file mode 100644
index 0000000..b237c02
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/mapper.java.btl
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package ${package.Mapper!};
+
+import ${packageName!}.entity.${modelClass!}Entity;
+import ${packageName!}.vo.${modelClass!}VO;
+import ${superMapperClassPackage!};
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import java.util.List;
+
+/**
+ * ${codeName!} Mapper 鎺ュ彛
+ *
+ * @author ${author!}
+ * @since ${date!}
+ */
+public interface ${modelClass!}Mapper extends ${superMapperClass!}<${modelClass!}Entity> {
+
+ /**
+ * 鑷畾涔夊垎椤�
+ *
+ * @param page
+ * @param ${modelCode!}
+ * @return
+ */
+ List<${modelClass!}VO> select${modelClass!}Page(IPage page, ${modelClass!}VO ${modelCode!});
+
+#if(templateType=="tree"){
+ /**
+ * 鑾峰彇鏍戝舰鑺傜偣
+ *
+ * @param tenantId
+ * @return
+ */
+ List<${modelClass!}VO> tree(String tenantId);
+#}
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/mapper.xml.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/mapper.xml.btl
new file mode 100644
index 0000000..b742f43
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/mapper.xml.btl
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="${package.Mapper!}.${modelClass!}Mapper">
+
+#if(enableCache){
+ <!-- 寮�鍚簩绾х紦瀛� -->
+ <cache type="org.mybatis.caches.ehcache.LoggingEhcache"/>
+#}
+ <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+ <resultMap id="${modelCode!}ResultMap" type="${package.Entity!}.${modelClass!}Entity">
+ #for(x in prototypes) {
+ <result column="${x.jdbcName!}" property="${x.propertyName!}"/>
+ #}
+ </resultMap>
+
+#if(templateType=="tree"){
+ <resultMap id="treeNodeResultMap" type="org.springblade.core.tool.node.TreeNode">
+ <id column="id" property="id"/>
+ <result column="parent_id" property="parentId"/>
+ <result column="title" property="title"/>
+ <result column="value" property="value"/>
+ <result column="key" property="key"/>
+ </resultMap>
+#}
+
+ <select id="select${modelClass!}Page" resultMap="${modelCode!}ResultMap">
+ select * from ${model.modelTable} where is_deleted = 0
+ </select>
+
+#if(templateType=="tree"){
+ <select id="tree" resultMap="treeNodeResultMap">
+ select ${treeId!} as id, ${treePid!} as parent_id, ${treeName!} as title, ${treeId!} as 'value', ${treeId!} as 'key' from ${model.modelTable!} where is_deleted = 0
+ <if test="_parameter!=null">
+ and tenant_id = \#{_parameter}
+ </if>
+ </select>
+#}
+
+</mapper>
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/service.java.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/service.java.btl
new file mode 100644
index 0000000..7ad025b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/service.java.btl
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package ${package.Service!};
+
+import ${packageName!}.entity.${modelClass!}Entity;
+import ${packageName!}.vo.${modelClass!}VO;
+import ${superServiceClassPackage!};
+import com.baomidou.mybatisplus.core.metadata.IPage;
+#if(templateType=="tree"){
+import java.util.List;
+#}
+
+/**
+ * ${codeName!} 鏈嶅姟绫�
+ *
+ * @author ${author!}
+ * @since ${date!}
+ */
+public interface I${modelClass!}Service extends ${superServiceClass!}<${modelClass!}Entity> {
+
+ /**
+ * 鑷畾涔夊垎椤�
+ *
+ * @param page
+ * @param ${modelCode!}
+ * @return
+ */
+ IPage<${modelClass!}VO> select${modelClass!}Page(IPage<${modelClass!}VO> page, ${modelClass!}VO ${modelCode!});
+
+#if(templateType=="tree"){
+ /**
+ * 鏍戝舰缁撴瀯
+ *
+ * @param tenantId
+ * @return
+ */
+ List<${modelClass!}VO> tree(String tenantId);
+#}
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/serviceImpl.java.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/serviceImpl.java.btl
new file mode 100644
index 0000000..776b1fd
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/serviceImpl.java.btl
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package ${package.ServiceImpl!};
+
+import ${packageName!}.entity.${modelClass!}Entity;
+import ${packageName!}.vo.${modelClass!}VO;
+import ${packageName!}.mapper.${model.modelClass!}Mapper;
+import ${packageName!}.service.I${model.modelClass!}Service;
+import ${superServiceImplClassPackage!};
+import org.springframework.stereotype.Service;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+#if(templateType=="tree"){
+import org.springblade.core.tool.node.ForestNodeMerger;
+import java.util.List;
+#}
+
+/**
+ * ${codeName!} 鏈嶅姟瀹炵幇绫�
+ *
+ * @author ${author!}
+ * @since ${date!}
+ */
+@Service
+public class ${modelClass!}ServiceImpl extends ${superServiceImplClass!}<${modelClass!}Mapper, ${modelClass!}Entity> implements I${model.modelClass!}Service {
+
+ @Override
+ public IPage<${modelClass!}VO> select${modelClass!}Page(IPage<${modelClass!}VO> page, ${modelClass!}VO ${modelCode!}) {
+ return page.setRecords(baseMapper.select${modelClass!}Page(page, ${modelCode!}));
+ }
+
+#if(templateType=="tree"){
+ @Override
+ public List<${modelClass!}VO> tree(String tenantId) {
+ return ForestNodeMerger.merge(baseMapper.tree(tenantId));
+ }
+#}
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/wrapper.java.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/wrapper.java.btl
new file mode 100644
index 0000000..0904ee6
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/api/wrapper.java.btl
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package ${strutil.replace(package.Entity,"entity","wrapper")};
+
+import org.springblade.core.mp.support.BaseEntityWrapper;
+import org.springblade.core.tool.utils.BeanUtil;
+import ${packageName!}.entity.${modelClass!}Entity;
+import ${packageName!}.vo.${modelClass!}VO;
+import java.util.Objects;
+#if(templateType=="tree"){
+import org.springblade.core.tool.node.ForestNodeMerger;
+import java.util.List;
+import java.util.stream.Collectors;
+#}
+
+/**
+ * ${codeName!} 鍖呰绫�,杩斿洖瑙嗗浘灞傛墍闇�鐨勫瓧娈�
+ *
+ * @author ${author!}
+ * @since ${date!}
+ */
+public class ${modelClass!}Wrapper extends BaseEntityWrapper<${modelClass!}Entity, ${modelClass!}VO> {
+
+ public static ${modelClass!}Wrapper build() {
+ return new ${modelClass!}Wrapper();
+ }
+
+ @Override
+ public ${modelClass!}VO entityVO(${modelClass!}Entity ${modelCode!}) {
+ ${modelClass!}VO ${modelCode!}VO = Objects.requireNonNull(BeanUtil.copy(${modelCode!}, ${modelClass!}VO.class));
+
+ //User createUser = UserCache.getUser(${modelCode!}.getCreateUser());
+ //User updateUser = UserCache.getUser(${modelCode!}.getUpdateUser());
+ //${modelCode!}VO.setCreateUserName(createUser.getName());
+ //${modelCode!}VO.setUpdateUserName(updateUser.getName());
+
+ return ${modelCode!}VO;
+ }
+
+#if(templateType=="tree"){
+ public List<${modelClass!}VO> treeNodeVO(List<${modelClass!}Entity> list) {
+ List<${modelClass!}VO> collect = list.stream().map(${modelCode!} -> BeanUtil.copy(${modelCode!}, ${modelClass!}VO.class)).collect(Collectors.toList());
+ return ForestNodeMerger.merge(collect);
+ }
+#}
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/code.properties b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/code.properties
new file mode 100644
index 0000000..50420cb
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/code.properties
@@ -0,0 +1,5 @@
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+spring.datasource.url=jdbc:mysql://localhost:3306/bladex?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
+spring.datasource.username=root
+spring.datasource.password=root
+author=BladeX
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/crud/api.js.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/crud/api.js.btl
new file mode 100644
index 0000000..fa12324
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/crud/api.js.btl
@@ -0,0 +1,50 @@
+import request from '@/router/axios';
+
+export const getList = (current, size, params) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/list',
+ method: 'get',
+ params: {
+ ...params,
+ current,
+ size,
+ }
+ })
+}
+
+export const getDetail = (id) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/detail',
+ method: 'get',
+ params: {
+ id
+ }
+ })
+}
+
+export const remove = (ids) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/remove',
+ method: 'post',
+ params: {
+ ids,
+ }
+ })
+}
+
+export const add = (row) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/submit',
+ method: 'post',
+ data: row
+ })
+}
+
+export const update = (row) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/submit',
+ method: 'post',
+ data: row
+ })
+}
+
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/crud/const.js.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/crud/const.js.btl
new file mode 100644
index 0000000..c1afc96
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/crud/const.js.btl
@@ -0,0 +1,31 @@
+export default {
+ size: 'small',
+ expand: false,
+ index: true,
+ border: true,
+ selection: true,
+ column: [
+#for(x in prototypes) {
+ {
+ label: "${x.comment!}",
+ prop: "${x.propertyName!}",
+#if(strutil.contain(x.componentType,"date")||strutil.contain(x.componentType,"time")){
+ format: "yyyy-MM-dd hh:mm:ss",
+ valueFormat: "yyyy-MM-dd hh:mm:ss",
+#}
+#if(x.isForm==0){
+ display: false,
+#}
+#if(x.isRow==1){
+ span: 24,
+#}
+#if(x.isList==0){
+ hide: true,
+#}
+#if(x.isQuery==1){
+ search: true,
+#}
+ },
+#}
+ ]
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/crud/crud.vue.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/crud/crud.vue.btl
new file mode 100644
index 0000000..f4464f3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/crud/crud.vue.btl
@@ -0,0 +1,347 @@
+<template>
+ <basic-container>
+ <div class="avue-crud">
+ <el-row :hidden="!search" style="padding:5px">
+ <!-- 鏌ヨ妯″潡 -->
+ <el-form :inline="true" :size="option.size" :model="query">
+ <template>
+#for(x in prototypes) {
+ #if(x.isQuery==1){
+ <el-form-item label="瀛楁">
+ <el-input v-model="query.${x.propertyName!}" placeholder="璇疯緭鍏�${x.comment!}"></el-input>
+ </el-form-item>
+ #}
+#}
+ </template>
+ <!-- 鏌ヨ鎸夐挳 -->
+ <el-form-item>
+ <el-button type="primary" icon="el-icon-search" @click="searchChange">鎼滅储</el-button>
+ <el-button icon="el-icon-delete" @click="searchReset()">娓呯┖</el-button>
+ </el-form-item>
+ </el-form>
+ </el-row>
+ <el-row>
+ <div class="avue-crud__menu">
+ <!-- 澶撮儴宸︿晶鎸夐挳妯″潡 -->
+ <div class="avue-crud__left">
+ <el-button :size="option.size" type="primary" icon="el-icon-plus" @click="handleAdd">鏂板</el-button>
+ <el-button :size="option.size" type="danger" icon="el-icon-delete" @click="handleDelete" plain>鍒犻櫎
+ </el-button>
+ </div>
+ <!-- 澶撮儴鍙充晶鎸夐挳妯″潡 -->
+ <div class="avue-crud__right">
+ <el-button :size="option.size" icon="el-icon-refresh" @click="searchChange" circle></el-button>
+ <el-button :size="option.size" icon="el-icon-search" @click="searchHide" circle></el-button>
+ </div>
+ </div>
+ </el-row>
+ <el-row>
+ <!-- 鍒楄〃妯″潡 -->
+ <el-table ref="table" v-loading="loading" :size="option.size" @selection-change="selectionChange" :data="data"
+ style="width: 100%"
+ :border="option.border">
+ <el-table-column type="selection" v-if="option.selection" width="55" align="center"></el-table-column>
+ <el-table-column type="expand" v-if="option.expand" align="center"></el-table-column>
+ <el-table-column v-if="option.index" label="\#" type="index" width="50" align="center">
+ </el-table-column>
+ <template v-for="(item,index) in option.column">
+ <!-- table瀛楁 -->
+ <el-table-column v-if="item.hide!==true"
+ :prop="item.prop"
+ :label="item.label"
+ :width="item.width"
+ :key="index">
+ </el-table-column>
+ </template>
+ <!-- 鎿嶄綔鏍忔ā鍧� -->
+ <el-table-column prop="menu" label="鎿嶄綔" :width="180" align="center">
+ <template slot-scope="{row}">
+ <el-button :size="option.size" type="text" icon="el-icon-view" @click="handleView(row)">鏌ョ湅</el-button>
+ <el-button :size="option.size" type="text" icon="el-icon-edit" @click="handleEdit(row)">缂栬緫</el-button>
+ <el-button :size="option.size" type="text" icon="el-icon-delete" @click="rowDel(row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-row>
+ <el-row>
+ <!-- 鍒嗛〉妯″潡 -->
+ <el-pagination
+ align="right" background
+ @size-change="sizeChange"
+ @current-change="currentChange"
+ :current-page="page.currentPage"
+ :page-sizes="[10, 20, 30, 40, 50, 100]"
+ :page-size="page.pageSize"
+ layout="total, sizes, prev, pager, next, jumper"
+ :total="page.total">
+ </el-pagination>
+ </el-row>
+ <!-- 琛ㄥ崟妯″潡 -->
+ <el-dialog :title="title" :visible.sync="box" width="50%" :before-close="beforeClose" append-to-body>
+ <el-form :disabled="view" :size="option.size" ref="form" :model="form" label-width="80px">
+ <!-- 琛ㄥ崟瀛楁 -->
+#for(x in prototypes) {
+ #if(x.isForm!=0){
+ #if(x.componentType=="input"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-input v-model="form.${x.propertyName!}" placeholder="璇疯緭鍏�${x.comment!}"/>
+ </el-form-item>
+ #}else if(x.componentType=="textarea"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-input type="textarea" :rows="5" v-model="form.${x.propertyName!}" placeholder="璇疯緭鍏�${x.comment!}"/>
+ </el-form-item>
+ #}else if(x.componentType=="select"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-select v-model="form.${x.propertyName!}" clearable placeholder="璇烽�夋嫨${x.comment!}">
+ <el-option
+ v-for="item in ${x.propertyName!}Data"
+ :key="item.dictKey"
+ :label="item.dictValue"
+ :value="item.dictKey">
+ </el-option>
+ </el-select>
+ </el-form-item>
+ #}else if(x.componentType=="tree"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-select v-model="form.${x.propertyName!}" clearable placeholder="璇烽�夋嫨${x.comment!}">
+ <el-option
+ v-for="item in ${x.propertyName!}Data"
+ :key="item.dictKey"
+ :label="item.dictValue"
+ :value="item.dictKey">
+ </el-option>
+ </el-select>
+ </el-form-item>
+ #}else if(x.componentType=="radio"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-radio-group v-model="form.${x.propertyName!}">
+ <el-radio v-for="(item,index) in ${x.propertyName!}Data" :key="index" :label="item.dictKey">
+ {{item.dictValue}}
+ </el-radio>
+ </el-radio-group>
+ </el-form-item>
+ #}else if(x.componentType=="checkbox"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-checkbox-group v-model="form.${x.propertyName!}">
+ <el-checkbox v-for="(item,index) in ${x.propertyName!}Data" :label="item.dictValue" :key="index">{{item.dictValue}}</el-checkbox>
+ </el-checkbox-group>
+ </el-form-item>
+ #}else if(x.componentType=="switch"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-switch v-model="form.${x.propertyName!}" </el-switch>
+ </el-form-item>
+ #}else if(x.componentType=="date"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-date-picker v-model="form.${x.propertyName!}" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="璇烽�夋嫨${x.comment!}"></el-date-picker>
+ </el-form-item>
+ #}
+ #}
+#}
+ </el-form>
+ <!-- 琛ㄥ崟鎸夐挳 -->
+ <span v-if="!view" slot="footer" class="dialog-footer">
+ <el-button type="primary" icon="el-icon-circle-check" :size="option.size" @click="handleSubmit">鎻� 浜�</el-button>
+ <el-button icon="el-icon-circle-close" :size="option.size" @click="box = false">鍙� 娑�</el-button>
+ </span>
+ </el-dialog>
+ </div>
+ </basic-container>
+</template>
+
+<script>
+ import {getList, getDetail, add, update, remove} from "@/api/${serviceCode!}/${modelCode!}";
+ import option from "@/const/${serviceCode!}/${modelCode!}";
+ import {mapGetters} from "vuex";
+ import {getDictionary} from '@/api/system/dict'
+
+export default {
+ data() {
+ return {
+ // 寮规鏍囬
+ title: '',
+ // 鏄惁灞曠ず寮规
+ box: false,
+ // 鏄惁鏄剧ず鏌ヨ
+ search: true,
+ // 鍔犺浇涓�
+ loading: true,
+ // 鏄惁涓烘煡鐪嬫ā寮�
+ view: false,
+ // 鏌ヨ淇℃伅
+ query: {},
+ // 鍒嗛〉淇℃伅
+ page: {
+ currentPage: 1,
+ pageSize: 10,
+ total: 40
+ },
+ // 琛ㄥ崟鏁版嵁
+ form: {},
+ // 閫夋嫨琛�
+ selectionList: [],
+ // 琛ㄥ崟閰嶇疆
+ option: option,
+ // 琛ㄥ崟鍒楄〃
+ data: [],
+#for(x in prototypes) {
+ #if(isNotEmpty(x.dictCode)){
+ // ${x.comment!}瀛楀吀鏁版嵁
+ ${x.propertyName!}Data: [],
+ #}
+#}
+ }
+ },
+ mounted() {
+ this.init();
+ this.onLoad(this.page);
+ },
+ computed: {
+ ...mapGetters(["permission"]),
+ ids() {
+ let ids = [];
+ this.selectionList.forEach(ele => {
+ ids.push(ele.id);
+ });
+ return ids.join(",");
+ }
+ },
+ methods: {
+ init() {
+#for(x in prototypes) {
+ #if(isNotEmpty(x.dictCode)){
+ getDictionary({code: '${x.dictCode!}'}).then(res => {
+ this.${x.propertyName!}Data = res.data.data;
+ });
+ #}
+#}
+ },
+ searchHide() {
+ this.search = !this.search;
+ },
+ searchChange() {
+ this.onLoad(this.page);
+ },
+ searchReset() {
+ this.query = {};
+ this.page.currentPage = 1;
+ this.onLoad(this.page);
+ },
+ handleSubmit() {
+ if (!this.form.id) {
+ add(this.form).then(() => {
+ this.box = false;
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ });
+ } else {
+ update(this.form).then(() => {
+ this.box = false;
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ })
+ }
+ },
+ handleAdd() {
+ this.title = '鏂板'
+ this.form = {}
+ this.box = true
+ },
+ handleEdit(row) {
+ this.title = '缂栬緫'
+ this.box = true
+ getDetail(row.id).then(res => {
+ this.form = res.data.data;
+ });
+ },
+ handleView(row) {
+ this.title = '鏌ョ湅'
+ this.view = true;
+ this.box = true;
+ getDetail(row.id).then(res => {
+ this.form = res.data.data;
+ });
+ },
+ handleDelete() {
+ if (this.selectionList.length === 0) {
+ this.$message.warning("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+ return;
+ }
+ this.$confirm("纭畾灏嗛�夋嫨鏁版嵁鍒犻櫎?", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return remove(this.ids);
+ })
+ .then(() => {
+ this.selectionClear();
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ });
+ },
+ rowDel(row) {
+ this.$confirm("纭畾灏嗛�夋嫨鏁版嵁鍒犻櫎?", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return remove(row.id);
+ })
+ .then(() => {
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ });
+ },
+ beforeClose(done) {
+ done()
+ this.form = {};
+ this.view = false;
+ },
+ selectionChange(list) {
+ this.selectionList = list;
+ },
+ selectionClear() {
+ this.selectionList = [];
+ this.$refs.table.clearSelection();
+ },
+ currentChange(currentPage) {
+ this.page.currentPage = currentPage;
+ this.onLoad(this.page);
+ },
+ sizeChange(pageSize) {
+ this.page.pageSize = pageSize;
+ this.onLoad(this.page);
+ },
+ onLoad(page, params = {}) {
+ this.loading = true;
+ getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
+ const data = res.data.data;
+ this.page.total = data.total;
+ this.data = data.records;
+ this.loading = false;
+ this.selectionClear();
+ });
+ }
+ }
+};
+</script>
+
+<style lang="scss" scoped>
+.el-pagination {
+ margin-top: 20px;
+}
+</style>
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/sub/api.js.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/sub/api.js.btl
new file mode 100644
index 0000000..fa12324
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/sub/api.js.btl
@@ -0,0 +1,50 @@
+import request from '@/router/axios';
+
+export const getList = (current, size, params) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/list',
+ method: 'get',
+ params: {
+ ...params,
+ current,
+ size,
+ }
+ })
+}
+
+export const getDetail = (id) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/detail',
+ method: 'get',
+ params: {
+ id
+ }
+ })
+}
+
+export const remove = (ids) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/remove',
+ method: 'post',
+ params: {
+ ids,
+ }
+ })
+}
+
+export const add = (row) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/submit',
+ method: 'post',
+ data: row
+ })
+}
+
+export const update = (row) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/submit',
+ method: 'post',
+ data: row
+ })
+}
+
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/sub/const.js.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/sub/const.js.btl
new file mode 100644
index 0000000..c1afc96
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/sub/const.js.btl
@@ -0,0 +1,31 @@
+export default {
+ size: 'small',
+ expand: false,
+ index: true,
+ border: true,
+ selection: true,
+ column: [
+#for(x in prototypes) {
+ {
+ label: "${x.comment!}",
+ prop: "${x.propertyName!}",
+#if(strutil.contain(x.componentType,"date")||strutil.contain(x.componentType,"time")){
+ format: "yyyy-MM-dd hh:mm:ss",
+ valueFormat: "yyyy-MM-dd hh:mm:ss",
+#}
+#if(x.isForm==0){
+ display: false,
+#}
+#if(x.isRow==1){
+ span: 24,
+#}
+#if(x.isList==0){
+ hide: true,
+#}
+#if(x.isQuery==1){
+ search: true,
+#}
+ },
+#}
+ ]
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/sub/crud.vue.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/sub/crud.vue.btl
new file mode 100644
index 0000000..881e8ba
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/sub/crud.vue.btl
@@ -0,0 +1,375 @@
+<template>
+ <basic-container>
+ <div class="avue-crud">
+ <el-row :hidden="!search" style="padding:5px">
+ <!-- 鏌ヨ妯″潡 -->
+ <el-form :inline="true" :size="option.size" :model="query">
+ <template>
+#for(x in prototypes) {
+ #if(x.isQuery==1){
+ <el-form-item label="瀛楁">
+ <el-input v-model="query.${x.propertyName!}" placeholder="璇疯緭鍏�${x.comment!}"></el-input>
+ </el-form-item>
+ #}
+#}
+ </template>
+ <!-- 鏌ヨ鎸夐挳 -->
+ <el-form-item>
+ <el-button type="primary" icon="el-icon-search" @click="searchChange">鎼滅储</el-button>
+ <el-button icon="el-icon-delete" @click="searchReset()">娓呯┖</el-button>
+ </el-form-item>
+ </el-form>
+ </el-row>
+ <el-row>
+ <div class="avue-crud__menu">
+ <!-- 澶撮儴宸︿晶鎸夐挳妯″潡 -->
+ <div class="avue-crud__left">
+ <el-button :size="option.size" type="primary" icon="el-icon-plus" @click="handleAdd">鏂板</el-button>
+ <el-button :size="option.size" type="danger" icon="el-icon-delete" @click="handleDelete" plain>鍒犻櫎
+ </el-button>
+ </div>
+ <!-- 澶撮儴鍙充晶鎸夐挳妯″潡 -->
+ <div class="avue-crud__right">
+ <el-button :size="option.size" icon="el-icon-refresh" @click="searchChange" circle></el-button>
+ <el-button :size="option.size" icon="el-icon-search" @click="searchHide" circle></el-button>
+ </div>
+ </div>
+ </el-row>
+ <el-row>
+ <!-- 鍒楄〃妯″潡 -->
+ <el-table ref="table" v-loading="loading" :size="option.size" @selection-change="selectionChange" :data="data"
+ style="width: 100%"
+ :border="option.border">
+ <el-table-column type="selection" v-if="option.selection" width="55" align="center"></el-table-column>
+ <el-table-column type="expand" v-if="option.expand" align="center"></el-table-column>
+ <el-table-column v-if="option.index" label="\#" type="index" width="50" align="center">
+ </el-table-column>
+ <template v-for="(item,index) in option.column">
+ <!-- table瀛楁 -->
+ <el-table-column v-if="item.hide!==true"
+ :prop="item.prop"
+ :label="item.label"
+ :width="item.width"
+ :key="index">
+ </el-table-column>
+ </template>
+ <!-- 鎿嶄綔鏍忔ā鍧� -->
+ <el-table-column prop="menu" label="鎿嶄綔" :width="300" align="center">
+ <template slot-scope="{row}">
+ <el-button :size="option.size" type="text" icon="el-icon-view" @click="handleView(row)">鏌ョ湅</el-button>
+ <el-button :size="option.size" type="text" icon="el-icon-edit" @click="handleEdit(row)">缂栬緫</el-button>
+ <el-button :size="option.size" type="text" icon="el-icon-delete" @click="rowDel(row)">鍒犻櫎</el-button>
+ <el-button :size="option.size" type="text" icon="el-icon-setting" @click="handleDrawer(row)">瀛愯〃閰嶇疆</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-row>
+ <el-row>
+ <!-- 鍒嗛〉妯″潡 -->
+ <el-pagination
+ align="right" background
+ @size-change="sizeChange"
+ @current-change="currentChange"
+ :current-page="page.currentPage"
+ :page-sizes="[10, 20, 30, 40, 50, 100]"
+ :page-size="page.pageSize"
+ layout="total, sizes, prev, pager, next, jumper"
+ :total="page.total">
+ </el-pagination>
+ </el-row>
+ <!-- 琛ㄥ崟妯″潡 -->
+ <el-dialog :title="title" :visible.sync="box" width="50%" :before-close="beforeClose" append-to-body>
+ <el-form :disabled="view" :size="option.size" ref="form" :model="form" label-width="80px">
+ <!-- 琛ㄥ崟瀛楁 -->
+#for(x in prototypes) {
+ #if(x.isForm!=0){
+ #if(x.componentType=="input"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-input v-model="form.${x.propertyName!}" placeholder="璇疯緭鍏�${x.comment!}"/>
+ </el-form-item>
+ #}else if(x.componentType=="textarea"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-input type="textarea" :rows="5" v-model="form.${x.propertyName!}" placeholder="璇疯緭鍏�${x.comment!}"/>
+ </el-form-item>
+ #}else if(x.componentType=="select"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-select v-model="form.${x.propertyName!}" clearable placeholder="璇烽�夋嫨${x.comment!}">
+ <el-option
+ v-for="item in ${x.propertyName!}Data"
+ :key="item.dictKey"
+ :label="item.dictValue"
+ :value="item.dictKey">
+ </el-option>
+ </el-select>
+ </el-form-item>
+ #}else if(x.componentType=="tree"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-select v-model="form.${x.propertyName!}" clearable placeholder="璇烽�夋嫨${x.comment!}">
+ <el-option
+ v-for="item in ${x.propertyName!}Data"
+ :key="item.dictKey"
+ :label="item.dictValue"
+ :value="item.dictKey">
+ </el-option>
+ </el-select>
+ </el-form-item>
+ #}else if(x.componentType=="radio"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-radio-group v-model="form.${x.propertyName!}">
+ <el-radio v-for="(item,index) in ${x.propertyName!}Data" :key="index" :label="item.dictKey">
+ {{item.dictValue}}
+ </el-radio>
+ </el-radio-group>
+ </el-form-item>
+ #}else if(x.componentType=="checkbox"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-checkbox-group v-model="form.${x.propertyName!}">
+ <el-checkbox v-for="(item,index) in ${x.propertyName!}Data" :label="item.dictValue" :key="index">{{item.dictValue}}</el-checkbox>
+ </el-checkbox-group>
+ </el-form-item>
+ #}else if(x.componentType=="switch"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-switch v-model="form.${x.propertyName!}" </el-switch>
+ </el-form-item>
+ #}else if(x.componentType=="date"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-date-picker v-model="form.${x.propertyName!}" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="璇烽�夋嫨${x.comment!}"></el-date-picker>
+ </el-form-item>
+ #}
+ #}
+#}
+ </el-form>
+ <!-- 琛ㄥ崟鎸夐挳 -->
+ <span v-if="!view" slot="footer" class="dialog-footer">
+ <el-button type="primary" icon="el-icon-circle-check" :size="option.size" @click="handleSubmit">鎻� 浜�</el-button>
+ <el-button icon="el-icon-circle-close" :size="option.size" @click="box = false">鍙� 娑�</el-button>
+ </span>
+ </el-dialog>
+ <el-drawer
+ title="瀛愯〃鎿嶄綔"
+ append-to-body
+ size="60%"
+ :visible.sync="drawer"
+ :direction="direction"
+ :before-close="handleDrawerClose">
+ <${subModel.modelClass!}Sub :mainId="${modelCode!}Id"></${subModel.modelClass!}Sub>
+ </el-drawer>
+ </div>
+ </basic-container>
+</template>
+
+<script>
+ import {getList, getDetail, add, update, remove} from "@/api/${serviceCode!}/${modelCode!}";
+ import option from "@/const/${serviceCode!}/${modelCode!}";
+ import {mapGetters} from "vuex";
+ import {getDictionary} from '@/api/system/dict'
+ import ${subModel.modelClass!}Sub from "@/views/${serviceCode!}/${subModel.modelCode!}Sub";
+
+export default {
+ components:{
+ ${subModel.modelClass!}Sub
+ },
+ data() {
+ return {
+ // 涓婚敭
+ ${modelCode!}Id: '',
+ // 寮规鏍囬
+ title: '',
+ // 鏄惁灞曠ず寮规
+ box: false,
+ // 鏄惁灞曠ず鎶藉眽
+ drawer: false,
+ // 鎶藉眽鏂瑰悜
+ direction: 'rtl',
+ // 鏄惁鏄剧ず鏌ヨ
+ search: true,
+ // 鍔犺浇涓�
+ loading: true,
+ // 鏄惁涓烘煡鐪嬫ā寮�
+ view: false,
+ // 鏌ヨ淇℃伅
+ query: {},
+ // 鍒嗛〉淇℃伅
+ page: {
+ currentPage: 1,
+ pageSize: 10,
+ total: 40
+ },
+ // 琛ㄥ崟鏁版嵁
+ form: {},
+ // 閫夋嫨琛�
+ selectionList: [],
+ // 琛ㄥ崟閰嶇疆
+ option: option,
+ // 琛ㄥ崟鍒楄〃
+ data: [],
+#for(x in prototypes) {
+ #if(isNotEmpty(x.dictCode)){
+ // ${x.comment!}瀛楀吀鏁版嵁
+ ${x.propertyName!}Data: [],
+ #}
+#}
+ }
+ },
+ mounted() {
+ this.init();
+ this.onLoad(this.page);
+ },
+ computed: {
+ ...mapGetters(["permission"]),
+ ids() {
+ let ids = [];
+ this.selectionList.forEach(ele => {
+ ids.push(ele.id);
+ });
+ return ids.join(",");
+ }
+ },
+ methods: {
+ init() {
+#for(x in prototypes) {
+ #if(isNotEmpty(x.dictCode)){
+ getDictionary({code: '${x.dictCode!}'}).then(res => {
+ this.${x.propertyName!}Data = res.data.data;
+ });
+ #}
+#}
+ },
+ searchHide() {
+ this.search = !this.search;
+ },
+ searchChange() {
+ this.onLoad(this.page);
+ },
+ searchReset() {
+ this.query = {};
+ this.page.currentPage = 1;
+ this.onLoad(this.page);
+ },
+ handleSubmit() {
+ if (!this.form.id) {
+ add(this.form).then(() => {
+ this.box = false;
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ });
+ } else {
+ update(this.form).then(() => {
+ this.box = false;
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ })
+ }
+ },
+ handleAdd() {
+ this.title = '鏂板'
+ this.form = {}
+ this.box = true
+ },
+ handleEdit(row) {
+ this.title = '缂栬緫'
+ this.box = true
+ getDetail(row.id).then(res => {
+ this.form = res.data.data;
+ });
+ },
+ handleView(row) {
+ this.title = '鏌ョ湅'
+ this.view = true;
+ this.box = true;
+ getDetail(row.id).then(res => {
+ this.form = res.data.data;
+ });
+ },
+ handleDrawer(row) {
+ this.${modelCode!}Id = row.id;
+ this.drawer = true;
+ },
+ handleDrawerClose(){
+ this.${modelCode!}Id = '';
+ this.drawer = false;
+ },
+ handleDelete() {
+ if (this.selectionList.length === 0) {
+ this.$message.warning("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+ return;
+ }
+ this.$confirm("纭畾灏嗛�夋嫨鏁版嵁鍒犻櫎?", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return remove(this.ids);
+ })
+ .then(() => {
+ this.selectionClear();
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ });
+ },
+ rowDel(row) {
+ this.$confirm("纭畾灏嗛�夋嫨鏁版嵁鍒犻櫎?", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return remove(row.id);
+ })
+ .then(() => {
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ });
+ },
+ beforeClose(done) {
+ done()
+ this.form = {};
+ this.view = false;
+ },
+ selectionChange(list) {
+ this.selectionList = list;
+ },
+ selectionClear() {
+ this.selectionList = [];
+ this.$refs.table.clearSelection();
+ },
+ currentChange(currentPage) {
+ this.page.currentPage = currentPage;
+ this.onLoad(this.page);
+ },
+ sizeChange(pageSize) {
+ this.page.pageSize = pageSize;
+ this.onLoad(this.page);
+ },
+ onLoad(page, params = {}) {
+ this.loading = true;
+ getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
+ const data = res.data.data;
+ this.page.total = data.total;
+ this.data = data.records;
+ this.loading = false;
+ this.selectionClear();
+ });
+ }
+ }
+};
+</script>
+
+<style lang="scss" scoped>
+.el-pagination {
+ margin-top: 20px;
+}
+</style>
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/sub/sub.vue.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/sub/sub.vue.btl
new file mode 100644
index 0000000..365a93e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/sub/sub.vue.btl
@@ -0,0 +1,358 @@
+<template>
+ <basic-container>
+ <div class="avue-crud">
+ <el-row :hidden="!search" style="padding:5px">
+ <!-- 鏌ヨ妯″潡 -->
+ <el-form :inline="true" :size="option.size" :model="query">
+ <template>
+#for(x in prototypes) {
+ #if(x.isQuery==1){
+ <el-form-item label="瀛楁">
+ <el-input v-model="query.${x.propertyName!}" placeholder="璇疯緭鍏�${x.comment!}"></el-input>
+ </el-form-item>
+ #}
+#}
+ </template>
+ <!-- 鏌ヨ鎸夐挳 -->
+ <el-form-item>
+ <el-button type="primary" icon="el-icon-search" @click="searchChange">鎼滅储</el-button>
+ <el-button icon="el-icon-delete" @click="searchReset()">娓呯┖</el-button>
+ </el-form-item>
+ </el-form>
+ </el-row>
+ <el-row>
+ <div class="avue-crud__menu">
+ <!-- 澶撮儴宸︿晶鎸夐挳妯″潡 -->
+ <div class="avue-crud__left">
+ <el-button :size="option.size" type="primary" icon="el-icon-plus" @click="handleAdd">鏂板</el-button>
+ <el-button :size="option.size" type="danger" icon="el-icon-delete" @click="handleDelete" plain>鍒犻櫎
+ </el-button>
+ </div>
+ <!-- 澶撮儴鍙充晶鎸夐挳妯″潡 -->
+ <div class="avue-crud__right">
+ <el-button :size="option.size" icon="el-icon-refresh" @click="searchChange" circle></el-button>
+ <el-button :size="option.size" icon="el-icon-search" @click="searchHide" circle></el-button>
+ </div>
+ </div>
+ </el-row>
+ <el-row>
+ <!-- 鍒楄〃妯″潡 -->
+ <el-table ref="table" v-loading="loading" :size="option.size" @selection-change="selectionChange" :data="data"
+ style="width: 100%"
+ :border="option.border">
+ <el-table-column type="selection" v-if="option.selection" width="55" align="center"></el-table-column>
+ <el-table-column type="expand" v-if="option.expand" align="center"></el-table-column>
+ <el-table-column v-if="option.index" label="\#" type="index" width="50" align="center">
+ </el-table-column>
+ <template v-for="(item,index) in option.column">
+ <!-- table瀛楁 -->
+ <el-table-column v-if="item.hide!==true"
+ :prop="item.prop"
+ :label="item.label"
+ :width="item.width"
+ :key="index">
+ </el-table-column>
+ </template>
+ <!-- 鎿嶄綔鏍忔ā鍧� -->
+ <el-table-column prop="menu" label="鎿嶄綔" :width="180" align="center">
+ <template slot-scope="{row}">
+ <el-button :size="option.size" type="text" icon="el-icon-view" @click="handleView(row)">鏌ョ湅</el-button>
+ <el-button :size="option.size" type="text" icon="el-icon-edit" @click="handleEdit(row)">缂栬緫</el-button>
+ <el-button :size="option.size" type="text" icon="el-icon-delete" @click="rowDel(row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-row>
+ <el-row>
+ <!-- 鍒嗛〉妯″潡 -->
+ <el-pagination
+ align="right" background
+ @size-change="sizeChange"
+ @current-change="currentChange"
+ :current-page="page.currentPage"
+ :page-sizes="[10, 20, 30, 40, 50, 100]"
+ :page-size="page.pageSize"
+ layout="total, sizes, prev, pager, next, jumper"
+ :total="page.total">
+ </el-pagination>
+ </el-row>
+ <!-- 琛ㄥ崟妯″潡 -->
+ <el-dialog :title="title" :visible.sync="box" width="50%" :before-close="beforeClose" append-to-body>
+ <el-form :disabled="view" :size="option.size" ref="form" :model="form" label-width="80px">
+ <!-- 琛ㄥ崟瀛楁 -->
+#for(x in prototypes) {
+ #if(x.isForm!=0){
+ #if(x.componentType=="input"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-input v-model="form.${x.propertyName!}" placeholder="璇疯緭鍏�${x.comment!}"/>
+ </el-form-item>
+ #}else if(x.componentType=="textarea"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-input type="textarea" :rows="5" v-model="form.${x.propertyName!}" placeholder="璇疯緭鍏�${x.comment!}"/>
+ </el-form-item>
+ #}else if(x.componentType=="select"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-select v-model="form.${x.propertyName!}" clearable placeholder="璇烽�夋嫨${x.comment!}">
+ <el-option
+ v-for="item in ${x.propertyName!}Data"
+ :key="item.dictKey"
+ :label="item.dictValue"
+ :value="item.dictKey">
+ </el-option>
+ </el-select>
+ </el-form-item>
+ #}else if(x.componentType=="tree"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-select v-model="form.${x.propertyName!}" clearable placeholder="璇烽�夋嫨${x.comment!}">
+ <el-option
+ v-for="item in ${x.propertyName!}Data"
+ :key="item.dictKey"
+ :label="item.dictValue"
+ :value="item.dictKey">
+ </el-option>
+ </el-select>
+ </el-form-item>
+ #}else if(x.componentType=="radio"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-radio-group v-model="form.${x.propertyName!}">
+ <el-radio v-for="(item,index) in ${x.propertyName!}Data" :key="index" :label="item.dictKey">
+ {{item.dictValue}}
+ </el-radio>
+ </el-radio-group>
+ </el-form-item>
+ #}else if(x.componentType=="checkbox"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-checkbox-group v-model="form.${x.propertyName!}">
+ <el-checkbox v-for="(item,index) in ${x.propertyName!}Data" :label="item.dictValue" :key="index">{{item.dictValue}}</el-checkbox>
+ </el-checkbox-group>
+ </el-form-item>
+ #}else if(x.componentType=="switch"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-switch v-model="form.${x.propertyName!}" </el-switch>
+ </el-form-item>
+ #}else if(x.componentType=="date"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-date-picker v-model="form.${x.propertyName!}" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="璇烽�夋嫨${x.comment!}"></el-date-picker>
+ </el-form-item>
+ #}
+ #}
+#}
+ </el-form>
+ <!-- 琛ㄥ崟鎸夐挳 -->
+ <span v-if="!view" slot="footer" class="dialog-footer">
+ <el-button type="primary" icon="el-icon-circle-check" :size="option.size" @click="handleSubmit">鎻� 浜�</el-button>
+ <el-button icon="el-icon-circle-close" :size="option.size" @click="box = false">鍙� 娑�</el-button>
+ </span>
+ </el-dialog>
+ </div>
+ </basic-container>
+</template>
+
+<script>
+ import {getList, getDetail, add, update, remove} from "@/api/${serviceCode!}/${modelCode!}";
+ import option from "@/const/${serviceCode!}/${modelCode!}";
+ import {mapGetters} from "vuex";
+ import {getDictionary} from '@/api/system/dict'
+
+export default {
+ props: {
+ mainId: {
+ type: String
+ },
+ },
+ data() {
+ return {
+ // 寮规鏍囬
+ title: '',
+ // 鏄惁灞曠ず寮规
+ box: false,
+ // 鏄惁鏄剧ず鏌ヨ
+ search: true,
+ // 鍔犺浇涓�
+ loading: true,
+ // 鏄惁涓烘煡鐪嬫ā寮�
+ view: false,
+ // 鏌ヨ淇℃伅
+ query: {},
+ // 鍒嗛〉淇℃伅
+ page: {
+ currentPage: 1,
+ pageSize: 10,
+ total: 40
+ },
+ // 琛ㄥ崟鏁版嵁
+ form: {},
+ // 閫夋嫨琛�
+ selectionList: [],
+ // 琛ㄥ崟閰嶇疆
+ option: option,
+ // 琛ㄥ崟鍒楄〃
+ data: [],
+#for(x in prototypes) {
+ #if(isNotEmpty(x.dictCode)){
+ // ${x.comment!}瀛楀吀鏁版嵁
+ ${x.propertyName!}Data: [],
+ #}
+#}
+ }
+ },
+ mounted() {
+ this.init();
+ this.onLoad(this.page, {${subFkIdHump!}: this.mainId});
+ },
+ computed: {
+ ...mapGetters(["permission"]),
+ ids() {
+ let ids = [];
+ this.selectionList.forEach(ele => {
+ ids.push(ele.id);
+ });
+ return ids.join(",");
+ }
+ },
+ watch: {
+ 'mainId'() {
+ this.onLoad(this.page, {${subFkIdHump!}: this.mainId});
+ }
+ },
+ methods: {
+ init() {
+#for(x in prototypes) {
+ #if(isNotEmpty(x.dictCode)){
+ getDictionary({code: '${x.dictCode!}'}).then(res => {
+ this.${x.propertyName!}Data = res.data.data;
+ });
+ #}
+#}
+ },
+ searchHide() {
+ this.search = !this.search;
+ },
+ searchChange() {
+ this.onLoad(this.page, {${subFkIdHump!}: this.mainId});
+ },
+ searchReset() {
+ this.query = {};
+ this.page.currentPage = 1;
+ this.onLoad(this.page, {${subFkIdHump!}: this.mainId});
+ },
+ handleSubmit() {
+ this.form.${subFkIdHump!} = this.mainId;
+ if (!this.form.id) {
+ add(this.form).then(() => {
+ this.box = false;
+ this.onLoad(this.page, {${subFkIdHump!}: this.mainId});
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ });
+ } else {
+ update(this.form).then(() => {
+ this.box = false;
+ this.onLoad(this.page, {${subFkIdHump!}: this.mainId});
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ })
+ }
+ },
+ handleAdd() {
+ this.title = '鏂板'
+ this.form = {}
+ this.box = true
+ },
+ handleEdit(row) {
+ this.title = '缂栬緫'
+ this.box = true
+ getDetail(row.id).then(res => {
+ this.form = res.data.data;
+ });
+ },
+ handleView(row) {
+ this.title = '鏌ョ湅'
+ this.view = true;
+ this.box = true;
+ getDetail(row.id).then(res => {
+ this.form = res.data.data;
+ });
+ },
+ handleDelete() {
+ if (this.selectionList.length === 0) {
+ this.$message.warning("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+ return;
+ }
+ this.$confirm("纭畾灏嗛�夋嫨鏁版嵁鍒犻櫎?", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return remove(this.ids);
+ })
+ .then(() => {
+ this.selectionClear();
+ this.onLoad(this.page, {${subFkIdHump!}: this.mainId});
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ });
+ },
+ rowDel(row) {
+ this.$confirm("纭畾灏嗛�夋嫨鏁版嵁鍒犻櫎?", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return remove(row.id);
+ })
+ .then(() => {
+ this.onLoad(this.page, {${subFkIdHump!}: this.mainId});
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ });
+ },
+ beforeClose(done) {
+ done()
+ this.form = {};
+ this.view = false;
+ },
+ selectionChange(list) {
+ this.selectionList = list;
+ },
+ selectionClear() {
+ this.selectionList = [];
+ this.$refs.table.clearSelection();
+ },
+ currentChange(currentPage) {
+ this.page.currentPage = currentPage;
+ this.onLoad(this.page, {${subFkIdHump!}: this.mainId});
+ },
+ sizeChange(pageSize) {
+ this.page.pageSize = pageSize;
+ this.onLoad(this.page, {${subFkIdHump!}: this.mainId});
+ },
+ onLoad(page, params = {}) {
+ this.loading = true;
+ getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
+ const data = res.data.data;
+ this.page.total = data.total;
+ this.data = data.records;
+ this.loading = false;
+ this.selectionClear();
+ });
+ }
+ }
+};
+</script>
+
+<style lang="scss" scoped>
+.el-pagination {
+ margin-top: 20px;
+}
+</style>
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/tree/api.js.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/tree/api.js.btl
new file mode 100644
index 0000000..ff4186a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/tree/api.js.btl
@@ -0,0 +1,60 @@
+import request from '@/router/axios';
+
+export const getList = (current, size, params) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/list',
+ method: 'get',
+ params: {
+ ...params,
+ current,
+ size,
+ }
+ })
+}
+
+export const getDetail = (id) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/detail',
+ method: 'get',
+ params: {
+ id
+ }
+ })
+}
+
+export const getTree = (tenantId) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/tree',
+ method: 'get',
+ params: {
+ tenantId,
+ }
+ })
+}
+
+export const remove = (ids) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/remove',
+ method: 'post',
+ params: {
+ ids,
+ }
+ })
+}
+
+export const add = (row) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/submit',
+ method: 'post',
+ data: row
+ })
+}
+
+export const update = (row) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/submit',
+ method: 'post',
+ data: row
+ })
+}
+
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/tree/const.js.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/tree/const.js.btl
new file mode 100644
index 0000000..c1afc96
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/tree/const.js.btl
@@ -0,0 +1,31 @@
+export default {
+ size: 'small',
+ expand: false,
+ index: true,
+ border: true,
+ selection: true,
+ column: [
+#for(x in prototypes) {
+ {
+ label: "${x.comment!}",
+ prop: "${x.propertyName!}",
+#if(strutil.contain(x.componentType,"date")||strutil.contain(x.componentType,"time")){
+ format: "yyyy-MM-dd hh:mm:ss",
+ valueFormat: "yyyy-MM-dd hh:mm:ss",
+#}
+#if(x.isForm==0){
+ display: false,
+#}
+#if(x.isRow==1){
+ span: 24,
+#}
+#if(x.isList==0){
+ hide: true,
+#}
+#if(x.isQuery==1){
+ search: true,
+#}
+ },
+#}
+ ]
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/tree/crud.vue.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/tree/crud.vue.btl
new file mode 100644
index 0000000..59ff4b9
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/element/tree/crud.vue.btl
@@ -0,0 +1,360 @@
+<template>
+ <basic-container>
+ <div class="avue-crud">
+ <el-row :hidden="!search" style="padding:5px">
+ <!-- 鏌ヨ妯″潡 -->
+ <el-form :inline="true" :size="option.size" :model="query">
+ <template>
+#for(x in prototypes) {
+ #if(x.isQuery==1){
+ <el-form-item label="瀛楁">
+ <el-input v-model="query.${x.propertyName!}" placeholder="璇疯緭鍏�${x.comment!}"></el-input>
+ </el-form-item>
+ #}
+#}
+ </template>
+ <!-- 鏌ヨ鎸夐挳 -->
+ <el-form-item>
+ <el-button type="primary" icon="el-icon-search" @click="searchChange">鎼滅储</el-button>
+ <el-button icon="el-icon-delete" @click="searchReset()">娓呯┖</el-button>
+ </el-form-item>
+ </el-form>
+ </el-row>
+ <el-row>
+ <div class="avue-crud__menu">
+ <!-- 澶撮儴宸︿晶鎸夐挳妯″潡 -->
+ <div class="avue-crud__left">
+ <el-button :size="option.size" type="primary" icon="el-icon-plus" @click="handleAdd">鏂板</el-button>
+ <el-button :size="option.size" type="danger" icon="el-icon-delete" @click="handleDelete" plain>鍒犻櫎
+ </el-button>
+ </div>
+ <!-- 澶撮儴鍙充晶鎸夐挳妯″潡 -->
+ <div class="avue-crud__right">
+ <el-button :size="option.size" icon="el-icon-refresh" @click="searchChange" circle></el-button>
+ <el-button :size="option.size" icon="el-icon-search" @click="searchHide" circle></el-button>
+ </div>
+ </div>
+ </el-row>
+ <el-row>
+ <!-- 鍒楄〃妯″潡 -->
+ <el-table ref="table" v-loading="loading" :size="option.size" @selection-change="selectionChange" :data="data"
+ row-key="id"
+ :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
+ style="width: 100%"
+ :border="option.border">
+ <el-table-column type="selection" v-if="option.selection" width="55" align="center"></el-table-column>
+ <el-table-column type="expand" v-if="option.expand" align="center"></el-table-column>
+ <el-table-column v-if="option.index" label="\#" type="index" width="50" align="center">
+ </el-table-column>
+ <template v-for="(item,index) in option.column">
+ <!-- table瀛楁 -->
+ <el-table-column v-if="item.hide!==true"
+ :prop="item.prop"
+ :label="item.label"
+ :width="item.width"
+ :key="index">
+ </el-table-column>
+ </template>
+ <!-- 鎿嶄綔鏍忔ā鍧� -->
+ <el-table-column prop="menu" label="鎿嶄綔" :width="180" align="center">
+ <template slot-scope="{row}">
+ <el-button :size="option.size" type="text" icon="el-icon-view" @click="handleView(row)">鏌ョ湅</el-button>
+ <el-button :size="option.size" type="text" icon="el-icon-edit" @click="handleEdit(row)">缂栬緫</el-button>
+ <el-button :size="option.size" type="text" icon="el-icon-delete" @click="rowDel(row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-row>
+ <!-- 琛ㄥ崟妯″潡 -->
+ <el-dialog :title="title" :visible.sync="box" width="50%" :before-close="beforeClose" append-to-body>
+ <el-form :disabled="view" :size="option.size" ref="form" :model="form" label-width="80px">
+ <!-- 琛ㄥ崟瀛楁 -->
+#for(x in prototypes) {
+ #if(x.isForm!=0){
+ #if(x.componentType=="input"){
+ <el-form-item label="${x.comment!}" prop="title">
+ <el-input v-model="form.${x.propertyName!}" placeholder="璇疯緭鍏�${x.comment!}"/>
+ </el-form-item>
+ #}else if(x.componentType=="textarea"){
+ <el-form-item label="${x.comment!}" prop="title">
+ <el-input type="textarea" :rows="5" v-model="form.${x.propertyName!}" placeholder="璇疯緭鍏�${x.comment!}"/>
+ </el-form-item>
+ #}else if(x.componentType=="select"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-select v-model="form.${x.propertyName!}" clearable placeholder="璇烽�夋嫨${x.comment!}">
+ <el-option
+ v-for="item in ${x.propertyName!}Data"
+ :key="item.dictKey"
+ :label="item.dictValue"
+ :value="item.dictKey">
+ </el-option>
+ </el-select>
+ </el-form-item>
+ #}else if(templateType=="tree"&&x.propertyName==treePidHump){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-tree
+ :data="treeData"
+ v-model="form.${treePidHump!}"
+ placeholder="璇烽�夋嫨${x.comment!}"
+ :props="defaultProps"
+ @node-click="handleNodeClick">
+ </el-tree>
+ </el-form-item>
+ #}else if(x.componentType=="tree"&&x.propertyName!=treePidHump){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-select v-model="form.${x.propertyName!}" clearable placeholder="璇烽�夋嫨${x.comment!}">
+ <el-option
+ v-for="item in ${x.propertyName!}Data"
+ :key="item.id"
+ :label="item.${treeName}"
+ :value="item.id">
+ </el-option>
+ </el-select>
+ </el-form-item>
+ #}else if(x.componentType=="radio"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-radio-group v-model="form.${x.propertyName!}">
+ <el-radio v-for="(item,index) in ${x.propertyName!}Data" :key="index" :label="item.dictKey">
+ {{item.dictValue}}
+ </el-radio>
+ </el-radio-group>
+ </el-form-item>
+ #}else if(x.componentType=="checkbox"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-checkbox-group v-model="form.${x.propertyName!}">
+ <el-checkbox v-for="(item,index) in ${x.propertyName!}Data" :label="item.dictValue" :key="index">{{item.dictValue}}</el-checkbox>
+ </el-checkbox-group>
+ </el-form-item>
+ #}else if(x.componentType=="switch"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-switch v-model="form.${x.propertyName!}" </el-switch>
+ </el-form-item>
+ #}else if(x.componentType=="date"){
+ <el-form-item label="${x.comment!}" prop="${x.propertyName!}">
+ <el-date-picker v-model="form.${x.propertyName!}" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="璇烽�夋嫨${x.comment!}"></el-date-picker>
+ </el-form-item>
+ #}
+ #}
+#}
+ </el-form>
+ <!-- 琛ㄥ崟鎸夐挳 -->
+ <span v-if="!view" slot="footer" class="dialog-footer">
+ <el-button type="primary" icon="el-icon-circle-check" :size="option.size" @click="handleSubmit">鎻� 浜�</el-button>
+ <el-button icon="el-icon-circle-close" :size="option.size" @click="box = false">鍙� 娑�</el-button>
+ </span>
+ </el-dialog>
+ </div>
+ </basic-container>
+</template>
+
+<script>
+ import {getList, getDetail, getTree, add, update, remove} from "@/api/${serviceCode!}/${modelCode!}";
+ import option from "@/const/${serviceCode!}/${modelCode!}";
+ import {getDictionary} from '@/api/system/dict';
+ import {mapGetters} from "vuex";
+ import {validatenull} from "@/util/validate";
+
+export default {
+ data() {
+ return {
+ // 寮规鏍囬
+ title: '',
+ // 鏄惁灞曠ず寮规
+ box: false,
+ // 鏄惁鏄剧ず鏌ヨ
+ search: true,
+ // 鍔犺浇涓�
+ loading: true,
+ // 鏄惁涓烘煡鐪嬫ā寮�
+ view: false,
+ // 鏌ヨ淇℃伅
+ query: {},
+ // 鍒嗛〉淇℃伅
+ page: {
+ currentPage: 1,
+ pageSize: 10,
+ total: 40
+ },
+ // 鏍戝瀷榛樿閰嶇疆
+ defaultProps: {
+ children: 'children',
+ label: '${treeName}'
+ },
+ // 琛ㄥ崟鏁版嵁
+ form: {},
+ // 閫夋嫨琛�
+ selectionList: [],
+ // 琛ㄥ崟閰嶇疆
+ option: option,
+ // 琛ㄥ崟鍒楄〃
+ data: [],
+ // 鐖惰妭鐐瑰垪琛�
+ treeData: [],
+#for(x in prototypes) {
+ #if(isNotEmpty(x.dictCode)){
+ // ${x.comment!}瀛楀吀鏁版嵁
+ ${x.propertyName!}Data: [],
+ #}
+#}
+ }
+ },
+ mounted() {
+ this.init();
+ this.onLoad(this.page);
+ },
+ computed: {
+ ...mapGetters(["permission"]),
+ ids() {
+ let ids = [];
+ this.selectionList.forEach(ele => {
+ ids.push(ele.id);
+ });
+ return ids.join(",");
+ }
+ },
+ methods: {
+ init() {
+#for(x in prototypes) {
+ #if(isNotEmpty(x.dictCode)){
+ getDictionary({code: '${x.dictCode!}'}).then(res => {
+ this.${x.propertyName!}Data = res.data.data;
+ });
+ #}
+#}
+ },
+ handleNodeClick(data) {
+ this.form.${treePidHump!} = data.${treeIdHump!};
+ },
+ searchHide() {
+ this.search = !this.search;
+ },
+ searchChange() {
+ this.onLoad(this.page);
+ },
+ searchReset() {
+ this.query = {};
+ this.page.currentPage = 1;
+ this.onLoad(this.page);
+ },
+ handleSubmit() {
+ if (validatenull(this.form.${treePidHump!})) {
+ this.form.${treePidHump!} = 0;
+ }
+ if (!this.form.id) {
+ add(this.form).then(() => {
+ this.box = false;
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ });
+ } else {
+ update(this.form).then(() => {
+ this.box = false;
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ })
+ }
+ },
+ handleAdd() {
+ this.title = '鏂板'
+ this.form = {}
+ this.box = true
+ },
+ handleEdit(row) {
+ this.title = '缂栬緫'
+ this.box = true
+ getDetail(row.id).then(res => {
+ this.form = res.data.data;
+ });
+ },
+ handleView(row) {
+ this.title = '鏌ョ湅'
+ this.view = true;
+ this.box = true;
+ getDetail(row.id).then(res => {
+ this.form = res.data.data;
+ });
+ },
+ handleDelete() {
+ if (this.selectionList.length === 0) {
+ this.$message.warning("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+ return;
+ }
+ this.$confirm("纭畾灏嗛�夋嫨鏁版嵁鍒犻櫎?", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return remove(this.ids);
+ })
+ .then(() => {
+ this.selectionClear();
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ });
+ },
+ rowDel(row) {
+ this.$confirm("纭畾灏嗛�夋嫨鏁版嵁鍒犻櫎?", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return remove(row.id);
+ })
+ .then(() => {
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ });
+ },
+ beforeClose(done) {
+ done()
+ this.form = {};
+ this.view = false;
+ },
+ selectionChange(list) {
+ this.selectionList = list;
+ },
+ selectionClear() {
+ this.selectionList = [];
+ this.$refs.table.clearSelection();
+ },
+ currentChange(currentPage) {
+ this.page.currentPage = currentPage;
+ this.onLoad(this.page);
+ },
+ sizeChange(pageSize) {
+ this.page.pageSize = pageSize;
+ this.onLoad(this.page);
+ },
+ onLoad(page, params = {}) {
+ this.loading = true;
+ getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
+ this.data = res.data.data;
+ this.loading = false;
+ getTree().then(res => {
+ this.treeData = res.data.data;
+ });
+ });
+ }
+ }
+};
+</script>
+
+<style lang="scss" scoped>
+.el-pagination {
+ margin-top: 20px;
+}
+</style>
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/crud/Modal.vue.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/crud/Modal.vue.btl
new file mode 100644
index 0000000..84af854
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/crud/Modal.vue.btl
@@ -0,0 +1,90 @@
+<template>
+ <BasicModal
+ v-bind="$attrs"
+ @register="registerModal"
+ :title="getTitle"
+ @ok="handleSubmit"
+ :showOkBtn="!isDetail"
+ :width="900"
+ >
+ <div v-show="isDetail">
+ <Description size="middle" @register="registerDetail" :column="2"/>
+ </div>
+ <div v-show="!isDetail">
+ <BasicForm @register="registerForm" />
+ </div>
+ </BasicModal>
+</template>
+<script lang="ts" setup>
+ import { ref, computed, unref } from 'vue';
+ import { BasicModal, useModalInner } from '/@/components/Modal';
+ import { BasicForm, useForm } from '/@/components/Form/index';
+ import { formSchema, detailSchema } from './${modelCode!}.data';
+ import { getDetail, submitObj } from '/@/api/${serviceCode!}/${modelCode!}';
+ import { Description, useDescription } from '/@/components/Description/index';
+
+ const emit = defineEmits(['success']);
+ const isDetail = ref(true);
+ const isUpdate = ref(true);
+ const rowId = ref('');
+ //璇︽儏
+ const [registerDetail, { setDescProps }] = useDescription({
+ schema: detailSchema,
+ });
+
+ const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
+ labelWidth: 100,
+ schemas: formSchema,
+ showActionButtonGroup: false,
+ baseColProps: {
+ span: 12,
+ },
+ actionColOptions: {
+ span: 23,
+ },
+ });
+
+ const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
+ resetFields();
+ setModalProps({ confirmLoading: false });
+ isUpdate.value = !!data?.isUpdate;
+ isDetail.value = !!data?.isDetail;
+ if (unref(isDetail)) {
+ const detail = await getDetail({ id: data.record.id });
+ setDescProps({
+ data: detail,
+ });
+ } else {
+ if (unref(isUpdate)) {
+ rowId.value = data.record.id;
+ const detailData = await getDetail({ id: data.record.id });
+ setFieldsValue({
+ ...detailData,
+ });
+ }
+ }
+ });
+
+ const getTitle = computed(() => {
+ if (unref(isDetail)) {
+ return '鏌ョ湅';
+ } else {
+ return !unref(isUpdate) ? '鏂板' : '缂栬緫';
+ }
+ });
+
+ async function handleSubmit() {
+ try {
+ const values = await validate();
+ setModalProps({ confirmLoading: true });
+ if (unref(isUpdate)) {
+ values.id = rowId.value;
+ }
+ await submitObj(values);
+ closeModal();
+ emit('success', { isUpdate: unref(isUpdate), values: { ...values, id: rowId.value } });
+ } finally {
+ setModalProps({ confirmLoading: false });
+ }
+ }
+</script>
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/crud/data.data.ts.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/crud/data.data.ts.btl
new file mode 100644
index 0000000..53bab06
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/crud/data.data.ts.btl
@@ -0,0 +1,102 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { DescItem } from '/@/components/Description/index';
+import { getDictList } from '/@/api/system/system';
+
+
+export const columns: BasicColumn[] = [
+ #for(x in prototypes) {
+ #if(x.isList==1){
+ {
+ title: "${x.comment!}",
+ dataIndex: "${x.propertyName!}",
+ },
+ #}
+ #}
+ ];
+
+export const searchFormSchema: FormSchema[] = [
+ #for(x in prototypes) {
+ #if(x.isQuery==1){
+ {
+ field: "${x.propertyName!}",
+ label: "${x.comment!}",
+ #if(x.componentType=="input"){
+ component: 'Input',
+ #}else if(x.componentType=="textarea"){
+ component: 'InputTextArea',
+ #}else if(x.componentType=="select"){
+ component: 'ApiSelect',
+ #}else if(x.componentType=="tree"){
+ component: 'ApiTreeSelect',
+ #}else if(x.componentType=="radio"){
+ component: 'RadioGroup',
+ #}else if(x.componentType=="checkbox"){
+ component: 'Checkbox',
+ #}else if(x.componentType=="switch"){
+ component: 'Switch',
+ #}else if(x.componentType=="date"){
+ component: 'DatePicker',
+ #}
+ #if(x.componentType=="select"&&x.dictCode!=null){
+ componentProps: {
+ api: getDictList,
+ params: { code: '${x.dictCode!}' },
+ labelField: 'dictValue',
+ valueField: 'dictKey',
+ },
+ #}
+ },
+ #}
+ #}
+];
+
+export const formSchema: FormSchema[] = [
+ #for(x in prototypes) {
+ #if(x.isForm!=0){
+ {
+ field: "${x.propertyName!}",
+ label: "${x.comment!}",
+ #if(x.componentType=="input"){
+ component: 'Input',
+ #}else if(x.componentType=="textarea"){
+ component: 'InputTextArea',
+ #}else if(x.componentType=="select"){
+ component: 'ApiSelect',
+ #}else if(x.componentType=="tree"){
+ component: 'ApiTreeSelect',
+ #}else if(x.componentType=="radio"){
+ component: 'RadioGroup',
+ #}else if(x.componentType=="checkbox"){
+ component: 'Checkbox',
+ #}else if(x.componentType=="switch"){
+ component: 'Switch',
+ #}else if(x.componentType=="date"){
+ component: 'DatePicker',
+ #}
+ #if(x.componentType=="select"&&x.dictCode!=null){
+ componentProps: {
+ api: getDictList,
+ params: { code: '${x.dictCode!}' },
+ labelField: 'dictValue',
+ valueField: 'dictKey',
+ },
+ #}
+ #if(x.isRequired==1){
+ required: true,
+ #}
+ },
+ #}
+ #}
+];
+
+export const detailSchema: DescItem[] = [
+ #for(x in prototypes) {
+ #if(x.isForm!=0){
+ {
+ field: "${x.propertyName!}",
+ label: "${x.comment!}",
+ },
+ #}
+ #}
+];
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/crud/data.ts.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/crud/data.ts.btl
new file mode 100644
index 0000000..0bc1437
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/crud/data.ts.btl
@@ -0,0 +1,30 @@
+import { defHttp } from '/@/utils/http/axios';
+
+enum Api {
+ List = '/${serviceName!}/${modelCode!}/list',
+ Submit = '/${serviceName!}/${modelCode!}/submit',
+ Detail = '/${serviceName!}/${modelCode!}/detail',
+ Remove = '/${serviceName!}/${modelCode!}/remove',
+}
+
+//鍒楄〃
+export function getList(params?: object) {
+ return defHttp.get({ url: Api.List, params: params },
+ { joinParamsToUrl: true, joinTime: false });
+}
+
+//鎻愪氦
+export function submitObj(params?: object) {
+ return defHttp.post({ url: Api.Submit, params: params });
+}
+
+//璇︽儏
+export function getDetail(params?: object) {
+ return defHttp.get({ url: Api.Detail, params });
+}
+
+//鍒犻櫎
+export function remove(params?: object) {
+ return defHttp.post({ url: Api.Remove, params }, { joinParamsToUrl: true });
+}
+
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/crud/index.vue.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/crud/index.vue.btl
new file mode 100644
index 0000000..2f495b6
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/crud/index.vue.btl
@@ -0,0 +1,124 @@
+<template>
+ <div>
+ <BasicTable @register="registerTable">
+ <template \#toolbar>
+ <a-button type="primary" v-auth="'${modelCode!}_add'" @click="handleCreate">
+ 鏂板
+ </a-button>
+ </template>
+ <template \#bodyCell="{ column, record }">
+ #for(x in prototypes) {
+ #if(isNotEmpty(x.dictCode)){
+ <template v-if="column.dataIndex === '${x.propertyName!}'">
+ {{ formatDictValue(options['${x.propertyName!}Data'], record.${x.propertyName!}) }}
+ </template>
+ #}
+ #}
+ <template v-if="column.dataIndex === 'action'">
+ <TableAction
+ :actions="[
+ {
+ auth: '${modelCode!}_view',
+ label: '鏌ョ湅',
+ color: 'success',
+ icon: 'clarity:info-standard-line',
+ onClick: handleView.bind(null, record),
+ },
+ {
+ auth: '${modelCode!}_edit',
+ label: '缂栬緫',
+ icon: 'clarity:note-edit-line',
+ onClick: handleEdit.bind(null, record),
+ },
+ {
+ auth: '${modelCode!}_delete',
+ label: '鍒犻櫎',
+ icon: 'ant-design:delete-outlined',
+ color: 'error',
+ popConfirm: {
+ title: '鏄惁纭鍒犻櫎',
+ confirm: handleDelete.bind(null, record),
+ },
+ },
+ ]"
+ />
+ </template>
+ </template>
+ </BasicTable>
+ <${modelClass!}Modal @register="registerModal" @success="handleSuccess" />
+ </div>
+</template>
+<script lang="ts" setup name="${modelClass!}">
+ import { ref } from 'vue';
+ import ${modelClass!}Modal from './${modelClass!}Modal.vue';
+ import { BasicTable, useTable, TableAction } from '/@/components/Table';
+ import { getList, remove } from '/@/api/${serviceCode!}/${modelCode!}';
+ import { useModal } from '/@/components/Modal';
+ import { columns, searchFormSchema } from './${modelCode!}.data';
+ import { useMessage } from '/@/hooks/web/useMessage';
+ import { formatDictValue } from '/@/utils';
+ import { getDictList } from '/@/api/system/system';
+ //鍒濆鍖栧瓧鍏�
+ let options = ref({});
+ async function fetch() {
+ #for(x in prototypes) {
+ #if(isNotEmpty(x.dictCode)){
+ options.value['${x.propertyName!}Data'] = await getDictList({ code: '${x.dictCode!}' });
+ #}
+ #}
+ }
+ fetch();
+
+ const { createMessage } = useMessage();
+ const [registerModal, { openModal }] = useModal();
+ const [registerTable, { reload }] = useTable({
+ api: getList,
+ rowKey: 'id',
+ columns,
+ formConfig: {
+ labelWidth: 120,
+ schemas: searchFormSchema,
+ baseColProps: { xl: 12, xxl: 8 },
+ },
+ useSearchForm: true,
+ actionColumn: {
+ width: 250,
+ title: '鎿嶄綔',
+ dataIndex: 'action',
+ },
+ });
+
+ function handleCreate() {
+ openModal(true, {
+ isDetail: false,
+ isUpdate: false,
+ });
+ }
+
+ function handleView(record: Recordable) {
+ openModal(true, {
+ record,
+ isUpdate: false,
+ isDetail: true,
+ });
+ }
+
+ function handleEdit(record: Recordable) {
+ openModal(true, {
+ record,
+ isDetail: false,
+ isUpdate: true,
+ });
+ }
+ async function handleDelete(record: Recordable) {
+ await remove({ ids: record.id });
+ createMessage.success('鎿嶄綔鎴愬姛');
+ reload();
+ }
+
+ function handleSuccess() {
+ //鎿嶄綔鎴愬姛鎻愮ず
+ createMessage.success('鎿嶄綔鎴愬姛');
+ reload();
+ }
+</script>
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/Modal.vue.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/Modal.vue.btl
new file mode 100644
index 0000000..84af854
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/Modal.vue.btl
@@ -0,0 +1,90 @@
+<template>
+ <BasicModal
+ v-bind="$attrs"
+ @register="registerModal"
+ :title="getTitle"
+ @ok="handleSubmit"
+ :showOkBtn="!isDetail"
+ :width="900"
+ >
+ <div v-show="isDetail">
+ <Description size="middle" @register="registerDetail" :column="2"/>
+ </div>
+ <div v-show="!isDetail">
+ <BasicForm @register="registerForm" />
+ </div>
+ </BasicModal>
+</template>
+<script lang="ts" setup>
+ import { ref, computed, unref } from 'vue';
+ import { BasicModal, useModalInner } from '/@/components/Modal';
+ import { BasicForm, useForm } from '/@/components/Form/index';
+ import { formSchema, detailSchema } from './${modelCode!}.data';
+ import { getDetail, submitObj } from '/@/api/${serviceCode!}/${modelCode!}';
+ import { Description, useDescription } from '/@/components/Description/index';
+
+ const emit = defineEmits(['success']);
+ const isDetail = ref(true);
+ const isUpdate = ref(true);
+ const rowId = ref('');
+ //璇︽儏
+ const [registerDetail, { setDescProps }] = useDescription({
+ schema: detailSchema,
+ });
+
+ const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
+ labelWidth: 100,
+ schemas: formSchema,
+ showActionButtonGroup: false,
+ baseColProps: {
+ span: 12,
+ },
+ actionColOptions: {
+ span: 23,
+ },
+ });
+
+ const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
+ resetFields();
+ setModalProps({ confirmLoading: false });
+ isUpdate.value = !!data?.isUpdate;
+ isDetail.value = !!data?.isDetail;
+ if (unref(isDetail)) {
+ const detail = await getDetail({ id: data.record.id });
+ setDescProps({
+ data: detail,
+ });
+ } else {
+ if (unref(isUpdate)) {
+ rowId.value = data.record.id;
+ const detailData = await getDetail({ id: data.record.id });
+ setFieldsValue({
+ ...detailData,
+ });
+ }
+ }
+ });
+
+ const getTitle = computed(() => {
+ if (unref(isDetail)) {
+ return '鏌ョ湅';
+ } else {
+ return !unref(isUpdate) ? '鏂板' : '缂栬緫';
+ }
+ });
+
+ async function handleSubmit() {
+ try {
+ const values = await validate();
+ setModalProps({ confirmLoading: true });
+ if (unref(isUpdate)) {
+ values.id = rowId.value;
+ }
+ await submitObj(values);
+ closeModal();
+ emit('success', { isUpdate: unref(isUpdate), values: { ...values, id: rowId.value } });
+ } finally {
+ setModalProps({ confirmLoading: false });
+ }
+ }
+</script>
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/data.data.ts.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/data.data.ts.btl
new file mode 100644
index 0000000..53bab06
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/data.data.ts.btl
@@ -0,0 +1,102 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { DescItem } from '/@/components/Description/index';
+import { getDictList } from '/@/api/system/system';
+
+
+export const columns: BasicColumn[] = [
+ #for(x in prototypes) {
+ #if(x.isList==1){
+ {
+ title: "${x.comment!}",
+ dataIndex: "${x.propertyName!}",
+ },
+ #}
+ #}
+ ];
+
+export const searchFormSchema: FormSchema[] = [
+ #for(x in prototypes) {
+ #if(x.isQuery==1){
+ {
+ field: "${x.propertyName!}",
+ label: "${x.comment!}",
+ #if(x.componentType=="input"){
+ component: 'Input',
+ #}else if(x.componentType=="textarea"){
+ component: 'InputTextArea',
+ #}else if(x.componentType=="select"){
+ component: 'ApiSelect',
+ #}else if(x.componentType=="tree"){
+ component: 'ApiTreeSelect',
+ #}else if(x.componentType=="radio"){
+ component: 'RadioGroup',
+ #}else if(x.componentType=="checkbox"){
+ component: 'Checkbox',
+ #}else if(x.componentType=="switch"){
+ component: 'Switch',
+ #}else if(x.componentType=="date"){
+ component: 'DatePicker',
+ #}
+ #if(x.componentType=="select"&&x.dictCode!=null){
+ componentProps: {
+ api: getDictList,
+ params: { code: '${x.dictCode!}' },
+ labelField: 'dictValue',
+ valueField: 'dictKey',
+ },
+ #}
+ },
+ #}
+ #}
+];
+
+export const formSchema: FormSchema[] = [
+ #for(x in prototypes) {
+ #if(x.isForm!=0){
+ {
+ field: "${x.propertyName!}",
+ label: "${x.comment!}",
+ #if(x.componentType=="input"){
+ component: 'Input',
+ #}else if(x.componentType=="textarea"){
+ component: 'InputTextArea',
+ #}else if(x.componentType=="select"){
+ component: 'ApiSelect',
+ #}else if(x.componentType=="tree"){
+ component: 'ApiTreeSelect',
+ #}else if(x.componentType=="radio"){
+ component: 'RadioGroup',
+ #}else if(x.componentType=="checkbox"){
+ component: 'Checkbox',
+ #}else if(x.componentType=="switch"){
+ component: 'Switch',
+ #}else if(x.componentType=="date"){
+ component: 'DatePicker',
+ #}
+ #if(x.componentType=="select"&&x.dictCode!=null){
+ componentProps: {
+ api: getDictList,
+ params: { code: '${x.dictCode!}' },
+ labelField: 'dictValue',
+ valueField: 'dictKey',
+ },
+ #}
+ #if(x.isRequired==1){
+ required: true,
+ #}
+ },
+ #}
+ #}
+];
+
+export const detailSchema: DescItem[] = [
+ #for(x in prototypes) {
+ #if(x.isForm!=0){
+ {
+ field: "${x.propertyName!}",
+ label: "${x.comment!}",
+ },
+ #}
+ #}
+];
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/data.ts.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/data.ts.btl
new file mode 100644
index 0000000..0bc1437
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/data.ts.btl
@@ -0,0 +1,30 @@
+import { defHttp } from '/@/utils/http/axios';
+
+enum Api {
+ List = '/${serviceName!}/${modelCode!}/list',
+ Submit = '/${serviceName!}/${modelCode!}/submit',
+ Detail = '/${serviceName!}/${modelCode!}/detail',
+ Remove = '/${serviceName!}/${modelCode!}/remove',
+}
+
+//鍒楄〃
+export function getList(params?: object) {
+ return defHttp.get({ url: Api.List, params: params },
+ { joinParamsToUrl: true, joinTime: false });
+}
+
+//鎻愪氦
+export function submitObj(params?: object) {
+ return defHttp.post({ url: Api.Submit, params: params });
+}
+
+//璇︽儏
+export function getDetail(params?: object) {
+ return defHttp.get({ url: Api.Detail, params });
+}
+
+//鍒犻櫎
+export function remove(params?: object) {
+ return defHttp.post({ url: Api.Remove, params }, { joinParamsToUrl: true });
+}
+
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/index.vue.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/index.vue.btl
new file mode 100644
index 0000000..5408645
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/index.vue.btl
@@ -0,0 +1,143 @@
+<template>
+ <div>
+ <BasicTable @register="registerTable">
+ <template \#toolbar>
+ <a-button type="primary" v-auth="'${modelCode!}_add'" @click="handleCreate">
+ 鏂板
+ </a-button>
+ </template>
+ <template \#bodyCell="{ column, record }">
+ #for(x in prototypes) {
+ #if(isNotEmpty(x.dictCode)){
+ <template v-if="column.dataIndex === '${x.propertyName!}'">
+ {{ formatDictValue(options['${x.propertyName!}Data'], record.${x.propertyName!}) }}
+ </template>
+ #}
+ #}
+ <template v-if="column.dataIndex === 'action'">
+ <TableAction
+ :actions="[
+ {
+ auth: '${modelCode!}_view',
+ label: '鏌ョ湅',
+ color: 'success',
+ icon: 'clarity:info-standard-line',
+ onClick: handleView.bind(null, record),
+ },
+ {
+ auth: '${modelCode!}_edit',
+ label: '缂栬緫',
+ icon: 'clarity:note-edit-line',
+ onClick: handleEdit.bind(null, record),
+ },
+ {
+ label: '瀛愯〃閰嶇疆',
+ icon: 'clarity:note-edit-line',
+ onClick: handleSubConfig.bind(null, record),
+ },
+ {
+ auth: '${modelCode!}_delete',
+ label: '鍒犻櫎',
+ icon: 'ant-design:delete-outlined',
+ color: 'error',
+ popConfirm: {
+ title: '鏄惁纭鍒犻櫎',
+ confirm: handleDelete.bind(null, record),
+ },
+ },
+ ]"
+ />
+ </template>
+ </template>
+ </BasicTable>
+ <${modelClass!}Modal @register="registerModal" @success="handleSuccess" />
+ <${subModel.modelClass!}Sub @register="registerDrawer" />
+
+ </div>
+</template>
+<script lang="ts" setup name="${modelClass!}">
+ import { ref } from 'vue';
+ import ${modelClass!}Modal from './${modelClass!}Modal.vue';
+ import { BasicTable, useTable, TableAction } from '/@/components/Table';
+ import { getList, remove } from '/@/api/${serviceCode!}/${modelCode!}';
+ import { useModal } from '/@/components/Modal';
+ import { useDrawer } from '/@/components/Drawer';
+ import ${subModel.modelClass!}Sub from './${subModel.modelClass!}Sub.vue';
+ import { columns, searchFormSchema } from './${modelCode!}.data';
+ import { useMessage } from '/@/hooks/web/useMessage';
+ import { formatDictValue } from '/@/utils';
+ import { getDictList } from '/@/api/system/system';
+ //鍒濆鍖栧瓧鍏�
+ let options = ref({});
+ async function fetch() {
+ #for(x in prototypes) {
+ #if(isNotEmpty(x.dictCode)){
+ options.value['${x.propertyName!}Data'] = await getDictList({ code: '${x.dictCode!}' });
+ #}
+ #}
+ }
+ fetch();
+
+ const { createMessage } = useMessage();
+ const [registerModal, { openModal }] = useModal();
+ const [registerDrawer, { openDrawer }] = useDrawer();
+
+ const [registerTable, { reload }] = useTable({
+ api: getList,
+ rowKey: 'id',
+ columns,
+ formConfig: {
+ labelWidth: 120,
+ schemas: searchFormSchema,
+ baseColProps: { xl: 12, xxl: 8 },
+ },
+ useSearchForm: true,
+ actionColumn: {
+ width: 250,
+ title: '鎿嶄綔',
+ dataIndex: 'action',
+ },
+ });
+
+ function handleCreate() {
+ openModal(true, {
+ isDetail: false,
+ isUpdate: false,
+ });
+ }
+
+ function handleView(record: Recordable) {
+ openModal(true, {
+ record,
+ isUpdate: false,
+ isDetail: true,
+ });
+ }
+
+ function handleEdit(record: Recordable) {
+ openModal(true, {
+ record,
+ isDetail: false,
+ isUpdate: true,
+ });
+ }
+
+ function handleSubConfig(record: Recordable) {
+ openDrawer(true, {
+ mainId:record.id
+ });
+ }
+
+
+ async function handleDelete(record: Recordable) {
+ await remove({ ids: record.id });
+ createMessage.success('鎿嶄綔鎴愬姛');
+ reload();
+ }
+
+ function handleSuccess() {
+ //鎿嶄綔鎴愬姛鎻愮ず
+ createMessage.success('鎿嶄綔鎴愬姛');
+ reload();
+ }
+</script>
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/sub.vue.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/sub.vue.btl
new file mode 100644
index 0000000..5f0f4f1
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/sub/sub.vue.btl
@@ -0,0 +1,113 @@
+<template>
+ <BasicDrawer
+ v-bind="$attrs"
+ @register="registerDrawer"
+ :title="`[\${${model.modelCode!}Name}] 閰嶇疆`"
+ width="1000px"
+ >
+ <BasicTable @register="registerTable">
+ <template \#toolbar>
+ <a-button type="primary" @click="handleCreate">鏂板</a-button>
+ </template>
+ <template \#bodyCell="{ column, record }">
+ <template v-if="column.dataIndex === 'action'">
+ <TableAction
+ :actions="[
+ {
+ label: '鏌ョ湅',
+ color: 'success',
+ icon: 'clarity:info-standard-line',
+ onClick: handleView.bind(null, record),
+ },
+ {
+ label: '缂栬緫',
+ icon: 'clarity:note-edit-line',
+ onClick: handleEdit.bind(null, record),
+ },
+ {
+ label: '鍒犻櫎',
+ icon: 'ant-design:delete-outlined',
+ color: 'error',
+ popConfirm: {
+ title: '鏄惁纭鍒犻櫎',
+ confirm: handleDelete.bind(null, record),
+ },
+ },
+ ]"
+ />
+ </template>
+ </template>
+ </BasicTable>
+ <${modelClass!}Modal @register="registerModal" @success="handleSuccess" />
+ </BasicDrawer>
+</template>
+<script lang="ts" setup>
+ import { BasicTable, useTable, TableAction } from '/@/components/Table';
+ import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
+ import { getList, remove } from '/@/api/${serviceCode!}/${modelCode!}';
+ import { columns, searchFormSchema } from './${modelCode!}.data';
+ import { useModal } from '/@/components/Modal';
+ import ${modelClass!}Modal from './${modelClass!}Modal.vue';
+ import { useMessage } from '/@/hooks/web/useMessage';
+ const { createMessage } = useMessage();
+ const { success } = createMessage;
+ const [registerModal, { openModal }] = useModal();
+ const [registerTable, { reload, setProps }] = useTable({
+ api: getList,
+ rowKey: 'id',
+ columns,
+ formConfig: {
+ labelWidth: 120,
+ schemas: searchFormSchema,
+ baseColProps: { xl: 12, xxl: 6 },
+ },
+ useSearchForm: true,
+ immediate: false,
+ actionColumn: {
+ width: 250,
+ title: '鎿嶄綔',
+ dataIndex: 'action',
+ },
+ });
+ const [registerDrawer, { setDrawerProps }] = useDrawerInner(async (data) => {
+ setProps({
+ searchInfo: {${subFkIdHump!}: data.mainId},
+ });
+ reload();
+ });
+
+ function handleCreate() {
+ openModal(true, {
+ isUpdate: false,
+ isDetail: false,
+ });
+ }
+
+ function handleEdit(record: Recordable) {
+ openModal(true, {
+ record,
+ isUpdate: true,
+ isDetail: false,
+ });
+ }
+
+ function handleView(record: Recordable) {
+ openModal(true, {
+ record,
+ isUpdate: false,
+ isDetail: true,
+ });
+ }
+
+ async function handleDelete(record: Recordable) {
+ await remove({ ids: record.id });
+ success('鎿嶄綔鎴愬姛');
+ reload();
+ }
+
+ function handleSuccess() {
+ //鎿嶄綔鎴愬姛鎻愮ず
+ success('鎿嶄綔鎴愬姛');
+ reload();
+ }
+</script>
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/tree/Modal.vue.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/tree/Modal.vue.btl
new file mode 100644
index 0000000..84af854
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/tree/Modal.vue.btl
@@ -0,0 +1,90 @@
+<template>
+ <BasicModal
+ v-bind="$attrs"
+ @register="registerModal"
+ :title="getTitle"
+ @ok="handleSubmit"
+ :showOkBtn="!isDetail"
+ :width="900"
+ >
+ <div v-show="isDetail">
+ <Description size="middle" @register="registerDetail" :column="2"/>
+ </div>
+ <div v-show="!isDetail">
+ <BasicForm @register="registerForm" />
+ </div>
+ </BasicModal>
+</template>
+<script lang="ts" setup>
+ import { ref, computed, unref } from 'vue';
+ import { BasicModal, useModalInner } from '/@/components/Modal';
+ import { BasicForm, useForm } from '/@/components/Form/index';
+ import { formSchema, detailSchema } from './${modelCode!}.data';
+ import { getDetail, submitObj } from '/@/api/${serviceCode!}/${modelCode!}';
+ import { Description, useDescription } from '/@/components/Description/index';
+
+ const emit = defineEmits(['success']);
+ const isDetail = ref(true);
+ const isUpdate = ref(true);
+ const rowId = ref('');
+ //璇︽儏
+ const [registerDetail, { setDescProps }] = useDescription({
+ schema: detailSchema,
+ });
+
+ const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
+ labelWidth: 100,
+ schemas: formSchema,
+ showActionButtonGroup: false,
+ baseColProps: {
+ span: 12,
+ },
+ actionColOptions: {
+ span: 23,
+ },
+ });
+
+ const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
+ resetFields();
+ setModalProps({ confirmLoading: false });
+ isUpdate.value = !!data?.isUpdate;
+ isDetail.value = !!data?.isDetail;
+ if (unref(isDetail)) {
+ const detail = await getDetail({ id: data.record.id });
+ setDescProps({
+ data: detail,
+ });
+ } else {
+ if (unref(isUpdate)) {
+ rowId.value = data.record.id;
+ const detailData = await getDetail({ id: data.record.id });
+ setFieldsValue({
+ ...detailData,
+ });
+ }
+ }
+ });
+
+ const getTitle = computed(() => {
+ if (unref(isDetail)) {
+ return '鏌ョ湅';
+ } else {
+ return !unref(isUpdate) ? '鏂板' : '缂栬緫';
+ }
+ });
+
+ async function handleSubmit() {
+ try {
+ const values = await validate();
+ setModalProps({ confirmLoading: true });
+ if (unref(isUpdate)) {
+ values.id = rowId.value;
+ }
+ await submitObj(values);
+ closeModal();
+ emit('success', { isUpdate: unref(isUpdate), values: { ...values, id: rowId.value } });
+ } finally {
+ setModalProps({ confirmLoading: false });
+ }
+ }
+</script>
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/tree/data.data.ts.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/tree/data.data.ts.btl
new file mode 100644
index 0000000..53bab06
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/tree/data.data.ts.btl
@@ -0,0 +1,102 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { DescItem } from '/@/components/Description/index';
+import { getDictList } from '/@/api/system/system';
+
+
+export const columns: BasicColumn[] = [
+ #for(x in prototypes) {
+ #if(x.isList==1){
+ {
+ title: "${x.comment!}",
+ dataIndex: "${x.propertyName!}",
+ },
+ #}
+ #}
+ ];
+
+export const searchFormSchema: FormSchema[] = [
+ #for(x in prototypes) {
+ #if(x.isQuery==1){
+ {
+ field: "${x.propertyName!}",
+ label: "${x.comment!}",
+ #if(x.componentType=="input"){
+ component: 'Input',
+ #}else if(x.componentType=="textarea"){
+ component: 'InputTextArea',
+ #}else if(x.componentType=="select"){
+ component: 'ApiSelect',
+ #}else if(x.componentType=="tree"){
+ component: 'ApiTreeSelect',
+ #}else if(x.componentType=="radio"){
+ component: 'RadioGroup',
+ #}else if(x.componentType=="checkbox"){
+ component: 'Checkbox',
+ #}else if(x.componentType=="switch"){
+ component: 'Switch',
+ #}else if(x.componentType=="date"){
+ component: 'DatePicker',
+ #}
+ #if(x.componentType=="select"&&x.dictCode!=null){
+ componentProps: {
+ api: getDictList,
+ params: { code: '${x.dictCode!}' },
+ labelField: 'dictValue',
+ valueField: 'dictKey',
+ },
+ #}
+ },
+ #}
+ #}
+];
+
+export const formSchema: FormSchema[] = [
+ #for(x in prototypes) {
+ #if(x.isForm!=0){
+ {
+ field: "${x.propertyName!}",
+ label: "${x.comment!}",
+ #if(x.componentType=="input"){
+ component: 'Input',
+ #}else if(x.componentType=="textarea"){
+ component: 'InputTextArea',
+ #}else if(x.componentType=="select"){
+ component: 'ApiSelect',
+ #}else if(x.componentType=="tree"){
+ component: 'ApiTreeSelect',
+ #}else if(x.componentType=="radio"){
+ component: 'RadioGroup',
+ #}else if(x.componentType=="checkbox"){
+ component: 'Checkbox',
+ #}else if(x.componentType=="switch"){
+ component: 'Switch',
+ #}else if(x.componentType=="date"){
+ component: 'DatePicker',
+ #}
+ #if(x.componentType=="select"&&x.dictCode!=null){
+ componentProps: {
+ api: getDictList,
+ params: { code: '${x.dictCode!}' },
+ labelField: 'dictValue',
+ valueField: 'dictKey',
+ },
+ #}
+ #if(x.isRequired==1){
+ required: true,
+ #}
+ },
+ #}
+ #}
+];
+
+export const detailSchema: DescItem[] = [
+ #for(x in prototypes) {
+ #if(x.isForm!=0){
+ {
+ field: "${x.propertyName!}",
+ label: "${x.comment!}",
+ },
+ #}
+ #}
+];
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/tree/data.ts.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/tree/data.ts.btl
new file mode 100644
index 0000000..0bc1437
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/tree/data.ts.btl
@@ -0,0 +1,30 @@
+import { defHttp } from '/@/utils/http/axios';
+
+enum Api {
+ List = '/${serviceName!}/${modelCode!}/list',
+ Submit = '/${serviceName!}/${modelCode!}/submit',
+ Detail = '/${serviceName!}/${modelCode!}/detail',
+ Remove = '/${serviceName!}/${modelCode!}/remove',
+}
+
+//鍒楄〃
+export function getList(params?: object) {
+ return defHttp.get({ url: Api.List, params: params },
+ { joinParamsToUrl: true, joinTime: false });
+}
+
+//鎻愪氦
+export function submitObj(params?: object) {
+ return defHttp.post({ url: Api.Submit, params: params });
+}
+
+//璇︽儏
+export function getDetail(params?: object) {
+ return defHttp.get({ url: Api.Detail, params });
+}
+
+//鍒犻櫎
+export function remove(params?: object) {
+ return defHttp.post({ url: Api.Remove, params }, { joinParamsToUrl: true });
+}
+
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/tree/index.vue.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/tree/index.vue.btl
new file mode 100644
index 0000000..71f9441
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/lemon/tree/index.vue.btl
@@ -0,0 +1,124 @@
+<template>
+ <div>
+ <BasicTable @register="registerTable">
+ <template \#toolbar>
+ <a-button type="primary" v-auth="'${modelCode!}_add'" @click="handleCreate">
+ 鏂板
+ </a-button>
+ </template>
+ <template \#bodyCell="{ column, record }">
+ #for(x in prototypes) {
+ #if(isNotEmpty(x.dictCode)){
+ <template v-if="column.dataIndex === '${x.propertyName!}'">
+ {{ formatDictValue(options['${x.propertyName!}Data'], record.category) }}
+ </template>
+ #}
+ #}
+ <template v-if="column.dataIndex === 'action'">
+ <TableAction
+ :actions="[
+ {
+ auth: '${modelCode!}_view',
+ label: '鏌ョ湅',
+ color: 'success',
+ icon: 'clarity:info-standard-line',
+ onClick: handleView.bind(null, record),
+ },
+ {
+ auth: '${modelCode!}_edit',
+ label: '缂栬緫',
+ icon: 'clarity:note-edit-line',
+ onClick: handleEdit.bind(null, record),
+ },
+ {
+ auth: '${modelCode!}_delete',
+ label: '鍒犻櫎',
+ icon: 'ant-design:delete-outlined',
+ color: 'error',
+ popConfirm: {
+ title: '鏄惁纭鍒犻櫎',
+ confirm: handleDelete.bind(null, record),
+ },
+ },
+ ]"
+ />
+ </template>
+ </template>
+ </BasicTable>
+ <${modelClass!}Modal @register="registerModal" @success="handleSuccess" />
+ </div>
+</template>
+<script lang="ts" setup name="${modelClass!}">
+ import { ref } from 'vue';
+ import ${modelClass!}Modal from './${modelCode!}Modal.vue';
+ import { BasicTable, useTable, TableAction } from '/@/components/Table';
+ import { getList, remove } from '/@/api/${serviceCode!}/${modelCode!}';
+ import { useModal } from '/@/components/Modal';
+ import { columns, searchFormSchema } from './${modelCode!}.data';
+ import { useMessage } from '/@/hooks/web/useMessage';
+ import { formatDictValue } from '/@/utils';
+ import { getDictList } from '/@/api/system/system';
+ //鍒濆鍖栧瓧鍏�
+ let options = ref({});
+ async function fetch() {
+ #for(x in prototypes) {
+ #if(isNotEmpty(x.dictCode)){
+ options.value['${x.propertyName!}Data'] = await getDictList({ code: '${x.dictCode!}' });
+ #}
+ #}
+ }
+ fetch();
+
+ const { createMessage } = useMessage();
+ const [registerModal, { openModal }] = useModal();
+ const [registerTable, { reload }] = useTable({
+ api: getList,
+ rowKey: 'id',
+ columns,
+ formConfig: {
+ labelWidth: 120,
+ schemas: searchFormSchema,
+ baseColProps: { xl: 12, xxl: 8 },
+ },
+ useSearchForm: true,
+ actionColumn: {
+ width: 250,
+ title: '鎿嶄綔',
+ dataIndex: 'action',
+ },
+ });
+
+ function handleCreate() {
+ openModal(true, {
+ isDetail: false,
+ isUpdate: false,
+ });
+ }
+
+ function handleView(record: Recordable) {
+ openModal(true, {
+ record,
+ isUpdate: false,
+ isDetail: true,
+ });
+ }
+
+ function handleEdit(record: Recordable) {
+ openModal(true, {
+ record,
+ isDetail: false,
+ isUpdate: true,
+ });
+ }
+ async function handleDelete(record: Recordable) {
+ await remove({ ids: record.id });
+ createMessage.success('鎿嶄綔鎴愬姛');
+ reload();
+ }
+
+ function handleSuccess() {
+ //鎿嶄綔鎴愬姛鎻愮ず
+ createMessage.success('鎿嶄綔鎴愬姛');
+ reload();
+ }
+</script>
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/crud/api.js.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/crud/api.js.btl
new file mode 100644
index 0000000..fa12324
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/crud/api.js.btl
@@ -0,0 +1,50 @@
+import request from '@/router/axios';
+
+export const getList = (current, size, params) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/list',
+ method: 'get',
+ params: {
+ ...params,
+ current,
+ size,
+ }
+ })
+}
+
+export const getDetail = (id) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/detail',
+ method: 'get',
+ params: {
+ id
+ }
+ })
+}
+
+export const remove = (ids) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/remove',
+ method: 'post',
+ params: {
+ ids,
+ }
+ })
+}
+
+export const add = (row) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/submit',
+ method: 'post',
+ data: row
+ })
+}
+
+export const update = (row) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/submit',
+ method: 'post',
+ data: row
+ })
+}
+
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/crud/const.js.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/crud/const.js.btl
new file mode 100644
index 0000000..05ff525
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/crud/const.js.btl
@@ -0,0 +1,54 @@
+export default {
+ height:'auto',
+ calcHeight: 30,
+ tip: false,
+ searchShow: true,
+ searchMenuSpan: 6,
+ border: true,
+ index: true,
+ viewBtn: true,
+ selection: true,
+ dialogClickModal: false,
+ column: [
+#for(x in prototypes) {
+ {
+ label: "${x.comment!}",
+ prop: "${x.propertyName!}",
+ type: "${x.componentType!}",
+#if(strutil.contain(x.componentType,"date")||strutil.contain(x.componentType,"time")){
+ format: "yyyy-MM-dd hh:mm:ss",
+ valueFormat: "yyyy-MM-dd hh:mm:ss",
+#}
+#if(isNotEmpty(x.dictCode)){
+ dicUrl: "/api/blade-system/dict/dictionary?code=${x.dictCode!}",
+ dataType: "number",
+ props: {
+ label: "dictValue",
+ value: "dictKey"
+ },
+#}
+#if(x.isForm==0){
+ addDisplay: false,
+ editDisplay: false,
+ viewDisplay: false,
+#}
+#if(x.isRow==1){
+ span: 24,
+#}
+#if(x.isList==0){
+ hide: true,
+#}
+#if(x.isQuery==1){
+ search: true,
+#}
+#if(x.isRequired==1){
+ rules: [{
+ required: true,
+ message: "璇疯緭鍏�${x.comment!}",
+ trigger: "blur"
+ }],
+#}
+ },
+#}
+ ]
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/crud/crud.vue.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/crud/crud.vue.btl
new file mode 100644
index 0000000..ef6c97e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/crud/crud.vue.btl
@@ -0,0 +1,188 @@
+<template>
+ <basic-container>
+ <avue-crud :option="option"
+ :table-loading="loading"
+ :data="data"
+ :page.sync="page"
+ :permission="permissionList"
+ :before-open="beforeOpen"
+ v-model="form"
+ ref="crud"
+ @row-update="rowUpdate"
+ @row-save="rowSave"
+ @row-del="rowDel"
+ @search-change="searchChange"
+ @search-reset="searchReset"
+ @selection-change="selectionChange"
+ @current-change="currentChange"
+ @size-change="sizeChange"
+ @refresh-change="refreshChange"
+ @on-load="onLoad">
+ <template slot="menuLeft">
+ <el-button type="danger"
+ size="small"
+ icon="el-icon-delete"
+ plain
+ v-if="permission.${modelCode!}_delete"
+ @click="handleDelete">鍒� 闄�
+ </el-button>
+ </template>
+ </avue-crud>
+ </basic-container>
+</template>
+
+<script>
+ import {getList, getDetail, add, update, remove} from "@/api/${serviceCode!}/${modelCode!}";
+ import option from "@/const/${serviceCode!}/${modelCode!}";
+ import {mapGetters} from "vuex";
+
+ export default {
+ data() {
+ return {
+ form: {},
+ query: {},
+ loading: true,
+ page: {
+ pageSize: 10,
+ currentPage: 1,
+ total: 0
+ },
+ selectionList: [],
+ option: option,
+ data: []
+ };
+ },
+ computed: {
+ ...mapGetters(["permission"]),
+ permissionList() {
+ return {
+ addBtn: this.vaildData(this.permission.${modelCode!}_add, false),
+ viewBtn: this.vaildData(this.permission.${modelCode!}_view, false),
+ delBtn: this.vaildData(this.permission.${modelCode!}_delete, false),
+ editBtn: this.vaildData(this.permission.${modelCode!}_edit, false)
+ };
+ },
+ ids() {
+ let ids = [];
+ this.selectionList.forEach(ele => {
+ ids.push(ele.id);
+ });
+ return ids.join(",");
+ }
+ },
+ methods: {
+ rowSave(row, done, loading) {
+ add(row).then(() => {
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ done();
+ }, error => {
+ loading();
+ window.console.log(error);
+ });
+ },
+ rowUpdate(row, index, done, loading) {
+ update(row).then(() => {
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ done();
+ }, error => {
+ loading();
+ console.log(error);
+ });
+ },
+ rowDel(row) {
+ this.$confirm("纭畾灏嗛�夋嫨鏁版嵁鍒犻櫎?", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return remove(row.id);
+ })
+ .then(() => {
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ });
+ },
+ handleDelete() {
+ if (this.selectionList.length === 0) {
+ this.$message.warning("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+ return;
+ }
+ this.$confirm("纭畾灏嗛�夋嫨鏁版嵁鍒犻櫎?", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return remove(this.ids);
+ })
+ .then(() => {
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ this.$refs.crud.toggleSelection();
+ });
+ },
+ beforeOpen(done, type) {
+ if (["edit", "view"].includes(type)) {
+ getDetail(this.form.id).then(res => {
+ this.form = res.data.data;
+ });
+ }
+ done();
+ },
+ searchReset() {
+ this.query = {};
+ this.onLoad(this.page);
+ },
+ searchChange(params, done) {
+ this.query = params;
+ this.page.currentPage = 1;
+ this.onLoad(this.page, params);
+ done();
+ },
+ selectionChange(list) {
+ this.selectionList = list;
+ },
+ selectionClear() {
+ this.selectionList = [];
+ this.$refs.crud.toggleSelection();
+ },
+ currentChange(currentPage){
+ this.page.currentPage = currentPage;
+ },
+ sizeChange(pageSize){
+ this.page.pageSize = pageSize;
+ },
+ refreshChange() {
+ this.onLoad(this.page, this.query);
+ },
+ onLoad(page, params = {}) {
+ this.loading = true;
+ getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
+ const data = res.data.data;
+ this.page.total = data.total;
+ this.data = data.records;
+ this.loading = false;
+ this.selectionClear();
+ });
+ }
+ }
+ };
+</script>
+
+<style>
+</style>
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/sub/api.js.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/sub/api.js.btl
new file mode 100644
index 0000000..fa12324
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/sub/api.js.btl
@@ -0,0 +1,50 @@
+import request from '@/router/axios';
+
+export const getList = (current, size, params) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/list',
+ method: 'get',
+ params: {
+ ...params,
+ current,
+ size,
+ }
+ })
+}
+
+export const getDetail = (id) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/detail',
+ method: 'get',
+ params: {
+ id
+ }
+ })
+}
+
+export const remove = (ids) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/remove',
+ method: 'post',
+ params: {
+ ids,
+ }
+ })
+}
+
+export const add = (row) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/submit',
+ method: 'post',
+ data: row
+ })
+}
+
+export const update = (row) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/submit',
+ method: 'post',
+ data: row
+ })
+}
+
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/sub/const.js.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/sub/const.js.btl
new file mode 100644
index 0000000..a8b682e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/sub/const.js.btl
@@ -0,0 +1,54 @@
+export default {
+ height:'auto',
+ calcHeight: 30,
+ tip: false,
+ searchShow: true,
+ searchMenuSpan: 6,
+ border: true,
+ index: true,
+ viewBtn: true,
+ selection: true,
+ menuWidth: 300,
+ column: [
+#for(x in prototypes) {
+ {
+ label: "${x.comment!}",
+ prop: "${x.propertyName!}",
+ type: "${x.componentType!}",
+#if(strutil.contain(x.componentType,"date")||strutil.contain(x.componentType,"time")){
+ format: "yyyy-MM-dd hh:mm:ss",
+ valueFormat: "yyyy-MM-dd hh:mm:ss",
+#}
+#if(isNotEmpty(x.dictCode)){
+ dicUrl: "/api/blade-system/dict/dictionary?code=${x.dictCode!}",
+ dataType: "number",
+ props: {
+ label: "dictValue",
+ value: "dictKey"
+ },
+#}
+#if(x.isForm==0){
+ addDisplay: false,
+ editDisplay: false,
+ viewDisplay: false,
+#}
+#if(x.isRow==1){
+ span: 24,
+#}
+#if(x.isList==0){
+ hide: true,
+#}
+#if(x.isQuery==1){
+ search: true,
+#}
+#if(x.isRequired==1&&isEmpty(x.validateRule)){
+ rules: [{
+ required: true,
+ message: "璇疯緭鍏�${x.comment!}",
+ trigger: "blur"
+ }],
+#}
+ },
+#}
+ ]
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/sub/crud.vue.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/sub/crud.vue.btl
new file mode 100644
index 0000000..b36a800
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/sub/crud.vue.btl
@@ -0,0 +1,371 @@
+<template>
+ <basic-container>
+ <avue-crud :option="option"
+ :table-loading="loading"
+ :data="data"
+ :page.sync="page"
+ :permission="permissionList"
+ :before-open="beforeOpen"
+ v-model="form"
+ ref="crud"
+ @row-update="rowUpdate"
+ @row-save="rowSave"
+ @row-del="rowDel"
+ @search-change="searchChange"
+ @search-reset="searchReset"
+ @selection-change="selectionChange"
+ @current-change="currentChange"
+ @size-change="sizeChange"
+ @refresh-change="refreshChange"
+ @on-load="onLoad">
+ <template slot="menuLeft">
+ <el-button type="danger"
+ size="small"
+ icon="el-icon-delete"
+ plain
+ v-if="permission.${model.modelCode!}_delete"
+ @click="handleDelete">鍒� 闄�
+ </el-button>
+ </template>
+ <template slot-scope="{row}" slot="menu">
+ <el-button type="text"
+ icon="el-icon-setting"
+ size="small"
+ @click.stop="handleDataSub(row)">閰� 缃�
+ </el-button>
+ </template>
+ </avue-crud>
+ <el-drawer :title="`[\${${model.modelCode!}Name}] 閰嶇疆`" :visible.sync="subVisible" :direction="direction" append-to-body
+ :before-close="handleSubClose" size="1000px">
+ <basic-container>
+ <avue-crud :option="optionSub"
+ :data="dataSub"
+ :page.sync="pageSub"
+ v-model="formSub"
+ :table-loading="loadingSub"
+ ref="crudSub"
+ @row-del="rowDelSub"
+ @row-update="rowUpdateSub"
+ @row-save="rowSaveSub"
+ :before-open="beforeOpenSub"
+ @search-change="searchChangeSub"
+ @search-reset="searchResetSub"
+ @selection-change="selectionChangeSub"
+ @current-change="currentChangeSub"
+ @size-change="sizeChangeSub"
+ @on-load="onLoadSub">
+ <template slot="menuLeft">
+ <el-button type="danger"
+ size="small"
+ icon="el-icon-delete"
+ plain
+ @click="handleDeleteSub">鍒� 闄�
+ </el-button>
+ </template>
+ </avue-crud>
+ </basic-container>
+ </el-drawer>
+ </basic-container>
+</template>
+
+<script>
+ import {getList, getDetail, add, update, remove} from "@/api/${serviceCode!}/${model.modelCode!}";
+ import {getList as getListSub, getDetail as getDetailSub, add as addSub, update as updateSub, remove as removeSub} from "@/api/${serviceCode!}/${subModel.modelCode!}";
+ import option from "@/const/${serviceCode!}/${model.modelCode!}";
+ import optionSub from "@/const/${serviceCode!}/${subModel.modelCode!}";
+ import {mapGetters} from "vuex";
+
+ export default {
+ data() {
+ return {
+ form: {},
+ query: {},
+ loading: true,
+ data: [],
+ selectionList: [],
+ page: {
+ pageSize: 10,
+ currentPage: 1,
+ total: 0
+ },
+ option: option,
+ subVisible: false,
+ direction: 'rtl',
+ ${subFkIdHump!}: 0,
+ ${model.modelCode!}Name: "${model.modelName!}",
+ formSub: {},
+ querySub: {},
+ loadingSub: true,
+ dataSub: [],
+ selectionListSub: [],
+ pageSub: {
+ pageSize: 10,
+ currentPage: 1,
+ total: 0
+ },
+ optionSub: optionSub
+ };
+ },
+ computed: {
+ ...mapGetters(["permission"]),
+ permissionList() {
+ return {
+ addBtn: this.vaildData(this.permission.${model.modelCode!}_add, false),
+ viewBtn: this.vaildData(this.permission.${model.modelCode!}_view, false),
+ delBtn: this.vaildData(this.permission.${model.modelCode!}_delete, false),
+ editBtn: this.vaildData(this.permission.${model.modelCode!}_edit, false)
+ };
+ },
+ ids() {
+ let ids = [];
+ this.selectionList.forEach(ele => {
+ ids.push(ele.id);
+ });
+ return ids.join(",");
+ },
+ subIds() {
+ let ids = [];
+ this.selectionListSub.forEach(ele => {
+ ids.push(ele.id);
+ });
+ return ids.join(",");
+ }
+ },
+ methods: {
+ // 涓昏〃妯″潡
+ rowSave(row, done, loading) {
+ add(row).then(() => {
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ done();
+ }, error => {
+ window.console.log(error);
+ loading();
+ });
+ },
+ rowUpdate(row, index, done, loading) {
+ update(row).then(() => {
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ done();
+ }, error => {
+ window.console.log(error);
+ loading();
+ });
+ },
+ rowDel(row) {
+ this.$confirm("纭畾灏嗛�夋嫨鏁版嵁鍒犻櫎?", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return remove(row.id);
+ })
+ .then(() => {
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ });
+ },
+ handleDelete() {
+ if (this.selectionList.length === 0) {
+ this.$message.warning("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+ return;
+ }
+ this.$confirm("纭畾灏嗛�夋嫨鏁版嵁鍒犻櫎?", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return remove(this.ids);
+ })
+ .then(() => {
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ this.$refs.crud.toggleSelection();
+ });
+ },
+ beforeOpen(done, type) {
+ if (["edit", "view"].includes(type)) {
+ getDetail(this.form.id).then(res => {
+ this.form = res.data.data;
+ });
+ }
+ done();
+ },
+ searchReset() {
+ this.query = {};
+ this.onLoad(this.page);
+ },
+ searchChange(params, done) {
+ this.query = params;
+ this.page.currentPage = 1
+ this.onLoad(this.page, params);
+ done();
+ },
+ selectionChange(list) {
+ this.selectionList = list;
+ },
+ selectionClear() {
+ this.selectionList = [];
+ this.$refs.crud.toggleSelection();
+ },
+ currentChange(currentPage){
+ this.page.currentPage = currentPage;
+ },
+ sizeChange(pageSize){
+ this.page.pageSize = pageSize;
+ },
+ onLoad(page, params = {}) {
+ this.loading = true;
+ getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
+ const data = res.data.data;
+ this.page.total = data.total;
+ this.data = data.records;
+ this.loading = false;
+ this.selectionClear();
+ });
+ },
+ // 瀛愯〃妯″潡
+ handleDataSub(row) {
+ this.subVisible = true;
+ this.${subFkIdHump!} = row.id;
+ this.onLoadSub(this.pageSub)
+ },
+ handleSubClose(hide) {
+ hide();
+ },
+ rowSaveSub(row, loading, done) {
+ row = {
+ ...row,
+ ${subFkIdHump!}: this.${subFkIdHump!},
+ };
+ addSub(row).then(() => {
+ loading();
+ this.onLoadSub(this.pageSub);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ }, error => {
+ done();
+ window.console.log(error);
+ });
+ },
+ rowUpdateSub(row, index, loading, done) {
+ row = {
+ ...row,
+ ${subFkIdHump!}: this.${subFkIdHump!},
+ };
+ updateSub(row).then(() => {
+ loading();
+ this.onLoadSub(this.pageSub);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ }, error => {
+ done();
+ window.console.log(error);
+ });
+ },
+ rowDelSub(row) {
+ this.$confirm("纭畾灏嗛�夋嫨鏁版嵁鍒犻櫎?", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return removeSub(row.id);
+ })
+ .then(() => {
+ this.onLoadSub(this.pageSub);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ });
+ },
+ handleDeleteSub() {
+ if (this.selectionListSub.length === 0) {
+ this.$message.warning("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+ return;
+ }
+ this.$confirm("纭畾灏嗛�夋嫨鏁版嵁鍒犻櫎?", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return removeSub(this.subIds);
+ })
+ .then(() => {
+ this.onLoadSub(this.pageSub);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ this.$refs.crudSub.toggleSelection();
+ });
+ },
+ beforeOpenSub(done, type) {
+ if (["edit", "view"].includes(type)) {
+ getDetailSub(this.formSub.id).then(res => {
+ this.formSub = res.data.data;
+ });
+ }
+ done();
+ },
+ searchResetSub() {
+ this.querySub = {};
+ this.onLoadSub(this.pageSub);
+ },
+ searchChangeSub(params) {
+ this.querySub = params;
+ this.onLoadSub(this.pageSub, params);
+ },
+ selectionChangeSub(list) {
+ this.selectionListSub = list;
+ },
+ currentChangeSub(currentPage) {
+ this.pageSub.currentPage = currentPage;
+ },
+ sizeChangeSub(pageSize) {
+ this.pageSub.pageSize = pageSize;
+ },
+ refreshChange() {
+ this.onLoad(this.page, this.query);
+ },
+ onLoadSub(page, params = {}) {
+ this.loadingSub = true;
+ const values = {
+ ...params,
+ ${subFkIdHump!}: this.${subFkIdHump!},
+ }
+ getListSub(page.currentPage, page.pageSize, Object.assign(values, this.querySub)).then(res => {
+ const data = res.data.data;
+ this.pageSub.total = data.total;
+ this.dataSub = data.records;
+ this.selectionListSub = [];
+ this.loadingSub = false;
+ });
+ },
+ }
+ };
+</script>
+
+<style>
+</style>
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/tree/api.js.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/tree/api.js.btl
new file mode 100644
index 0000000..ff4186a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/tree/api.js.btl
@@ -0,0 +1,60 @@
+import request from '@/router/axios';
+
+export const getList = (current, size, params) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/list',
+ method: 'get',
+ params: {
+ ...params,
+ current,
+ size,
+ }
+ })
+}
+
+export const getDetail = (id) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/detail',
+ method: 'get',
+ params: {
+ id
+ }
+ })
+}
+
+export const getTree = (tenantId) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/tree',
+ method: 'get',
+ params: {
+ tenantId,
+ }
+ })
+}
+
+export const remove = (ids) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/remove',
+ method: 'post',
+ params: {
+ ids,
+ }
+ })
+}
+
+export const add = (row) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/submit',
+ method: 'post',
+ data: row
+ })
+}
+
+export const update = (row) => {
+ return request({
+ url: '/api/${serviceName!}/${modelCode!}/submit',
+ method: 'post',
+ data: row
+ })
+}
+
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/tree/const.js.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/tree/const.js.btl
new file mode 100644
index 0000000..69a1999
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/tree/const.js.btl
@@ -0,0 +1,63 @@
+export default {
+ height:'auto',
+ calcHeight: 30,
+ tip: false,
+ searchShow: true,
+ searchMenuSpan: 6,
+ border: true,
+ index: true,
+ viewBtn: true,
+ selection: true,
+ dialogClickModal: false,
+ column: [
+#for(x in prototypes) {
+ {
+ label: "${x.comment!}",
+ prop: "${x.propertyName!}",
+ #if(x.propertyName==treePidHump){
+ type: "tree",
+ dicData: [],
+ props: {
+ label: "title",
+ },
+ value: 0,
+ #}else{
+ type: "${x.componentType!}",
+ #}
+#if(strutil.contain(x.componentType,"date")||strutil.contain(x.componentType,"time")){
+ format: "yyyy-MM-dd hh:mm:ss",
+ valueFormat: "yyyy-MM-dd hh:mm:ss",
+#}
+#if(isNotEmpty(x.dictCode)&&x.propertyName!=treePidHump){
+ dicUrl: "/api/blade-system/dict/dictionary?code=${x.dictCode!}",
+ dataType: "number",
+ props: {
+ label: "dictValue",
+ value: "dictKey"
+ },
+#}
+#if(x.isForm==0){
+ addDisplay: false,
+ editDisplay: false,
+ viewDisplay: false,
+#}
+#if(x.isRow==1){
+ span: 24,
+#}
+#if(x.isList==0){
+ hide: true,
+#}
+#if(x.isQuery==1){
+ search: true,
+#}
+#if(x.isRequired==1){
+ rules: [{
+ required: true,
+ message: "璇疯緭鍏�${x.comment!}",
+ trigger: "blur"
+ }],
+#}
+ },
+#}
+ ]
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/tree/crud.vue.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/tree/crud.vue.btl
new file mode 100644
index 0000000..f3d5d06
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/saber/tree/crud.vue.btl
@@ -0,0 +1,189 @@
+<template>
+ <basic-container>
+ <avue-crud :option="option"
+ :table-loading="loading"
+ :data="data"
+ :page.sync="page"
+ :permission="permissionList"
+ :before-open="beforeOpen"
+ v-model="form"
+ ref="crud"
+ @row-update="rowUpdate"
+ @row-save="rowSave"
+ @row-del="rowDel"
+ @search-change="searchChange"
+ @search-reset="searchReset"
+ @selection-change="selectionChange"
+ @current-change="currentChange"
+ @size-change="sizeChange"
+ @refresh-change="refreshChange"
+ @on-load="onLoad">
+ <template slot="menuLeft">
+ <el-button type="danger"
+ size="small"
+ icon="el-icon-delete"
+ plain
+ v-if="permission.${modelCode!}_delete"
+ @click="handleDelete">鍒� 闄�
+ </el-button>
+ </template>
+ </avue-crud>
+ </basic-container>
+</template>
+
+<script>
+ import {getList, getDetail, getTree, add, update, remove} from "@/api/${serviceCode!}/${modelCode!}";
+ import option from "@/const/${serviceCode!}/${modelCode!}";
+ import {mapGetters} from "vuex";
+
+ export default {
+ data() {
+ return {
+ form: {},
+ query: {},
+ loading: true,
+ page: {
+ pageSize: 10,
+ currentPage: 1,
+ total: 0
+ },
+ selectionList: [],
+ option: option,
+ data: []
+ };
+ },
+ computed: {
+ ...mapGetters(["permission"]),
+ permissionList() {
+ return {
+ addBtn: this.vaildData(this.permission.${modelCode!}_add, false),
+ viewBtn: this.vaildData(this.permission.${modelCode!}_view, false),
+ delBtn: this.vaildData(this.permission.${modelCode!}_delete, false),
+ editBtn: this.vaildData(this.permission.${modelCode!}_edit, false)
+ };
+ },
+ ids() {
+ let ids = [];
+ this.selectionList.forEach(ele => {
+ ids.push(ele.id);
+ });
+ return ids.join(",");
+ }
+ },
+ methods: {
+ rowSave(row, done, loading) {
+ add(row).then(() => {
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ done();
+ }, error => {
+ loading();
+ window.console.log(error);
+ });
+ },
+ rowUpdate(row, index, done, loading) {
+ update(row).then(() => {
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ done();
+ }, error => {
+ loading();
+ console.log(error);
+ });
+ },
+ rowDel(row) {
+ this.$confirm("纭畾灏嗛�夋嫨鏁版嵁鍒犻櫎?", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return remove(row.id);
+ })
+ .then(() => {
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ });
+ },
+ handleDelete() {
+ if (this.selectionList.length === 0) {
+ this.$message.warning("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+ return;
+ }
+ this.$confirm("纭畾灏嗛�夋嫨鏁版嵁鍒犻櫎?", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return remove(this.ids);
+ })
+ .then(() => {
+ this.onLoad(this.page);
+ this.$message({
+ type: "success",
+ message: "鎿嶄綔鎴愬姛!"
+ });
+ this.$refs.crud.toggleSelection();
+ });
+ },
+ beforeOpen(done, type) {
+ if (["edit", "view"].includes(type)) {
+ getDetail(this.form.id).then(res => {
+ this.form = res.data.data;
+ });
+ }
+ done();
+ },
+ searchReset() {
+ this.query = {};
+ this.onLoad(this.page);
+ },
+ searchChange(params, done) {
+ this.query = params;
+ this.page.currentPage = 1;
+ this.onLoad(this.page, params);
+ done();
+ },
+ selectionChange(list) {
+ this.selectionList = list;
+ },
+ selectionClear() {
+ this.selectionList = [];
+ this.$refs.crud.toggleSelection();
+ },
+ currentChange(currentPage){
+ this.page.currentPage = currentPage;
+ },
+ sizeChange(pageSize){
+ this.page.pageSize = pageSize;
+ },
+ refreshChange() {
+ this.onLoad(this.page, this.query);
+ },
+ onLoad(page, params = {}) {
+ this.loading = true;
+ getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
+ this.data = res.data.data;
+ this.loading = false;
+ getTree().then(res => {
+ const column = this.findObject(this.option.column, "${treePidHump!}");
+ column.dicData = res.data.data;
+ });
+ });
+ }
+ }
+ };
+</script>
+
+<style>
+</style>
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sql/menu.sql.btl b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sql/menu.sql.btl
new file mode 100644
index 0000000..246f680
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sql/menu.sql.btl
@@ -0,0 +1,10 @@
+INSERT INTO `blade_menu`(`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES ('${menuId}', 1123598815738675201, '${modelCode!}', '${codeName!}', 'menu', '/${serviceCode!}/${modelCode!}', NULL, 1, 1, 0, 1, NULL, 0);
+INSERT INTO `blade_menu`(`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES ('${addMenuId}', '${menuId}', '${modelCode!}_add', '鏂板', 'add', '/${serviceCode!}/${modelCode!}/add', 'plus', 1, 2, 1, 1, NULL, 0);
+INSERT INTO `blade_menu`(`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES ('${editMenuId}', '${menuId}', '${modelCode!}_edit', '淇敼', 'edit', '/${serviceCode!}/${modelCode!}/edit', 'form', 2, 2, 2, 1, NULL, 0);
+INSERT INTO `blade_menu`(`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES ('${removeMenuId}', '${menuId}', '${modelCode!}_delete', '鍒犻櫎', 'delete', '/api/${serviceName!}/${modelCode!}/remove', 'delete', 3, 2, 3, 1, NULL, 0);
+INSERT INTO `blade_menu`(`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES ('${viewMenuId}', '${menuId}', '${modelCode!}_view', '鏌ョ湅', 'view', '/${serviceCode!}/${modelCode!}/view', 'file-text', 4, 2, 2, 1, NULL, 0);
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/action.js.vm b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/action.js.vm
new file mode 100644
index 0000000..e0eb476
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/action.js.vm
@@ -0,0 +1,37 @@
+#set($upperEntityPath=$table.entityPath.toUpperCase())
+export const $!{upperEntityPath}_NAMESPACE = '$!{table.entityPath}';
+
+export function $!{upperEntityPath}_LIST(payload) {
+ return {
+ type: `${$!{upperEntityPath}_NAMESPACE}/fetchList`,
+ payload,
+ };
+}
+
+export function $!{upperEntityPath}_DETAIL(id) {
+ return {
+ type: `${$!{upperEntityPath}_NAMESPACE}/fetchDetail`,
+ payload: { id },
+ };
+}
+
+export function $!{upperEntityPath}_CLEAR_DETAIL() {
+ return {
+ type: `${$!{upperEntityPath}_NAMESPACE}/clearDetail`,
+ payload: {},
+ };
+}
+
+export function $!{upperEntityPath}_SUBMIT(payload) {
+ return {
+ type: `${$!{upperEntityPath}_NAMESPACE}/submit`,
+ payload,
+ };
+}
+
+export function $!{upperEntityPath}_REMOVE(payload) {
+ return {
+ type: `${$!{upperEntityPath}_NAMESPACE}/remove`,
+ payload,
+ };
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/add.js.vm b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/add.js.vm
new file mode 100644
index 0000000..dd17383
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/add.js.vm
@@ -0,0 +1,75 @@
+#set($upperEntityPath=$table.entityPath.toUpperCase())
+import React, { PureComponent } from 'react';
+import { Form, Input, Card, Button } from 'antd';
+import { connect } from 'dva';
+import Panel from '../../../components/Panel';
+import styles from '../../../layouts/Sword.less';
+import { $!{upperEntityPath}_SUBMIT } from '../../../actions/$!{table.entityPath}';
+
+const FormItem = Form.Item;
+
+@connect(({ loading }) => ({
+ submitting: loading.effects['$!{table.entityPath}/submit'],
+}))
+@Form.create()
+class $!{entity}Add extends PureComponent {
+ handleSubmit = e => {
+ e.preventDefault();
+ const { dispatch, form } = this.props;
+ form.validateFieldsAndScroll((err, values) => {
+ if (!err) {
+ dispatch($!{upperEntityPath}_SUBMIT(values));
+ }
+ });
+ };
+
+ render() {
+ const {
+ form: { getFieldDecorator },
+ submitting,
+ } = this.props;
+
+ const formItemLayout = {
+ labelCol: {
+ xs: { span: 24 },
+ sm: { span: 7 },
+ },
+ wrapperCol: {
+ xs: { span: 24 },
+ sm: { span: 12 },
+ md: { span: 10 },
+ },
+ };
+
+ const action = (
+ <Button type="primary" onClick={this.handleSubmit} loading={submitting}>
+ 鎻愪氦
+ </Button>
+ );
+
+ return (
+ <Panel title="鏂板" back="/$!{servicePackage}/$!{table.entityPath}" action={action}>
+ <Form hideRequiredMark style={{ marginTop: 8 }}>
+ <Card className={styles.card} bordered={false}>
+#foreach($field in $!{table.fields})
+#if($!{field.name}!=$!{tenantColumn})
+ <FormItem {...formItemLayout} label="$!{field.comment}">
+ {getFieldDecorator('$!{field.propertyName}', {
+ rules: [
+ {
+ required: true,
+ message: '璇疯緭鍏�$!{field.comment}',
+ },
+ ],
+ })(<Input placeholder="璇疯緭鍏�$!{field.comment}" />)}
+ </FormItem>
+#end
+#end
+ </Card>
+ </Form>
+ </Panel>
+ );
+ }
+}
+
+export default $!{entity}Add;
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/edit.js.vm b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/edit.js.vm
new file mode 100644
index 0000000..7a4f33d
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/edit.js.vm
@@ -0,0 +1,99 @@
+#set($upperEntityPath=$table.entityPath.toUpperCase())
+import React, { PureComponent } from 'react';
+import { Form, Input, Card, Button } from 'antd';
+import { connect } from 'dva';
+import Panel from '../../../components/Panel';
+import styles from '../../../layouts/Sword.less';
+import { $!{upperEntityPath}_DETAIL, $!{upperEntityPath}_SUBMIT } from '../../../actions/$!{table.entityPath}';
+
+const FormItem = Form.Item;
+
+@connect(({ $!{table.entityPath}, loading }) => ({
+ $!{table.entityPath},
+ submitting: loading.effects['$!{table.entityPath}/submit'],
+}))
+@Form.create()
+class $!{entity}Edit extends PureComponent {
+ componentWillMount() {
+ const {
+ dispatch,
+ match: {
+ params: { id },
+ },
+ } = this.props;
+ dispatch($!{upperEntityPath}_DETAIL(id));
+ }
+
+ handleSubmit = e => {
+ e.preventDefault();
+ const {
+ dispatch,
+ match: {
+ params: { id },
+ },
+ form,
+ } = this.props;
+ form.validateFieldsAndScroll((err, values) => {
+ if (!err) {
+ const params = {
+ id,
+ ...values,
+ };
+ console.log(params);
+ dispatch($!{upperEntityPath}_SUBMIT(params));
+ }
+ });
+ };
+
+ render() {
+ const {
+ form: { getFieldDecorator },
+ $!{table.entityPath}: { detail },
+ submitting,
+ } = this.props;
+
+ const formItemLayout = {
+ labelCol: {
+ xs: { span: 24 },
+ sm: { span: 7 },
+ },
+ wrapperCol: {
+ xs: { span: 24 },
+ sm: { span: 12 },
+ md: { span: 10 },
+ },
+ };
+
+ const action = (
+ <Button type="primary" onClick={this.handleSubmit} loading={submitting}>
+ 鎻愪氦
+ </Button>
+ );
+
+ return (
+ <Panel title="淇敼" back="/$!{servicePackage}/$!{table.entityPath}" action={action}>
+ <Form hideRequiredMark style={{ marginTop: 8 }}>
+ <Card className={styles.card} bordered={false}>
+#foreach($field in $!{table.fields})
+#if($!{field.name}!=$!{tenantColumn})
+ <FormItem {...formItemLayout} label="$!{field.comment}">
+ {getFieldDecorator('$!{field.propertyName}', {
+ rules: [
+ {
+ required: true,
+ message: '璇疯緭鍏�$!{field.comment}',
+ },
+ ],
+ initialValue: detail.$!{field.propertyName},
+ })(<Input placeholder="璇疯緭鍏�$!{field.comment}" />)}
+ </FormItem>
+#end
+#end
+ </Card>
+ </Form>
+ </Panel>
+ );
+ }
+}
+
+export default $!{entity}Edit;
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/list.js.vm b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/list.js.vm
new file mode 100644
index 0000000..298de7e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/list.js.vm
@@ -0,0 +1,84 @@
+#set($upperEntityPath=$table.entityPath.toUpperCase())
+import React, { PureComponent } from 'react';
+import { connect } from 'dva';
+import { Button, Col, Form, Input, Row } from 'antd';
+import Panel from '../../../components/Panel';
+import { $!{upperEntityPath}_LIST } from '../../../actions/$!{table.entityPath}';
+import Grid from '../../../components/Sword/Grid';
+
+const FormItem = Form.Item;
+
+@connect(({ $!{table.entityPath}, loading }) => ({
+ $!{table.entityPath},
+ loading: loading.models.$!{table.entityPath},
+}))
+@Form.create()
+class $!{entity} extends PureComponent {
+ // ============ 鏌ヨ ===============
+ handleSearch = params => {
+ const { dispatch } = this.props;
+ dispatch($!{upperEntityPath}_LIST(params));
+ };
+
+ // ============ 鏌ヨ琛ㄥ崟 ===============
+ renderSearchForm = onReset => {
+ const { form } = this.props;
+ const { getFieldDecorator } = form;
+
+ return (
+ <Row gutter={{ md: 8, lg: 24, xl: 48 }}>
+ <Col md={6} sm={24}>
+ <FormItem label="鏌ヨ鍚嶇О">
+ {getFieldDecorator('name')(<Input placeholder="鏌ヨ鍚嶇О" />)}
+ </FormItem>
+ </Col>
+ <Col>
+ <div style={{ float: 'right' }}>
+ <Button type="primary" htmlType="submit">
+ 鏌ヨ
+ </Button>
+ <Button style={{ marginLeft: 8 }} onClick={onReset}>
+ 閲嶇疆
+ </Button>
+ </div>
+ </Col>
+ </Row>
+ );
+ };
+
+ render() {
+ const code = '$!{table.entityPath}';
+
+ const {
+ form,
+ loading,
+ $!{table.entityPath}: { data },
+ } = this.props;
+
+ const columns = [
+#foreach($field in $!{table.fields})
+#if($!{field.name}!=$!{tenantColumn})
+ {
+ title: '$!{field.comment}',
+ dataIndex: '$!{field.propertyName}',
+ },
+#end
+#end
+ ];
+
+ return (
+ <Panel>
+ <Grid
+ code={code}
+ form={form}
+ onSearch={this.handleSearch}
+ renderSearchForm={this.renderSearchForm}
+ loading={loading}
+ data={data}
+ columns={columns}
+ />
+ </Panel>
+ );
+ }
+}
+export default $!{entity};
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/model.js.vm b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/model.js.vm
new file mode 100644
index 0000000..6188988
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/model.js.vm
@@ -0,0 +1,88 @@
+#set($upperEntityPath=$table.entityPath.toUpperCase())
+import { message } from 'antd';
+import router from 'umi/router';
+import { $!{upperEntityPath}_NAMESPACE } from '../actions/$!{table.entityPath}';
+import { list, submit, detail, remove } from '../services/$!{table.entityPath}';
+
+export default {
+ namespace: $!{upperEntityPath}_NAMESPACE,
+ state: {
+ data: {
+ list: [],
+ pagination: false,
+ },
+ detail: {},
+ },
+ effects: {
+ *fetchList({ payload }, { call, put }) {
+ const response = yield call(list, payload);
+ if (response.success) {
+ yield put({
+ type: 'saveList',
+ payload: {
+ list: response.data.records,
+ pagination: {
+ total: response.data.total,
+ current: response.data.current,
+ pageSize: response.data.size,
+ },
+ },
+ });
+ }
+ },
+ *fetchDetail({ payload }, { call, put }) {
+ const response = yield call(detail, payload);
+ if (response.success) {
+ yield put({
+ type: 'saveDetail',
+ payload: {
+ detail: response.data,
+ },
+ });
+ }
+ },
+ *clearDetail({ payload }, { put }) {
+ yield put({
+ type: 'removeDetail',
+ payload: { payload },
+ });
+ },
+ *submit({ payload }, { call }) {
+ const response = yield call(submit, payload);
+ if (response.success) {
+ message.success('鎻愪氦鎴愬姛');
+ router.push('/$!{servicePackage}/$!{table.entityPath}');
+ }
+ },
+ *remove({ payload }, { call }) {
+ const {
+ data: { keys },
+ success,
+ } = payload;
+ const response = yield call(remove, { ids: keys });
+ if (response.success) {
+ success();
+ }
+ },
+ },
+ reducers: {
+ saveList(state, action) {
+ return {
+ ...state,
+ data: action.payload,
+ };
+ },
+ saveDetail(state, action) {
+ return {
+ ...state,
+ detail: action.payload.detail,
+ };
+ },
+ removeDetail(state) {
+ return {
+ ...state,
+ detail: {},
+ };
+ },
+ },
+};
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/service.js.vm b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/service.js.vm
new file mode 100644
index 0000000..cf3d16e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/service.js.vm
@@ -0,0 +1,26 @@
+#set($params="$" + "{stringify" + "(params)" + "}")
+import { stringify } from 'qs';
+import func from '../utils/Func';
+import request from '../utils/request';
+
+export async function list(params) {
+ return request(`/api/$!{serviceName}/$!{entityKey}/list?$!{params}`);
+}
+
+export async function submit(params) {
+ return request('/api/$!{serviceName}/$!{entityKey}/submit', {
+ method: 'POST',
+ body: params,
+ });
+}
+
+export async function detail(params) {
+ return request(`/api/$!{serviceName}/$!{entityKey}/detail?$!{params}`);
+}
+
+export async function remove(params) {
+ return request('/api/$!{serviceName}/$!{entityKey}/remove', {
+ method: 'POST',
+ body: func.toFormData(params),
+ });
+}
diff --git a/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/view.js.vm b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/view.js.vm
new file mode 100644
index 0000000..397b23e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-develop/src/main/resources/templates/sword/view.js.vm
@@ -0,0 +1,77 @@
+#set($upperEntityPath=$table.entityPath.toUpperCase())
+#set($editId="$" + "{" + "id" + "}")
+import React, { PureComponent } from 'react';
+import router from 'umi/router';
+import { Form, Card, Button } from 'antd';
+import { connect } from 'dva';
+import Panel from '../../../components/Panel';
+import styles from '../../../layouts/Sword.less';
+import { $!{upperEntityPath}_DETAIL } from '../../../actions/$!{table.entityPath}';
+
+const FormItem = Form.Item;
+
+@connect(({ $!{table.entityPath} }) => ({
+ $!{table.entityPath},
+}))
+@Form.create()
+class $!{entity}View extends PureComponent {
+ componentWillMount() {
+ const {
+ dispatch,
+ match: {
+ params: { id },
+ },
+ } = this.props;
+ dispatch($!{upperEntityPath}_DETAIL(id));
+ }
+
+ handleEdit = () => {
+ const {
+ match: {
+ params: { id },
+ },
+ } = this.props;
+ router.push(`/$!{servicePackage}/$!{table.entityPath}/edit/$!{editId}`);
+ };
+
+ render() {
+ const {
+ $!{table.entityPath}: { detail },
+ } = this.props;
+
+ const formItemLayout = {
+ labelCol: {
+ xs: { span: 24 },
+ sm: { span: 7 },
+ },
+ wrapperCol: {
+ xs: { span: 24 },
+ sm: { span: 12 },
+ md: { span: 10 },
+ },
+ };
+
+ const action = (
+ <Button type="primary" onClick={this.handleEdit}>
+ 淇敼
+ </Button>
+ );
+
+ return (
+ <Panel title="鏌ョ湅" back="/$!{servicePackage}/$!{table.entityPath}" action={action}>
+ <Form hideRequiredMark style={{ marginTop: 8 }}>
+ <Card className={styles.card} bordered={false}>
+#foreach($field in $!{table.fields})
+#if($!{field.name}!=$!{tenantColumn})
+ <FormItem {...formItemLayout} label="$!{field.comment}">
+ <span>{detail.$!{field.propertyName}}</span>
+ </FormItem>
+#end
+#end
+ </Card>
+ </Form>
+ </Panel>
+ );
+ }
+}
+export default $!{entity}View;
diff --git a/Source/BladeX-Tool/blade-starter-ehcache/pom.xml b/Source/BladeX-Tool/blade-starter-ehcache/pom.xml
new file mode 100644
index 0000000..d8fd130
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-ehcache/pom.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-ehcache</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-cache</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.ehcache</groupId>
+ <artifactId>ehcache</artifactId>
+ <version>2.10.5</version>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-ehcache/src/main/java/org/springblade/core/ehcache/EhcacheConfiguration.java b/Source/BladeX-Tool/blade-starter-ehcache/src/main/java/org/springblade/core/ehcache/EhcacheConfiguration.java
new file mode 100644
index 0000000..2d33126
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-ehcache/src/main/java/org/springblade/core/ehcache/EhcacheConfiguration.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.ehcache;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.cache.annotation.EnableCaching;
+
+/**
+ * Ehcache閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@EnableCaching
+@AutoConfiguration
+public class EhcacheConfiguration {
+}
diff --git a/Source/BladeX-Tool/blade-starter-ehcache/src/main/resources/ehcache.xml b/Source/BladeX-Tool/blade-starter-ehcache/src/main/resources/ehcache.xml
new file mode 100644
index 0000000..1c6ad0c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-ehcache/src/main/resources/ehcache.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ehcache updateCheck="false" dynamicConfig="false">
+ <diskStore path="java.io.tmpdir"/>
+
+ <cache name="retry:limit:cache"
+ maxEntriesLocalHeap="2000"
+ eternal="false"
+ timeToIdleSeconds="1800"
+ timeToLiveSeconds="1800"
+ overflowToDisk="false"
+ statistics="true">
+ </cache>
+
+ <!-- =================涓氬姟缂撳瓨================= -->
+
+ <!-- 缂撳瓨鍗婂皬鏃� -->
+ <cache name="blade:biz"
+ maxElementsInMemory="10000"
+ maxElementsOnDisk="100000"
+ eternal="false"
+ timeToIdleSeconds="1800"
+ timeToLiveSeconds="1800"
+ overflowToDisk="false"
+ diskPersistent="false"/>
+
+ <!-- 缂撳瓨鍗婂皬鏃� -->
+ <cache name="blade:dict"
+ maxElementsInMemory="10000"
+ maxElementsOnDisk="100000"
+ eternal="false"
+ timeToIdleSeconds="1800"
+ timeToLiveSeconds="1800"
+ overflowToDisk="false"
+ diskPersistent="false"/>
+
+ <!-- 缂撳瓨鍗婂皬鏃� -->
+ <cache name="blade:menu"
+ maxElementsInMemory="10000"
+ maxElementsOnDisk="100000"
+ eternal="false"
+ timeToIdleSeconds="1800"
+ timeToLiveSeconds="1800"
+ overflowToDisk="false"
+ diskPersistent="false"/>
+
+ <!-- 缂撳瓨鍗婂皬鏃� -->
+ <cache name="blade:user"
+ maxElementsInMemory="10000"
+ maxElementsOnDisk="100000"
+ eternal="false"
+ timeToIdleSeconds="1800"
+ timeToLiveSeconds="1800"
+ overflowToDisk="false"
+ diskPersistent="false"/>
+
+ <!-- 缂撳瓨鍗婂皬鏃� -->
+ <cache name="blade:sys"
+ maxElementsInMemory="10000"
+ maxElementsOnDisk="100000"
+ eternal="false"
+ timeToIdleSeconds="1800"
+ timeToLiveSeconds="1800"
+ overflowToDisk="false"
+ diskPersistent="false"/>
+
+ <!-- 缂撳瓨鍗婂皬鏃� -->
+ <cache name="blade:flow"
+ maxElementsInMemory="10000"
+ maxElementsOnDisk="100000"
+ eternal="false"
+ timeToIdleSeconds="1800"
+ timeToLiveSeconds="1800"
+ overflowToDisk="false"
+ diskPersistent="false"/>
+
+ <!-- =================绯荤粺缂撳瓨================= -->
+ <!-- 缂撳瓨鍗婂皬鏃� -->
+ <cache name="half:hour"
+ maxElementsInMemory="10000"
+ maxElementsOnDisk="100000"
+ eternal="false"
+ timeToIdleSeconds="1800"
+ timeToLiveSeconds="1800"
+ overflowToDisk="false"
+ diskPersistent="false"/>
+
+ <!-- 缂撳瓨涓�灏忔椂 -->
+ <cache name="hour"
+ maxElementsInMemory="10000"
+ maxElementsOnDisk="100000"
+ eternal="false"
+ timeToIdleSeconds="3600"
+ timeToLiveSeconds="3600"
+ overflowToDisk="false"
+ diskPersistent="false"/>
+
+ <!-- 缂撳瓨涓�澶� -->
+ <cache name="one:day"
+ maxElementsInMemory="10000"
+ maxElementsOnDisk="100000"
+ eternal="false"
+ timeToIdleSeconds="86400"
+ timeToLiveSeconds="86400"
+ overflowToDisk="false"
+ diskPersistent="false"/>
+
+ <!--
+ name:缂撳瓨鍚嶇О銆�
+ maxElementsInMemory锛氱紦瀛樻渶澶т釜鏁般��
+ eternal:瀵硅薄鏄惁姘镐箙鏈夋晥锛屼竴浣嗚缃簡锛宼imeout灏嗕笉璧蜂綔鐢ㄣ��
+ timeToIdleSeconds锛氳缃璞″湪澶辨晥鍓嶇殑鍏佽闂茬疆鏃堕棿锛堝崟浣嶏細绉掞級銆備粎褰揺ternal=false瀵硅薄涓嶆槸姘镐箙鏈夋晥鏃朵娇鐢紝鍙�夊睘鎬э紝榛樿鍊兼槸0锛屼篃灏辨槸鍙棽缃椂闂存棤绌峰ぇ銆�
+ timeToLiveSeconds锛氳缃璞″湪澶辨晥鍓嶅厑璁稿瓨娲绘椂闂达紙鍗曚綅锛氱锛夈�傛渶澶ф椂闂翠粙浜庡垱寤烘椂闂村拰澶辨晥鏃堕棿涔嬮棿銆備粎褰揺ternal=false瀵硅薄涓嶆槸姘镐箙鏈夋晥鏃朵娇鐢紝榛樿鏄�0.锛屼篃灏辨槸瀵硅薄瀛樻椿鏃堕棿鏃犵┓澶с��
+ overflowToDisk锛氬綋鍐呭瓨涓璞℃暟閲忚揪鍒癿axElementsInMemory鏃讹紝Ehcache灏嗕細瀵硅薄鍐欏埌纾佺洏涓��
+ diskSpoolBufferSizeMB锛氳繖涓弬鏁拌缃瓺iskStore锛堢鐩樼紦瀛橈級鐨勭紦瀛樺尯澶у皬銆傞粯璁ゆ槸30MB銆傛瘡涓狢ache閮藉簲璇ユ湁鑷繁鐨勪竴涓紦鍐插尯銆�
+ maxElementsOnDisk锛氱‖鐩樻渶澶х紦瀛樹釜鏁般��
+ diskPersistent锛氭槸鍚︾紦瀛樿櫄鎷熸満閲嶅惎鏈熸暟鎹� Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
+ diskExpiryThreadIntervalSeconds锛氱鐩樺け鏁堢嚎绋嬭繍琛屾椂闂撮棿闅旓紝榛樿鏄�120绉掋��
+ memoryStoreEvictionPolicy锛氬綋杈惧埌maxElementsInMemory闄愬埗鏃讹紝Ehcache灏嗕細鏍规嵁鎸囧畾鐨勭瓥鐣ュ幓娓呯悊鍐呭瓨銆傞粯璁ょ瓥鐣ユ槸LRU锛堟渶杩戞渶灏戜娇鐢級銆備綘鍙互璁剧疆涓篎IFO锛堝厛杩涘厛鍑猴級鎴栨槸LFU锛堣緝灏戜娇鐢級銆�
+ clearOnFlush锛氬唴瀛樻暟閲忔渶澶ф椂鏄惁娓呴櫎銆�
+ -->
+ <defaultCache name="default:cache"
+ maxElementsInMemory="10000"
+ eternal="false"
+ timeToIdleSeconds="120"
+ timeToLiveSeconds="120"
+ overflowToDisk="false"
+ maxElementsOnDisk="100000"
+ diskPersistent="false"
+ diskExpiryThreadIntervalSeconds="120"
+ memoryStoreEvictionPolicy="LRU"/>
+
+</ehcache>
diff --git a/Source/BladeX-Tool/blade-starter-excel/pom.xml b/Source/BladeX-Tool/blade-starter-excel/pom.xml
new file mode 100644
index 0000000..a65aefe
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-excel/pom.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-excel</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-launch</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>easyexcel</artifactId>
+ </dependency>
+ </dependencies>
+
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/listener/DataListener.java b/Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/listener/DataListener.java
new file mode 100644
index 0000000..ebd2100
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/listener/DataListener.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.excel.listener;
+
+import com.alibaba.excel.context.AnalysisContext;
+import com.alibaba.excel.event.AnalysisEventListener;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Excel鐩戝惉鍣�
+ *
+ * @author Chill
+ */
+@Getter
+@EqualsAndHashCode(callSuper = true)
+public class DataListener<T> extends AnalysisEventListener<T> {
+
+ /**
+ * 缂撳瓨鐨勬暟鎹垪琛�
+ */
+ private final List<T> dataList = new ArrayList<>();
+
+ @Override
+ public void invoke(T data, AnalysisContext analysisContext) {
+ dataList.add(data);
+ }
+
+ @Override
+ public void doAfterAllAnalysed(AnalysisContext analysisContext) {
+
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/listener/ImportListener.java b/Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/listener/ImportListener.java
new file mode 100644
index 0000000..408730f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/listener/ImportListener.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.excel.listener;
+
+import com.alibaba.excel.context.AnalysisContext;
+import com.alibaba.excel.event.AnalysisEventListener;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.RequiredArgsConstructor;
+import org.springblade.core.excel.support.ExcelImporter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Excel鐩戝惉鍣�
+ *
+ * @author Chill
+ */
+@Data
+@RequiredArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class ImportListener<T> extends AnalysisEventListener<T> {
+
+ /**
+ * 榛樿姣忛殧3000鏉″瓨鍌ㄦ暟鎹簱
+ */
+ private int batchCount = 3000;
+ /**
+ * 缂撳瓨鐨勬暟鎹垪琛�
+ */
+ private List<T> list = new ArrayList<>();
+ /**
+ * 鏁版嵁瀵煎叆绫�
+ */
+ private final ExcelImporter<T> importer;
+
+ @Override
+ public void invoke(T data, AnalysisContext analysisContext) {
+ list.add(data);
+ // 杈惧埌BATCH_COUNT锛屽垯璋冪敤importer鏂规硶鍏ュ簱锛岄槻姝㈡暟鎹嚑涓囨潯鏁版嵁鍦ㄥ唴瀛橈紝瀹规槗OOM
+ if (list.size() >= batchCount) {
+ // 璋冪敤importer鏂规硶
+ importer.save(list);
+ // 瀛樺偍瀹屾垚娓呯悊list
+ list.clear();
+ }
+ }
+
+ @Override
+ public void doAfterAllAnalysed(AnalysisContext analysisContext) {
+ // 璋冪敤importer鏂规硶
+ importer.save(list);
+ // 瀛樺偍瀹屾垚娓呯悊list
+ list.clear();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/support/ExcelException.java b/Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/support/ExcelException.java
new file mode 100644
index 0000000..fb7fb53
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/support/ExcelException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.excel.support;
+
+/**
+ * Excel寮傚父澶勭悊绫�
+ *
+ * @author Chill
+ */
+public class ExcelException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public ExcelException(String message) {
+ super(message);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/support/ExcelImporter.java b/Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/support/ExcelImporter.java
new file mode 100644
index 0000000..4f2047e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/support/ExcelImporter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.excel.support;
+
+import java.util.List;
+
+/**
+ * Excel缁熶竴瀵煎叆鎺ュ彛
+ *
+ * @author Chill
+ */
+public interface ExcelImporter<T> {
+
+ /**
+ * 瀵煎叆鏁版嵁閫昏緫
+ *
+ * @param data 鏁版嵁闆嗗悎
+ */
+ void save(List<T> data);
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/util/ExcelUtil.java b/Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/util/ExcelUtil.java
new file mode 100644
index 0000000..48797b7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-excel/src/main/java/org/springblade/core/excel/util/ExcelUtil.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.excel.util;
+
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.excel.read.builder.ExcelReaderBuilder;
+import com.alibaba.excel.read.listener.ReadListener;
+import com.alibaba.excel.util.DateUtils;
+import com.alibaba.excel.write.handler.WriteHandler;
+import lombok.SneakyThrows;
+import org.apache.commons.codec.Charsets;
+import org.springblade.core.excel.listener.DataListener;
+import org.springblade.core.excel.listener.ImportListener;
+import org.springblade.core.excel.support.ExcelException;
+import org.springblade.core.excel.support.ExcelImporter;
+import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLEncoder;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Excel宸ュ叿绫�
+ *
+ * @author Chill
+ * @apiNote https://www.yuque.com/easyexcel/doc/easyexcel
+ */
+public class ExcelUtil {
+
+ /**
+ * 璇诲彇excel鐨勬墍鏈塻heet鏁版嵁
+ *
+ * @param excel excel鏂囦欢
+ * @return List<Object>
+ */
+ public static <T> List<T> read(MultipartFile excel, Class<T> clazz) {
+ DataListener<T> dataListener = new DataListener<>();
+ ExcelReaderBuilder builder = getReaderBuilder(excel, dataListener, clazz);
+ if (builder == null) {
+ return null;
+ }
+ builder.doReadAll();
+ return dataListener.getDataList();
+ }
+
+ /**
+ * 璇诲彇excel鐨勬寚瀹歴heet鏁版嵁
+ *
+ * @param excel excel鏂囦欢
+ * @param sheetNo sheet搴忓彿(浠�0寮�濮�)
+ * @return List<Object>
+ */
+ public static <T> List<T> read(MultipartFile excel, int sheetNo, Class<T> clazz) {
+ return read(excel, sheetNo, 1, clazz);
+ }
+
+ /**
+ * 璇诲彇excel鐨勬寚瀹歴heet鏁版嵁
+ *
+ * @param excel excel鏂囦欢
+ * @param sheetNo sheet搴忓彿(浠�0寮�濮�)
+ * @param headRowNumber 琛ㄥご琛屾暟
+ * @return List<Object>
+ */
+ public static <T> List<T> read(MultipartFile excel, int sheetNo, int headRowNumber, Class<T> clazz) {
+ DataListener<T> dataListener = new DataListener<>();
+ ExcelReaderBuilder builder = getReaderBuilder(excel, dataListener, clazz);
+ if (builder == null) {
+ return null;
+ }
+ builder.sheet(sheetNo).headRowNumber(headRowNumber).doRead();
+ return dataListener.getDataList();
+ }
+
+ /**
+ * 璇诲彇骞跺鍏ユ暟鎹�
+ *
+ * @param excel excel鏂囦欢
+ * @param importer 瀵煎叆閫昏緫绫�
+ * @param <T> 娉涘瀷
+ */
+ public static <T> void save(MultipartFile excel, ExcelImporter<T> importer, Class<T> clazz) {
+ ImportListener<T> importListener = new ImportListener<>(importer);
+ ExcelReaderBuilder builder = getReaderBuilder(excel, importListener, clazz);
+ if (builder != null) {
+ builder.doReadAll();
+ }
+ }
+
+ /**
+ * 瀵煎嚭excel
+ *
+ * @param response 鍝嶅簲绫�
+ * @param dataList 鏁版嵁鍒楄〃
+ * @param clazz class绫�
+ * @param <T> 娉涘瀷
+ */
+ @SneakyThrows
+ public static <T> void export(HttpServletResponse response, List<T> dataList, Class<T> clazz) {
+ export(response, DateUtils.format(new Date(), DateUtils.DATE_FORMAT_14), "瀵煎嚭鏁版嵁", dataList, clazz);
+ }
+
+ /**
+ * 瀵煎嚭excel
+ *
+ * @param response 鍝嶅簲绫�
+ * @param fileName 鏂囦欢鍚�
+ * @param sheetName sheet鍚�
+ * @param dataList 鏁版嵁鍒楄〃
+ * @param clazz class绫�
+ * @param <T> 娉涘瀷
+ */
+ @SneakyThrows
+ public static <T> void export(HttpServletResponse response, String fileName, String sheetName, List<T> dataList, Class<T> clazz) {
+ response.setContentType("application/vnd.ms-excel");
+ response.setCharacterEncoding(Charsets.UTF_8.name());
+ fileName = URLEncoder.encode(fileName, Charsets.UTF_8.name());
+ response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
+ EasyExcel.write(response.getOutputStream(), clazz).sheet(sheetName).doWrite(dataList);
+ }
+
+ /**
+ * 瀵煎嚭excel
+ *
+ * @param response 鍝嶅簲绫�
+ * @param fileName 鏂囦欢鍚�
+ * @param sheetName sheet鍚�
+ * @param dataList 鏁版嵁鍒楄〃
+ * @param clazz class绫�
+ * @param writeHandler 鑷畾涔夊鐞嗗櫒
+ * @param <T> 娉涘瀷
+ */
+ @SneakyThrows
+ public static <T> void export(HttpServletResponse response, String fileName, String sheetName, List<T> dataList, WriteHandler writeHandler, Class<T> clazz) {
+ response.setContentType("application/vnd.ms-excel");
+ response.setCharacterEncoding(Charsets.UTF_8.name());
+ fileName = URLEncoder.encode(fileName, Charsets.UTF_8.name());
+ response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
+ EasyExcel.write(response.getOutputStream(), clazz).registerWriteHandler(writeHandler).sheet(sheetName).doWrite(dataList);
+ }
+
+ /**
+ * 鑾峰彇鏋勫缓绫�
+ *
+ * @param excel excel鏂囦欢
+ * @param readListener excel鐩戝惉绫�
+ * @return ExcelReaderBuilder
+ */
+ public static <T> ExcelReaderBuilder getReaderBuilder(MultipartFile excel, ReadListener<T> readListener, Class<T> clazz) {
+ String filename = excel.getOriginalFilename();
+ if (StringUtils.isEmpty(filename)) {
+ throw new ExcelException("璇蜂笂浼犳枃浠�!");
+ }
+ if ((!StringUtils.endsWithIgnoreCase(filename, ".xls") && !StringUtils.endsWithIgnoreCase(filename, ".xlsx"))) {
+ throw new ExcelException("璇蜂笂浼犳纭殑excel鏂囦欢!");
+ }
+ InputStream inputStream;
+ try {
+ inputStream = new BufferedInputStream(excel.getInputStream());
+ return EasyExcel.read(inputStream, clazz, readListener);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-flowable/pom.xml b/Source/BladeX-Tool/blade-starter-flowable/pom.xml
new file mode 100644
index 0000000..7f03653
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-flowable/pom.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-flowable</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <properties>
+ <flowable.version>6.4.2</flowable.version>
+ </properties>
+
+ <dependencies>
+ <!-- 宸ヤ綔娴� -->
+ <dependency>
+ <groupId>org.flowable</groupId>
+ <artifactId>flowable-spring-boot-starter</artifactId>
+ <version>${flowable.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.flowable</groupId>
+ <artifactId>flowable-json-converter</artifactId>
+ <version>${flowable.version}</version>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-flowable/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java b/Source/BladeX-Tool/blade-starter-flowable/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java
new file mode 100644
index 0000000..21ad6d3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-flowable/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java
@@ -0,0 +1,1647 @@
+/* Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.flowable.common.engine.impl;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.ibatis.builder.xml.XMLConfigBuilder;
+import org.apache.ibatis.builder.xml.XMLMapperBuilder;
+import org.apache.ibatis.datasource.pooled.PooledDataSource;
+import org.apache.ibatis.mapping.Environment;
+import org.apache.ibatis.plugin.Interceptor;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
+import org.apache.ibatis.transaction.TransactionFactory;
+import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
+import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;
+import org.flowable.common.engine.api.FlowableException;
+import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher;
+import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
+import org.flowable.common.engine.impl.cfg.CommandExecutorImpl;
+import org.flowable.common.engine.impl.cfg.IdGenerator;
+import org.flowable.common.engine.impl.cfg.TransactionContextFactory;
+import org.flowable.common.engine.impl.cfg.standalone.StandaloneMybatisTransactionContextFactory;
+import org.flowable.common.engine.impl.db.*;
+import org.flowable.common.engine.impl.event.EventDispatchAction;
+import org.flowable.common.engine.impl.interceptor.*;
+import org.flowable.common.engine.impl.persistence.GenericManagerFactory;
+import org.flowable.common.engine.impl.persistence.StrongUuidGenerator;
+import org.flowable.common.engine.impl.persistence.cache.EntityCache;
+import org.flowable.common.engine.impl.persistence.cache.EntityCacheImpl;
+import org.flowable.common.engine.impl.persistence.entity.Entity;
+import org.flowable.common.engine.impl.runtime.Clock;
+import org.flowable.common.engine.impl.service.CommonEngineServiceImpl;
+import org.flowable.common.engine.impl.util.DefaultClockImpl;
+import org.flowable.common.engine.impl.util.IoUtil;
+import org.flowable.common.engine.impl.util.ReflectUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.naming.InitialContext;
+import javax.sql.DataSource;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.sql.*;
+import java.util.*;
+
+public abstract class AbstractEngineConfiguration {
+
+ protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+ /** The tenant id indicating 'no tenant' */
+ public static final String NO_TENANT_ID = "";
+
+ /**
+ * Checks the version of the DB schema against the library when the form engine is being created and throws an exception if the versions don't match.
+ */
+ public static final String DB_SCHEMA_UPDATE_FALSE = "false";
+ public static final String DB_SCHEMA_UPDATE_CREATE = "create";
+ public static final String DB_SCHEMA_UPDATE_CREATE_DROP = "create-drop";
+
+ /**
+ * Creates the schema when the form engine is being created and drops the schema when the form engine is being closed.
+ */
+ public static final String DB_SCHEMA_UPDATE_DROP_CREATE = "drop-create";
+
+ /**
+ * Upon building of the process engine, a check is performed and an update of the schema is performed if it is necessary.
+ */
+ public static final String DB_SCHEMA_UPDATE_TRUE = "true";
+
+ protected boolean forceCloseMybatisConnectionPool = true;
+
+ protected String databaseType;
+ protected String jdbcDriver = "org.h2.Driver";
+ protected String jdbcUrl = "jdbc:h2:tcp://localhost/~/flowable";
+ protected String jdbcUsername = "sa";
+ protected String jdbcPassword = "";
+ protected String dataSourceJndiName;
+ protected int jdbcMaxActiveConnections;
+ protected int jdbcMaxIdleConnections;
+ protected int jdbcMaxCheckoutTime;
+ protected int jdbcMaxWaitTime;
+ protected boolean jdbcPingEnabled;
+ protected String jdbcPingQuery;
+ protected int jdbcPingConnectionNotUsedFor;
+ protected int jdbcDefaultTransactionIsolationLevel;
+ protected DataSource dataSource;
+ protected SchemaManager commonSchemaManager;
+ protected SchemaManager schemaManager;
+ protected Command<Void> schemaManagementCmd;
+
+ protected String databaseSchemaUpdate = DB_SCHEMA_UPDATE_FALSE;
+
+ protected String xmlEncoding = "UTF-8";
+
+ // COMMAND EXECUTORS ///////////////////////////////////////////////
+
+ protected CommandExecutor commandExecutor;
+ protected Collection<? extends CommandInterceptor> defaultCommandInterceptors;
+ protected CommandConfig defaultCommandConfig;
+ protected CommandConfig schemaCommandConfig;
+ protected CommandContextFactory commandContextFactory;
+ protected CommandInterceptor commandInvoker;
+
+ protected List<CommandInterceptor> customPreCommandInterceptors;
+ protected List<CommandInterceptor> customPostCommandInterceptors;
+ protected List<CommandInterceptor> commandInterceptors;
+
+ protected Map<String, AbstractEngineConfiguration> engineConfigurations = new HashMap<>();
+ protected Map<String, AbstractServiceConfiguration> serviceConfigurations = new HashMap<>();
+
+ protected ClassLoader classLoader;
+ /**
+ * Either use Class.forName or ClassLoader.loadClass for class loading. See http://forums.activiti.org/content/reflectutilloadclass-and-custom- classloader
+ */
+ protected boolean useClassForNameClassLoading = true;
+
+ // MYBATIS SQL SESSION FACTORY /////////////////////////////////////
+
+ protected boolean isDbHistoryUsed = true;
+ protected DbSqlSessionFactory dbSqlSessionFactory;
+ protected SqlSessionFactory sqlSessionFactory;
+ protected TransactionFactory transactionFactory;
+ protected TransactionContextFactory transactionContextFactory;
+
+ /**
+ * If set to true, enables bulk insert (grouping sql inserts together). Default true.
+ * For some databases (eg DB2+z/OS) needs to be set to false.
+ */
+ protected boolean isBulkInsertEnabled = true;
+
+ /**
+ * Some databases have a limit of how many parameters one sql insert can have (eg SQL Server, 2000 params (!= insert statements) ). Tweak this parameter in case of exceptions indicating too much
+ * is being put into one bulk insert, or make it higher if your database can cope with it and there are inserts with a huge amount of data.
+ * <p>
+ * By default: 100 (75 for mssql server as it has a hard limit of 2000 parameters in a statement)
+ */
+ protected int maxNrOfStatementsInBulkInsert = 100;
+
+ public int DEFAULT_MAX_NR_OF_STATEMENTS_BULK_INSERT_SQL_SERVER = 60; // currently Execution has most params (31). 2000 / 31 = 64.
+
+ protected Set<Class<?>> customMybatisMappers;
+ protected Set<String> customMybatisXMLMappers;
+ protected List<Interceptor> customMybatisInterceptors;
+
+
+ protected Set<String> dependentEngineMyBatisXmlMappers;
+ protected List<MybatisTypeAliasConfigurator> dependentEngineMybatisTypeAliasConfigs;
+ protected List<MybatisTypeHandlerConfigurator> dependentEngineMybatisTypeHandlerConfigs;
+
+ // SESSION FACTORIES ///////////////////////////////////////////////
+ protected List<SessionFactory> customSessionFactories;
+ protected Map<Class<?>, SessionFactory> sessionFactories;
+
+ protected boolean enableEventDispatcher = true;
+ protected FlowableEventDispatcher eventDispatcher;
+ protected List<FlowableEventListener> eventListeners;
+ protected Map<String, List<FlowableEventListener>> typedEventListeners;
+ protected List<EventDispatchAction> additionalEventDispatchActions;
+
+ protected boolean transactionsExternallyManaged;
+
+ /**
+ * Flag that can be set to configure or not a relational database is used. This is useful for custom implementations that do not use relational databases at all.
+ *
+ * If true (default), the {@link AbstractEngineConfiguration#getDatabaseSchemaUpdate()} value will be used to determine what needs to happen wrt the database schema.
+ *
+ * If false, no validation or schema creation will be done. That means that the database schema must have been created 'manually' before but the engine does not validate whether the schema is
+ * correct. The {@link AbstractEngineConfiguration#getDatabaseSchemaUpdate()} value will not be used.
+ */
+ protected boolean usingRelationalDatabase = true;
+
+ /**
+ * Flag that can be set to configure whether or not a schema is used. This is usefil for custom implementations that do not use relational databases at all.
+ * Setting {@link #usingRelationalDatabase} to true will automotically imply using a schema.
+ */
+ protected boolean usingSchemaMgmt = true;
+
+ /**
+ * Allows configuring a database table prefix which is used for all runtime operations of the process engine. For example, if you specify a prefix named 'PRE1.', Flowable will query for executions
+ * in a table named 'PRE1.ACT_RU_EXECUTION_'.
+ *
+ * <p />
+ * <strong>NOTE: the prefix is not respected by automatic database schema management. If you use {@link AbstractEngineConfiguration#DB_SCHEMA_UPDATE_CREATE_DROP} or
+ * {@link AbstractEngineConfiguration#DB_SCHEMA_UPDATE_TRUE}, Flowable will create the database tables using the default names, regardless of the prefix configured here.</strong>
+ */
+ protected String databaseTablePrefix = "";
+
+ /**
+ * Escape character for doing wildcard searches.
+ *
+ * This will be added at then end of queries that include for example a LIKE clause. For example: SELECT * FROM table WHERE column LIKE '%\%%' ESCAPE '\';
+ */
+ protected String databaseWildcardEscapeCharacter;
+
+ /**
+ * database catalog to use
+ */
+ protected String databaseCatalog = "";
+
+ /**
+ * In some situations you want to set the schema to use for table checks / generation if the database metadata doesn't return that correctly, see https://jira.codehaus.org/browse/ACT-1220,
+ * https://jira.codehaus.org/browse/ACT-1062
+ */
+ protected String databaseSchema;
+
+ /**
+ * Set to true in case the defined databaseTablePrefix is a schema-name, instead of an actual table name prefix. This is relevant for checking if Flowable-tables exist, the databaseTablePrefix
+ * will not be used here - since the schema is taken into account already, adding a prefix for the table-check will result in wrong table-names.
+ */
+ protected boolean tablePrefixIsSchema;
+
+ /**
+ * Set to true if the latest version of a definition should be retrieved, ignoring a possible parent deployment id value
+ */
+ protected boolean alwaysLookupLatestDefinitionVersion;
+
+ /**
+ * Set to true if by default lookups should fallback to the default tenant (an empty string by default or a defined tenant value)
+ */
+ protected boolean fallbackToDefaultTenant;
+
+ /**
+ * Default tenant provider that is executed when looking up definitions, in case the global or local fallback to default tenant value is true
+ */
+ protected DefaultTenantProvider defaultTenantProvider = (tenantId, scope, scopeKey) -> NO_TENANT_ID;
+
+ /**
+ * Enables the MyBatis plugin that logs the execution time of sql statements.
+ */
+ protected boolean enableLogSqlExecutionTime;
+
+ protected Properties databaseTypeMappings = getDefaultDatabaseTypeMappings();
+
+ protected List<EngineDeployer> customPreDeployers;
+ protected List<EngineDeployer> customPostDeployers;
+ protected List<EngineDeployer> deployers;
+
+ // CONFIGURATORS ////////////////////////////////////////////////////////////
+
+ protected boolean enableConfiguratorServiceLoader = true; // Enabled by default. In certain environments this should be set to false (eg osgi)
+ protected List<EngineConfigurator> configurators; // The injected configurators
+ protected List<EngineConfigurator> allConfigurators; // Including auto-discovered configurators
+ protected EngineConfigurator idmEngineConfigurator;
+
+ public static final String PRODUCT_NAME_POSTGRES = "PostgreSQL";
+ public static final String PRODUCT_NAME_CRDB = "CockroachDB";
+
+ public static final String DATABASE_TYPE_H2 = "h2";
+ public static final String DATABASE_TYPE_HSQL = "hsql";
+ public static final String DATABASE_TYPE_MYSQL = "mysql";
+ public static final String DATABASE_TYPE_ORACLE = "oracle";
+ public static final String DATABASE_TYPE_POSTGRES = "postgres";
+ public static final String DATABASE_TYPE_MSSQL = "mssql";
+ public static final String DATABASE_TYPE_DB2 = "db2";
+ public static final String DATABASE_TYPE_COCKROACHDB = "cockroachdb";
+ public static final String DATABASE_TYPE_DM = "oracle";
+
+ public static Properties getDefaultDatabaseTypeMappings() {
+ Properties databaseTypeMappings = new Properties();
+ databaseTypeMappings.setProperty("H2", DATABASE_TYPE_H2);
+ databaseTypeMappings.setProperty("HSQL Database Engine", DATABASE_TYPE_HSQL);
+ databaseTypeMappings.setProperty("MySQL", DATABASE_TYPE_MYSQL);
+ databaseTypeMappings.setProperty("MariaDB", DATABASE_TYPE_MYSQL);
+ databaseTypeMappings.setProperty("Oracle", DATABASE_TYPE_ORACLE);
+ databaseTypeMappings.setProperty(PRODUCT_NAME_POSTGRES, DATABASE_TYPE_POSTGRES);
+ databaseTypeMappings.setProperty("Microsoft SQL Server", DATABASE_TYPE_MSSQL);
+ databaseTypeMappings.setProperty(DATABASE_TYPE_DB2, DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/NT", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/NT64", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2 UDP", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/LINUX", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/LINUX390", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/LINUXX8664", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/LINUXZ64", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/LINUXPPC64", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/400 SQL", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/6000", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2 UDB iSeries", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/AIX64", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/HPUX", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/HP64", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/SUN", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/SUN64", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/PTX", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/2", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2 UDB AS400", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DM DBMS", DATABASE_TYPE_DM);
+ databaseTypeMappings.setProperty(PRODUCT_NAME_CRDB, DATABASE_TYPE_COCKROACHDB);
+ return databaseTypeMappings;
+ }
+
+ protected Map<Object, Object> beans;
+
+ protected IdGenerator idGenerator;
+ protected boolean usePrefixId;
+
+ protected Clock clock;
+
+ // Variables
+
+ public static final int DEFAULT_GENERIC_MAX_LENGTH_STRING = 4000;
+ public static final int DEFAULT_ORACLE_MAX_LENGTH_STRING = 2000;
+
+ /**
+ * Define a max length for storing String variable types in the database. Mainly used for the Oracle NVARCHAR2 limit of 2000 characters
+ */
+ protected int maxLengthStringVariableType = -1;
+
+ protected void initEngineConfigurations() {
+ engineConfigurations.put(getEngineCfgKey(), this);
+ }
+
+ // DataSource
+ // ///////////////////////////////////////////////////////////////
+
+ protected void initDataSource() {
+ if (dataSource == null) {
+ if (dataSourceJndiName != null) {
+ try {
+ dataSource = (DataSource) new InitialContext().lookup(dataSourceJndiName);
+ } catch (Exception e) {
+ throw new FlowableException("couldn't lookup datasource from " + dataSourceJndiName + ": " + e.getMessage(), e);
+ }
+
+ } else if (jdbcUrl != null) {
+ if ((jdbcDriver == null) || (jdbcUsername == null)) {
+ throw new FlowableException("DataSource or JDBC properties have to be specified in a process engine configuration");
+ }
+
+ logger.debug("initializing datasource to db: {}", jdbcUrl);
+
+ if (logger.isInfoEnabled()) {
+ logger.info("Configuring Datasource with following properties (omitted password for security)");
+ logger.info("datasource driver : {}", jdbcDriver);
+ logger.info("datasource url : {}", jdbcUrl);
+ logger.info("datasource user name : {}", jdbcUsername);
+ }
+
+ PooledDataSource pooledDataSource = new PooledDataSource(this.getClass().getClassLoader(), jdbcDriver, jdbcUrl, jdbcUsername, jdbcPassword);
+
+ if (jdbcMaxActiveConnections > 0) {
+ pooledDataSource.setPoolMaximumActiveConnections(jdbcMaxActiveConnections);
+ }
+ if (jdbcMaxIdleConnections > 0) {
+ pooledDataSource.setPoolMaximumIdleConnections(jdbcMaxIdleConnections);
+ }
+ if (jdbcMaxCheckoutTime > 0) {
+ pooledDataSource.setPoolMaximumCheckoutTime(jdbcMaxCheckoutTime);
+ }
+ if (jdbcMaxWaitTime > 0) {
+ pooledDataSource.setPoolTimeToWait(jdbcMaxWaitTime);
+ }
+ if (jdbcPingEnabled) {
+ pooledDataSource.setPoolPingEnabled(true);
+ if (jdbcPingQuery != null) {
+ pooledDataSource.setPoolPingQuery(jdbcPingQuery);
+ }
+ pooledDataSource.setPoolPingConnectionsNotUsedFor(jdbcPingConnectionNotUsedFor);
+ }
+ if (jdbcDefaultTransactionIsolationLevel > 0) {
+ pooledDataSource.setDefaultTransactionIsolationLevel(jdbcDefaultTransactionIsolationLevel);
+ }
+ dataSource = pooledDataSource;
+ }
+ }
+
+ if (databaseType == null) {
+ initDatabaseType();
+ }
+ }
+
+ public void initDatabaseType() {
+ Connection connection = null;
+ try {
+ connection = dataSource.getConnection();
+ DatabaseMetaData databaseMetaData = connection.getMetaData();
+ String databaseProductName = databaseMetaData.getDatabaseProductName();
+ logger.debug("database product name: '{}'", databaseProductName);
+
+ // CRDB does not expose the version through the jdbc driver, so we need to fetch it through version().
+ if (PRODUCT_NAME_POSTGRES.equalsIgnoreCase(databaseProductName)) {
+ PreparedStatement preparedStatement = connection.prepareStatement("select version() as version;");
+ ResultSet resultSet = preparedStatement.executeQuery();
+ String version = null;
+ if (resultSet.next()) {
+ version = resultSet.getString("version");
+ }
+ resultSet.close();
+
+ if (StringUtils.isNotEmpty(version) && version.toLowerCase().startsWith(PRODUCT_NAME_CRDB.toLowerCase())) {
+ databaseProductName = PRODUCT_NAME_CRDB;
+ logger.info("CockroachDB version '{}' detected", version);
+ }
+ }
+
+ databaseType = databaseTypeMappings.getProperty(databaseProductName);
+ if (databaseType == null) {
+ throw new FlowableException("couldn't deduct database type from database product name '" + databaseProductName + "'");
+ }
+ logger.debug("using database type: {}", databaseType);
+
+ } catch (SQLException e) {
+ logger.error("Exception while initializing Database connection", e);
+ } finally {
+ try {
+ if (connection != null) {
+ connection.close();
+ }
+ } catch (SQLException e) {
+ logger.error("Exception while closing the Database connection", e);
+ }
+ }
+
+ // Special care for MSSQL, as it has a hard limit of 2000 params per statement (incl bulk statement).
+ // Especially with executions, with 100 as default, this limit is passed.
+ if (DATABASE_TYPE_MSSQL.equals(databaseType)) {
+ maxNrOfStatementsInBulkInsert = DEFAULT_MAX_NR_OF_STATEMENTS_BULK_INSERT_SQL_SERVER;
+ }
+ }
+
+ public void initSchemaManager() {
+ if (this.commonSchemaManager == null) {
+ this.commonSchemaManager = new CommonDbSchemaManager();
+ }
+ }
+
+ // session factories ////////////////////////////////////////////////////////
+
+ public void addSessionFactory(SessionFactory sessionFactory) {
+ sessionFactories.put(sessionFactory.getSessionType(), sessionFactory);
+ }
+
+ public void initCommandContextFactory() {
+ if (commandContextFactory == null) {
+ commandContextFactory = new CommandContextFactory();
+ }
+ }
+
+ public void initTransactionContextFactory() {
+ if (transactionContextFactory == null) {
+ transactionContextFactory = new StandaloneMybatisTransactionContextFactory();
+ }
+ }
+
+ public void initCommandExecutors() {
+ initDefaultCommandConfig();
+ initSchemaCommandConfig();
+ initCommandInvoker();
+ initCommandInterceptors();
+ initCommandExecutor();
+ }
+
+
+ public void initDefaultCommandConfig() {
+ if (defaultCommandConfig == null) {
+ defaultCommandConfig = new CommandConfig();
+ }
+ }
+
+ public void initSchemaCommandConfig() {
+ if (schemaCommandConfig == null) {
+ schemaCommandConfig = new CommandConfig();
+ }
+ }
+
+ public void initCommandInvoker() {
+ if (commandInvoker == null) {
+ commandInvoker = new DefaultCommandInvoker();
+ }
+ }
+
+ public void initCommandInterceptors() {
+ if (commandInterceptors == null) {
+ commandInterceptors = new ArrayList<>();
+ if (customPreCommandInterceptors != null) {
+ commandInterceptors.addAll(customPreCommandInterceptors);
+ }
+ commandInterceptors.addAll(getDefaultCommandInterceptors());
+ if (customPostCommandInterceptors != null) {
+ commandInterceptors.addAll(customPostCommandInterceptors);
+ }
+ commandInterceptors.add(commandInvoker);
+ }
+ }
+
+ public Collection<? extends CommandInterceptor> getDefaultCommandInterceptors() {
+ if (defaultCommandInterceptors == null) {
+ List<CommandInterceptor> interceptors = new ArrayList<>();
+ interceptors.add(new LogInterceptor());
+
+ if (DATABASE_TYPE_COCKROACHDB.equals(databaseType)) {
+ interceptors.add(new CrDbRetryInterceptor());
+ }
+
+ CommandInterceptor transactionInterceptor = createTransactionInterceptor();
+ if (transactionInterceptor != null) {
+ interceptors.add(transactionInterceptor);
+ }
+
+ if (commandContextFactory != null) {
+ String engineCfgKey = getEngineCfgKey();
+ CommandContextInterceptor commandContextInterceptor = new CommandContextInterceptor(commandContextFactory);
+ engineConfigurations.put(engineCfgKey, this);
+ commandContextInterceptor.setEngineConfigurations(engineConfigurations);
+ commandContextInterceptor.setServiceConfigurations(serviceConfigurations);
+ commandContextInterceptor.setCurrentEngineConfigurationKey(engineCfgKey);
+ interceptors.add(commandContextInterceptor);
+ }
+
+ if (transactionContextFactory != null) {
+ interceptors.add(new TransactionContextInterceptor(transactionContextFactory));
+ }
+
+ List<CommandInterceptor> additionalCommandInterceptors = getAdditionalDefaultCommandInterceptors();
+ if (additionalCommandInterceptors != null) {
+ interceptors.addAll(additionalCommandInterceptors);
+ }
+
+ defaultCommandInterceptors = interceptors;
+ }
+ return defaultCommandInterceptors;
+ }
+
+ public abstract String getEngineCfgKey();
+
+ public List<CommandInterceptor> getAdditionalDefaultCommandInterceptors() {
+ return null;
+ }
+
+ public void initCommandExecutor() {
+ if (commandExecutor == null) {
+ CommandInterceptor first = initInterceptorChain(commandInterceptors);
+ commandExecutor = new CommandExecutorImpl(getDefaultCommandConfig(), first);
+ }
+ }
+
+ public CommandInterceptor initInterceptorChain(List<CommandInterceptor> chain) {
+ if (chain == null || chain.isEmpty()) {
+ throw new FlowableException("invalid command interceptor chain configuration: " + chain);
+ }
+ for (int i = 0; i < chain.size() - 1; i++) {
+ chain.get(i).setNext(chain.get(i + 1));
+ }
+ return chain.get(0);
+ }
+
+ public abstract CommandInterceptor createTransactionInterceptor();
+
+
+ public void initBeans() {
+ if (beans == null) {
+ beans = new HashMap<>();
+ }
+ }
+
+ // id generator
+ // /////////////////////////////////////////////////////////////
+
+ public void initIdGenerator() {
+ if (idGenerator == null) {
+ idGenerator = new StrongUuidGenerator();
+ }
+ }
+
+ public void initClock() {
+ if (clock == null) {
+ clock = new DefaultClockImpl();
+ }
+ }
+
+ // services
+ // /////////////////////////////////////////////////////////////////
+
+ protected void initService(Object service) {
+ if (service instanceof CommonEngineServiceImpl) {
+ ((CommonEngineServiceImpl) service).setCommandExecutor(commandExecutor);
+ }
+ }
+
+ // myBatis SqlSessionFactory
+ // ////////////////////////////////////////////////
+
+ public void initSessionFactories() {
+ if (sessionFactories == null) {
+ sessionFactories = new HashMap<>();
+
+ if (usingRelationalDatabase) {
+ initDbSqlSessionFactory();
+ }
+
+ addSessionFactory(new GenericManagerFactory(EntityCache.class, EntityCacheImpl.class));
+ commandContextFactory.setSessionFactories(sessionFactories);
+ }
+
+ if (customSessionFactories != null) {
+ for (SessionFactory sessionFactory : customSessionFactories) {
+ addSessionFactory(sessionFactory);
+ }
+ }
+ }
+
+ public void initDbSqlSessionFactory() {
+ if (dbSqlSessionFactory == null) {
+ dbSqlSessionFactory = createDbSqlSessionFactory();
+ }
+ dbSqlSessionFactory.setDatabaseType(databaseType);
+ dbSqlSessionFactory.setSqlSessionFactory(sqlSessionFactory);
+ dbSqlSessionFactory.setDbHistoryUsed(isDbHistoryUsed);
+ dbSqlSessionFactory.setDatabaseTablePrefix(databaseTablePrefix);
+ dbSqlSessionFactory.setTablePrefixIsSchema(tablePrefixIsSchema);
+ dbSqlSessionFactory.setDatabaseCatalog(databaseCatalog);
+ dbSqlSessionFactory.setDatabaseSchema(databaseSchema);
+ dbSqlSessionFactory.setMaxNrOfStatementsInBulkInsert(maxNrOfStatementsInBulkInsert);
+
+ initDbSqlSessionFactoryEntitySettings();
+
+ addSessionFactory(dbSqlSessionFactory);
+ }
+
+ public DbSqlSessionFactory createDbSqlSessionFactory() {
+ return new DbSqlSessionFactory(usePrefixId);
+ }
+
+ protected abstract void initDbSqlSessionFactoryEntitySettings();
+
+ protected void defaultInitDbSqlSessionFactoryEntitySettings(List<Class<? extends Entity>> insertOrder, List<Class<? extends Entity>> deleteOrder) {
+ if (insertOrder != null) {
+ for (Class<? extends Entity> clazz : insertOrder) {
+ dbSqlSessionFactory.getInsertionOrder().add(clazz);
+
+ if (isBulkInsertEnabled) {
+ dbSqlSessionFactory.getBulkInserteableEntityClasses().add(clazz);
+ }
+ }
+ }
+
+ if (deleteOrder != null) {
+ for (Class<? extends Entity> clazz : deleteOrder) {
+ dbSqlSessionFactory.getDeletionOrder().add(clazz);
+ }
+ }
+ }
+
+ public void initTransactionFactory() {
+ if (transactionFactory == null) {
+ if (transactionsExternallyManaged) {
+ transactionFactory = new ManagedTransactionFactory();
+ Properties properties = new Properties();
+ properties.put("closeConnection", "false");
+ this.transactionFactory.setProperties(properties);
+ } else {
+ transactionFactory = new JdbcTransactionFactory();
+ }
+ }
+ }
+
+ public void initSqlSessionFactory() {
+ if (sqlSessionFactory == null) {
+ InputStream inputStream = null;
+ try {
+ inputStream = getMyBatisXmlConfigurationStream();
+
+ Environment environment = new Environment("default", transactionFactory, dataSource);
+ Reader reader = new InputStreamReader(inputStream);
+ Properties properties = new Properties();
+ properties.put("prefix", databaseTablePrefix);
+
+ String wildcardEscapeClause = "";
+ if ((databaseWildcardEscapeCharacter != null) && (databaseWildcardEscapeCharacter.length() != 0)) {
+ wildcardEscapeClause = " escape '" + databaseWildcardEscapeCharacter + "'";
+ }
+ properties.put("wildcardEscapeClause", wildcardEscapeClause);
+
+ // set default properties
+ properties.put("limitBefore", "");
+ properties.put("limitAfter", "");
+ properties.put("limitBetween", "");
+ properties.put("limitOuterJoinBetween", "");
+ properties.put("limitBeforeNativeQuery", "");
+ properties.put("blobType", "BLOB");
+ properties.put("boolValue", "TRUE");
+
+ if (databaseType != null) {
+ properties.load(getResourceAsStream(pathToEngineDbProperties()));
+ }
+
+ Configuration configuration = initMybatisConfiguration(environment, reader, properties);
+ sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
+
+ } catch (Exception e) {
+ throw new FlowableException("Error while building ibatis SqlSessionFactory: " + e.getMessage(), e);
+ } finally {
+ IoUtil.closeSilently(inputStream);
+ }
+ }
+ }
+
+ public String pathToEngineDbProperties() {
+ return "org/flowable/common/db/properties/" + databaseType + ".properties";
+ }
+
+ public Configuration initMybatisConfiguration(Environment environment, Reader reader, Properties properties) {
+ XMLConfigBuilder parser = new XMLConfigBuilder(reader, "", properties);
+ Configuration configuration = parser.getConfiguration();
+
+ if (databaseType != null) {
+ configuration.setDatabaseId(databaseType);
+ }
+
+ configuration.setEnvironment(environment);
+
+ initCustomMybatisMappers(configuration);
+ initMybatisTypeHandlers(configuration);
+ initCustomMybatisInterceptors(configuration);
+ if (isEnableLogSqlExecutionTime()) {
+ initMyBatisLogSqlExecutionTimePlugin(configuration);
+ }
+
+ configuration = parseMybatisConfiguration(parser);
+ return configuration;
+ }
+
+ public void initCustomMybatisMappers(Configuration configuration) {
+ if (getCustomMybatisMappers() != null) {
+ for (Class<?> clazz : getCustomMybatisMappers()) {
+ configuration.addMapper(clazz);
+ }
+ }
+ }
+
+ public void initMybatisTypeHandlers(Configuration configuration) {
+ // To be extended
+ }
+
+ public void initCustomMybatisInterceptors(Configuration configuration) {
+ if (customMybatisInterceptors!=null){
+ for (Interceptor interceptor :customMybatisInterceptors){
+ configuration.addInterceptor(interceptor);
+ }
+ }
+ }
+
+ public void initMyBatisLogSqlExecutionTimePlugin(Configuration configuration) {
+ configuration.addInterceptor(new LogSqlExecutionTimePlugin());
+ }
+
+ public Configuration parseMybatisConfiguration(XMLConfigBuilder parser) {
+ Configuration configuration = parser.parse();
+
+ if (dependentEngineMybatisTypeAliasConfigs != null) {
+ for (MybatisTypeAliasConfigurator typeAliasConfig : dependentEngineMybatisTypeAliasConfigs) {
+ typeAliasConfig.configure(configuration.getTypeAliasRegistry());
+ }
+ }
+ if (dependentEngineMybatisTypeHandlerConfigs != null) {
+ for (MybatisTypeHandlerConfigurator typeHandlerConfig : dependentEngineMybatisTypeHandlerConfigs) {
+ typeHandlerConfig.configure(configuration.getTypeHandlerRegistry());
+ }
+ }
+
+ parseDependentEngineMybatisXMLMappers(configuration);
+ parseCustomMybatisXMLMappers(configuration);
+ return configuration;
+ }
+
+ public void parseCustomMybatisXMLMappers(Configuration configuration) {
+ if (getCustomMybatisXMLMappers() != null) {
+ for (String resource : getCustomMybatisXMLMappers()) {
+ parseMybatisXmlMapping(configuration, resource);
+ }
+ }
+ }
+
+ public void parseDependentEngineMybatisXMLMappers(Configuration configuration) {
+ if (getDependentEngineMyBatisXmlMappers() != null) {
+ for (String resource : getDependentEngineMyBatisXmlMappers()) {
+ parseMybatisXmlMapping(configuration, resource);
+ }
+ }
+ }
+
+ protected void parseMybatisXmlMapping(Configuration configuration, String resource) {
+ // see XMLConfigBuilder.mapperElement()
+ XMLMapperBuilder mapperParser = new XMLMapperBuilder(getResourceAsStream(resource), configuration, resource, configuration.getSqlFragments());
+ mapperParser.parse();
+ }
+
+ protected InputStream getResourceAsStream(String resource) {
+ ClassLoader classLoader = getClassLoader();
+ if (classLoader != null) {
+ return getClassLoader().getResourceAsStream(resource);
+ } else {
+ return this.getClass().getClassLoader().getResourceAsStream(resource);
+ }
+ }
+
+ public abstract InputStream getMyBatisXmlConfigurationStream();
+
+ public void initConfigurators() {
+
+ allConfigurators = new ArrayList<>();
+ allConfigurators.addAll(getEngineSpecificEngineConfigurators());
+
+ // Configurators that are explicitly added to the config
+ if (configurators != null) {
+ allConfigurators.addAll(configurators);
+ }
+
+ // Auto discovery through ServiceLoader
+ if (enableConfiguratorServiceLoader) {
+ ClassLoader classLoader = getClassLoader();
+ if (classLoader == null) {
+ classLoader = ReflectUtil.getClassLoader();
+ }
+
+ ServiceLoader<EngineConfigurator> configuratorServiceLoader = ServiceLoader.load(EngineConfigurator.class, classLoader);
+ int nrOfServiceLoadedConfigurators = 0;
+ for (EngineConfigurator configurator : configuratorServiceLoader) {
+ allConfigurators.add(configurator);
+ nrOfServiceLoadedConfigurators++;
+ }
+
+ if (nrOfServiceLoadedConfigurators > 0) {
+ logger.info("Found {} auto-discoverable Process Engine Configurator{}", nrOfServiceLoadedConfigurators, nrOfServiceLoadedConfigurators > 1 ? "s" : "");
+ }
+
+ if (!allConfigurators.isEmpty()) {
+
+ // Order them according to the priorities (useful for dependent
+ // configurator)
+ Collections.sort(allConfigurators, new Comparator<EngineConfigurator>() {
+ @Override
+ public int compare(EngineConfigurator configurator1, EngineConfigurator configurator2) {
+ int priority1 = configurator1.getPriority();
+ int priority2 = configurator2.getPriority();
+
+ if (priority1 < priority2) {
+ return -1;
+ } else if (priority1 > priority2) {
+ return 1;
+ }
+ return 0;
+ }
+ });
+
+ // Execute the configurators
+ logger.info("Found {} Engine Configurators in total:", allConfigurators.size());
+ for (EngineConfigurator configurator : allConfigurators) {
+ logger.info("{} (priority:{})", configurator.getClass(), configurator.getPriority());
+ }
+
+ }
+
+ }
+ }
+
+ public void close() {
+ if (forceCloseMybatisConnectionPool && dataSource instanceof PooledDataSource) {
+ /*
+ * When the datasource is created by a Flowable engine (i.e. it's an instance of PooledDataSource),
+ * the connection pool needs to be closed when closing the engine.
+ * Note that calling forceCloseAll() multiple times (as is the case when running with multiple engine) is ok.
+ */
+ ((PooledDataSource) dataSource).forceCloseAll();
+ }
+ }
+
+ protected List<EngineConfigurator> getEngineSpecificEngineConfigurators() {
+ // meant to be overridden if needed
+ return Collections.emptyList();
+ }
+
+ public void configuratorsBeforeInit() {
+ for (EngineConfigurator configurator : allConfigurators) {
+ logger.info("Executing beforeInit() of {} (priority:{})", configurator.getClass(), configurator.getPriority());
+ configurator.beforeInit(this);
+ }
+ }
+
+ public void configuratorsAfterInit() {
+ for (EngineConfigurator configurator : allConfigurators) {
+ logger.info("Executing configure() of {} (priority:{})", configurator.getClass(), configurator.getPriority());
+ configurator.configure(this);
+ }
+ }
+
+ // getters and setters
+ // //////////////////////////////////////////////////////
+
+ public abstract String getEngineName();
+
+ public ClassLoader getClassLoader() {
+ return classLoader;
+ }
+
+ public AbstractEngineConfiguration setClassLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ return this;
+ }
+
+ public boolean isUseClassForNameClassLoading() {
+ return useClassForNameClassLoading;
+ }
+
+ public AbstractEngineConfiguration setUseClassForNameClassLoading(boolean useClassForNameClassLoading) {
+ this.useClassForNameClassLoading = useClassForNameClassLoading;
+ return this;
+ }
+
+ public String getDatabaseType() {
+ return databaseType;
+ }
+
+ public AbstractEngineConfiguration setDatabaseType(String databaseType) {
+ this.databaseType = databaseType;
+ return this;
+ }
+
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
+ public AbstractEngineConfiguration setDataSource(DataSource dataSource) {
+ this.dataSource = dataSource;
+ return this;
+ }
+
+ public SchemaManager getSchemaManager() {
+ return schemaManager;
+ }
+
+ public AbstractEngineConfiguration setSchemaManager(SchemaManager schemaManager) {
+ this.schemaManager = schemaManager;
+ return this;
+ }
+
+ public SchemaManager getCommonSchemaManager() {
+ return commonSchemaManager;
+ }
+
+ public AbstractEngineConfiguration setCommonSchemaManager(SchemaManager commonSchemaManager) {
+ this.commonSchemaManager = commonSchemaManager;
+ return this;
+ }
+
+ public Command<Void> getSchemaManagementCmd() {
+ return schemaManagementCmd;
+ }
+
+ public AbstractEngineConfiguration setSchemaManagementCmd(Command<Void> schemaManagementCmd) {
+ this.schemaManagementCmd = schemaManagementCmd;
+ return this;
+ }
+
+ public String getJdbcDriver() {
+ return jdbcDriver;
+ }
+
+ public AbstractEngineConfiguration setJdbcDriver(String jdbcDriver) {
+ this.jdbcDriver = jdbcDriver;
+ return this;
+ }
+
+ public String getJdbcUrl() {
+ return jdbcUrl;
+ }
+
+ public AbstractEngineConfiguration setJdbcUrl(String jdbcUrl) {
+ this.jdbcUrl = jdbcUrl;
+ return this;
+ }
+
+ public String getJdbcUsername() {
+ return jdbcUsername;
+ }
+
+ public AbstractEngineConfiguration setJdbcUsername(String jdbcUsername) {
+ this.jdbcUsername = jdbcUsername;
+ return this;
+ }
+
+ public String getJdbcPassword() {
+ return jdbcPassword;
+ }
+
+ public AbstractEngineConfiguration setJdbcPassword(String jdbcPassword) {
+ this.jdbcPassword = jdbcPassword;
+ return this;
+ }
+
+ public int getJdbcMaxActiveConnections() {
+ return jdbcMaxActiveConnections;
+ }
+
+ public AbstractEngineConfiguration setJdbcMaxActiveConnections(int jdbcMaxActiveConnections) {
+ this.jdbcMaxActiveConnections = jdbcMaxActiveConnections;
+ return this;
+ }
+
+ public int getJdbcMaxIdleConnections() {
+ return jdbcMaxIdleConnections;
+ }
+
+ public AbstractEngineConfiguration setJdbcMaxIdleConnections(int jdbcMaxIdleConnections) {
+ this.jdbcMaxIdleConnections = jdbcMaxIdleConnections;
+ return this;
+ }
+
+ public int getJdbcMaxCheckoutTime() {
+ return jdbcMaxCheckoutTime;
+ }
+
+ public AbstractEngineConfiguration setJdbcMaxCheckoutTime(int jdbcMaxCheckoutTime) {
+ this.jdbcMaxCheckoutTime = jdbcMaxCheckoutTime;
+ return this;
+ }
+
+ public int getJdbcMaxWaitTime() {
+ return jdbcMaxWaitTime;
+ }
+
+ public AbstractEngineConfiguration setJdbcMaxWaitTime(int jdbcMaxWaitTime) {
+ this.jdbcMaxWaitTime = jdbcMaxWaitTime;
+ return this;
+ }
+
+ public boolean isJdbcPingEnabled() {
+ return jdbcPingEnabled;
+ }
+
+ public AbstractEngineConfiguration setJdbcPingEnabled(boolean jdbcPingEnabled) {
+ this.jdbcPingEnabled = jdbcPingEnabled;
+ return this;
+ }
+
+ public int getJdbcPingConnectionNotUsedFor() {
+ return jdbcPingConnectionNotUsedFor;
+ }
+
+ public AbstractEngineConfiguration setJdbcPingConnectionNotUsedFor(int jdbcPingConnectionNotUsedFor) {
+ this.jdbcPingConnectionNotUsedFor = jdbcPingConnectionNotUsedFor;
+ return this;
+ }
+
+ public int getJdbcDefaultTransactionIsolationLevel() {
+ return jdbcDefaultTransactionIsolationLevel;
+ }
+
+ public AbstractEngineConfiguration setJdbcDefaultTransactionIsolationLevel(int jdbcDefaultTransactionIsolationLevel) {
+ this.jdbcDefaultTransactionIsolationLevel = jdbcDefaultTransactionIsolationLevel;
+ return this;
+ }
+
+ public String getJdbcPingQuery() {
+ return jdbcPingQuery;
+ }
+
+ public AbstractEngineConfiguration setJdbcPingQuery(String jdbcPingQuery) {
+ this.jdbcPingQuery = jdbcPingQuery;
+ return this;
+ }
+
+ public String getDataSourceJndiName() {
+ return dataSourceJndiName;
+ }
+
+ public AbstractEngineConfiguration setDataSourceJndiName(String dataSourceJndiName) {
+ this.dataSourceJndiName = dataSourceJndiName;
+ return this;
+ }
+
+ public CommandConfig getSchemaCommandConfig() {
+ return schemaCommandConfig;
+ }
+
+ public AbstractEngineConfiguration setSchemaCommandConfig(CommandConfig schemaCommandConfig) {
+ this.schemaCommandConfig = schemaCommandConfig;
+ return this;
+ }
+
+ public boolean isTransactionsExternallyManaged() {
+ return transactionsExternallyManaged;
+ }
+
+ public AbstractEngineConfiguration setTransactionsExternallyManaged(boolean transactionsExternallyManaged) {
+ this.transactionsExternallyManaged = transactionsExternallyManaged;
+ return this;
+ }
+
+ public Map<Object, Object> getBeans() {
+ return beans;
+ }
+
+ public AbstractEngineConfiguration setBeans(Map<Object, Object> beans) {
+ this.beans = beans;
+ return this;
+ }
+
+ public IdGenerator getIdGenerator() {
+ return idGenerator;
+ }
+
+ public AbstractEngineConfiguration setIdGenerator(IdGenerator idGenerator) {
+ this.idGenerator = idGenerator;
+ return this;
+ }
+
+ public boolean isUsePrefixId() {
+ return usePrefixId;
+ }
+
+ public AbstractEngineConfiguration setUsePrefixId(boolean usePrefixId) {
+ this.usePrefixId = usePrefixId;
+ return this;
+ }
+
+ public String getXmlEncoding() {
+ return xmlEncoding;
+ }
+
+ public AbstractEngineConfiguration setXmlEncoding(String xmlEncoding) {
+ this.xmlEncoding = xmlEncoding;
+ return this;
+ }
+
+ public CommandConfig getDefaultCommandConfig() {
+ return defaultCommandConfig;
+ }
+
+ public AbstractEngineConfiguration setDefaultCommandConfig(CommandConfig defaultCommandConfig) {
+ this.defaultCommandConfig = defaultCommandConfig;
+ return this;
+ }
+
+ public CommandExecutor getCommandExecutor() {
+ return commandExecutor;
+ }
+
+ public AbstractEngineConfiguration setCommandExecutor(CommandExecutor commandExecutor) {
+ this.commandExecutor = commandExecutor;
+ return this;
+ }
+
+ public CommandContextFactory getCommandContextFactory() {
+ return commandContextFactory;
+ }
+
+ public AbstractEngineConfiguration setCommandContextFactory(CommandContextFactory commandContextFactory) {
+ this.commandContextFactory = commandContextFactory;
+ return this;
+ }
+
+ public CommandInterceptor getCommandInvoker() {
+ return commandInvoker;
+ }
+
+ public AbstractEngineConfiguration setCommandInvoker(CommandInterceptor commandInvoker) {
+ this.commandInvoker = commandInvoker;
+ return this;
+ }
+
+ public List<CommandInterceptor> getCustomPreCommandInterceptors() {
+ return customPreCommandInterceptors;
+ }
+
+ public AbstractEngineConfiguration setCustomPreCommandInterceptors(List<CommandInterceptor> customPreCommandInterceptors) {
+ this.customPreCommandInterceptors = customPreCommandInterceptors;
+ return this;
+ }
+
+ public List<CommandInterceptor> getCustomPostCommandInterceptors() {
+ return customPostCommandInterceptors;
+ }
+
+ public AbstractEngineConfiguration setCustomPostCommandInterceptors(List<CommandInterceptor> customPostCommandInterceptors) {
+ this.customPostCommandInterceptors = customPostCommandInterceptors;
+ return this;
+ }
+
+ public List<CommandInterceptor> getCommandInterceptors() {
+ return commandInterceptors;
+ }
+
+ public AbstractEngineConfiguration setCommandInterceptors(List<CommandInterceptor> commandInterceptors) {
+ this.commandInterceptors = commandInterceptors;
+ return this;
+ }
+
+ public Map<String, AbstractEngineConfiguration> getEngineConfigurations() {
+ return engineConfigurations;
+ }
+
+ public AbstractEngineConfiguration setEngineConfigurations(Map<String, AbstractEngineConfiguration> engineConfigurations) {
+ this.engineConfigurations = engineConfigurations;
+ return this;
+ }
+
+ public void addEngineConfiguration(String key, AbstractEngineConfiguration engineConfiguration) {
+ if (engineConfigurations == null) {
+ engineConfigurations = new HashMap<>();
+ }
+ engineConfigurations.put(key, engineConfiguration);
+ }
+
+ public Map<String, AbstractServiceConfiguration> getServiceConfigurations() {
+ return serviceConfigurations;
+ }
+
+ public AbstractEngineConfiguration setServiceConfigurations(Map<String, AbstractServiceConfiguration> serviceConfigurations) {
+ this.serviceConfigurations = serviceConfigurations;
+ return this;
+ }
+
+ public void addServiceConfiguration(String key, AbstractServiceConfiguration serviceConfiguration) {
+ if (serviceConfigurations == null) {
+ serviceConfigurations = new HashMap<>();
+ }
+ serviceConfigurations.put(key, serviceConfiguration);
+ }
+
+ public void setDefaultCommandInterceptors(Collection<? extends CommandInterceptor> defaultCommandInterceptors) {
+ this.defaultCommandInterceptors = defaultCommandInterceptors;
+ }
+
+ public SqlSessionFactory getSqlSessionFactory() {
+ return sqlSessionFactory;
+ }
+
+ public AbstractEngineConfiguration setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
+ this.sqlSessionFactory = sqlSessionFactory;
+ return this;
+ }
+
+ public boolean isDbHistoryUsed() {
+ return isDbHistoryUsed;
+ }
+
+ public AbstractEngineConfiguration setDbHistoryUsed(boolean isDbHistoryUsed) {
+ this.isDbHistoryUsed = isDbHistoryUsed;
+ return this;
+ }
+
+ public DbSqlSessionFactory getDbSqlSessionFactory() {
+ return dbSqlSessionFactory;
+ }
+
+ public AbstractEngineConfiguration setDbSqlSessionFactory(DbSqlSessionFactory dbSqlSessionFactory) {
+ this.dbSqlSessionFactory = dbSqlSessionFactory;
+ return this;
+ }
+
+ public TransactionFactory getTransactionFactory() {
+ return transactionFactory;
+ }
+
+ public AbstractEngineConfiguration setTransactionFactory(TransactionFactory transactionFactory) {
+ this.transactionFactory = transactionFactory;
+ return this;
+ }
+
+ public TransactionContextFactory getTransactionContextFactory() {
+ return transactionContextFactory;
+ }
+
+ public AbstractEngineConfiguration setTransactionContextFactory(TransactionContextFactory transactionContextFactory) {
+ this.transactionContextFactory = transactionContextFactory;
+ return this;
+ }
+
+ public int getMaxNrOfStatementsInBulkInsert() {
+ return maxNrOfStatementsInBulkInsert;
+ }
+
+ public AbstractEngineConfiguration setMaxNrOfStatementsInBulkInsert(int maxNrOfStatementsInBulkInsert) {
+ this.maxNrOfStatementsInBulkInsert = maxNrOfStatementsInBulkInsert;
+ return this;
+ }
+
+ public boolean isBulkInsertEnabled() {
+ return isBulkInsertEnabled;
+ }
+
+ public AbstractEngineConfiguration setBulkInsertEnabled(boolean isBulkInsertEnabled) {
+ this.isBulkInsertEnabled = isBulkInsertEnabled;
+ return this;
+ }
+
+ public Set<Class<?>> getCustomMybatisMappers() {
+ return customMybatisMappers;
+ }
+
+ public AbstractEngineConfiguration setCustomMybatisMappers(Set<Class<?>> customMybatisMappers) {
+ this.customMybatisMappers = customMybatisMappers;
+ return this;
+ }
+
+ public Set<String> getCustomMybatisXMLMappers() {
+ return customMybatisXMLMappers;
+ }
+
+ public AbstractEngineConfiguration setCustomMybatisXMLMappers(Set<String> customMybatisXMLMappers) {
+ this.customMybatisXMLMappers = customMybatisXMLMappers;
+ return this;
+ }
+
+ public Set<String> getDependentEngineMyBatisXmlMappers() {
+ return dependentEngineMyBatisXmlMappers;
+ }
+
+ public AbstractEngineConfiguration setCustomMybatisInterceptors(List<Interceptor> customMybatisInterceptors) {
+ this.customMybatisInterceptors = customMybatisInterceptors;
+ return this;
+ }
+
+ public List<Interceptor> getCustomMybatisInterceptors() {
+ return customMybatisInterceptors;
+ }
+
+ public AbstractEngineConfiguration setDependentEngineMyBatisXmlMappers(Set<String> dependentEngineMyBatisXmlMappers) {
+ this.dependentEngineMyBatisXmlMappers = dependentEngineMyBatisXmlMappers;
+ return this;
+ }
+
+ public List<MybatisTypeAliasConfigurator> getDependentEngineMybatisTypeAliasConfigs() {
+ return dependentEngineMybatisTypeAliasConfigs;
+ }
+
+ public AbstractEngineConfiguration setDependentEngineMybatisTypeAliasConfigs(List<MybatisTypeAliasConfigurator> dependentEngineMybatisTypeAliasConfigs) {
+ this.dependentEngineMybatisTypeAliasConfigs = dependentEngineMybatisTypeAliasConfigs;
+ return this;
+ }
+
+ public List<MybatisTypeHandlerConfigurator> getDependentEngineMybatisTypeHandlerConfigs() {
+ return dependentEngineMybatisTypeHandlerConfigs;
+ }
+
+ public AbstractEngineConfiguration setDependentEngineMybatisTypeHandlerConfigs(List<MybatisTypeHandlerConfigurator> dependentEngineMybatisTypeHandlerConfigs) {
+ this.dependentEngineMybatisTypeHandlerConfigs = dependentEngineMybatisTypeHandlerConfigs;
+ return this;
+ }
+
+ public List<SessionFactory> getCustomSessionFactories() {
+ return customSessionFactories;
+ }
+
+ public AbstractEngineConfiguration addCustomSessionFactory(SessionFactory sessionFactory) {
+ if (customSessionFactories == null) {
+ customSessionFactories = new ArrayList<>();
+ }
+ customSessionFactories.add(sessionFactory);
+ return this;
+ }
+
+ public AbstractEngineConfiguration setCustomSessionFactories(List<SessionFactory> customSessionFactories) {
+ this.customSessionFactories = customSessionFactories;
+ return this;
+ }
+
+ public boolean isUsingRelationalDatabase() {
+ return usingRelationalDatabase;
+ }
+
+ public AbstractEngineConfiguration setUsingRelationalDatabase(boolean usingRelationalDatabase) {
+ this.usingRelationalDatabase = usingRelationalDatabase;
+ return this;
+ }
+
+ public boolean isUsingSchemaMgmt() {
+ return usingSchemaMgmt;
+ }
+
+ public AbstractEngineConfiguration setUsingSchemaMgmt(boolean usingSchema) {
+ this.usingSchemaMgmt = usingSchema;
+ return this;
+ }
+
+ public String getDatabaseTablePrefix() {
+ return databaseTablePrefix;
+ }
+
+ public AbstractEngineConfiguration setDatabaseTablePrefix(String databaseTablePrefix) {
+ this.databaseTablePrefix = databaseTablePrefix;
+ return this;
+ }
+
+ public String getDatabaseWildcardEscapeCharacter() {
+ return databaseWildcardEscapeCharacter;
+ }
+
+ public AbstractEngineConfiguration setDatabaseWildcardEscapeCharacter(String databaseWildcardEscapeCharacter) {
+ this.databaseWildcardEscapeCharacter = databaseWildcardEscapeCharacter;
+ return this;
+ }
+
+ public String getDatabaseCatalog() {
+ return databaseCatalog;
+ }
+
+ public AbstractEngineConfiguration setDatabaseCatalog(String databaseCatalog) {
+ this.databaseCatalog = databaseCatalog;
+ return this;
+ }
+
+ public String getDatabaseSchema() {
+ return databaseSchema;
+ }
+
+ public AbstractEngineConfiguration setDatabaseSchema(String databaseSchema) {
+ this.databaseSchema = databaseSchema;
+ return this;
+ }
+
+ public boolean isTablePrefixIsSchema() {
+ return tablePrefixIsSchema;
+ }
+
+ public AbstractEngineConfiguration setTablePrefixIsSchema(boolean tablePrefixIsSchema) {
+ this.tablePrefixIsSchema = tablePrefixIsSchema;
+ return this;
+ }
+
+ public boolean isAlwaysLookupLatestDefinitionVersion() {
+ return alwaysLookupLatestDefinitionVersion;
+ }
+
+ public AbstractEngineConfiguration setAlwaysLookupLatestDefinitionVersion(boolean alwaysLookupLatestDefinitionVersion) {
+ this.alwaysLookupLatestDefinitionVersion = alwaysLookupLatestDefinitionVersion;
+ return this;
+ }
+
+ public boolean isFallbackToDefaultTenant() {
+ return fallbackToDefaultTenant;
+ }
+
+ public AbstractEngineConfiguration setFallbackToDefaultTenant(boolean fallbackToDefaultTenant) {
+ this.fallbackToDefaultTenant = fallbackToDefaultTenant;
+ return this;
+ }
+
+ /**
+ * @return name of the default tenant
+ * @deprecated use {@link AbstractEngineConfiguration#getDefaultTenantProvider()} instead
+ */
+ @Deprecated
+ public String getDefaultTenantValue() {
+ return getDefaultTenantProvider().getDefaultTenant(null, null, null);
+ }
+
+ public AbstractEngineConfiguration setDefaultTenantValue(String defaultTenantValue) {
+ this.defaultTenantProvider = (tenantId, scope, scopeKey) -> defaultTenantValue;
+ return this;
+ }
+
+ public DefaultTenantProvider getDefaultTenantProvider() {
+ return defaultTenantProvider;
+ }
+
+ public AbstractEngineConfiguration setDefaultTenantProvider(DefaultTenantProvider defaultTenantProvider) {
+ this.defaultTenantProvider = defaultTenantProvider;
+ return this;
+ }
+
+ public boolean isEnableLogSqlExecutionTime() {
+ return enableLogSqlExecutionTime;
+ }
+
+ public void setEnableLogSqlExecutionTime(boolean enableLogSqlExecutionTime) {
+ this.enableLogSqlExecutionTime = enableLogSqlExecutionTime;
+ }
+
+ public Map<Class<?>, SessionFactory> getSessionFactories() {
+ return sessionFactories;
+ }
+
+ public AbstractEngineConfiguration setSessionFactories(Map<Class<?>, SessionFactory> sessionFactories) {
+ this.sessionFactories = sessionFactories;
+ return this;
+ }
+
+ public String getDatabaseSchemaUpdate() {
+ return databaseSchemaUpdate;
+ }
+
+ public AbstractEngineConfiguration setDatabaseSchemaUpdate(String databaseSchemaUpdate) {
+ this.databaseSchemaUpdate = databaseSchemaUpdate;
+ return this;
+ }
+
+ public boolean isEnableEventDispatcher() {
+ return enableEventDispatcher;
+ }
+
+ public AbstractEngineConfiguration setEnableEventDispatcher(boolean enableEventDispatcher) {
+ this.enableEventDispatcher = enableEventDispatcher;
+ return this;
+ }
+
+ public FlowableEventDispatcher getEventDispatcher() {
+ return eventDispatcher;
+ }
+
+ public AbstractEngineConfiguration setEventDispatcher(FlowableEventDispatcher eventDispatcher) {
+ this.eventDispatcher = eventDispatcher;
+ return this;
+ }
+
+ public List<FlowableEventListener> getEventListeners() {
+ return eventListeners;
+ }
+
+ public AbstractEngineConfiguration setEventListeners(List<FlowableEventListener> eventListeners) {
+ this.eventListeners = eventListeners;
+ return this;
+ }
+
+ public Map<String, List<FlowableEventListener>> getTypedEventListeners() {
+ return typedEventListeners;
+ }
+
+ public AbstractEngineConfiguration setTypedEventListeners(Map<String, List<FlowableEventListener>> typedEventListeners) {
+ this.typedEventListeners = typedEventListeners;
+ return this;
+ }
+
+ public List<EventDispatchAction> getAdditionalEventDispatchActions() {
+ return additionalEventDispatchActions;
+ }
+
+ public AbstractEngineConfiguration setAdditionalEventDispatchActions(List<EventDispatchAction> additionalEventDispatchActions) {
+ this.additionalEventDispatchActions = additionalEventDispatchActions;
+ return this;
+ }
+
+ public Clock getClock() {
+ return clock;
+ }
+
+ public AbstractEngineConfiguration setClock(Clock clock) {
+ this.clock = clock;
+ return this;
+ }
+
+ public int getMaxLengthString() {
+ if (maxLengthStringVariableType == -1) {
+ if ("oracle".equalsIgnoreCase(databaseType)) {
+ return DEFAULT_ORACLE_MAX_LENGTH_STRING;
+ } else {
+ return DEFAULT_GENERIC_MAX_LENGTH_STRING;
+ }
+ } else {
+ return maxLengthStringVariableType;
+ }
+ }
+
+ public int getMaxLengthStringVariableType() {
+ return maxLengthStringVariableType;
+ }
+
+ public AbstractEngineConfiguration setMaxLengthStringVariableType(int maxLengthStringVariableType) {
+ this.maxLengthStringVariableType = maxLengthStringVariableType;
+ return this;
+ }
+
+ public List<EngineDeployer> getDeployers() {
+ return deployers;
+ }
+
+ public AbstractEngineConfiguration setDeployers(List<EngineDeployer> deployers) {
+ this.deployers = deployers;
+ return this;
+ }
+
+ public List<EngineDeployer> getCustomPreDeployers() {
+ return customPreDeployers;
+ }
+
+ public AbstractEngineConfiguration setCustomPreDeployers(List<EngineDeployer> customPreDeployers) {
+ this.customPreDeployers = customPreDeployers;
+ return this;
+ }
+
+ public List<EngineDeployer> getCustomPostDeployers() {
+ return customPostDeployers;
+ }
+
+ public AbstractEngineConfiguration setCustomPostDeployers(List<EngineDeployer> customPostDeployers) {
+ this.customPostDeployers = customPostDeployers;
+ return this;
+ }
+
+ public boolean isEnableConfiguratorServiceLoader() {
+ return enableConfiguratorServiceLoader;
+ }
+
+ public AbstractEngineConfiguration setEnableConfiguratorServiceLoader(boolean enableConfiguratorServiceLoader) {
+ this.enableConfiguratorServiceLoader = enableConfiguratorServiceLoader;
+ return this;
+ }
+
+ public List<EngineConfigurator> getConfigurators() {
+ return configurators;
+ }
+
+ public AbstractEngineConfiguration addConfigurator(EngineConfigurator configurator) {
+ if (configurators == null) {
+ configurators = new ArrayList<>();
+ }
+ configurators.add(configurator);
+ return this;
+ }
+
+ public AbstractEngineConfiguration setConfigurators(List<EngineConfigurator> configurators) {
+ this.configurators = configurators;
+ return this;
+ }
+
+ public EngineConfigurator getIdmEngineConfigurator() {
+ return idmEngineConfigurator;
+ }
+
+ public AbstractEngineConfiguration setIdmEngineConfigurator(EngineConfigurator idmEngineConfigurator) {
+ this.idmEngineConfigurator = idmEngineConfigurator;
+ return this;
+ }
+
+ public AbstractEngineConfiguration setForceCloseMybatisConnectionPool(boolean forceCloseMybatisConnectionPool) {
+ this.forceCloseMybatisConnectionPool = forceCloseMybatisConnectionPool;
+ return this;
+ }
+
+ public boolean isForceCloseMybatisConnectionPool() {
+ return forceCloseMybatisConnectionPool;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-flowable/src/main/java/org/flowable/common/engine/impl/db/LiquibaseBasedSchemaManager.java b/Source/BladeX-Tool/blade-starter-flowable/src/main/java/org/flowable/common/engine/impl/db/LiquibaseBasedSchemaManager.java
new file mode 100644
index 0000000..2be5e68
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-flowable/src/main/java/org/flowable/common/engine/impl/db/LiquibaseBasedSchemaManager.java
@@ -0,0 +1,191 @@
+/* Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.flowable.common.engine.impl.db;
+
+import liquibase.Liquibase;
+import liquibase.database.Database;
+import liquibase.database.DatabaseConnection;
+import liquibase.database.DatabaseFactory;
+import liquibase.database.jvm.JdbcConnection;
+import liquibase.exception.DatabaseException;
+import liquibase.resource.ClassLoaderResourceAccessor;
+import org.apache.commons.lang3.StringUtils;
+import org.flowable.common.engine.api.FlowableException;
+import org.flowable.common.engine.impl.AbstractEngineConfiguration;
+import org.flowable.common.engine.impl.context.Context;
+import org.flowable.common.engine.impl.interceptor.CommandContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * @author Filip Hrisafov
+ */
+public abstract class LiquibaseBasedSchemaManager implements SchemaManager {
+
+ protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+ protected final String context;
+ protected final String changeLogFile;
+ protected final String changeLogPrefix;
+
+ public LiquibaseBasedSchemaManager(String context, String changeLogFile, String changeLogPrefix) {
+ this.context = context;
+ this.changeLogFile = changeLogFile;
+ this.changeLogPrefix = changeLogPrefix;
+ }
+
+ public void initSchema(String databaseSchemaUpdate) {
+ try {
+ if (AbstractEngineConfiguration.DB_SCHEMA_UPDATE_CREATE_DROP.equals(databaseSchemaUpdate)) {
+ schemaCreate();
+
+ } else if (AbstractEngineConfiguration.DB_SCHEMA_UPDATE_DROP_CREATE.equals(databaseSchemaUpdate)) {
+ schemaDrop();
+ schemaCreate();
+
+ } else if (AbstractEngineConfiguration.DB_SCHEMA_UPDATE_TRUE.equals(databaseSchemaUpdate)) {
+ schemaUpdate();
+
+ } else if (AbstractEngineConfiguration.DB_SCHEMA_UPDATE_FALSE.equals(databaseSchemaUpdate)) {
+ //鍙栨秷鑷鏌�
+ //schemaCheckVersion();
+ }
+ } catch (Exception e) {
+ throw new FlowableException("Error initialising " + context + " data model", e);
+ }
+ }
+
+ @Override
+ public void schemaCreate() {
+ Liquibase liquibase = null;
+ try {
+ liquibase = createLiquibaseInstance(getDatabaseConfiguration());
+ liquibase.update(context);
+ } catch (Exception e) {
+ throw new FlowableException("Error creating " + context + " engine tables", e);
+ } finally {
+ closeDatabase(liquibase);
+ }
+ }
+
+ @Override
+ public void schemaDrop() {
+ Liquibase liquibase = null;
+ try {
+ liquibase = createLiquibaseInstance(getDatabaseConfiguration());
+ liquibase.dropAll();
+ } catch (Exception e) {
+ throw new FlowableException("Error dropping " + context + " engine tables", e);
+ } finally {
+ closeDatabase(liquibase);
+ }
+ }
+
+ @Override
+ public String schemaUpdate() {
+ Liquibase liquibase = null;
+ try {
+ liquibase = createLiquibaseInstance(getDatabaseConfiguration());
+ liquibase.update(context);
+ } catch (Exception e) {
+ throw new FlowableException("Error updating " + context + " engine tables", e);
+ } finally {
+ closeDatabase(liquibase);
+ }
+ return null;
+ }
+
+ @Override
+ public void schemaCheckVersion() {
+ Liquibase liquibase = null;
+ try {
+ liquibase = createLiquibaseInstance(getDatabaseConfiguration());
+ liquibase.validate();
+ } catch (Exception e) {
+ throw new FlowableException("Error validating " + context + " engine schema", e);
+ } finally {
+ closeDatabase(liquibase);
+ }
+ }
+
+ protected abstract LiquibaseDatabaseConfiguration getDatabaseConfiguration();
+
+ protected Liquibase createLiquibaseInstance(LiquibaseDatabaseConfiguration databaseConfiguration) throws SQLException {
+ Connection jdbcConnection = null;
+ boolean closeConnection = false;
+ try {
+ CommandContext commandContext = Context.getCommandContext();
+ if (commandContext == null) {
+ jdbcConnection = databaseConfiguration.getDataSource().getConnection();
+ closeConnection = true;
+ } else {
+ jdbcConnection = commandContext.getSession(DbSqlSession.class).getSqlSession().getConnection();
+ }
+
+ // A commit is needed here, because one of the things that Liquibase does when acquiring its lock
+ // is doing a rollback, which removes all changes done so far.
+ // For most databases, this is not a problem as DDL statements are not transactional.
+ // However for some (e.g. sql server), this would remove all previous statements, which is not wanted,
+ // hence the extra commit here.
+ if (!jdbcConnection.getAutoCommit()) {
+ jdbcConnection.commit();
+ }
+
+ DatabaseConnection connection = new JdbcConnection(jdbcConnection);
+ Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(connection);
+ database.setDatabaseChangeLogTableName(changeLogPrefix + database.getDatabaseChangeLogTableName());
+ database.setDatabaseChangeLogLockTableName(changeLogPrefix + database.getDatabaseChangeLogLockTableName());
+
+ String databaseSchema = databaseConfiguration.getDatabaseSchema();
+ if (StringUtils.isNotEmpty(databaseSchema)) {
+ database.setDefaultSchemaName(databaseSchema);
+ database.setLiquibaseSchemaName(databaseSchema);
+ }
+
+ String databaseCatalog = databaseConfiguration.getDatabaseCatalog();
+ if (StringUtils.isNotEmpty(databaseCatalog)) {
+ database.setDefaultCatalogName(databaseCatalog);
+ database.setLiquibaseCatalogName(databaseCatalog);
+ }
+
+ return new Liquibase(changeLogFile, new ClassLoaderResourceAccessor(), database);
+
+ } catch (Exception e) {
+ // We only close the connection if an exception occurred, otherwise the Liquibase instance cannot be used
+ if (jdbcConnection != null && closeConnection) {
+ jdbcConnection.close();
+ }
+ throw new FlowableException("Error creating " + context + " liquibase instance", e);
+ }
+ }
+
+ protected void closeDatabase(Liquibase liquibase) {
+ if (liquibase != null) {
+ Database database = liquibase.getDatabase();
+ if (database != null) {
+ // do not close the shared connection if a command context is currently active
+ if (Context.getCommandContext() == null) {
+ try {
+ database.close();
+ } catch (DatabaseException e) {
+ logger.warn("Error closing database for {}", context, e);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-flowable/src/main/resources/processes/LeaveProcess.bpmn20.xml b/Source/BladeX-Tool/blade-starter-flowable/src/main/resources/processes/LeaveProcess.bpmn20.xml
new file mode 100644
index 0000000..4d86c05
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-flowable/src/main/resources/processes/LeaveProcess.bpmn20.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef">
+ <process id="Leave" name="璇峰亣娴佺▼" isExecutable="true">
+ <documentation>璇峰亣娴佺▼</documentation>
+ <startEvent id="start" name="寮�濮�" flowable:initiator="applyUser"></startEvent>
+ <userTask id="hrTask" name="浜轰簨瀹℃壒" flowable:assignee="${taskUser}">
+ <extensionElements>
+ <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
+ </extensionElements>
+ </userTask>
+ <exclusiveGateway id="judgeTask"></exclusiveGateway>
+ <userTask id="managerTak" name="缁忕悊瀹℃壒" flowable:candidateGroups="manager"></userTask>
+ <userTask id="bossTask" name="鑰佹澘瀹℃壒" flowable:candidateGroups="boss"></userTask>
+ <endEvent id="end" name="缁撴潫"></endEvent>
+ <sequenceFlow id="flow1" sourceRef="start" targetRef="hrTask"></sequenceFlow>
+ <sequenceFlow id="managerPassFlow" name="閫氳繃" sourceRef="managerTak" targetRef="end">
+ <conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass}]]></conditionExpression>
+ </sequenceFlow>
+ <userTask id="userTask" name="璋冩暣鐢宠" flowable:assignee="${applyUser}">
+ <extensionElements>
+ <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
+ </extensionElements>
+ </userTask>
+ <sequenceFlow id="bossPassFlow" name="閫氳繃" sourceRef="bossTask" targetRef="end">
+ <conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass}]]></conditionExpression>
+ </sequenceFlow>
+ <sequenceFlow id="judgeMore" name="澶т簬3澶�" sourceRef="judgeTask" targetRef="bossTask">
+ <conditionExpression xsi:type="tFormalExpression"><![CDATA[${days > 3}]]></conditionExpression>
+ </sequenceFlow>
+ <sequenceFlow id="managerNotPassFlow" name="椹冲洖" sourceRef="managerTak" targetRef="userTask">
+ <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!pass}]]></conditionExpression>
+ </sequenceFlow>
+ <sequenceFlow id="bossNotPassFlow" name="椹冲洖" sourceRef="bossTask" targetRef="userTask">
+ <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!pass}]]></conditionExpression>
+ </sequenceFlow>
+ <sequenceFlow id="hrPassFlow" name="鍚屾剰" sourceRef="hrTask" targetRef="judgeTask">
+ <conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass}]]></conditionExpression>
+ </sequenceFlow>
+ <sequenceFlow id="hrNotPassFlow" name="椹冲洖" sourceRef="hrTask" targetRef="userTask">
+ <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!pass}]]></conditionExpression>
+ </sequenceFlow>
+ <sequenceFlow id="judgeLess" name="灏忎簬3澶�" sourceRef="judgeTask" targetRef="managerTak">
+ <conditionExpression xsi:type="tFormalExpression"><![CDATA[${days <= 3}]]></conditionExpression>
+ </sequenceFlow>
+ <sequenceFlow id="userPassFlow" name="閲嶆柊鐢宠" sourceRef="userTask" targetRef="hrTask">
+ <conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass}]]></conditionExpression>
+ </sequenceFlow>
+ <sequenceFlow id="userNotPassFlow" name="鍏抽棴鐢宠" sourceRef="userTask" targetRef="end">
+ <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!pass}]]></conditionExpression>
+ </sequenceFlow>
+ </process>
+ <bpmndi:BPMNDiagram id="BPMNDiagram_Leave">
+ <bpmndi:BPMNPlane bpmnElement="Leave" id="BPMNPlane_Leave">
+ <bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
+ <omgdc:Bounds height="30.0" width="30.0" x="300.0" y="135.0"></omgdc:Bounds>
+ </bpmndi:BPMNShape>
+ <bpmndi:BPMNShape bpmnElement="hrTask" id="BPMNShape_hrTask">
+ <omgdc:Bounds height="80.0" width="100.0" x="360.0" y="165.0"></omgdc:Bounds>
+ </bpmndi:BPMNShape>
+ <bpmndi:BPMNShape bpmnElement="judgeTask" id="BPMNShape_judgeTask">
+ <omgdc:Bounds height="40.0" width="40.0" x="255.0" y="300.0"></omgdc:Bounds>
+ </bpmndi:BPMNShape>
+ <bpmndi:BPMNShape bpmnElement="managerTak" id="BPMNShape_managerTak">
+ <omgdc:Bounds height="80.0" width="100.0" x="555.0" y="75.0"></omgdc:Bounds>
+ </bpmndi:BPMNShape>
+ <bpmndi:BPMNShape bpmnElement="bossTask" id="BPMNShape_bossTask">
+ <omgdc:Bounds height="80.0" width="100.0" x="450.0" y="420.0"></omgdc:Bounds>
+ </bpmndi:BPMNShape>
+ <bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end">
+ <omgdc:Bounds height="28.0" width="28.0" x="705.0" y="390.0"></omgdc:Bounds>
+ </bpmndi:BPMNShape>
+ <bpmndi:BPMNShape bpmnElement="userTask" id="BPMNShape_userTask">
+ <omgdc:Bounds height="80.0" width="100.0" x="510.0" y="270.0"></omgdc:Bounds>
+ </bpmndi:BPMNShape>
+ <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
+ <omgdi:waypoint x="327.9390183144677" y="157.4917313275668"></omgdi:waypoint>
+ <omgdi:waypoint x="360.0" y="176.05263157894737"></omgdi:waypoint>
+ </bpmndi:BPMNEdge>
+ <bpmndi:BPMNEdge bpmnElement="hrPassFlow" id="BPMNEdge_hrPassFlow">
+ <omgdi:waypoint x="363.04347826086956" y="244.95000000000002"></omgdi:waypoint>
+ <omgdi:waypoint x="285.77299999999997" y="310.79999999999995"></omgdi:waypoint>
+ </bpmndi:BPMNEdge>
+ <bpmndi:BPMNEdge bpmnElement="hrNotPassFlow" id="BPMNEdge_hrNotPassFlow">
+ <omgdi:waypoint x="459.95" y="236.21875000000006"></omgdi:waypoint>
+ <omgdi:waypoint x="513.9794844818516" y="270.0"></omgdi:waypoint>
+ </bpmndi:BPMNEdge>
+ <bpmndi:BPMNEdge bpmnElement="judgeLess" id="BPMNEdge_judgeLess">
+ <omgdi:waypoint x="274.3359375" y="300.66397214564284"></omgdi:waypoint>
+ <omgdi:waypoint x="274.3359375" y="115.0"></omgdi:waypoint>
+ <omgdi:waypoint x="554.9999999999982" y="115.0"></omgdi:waypoint>
+ </bpmndi:BPMNEdge>
+ <bpmndi:BPMNEdge bpmnElement="userPassFlow" id="BPMNEdge_userPassFlow">
+ <omgdi:waypoint x="510.0" y="310.0"></omgdi:waypoint>
+ <omgdi:waypoint x="411.0" y="310.0"></omgdi:waypoint>
+ <omgdi:waypoint x="411.0" y="244.95000000000002"></omgdi:waypoint>
+ </bpmndi:BPMNEdge>
+ <bpmndi:BPMNEdge bpmnElement="bossPassFlow" id="BPMNEdge_bossPassFlow">
+ <omgdi:waypoint x="549.9499999999998" y="447.2146118721461"></omgdi:waypoint>
+ <omgdi:waypoint x="705.4331577666419" y="407.4567570622598"></omgdi:waypoint>
+ </bpmndi:BPMNEdge>
+ <bpmndi:BPMNEdge bpmnElement="judgeMore" id="BPMNEdge_judgeMore">
+ <omgdi:waypoint x="287.29730895645025" y="327.65205479452055"></omgdi:waypoint>
+ <omgdi:waypoint x="450.0" y="428.8888888888889"></omgdi:waypoint>
+ </bpmndi:BPMNEdge>
+ <bpmndi:BPMNEdge bpmnElement="managerPassFlow" id="BPMNEdge_managerPassFlow">
+ <omgdi:waypoint x="620.7588235294118" y="154.95"></omgdi:waypoint>
+ <omgdi:waypoint x="713.8613704477151" y="390.96328050279476"></omgdi:waypoint>
+ </bpmndi:BPMNEdge>
+ <bpmndi:BPMNEdge bpmnElement="userNotPassFlow" id="BPMNEdge_userNotPassFlow">
+ <omgdi:waypoint x="609.95" y="339.5301886792453"></omgdi:waypoint>
+ <omgdi:waypoint x="706.9383699359797" y="396.87411962686997"></omgdi:waypoint>
+ </bpmndi:BPMNEdge>
+ <bpmndi:BPMNEdge bpmnElement="bossNotPassFlow" id="BPMNEdge_bossNotPassFlow">
+ <omgdi:waypoint x="515.98" y="420.0"></omgdi:waypoint>
+ <omgdi:waypoint x="544.0" y="349.95000000000005"></omgdi:waypoint>
+ </bpmndi:BPMNEdge>
+ <bpmndi:BPMNEdge bpmnElement="managerNotPassFlow" id="BPMNEdge_managerNotPassFlow">
+ <omgdi:waypoint x="595.438344721373" y="154.95"></omgdi:waypoint>
+ <omgdi:waypoint x="567.9366337262223" y="270.0"></omgdi:waypoint>
+ </bpmndi:BPMNEdge>
+ </bpmndi:BPMNPlane>
+ </bpmndi:BPMNDiagram>
+</definitions>
diff --git a/Source/BladeX-Tool/blade-starter-http/pom.xml b/Source/BladeX-Tool/blade-starter-http/pom.xml
new file mode 100644
index 0000000..6721054
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-http</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-tool</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.squareup.okhttp3</groupId>
+ <artifactId>okhttp</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.squareup.okhttp3</groupId>
+ <artifactId>logging-interceptor</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jsoup</groupId>
+ <artifactId>jsoup</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/AsyncCall.java b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/AsyncCall.java
new file mode 100644
index 0000000..4cb79e9
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/AsyncCall.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http;
+
+import okhttp3.Call;
+import okhttp3.Request;
+
+import java.io.IOException;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * 寮傛鎵ц鍣�
+ *
+ * @author L.cm
+ */
+public class AsyncCall {
+ private final static Consumer<ResponseSpec> DEFAULT_CONSUMER = (r) -> {};
+ private final static BiConsumer<Request, IOException> DEFAULT_FAIL_CONSUMER = (r, e) -> {};
+ private final Call call;
+ private Consumer<ResponseSpec> successConsumer;
+ private Consumer<ResponseSpec> responseConsumer;
+ private BiConsumer<Request, IOException> failedBiConsumer;
+
+ AsyncCall(Call call) {
+ this.call = call;
+ this.successConsumer = DEFAULT_CONSUMER;
+ this.responseConsumer = DEFAULT_CONSUMER;
+ this.failedBiConsumer = DEFAULT_FAIL_CONSUMER;
+ }
+
+ public void onSuccessful(Consumer<ResponseSpec> consumer) {
+ this.successConsumer = consumer;
+ this.execute();
+ }
+
+ public void onResponse(Consumer<ResponseSpec> consumer) {
+ this.responseConsumer = consumer;
+ this.execute();
+ }
+
+ public AsyncCall onFailed(BiConsumer<Request, IOException> biConsumer) {
+ this.failedBiConsumer = biConsumer;
+ return this;
+ }
+
+ private void execute() {
+ call.enqueue(new AsyncCallback(this));
+ }
+
+ void onResponse(HttpResponse httpResponse) {
+ responseConsumer.accept(httpResponse);
+ }
+
+ void onSuccessful(HttpResponse httpResponse) {
+ successConsumer.accept(httpResponse);
+ }
+
+ void onFailure(Request request, IOException e) {
+ failedBiConsumer.accept(request, e);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/AsyncCallback.java b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/AsyncCallback.java
new file mode 100644
index 0000000..08a0ee2
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/AsyncCallback.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http;
+
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.Response;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+import java.io.IOException;
+
+/**
+ * 寮傛澶勭悊
+ *
+ * @author L.cm
+ */
+@ParametersAreNonnullByDefault
+public class AsyncCallback implements Callback {
+ private final AsyncCall asyncCall;
+
+ AsyncCallback(AsyncCall asyncCall) {
+ this.asyncCall = asyncCall;
+ }
+
+ @Override
+ public void onFailure(Call call, IOException e) {
+ asyncCall.onFailure(call.request(), e);
+ }
+
+ @Override
+ public void onResponse(Call call, Response response) throws IOException {
+ try (HttpResponse httpResponse = new HttpResponse(response)) {
+ asyncCall.onResponse(httpResponse);
+ if (response.isSuccessful()) {
+ asyncCall.onSuccessful(httpResponse);
+ } else {
+ asyncCall.onFailure(call.request(), new IOException(httpResponse.message()));
+ }
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/BaseAuthenticator.java b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/BaseAuthenticator.java
new file mode 100644
index 0000000..7588b7f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/BaseAuthenticator.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http;
+
+import lombok.RequiredArgsConstructor;
+import okhttp3.*;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * BaseAuth
+ *
+ * @author L.cm
+ */
+@RequiredArgsConstructor
+public class BaseAuthenticator implements Authenticator {
+ private final String userName;
+ private final String password;
+
+ @Override
+ public Request authenticate(Route route, Response response) throws IOException {
+ String credential = Credentials.basic(userName, password, StandardCharsets.UTF_8);
+ return response.request().newBuilder()
+ .header("Authorization", credential)
+ .build();
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/CssQuery.java b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/CssQuery.java
new file mode 100644
index 0000000..df4a9ca
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/CssQuery.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http;
+
+import java.lang.annotation.*;
+
+/**
+ * xml CssQuery
+ *
+ * @author L.cm
+ */
+@Target({ElementType.FIELD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface CssQuery {
+
+ /**
+ * CssQuery
+ *
+ * @return CssQuery
+ */
+ String value();
+
+ /**
+ * 璇诲彇鐨� dom attr
+ *
+ * <p>
+ * attr锛氬厓绱犲浜庣殑 attr 鐨勫��
+ * html锛氭暣涓厓绱犵殑html
+ * text锛氬厓绱犲唴鏂囨湰
+ * allText锛氬涓厓绱犵殑鏂囨湰鍊�
+ * </p>
+ *
+ * @return attr
+ */
+ String attr() default "";
+
+ /**
+ * 姝e垯锛岀敤浜庡 attr value 澶勭悊
+ *
+ * @return regex
+ */
+ String regex() default "";
+
+ /**
+ * 榛樿鐨勬鍒� group
+ */
+ int DEFAULT_REGEX_GROUP = 0;
+
+ /**
+ * 姝e垯 group锛岄粯璁や负 0
+ *
+ * @return regexGroup
+ */
+ int regexGroup() default DEFAULT_REGEX_GROUP;
+
+ /**
+ * 宓屽鐨勫唴閮ㄦā鍨嬶細榛樿 false
+ *
+ * @return 鏄惁涓哄唴閮ㄦā鍨�
+ */
+ boolean inner() default false;
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/CssQueryMethodInterceptor.java b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/CssQueryMethodInterceptor.java
new file mode 100644
index 0000000..78f4d6c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/CssQueryMethodInterceptor.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http;
+
+import lombok.RequiredArgsConstructor;
+import org.jsoup.nodes.Element;
+import org.jsoup.nodes.TextNode;
+import org.jsoup.select.Elements;
+import org.jsoup.select.Selector;
+import org.springblade.core.tool.utils.ConvertUtil;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.cglib.proxy.MethodInterceptor;
+import org.springframework.cglib.proxy.MethodProxy;
+import org.springframework.core.ResolvableType;
+import org.springframework.core.convert.TypeDescriptor;
+
+import javax.annotation.Nullable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * 浠g悊妯″瀷
+ *
+ * @author L.cm
+ */
+@RequiredArgsConstructor
+public class CssQueryMethodInterceptor implements MethodInterceptor {
+ private final Class<?> clazz;
+ private final Element element;
+
+ @Nullable
+ @Override
+ public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
+ // 鍙鐞� get 鏂规硶 is
+ String name = method.getName();
+ if (!StringUtil.startsWithIgnoreCase(name, StringPool.GET)) {
+ return methodProxy.invokeSuper(object, args);
+ }
+ Field field = clazz.getDeclaredField(StringUtil.firstCharToLower(name.substring(3)));
+ CssQuery cssQuery = field.getAnnotation(CssQuery.class);
+ // 娌℃湁娉ㄨВ锛屼笉浠g悊
+ if (cssQuery == null) {
+ return methodProxy.invokeSuper(object, args);
+ }
+ Class<?> returnType = method.getReturnType();
+ boolean isColl = Collection.class.isAssignableFrom(returnType);
+ String cssQueryValue = cssQuery.value();
+ // 鏄惁涓� bean 涓� bean
+ boolean isInner = cssQuery.inner();
+ if (isInner) {
+ return proxyInner(cssQueryValue, method, returnType, isColl);
+ }
+ Object proxyValue = proxyValue(cssQueryValue, cssQuery, returnType, isColl);
+ if (String.class.isAssignableFrom(returnType)) {
+ return proxyValue;
+ }
+ // 鐢ㄤ簬璇诲彇 field 涓婄殑娉ㄨВ
+ TypeDescriptor typeDescriptor = new TypeDescriptor(field);
+ return ConvertUtil.convert(proxyValue, typeDescriptor);
+ }
+
+ @Nullable
+ private Object proxyValue(String cssQueryValue, CssQuery cssQuery, Class<?> returnType, boolean isColl) {
+ if (isColl) {
+ Elements elements = Selector.select(cssQueryValue, element);
+ Collection<Object> valueList = newColl(returnType);
+ if (elements.isEmpty()) {
+ return valueList;
+ }
+ for (Element select : elements) {
+ String value = getValue(select, cssQuery);
+ if (value != null) {
+ valueList.add(value);
+ }
+ }
+ return valueList;
+ }
+ Element select = Selector.selectFirst(cssQueryValue, element);
+ return getValue(select, cssQuery);
+ }
+
+ private Object proxyInner(String cssQueryValue, Method method, Class<?> returnType, boolean isColl) {
+ if (isColl) {
+ Elements elements = Selector.select(cssQueryValue, element);
+ Collection<Object> valueList = newColl(returnType);
+ ResolvableType resolvableType = ResolvableType.forMethodReturnType(method);
+ Class<?> innerType = resolvableType.getGeneric(0).resolve();
+ if (innerType == null) {
+ throw new IllegalArgumentException("Class " + returnType + " 璇诲彇娉涘瀷澶辫触銆�");
+ }
+ for (Element select : elements) {
+ valueList.add(DomMapper.readValue(select, innerType));
+ }
+ return valueList;
+ }
+ Element select = Selector.selectFirst(cssQueryValue, element);
+ return DomMapper.readValue(select, returnType);
+ }
+
+ @Nullable
+ private String getValue(@Nullable Element element, CssQuery cssQuery) {
+ if (element == null) {
+ return null;
+ }
+ // 璇诲彇鐨勫睘鎬у悕
+ String attrName = cssQuery.attr();
+ // 璇诲彇鐨勫��
+ String attrValue;
+ if (StringUtil.isBlank(attrName)) {
+ attrValue = element.outerHtml();
+ } else if ("html".equalsIgnoreCase(attrName)) {
+ attrValue = element.html();
+ } else if ("text".equalsIgnoreCase(attrName)) {
+ attrValue = getText(element);
+ } else if ("allText".equalsIgnoreCase(attrName)) {
+ attrValue = element.text();
+ } else {
+ attrValue = element.attr(attrName);
+ }
+ // 鍒ゆ柇鏄惁闇�瑕佹鍒欏鐞�
+ String regex = cssQuery.regex();
+ if (StringUtil.isBlank(attrValue) || StringUtil.isBlank(regex)) {
+ return attrValue;
+ }
+ // 澶勭悊姝e垯琛ㄨ揪寮�
+ return getRegexValue(regex, cssQuery.regexGroup(), attrValue);
+ }
+
+ @Nullable
+ private String getRegexValue(String regex, int regexGroup, String value) {
+ // 澶勭悊姝e垯琛ㄨ揪寮�
+ Matcher matcher = Pattern.compile(regex, Pattern.DOTALL | Pattern.CASE_INSENSITIVE).matcher(value);
+ if (!matcher.find()) {
+ return null;
+ }
+ // 姝e垯 group
+ if (regexGroup > CssQuery.DEFAULT_REGEX_GROUP) {
+ return matcher.group(regexGroup);
+ }
+ return matcher.group();
+ }
+
+ private String getText(Element element) {
+ return element.childNodes().stream()
+ .filter(node -> node instanceof TextNode)
+ .map(node -> (TextNode) node)
+ .map(TextNode::text)
+ .collect(Collectors.joining());
+ }
+
+ private Collection<Object> newColl(Class<?> returnType) {
+ return Set.class.isAssignableFrom(returnType) ? new HashSet<>() : new ArrayList<>();
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/DomMapper.java b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/DomMapper.java
new file mode 100644
index 0000000..de26866
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/DomMapper.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http;
+
+import org.jsoup.helper.DataUtil;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.parser.Parser;
+import org.jsoup.select.Elements;
+import org.springblade.core.tool.utils.Exceptions;
+import org.springframework.cglib.proxy.Enhancer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 鐖櫕 xml 杞� bean 鍩轰簬 jsoup
+ *
+ * @author L.cm
+ */
+public class DomMapper {
+
+ /**
+ * Returns body to jsoup Document.
+ *
+ * @return Document
+ */
+ public static Document asDocument(ResponseSpec response) {
+ return readDocument(response.asString());
+ }
+
+ /**
+ * 灏嗘祦璇诲彇涓� jsoup Document
+ *
+ * @param inputStream InputStream
+ * @return Document
+ */
+ public static Document readDocument(InputStream inputStream) {
+ try {
+ return DataUtil.load(inputStream, StandardCharsets.UTF_8.name(), "");
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ /**
+ * 灏� html 瀛楃涓茶鍙栦负 jsoup Document
+ *
+ * @param html String
+ * @return Document
+ */
+ public static Document readDocument(String html) {
+ return Parser.parse(html, "");
+ }
+
+ /**
+ * 璇诲彇 xml 淇℃伅涓� java Bean
+ *
+ * @param inputStream InputStream
+ * @param clazz bean Class
+ * @param <T> 娉涘瀷
+ * @return 瀵硅薄
+ */
+ public static <T> T readValue(InputStream inputStream, final Class<T> clazz) {
+ return readValue(readDocument(inputStream), clazz);
+ }
+
+ /**
+ * 璇诲彇 xml 淇℃伅涓� java Bean
+ *
+ * @param html html String
+ * @param clazz bean Class
+ * @param <T> 娉涘瀷
+ * @return 瀵硅薄
+ */
+ public static <T> T readValue(String html, final Class<T> clazz) {
+ return readValue(readDocument(html), clazz);
+ }
+
+ /**
+ * 璇诲彇 xml 淇℃伅涓� java Bean
+ *
+ * @param doc xml element
+ * @param clazz bean Class
+ * @param <T> 娉涘瀷
+ * @return 瀵硅薄
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T readValue(final Element doc, final Class<T> clazz) {
+ Enhancer enhancer = new Enhancer();
+ enhancer.setSuperclass(clazz);
+ enhancer.setUseCache(true);
+ enhancer.setCallback(new CssQueryMethodInterceptor(clazz, doc));
+ return (T) enhancer.create();
+ }
+
+ /**
+ * 璇诲彇 xml 淇℃伅涓� java Bean
+ *
+ * @param <T> 娉涘瀷
+ * @param inputStream InputStream
+ * @param clazz bean Class
+ * @return 瀵硅薄
+ */
+ public static <T> List<T> readList(InputStream inputStream, final Class<T> clazz) {
+ return readList(readDocument(inputStream), clazz);
+ }
+
+ /**
+ * 璇诲彇 xml 淇℃伅涓� java Bean
+ *
+ * @param <T> 娉涘瀷
+ * @param html html String
+ * @param clazz bean Class
+ * @return 瀵硅薄
+ */
+ public static <T> List<T> readList(String html, final Class<T> clazz) {
+ return readList(readDocument(html), clazz);
+ }
+
+ /**
+ * 璇诲彇 xml 淇℃伅涓� java Bean
+ *
+ * @param doc xml element
+ * @param clazz bean Class
+ * @param <T> 娉涘瀷
+ * @return 瀵硅薄鍒楄〃
+ */
+ public static <T> List<T> readList(Element doc, Class<T> clazz) {
+ CssQuery annotation = clazz.getAnnotation(CssQuery.class);
+ if (annotation == null) {
+ throw new IllegalArgumentException("DomMapper readList " + clazz + " mast has annotation @CssQuery.");
+ }
+ String cssQueryValue = annotation.value();
+ Elements elements = doc.select(cssQueryValue);
+ List<T> valueList = new ArrayList<>();
+ for (Element element : elements) {
+ valueList.add(readValue(element, clazz));
+ }
+ return valueList;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/Exchange.java b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/Exchange.java
new file mode 100644
index 0000000..3b03618
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/Exchange.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.RequiredArgsConstructor;
+import okhttp3.Call;
+import okhttp3.Request;
+import org.springblade.core.tool.utils.Exceptions;
+
+import javax.annotation.Nullable;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+/**
+ * Exchange
+ *
+ * @author L.cm
+ */
+@RequiredArgsConstructor
+public class Exchange {
+ private BiConsumer<Request, IOException> failedBiConsumer = (r, e) -> {};
+ private final Call call;
+
+ public Exchange onFailed(BiConsumer<Request, IOException> failConsumer) {
+ this.failedBiConsumer = failConsumer;
+ return this;
+ }
+
+ public <R> R onResponse(Function<ResponseSpec, R> func) {
+ try (HttpResponse response = new HttpResponse(call.execute())) {
+ return func.apply(response);
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ @Nullable
+ public <R> R onSuccess(Function<ResponseSpec, R> func) {
+ try (HttpResponse response = new HttpResponse(call.execute())) {
+ return func.apply(response);
+ } catch (IOException e) {
+ failedBiConsumer.accept(call.request(), e);
+ return null;
+ }
+ }
+
+ @Nullable
+ public <R> R onSuccessful(Function<ResponseSpec, R> func) {
+ try (HttpResponse response = new HttpResponse(call.execute())) {
+ if (response.isOk()) {
+ return func.apply(response);
+ } else {
+ failedBiConsumer.accept(call.request(), new IOException(response.toString()));
+ }
+ } catch (IOException e) {
+ failedBiConsumer.accept(call.request(), e);
+ }
+ return null;
+ }
+
+ public <R> Optional<R> onSuccessOpt(Function<ResponseSpec, R> func) {
+ return Optional.ofNullable(this.onSuccess(func));
+ }
+
+ public <R> Optional<R> onSuccessfulOpt(Function<ResponseSpec, R> func) {
+ return Optional.ofNullable(this.onSuccessful(func));
+ }
+
+ /**
+ * Returns body String.
+ *
+ * @return body String
+ */
+ public String asString() {
+ return onResponse(ResponseSpec::asString);
+ }
+
+ /**
+ * Returns body to byte arrays.
+ *
+ * @return byte arrays
+ */
+ public byte[] asBytes() {
+ return onResponse(ResponseSpec::asBytes);
+ }
+
+ /**
+ * Returns body to JsonNode.
+ *
+ * @return JsonNode
+ */
+ public JsonNode asJsonNode() {
+ return onResponse(ResponseSpec::asJsonNode);
+ }
+
+ /**
+ * Returns body to Object.
+ *
+ * @param valueType value value type
+ * @return Object
+ */
+ public <T> T asValue(Class<T> valueType) {
+ return onResponse(responseSpec -> responseSpec.asValue(valueType));
+ }
+
+ /**
+ * Returns body to Object.
+ *
+ * @param typeReference value Type Reference
+ * @return Object
+ */
+ public <T> T asValue(TypeReference<T> typeReference) {
+ return onResponse(responseSpec -> responseSpec.asValue(typeReference));
+ }
+
+ /**
+ * Returns body to List.
+ *
+ * @param valueType value type
+ * @return List
+ */
+ public <T> List<T> asList(Class<T> valueType) {
+ return onResponse(responseSpec -> responseSpec.asList(valueType));
+ }
+
+ /**
+ * Returns body to Map.
+ *
+ * @param keyClass key type
+ * @param valueType value type
+ * @return Map
+ */
+ public <K, V> Map<K, V> asMap(Class<?> keyClass, Class<?> valueType) {
+ return onResponse(responseSpec -> responseSpec.asMap(keyClass, valueType));
+ }
+
+ /**
+ * Returns body to Map.
+ *
+ * @param valueType value 绫诲瀷
+ * @return Map
+ */
+ public <V> Map<String, V> asMap(Class<?> valueType) {
+ return onResponse(responseSpec -> responseSpec.asMap(valueType));
+ }
+
+ /**
+ * 灏� xml銆乭eml 杞垚瀵硅薄
+ *
+ * @param valueType 瀵硅薄绫�
+ * @param <T> 娉涘瀷
+ * @return 瀵硅薄
+ */
+ public <T> T asDomValue(Class<T> valueType) {
+ return onResponse(responseSpec -> responseSpec.asDomValue(valueType));
+ }
+
+ /**
+ * 灏� xml銆乭eml 杞垚瀵硅薄
+ *
+ * @param valueType 瀵硅薄绫�
+ * @param <T> 娉涘瀷
+ * @return 瀵硅薄闆嗗悎
+ */
+ public <T> List<T> asDomList(Class<T> valueType) {
+ return onResponse(responseSpec -> responseSpec.asDomList(valueType));
+ }
+
+ /**
+ * toFile.
+ *
+ * @param file File
+ */
+ public File toFile(File file) {
+ return onResponse(responseSpec -> responseSpec.toFile(file));
+ }
+
+ /**
+ * toFile.
+ *
+ * @param path Path
+ */
+ public Path toFile(Path path) {
+ return onResponse(responseSpec -> responseSpec.toFile(path));
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/FormBuilder.java b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/FormBuilder.java
new file mode 100644
index 0000000..8c0c9a6
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/FormBuilder.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http;
+
+import okhttp3.FormBody;
+
+import javax.annotation.Nullable;
+import java.util.Map;
+
+/**
+ * 琛ㄥ崟鏋勯�犲櫒
+ *
+ * @author L.cm
+ */
+public class FormBuilder {
+ private final HttpRequest request;
+ private final FormBody.Builder formBuilder;
+
+ FormBuilder(HttpRequest request) {
+ this.request = request;
+ this.formBuilder = new FormBody.Builder();
+ }
+
+ public FormBuilder add(String name, @Nullable Object value) {
+ this.formBuilder.add(name, HttpRequest.handleValue(value));
+ return this;
+ }
+
+ public FormBuilder addMap(@Nullable Map<String, Object> formMap) {
+ if (formMap != null && !formMap.isEmpty()) {
+ formMap.forEach(this::add);
+ }
+ return this;
+ }
+
+ public FormBuilder addEncoded(String name, @Nullable Object encodedValue) {
+ this.formBuilder.addEncoded(name, HttpRequest.handleValue(encodedValue));
+ return this;
+ }
+
+ public HttpRequest build() {
+ FormBody formBody = formBuilder.build();
+ this.request.form(formBody);
+ return this.request;
+ }
+
+ public Exchange execute() {
+ return this.build().execute();
+ }
+
+ public AsyncCall async() {
+ return this.build().async();
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/HttpRequest.java b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/HttpRequest.java
new file mode 100644
index 0000000..3b96816
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/HttpRequest.java
@@ -0,0 +1,492 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http;
+
+import okhttp3.*;
+import okhttp3.internal.Util;
+import okhttp3.internal.http.HttpMethod;
+import okhttp3.logging.HttpLoggingInterceptor;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.ssl.DisableValidationTrustManager;
+import org.springblade.core.tool.ssl.TrustAllHostNames;
+import org.springblade.core.tool.utils.Exceptions;
+import org.springblade.core.tool.utils.Holder;
+import org.springblade.core.tool.utils.StringPool;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.net.ssl.*;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
+/**
+ * ok http 灏佽锛岃姹傜粨鏋勪綋
+ *
+ * @author L.cm
+ */
+public class HttpRequest {
+ private static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36";
+ private static final MediaType APPLICATION_JSON = MediaType.parse("application/json;charset=UTF-8");
+ private static volatile OkHttpClient httpClient = new OkHttpClient();
+ @Nullable
+ private static HttpLoggingInterceptor globalLoggingInterceptor = null;
+ private final Request.Builder requestBuilder;
+ private final HttpUrl.Builder uriBuilder;
+ private final String httpMethod;
+ private String userAgent;
+ @Nullable
+ private RequestBody requestBody;
+ @Nullable
+ private Boolean followRedirects;
+ @Nullable
+ private Boolean followSslRedirects;
+ @Nullable
+ private HttpLoggingInterceptor.Level level;
+ @Nullable
+ private CookieJar cookieJar;
+ @Nullable
+ private EventListener eventListener;
+ private final List<Interceptor> interceptors = new ArrayList<>();
+ @Nullable
+ private Authenticator authenticator;
+ @Nullable
+ private Duration connectTimeout;
+ @Nullable
+ private Duration readTimeout;
+ @Nullable
+ private Duration writeTimeout;
+ @Nullable
+ private Proxy proxy;
+ @Nullable
+ private ProxySelector proxySelector;
+ @Nullable
+ private Authenticator proxyAuthenticator;
+ @Nullable
+ private RetryPolicy retryPolicy;
+ @Nullable
+ private Boolean disableSslValidation;
+ @Nullable
+ private HostnameVerifier hostnameVerifier;
+ @Nullable
+ private SSLSocketFactory sslSocketFactory;
+ @Nullable
+ private X509TrustManager trustManager;
+
+ public static HttpRequest get(final String url) {
+ return new HttpRequest(new Request.Builder(), url, Method.GET);
+ }
+
+ public static HttpRequest get(final URI uri) {
+ return get(uri.toString());
+ }
+
+ public static HttpRequest post(final String url) {
+ return new HttpRequest(new Request.Builder(), url, Method.POST);
+ }
+
+ public static HttpRequest post(final URI uri) {
+ return post(uri.toString());
+ }
+
+ public static HttpRequest patch(final String url) {
+ return new HttpRequest(new Request.Builder(), url, Method.PATCH);
+ }
+
+ public static HttpRequest patch(final URI uri) {
+ return patch(uri.toString());
+ }
+
+ public static HttpRequest put(final String url) {
+ return new HttpRequest(new Request.Builder(), url, Method.PUT);
+ }
+
+ public static HttpRequest put(final URI uri) {
+ return put(uri.toString());
+ }
+
+ public static HttpRequest delete(final String url) {
+ return new HttpRequest(new Request.Builder(), url, Method.DELETE);
+ }
+
+ public static HttpRequest delete(final URI uri) {
+ return delete(uri.toString());
+ }
+
+ public HttpRequest query(String query) {
+ this.uriBuilder.query(query);
+ return this;
+ }
+
+ public HttpRequest queryEncoded(String encodedQuery) {
+ this.uriBuilder.encodedQuery(encodedQuery);
+ return this;
+ }
+
+ public HttpRequest queryMap(@Nullable Map<String, Object> queryMap) {
+ if (queryMap != null && !queryMap.isEmpty()) {
+ queryMap.forEach(this::query);
+ }
+ return this;
+ }
+
+ public HttpRequest query(String name, @Nullable Object value) {
+ this.uriBuilder.addQueryParameter(name, value == null ? null : String.valueOf(value));
+ return this;
+ }
+
+ public HttpRequest queryEncoded(String encodedName, @Nullable Object encodedValue) {
+ this.uriBuilder.addEncodedQueryParameter(encodedName, encodedValue == null ? null : String.valueOf(encodedValue));
+ return this;
+ }
+
+ HttpRequest form(FormBody formBody) {
+ this.requestBody = formBody;
+ return this;
+ }
+
+ HttpRequest multipartForm(MultipartBody multipartBody) {
+ this.requestBody = multipartBody;
+ return this;
+ }
+
+ public FormBuilder formBuilder() {
+ return new FormBuilder(this);
+ }
+
+ public MultipartFormBuilder multipartFormBuilder() {
+ return new MultipartFormBuilder(this);
+ }
+
+ public HttpRequest body(RequestBody requestBody) {
+ this.requestBody = requestBody;
+ return this;
+ }
+
+ public HttpRequest bodyString(String body) {
+ this.requestBody = RequestBody.create(APPLICATION_JSON, body);
+ return this;
+ }
+
+ public HttpRequest bodyString(MediaType contentType, String body) {
+ this.requestBody = RequestBody.create(contentType, body);
+ return this;
+ }
+
+ public HttpRequest bodyJson(@Nonnull Object body) {
+ return bodyString(JsonUtil.toJson(body));
+ }
+
+ private HttpRequest(final Request.Builder requestBuilder, String url, String httpMethod) {
+ HttpUrl httpUrl = HttpUrl.parse(url);
+ if (httpUrl == null) {
+ throw new IllegalArgumentException(String.format("Url 涓嶈兘瑙f瀽: %s: [%s]銆�", httpMethod.toLowerCase(), url));
+ }
+ this.requestBuilder = requestBuilder;
+ this.uriBuilder = httpUrl.newBuilder();
+ this.httpMethod = httpMethod;
+ this.userAgent = DEFAULT_USER_AGENT;
+ }
+
+ private Call internalCall(final OkHttpClient client) {
+ OkHttpClient.Builder builder = client.newBuilder();
+ if (connectTimeout != null) {
+ builder.connectTimeout(connectTimeout.toMillis(), TimeUnit.MILLISECONDS);
+ }
+ if (readTimeout != null) {
+ builder.readTimeout(readTimeout.toMillis(), TimeUnit.MILLISECONDS);
+ }
+ if (writeTimeout != null) {
+ builder.writeTimeout(writeTimeout.toMillis(), TimeUnit.MILLISECONDS);
+ }
+ if (proxy != null) {
+ builder.proxy(proxy);
+ }
+ if (proxySelector != null) {
+ builder.proxySelector(proxySelector);
+ }
+ if (proxyAuthenticator != null) {
+ builder.proxyAuthenticator(proxyAuthenticator);
+ }
+ if (hostnameVerifier != null) {
+ builder.hostnameVerifier(hostnameVerifier);
+ }
+ if (sslSocketFactory != null && trustManager != null) {
+ builder.sslSocketFactory(sslSocketFactory, trustManager);
+ }
+ if (Boolean.TRUE.equals(disableSslValidation)) {
+ disableSslValidation(builder);
+ }
+ if (authenticator != null) {
+ builder.authenticator(authenticator);
+ }
+ if (!interceptors.isEmpty()) {
+ builder.interceptors().addAll(interceptors);
+ }
+ if (cookieJar != null) {
+ builder.cookieJar(cookieJar);
+ }
+ if (eventListener != null) {
+ builder.eventListener(eventListener);
+ }
+ if (followRedirects != null) {
+ builder.followRedirects(followRedirects);
+ }
+ if (followSslRedirects != null) {
+ builder.followSslRedirects(followSslRedirects);
+ }
+ if (retryPolicy != null) {
+ builder.addInterceptor(new RetryInterceptor(retryPolicy));
+ }
+ if (level != null && HttpLoggingInterceptor.Level.NONE != level) {
+ builder.addInterceptor(getLoggingInterceptor(level));
+ } else if (globalLoggingInterceptor != null) {
+ builder.addInterceptor(globalLoggingInterceptor);
+ }
+ // 璁剧疆 User-Agent
+ requestBuilder.header("User-Agent", userAgent);
+ // url
+ requestBuilder.url(uriBuilder.build());
+ String method = httpMethod;
+ Request request;
+ if (HttpMethod.requiresRequestBody(method) && requestBody == null) {
+ request = requestBuilder.method(method, Util.EMPTY_REQUEST).build();
+ } else {
+ request = requestBuilder.method(method, requestBody).build();
+ }
+ return builder.build().newCall(request);
+ }
+
+ public Exchange execute() {
+ return new Exchange(internalCall(httpClient));
+ }
+
+ public AsyncCall async() {
+ return new AsyncCall(internalCall(httpClient));
+ }
+
+ public HttpRequest baseAuth(String userName, String password) {
+ this.authenticator = new BaseAuthenticator(userName, password);
+ return this;
+ }
+
+ //// HTTP header operations
+ public HttpRequest addHeader(final Map<String, String> headers) {
+ this.requestBuilder.headers(Headers.of(headers));
+ return this;
+ }
+
+ public HttpRequest addHeader(final String... namesAndValues) {
+ Headers headers = Headers.of(namesAndValues);
+ this.requestBuilder.headers(headers);
+ return this;
+ }
+
+ public HttpRequest addHeader(final String name, final String value) {
+ this.requestBuilder.addHeader(name, value);
+ return this;
+ }
+
+ public HttpRequest setHeader(final String name, final String value) {
+ this.requestBuilder.header(name, value);
+ return this;
+ }
+
+ public HttpRequest removeHeader(final String name) {
+ this.requestBuilder.removeHeader(name);
+ return this;
+ }
+
+ public HttpRequest addCookie(final Cookie cookie) {
+ this.addHeader("Cookie", cookie.toString());
+ return this;
+ }
+
+ public HttpRequest cacheControl(final CacheControl cacheControl) {
+ this.requestBuilder.cacheControl(cacheControl);
+ return this;
+ }
+
+ public HttpRequest userAgent(final String userAgent) {
+ this.userAgent = userAgent;
+ return this;
+ }
+
+ public HttpRequest followRedirects(boolean followRedirects) {
+ this.followRedirects = followRedirects;
+ return this;
+ }
+
+ public HttpRequest followSslRedirects(boolean followSslRedirects) {
+ this.followSslRedirects = followSslRedirects;
+ return this;
+ }
+
+ private static HttpLoggingInterceptor getLoggingInterceptor(HttpLoggingInterceptor.Level level) {
+ HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(Slf4jLogger.INSTANCE);
+ loggingInterceptor.setLevel(level);
+ return loggingInterceptor;
+ }
+
+ public HttpRequest log() {
+ this.level = HttpLoggingInterceptor.Level.BODY;
+ return this;
+ }
+
+ public HttpRequest log(LogLevel logLevel) {
+ this.level = logLevel.getLevel();
+ return this;
+ }
+
+ public HttpRequest authenticator(Authenticator authenticator) {
+ this.authenticator = authenticator;
+ return this;
+ }
+
+ public HttpRequest interceptor(Interceptor interceptor) {
+ this.interceptors.add(interceptor);
+ return this;
+ }
+
+ public HttpRequest cookieManager(CookieJar cookieJar) {
+ this.cookieJar = cookieJar;
+ return this;
+ }
+
+ public HttpRequest eventListener(EventListener eventListener) {
+ this.eventListener = eventListener;
+ return this;
+ }
+
+ //// HTTP connection parameter operations
+ public HttpRequest connectTimeout(final Duration timeout) {
+ this.connectTimeout = timeout;
+ return this;
+ }
+
+ public HttpRequest readTimeout(Duration readTimeout) {
+ this.readTimeout = readTimeout;
+ return this;
+ }
+
+ public HttpRequest writeTimeout(Duration writeTimeout) {
+ this.writeTimeout = writeTimeout;
+ return this;
+ }
+
+ public HttpRequest proxy(final InetSocketAddress address) {
+ this.proxy = new Proxy(Proxy.Type.HTTP, address);
+ return this;
+ }
+
+ public HttpRequest proxySelector(final ProxySelector proxySelector) {
+ this.proxySelector = proxySelector;
+ return this;
+ }
+
+ public HttpRequest proxyAuthenticator(final Authenticator proxyAuthenticator) {
+ this.proxyAuthenticator = proxyAuthenticator;
+ return this;
+ }
+
+ public HttpRequest retry() {
+ this.retryPolicy = RetryPolicy.INSTANCE;
+ return this;
+ }
+
+ public HttpRequest retryOn(Predicate<ResponseSpec> respPredicate) {
+ this.retryPolicy = new RetryPolicy(respPredicate);
+ return this;
+ }
+
+ public HttpRequest retry(int maxAttempts, long sleepMillis) {
+ this.retryPolicy = new RetryPolicy(maxAttempts, sleepMillis);
+ return this;
+ }
+
+ public HttpRequest retry(int maxAttempts, long sleepMillis, Predicate<ResponseSpec> respPredicate) {
+ this.retryPolicy = new RetryPolicy(maxAttempts, sleepMillis);
+ return this;
+ }
+
+ /**
+ * 鍏抽棴 ssl 鏍¢獙
+ *
+ * @return HttpRequest
+ */
+ public HttpRequest disableSslValidation() {
+ this.disableSslValidation = Boolean.TRUE;
+ return this;
+ }
+
+ public HttpRequest hostnameVerifier(HostnameVerifier hostnameVerifier) {
+ this.hostnameVerifier = hostnameVerifier;
+ return this;
+ }
+
+ public HttpRequest sslSocketFactory(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager) {
+ this.sslSocketFactory = sslSocketFactory;
+ this.trustManager = trustManager;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return requestBuilder.toString();
+ }
+
+ public static void setHttpClient(OkHttpClient httpClient) {
+ HttpRequest.httpClient = httpClient;
+ }
+
+ public static void setGlobalLog(LogLevel logLevel) {
+ HttpRequest.globalLoggingInterceptor = getLoggingInterceptor(logLevel.getLevel());
+ }
+
+ static String handleValue(@Nullable Object value) {
+ if (value == null) {
+ return StringPool.EMPTY;
+ }
+ if (value instanceof String) {
+ return (String) value;
+ }
+ return String.valueOf(value);
+ }
+
+ private static void disableSslValidation(OkHttpClient.Builder builder) {
+ try {
+ X509TrustManager disabledTrustManager = DisableValidationTrustManager.INSTANCE;
+ TrustManager[] trustManagers = new TrustManager[]{disabledTrustManager};
+ SSLContext sslContext = SSLContext.getInstance("SSL");
+ sslContext.init(null, trustManagers, Holder.SECURE_RANDOM);
+ SSLSocketFactory disabledSslSocketFactory = sslContext.getSocketFactory();
+ builder.sslSocketFactory(disabledSslSocketFactory, disabledTrustManager);
+ builder.hostnameVerifier(TrustAllHostNames.INSTANCE);
+ } catch (NoSuchAlgorithmException | KeyManagementException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/HttpResponse.java b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/HttpResponse.java
new file mode 100644
index 0000000..5396bb1
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/HttpResponse.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import okhttp3.*;
+import okhttp3.internal.Util;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.utils.Exceptions;
+
+import javax.annotation.Nullable;
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * ok http 灏佽锛岀浉搴旂粨鏋勪綋
+ *
+ * @author L.cm
+ */
+public class HttpResponse implements ResponseSpec, Closeable {
+ private final Request request;
+ private final Response response;
+ private final ResponseBody body;
+
+ HttpResponse(final Response response) {
+ this.request = response.request();
+ this.response = response;
+ this.body = ifNullBodyToEmpty(response.body());
+ }
+
+ @Override
+ public int code() {
+ return response.code();
+ }
+
+ @Override
+ public String message() {
+ return response.message();
+ }
+
+ @Override
+ public boolean isOk() {
+ return response.isSuccessful();
+ }
+
+ @Override
+ public boolean isRedirect() {
+ return response.isRedirect();
+ }
+
+ @Override
+ public Headers headers() {
+ return response.headers();
+ }
+
+ @Override
+ public List<Cookie> cookies() {
+ return Cookie.parseAll(request.url(), this.headers());
+ }
+
+ @Override
+ public Request rawRequest() {
+ return this.request;
+ }
+
+ @Override
+ public Response rawResponse() {
+ return this.response;
+ }
+
+ @Override
+ public ResponseBody rawBody() {
+ return this.body;
+ }
+
+ @Override
+ public String asString() {
+ try {
+ return body.string();
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ @Override
+ public byte[] asBytes() {
+ try {
+ return body.bytes();
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ @Override
+ public InputStream asStream() {
+ return body.byteStream();
+ }
+
+ @Override
+ public JsonNode asJsonNode() {
+ return JsonUtil.readTree(asBytes());
+ }
+
+ @Override
+ public <T> T asValue(Class<T> valueType) {
+ return JsonUtil.readValue(asBytes(), valueType);
+ }
+
+ @Override
+ public <T> T asValue(TypeReference<T> typeReference) {
+ return JsonUtil.readValue(asBytes(), typeReference);
+ }
+
+ @Override
+ public <T> List<T> asList(Class<T> valueType) {
+ return JsonUtil.readList(asBytes(), valueType);
+ }
+
+ @Override
+ public <K, V> Map<K, V> asMap(Class<?> keyClass, Class<?> valueType) {
+ return JsonUtil.readMap(asBytes(), keyClass, valueType);
+ }
+
+ @Override
+ public <V> Map<String, V> asMap(Class<?> valueType) {
+ return this.asMap(String.class, valueType);
+ }
+
+ @Override
+ public <T> T asDomValue(Class<T> valueType) {
+ return DomMapper.readValue(this.asStream(), valueType);
+ }
+
+ @Override
+ public <T> List<T> asDomList(Class<T> valueType) {
+ return DomMapper.readList(this.asStream(), valueType);
+ }
+
+ @Override
+ public File toFile(File file) {
+ toFile(file.toPath());
+ return file;
+ }
+
+ @Override
+ public Path toFile(Path path) {
+ try {
+ Files.copy(this.asStream(), path);
+ return path;
+ } catch (IOException e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+
+ @Override
+ public MediaType contentType() {
+ return body.contentType();
+ }
+
+ @Override
+ public long contentLength() {
+ return body.contentLength();
+ }
+
+ @Override
+ public String toString() {
+ return response.toString();
+ }
+
+ private static ResponseBody ifNullBodyToEmpty(@Nullable ResponseBody body) {
+ return body == null ? Util.EMPTY_RESPONSE : body;
+ }
+
+ @Override
+ public void close() throws IOException {
+ Util.closeQuietly(this.body);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/LogLevel.java b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/LogLevel.java
new file mode 100644
index 0000000..2c7da6c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/LogLevel.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import okhttp3.logging.HttpLoggingInterceptor;
+
+/**
+ * 鏃ュ織绾у埆
+ *
+ * @author L.cm
+ */
+@Getter
+@AllArgsConstructor
+public enum LogLevel {
+ /**
+ * No logs.
+ */
+ NONE(HttpLoggingInterceptor.Level.NONE),
+ /**
+ * Logs request and response lines.
+ *
+ * <p>Example:
+ * <pre>{@code
+ * --> POST /greeting http/1.1 (3-byte body)
+ *
+ * <-- 200 OK (22ms, 6-byte body)
+ * }</pre>
+ */
+ BASIC(HttpLoggingInterceptor.Level.BASIC),
+ /**
+ * Logs request and response lines and their respective headers.
+ *
+ * <p>Example:
+ * <pre>{@code
+ * --> POST /greeting http/1.1
+ * Host: example.com
+ * Content-Type: plain/text
+ * Content-Length: 3
+ * --> END POST
+ *
+ * <-- 200 OK (22ms)
+ * Content-Type: plain/text
+ * Content-Length: 6
+ * <-- END HTTP
+ * }</pre>
+ */
+ HEADERS(HttpLoggingInterceptor.Level.HEADERS),
+ /**
+ * Logs request and response lines and their respective headers and bodies (if present).
+ *
+ * <p>Example:
+ * <pre>{@code
+ * --> POST /greeting http/1.1
+ * Host: example.com
+ * Content-Type: plain/text
+ * Content-Length: 3
+ *
+ * Hi?
+ * --> END POST
+ *
+ * <-- 200 OK (22ms)
+ * Content-Type: plain/text
+ * Content-Length: 6
+ *
+ * Hello!
+ * <-- END HTTP
+ * }</pre>
+ */
+ BODY(HttpLoggingInterceptor.Level.BODY);
+
+ private final HttpLoggingInterceptor.Level level;
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/Method.java b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/Method.java
new file mode 100644
index 0000000..b93a264
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/Method.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.http;
+
+/**
+ * http method
+ *
+ * @author dream.lu
+ */
+public interface Method {
+ String GET = "GET";
+ String POST = "POST";
+ String PATCH = "PATCH";
+ String PUT = "PUT";
+ String DELETE = "DELETE";
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/MultipartFormBuilder.java b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/MultipartFormBuilder.java
new file mode 100644
index 0000000..5fa8c24
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/MultipartFormBuilder.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http;
+
+import okhttp3.Headers;
+import okhttp3.MultipartBody;
+import okhttp3.RequestBody;
+
+import javax.annotation.Nullable;
+import java.io.File;
+import java.util.Map;
+
+/**
+ * 琛ㄥ崟鏋勯�犲櫒
+ *
+ * @author L.cm
+ */
+public class MultipartFormBuilder {
+ private final HttpRequest request;
+ private final MultipartBody.Builder formBuilder;
+
+ MultipartFormBuilder(HttpRequest request) {
+ this.request = request;
+ this.formBuilder = new MultipartBody.Builder();
+ }
+
+ public MultipartFormBuilder add(String name, @Nullable Object value) {
+ this.formBuilder.addFormDataPart(name, HttpRequest.handleValue(value));
+ return this;
+ }
+
+ public MultipartFormBuilder addMap(@Nullable Map<String, Object> formMap) {
+ if (formMap != null && !formMap.isEmpty()) {
+ formMap.forEach(this::add);
+ }
+ return this;
+ }
+
+ public MultipartFormBuilder add(String name, File file) {
+ String fileName = file.getName();
+ return add(name, fileName, file);
+ }
+
+ public MultipartFormBuilder add(String name, @Nullable String filename, File file) {
+ RequestBody fileBody = RequestBody.create(null, file);
+ return add(name, filename, fileBody);
+ }
+
+ public MultipartFormBuilder add(String name, @Nullable String filename, RequestBody fileBody) {
+ this.formBuilder.addFormDataPart(name, filename, fileBody);
+ return this;
+ }
+
+ public MultipartFormBuilder add(RequestBody body) {
+ this.formBuilder.addPart(body);
+ return this;
+ }
+
+ public MultipartFormBuilder add(@Nullable Headers headers, RequestBody body) {
+ this.formBuilder.addPart(headers, body);
+ return this;
+ }
+
+ public MultipartFormBuilder add(MultipartBody.Part part) {
+ this.formBuilder.addPart(part);
+ return this;
+ }
+
+ public HttpRequest build() {
+ formBuilder.setType(MultipartBody.FORM);
+ MultipartBody formBody = formBuilder.build();
+ this.request.multipartForm(formBody);
+ return this.request;
+ }
+
+ public Exchange execute() {
+ return this.build().execute();
+ }
+
+ public AsyncCall async() {
+ return this.build().async();
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/ResponseSpec.java b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/ResponseSpec.java
new file mode 100644
index 0000000..a3d38d6
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/ResponseSpec.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import okhttp3.*;
+
+import javax.annotation.Nullable;
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * 鐩稿簲鎺ュ彛
+ *
+ * @author L.cm
+ */
+public interface ResponseSpec {
+
+ /**
+ * Returns the HTTP code.
+ *
+ * @return code
+ */
+ int code();
+
+ /**
+ * Returns the HTTP status message.
+ *
+ * @return message
+ */
+ String message();
+
+ /**
+ * Returns the HTTP isSuccessful.
+ *
+ * @return boolean
+ */
+ default boolean isOk() {
+ return false;
+ }
+
+ /**
+ * Returns the is Redirect.
+ *
+ * @return is Redirect
+ */
+ boolean isRedirect();
+
+ /**
+ * Returns the Headers.
+ *
+ * @return Headers
+ */
+ Headers headers();
+
+ /**
+ * Headers Consumer.
+ *
+ * @param consumer Consumer
+ * @return Headers
+ */
+ default ResponseSpec headers(Consumer<Headers> consumer) {
+ consumer.accept(this.headers());
+ return this;
+ }
+
+ /**
+ * Returns the Cookies.
+ *
+ * @return Cookie List
+ */
+ List<Cookie> cookies();
+
+ /**
+ * 璇诲彇娑堣垂 cookie
+ *
+ * @param consumer Consumer
+ * @return ResponseSpec
+ */
+ default ResponseSpec cookies(Consumer<List<Cookie>> consumer) {
+ consumer.accept(this.cookies());
+ return this;
+ }
+
+ /**
+ * Returns body String.
+ *
+ * @return body String
+ */
+ String asString();
+
+ /**
+ * Returns body to byte arrays.
+ *
+ * @return byte arrays
+ */
+ byte[] asBytes();
+
+ /**
+ * Returns body to InputStream.
+ *
+ * @return InputStream
+ */
+ InputStream asStream();
+
+ /**
+ * Returns body to JsonNode.
+ *
+ * @return JsonNode
+ */
+ JsonNode asJsonNode();
+
+ /**
+ * Returns body to Object.
+ *
+ * @param valueType value value type
+ * @return Object
+ */
+ @Nullable
+ <T> T asValue(Class<T> valueType);
+
+ /**
+ * Returns body to Object.
+ *
+ * @param typeReference value Type Reference
+ * @return Object
+ */
+ @Nullable
+ <T> T asValue(TypeReference<T> typeReference);
+
+ /**
+ * Returns body to List.
+ *
+ * @param valueType value type
+ * @return List
+ */
+ <T> List<T> asList(Class<T> valueType);
+
+ /**
+ * Returns body to Map.
+ *
+ * @param keyClass key type
+ * @param valueType value type
+ * @return Map
+ */
+ <K, V> Map<K, V> asMap(Class<?> keyClass, Class<?> valueType);
+
+ /**
+ * Returns body to Map.
+ *
+ * @param valueType value 绫诲瀷
+ * @return Map
+ */
+ <V> Map<String, V> asMap(Class<?> valueType);
+
+ /**
+ * 灏� xml銆乭eml 杞垚瀵硅薄
+ *
+ * @param valueType 瀵硅薄绫�
+ * @param <T> 娉涘瀷
+ * @return 瀵硅薄
+ */
+ <T> T asDomValue(Class<T> valueType);
+
+ /**
+ * 灏� xml銆乭eml 杞垚瀵硅薄
+ *
+ * @param valueType 瀵硅薄绫�
+ * @param <T> 娉涘瀷
+ * @return 瀵硅薄闆嗗悎
+ */
+ <T> List<T> asDomList(Class<T> valueType);
+
+ /**
+ * toFile.
+ *
+ * @param file File
+ */
+ File toFile(File file);
+
+ /**
+ * toFile.
+ *
+ * @param path Path
+ */
+ Path toFile(Path path);
+
+ /**
+ * Returns contentType.
+ *
+ * @return contentType
+ */
+ @Nullable
+ MediaType contentType();
+
+ /**
+ * Returns contentLength.
+ *
+ * @return contentLength
+ */
+ long contentLength();
+
+ /**
+ * Returns rawRequest.
+ *
+ * @return Request
+ */
+ Request rawRequest();
+
+ /**
+ * rawRequest Consumer.
+ *
+ * @param consumer Consumer
+ * @return ResponseSpec
+ */
+ @Nullable
+ default ResponseSpec rawRequest(Consumer<Request> consumer) {
+ consumer.accept(this.rawRequest());
+ return this;
+ }
+
+ /**
+ * Returns rawResponse.
+ *
+ * @return Response
+ */
+ Response rawResponse();
+
+ /**
+ * rawResponse Consumer.
+ *
+ * @param consumer Consumer
+ * @return Response
+ */
+ default ResponseSpec rawResponse(Consumer<Response> consumer) {
+ consumer.accept(this.rawResponse());
+ return this;
+ }
+
+ /**
+ * Returns rawBody.
+ *
+ * @return ResponseBody
+ */
+ @Nullable
+ ResponseBody rawBody();
+
+ /**
+ * rawBody Consumer.
+ *
+ * @param consumer Consumer
+ * @return ResponseBody
+ */
+ @Nullable
+ default ResponseSpec rawBody(Consumer<ResponseBody> consumer) {
+ consumer.accept(this.rawBody());
+ return this;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/RetryInterceptor.java b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/RetryInterceptor.java
new file mode 100644
index 0000000..a6ff367
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/RetryInterceptor.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http;
+
+import lombok.RequiredArgsConstructor;
+import okhttp3.Interceptor;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import org.springframework.retry.backoff.FixedBackOffPolicy;
+import org.springframework.retry.policy.SimpleRetryPolicy;
+import org.springframework.retry.support.RetryTemplate;
+
+import java.io.IOException;
+import java.util.function.Predicate;
+
+/**
+ * 閲嶈瘯鎷︽埅鍣紝搴斿浠g悊闂
+ *
+ * @author L.cm
+ */
+@RequiredArgsConstructor
+public class RetryInterceptor implements Interceptor {
+ private final RetryPolicy retryPolicy;
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ Request request = chain.request();
+ RetryTemplate template = createRetryTemplate(retryPolicy);
+ return template.execute(context -> {
+ Response response = chain.proceed(request);
+ // 缁撴灉闆嗘牎楠�
+ Predicate<ResponseSpec> respPredicate = retryPolicy.getRespPredicate();
+ if (respPredicate == null) {
+ return response;
+ }
+ // copy 涓�浠� body
+ ResponseBody body = response.peekBody(Long.MAX_VALUE);
+ try (HttpResponse httpResponse = new HttpResponse(response)) {
+ if (respPredicate.test(httpResponse)) {
+ throw new IOException("Http Retry ResponsePredicate test Failure.");
+ }
+ }
+ return response.newBuilder().body(body).build();
+ });
+ }
+
+ private static RetryTemplate createRetryTemplate(RetryPolicy policy) {
+ RetryTemplate template = new RetryTemplate();
+ // 閲嶈瘯绛栫暐
+ SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
+ retryPolicy.setMaxAttempts(policy.getMaxAttempts());
+ // 璁剧疆闂撮殧绛栫暐
+ FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
+ backOffPolicy.setBackOffPeriod(policy.getSleepMillis());
+ template.setRetryPolicy(retryPolicy);
+ template.setBackOffPolicy(backOffPolicy);
+ return template;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/RetryPolicy.java b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/RetryPolicy.java
new file mode 100644
index 0000000..2a86938
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/RetryPolicy.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http;
+
+import lombok.Getter;
+import lombok.ToString;
+import org.springframework.retry.policy.SimpleRetryPolicy;
+
+import javax.annotation.Nullable;
+import java.util.function.Predicate;
+
+/**
+ * 閲嶈瘯绛栫暐
+ *
+ * @author dream.lu
+ */
+@Getter
+@ToString
+public class RetryPolicy {
+ public static final RetryPolicy INSTANCE = new RetryPolicy();
+
+ private final int maxAttempts;
+ private final long sleepMillis;
+ @Nullable
+ private final Predicate<ResponseSpec> respPredicate;
+
+ public RetryPolicy() {
+ this(null);
+ }
+
+ public RetryPolicy(int maxAttempts, long sleepMillis) {
+ this(maxAttempts, sleepMillis, null);
+ }
+
+ public RetryPolicy(@Nullable Predicate<ResponseSpec> respPredicate) {
+ this(SimpleRetryPolicy.DEFAULT_MAX_ATTEMPTS, 0L, respPredicate);
+ }
+
+ public RetryPolicy(int maxAttempts, long sleepMillis, @Nullable Predicate<ResponseSpec> respPredicate) {
+ this.maxAttempts = maxAttempts;
+ this.sleepMillis = sleepMillis;
+ this.respPredicate = respPredicate;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/Slf4jLogger.java b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/Slf4jLogger.java
new file mode 100644
index 0000000..2f8f708
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/Slf4jLogger.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http;
+
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.logging.HttpLoggingInterceptor;
+
+/**
+ * OkHttp Slf4j logger
+ *
+ * @author L.cm
+ */
+@Slf4j
+public class Slf4jLogger implements HttpLoggingInterceptor.Logger {
+
+ public static final HttpLoggingInterceptor.Logger INSTANCE = new Slf4jLogger();
+
+ @Override
+ public void log(String message) {
+ log.info(message);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/util/HttpUtil.java b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/util/HttpUtil.java
new file mode 100644
index 0000000..fe9c887
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/main/java/org/springblade/core/http/util/HttpUtil.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.http.Exchange;
+import org.springblade.core.http.FormBuilder;
+import org.springblade.core.http.HttpRequest;
+
+import java.util.Map;
+
+/**
+ * Http璇锋眰宸ュ叿绫�
+ *
+ * @author Chill
+ */
+@Slf4j
+public class HttpUtil {
+
+ /**
+ * GET
+ *
+ * @param url 璇锋眰鐨剈rl
+ * @param queries 璇锋眰鐨勫弬鏁帮紝鍦ㄦ祻瑙堝櫒锛熷悗闈㈢殑鏁版嵁锛屾病鏈夊彲浠ヤ紶null
+ * @return String
+ */
+ public static String get(String url, Map<String, Object> queries) {
+ return get(url, null, queries);
+ }
+
+ /**
+ * GET
+ *
+ * @param url 璇锋眰鐨剈rl
+ * @param header 璇锋眰澶�
+ * @param queries 璇锋眰鐨勫弬鏁帮紝鍦ㄦ祻瑙堝櫒锛熷悗闈㈢殑鏁版嵁锛屾病鏈夊彲浠ヤ紶null
+ * @return String
+ */
+ public static String get(String url, Map<String, String> header, Map<String, Object> queries) {
+ // 娣诲姞璇锋眰澶�
+ HttpRequest httpRequest = HttpRequest.get(url);
+ if (header != null && header.keySet().size() > 0) {
+ header.forEach(httpRequest::addHeader);
+ }
+ // 娣诲姞鍙傛暟
+ httpRequest.queryMap(queries);
+ return httpRequest.execute().asString();
+ }
+
+ /**
+ * POST
+ *
+ * @param url 璇锋眰鐨剈rl
+ * @param params post form 鎻愪氦鐨勫弬鏁�
+ * @return String
+ */
+ public static String post(String url, Map<String, Object> params) {
+ return exchange(url, null, params).asString();
+ }
+
+ /**
+ * POST
+ *
+ * @param url 璇锋眰鐨剈rl
+ * @param header 璇锋眰澶�
+ * @param params post form 鎻愪氦鐨勫弬鏁�
+ * @return String
+ */
+ public static String post(String url, Map<String, String> header, Map<String, Object> params) {
+ return exchange(url, header, params).asString();
+ }
+
+ /**
+ * POST璇锋眰鍙戦�丣SON鏁版嵁
+ *
+ * @param url 璇锋眰鐨剈rl
+ * @param json 璇锋眰鐨刯son涓�
+ * @return String
+ */
+ public static String postJson(String url, String json) {
+ return exchange(url, null, json).asString();
+ }
+
+ /**
+ * POST璇锋眰鍙戦�丣SON鏁版嵁
+ *
+ * @param url 璇锋眰鐨剈rl
+ * @param header 璇锋眰澶�
+ * @param json 璇锋眰鐨刯son涓�
+ * @return String
+ */
+ public static String postJson(String url, Map<String, String> header, String json) {
+ return exchange(url, header, json).asString();
+ }
+
+ public static Exchange exchange(String url, Map<String, String> header, Map<String, Object> params) {
+ HttpRequest httpRequest = HttpRequest.post(url);
+ //娣诲姞璇锋眰澶�
+ if (header != null && header.keySet().size() > 0) {
+ header.forEach(httpRequest::addHeader);
+ }
+ FormBuilder formBuilder = httpRequest.formBuilder();
+ //娣诲姞鍙傛暟
+ if (params != null && params.keySet().size() > 0) {
+ params.forEach(formBuilder::add);
+ }
+ return formBuilder.execute();
+ }
+
+ public static Exchange exchange(String url, Map<String, String> header, String content) {
+ HttpRequest httpRequest = HttpRequest.post(url);
+ //娣诲姞璇锋眰澶�
+ if (header != null && header.keySet().size() > 0) {
+ header.forEach(httpRequest::addHeader);
+ }
+ return httpRequest.bodyString(content).execute();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/BladeProxySelector.java b/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/BladeProxySelector.java
new file mode 100644
index 0000000..83e8900
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/BladeProxySelector.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http.test;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.net.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 浠g悊璁剧疆
+ *
+ * @author L.cm
+ */
+@Slf4j
+public class BladeProxySelector extends ProxySelector {
+
+ @Override
+ public List<Proxy> select(URI uri) {
+ // 娉ㄦ剰浠g悊閮戒笉鍙敤
+ List<Proxy> proxyList = new ArrayList<>();
+ proxyList.add(getProxy("127.0.0.1", 8080));
+ proxyList.add(getProxy("127.0.0.1", 8081));
+ proxyList.add(getProxy("127.0.0.1", 8082));
+ proxyList.add(getProxy("127.0.0.1", 3128));
+ return proxyList;
+ }
+
+ @Override
+ public void connectFailed(URI uri, SocketAddress address, IOException ioe) {
+ // 娉ㄦ剰锛氱粡杩囨祴璇曪紝姝ゅ涓嶄細瑙﹀彂
+ log.error("ConnectFailed uri:{}, address:{}, ioe:{}", uri, address, ioe);
+ }
+
+ /**
+ * 鏋勯�� Proxy
+ *
+ * @param host host
+ * @param port 绔彛
+ * @return Proxy 瀵硅薄
+ */
+ public static Proxy getProxy(String host, int port) {
+ return new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(host, port));
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/HttpRequestDemo.java b/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/HttpRequestDemo.java
new file mode 100644
index 0000000..1f28e63
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/HttpRequestDemo.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http.test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.springblade.core.http.HttpRequest;
+import org.springblade.core.http.LogLevel;
+import org.springblade.core.http.ResponseSpec;
+import okhttp3.Cookie;
+import org.springblade.core.tool.utils.Base64Util;
+
+import java.net.URI;
+import java.time.Duration;
+import java.util.Optional;
+
+/**
+ * This example of blade http
+ *
+ * @author L.cm
+ */
+public class HttpRequestDemo {
+
+ public void doc() {
+ // 璁惧畾鍏ㄥ眬鏃ュ織绾у埆 NONE锛孊ASIC锛孒EADERS锛孊ODY锛� 榛樿锛歂ONE
+ HttpRequest.setGlobalLog(LogLevel.BODY);
+
+ // 鍚屾璇锋眰 url锛屾柟娉曟敮鎸� get銆乸ost銆乸atch銆乸ut銆乨elete
+ HttpRequest.get("https://www.baidu.com")
+ .log(LogLevel.BASIC) //璁惧畾鏈鐨勬棩蹇楃骇鍒紝浼樺厛浜庡叏灞�
+ .addHeader("x-account-id", "blade001") // 娣诲姞 header
+ .addCookie(new Cookie.Builder() // 娣诲姞 cookie
+ .name("sid")
+ .value("blade_user_001")
+ .build()
+ )
+ .query("q", "blade") //璁剧疆 url 鍙傛暟锛岄粯璁よ繘琛� url encode
+ .queryEncoded("name", "encodedValue")
+ .formBuilder() // 琛ㄥ崟鏋勯�犲櫒锛屽悓绫� multipartFormBuilder 鏂囦欢涓婁紶琛ㄥ崟
+ .add("id", 123123) // 琛ㄥ崟鍙傛暟
+ .execute()// 鍙戣捣璇锋眰
+ .asJsonNode();
+ // 缁撴灉闆嗚浆鎹紝娉細濡傛灉缃戠粶寮傚父绛変細鐩存帴鎶涘嚭寮傚父銆�
+ // 鍚岀被鐨勬柟娉曟湁 asString銆乤sBytes
+ // json 绫诲搷搴旓細asJsonNode銆乤sObject銆乤sList銆乤sMap锛岄噰鐢� jackson 澶勭悊
+ // xml銆乭tml鍝嶅簲锛歛sDocument锛岄噰鐢ㄧ殑 jsoup 澶勭悊
+ // file 鏂囦欢锛歵oFile
+
+ // 鍚屾
+ String html = HttpRequest.post("https://www.baidu.com")
+ .execute()
+ .onSuccess(ResponseSpec::asString);// 澶勭悊鍝嶅簲锛屾湁缃戠粶寮傚父绛夌洿鎺ヨ繑鍥� null
+
+ // 鍚屾
+ String text = HttpRequest.patch("https://www.baidu.com")
+ .execute()
+ .onSuccess(ResponseSpec::asString);
+ // onSuccess http code in [200..300) 澶勭悊鍝嶅簲锛屾湁缃戠粶寮傚父绛夌洿鎺ヨ繑鍥� null
+
+ // 鍙戦�佸紓姝ヨ姹�
+ HttpRequest.delete("https://www.baidu.com")
+ .async() // 寮�鍚紓姝�
+ .onFailed((request, e) -> { // 寮傚父鏃剁殑澶勭悊
+ e.printStackTrace();
+ })
+ .onSuccessful(responseSpec -> { // 娑堣垂鍝嶅簲鎴愬姛 http code in [200..300)
+ // 娉ㄦ剰锛氬搷搴旂粨鏋滄祦鍙兘璇讳竴娆�
+ JsonNode jsonNode = responseSpec.asJsonNode();
+ });
+ }
+
+ public static void main(String[] args) {
+ // 璁惧畾鍏ㄥ眬鏃ュ織绾у埆
+ HttpRequest.setGlobalLog(LogLevel.BODY);
+
+ // 鍚屾锛屽紓甯告椂 杩斿洖 null
+ String html = HttpRequest.get("https://www.baidu.com")
+ .connectTimeout(Duration.ofSeconds(1000))
+ .query("test", "a")
+ .query("name", "寮典笁")
+ .query("x", 1)
+ .query("abd", Base64Util.encode("123&$#%"))
+ .queryEncoded("abc", Base64Util.encode("123&$#%"))
+ .execute()
+ .onFailed(((request, e) -> {
+ e.printStackTrace();
+ }))
+ .onSuccess(ResponseSpec::asString);
+ System.out.println(html);
+
+ // 鍚屾璋冪敤锛岃繑鍥� Optional锛屽紓甯告椂杩斿洖 Optional.empty()
+ Optional<String> opt = HttpRequest.post(URI.create("https://www.baidu.com"))
+ .bodyString("Important stuff")
+ .formBuilder()
+ .add("a", "b")
+ .execute()
+ .onSuccessOpt(ResponseSpec::asString);
+
+ // 鍚屾锛屾垚鍔熸椂娑堣垂锛堝鐞嗭級 response
+ HttpRequest.post("https://www.baidu.com/some-form")
+ .addHeader("X-Custom-header", "stuff")
+ .execute()
+ .onFailed((request, e) -> {
+ e.printStackTrace();
+ })
+ .onSuccessful(ResponseSpec::asString);
+
+ // async锛屽紓姝ユ墽琛岀粨鏋滐紝澶辫触鏃舵墦鍗板爢鏍�
+ HttpRequest.get("https://www.baidu.com/some-form")
+ .async()
+ .onFailed((request, e) -> {
+ e.printStackTrace();
+ })
+ .onSuccessful(System.out::println);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/HttpRequestProxyTest.java b/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/HttpRequestProxyTest.java
new file mode 100644
index 0000000..4be1ec2
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/HttpRequestProxyTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.http.test;
+
+import org.springblade.core.http.HttpRequest;
+
+public class HttpRequestProxyTest {
+
+ //@Test(expected = IOException.class)
+ public void proxy() {
+ // 浠g悊閮戒笉鍙敤
+ HttpRequest.get("https://www.baidu.com")
+ .log()
+ .retry()
+ .proxySelector(new BladeProxySelector())
+ .execute()
+ .asString();
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/OsChina.java b/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/OsChina.java
new file mode 100644
index 0000000..1a56684
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/OsChina.java
@@ -0,0 +1,22 @@
+package org.springblade.core.http.test;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springblade.core.http.CssQuery;
+
+import java.util.List;
+
+@Getter
+@Setter
+public class OsChina {
+
+ @CssQuery(value = "head > title", attr = "text")
+ private String title;
+
+ @CssQuery(value = "#v_news .page .news", inner = true)
+ private List<VNews> vNews;
+
+ @CssQuery(value = ".blog-container .blog-list div", inner = true)
+ private List<VBlog> vBlogList;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/OsChinaTest.java b/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/OsChinaTest.java
new file mode 100644
index 0000000..ee7165f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/OsChinaTest.java
@@ -0,0 +1,38 @@
+package org.springblade.core.http.test;
+
+import org.springblade.core.http.HttpRequest;
+
+import java.util.List;
+
+public class OsChinaTest {
+
+ public static void main(String[] args) {
+ // 鍚屾锛屽紓甯歌繑鍥� null
+ OsChina oschina = HttpRequest.get("https://www.oschina.net")
+ .execute()
+ .onSuccess(responseSpec -> responseSpec.asDomValue(OsChina.class));
+ if (oschina == null) {
+ return;
+ }
+ System.out.println(oschina.getTitle());
+
+ System.out.println("鐑棬鏂伴椈");
+
+ List<VNews> vNews = oschina.getVNews();
+ for (VNews vNew : vNews) {
+ System.out.println("title:\t" + vNew.getTitle());
+ System.out.println("href:\t" + vNew.getHref());
+ System.out.println("鏃堕棿:\t" + vNew.getDate());
+ }
+
+ System.out.println("鐑棬鍗氬");
+ List<VBlog> vBlogList = oschina.getVBlogList();
+ for (VBlog vBlog : vBlogList) {
+ System.out.println("title:\t" + vBlog.getTitle());
+ System.out.println("href:\t" + vBlog.getHref());
+ System.out.println("闃呰鏁�:\t" + vBlog.getRead());
+ System.out.println("璇勪环鏁�:\t" + vBlog.getPing());
+ System.out.println("鐐硅禐鏁�:\t" + vBlog.getZhan());
+ }
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/VBlog.java b/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/VBlog.java
new file mode 100644
index 0000000..b7aa3f4
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/VBlog.java
@@ -0,0 +1,30 @@
+package org.springblade.core.http.test;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springblade.core.http.CssQuery;
+
+/**
+ * 鐑棬鍗氬
+ */
+@Getter
+@Setter
+public class VBlog {
+
+ @CssQuery(value = "a", attr = "title")
+ private String title;
+
+ @CssQuery(value = "a", attr = "href")
+ private String href;
+
+ //1341闃�/9璇�/4璧�
+ @CssQuery(value = "span", attr = "text", regex = "^\\d+")
+ private Integer read;
+
+ @CssQuery(value = "span", attr = "text", regex = "(\\d*).*/(\\d*).*/(\\d*).*", regexGroup = 2)
+ private Integer ping;
+
+ @CssQuery(value = "span", attr = "text", regex = "(\\d*).*/(\\d*).*/(\\d*).*", regexGroup = 3)
+ private Integer zhan;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/VNews.java b/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/VNews.java
new file mode 100644
index 0000000..536dbe4
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-http/src/test/java/org/springblade/core/http/test/VNews.java
@@ -0,0 +1,24 @@
+package org.springblade.core.http.test;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springblade.core.http.CssQuery;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+@Setter
+@Getter
+public class VNews {
+
+ @CssQuery(value = "a", attr = "title")
+ private String title;
+
+ @CssQuery(value = "a", attr = "href")
+ private String href;
+
+ @CssQuery(value = ".news-date", attr = "text")
+ @DateTimeFormat(pattern = "MM/dd")
+ private Date date;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-jwt/pom.xml b/Source/BladeX-Tool/blade-starter-jwt/pom.xml
new file mode 100644
index 0000000..6f6b9c0
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-jwt/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-jwt</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!-- Redis -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-data-redis</artifactId>
+ </dependency>
+ <!-- JWT -->
+ <dependency>
+ <groupId>io.jsonwebtoken</groupId>
+ <artifactId>jjwt-impl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.jsonwebtoken</groupId>
+ <artifactId>jjwt-jackson</artifactId>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/JwtUtil.java b/Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/JwtUtil.java
new file mode 100644
index 0000000..96bcc8b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/JwtUtil.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.jwt;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import org.springblade.core.jwt.props.JwtProperties;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.util.StringUtils;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Jwt宸ュ叿绫�
+ *
+ * @author Chill
+ */
+public class JwtUtil {
+
+ /**
+ * token鍩虹閰嶇疆
+ */
+ public static String BEARER = "bearer";
+ public static Integer AUTH_LENGTH = 7;
+
+ /**
+ * token淇濆瓨鑷硆edis鐨刱ey
+ */
+ private static final String REFRESH_TOKEN_CACHE = "blade:refreshToken";
+ private static final String TOKEN_CACHE = "blade:token";
+ private static final String TOKEN_KEY = "token:state:";
+
+ /**
+ * jwt閰嶇疆
+ */
+ private static JwtProperties jwtProperties;
+
+ /**
+ * redis宸ュ叿
+ */
+ private static RedisTemplate<String, Object> redisTemplate;
+
+ public static JwtProperties getJwtProperties() {
+ return jwtProperties;
+ }
+
+ public static void setJwtProperties(JwtProperties properties) {
+ if (JwtUtil.jwtProperties == null) {
+ JwtUtil.jwtProperties = properties;
+ }
+ }
+
+ public static RedisTemplate<String, Object> getRedisTemplate() {
+ return redisTemplate;
+ }
+
+ public static void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
+ if (JwtUtil.redisTemplate == null) {
+ JwtUtil.redisTemplate = redisTemplate;
+ }
+ }
+
+ /**
+ * 绛惧悕鍔犲瘑
+ */
+ public static String getBase64Security() {
+ return Base64.getEncoder().encodeToString(getJwtProperties().getSignKey().getBytes(StandardCharsets.UTF_8));
+ }
+
+ /**
+ * 鑾峰彇璇锋眰浼犻�掔殑token涓�
+ *
+ * @param auth token
+ * @return String
+ */
+ public static String getToken(String auth) {
+ if ((auth != null) && (auth.length() > AUTH_LENGTH)) {
+ String headStr = auth.substring(0, 6).toLowerCase();
+ if (headStr.compareTo(BEARER) == 0) {
+ auth = auth.substring(7);
+ }
+ return auth;
+ }
+ return null;
+ }
+
+ /**
+ * 瑙f瀽jsonWebToken
+ *
+ * @param jsonWebToken token涓�
+ * @return Claims
+ */
+ public static Claims parseJWT(String jsonWebToken) {
+ try {
+ return Jwts.parserBuilder()
+ .setSigningKey(Base64.getDecoder().decode(getBase64Security())).build()
+ .parseClaimsJws(jsonWebToken).getBody();
+ } catch (Exception ex) {
+ return null;
+ }
+ }
+
+ /**
+ * 鑾峰彇淇濆瓨鍦╮edis鐨刟ccessToken
+ *
+ * @param tenantId 绉熸埛id
+ * @param userId 鐢ㄦ埛id
+ * @param accessToken accessToken
+ * @return accessToken
+ */
+ public static String getAccessToken(String tenantId, String userId, String accessToken) {
+ return String.valueOf(getRedisTemplate().opsForValue().get(getAccessTokenKey(tenantId, userId, accessToken)));
+ }
+
+
+ /**
+ * 娣诲姞accessToken鑷硆edis
+ *
+ * @param tenantId 绉熸埛id
+ * @param userId 鐢ㄦ埛id
+ * @param accessToken accessToken
+ * @param expire 杩囨湡鏃堕棿
+ */
+ public static void addAccessToken(String tenantId, String userId, String accessToken, int expire) {
+ getRedisTemplate().delete(getAccessTokenKey(tenantId, userId, accessToken));
+ getRedisTemplate().opsForValue().set(getAccessTokenKey(tenantId, userId, accessToken), accessToken, expire, TimeUnit.SECONDS);
+ }
+
+ /**
+ * 鍒犻櫎淇濆瓨鍦╮edis鐨刟ccessToken
+ *
+ * @param tenantId 绉熸埛id
+ * @param userId 鐢ㄦ埛id
+ */
+ public static void removeAccessToken(String tenantId, String userId) {
+ removeAccessToken(tenantId, userId, null);
+ }
+
+ /**
+ * 鍒犻櫎淇濆瓨鍦╮edis鐨刟ccessToken
+ *
+ * @param tenantId 绉熸埛id
+ * @param userId 鐢ㄦ埛id
+ * @param accessToken accessToken
+ */
+ public static void removeAccessToken(String tenantId, String userId, String accessToken) {
+ getRedisTemplate().delete(getAccessTokenKey(tenantId, userId, accessToken));
+ }
+
+ /**
+ * 鑾峰彇accessToken绱㈠紩
+ *
+ * @param tenantId 绉熸埛id
+ * @param userId 鐢ㄦ埛id
+ * @param accessToken accessToken
+ * @return token绱㈠紩
+ */
+ public static String getAccessTokenKey(String tenantId, String userId, String accessToken) {
+ String key = tenantId.concat(":").concat(TOKEN_CACHE).concat("::").concat(TOKEN_KEY);
+ if (getJwtProperties().getSingle() || StringUtils.isEmpty(accessToken)) {
+ return key.concat(userId);
+ } else {
+ return key.concat(accessToken);
+ }
+ }
+
+ /**
+ * 鑾峰彇淇濆瓨鍦╮edis鐨剅efreshToken
+ *
+ * @param tenantId 绉熸埛id
+ * @param userId 鐢ㄦ埛id
+ * @param refreshToken refreshToken
+ * @return accessToken
+ */
+ public static String getRefreshToken(String tenantId, String userId, String refreshToken) {
+ return String.valueOf(getRedisTemplate().opsForValue().get(getRefreshTokenKey(tenantId, userId)));
+ }
+
+ /**
+ * 娣诲姞refreshToken鑷硆edis
+ *
+ * @param tenantId 绉熸埛id
+ * @param userId 鐢ㄦ埛id
+ * @param refreshToken refreshToken
+ * @param expire 杩囨湡鏃堕棿
+ */
+ public static void addRefreshToken(String tenantId, String userId, String refreshToken, int expire) {
+ getRedisTemplate().delete(getRefreshTokenKey(tenantId, userId));
+ getRedisTemplate().opsForValue().set(getRefreshTokenKey(tenantId, userId), refreshToken, expire, TimeUnit.SECONDS);
+ }
+
+ /**
+ * 鍒犻櫎淇濆瓨鍦╮efreshToken鐨則oken
+ *
+ * @param tenantId 绉熸埛id
+ * @param userId 鐢ㄦ埛id
+ */
+ public static void removeRefreshToken(String tenantId, String userId) {
+ getRedisTemplate().delete(getRefreshTokenKey(tenantId, userId));
+ }
+
+ /**
+ * 鑾峰彇refreshToken绱㈠紩
+ *
+ * @param tenantId 绉熸埛id
+ * @param userId 鐢ㄦ埛id
+ * @return token绱㈠紩
+ */
+ public static String getRefreshTokenKey(String tenantId, String userId) {
+ return tenantId.concat(":").concat(REFRESH_TOKEN_CACHE).concat("::").concat(TOKEN_KEY).concat(userId);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/config/JwtConfiguration.java b/Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/config/JwtConfiguration.java
new file mode 100644
index 0000000..e33611b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/config/JwtConfiguration.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.jwt.config;
+
+import lombok.AllArgsConstructor;
+import org.springblade.core.jwt.JwtUtil;
+import org.springblade.core.jwt.props.JwtProperties;
+import org.springblade.core.jwt.serializer.JwtRedisKeySerializer;
+import org.springframework.beans.factory.SmartInitializingSingleton;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
+
+/**
+ * Jwt閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+@AutoConfiguration(after = JwtRedisConfiguration.class)
+@EnableConfigurationProperties({JwtProperties.class})
+public class JwtConfiguration implements SmartInitializingSingleton {
+
+ private final JwtProperties jwtProperties;
+ private final RedisConnectionFactory redisConnectionFactory;
+
+ @Override
+ public void afterSingletonsInstantiated() {
+ // redisTemplate 瀹炰緥鍖�
+ RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
+ JwtRedisKeySerializer redisKeySerializer = new JwtRedisKeySerializer();
+ JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();
+ // key 搴忓垪鍖�
+ redisTemplate.setKeySerializer(redisKeySerializer);
+ redisTemplate.setHashKeySerializer(redisKeySerializer);
+ // value 搴忓垪鍖�
+ redisTemplate.setValueSerializer(jdkSerializationRedisSerializer);
+ redisTemplate.setHashValueSerializer(jdkSerializationRedisSerializer);
+ redisTemplate.setConnectionFactory(redisConnectionFactory);
+ redisTemplate.afterPropertiesSet();
+ JwtUtil.setJwtProperties(jwtProperties);
+ JwtUtil.setRedisTemplate(redisTemplate);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/config/JwtRedisConfiguration.java b/Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/config/JwtRedisConfiguration.java
new file mode 100644
index 0000000..f6996c5
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/config/JwtRedisConfiguration.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.jwt.config;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.annotation.Order;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+
+import java.time.Duration;
+
+/**
+ * RedisTemplate 閰嶇疆
+ *
+ * @author Chill
+ */
+@Order
+@EnableCaching
+@AutoConfiguration(after = RedisAutoConfiguration.class)
+public class JwtRedisConfiguration {
+
+ @Bean("redisCacheManager")
+ @ConditionalOnMissingBean(name = "redisCacheManager")
+ public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
+ RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
+ .entryTtl(Duration.ofHours(1));
+ return RedisCacheManager
+ .builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
+ .cacheDefaults(redisCacheConfiguration).build();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/constant/JwtConstant.java b/Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/constant/JwtConstant.java
new file mode 100644
index 0000000..c86fcb5
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/constant/JwtConstant.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.jwt.constant;
+
+/**
+ * Jwt甯搁噺
+ *
+ * @author Chill
+ */
+public interface JwtConstant {
+
+ /**
+ * 榛樿key
+ */
+ String DEFAULT_SECRET_KEY = "bladexisapowerfulmicroservicearchitectureupgradedandoptimizedfromacommercialproject";
+
+ /**
+ * key瀹夊叏闀垮害锛屽叿浣撹锛歨ttps://tools.ietf.org/html/rfc7518#section-3.2
+ */
+ int SECRET_KEY_LENGTH = 32;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/props/JwtProperties.java b/Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/props/JwtProperties.java
new file mode 100644
index 0000000..e3dd560
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/props/JwtProperties.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.jwt.props;
+
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.jwt.constant.JwtConstant;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * JWT閰嶇疆
+ *
+ * @author Chill
+ */
+@Slf4j
+@Data
+@ConfigurationProperties("blade.token")
+public class JwtProperties {
+
+ /**
+ * token鏄惁鏈夌姸鎬�
+ */
+ private Boolean state = Boolean.FALSE;
+
+ /**
+ * 鏄惁鍙彲鍚屾椂鍦ㄧ嚎涓�浜�
+ */
+ private Boolean single = Boolean.FALSE;
+
+ /**
+ * token绛惧悕
+ */
+ private String signKey = JwtConstant.DEFAULT_SECRET_KEY;
+
+ /**
+ * 鑾峰彇绛惧悕瑙勫垯
+ */
+ public String getSignKey() {
+ if (this.signKey.length() < JwtConstant.SECRET_KEY_LENGTH) {
+ log.warn("Token宸插惎鐢ㄩ粯璁ょ鍚�,璇峰墠寰�blade.token.sign-key璁剧疆32浣嶇殑key");
+ return JwtConstant.DEFAULT_SECRET_KEY;
+ }
+ return this.signKey;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/serializer/JwtRedisKeySerializer.java b/Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/serializer/JwtRedisKeySerializer.java
new file mode 100644
index 0000000..06de3ee
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-jwt/src/main/java/org/springblade/core/jwt/serializer/JwtRedisKeySerializer.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.jwt.serializer;
+
+import org.springframework.cache.interceptor.SimpleKey;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.data.redis.serializer.RedisSerializer;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+
+/**
+ * 灏唕edis key搴忓垪鍖栦负瀛楃涓�
+ *
+ * <p>
+ * spring cache涓殑绠�鍗曞熀鏈被鍨嬬洿鎺ヤ娇鐢� StringRedisSerializer 浼氭湁闂
+ * </p>
+ *
+ * @author L.cm
+ */
+public class JwtRedisKeySerializer implements RedisSerializer<Object> {
+ private final Charset charset;
+ private final ConversionService converter;
+
+ public JwtRedisKeySerializer() {
+ this(StandardCharsets.UTF_8);
+ }
+
+ public JwtRedisKeySerializer(Charset charset) {
+ Objects.requireNonNull(charset, "Charset must not be null");
+ this.charset = charset;
+ this.converter = DefaultConversionService.getSharedInstance();
+ }
+
+ @Override
+ public Object deserialize(byte[] bytes) {
+ // redis keys 浼氱敤鍒板弽搴忓垪鍖�
+ if (bytes == null) {
+ return null;
+ }
+ return new String(bytes, charset);
+ }
+
+ @Override
+ public byte[] serialize(Object object) {
+ Objects.requireNonNull(object, "redis key is null");
+ String key;
+ if (object instanceof SimpleKey) {
+ key = "";
+ } else if (object instanceof String) {
+ key = (String) object;
+ } else {
+ key = converter.convert(object, String.class);
+ }
+ return key.getBytes(this.charset);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-loadbalancer/pom.xml b/Source/BladeX-Tool/blade-starter-loadbalancer/pom.xml
new file mode 100644
index 0000000..45765a4
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-loadbalancer/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-loadbalancer</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!-- LoadBalancer -->
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-starter-loadbalancer</artifactId>
+ </dependency>
+ <!-- Feign -->
+ <dependency>
+ <groupId>io.github.openfeign</groupId>
+ <artifactId>feign-okhttp</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-starter-openfeign</artifactId>
+ </dependency>
+ <!-- Nacos -->
+ <dependency>
+ <groupId>com.alibaba.cloud</groupId>
+ <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>com.alibaba.nacos</groupId>
+ <artifactId>nacos-client</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>com.alibaba.cloud</groupId>
+ <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>com.alibaba.nacos</groupId>
+ <artifactId>nacos-client</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>com.alibaba.nacos</groupId>
+ <artifactId>nacos-client</artifactId>
+ </dependency>
+ <!-- Web -->
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-web</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!-- WebFlux -->
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-webflux</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/config/BladeLoadBalancerConfiguration.java b/Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/config/BladeLoadBalancerConfiguration.java
new file mode 100644
index 0000000..4519171
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/config/BladeLoadBalancerConfiguration.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.loadbalancer.config;
+
+import org.springblade.core.loadbalancer.props.BladeLoadBalancerProperties;
+import org.springblade.core.loadbalancer.rule.GrayscaleLoadBalancer;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration;
+import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification;
+import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
+import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
+import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.env.Environment;
+
+/**
+ * blade 璐熻浇鍧囪 绛栫暐
+ *
+ * @author Chill
+ */
+@AutoConfiguration(before = LoadBalancerClientConfiguration.class)
+@EnableConfigurationProperties(BladeLoadBalancerProperties.class)
+@ConditionalOnProperty(value = BladeLoadBalancerProperties.PROPERTIES_PREFIX + ".enabled", matchIfMissing = true)
+@Order(BladeLoadBalancerConfiguration.REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER)
+public class BladeLoadBalancerConfiguration {
+ public static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 193827465;
+
+ @Bean
+ public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
+ LoadBalancerClientFactory loadBalancerClientFactory,
+ BladeLoadBalancerProperties bladeLoadBalancerProperties) {
+ String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
+ return new GrayscaleLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), bladeLoadBalancerProperties);
+ }
+
+ @Bean
+ public LoadBalancerClientSpecification loadBalancerClientSpecification() {
+ return new LoadBalancerClientSpecification("default.bladeLoadBalancerConfiguration",
+ new Class[]{BladeLoadBalancerConfiguration.class});
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/constant/LoadBalancerConstant.java b/Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/constant/LoadBalancerConstant.java
new file mode 100644
index 0000000..53d6119
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/constant/LoadBalancerConstant.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.loadbalancer.constant;
+
+/**
+ * LoadBalancer 甯搁噺
+ *
+ * @author Chill
+ */
+public interface LoadBalancerConstant {
+
+ /**
+ * 鐏板害鏈嶅姟鐨勮姹傚ご鍙傛暟
+ */
+ String VERSION_NAME = "version";
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/props/BladeLoadBalancerProperties.java b/Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/props/BladeLoadBalancerProperties.java
new file mode 100644
index 0000000..f458308
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/props/BladeLoadBalancerProperties.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.loadbalancer.props;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * LoadBalancer 閰嶇疆
+ *
+ * @author Chill
+ */
+@Getter
+@Setter
+@RefreshScope
+@ConfigurationProperties(BladeLoadBalancerProperties.PROPERTIES_PREFIX)
+public class BladeLoadBalancerProperties {
+ public static final String PROPERTIES_PREFIX = "blade.loadbalancer";
+
+ /**
+ * 鏄惁寮�鍚嚜瀹氫箟璐熻浇鍧囪
+ */
+ private boolean enabled = true;
+ /**
+ * 鐏板害鏈嶅姟鐗堟湰
+ */
+ private String version;
+ /**
+ * 浼樺厛鐨刬p鍒楄〃锛屾敮鎸侀�氶厤绗︼紝渚嬪锛�10.20.0.8*銆�10.20.0.*
+ */
+ private List<String> priorIpPattern = new ArrayList<>();
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/rule/GrayscaleEnvPostProcessor.java b/Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/rule/GrayscaleEnvPostProcessor.java
new file mode 100644
index 0000000..b2f484e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/rule/GrayscaleEnvPostProcessor.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.loadbalancer.rule;
+
+import org.springblade.core.auto.annotation.AutoEnvPostProcessor;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.env.EnvironmentPostProcessor;
+import org.springframework.core.Ordered;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.util.StringUtils;
+
+/**
+ * 鐏板害鐗堟湰 鑷姩澶勭悊
+ *
+ * @author Chill
+ */
+@AutoEnvPostProcessor
+public class GrayscaleEnvPostProcessor implements EnvironmentPostProcessor, Ordered {
+ private static final String GREYSCALE_KEY = "blade.loadbalancer.version";
+ private static final String METADATA_KEY = "spring.cloud.nacos.discovery.metadata.version";
+
+ @Override
+ public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
+ String version = environment.getProperty(GREYSCALE_KEY);
+
+ if (StringUtils.hasText(version)) {
+ environment.getSystemProperties().put(METADATA_KEY, version);
+ }
+ }
+
+ @Override
+ public int getOrder() {
+ return Ordered.LOWEST_PRECEDENCE;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/rule/GrayscaleLoadBalancer.java b/Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/rule/GrayscaleLoadBalancer.java
new file mode 100644
index 0000000..476ad47
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/rule/GrayscaleLoadBalancer.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.loadbalancer.rule;
+
+import com.alibaba.nacos.common.utils.StringUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.loadbalancer.props.BladeLoadBalancerProperties;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.loadbalancer.*;
+import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
+import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
+import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
+import org.springframework.http.HttpHeaders;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.PatternMatchUtils;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.Collectors;
+
+import static org.springblade.core.loadbalancer.constant.LoadBalancerConstant.VERSION_NAME;
+
+/**
+ * LoadBalancer 璐熻浇瑙勫垯
+ *
+ * @author Chill
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class GrayscaleLoadBalancer implements ReactorServiceInstanceLoadBalancer {
+ private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
+ private final BladeLoadBalancerProperties bladeLoadBalancerProperties;
+
+ @Override
+ public Mono<Response<ServiceInstance>> choose(Request request) {
+ ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
+ .getIfAvailable(NoopServiceInstanceListSupplier::new);
+ return supplier.get(request).next()
+ .map(serviceInstances -> getInstanceResponse(serviceInstances, request));
+ }
+
+ /**
+ * 鑷畾涔夎妭鐐硅鍒欒繑鍥炵洰鏍囪妭鐐�
+ */
+ private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, Request request) {
+ // 娉ㄥ唽涓績鏃犲彲鐢ㄥ疄渚� 杩斿洖绌�
+ if (CollectionUtils.isEmpty(instances)) {
+ return new EmptyResponse();
+ }
+ // 鎸囧畾ip鍒欒繑鍥炴弧瓒砳p鐨勬湇鍔�
+ List<String> priorIpPattern = bladeLoadBalancerProperties.getPriorIpPattern();
+ if (!priorIpPattern.isEmpty()) {
+ String[] priorIpPatterns = priorIpPattern.toArray(new String[0]);
+ List<ServiceInstance> priorIpInstances = instances.stream().filter(
+ (i -> PatternMatchUtils.simpleMatch(priorIpPatterns, i.getHost()))
+ ).collect(Collectors.toList());
+ if (!priorIpInstances.isEmpty()) {
+ instances = priorIpInstances;
+ }
+ }
+
+ // 鑾峰彇鐏板害鐗堟湰鍙�
+ DefaultRequestContext context = (DefaultRequestContext) request.getContext();
+ RequestData requestData = (RequestData) context.getClientRequest();
+ HttpHeaders headers = requestData.getHeaders();
+ String versionName = headers.getFirst(VERSION_NAME);
+
+ // 娌℃湁鎸囧畾鐏板害鐗堟湰鍒欒繑鍥炴寮忕殑鏈嶅姟
+ if (StringUtils.isBlank(versionName)) {
+ List<ServiceInstance> noneGrayscaleInstances = instances.stream().filter(
+ i -> !i.getMetadata().containsKey(VERSION_NAME)
+ ).collect(Collectors.toList());
+ return randomInstance(noneGrayscaleInstances);
+ }
+
+ // 鎸囧畾鐏板害鐗堟湰鍒欒繑鍥炴爣璁扮殑鏈嶅姟
+ List<ServiceInstance> grayscaleInstances = instances.stream().filter(i -> {
+ String versionNameInMetadata = i.getMetadata().get(VERSION_NAME);
+ return StringUtils.equalsIgnoreCase(versionNameInMetadata, versionName);
+ }).collect(Collectors.toList());
+ return randomInstance(grayscaleInstances);
+ }
+
+ /**
+ * 閲囩敤闅忔満瑙勫垯杩斿洖
+ */
+ private Response<ServiceInstance> randomInstance(List<ServiceInstance> instances) {
+ // 鑻ユ病鏈夊彲鐢ㄨ妭鐐瑰垯杩斿洖绌�
+ if (instances.isEmpty()) {
+ return new EmptyResponse();
+ }
+
+ // 鎸戦�夐殢鏈鸿妭鐐硅繑鍥�
+ int randomIndex = ThreadLocalRandom.current().nextInt(instances.size());
+ ServiceInstance instance = instances.get(randomIndex % instances.size());
+ return new DefaultResponse(instance);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/pom.xml b/Source/BladeX-Tool/blade-starter-log/pom.xml
new file mode 100644
index 0000000..feb9f9a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/pom.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-log</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!--Blade-->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-cloud</artifactId>
+ </dependency>
+ <!--Mybatis-->
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus</artifactId>
+ </dependency>
+ <!-- Logstash -->
+ <dependency>
+ <groupId>net.logstash.logback</groupId>
+ <artifactId>logstash-logback-encoder</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.janino</groupId>
+ <artifactId>janino</artifactId>
+ </dependency>
+ <!-- validator -->
+ <dependency>
+ <artifactId>hibernate-validator</artifactId>
+ <groupId>org.hibernate.validator</groupId>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/annotation/ApiLog.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/annotation/ApiLog.java
new file mode 100644
index 0000000..fc7a5fa
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/annotation/ApiLog.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+
+package org.springblade.core.log.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 鎿嶄綔鏃ュ織娉ㄨВ
+ *
+ * @author Chill
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface ApiLog {
+
+ /**
+ * 鏃ュ織鎻忚堪
+ *
+ * @return {String}
+ */
+ String value() default "鏃ュ織璁板綍";
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/aspect/ApiLogAspect.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/aspect/ApiLogAspect.java
new file mode 100644
index 0000000..c897698
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/aspect/ApiLogAspect.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+
+package org.springblade.core.log.aspect;
+
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springblade.core.log.annotation.ApiLog;
+import org.springblade.core.log.publisher.ApiLogPublisher;
+
+/**
+ * 鎿嶄綔鏃ュ織浣跨敤spring event寮傛鍏ュ簱
+ *
+ * @author Chill
+ */
+@Slf4j
+@Aspect
+public class ApiLogAspect {
+
+ @Around("@annotation(apiLog)")
+ public Object around(ProceedingJoinPoint point, ApiLog apiLog) throws Throwable {
+ //鑾峰彇绫诲悕
+ String className = point.getTarget().getClass().getName();
+ //鑾峰彇鏂规硶
+ String methodName = point.getSignature().getName();
+ // 鍙戦�佸紓姝ユ棩蹇椾簨浠�
+ long beginTime = System.currentTimeMillis();
+ //鎵ц鏂规硶
+ Object result = point.proceed();
+ //鎵ц鏃堕暱(姣)
+ long time = System.currentTimeMillis() - beginTime;
+ //璁板綍鏃ュ織
+ ApiLogPublisher.publishEvent(methodName, className, apiLog, time);
+ return result;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/aspect/LogTraceAspect.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/aspect/LogTraceAspect.java
new file mode 100644
index 0000000..0207108
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/aspect/LogTraceAspect.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.aspect;
+
+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.springblade.core.log.utils.LogTraceUtil;
+
+/**
+ * 涓哄紓姝ユ柟娉曟坊鍔爐raceId
+ *
+ * @author Chill
+ */
+@Aspect
+public class LogTraceAspect {
+
+ @Pointcut("@annotation(org.springframework.scheduling.annotation.Async)")
+ public void logPointCut() {
+ }
+
+ @Around("logPointCut()")
+ public Object around(ProceedingJoinPoint point) throws Throwable {
+ try {
+ LogTraceUtil.insert();
+ return point.proceed();
+ } finally {
+ LogTraceUtil.remove();
+ }
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/aspect/RequestLogAspect.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/aspect/RequestLogAspect.java
new file mode 100644
index 0000000..1008b09
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/aspect/RequestLogAspect.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.log.aspect;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springblade.core.launch.log.BladeLogLevel;
+import org.springblade.core.log.props.BladeRequestLogProperties;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.utils.ClassUtil;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.core.tool.utils.WebUtil;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.io.InputStreamSource;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Spring boot 鎺у埗鍣� 璇锋眰鏃ュ織锛屾柟渚夸唬鐮佽皟璇�
+ *
+ * @author L.cm
+ */
+@Slf4j
+@Aspect
+@AutoConfiguration
+@AllArgsConstructor
+@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
+@ConditionalOnProperty(value = BladeLogLevel.REQ_LOG_PROPS_PREFIX + ".enabled", havingValue = "true", matchIfMissing = true)
+public class RequestLogAspect {
+
+ private final BladeRequestLogProperties properties;
+
+ /**
+ * AOP 鐜垏 鎺у埗鍣� R 杩斿洖鍊�
+ *
+ * @param point JoinPoint
+ * @return Object
+ * @throws Throwable 寮傚父
+ */
+ @Around(
+ "execution(!static org.springblade.core.tool.api.R *(..)) && " +
+ "(@within(org.springframework.stereotype.Controller) || " +
+ "@within(org.springframework.web.bind.annotation.RestController))"
+ )
+ public Object aroundApi(ProceedingJoinPoint point) throws Throwable {
+ BladeLogLevel level = properties.getLevel();
+ // 涓嶆墦鍗版棩蹇楋紝鐩存帴杩斿洖
+ if (BladeLogLevel.NONE == level) {
+ return point.proceed();
+ }
+ HttpServletRequest request = WebUtil.getRequest();
+ String requestUrl = Objects.requireNonNull(request).getRequestURI();
+ String requestMethod = request.getMethod();
+
+ // 鏋勫缓鎴愪竴鏉¢暱 鏃ュ織锛岄伩鍏嶅苟鍙戜笅鏃ュ織閿欎贡
+ StringBuilder beforeReqLog = new StringBuilder(300);
+ // 鏃ュ織鍙傛暟
+ List<Object> beforeReqArgs = new ArrayList<>();
+ beforeReqLog.append("\n\n================ Request Start ================\n");
+ // 鎵撳嵃璺敱
+ beforeReqLog.append("===> {}: {}");
+ beforeReqArgs.add(requestMethod);
+ beforeReqArgs.add(requestUrl);
+ // 鎵撳嵃璇锋眰鍙傛暟
+ logIngArgs(point, beforeReqLog, beforeReqArgs);
+ // 鎵撳嵃璇锋眰 headers
+ logIngHeaders(request, level, beforeReqLog, beforeReqArgs);
+ beforeReqLog.append("================ Request End ================\n");
+
+ // 鎵撳嵃鎵ц鏃堕棿
+ long startNs = System.nanoTime();
+ log.info(beforeReqLog.toString(), beforeReqArgs.toArray());
+ // aop 鎵ц鍚庣殑鏃ュ織
+ StringBuilder afterReqLog = new StringBuilder(200);
+ // 鏃ュ織鍙傛暟
+ List<Object> afterReqArgs = new ArrayList<>();
+ afterReqLog.append("\n\n=============== Response Start ================\n");
+ try {
+ Object result = point.proceed();
+ // 鎵撳嵃杩斿洖缁撴瀯浣�
+ if (BladeLogLevel.BODY.lte(level)) {
+ afterReqLog.append("===Result=== {}\n");
+ afterReqArgs.add(JsonUtil.toJson(result));
+ }
+ return result;
+ } finally {
+ long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
+ afterReqLog.append("<=== {}: {} ({} ms)\n");
+ afterReqArgs.add(requestMethod);
+ afterReqArgs.add(requestUrl);
+ afterReqArgs.add(tookMs);
+ afterReqLog.append("=============== Response End ================\n");
+ log.info(afterReqLog.toString(), afterReqArgs.toArray());
+ }
+ }
+
+ /**
+ * 婵�鍔辫姹傚弬鏁�
+ *
+ * @param point ProceedingJoinPoint
+ * @param beforeReqLog StringBuilder
+ * @param beforeReqArgs beforeReqArgs
+ */
+ public void logIngArgs(ProceedingJoinPoint point, StringBuilder beforeReqLog, List<Object> beforeReqArgs) {
+ MethodSignature ms = (MethodSignature) point.getSignature();
+ Method method = ms.getMethod();
+ Object[] args = point.getArgs();
+ // 璇锋眰鍙傛暟澶勭悊
+ final Map<String, Object> paraMap = new HashMap<>(16);
+ // 涓�娆¤姹傚彧鑳芥湁涓�涓� request body
+ Object requestBodyValue = null;
+ for (int i = 0; i < args.length; i++) {
+ // 璇诲彇鏂规硶鍙傛暟
+ MethodParameter methodParam = ClassUtil.getMethodParameter(method, i);
+ // PathVariable 鍙傛暟璺宠繃
+ PathVariable pathVariable = methodParam.getParameterAnnotation(PathVariable.class);
+ if (pathVariable != null) {
+ continue;
+ }
+ RequestBody requestBody = methodParam.getParameterAnnotation(RequestBody.class);
+ String parameterName = methodParam.getParameterName();
+ Object value = args[i];
+ // 濡傛灉鏄痓ody鐨刯son鍒欐槸瀵硅薄
+ if (requestBody != null) {
+ requestBodyValue = value;
+ continue;
+ }
+ // 澶勭悊 鍙傛暟
+ if (value instanceof HttpServletRequest) {
+ paraMap.putAll(((HttpServletRequest) value).getParameterMap());
+ continue;
+ } else if (value instanceof WebRequest) {
+ paraMap.putAll(((WebRequest) value).getParameterMap());
+ continue;
+ } else if (value instanceof HttpServletResponse) {
+ continue;
+ } else if (value instanceof MultipartFile) {
+ MultipartFile multipartFile = (MultipartFile) value;
+ String name = multipartFile.getName();
+ String fileName = multipartFile.getOriginalFilename();
+ paraMap.put(name, fileName);
+ continue;
+ } else if (value instanceof MultipartFile[]) {
+ MultipartFile[] arr = (MultipartFile[]) value;
+ if (arr.length == 0) {
+ continue;
+ }
+ String name = arr[0].getName();
+ StringBuilder sb = new StringBuilder(arr.length);
+ for (MultipartFile multipartFile : arr) {
+ sb.append(multipartFile.getOriginalFilename());
+ sb.append(StringPool.COMMA);
+ }
+ paraMap.put(name, StringUtil.removeSuffix(sb.toString(), StringPool.COMMA));
+ continue;
+ } else if (value instanceof List) {
+ List<?> list = (List<?>) value;
+ AtomicBoolean isSkip = new AtomicBoolean(false);
+ for (Object o : list) {
+ if ("StandardMultipartFile".equalsIgnoreCase(o.getClass().getSimpleName())) {
+ isSkip.set(true);
+ break;
+ }
+ }
+ if (isSkip.get()) {
+ paraMap.put(parameterName, "姝ゅ弬鏁颁笉鑳藉簭鍒楀寲涓簀son");
+ continue;
+ }
+ }
+ // 鍙傛暟鍚�
+ RequestParam requestParam = methodParam.getParameterAnnotation(RequestParam.class);
+ String paraName = parameterName;
+ if (requestParam != null && StringUtil.isNotBlank(requestParam.value())) {
+ paraName = requestParam.value();
+ }
+ if (value == null) {
+ paraMap.put(paraName, null);
+ } else if (ClassUtil.isPrimitiveOrWrapper(value.getClass())) {
+ paraMap.put(paraName, value);
+ } else if (value instanceof InputStream) {
+ paraMap.put(paraName, "InputStream");
+ } else if (value instanceof InputStreamSource) {
+ paraMap.put(paraName, "InputStreamSource");
+ } else if (JsonUtil.canSerialize(value)) {
+ // 鍒ゆ柇妯″瀷鑳借 json 搴忓垪鍖栵紝鍒欐坊鍔�
+ paraMap.put(paraName, value);
+ } else {
+ paraMap.put(paraName, "姝ゅ弬鏁颁笉鑳藉簭鍒楀寲涓簀son");
+ }
+ }
+ // 璇锋眰鍙傛暟
+ if (paraMap.isEmpty()) {
+ beforeReqLog.append("\n");
+ } else {
+ beforeReqLog.append(" Parameters: {}\n");
+ beforeReqArgs.add(JsonUtil.toJson(paraMap));
+ }
+ if (requestBodyValue != null) {
+ beforeReqLog.append("====Body===== {}\n");
+ beforeReqArgs.add(JsonUtil.toJson(requestBodyValue));
+ }
+ }
+
+ /**
+ * 璁板綍璇锋眰澶�
+ *
+ * @param request HttpServletRequest
+ * @param level 鏃ュ織绾у埆
+ * @param beforeReqLog StringBuilder
+ * @param beforeReqArgs beforeReqArgs
+ */
+ public void logIngHeaders(HttpServletRequest request, BladeLogLevel level,
+ StringBuilder beforeReqLog, List<Object> beforeReqArgs) {
+ // 鎵撳嵃璇锋眰澶�
+ if (BladeLogLevel.HEADERS.lte(level)) {
+ Enumeration<String> headers = request.getHeaderNames();
+ while (headers.hasMoreElements()) {
+ String headerName = headers.nextElement();
+ String headerValue = request.getHeader(headerName);
+ beforeReqLog.append("===Headers=== {}: {}\n");
+ beforeReqArgs.add(headerName);
+ beforeReqArgs.add(headerValue);
+ }
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/config/BladeErrorMvcAutoConfiguration.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/config/BladeErrorMvcAutoConfiguration.java
new file mode 100644
index 0000000..4c09a3b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/config/BladeErrorMvcAutoConfiguration.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.config;
+
+
+import lombok.AllArgsConstructor;
+import org.springblade.core.log.error.BladeErrorAttributes;
+import org.springblade.core.log.error.BladeErrorController;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.autoconfigure.condition.SearchStrategy;
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
+import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
+import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
+import org.springframework.boot.web.servlet.error.ErrorAttributes;
+import org.springframework.boot.web.servlet.error.ErrorController;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.servlet.DispatcherServlet;
+
+import javax.servlet.Servlet;
+
+/**
+ * 缁熶竴寮傚父澶勭悊
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+@ConditionalOnWebApplication
+@AutoConfiguration(before = ErrorMvcAutoConfiguration.class)
+@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
+public class BladeErrorMvcAutoConfiguration {
+
+ private final ServerProperties serverProperties;
+
+ @Bean
+ @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
+ public DefaultErrorAttributes errorAttributes() {
+ return new BladeErrorAttributes();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
+ public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
+ return new BladeErrorController(errorAttributes, serverProperties.getError());
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/config/BladeLogToolAutoConfiguration.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/config/BladeLogToolAutoConfiguration.java
new file mode 100644
index 0000000..87b7b58
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/config/BladeLogToolAutoConfiguration.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+
+package org.springblade.core.log.config;
+
+import org.springblade.core.launch.props.BladeProperties;
+import org.springblade.core.launch.props.BladePropertySource;
+import org.springblade.core.launch.server.ServerInfo;
+import org.springblade.core.log.aspect.ApiLogAspect;
+import org.springblade.core.log.aspect.LogTraceAspect;
+import org.springblade.core.log.event.ApiLogListener;
+import org.springblade.core.log.event.ErrorLogListener;
+import org.springblade.core.log.event.UsualLogListener;
+import org.springblade.core.log.feign.ILogClient;
+import org.springblade.core.log.filter.LogTraceFilter;
+import org.springblade.core.log.logger.BladeLogger;
+import org.springblade.core.log.props.BladeRequestLogProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.Ordered;
+
+import javax.servlet.DispatcherType;
+
+/**
+ * 鏃ュ織宸ュ叿鑷姩閰嶇疆
+ *
+ * @author Chill
+ */
+@AutoConfiguration
+@ConditionalOnWebApplication
+@EnableConfigurationProperties(BladeRequestLogProperties.class)
+@BladePropertySource(value = "classpath:/blade-log.yml")
+public class BladeLogToolAutoConfiguration {
+
+ @Bean
+ public ApiLogAspect apiLogAspect() {
+ return new ApiLogAspect();
+ }
+
+ @Bean
+ public LogTraceAspect logTraceAspect() {
+ return new LogTraceAspect();
+ }
+
+ @Bean
+ public BladeLogger bladeLogger() {
+ return new BladeLogger();
+ }
+
+ @Bean
+ public FilterRegistrationBean<LogTraceFilter> logTraceFilterRegistration() {
+ FilterRegistrationBean<LogTraceFilter> registration = new FilterRegistrationBean<>();
+ registration.setDispatcherTypes(DispatcherType.REQUEST);
+ registration.setFilter(new LogTraceFilter());
+ registration.addUrlPatterns("/*");
+ registration.setName("LogTraceFilter");
+ registration.setOrder(Ordered.LOWEST_PRECEDENCE);
+ return registration;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(name = "apiLogListener")
+ public ApiLogListener apiLogListener(ILogClient logService, ServerInfo serverInfo, BladeProperties bladeProperties) {
+ return new ApiLogListener(logService, serverInfo, bladeProperties);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(name = "errorEventListener")
+ public ErrorLogListener errorEventListener(ILogClient logService, ServerInfo serverInfo, BladeProperties bladeProperties) {
+ return new ErrorLogListener(logService, serverInfo, bladeProperties);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(name = "usualEventListener")
+ public UsualLogListener usualEventListener(ILogClient logService, ServerInfo serverInfo, BladeProperties bladeProperties) {
+ return new UsualLogListener(logService, serverInfo, bladeProperties);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/constant/EventConstant.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/constant/EventConstant.java
new file mode 100644
index 0000000..f4669e5
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/constant/EventConstant.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.constant;
+
+/**
+ * 浜嬩欢甯搁噺
+ *
+ * @author Chill
+ */
+public interface EventConstant {
+
+ /**
+ * log
+ */
+ String EVENT_LOG = "log";
+ /**
+ * request
+ */
+ String EVENT_REQUEST = "request";
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/error/BladeErrorAttributes.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/error/BladeErrorAttributes.java
new file mode 100644
index 0000000..297ecfa
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/error/BladeErrorAttributes.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.error;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.log.publisher.ErrorLogPublisher;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.api.ResultCode;
+import org.springblade.core.tool.utils.BeanUtil;
+import org.springframework.boot.web.error.ErrorAttributeOptions;
+import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
+import org.springframework.lang.Nullable;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.WebRequest;
+
+import java.util.Map;
+
+/**
+ * 鍏ㄥ眬寮傚父澶勭悊
+ *
+ * @author Chill
+ */
+@Slf4j
+public class BladeErrorAttributes extends DefaultErrorAttributes {
+
+ @Override
+ public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
+ String requestUri = this.getAttr(webRequest, "javax.servlet.error.request_uri");
+ Integer status = this.getAttr(webRequest, "javax.servlet.error.status_code");
+ Throwable error = getError(webRequest);
+ R result;
+ if (error == null) {
+ log.error("URL:{} error status:{}", requestUri, status);
+ result = R.fail(ResultCode.FAILURE, "绯荤粺鏈煡寮傚父[HttpStatus]:" + status);
+ } else {
+ log.error(String.format("URL:%s error status:%d", requestUri, status), error);
+ result = R.fail(status, error.getMessage());
+ }
+ //鍙戦�佹湇鍔″紓甯镐簨浠�
+ ErrorLogPublisher.publishEvent(error, requestUri);
+ return BeanUtil.toMap(result);
+ }
+
+ @Nullable
+ private <T> T getAttr(WebRequest webRequest, String name) {
+ return (T) webRequest.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/error/BladeErrorController.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/error/BladeErrorController.java
new file mode 100644
index 0000000..864ad98
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/error/BladeErrorController.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.error;
+
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springframework.boot.autoconfigure.web.ErrorProperties;
+import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
+import org.springframework.boot.web.error.ErrorAttributeOptions;
+import org.springframework.boot.web.servlet.error.ErrorAttributes;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Map;
+
+/**
+ * 鏇存敼html璇锋眰寮傚父涓篴jax
+ *
+ * @author Chill
+ */
+public class BladeErrorController extends BasicErrorController {
+
+ public BladeErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
+ super(errorAttributes, errorProperties);
+ }
+
+ @Override
+ public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
+ boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
+ Map<String, Object> body = getErrorAttributes(request, (includeStackTrace) ? ErrorAttributeOptions.of(ErrorAttributeOptions.Include.STACK_TRACE) : ErrorAttributeOptions.defaults());
+ HttpStatus status = getStatus(request);
+ response.setStatus(status.value());
+ MappingJackson2JsonView view = new MappingJackson2JsonView();
+ view.setObjectMapper(JsonUtil.getInstance());
+ view.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ return new ModelAndView(view, body);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/error/BladeRestExceptionTranslator.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/error/BladeRestExceptionTranslator.java
new file mode 100644
index 0000000..f475928
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/error/BladeRestExceptionTranslator.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.error;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.log.props.BladeRequestLogProperties;
+import org.springblade.core.log.publisher.ErrorLogPublisher;
+import org.springblade.core.secure.exception.SecureException;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.api.ResultCode;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.UrlUtil;
+import org.springblade.core.tool.utils.WebUtil;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.servlet.DispatcherServlet;
+
+import javax.servlet.Servlet;
+
+/**
+ * 鏈煡寮傚父杞瘧鍜屽彂閫侊紝鏂逛究鐩戝惉锛屽鏈煡寮傚父缁熶竴澶勭悊銆侽rder 鎺掑簭浼樺厛绾т綆
+ *
+ * @author Chill
+ */
+@Slf4j
+@Order
+@RequiredArgsConstructor
+@AutoConfiguration
+@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
+@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
+@RestControllerAdvice
+public class BladeRestExceptionTranslator {
+
+ private final BladeRequestLogProperties properties;
+
+ @ExceptionHandler(ServiceException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public R handleError(ServiceException e) {
+ log.error("涓氬姟寮傚父", e);
+ return R.fail(e.getResultCode(), e.getMessage());
+ }
+
+ @ExceptionHandler(SecureException.class)
+ @ResponseStatus(HttpStatus.UNAUTHORIZED)
+ public R handleError(SecureException e) {
+ log.error("璁よ瘉寮傚父", e);
+ return R.fail(e.getResultCode(), e.getMessage());
+ }
+
+ @ExceptionHandler(Throwable.class)
+ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+ public R handleError(Throwable e) {
+ log.error("鏈嶅姟鍣ㄥ紓甯�", e);
+ if (properties.getErrorLog()) {
+ //鍙戦�佹湇鍔″紓甯镐簨浠�
+ ErrorLogPublisher.publishEvent(e, UrlUtil.getPath(WebUtil.getRequest().getRequestURI()));
+ }
+ return R.fail(ResultCode.INTERNAL_SERVER_ERROR, (Func.isEmpty(e.getMessage()) ? ResultCode.INTERNAL_SERVER_ERROR.getMessage() : e.getMessage()));
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/error/RestExceptionTranslator.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/error/RestExceptionTranslator.java
new file mode 100644
index 0000000..8dfede0
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/error/RestExceptionTranslator.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.error;
+
+import lombok.extern.slf4j.Slf4j;
+import org.hibernate.validator.internal.engine.path.PathImpl;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.api.ResultCode;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.validation.BindException;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.FieldError;
+import org.springframework.web.HttpMediaTypeNotAcceptableException;
+import org.springframework.web.HttpMediaTypeNotSupportedException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.MissingServletRequestParameterException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
+import org.springframework.web.servlet.DispatcherServlet;
+import org.springframework.web.servlet.NoHandlerFoundException;
+
+import javax.servlet.Servlet;
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import java.util.Set;
+
+/**
+ * 鍏ㄥ眬寮傚父澶勭悊锛屽鐞嗗彲棰勮鐨勫紓甯革紝Order 鎺掑簭浼樺厛绾ч珮
+ *
+ * @author Chill
+ */
+@Slf4j
+@Order(Ordered.HIGHEST_PRECEDENCE)
+@AutoConfiguration
+@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
+@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
+@RestControllerAdvice
+public class RestExceptionTranslator {
+
+ @ExceptionHandler(MissingServletRequestParameterException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public R handleError(MissingServletRequestParameterException e) {
+ log.warn("缂哄皯璇锋眰鍙傛暟", e.getMessage());
+ String message = String.format("缂哄皯蹇呰鐨勮姹傚弬鏁�: %s", e.getParameterName());
+ return R.fail(ResultCode.PARAM_MISS, message);
+ }
+
+ @ExceptionHandler(MethodArgumentTypeMismatchException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public R handleError(MethodArgumentTypeMismatchException e) {
+ log.warn("璇锋眰鍙傛暟鏍煎紡閿欒", e.getMessage());
+ String message = String.format("璇锋眰鍙傛暟鏍煎紡閿欒: %s", e.getName());
+ return R.fail(ResultCode.PARAM_TYPE_ERROR, message);
+ }
+
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public R handleError(MethodArgumentNotValidException e) {
+ log.warn("鍙傛暟楠岃瘉澶辫触", e.getMessage());
+ return handleError(e.getBindingResult());
+ }
+
+ @ExceptionHandler(BindException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public R handleError(BindException e) {
+ log.warn("鍙傛暟缁戝畾澶辫触", e.getMessage());
+ return handleError(e.getBindingResult());
+ }
+
+ private R handleError(BindingResult result) {
+ FieldError error = result.getFieldError();
+ String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
+ return R.fail(ResultCode.PARAM_BIND_ERROR, message);
+ }
+
+ @ExceptionHandler(ConstraintViolationException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public R handleError(ConstraintViolationException e) {
+ log.warn("鍙傛暟楠岃瘉澶辫触", e.getMessage());
+ Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
+ ConstraintViolation<?> violation = violations.iterator().next();
+ String path = ((PathImpl) violation.getPropertyPath()).getLeafNode().getName();
+ String message = String.format("%s:%s", path, violation.getMessage());
+ return R.fail(ResultCode.PARAM_VALID_ERROR, message);
+ }
+
+ @ExceptionHandler(NoHandlerFoundException.class)
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ public R handleError(NoHandlerFoundException e) {
+ log.error("404娌℃壘鍒拌姹�:{}", e.getMessage());
+ return R.fail(ResultCode.NOT_FOUND, e.getMessage());
+ }
+
+ @ExceptionHandler(HttpMessageNotReadableException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public R handleError(HttpMessageNotReadableException e) {
+ log.error("娑堟伅涓嶈兘璇诲彇:{}", e.getMessage());
+ return R.fail(ResultCode.MSG_NOT_READABLE, e.getMessage());
+ }
+
+ @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+ @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
+ public R handleError(HttpRequestMethodNotSupportedException e) {
+ log.error("涓嶆敮鎸佸綋鍓嶈姹傛柟娉�:{}", e.getMessage());
+ return R.fail(ResultCode.METHOD_NOT_SUPPORTED, e.getMessage());
+ }
+
+ @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
+ @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
+ public R handleError(HttpMediaTypeNotSupportedException e) {
+ log.error("涓嶆敮鎸佸綋鍓嶅獟浣撶被鍨�:{}", e.getMessage());
+ return R.fail(ResultCode.MEDIA_TYPE_NOT_SUPPORTED, e.getMessage());
+ }
+
+ @ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
+ @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
+ public R handleError(HttpMediaTypeNotAcceptableException e) {
+ String message = e.getMessage() + " " + StringUtil.join(e.getSupportedMediaTypes());
+ log.error("涓嶆帴鍙楃殑濯掍綋绫诲瀷:{}", message);
+ return R.fail(ResultCode.MEDIA_TYPE_NOT_SUPPORTED, message);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/ApiLogEvent.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/ApiLogEvent.java
new file mode 100644
index 0000000..6a796a0
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/ApiLogEvent.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+
+package org.springblade.core.log.event;
+
+import org.springframework.context.ApplicationEvent;
+
+import java.util.Map;
+
+/**
+ * 绯荤粺鏃ュ織浜嬩欢
+ *
+ * @author Chill
+ */
+public class ApiLogEvent extends ApplicationEvent {
+
+ public ApiLogEvent(Map<String, Object> source) {
+ super(source);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/ApiLogListener.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/ApiLogListener.java
new file mode 100644
index 0000000..9811d2a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/ApiLogListener.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+
+package org.springblade.core.log.event;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.launch.props.BladeProperties;
+import org.springblade.core.launch.server.ServerInfo;
+import org.springblade.core.log.constant.EventConstant;
+import org.springblade.core.log.feign.ILogClient;
+import org.springblade.core.log.model.LogApi;
+import org.springblade.core.log.utils.LogAbstractUtil;
+import org.springframework.context.event.EventListener;
+import org.springframework.core.annotation.Order;
+import org.springframework.scheduling.annotation.Async;
+
+import java.util.Map;
+
+
+/**
+ * 寮傛鐩戝惉鏃ュ織浜嬩欢
+ *
+ * @author Chill
+ */
+@Slf4j
+@AllArgsConstructor
+public class ApiLogListener {
+
+ private final ILogClient logService;
+ private final ServerInfo serverInfo;
+ private final BladeProperties bladeProperties;
+
+
+ @Async
+ @Order
+ @EventListener(ApiLogEvent.class)
+ public void saveApiLog(ApiLogEvent event) {
+ Map<String, Object> source = (Map<String, Object>) event.getSource();
+ LogApi logApi = (LogApi) source.get(EventConstant.EVENT_LOG);
+ LogAbstractUtil.addOtherInfoToLog(logApi, bladeProperties, serverInfo);
+ logService.saveApiLog(logApi);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/ErrorLogEvent.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/ErrorLogEvent.java
new file mode 100644
index 0000000..953a354
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/ErrorLogEvent.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.event;
+
+
+import org.springframework.context.ApplicationEvent;
+
+import java.util.Map;
+
+/**
+ * 閿欒鏃ュ織浜嬩欢
+ *
+ * @author Chill
+ */
+public class ErrorLogEvent extends ApplicationEvent {
+
+ public ErrorLogEvent(Map<String, Object> source) {
+ super(source);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/ErrorLogListener.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/ErrorLogListener.java
new file mode 100644
index 0000000..1eecc54
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/ErrorLogListener.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.event;
+
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.launch.props.BladeProperties;
+import org.springblade.core.launch.server.ServerInfo;
+import org.springblade.core.log.constant.EventConstant;
+import org.springblade.core.log.feign.ILogClient;
+import org.springblade.core.log.model.LogError;
+import org.springblade.core.log.utils.LogAbstractUtil;
+import org.springframework.context.event.EventListener;
+import org.springframework.core.annotation.Order;
+import org.springframework.scheduling.annotation.Async;
+
+import java.util.Map;
+
+/**
+ * 寮傛鐩戝惉閿欒鏃ュ織浜嬩欢
+ *
+ * @author Chill
+ */
+@Slf4j
+@AllArgsConstructor
+public class ErrorLogListener {
+
+ private final ILogClient logService;
+ private final ServerInfo serverInfo;
+ private final BladeProperties bladeProperties;
+
+ @Async
+ @Order
+ @EventListener(ErrorLogEvent.class)
+ public void saveErrorLog(ErrorLogEvent event) {
+ Map<String, Object> source = (Map<String, Object>) event.getSource();
+ LogError logError = (LogError) source.get(EventConstant.EVENT_LOG);
+ LogAbstractUtil.addOtherInfoToLog(logError, bladeProperties, serverInfo);
+ logService.saveErrorLog(logError);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/UsualLogEvent.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/UsualLogEvent.java
new file mode 100644
index 0000000..cb556c8
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/UsualLogEvent.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+
+package org.springblade.core.log.event;
+
+import org.springframework.context.ApplicationEvent;
+
+import java.util.Map;
+
+/**
+ * 绯荤粺鏃ュ織浜嬩欢
+ *
+ * @author Chill
+ */
+public class UsualLogEvent extends ApplicationEvent {
+
+ public UsualLogEvent(Map<String, Object> source) {
+ super(source);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/UsualLogListener.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/UsualLogListener.java
new file mode 100644
index 0000000..cfe555b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/event/UsualLogListener.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.event;
+
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.launch.props.BladeProperties;
+import org.springblade.core.launch.server.ServerInfo;
+import org.springblade.core.log.constant.EventConstant;
+import org.springblade.core.log.feign.ILogClient;
+import org.springblade.core.log.model.LogUsual;
+import org.springblade.core.log.utils.LogAbstractUtil;
+import org.springframework.context.event.EventListener;
+import org.springframework.core.annotation.Order;
+import org.springframework.scheduling.annotation.Async;
+
+import java.util.Map;
+
+/**
+ * 寮傛鐩戝惉鏃ュ織浜嬩欢
+ *
+ * @author Chill
+ */
+@Slf4j
+@AllArgsConstructor
+public class UsualLogListener {
+
+ private final ILogClient logService;
+ private final ServerInfo serverInfo;
+ private final BladeProperties bladeProperties;
+
+ @Async
+ @Order
+ @EventListener(UsualLogEvent.class)
+ public void saveUsualLog(UsualLogEvent event) {
+ Map<String, Object> source = (Map<String, Object>) event.getSource();
+ LogUsual logUsual = (LogUsual) source.get(EventConstant.EVENT_LOG);
+ LogAbstractUtil.addOtherInfoToLog(logUsual, bladeProperties, serverInfo);
+ logService.saveUsualLog(logUsual);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/exception/ServiceException.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/exception/ServiceException.java
new file mode 100644
index 0000000..c712e5d
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/exception/ServiceException.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.exception;
+
+import lombok.Getter;
+import org.springblade.core.tool.api.IResultCode;
+import org.springblade.core.tool.api.ResultCode;
+
+
+/**
+ * 涓氬姟寮傚父
+ *
+ * @author Chill
+ */
+public class ServiceException extends RuntimeException {
+ private static final long serialVersionUID = 2359767895161832954L;
+
+ @Getter
+ private final IResultCode resultCode;
+
+ public ServiceException(String message) {
+ super(message);
+ this.resultCode = ResultCode.FAILURE;
+ }
+
+ public ServiceException(IResultCode resultCode) {
+ super(resultCode.getMessage());
+ this.resultCode = resultCode;
+ }
+
+ public ServiceException(IResultCode resultCode, Throwable cause) {
+ super(cause);
+ this.resultCode = resultCode;
+ }
+
+ /**
+ * 鎻愰珮鎬ц兘
+ *
+ * @return Throwable
+ */
+ @Override
+ public Throwable fillInStackTrace() {
+ return this;
+ }
+
+ public Throwable doFillInStackTrace() {
+ return super.fillInStackTrace();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/feign/ILogClient.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/feign/ILogClient.java
new file mode 100644
index 0000000..a6408b0
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/feign/ILogClient.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.feign;
+
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.log.model.LogApi;
+import org.springblade.core.log.model.LogUsual;
+import org.springblade.core.log.model.LogError;
+import org.springblade.core.tool.api.R;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+/**
+ * Feign鎺ュ彛绫�
+ *
+ * @author Chill
+ */
+@FeignClient(
+ value = AppConstant.APPLICATION_LOG_NAME,
+ fallback = LogClientFallback.class
+)
+public interface ILogClient {
+
+ String API_PREFIX = "/log";
+
+ /**
+ * 淇濆瓨閿欒鏃ュ織
+ *
+ * @param log
+ * @return
+ */
+ @PostMapping(API_PREFIX + "/saveUsualLog")
+ R<Boolean> saveUsualLog(@RequestBody LogUsual log);
+
+ /**
+ * 淇濆瓨鎿嶄綔鏃ュ織
+ *
+ * @param log
+ * @return
+ */
+ @PostMapping(API_PREFIX + "/saveApiLog")
+ R<Boolean> saveApiLog(@RequestBody LogApi log);
+
+ /**
+ * 淇濆瓨閿欒鏃ュ織
+ *
+ * @param log
+ * @return
+ */
+ @PostMapping(API_PREFIX + "/saveErrorLog")
+ R<Boolean> saveErrorLog(@RequestBody LogError log);
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/feign/LogClientFallback.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/feign/LogClientFallback.java
new file mode 100644
index 0000000..2e16a7f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/feign/LogClientFallback.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.feign;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.log.model.LogApi;
+import org.springblade.core.log.model.LogError;
+import org.springblade.core.log.model.LogUsual;
+import org.springblade.core.tool.api.R;
+import org.springframework.stereotype.Component;
+
+/**
+ * 鏃ュ織fallback
+ *
+ * @author jiang
+ */
+@Slf4j
+@Component
+public class LogClientFallback implements ILogClient {
+
+ @Override
+ public R<Boolean> saveUsualLog(LogUsual log) {
+ return R.fail("usual log send fail");
+ }
+
+ @Override
+ public R<Boolean> saveApiLog(LogApi log) {
+ return R.fail("api log send fail");
+ }
+
+ @Override
+ public R<Boolean> saveErrorLog(LogError log) {
+ return R.fail("error log send fail");
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/filter/LogTraceFilter.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/filter/LogTraceFilter.java
new file mode 100644
index 0000000..b834c0b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/filter/LogTraceFilter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.filter;
+
+import org.springblade.core.log.utils.LogTraceUtil;
+
+import javax.servlet.*;
+import java.io.IOException;
+
+/**
+ * 鏃ュ織杩借釜杩囨护鍣�
+ *
+ * @author Chill
+ */
+public class LogTraceFilter implements Filter {
+
+ @Override
+ public void init(FilterConfig filterConfig) {
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ boolean flag = LogTraceUtil.insert();
+ try {
+ chain.doFilter(request, response);
+ } finally {
+ if (flag) {
+ LogTraceUtil.remove();
+ }
+ }
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/launch/LogLauncherServiceImpl.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/launch/LogLauncherServiceImpl.java
new file mode 100644
index 0000000..1e0e20f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/launch/LogLauncherServiceImpl.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.launch;
+
+import org.springblade.core.auto.service.AutoService;
+import org.springblade.core.launch.service.LauncherService;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.core.Ordered;
+
+import java.util.Properties;
+
+/**
+ * 鏃ュ織鍚姩閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@AutoService(LauncherService.class)
+public class LogLauncherServiceImpl implements LauncherService {
+ @Override
+ public void launcher(SpringApplicationBuilder builder, String appName, String profile, boolean isLocalDev) {
+ Properties props = System.getProperties();
+ props.setProperty("logging.config", "classpath:log/logback-" + profile + ".xml");
+ }
+
+ @Override
+ public int getOrder() {
+ return Ordered.HIGHEST_PRECEDENCE;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/listener/LoggerStartupListener.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/listener/LoggerStartupListener.java
new file mode 100644
index 0000000..5ff642e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/listener/LoggerStartupListener.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.listener;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.LoggerContextListener;
+import ch.qos.logback.core.Context;
+import ch.qos.logback.core.spi.ContextAwareBase;
+import ch.qos.logback.core.spi.LifeCycle;
+import org.springblade.core.log.utils.ElkPropsUtil;
+import org.springblade.core.tool.utils.StringUtil;
+
+/**
+ * logback鐩戝惉绫�
+ *
+ * @author Chill
+ */
+public class LoggerStartupListener extends ContextAwareBase implements LoggerContextListener, LifeCycle {
+
+ @Override
+ public void start() {
+ Context context = getContext();
+ context.putProperty("ELK_MODE", "FALSE");
+ context.putProperty("STDOUT_APPENDER", "STDOUT");
+ context.putProperty("INFO_APPENDER", "INFO");
+ context.putProperty("ERROR_APPENDER", "ERROR");
+ context.putProperty("DESTINATION", "127.0.0.1:9000");
+ String destination = ElkPropsUtil.getDestination();
+ if (StringUtil.isNotBlank(destination)) {
+ context.putProperty("ELK_MODE", "TRUE");
+ context.putProperty("STDOUT_APPENDER", "STDOUT_LOGSTASH");
+ context.putProperty("INFO_APPENDER", "INFO_LOGSTASH");
+ context.putProperty("ERROR_APPENDER", "ERROR_LOGSTASH");
+ context.putProperty("DESTINATION", destination);
+ }
+ }
+
+ @Override
+ public void stop() {
+
+ }
+
+ @Override
+ public boolean isStarted() {
+ return false;
+ }
+
+ @Override
+ public boolean isResetResistant() {
+ return false;
+ }
+
+ @Override
+ public void onStart(LoggerContext context) {
+
+ }
+
+ @Override
+ public void onReset(LoggerContext context) {
+
+ }
+
+ @Override
+ public void onStop(LoggerContext context) {
+
+ }
+
+ @Override
+ public void onLevelChange(Logger logger, Level level) {
+
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/logger/BladeLogger.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/logger/BladeLogger.java
new file mode 100644
index 0000000..78f192e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/logger/BladeLogger.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.logger;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.log.publisher.UsualLogPublisher;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Value;
+
+/**
+ * 鏃ュ織宸ュ叿绫�
+ *
+ * @author Chill
+ */
+@Slf4j
+public class BladeLogger implements InitializingBean {
+
+ @Value("${spring.application.name}")
+ private String serviceId;
+
+ public void info(String id, String data) {
+ UsualLogPublisher.publishEvent("info", id, data);
+ }
+
+ public void debug(String id, String data) {
+ UsualLogPublisher.publishEvent("debug", id, data);
+ }
+
+ public void warn(String id, String data) {
+ UsualLogPublisher.publishEvent("warn", id, data);
+ }
+
+ public void error(String id, String data) {
+ UsualLogPublisher.publishEvent("error", id, data);
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ log.info(serviceId + ": BladeLogger init success!");
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/model/LogAbstract.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/model/LogAbstract.java
new file mode 100644
index 0000000..83ae2f9
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/model/LogAbstract.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.model;
+
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import lombok.Data;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * logApi銆乴ogError銆乴ogUsual鐖剁被
+ *
+ * @author Chill
+ */
+@Data
+public class LogAbstract implements Serializable {
+
+ protected static final long serialVersionUID = 1L;
+
+ /**
+ * 涓婚敭id
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ protected Long id;
+ /**
+ * 绉熸埛ID
+ */
+ private String tenantId;
+ /**
+ * 鏈嶅姟ID
+ */
+ protected String serviceId;
+ /**
+ * 鏈嶅姟鍣� ip
+ */
+ protected String serverIp;
+ /**
+ * 鏈嶅姟鍣ㄥ悕
+ */
+ protected String serverHost;
+ /**
+ * 鐜
+ */
+ protected String env;
+ /**
+ * 鎿嶄綔IP鍦板潃
+ */
+ protected String remoteIp;
+ /**
+ * 鐢ㄦ埛浠g悊
+ */
+ protected String userAgent;
+ /**
+ * 璇锋眰URI
+ */
+ protected String requestUri;
+ /**
+ * 鎿嶄綔鏂瑰紡
+ */
+ protected String method;
+ /**
+ * 鏂规硶绫�
+ */
+ protected String methodClass;
+ /**
+ * 鏂规硶鍚�
+ */
+ protected String methodName;
+ /**
+ * 鎿嶄綔鎻愪氦鐨勬暟鎹�
+ */
+ protected String params;
+ /**
+ * 鍒涘缓浜�
+ */
+ protected String createBy;
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
+ protected Date createTime;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/model/LogApi.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/model/LogApi.java
new file mode 100644
index 0000000..255eeb0
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/model/LogApi.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.model;
+
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 瀹炰綋绫�
+ *
+ * @author Chill
+ */
+@Data
+@TableName("blade_log_api")
+@EqualsAndHashCode(callSuper = true)
+public class LogApi extends LogAbstract {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏃ュ織绫诲瀷
+ */
+ private String type;
+ /**
+ * 鏃ュ織鏍囬
+ */
+ private String title;
+ /**
+ * 鎵ц鏃堕棿
+ */
+ private String time;
+
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/model/LogError.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/model/LogError.java
new file mode 100644
index 0000000..96dc19c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/model/LogError.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.model;
+
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 鏈嶅姟 寮傚父
+ *
+ * @author Chill
+ */
+@Data
+@TableName("blade_log_error")
+@EqualsAndHashCode(callSuper = true)
+public class LogError extends LogAbstract {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鍫嗘爤淇℃伅
+ */
+ private String stackTrace;
+ /**
+ * 寮傚父鍚�
+ */
+ private String exceptionName;
+ /**
+ * 寮傚父娑堟伅
+ */
+ private String message;
+
+ /**
+ * 鏂囦欢鍚�
+ */
+ private String fileName;
+
+ /**
+ * 浠g爜琛屾暟
+ */
+ private Integer lineNumber;
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/model/LogUsual.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/model/LogUsual.java
new file mode 100644
index 0000000..bd0ffff
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/model/LogUsual.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.model;
+
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 瀹炰綋绫�
+ *
+ * @author Chill
+ */
+@Data
+@TableName("blade_log_usual")
+@EqualsAndHashCode(callSuper = true)
+public class LogUsual extends LogAbstract {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏃ュ織绾у埆
+ */
+ private String logLevel;
+ /**
+ * 鏃ュ織涓氬姟id
+ */
+ private String logId;
+ /**
+ * 鏃ュ織鏁版嵁
+ */
+ private String logData;
+
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/props/BladeRequestLogProperties.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/props/BladeRequestLogProperties.java
new file mode 100644
index 0000000..c66db6a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/props/BladeRequestLogProperties.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.log.props;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springblade.core.launch.log.BladeLogLevel;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+
+/**
+ * 鏃ュ織閰嶇疆
+ *
+ * @author L.cm
+ */
+@Getter
+@Setter
+@RefreshScope
+@ConfigurationProperties(BladeLogLevel.REQ_LOG_PROPS_PREFIX)
+public class BladeRequestLogProperties {
+
+ /**
+ * 鏄惁寮�鍚姹傛棩蹇�
+ */
+ private Boolean enabled = true;
+
+ /**
+ * 鏄惁寮�鍚紓甯告棩蹇楁帹閫�
+ */
+ private Boolean errorLog = true;
+
+ /**
+ * 鏃ュ織绾у埆閰嶇疆锛岄粯璁わ細BODY
+ */
+ private BladeLogLevel level = BladeLogLevel.BODY;
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/publisher/ApiLogPublisher.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/publisher/ApiLogPublisher.java
new file mode 100644
index 0000000..86be608
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/publisher/ApiLogPublisher.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+
+package org.springblade.core.log.publisher;
+
+import org.springblade.core.log.annotation.ApiLog;
+import org.springblade.core.log.constant.EventConstant;
+import org.springblade.core.log.event.ApiLogEvent;
+import org.springblade.core.log.model.LogApi;
+import org.springblade.core.log.utils.LogAbstractUtil;
+import org.springblade.core.tool.constant.BladeConstant;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.core.tool.utils.WebUtil;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * API鏃ュ織淇℃伅浜嬩欢鍙戦��
+ *
+ * @author Chill
+ */
+public class ApiLogPublisher {
+
+ public static void publishEvent(String methodName, String methodClass, ApiLog apiLog, long time) {
+ HttpServletRequest request = WebUtil.getRequest();
+ LogApi logApi = new LogApi();
+ logApi.setType(BladeConstant.LOG_NORMAL_TYPE);
+ logApi.setTitle(apiLog.value());
+ logApi.setTime(String.valueOf(time));
+ logApi.setMethodClass(methodClass);
+ logApi.setMethodName(methodName);
+ LogAbstractUtil.addRequestInfoToLog(request, logApi);
+ Map<String, Object> event = new HashMap<>(16);
+ event.put(EventConstant.EVENT_LOG, logApi);
+ SpringUtil.publishEvent(new ApiLogEvent(event));
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/publisher/ErrorLogPublisher.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/publisher/ErrorLogPublisher.java
new file mode 100644
index 0000000..b8c8f44
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/publisher/ErrorLogPublisher.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+
+package org.springblade.core.log.publisher;
+
+import org.springblade.core.log.constant.EventConstant;
+import org.springblade.core.log.event.ErrorLogEvent;
+import org.springblade.core.log.model.LogError;
+import org.springblade.core.log.utils.LogAbstractUtil;
+import org.springblade.core.tool.utils.Exceptions;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.core.tool.utils.WebUtil;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 寮傚父淇℃伅浜嬩欢鍙戦��
+ *
+ * @author Chill
+ */
+public class ErrorLogPublisher {
+
+ public static void publishEvent(Throwable error, String requestUri) {
+ HttpServletRequest request = WebUtil.getRequest();
+ LogError logError = new LogError();
+ logError.setRequestUri(requestUri);
+ if (Func.isNotEmpty(error)) {
+ logError.setStackTrace(Exceptions.getStackTraceAsString(error));
+ logError.setExceptionName(error.getClass().getName());
+ logError.setMessage(error.getMessage());
+ StackTraceElement[] elements = error.getStackTrace();
+ if (Func.isNotEmpty(elements)) {
+ StackTraceElement element = elements[0];
+ logError.setMethodName(element.getMethodName());
+ logError.setMethodClass(element.getClassName());
+ logError.setFileName(element.getFileName());
+ logError.setLineNumber(element.getLineNumber());
+ }
+ }
+ LogAbstractUtil.addRequestInfoToLog(request, logError);
+ Map<String, Object> event = new HashMap<>(16);
+ event.put(EventConstant.EVENT_LOG, logError);
+ SpringUtil.publishEvent(new ErrorLogEvent(event));
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/publisher/UsualLogPublisher.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/publisher/UsualLogPublisher.java
new file mode 100644
index 0000000..b934207
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/publisher/UsualLogPublisher.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+
+package org.springblade.core.log.publisher;
+
+import org.springblade.core.log.constant.EventConstant;
+import org.springblade.core.log.event.UsualLogEvent;
+import org.springblade.core.log.model.LogUsual;
+import org.springblade.core.log.utils.LogAbstractUtil;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.core.tool.utils.WebUtil;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * BLADE鏃ュ織淇℃伅浜嬩欢鍙戦��
+ *
+ * @author Chill
+ */
+public class UsualLogPublisher {
+
+ public static void publishEvent(String level, String id, String data) {
+ HttpServletRequest request = WebUtil.getRequest();
+ LogUsual logUsual = new LogUsual();
+ logUsual.setLogLevel(level);
+ logUsual.setLogId(id);
+ logUsual.setLogData(data);
+ Thread thread = Thread.currentThread();
+ StackTraceElement[] trace = thread.getStackTrace();
+ if (trace.length > 3) {
+ logUsual.setMethodClass(trace[3].getClassName());
+ logUsual.setMethodName(trace[3].getMethodName());
+ }
+ LogAbstractUtil.addRequestInfoToLog(request, logUsual);
+ Map<String, Object> event = new HashMap<>(16);
+ event.put(EventConstant.EVENT_LOG, logUsual);
+ SpringUtil.publishEvent(new UsualLogEvent(event));
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/utils/ElkPropsUtil.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/utils/ElkPropsUtil.java
new file mode 100644
index 0000000..3431e1c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/utils/ElkPropsUtil.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.utils;
+
+import org.springblade.core.tool.utils.StringPool;
+
+import java.util.Properties;
+
+/**
+ * Elk閰嶇疆宸ュ叿
+ *
+ * @author Chill
+ */
+public class ElkPropsUtil {
+
+ /**
+ * 鑾峰彇elk鏈嶅姟鍦板潃
+ *
+ * @return 鏈嶅姟鍦板潃
+ */
+ public static String getDestination() {
+ Properties props = System.getProperties();
+ return props.getProperty("blade.log.elk.destination", StringPool.EMPTY);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/utils/LogAbstractUtil.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/utils/LogAbstractUtil.java
new file mode 100644
index 0000000..9e8b02e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/utils/LogAbstractUtil.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+
+package org.springblade.core.log.utils;
+
+import org.springblade.core.launch.props.BladeProperties;
+import org.springblade.core.launch.server.ServerInfo;
+import org.springblade.core.log.model.LogAbstract;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.constant.BladeConstant;
+import org.springblade.core.tool.utils.*;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Log 鐩稿叧宸ュ叿
+ *
+ * @author Chill
+ */
+public class LogAbstractUtil {
+
+ /**
+ * 鍚憀og涓坊鍔犺ˉ榻恟equest鐨勪俊鎭�
+ *
+ * @param request 璇锋眰
+ * @param logAbstract 鏃ュ織鍩虹绫�
+ */
+ public static void addRequestInfoToLog(HttpServletRequest request, LogAbstract logAbstract) {
+ if (ObjectUtil.isNotEmpty(request)) {
+ logAbstract.setTenantId(Func.toStrWithEmpty(AuthUtil.getTenantId(), BladeConstant.ADMIN_TENANT_ID));
+ logAbstract.setRemoteIp(WebUtil.getIP(request));
+ logAbstract.setUserAgent(request.getHeader(WebUtil.USER_AGENT_HEADER));
+ logAbstract.setRequestUri(UrlUtil.getPath(request.getRequestURI()));
+ logAbstract.setMethod(request.getMethod());
+ logAbstract.setParams(WebUtil.getRequestContent(request));
+ logAbstract.setCreateBy(AuthUtil.getUserAccount(request));
+ }
+ }
+
+ /**
+ * 鍚憀og涓坊鍔犺ˉ榻愬叾浠栫殑淇℃伅锛坋g锛歜lade銆乻erver绛夛級
+ *
+ * @param logAbstract 鏃ュ織鍩虹绫�
+ * @param bladeProperties 閰嶇疆淇℃伅
+ * @param serverInfo 鏈嶅姟淇℃伅
+ */
+ public static void addOtherInfoToLog(LogAbstract logAbstract, BladeProperties bladeProperties, ServerInfo serverInfo) {
+ logAbstract.setServiceId(bladeProperties.getName());
+ logAbstract.setServerHost(serverInfo.getHostName());
+ logAbstract.setServerIp(serverInfo.getIpWithPort());
+ logAbstract.setEnv(bladeProperties.getEnv());
+ logAbstract.setCreateTime(DateUtil.now());
+ if (logAbstract.getParams() == null) {
+ logAbstract.setParams(StringPool.EMPTY);
+ }
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/utils/LogTraceUtil.java b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/utils/LogTraceUtil.java
new file mode 100644
index 0000000..385fdf7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/utils/LogTraceUtil.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.log.utils;
+
+import org.slf4j.MDC;
+import org.springblade.core.tool.utils.StringUtil;
+
+/**
+ * 鏃ュ織杩借釜宸ュ叿绫�
+ *
+ * @author Chill
+ */
+public class LogTraceUtil {
+ private static final String UNIQUE_ID = "traceId";
+
+ /**
+ * 鑾峰彇鏃ュ織杩借釜id鏍煎紡
+ */
+ public static String getTraceId() {
+ return StringUtil.randomUUID();
+ }
+
+ /**
+ * 鎻掑叆traceId
+ */
+ public static boolean insert() {
+ MDC.put(UNIQUE_ID, getTraceId());
+ return true;
+ }
+
+ /**
+ * 绉婚櫎traceId
+ */
+ public static boolean remove() {
+ MDC.remove(UNIQUE_ID);
+ return true;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/resources/blade-log.yml b/Source/BladeX-Tool/blade-starter-log/src/main/resources/blade-log.yml
new file mode 100644
index 0000000..0ae5269
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/resources/blade-log.yml
@@ -0,0 +1,3 @@
+#閰嶇疆鏃ュ織鍦板潃
+logging:
+ config: classpath:log/logback-${blade.env}.xml
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/resources/log/logback-dev.xml b/Source/BladeX-Tool/blade-starter-log/src/main/resources/log/logback-dev.xml
new file mode 100644
index 0000000..9172296
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/resources/log/logback-dev.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="false" debug="false">
+ <!-- 鑷畾涔夊弬鏁扮洃鍚� -->
+ <contextListener class="org.springblade.core.log.listener.LoggerStartupListener"/>
+ <springProperty scope="context" name="springAppName" source="spring.application.name"/>
+
+ <!-- 褰╄壊鏃ュ織渚濊禆鐨勬覆鏌撶被 -->
+ <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
+ <conversionRule conversionWord="wex"
+ converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
+ <conversionRule conversionWord="wEx"
+ converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
+ <!-- 褰╄壊鏃ュ織鏍煎紡 -->
+ <property name="CONSOLE_LOG_PATTERN"
+ value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
+ <!-- 鎺у埗鍙拌緭鍑� -->
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+ <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+ <charset>utf8</charset>
+ </encoder>
+ </appender>
+
+ <if condition='property("ELK_MODE").toUpperCase().contains("TRUE")'>
+ <then>
+ <!-- 鎺ㄩ�佹棩蹇楄嚦elk -->
+ <appender name="STDOUT_LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
+ <destination>${DESTINATION}</destination>
+ <!-- 鏃ュ織杈撳嚭缂栫爜 -->
+ <encoder charset="UTF-8" class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
+ <providers>
+ <timestamp>
+ <timeZone>UTC</timeZone>
+ </timestamp>
+ <pattern>
+ <pattern>
+ {
+ "traceId": "%X{traceId}",
+ "requestId": "%X{requestId}",
+ "accountId": "%X{accountId}",
+ "tenantId": "%X{tenantId}",
+ "logLevel": "%level",
+ "serviceName": "${springAppName:-SpringApp}",
+ "pid": "${PID:-}",
+ "thread": "%thread",
+ "class": "%logger{40}",
+ "line":"%L",
+ "message": "%message"
+ }
+ </pattern>
+ </pattern>
+ <mdc/>
+ <stackTrace/>
+ </providers>
+ </encoder>
+ </appender>
+ </then>
+ </if>
+
+ <!-- 鏃ュ織杈撳嚭绾у埆 -->
+ <root level="INFO">
+ <appender-ref ref="STDOUT"/>
+ <appender-ref ref="${STDOUT_APPENDER}"/>
+ </root>
+
+ <logger name="net.sf.ehcache" level="INFO"/>
+ <logger name="druid.sql" level="INFO"/>
+
+
+ <!-- MyBatis log configure -->
+ <logger name="com.apache.ibatis" level="INFO"/>
+ <logger name="org.mybatis.spring" level="INFO"/>
+ <logger name="java.sql.Connection" level="INFO"/>
+ <logger name="java.sql.Statement" level="INFO"/>
+ <logger name="java.sql.PreparedStatement" level="INFO"/>
+
+ <!-- 鍑忓皯閮ㄥ垎debug鏃ュ織 -->
+ <logger name="druid.sql" level="INFO"/>
+ <logger name="org.apache.shiro" level="INFO"/>
+ <logger name="org.mybatis.spring" level="INFO"/>
+ <logger name="org.springframework" level="INFO"/>
+ <logger name="org.springframework.context" level="WARN"/>
+ <logger name="org.springframework.beans" level="WARN"/>
+ <logger name="com.baomidou.mybatisplus" level="INFO"/>
+ <logger name="org.apache.ibatis.io" level="INFO"/>
+ <logger name="org.apache.velocity" level="INFO"/>
+ <logger name="org.eclipse.jetty" level="INFO"/>
+ <logger name="io.undertow" level="INFO"/>
+ <logger name="org.xnio.nio" level="INFO"/>
+ <logger name="org.thymeleaf" level="INFO"/>
+ <logger name="springfox.documentation" level="INFO"/>
+ <logger name="org.hibernate.validator" level="INFO"/>
+ <logger name="com.netflix.loadbalancer" level="INFO"/>
+ <logger name="com.netflix.hystrix" level="INFO"/>
+ <logger name="com.netflix.zuul" level="INFO"/>
+ <logger name="de.codecentric" level="INFO"/>
+ <!-- cache INFO -->
+ <logger name="net.sf.ehcache" level="INFO"/>
+ <logger name="org.springframework.cache" level="INFO"/>
+ <!-- cloud -->
+ <logger name="org.apache.http" level="INFO"/>
+ <logger name="com.netflix.discovery" level="INFO"/>
+ <logger name="com.netflix.eureka" level="INFO"/>
+ <!-- 涓氬姟鏃ュ織 -->
+ <Logger name="org.springblade" level="INFO"/>
+ <Logger name="org.springblade.core.tenant" level="INFO"/>
+ <Logger name="org.springblade.core.version" level="INFO"/>
+
+ <!-- 鍑忓皯nacos鏃ュ織 -->
+ <logger name="com.alibaba.nacos" level="ERROR"/>
+
+
+</configuration>
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/resources/log/logback-prod.xml b/Source/BladeX-Tool/blade-starter-log/src/main/resources/log/logback-prod.xml
new file mode 100644
index 0000000..a8f42de
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/resources/log/logback-prod.xml
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="false" debug="false">
+ <!-- 鑷畾涔夊弬鏁扮洃鍚� -->
+ <contextListener class="org.springblade.core.log.listener.LoggerStartupListener"/>
+ <springProperty scope="context" name="springAppName" source="spring.application.name"/>
+
+ <!-- 褰╄壊鏃ュ織渚濊禆鐨勬覆鏌撶被 -->
+ <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
+ <conversionRule conversionWord="wex"
+ converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
+ <conversionRule conversionWord="wEx"
+ converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
+ <!-- 褰╄壊鏃ュ織鏍煎紡 -->
+ <property name="CONSOLE_LOG_PATTERN"
+ value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
+ <!-- 鎺у埗鍙拌緭鍑� -->
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+ <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+ <charset>utf8</charset>
+ </encoder>
+ </appender>
+
+ <!-- 鐢熸垚鏃ュ織鏂囦欢 -->
+ <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+ <!-- 鏃ュ織鏂囦欢杈撳嚭鐨勬枃浠跺悕 -->
+ <FileNamePattern>target/blade/log/info-%d{yyyy-MM-dd}.log</FileNamePattern>
+ </rollingPolicy>
+ <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+ <pattern>%n%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%logger{50}] %n%-5level: %msg%n</pattern>
+ </encoder>
+ <!-- 鎵撳嵃鏃ュ織绾у埆 -->
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <level>INFO</level>
+ <onMatch>ACCEPT</onMatch>
+ <onMismatch>DENY</onMismatch>
+ </filter>
+ </appender>
+
+ <!-- 鐢熸垚鏃ュ織鏂囦欢 -->
+ <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+ <!-- 鏃ュ織鏂囦欢杈撳嚭鐨勬枃浠跺悕 -->
+ <FileNamePattern>target/blade/log/error-%d{yyyy-MM-dd}.log</FileNamePattern>
+ </rollingPolicy>
+ <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+ <pattern>%n%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%logger{50}] %n%-5level: %msg%n</pattern>
+ </encoder>
+ <!-- 鎵撳嵃鏃ュ織绾у埆 -->
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <level>ERROR</level>
+ <onMatch>ACCEPT</onMatch>
+ <onMismatch>DENY</onMismatch>
+ </filter>
+ </appender>
+
+ <if condition='property("ELK_MODE").toUpperCase().contains("TRUE")'>
+ <then>
+ <!-- 鎺ㄩ�佹棩蹇楄嚦elk -->
+ <appender name="INFO_LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
+ <destination>${DESTINATION}</destination>
+ <!-- 鏃ュ織杈撳嚭缂栫爜 -->
+ <encoder charset="UTF-8" class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
+ <providers>
+ <timestamp>
+ <timeZone>UTC</timeZone>
+ </timestamp>
+ <pattern>
+ <pattern>
+ {
+ "traceId": "%X{traceId}",
+ "requestId": "%X{requestId}",
+ "accountId": "%X{accountId}",
+ "tenantId": "%X{tenantId}",
+ "logLevel": "%level",
+ "serviceName": "${springAppName:-SpringApp}",
+ "pid": "${PID:-}",
+ "thread": "%thread",
+ "class": "%logger{40}",
+ "line":"%L",
+ "message": "%message"
+ }
+ </pattern>
+ </pattern>
+ <mdc/>
+ <stackTrace/>
+ </providers>
+ </encoder>
+ <!-- 鎵撳嵃鏃ュ織绾у埆 -->
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <level>INFO</level>
+ <onMatch>ACCEPT</onMatch>
+ <onMismatch>DENY</onMismatch>
+ </filter>
+ </appender>
+
+ <!-- 鎺ㄩ�佹棩蹇楄嚦elk -->
+ <appender name="ERROR_LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
+ <destination>${DESTINATION}</destination>
+ <!-- 鏃ュ織杈撳嚭缂栫爜 -->
+ <encoder charset="UTF-8" class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
+ <providers>
+ <timestamp>
+ <timeZone>UTC</timeZone>
+ </timestamp>
+ <pattern>
+ <pattern>
+ {
+ "traceId": "%X{traceId}",
+ "requestId": "%X{requestId}",
+ "accountId": "%X{accountId}",
+ "tenantId": "%X{tenantId}",
+ "logLevel": "%level",
+ "serviceName": "${springAppName:-SpringApp}",
+ "pid": "${PID:-}",
+ "thread": "%thread",
+ "class": "%logger{40}",
+ "line":"%L",
+ "message": "%message"
+ }
+ </pattern>
+ </pattern>
+ <mdc/>
+ <stackTrace/>
+ </providers>
+ </encoder>
+ <!-- 鎵撳嵃鏃ュ織绾у埆 -->
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <level>ERROR</level>
+ <onMatch>ACCEPT</onMatch>
+ <onMismatch>DENY</onMismatch>
+ </filter>
+ </appender>
+ </then>
+ </if>
+
+ <!-- 鏃ュ織杈撳嚭绾у埆 -->
+ <root level="INFO">
+ <appender-ref ref="STDOUT"/>
+ <appender-ref ref="${INFO_APPENDER}"/>
+ <appender-ref ref="${ERROR_APPENDER}"/>
+ </root>
+
+ <logger name="net.sf.ehcache" level="INFO"/>
+ <logger name="druid.sql" level="INFO"/>
+
+ <!-- 鍑忓皯nacos鏃ュ織 -->
+ <logger name="com.alibaba.nacos" level="ERROR"/>
+
+</configuration>
diff --git a/Source/BladeX-Tool/blade-starter-log/src/main/resources/log/logback-test.xml b/Source/BladeX-Tool/blade-starter-log/src/main/resources/log/logback-test.xml
new file mode 100644
index 0000000..a8f42de
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-log/src/main/resources/log/logback-test.xml
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="false" debug="false">
+ <!-- 鑷畾涔夊弬鏁扮洃鍚� -->
+ <contextListener class="org.springblade.core.log.listener.LoggerStartupListener"/>
+ <springProperty scope="context" name="springAppName" source="spring.application.name"/>
+
+ <!-- 褰╄壊鏃ュ織渚濊禆鐨勬覆鏌撶被 -->
+ <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
+ <conversionRule conversionWord="wex"
+ converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
+ <conversionRule conversionWord="wEx"
+ converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
+ <!-- 褰╄壊鏃ュ織鏍煎紡 -->
+ <property name="CONSOLE_LOG_PATTERN"
+ value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
+ <!-- 鎺у埗鍙拌緭鍑� -->
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+ <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+ <charset>utf8</charset>
+ </encoder>
+ </appender>
+
+ <!-- 鐢熸垚鏃ュ織鏂囦欢 -->
+ <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+ <!-- 鏃ュ織鏂囦欢杈撳嚭鐨勬枃浠跺悕 -->
+ <FileNamePattern>target/blade/log/info-%d{yyyy-MM-dd}.log</FileNamePattern>
+ </rollingPolicy>
+ <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+ <pattern>%n%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%logger{50}] %n%-5level: %msg%n</pattern>
+ </encoder>
+ <!-- 鎵撳嵃鏃ュ織绾у埆 -->
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <level>INFO</level>
+ <onMatch>ACCEPT</onMatch>
+ <onMismatch>DENY</onMismatch>
+ </filter>
+ </appender>
+
+ <!-- 鐢熸垚鏃ュ織鏂囦欢 -->
+ <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+ <!-- 鏃ュ織鏂囦欢杈撳嚭鐨勬枃浠跺悕 -->
+ <FileNamePattern>target/blade/log/error-%d{yyyy-MM-dd}.log</FileNamePattern>
+ </rollingPolicy>
+ <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+ <pattern>%n%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%logger{50}] %n%-5level: %msg%n</pattern>
+ </encoder>
+ <!-- 鎵撳嵃鏃ュ織绾у埆 -->
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <level>ERROR</level>
+ <onMatch>ACCEPT</onMatch>
+ <onMismatch>DENY</onMismatch>
+ </filter>
+ </appender>
+
+ <if condition='property("ELK_MODE").toUpperCase().contains("TRUE")'>
+ <then>
+ <!-- 鎺ㄩ�佹棩蹇楄嚦elk -->
+ <appender name="INFO_LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
+ <destination>${DESTINATION}</destination>
+ <!-- 鏃ュ織杈撳嚭缂栫爜 -->
+ <encoder charset="UTF-8" class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
+ <providers>
+ <timestamp>
+ <timeZone>UTC</timeZone>
+ </timestamp>
+ <pattern>
+ <pattern>
+ {
+ "traceId": "%X{traceId}",
+ "requestId": "%X{requestId}",
+ "accountId": "%X{accountId}",
+ "tenantId": "%X{tenantId}",
+ "logLevel": "%level",
+ "serviceName": "${springAppName:-SpringApp}",
+ "pid": "${PID:-}",
+ "thread": "%thread",
+ "class": "%logger{40}",
+ "line":"%L",
+ "message": "%message"
+ }
+ </pattern>
+ </pattern>
+ <mdc/>
+ <stackTrace/>
+ </providers>
+ </encoder>
+ <!-- 鎵撳嵃鏃ュ織绾у埆 -->
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <level>INFO</level>
+ <onMatch>ACCEPT</onMatch>
+ <onMismatch>DENY</onMismatch>
+ </filter>
+ </appender>
+
+ <!-- 鎺ㄩ�佹棩蹇楄嚦elk -->
+ <appender name="ERROR_LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
+ <destination>${DESTINATION}</destination>
+ <!-- 鏃ュ織杈撳嚭缂栫爜 -->
+ <encoder charset="UTF-8" class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
+ <providers>
+ <timestamp>
+ <timeZone>UTC</timeZone>
+ </timestamp>
+ <pattern>
+ <pattern>
+ {
+ "traceId": "%X{traceId}",
+ "requestId": "%X{requestId}",
+ "accountId": "%X{accountId}",
+ "tenantId": "%X{tenantId}",
+ "logLevel": "%level",
+ "serviceName": "${springAppName:-SpringApp}",
+ "pid": "${PID:-}",
+ "thread": "%thread",
+ "class": "%logger{40}",
+ "line":"%L",
+ "message": "%message"
+ }
+ </pattern>
+ </pattern>
+ <mdc/>
+ <stackTrace/>
+ </providers>
+ </encoder>
+ <!-- 鎵撳嵃鏃ュ織绾у埆 -->
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <level>ERROR</level>
+ <onMatch>ACCEPT</onMatch>
+ <onMismatch>DENY</onMismatch>
+ </filter>
+ </appender>
+ </then>
+ </if>
+
+ <!-- 鏃ュ織杈撳嚭绾у埆 -->
+ <root level="INFO">
+ <appender-ref ref="STDOUT"/>
+ <appender-ref ref="${INFO_APPENDER}"/>
+ <appender-ref ref="${ERROR_APPENDER}"/>
+ </root>
+
+ <logger name="net.sf.ehcache" level="INFO"/>
+ <logger name="druid.sql" level="INFO"/>
+
+ <!-- 鍑忓皯nacos鏃ュ織 -->
+ <logger name="com.alibaba.nacos" level="ERROR"/>
+
+</configuration>
diff --git a/Source/BladeX-Tool/blade-starter-metrics/pom.xml b/Source/BladeX-Tool/blade-starter-metrics/pom.xml
new file mode 100644
index 0000000..eb4305f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-metrics/pom.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-metrics</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.micrometer</groupId>
+ <artifactId>micrometer-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.micrometer</groupId>
+ <artifactId>micrometer-registry-prometheus</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.alibaba.csp</groupId>
+ <artifactId>sentinel-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>druid</artifactId>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-metrics/src/main/java/org/springblade/core/metrics/druid/DruidDataSourceMetadataProviderConfiguration.java b/Source/BladeX-Tool/blade-starter-metrics/src/main/java/org/springblade/core/metrics/druid/DruidDataSourceMetadataProviderConfiguration.java
new file mode 100644
index 0000000..a7525de
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-metrics/src/main/java/org/springblade/core/metrics/druid/DruidDataSourceMetadataProviderConfiguration.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.metrics.druid;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.jdbc.DataSourceUnwrapper;
+import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * DruidDataSourceMetadata Provide
+ *
+ * @author L.cm
+ */
+@AutoConfiguration
+@ConditionalOnClass(DruidDataSource.class)
+public class DruidDataSourceMetadataProviderConfiguration {
+
+ @Bean
+ public DataSourcePoolMetadataProvider druidDataSourceMetadataProvider() {
+ return (dataSource) -> {
+ DruidDataSource druidDataSource = DataSourceUnwrapper.unwrap(dataSource, DruidDataSource.class);
+ if (druidDataSource != null) {
+ return new DruidDataSourcePoolMetadata(druidDataSource);
+ }
+ return null;
+ };
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-metrics/src/main/java/org/springblade/core/metrics/druid/DruidDataSourcePoolMetadata.java b/Source/BladeX-Tool/blade-starter-metrics/src/main/java/org/springblade/core/metrics/druid/DruidDataSourcePoolMetadata.java
new file mode 100644
index 0000000..087bedd
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-metrics/src/main/java/org/springblade/core/metrics/druid/DruidDataSourcePoolMetadata.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.metrics.druid;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import org.springframework.boot.jdbc.metadata.AbstractDataSourcePoolMetadata;
+
+/**
+ * druid 杩炴帴姹� pool meta data
+ *
+ * @author L.cm
+ */
+public class DruidDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata<DruidDataSource> {
+
+ public DruidDataSourcePoolMetadata(DruidDataSource dataSource) {
+ super(dataSource);
+ }
+
+ @Override
+ public Integer getActive() {
+ return getDataSource().getActiveCount();
+ }
+
+ @Override
+ public Integer getMax() {
+ return getDataSource().getMaxActive();
+ }
+
+ @Override
+ public Integer getMin() {
+ return getDataSource().getMinIdle();
+ }
+
+ @Override
+ public String getValidationQuery() {
+ return getDataSource().getValidationQuery();
+ }
+
+ @Override
+ public Boolean getDefaultAutoCommit() {
+ return getDataSource().isDefaultAutoCommit();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-metrics/src/main/java/org/springblade/core/metrics/sentinel/SentinelMetricsExtension.java b/Source/BladeX-Tool/blade-starter-metrics/src/main/java/org/springblade/core/metrics/sentinel/SentinelMetricsExtension.java
new file mode 100644
index 0000000..d03804a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-metrics/src/main/java/org/springblade/core/metrics/sentinel/SentinelMetricsExtension.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.metrics.sentinel;
+
+import com.alibaba.csp.sentinel.metric.extension.MetricExtension;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import io.micrometer.core.instrument.Metrics;
+import io.micrometer.core.instrument.Tags;
+import org.springblade.core.auto.service.AutoService;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Sentinel Metrics Extension
+ *
+ * @author L.cm
+ */
+@AutoService(MetricExtension.class)
+public class SentinelMetricsExtension implements MetricExtension {
+ public static final String PASS_REQUESTS_TOTAL = "sentinel_pass_requests_total";
+ public static final String BLOCK_REQUESTS_TOTAL = "sentinel_block_requests_total";
+ public static final String SUCCESS_REQUESTS_TOTAL = "sentinel_success_requests_total";
+ public static final String EXCEPTION_REQUESTS_TOTAL = "sentinel_exception_requests_total";
+ public static final String REQUESTS_LATENCY_SECONDS = "sentinel_requests_latency_seconds";
+ public static final String CURRENT_THREADS = "sentinel_current_threads";
+ public static final String DEFAULT_TAT_NAME = "resource";
+ private final AtomicLong CURRENT_THREAD_COUNT = new AtomicLong(0);
+
+ @Override
+ public void addPass(String resource, int n, Object... args) {
+ Metrics.counter(PASS_REQUESTS_TOTAL, DEFAULT_TAT_NAME, resource).increment(n);
+ }
+
+ @Override
+ public void addBlock(String resource, int n, String origin, BlockException ex, Object... args) {
+ Metrics.counter(BLOCK_REQUESTS_TOTAL, resource, ex.getClass().getSimpleName(), ex.getRuleLimitApp(), origin).increment(n);
+ }
+
+ @Override
+ public void addSuccess(String resource, int n, Object... args) {
+ Metrics.counter(SUCCESS_REQUESTS_TOTAL, DEFAULT_TAT_NAME, resource).increment(n);
+ }
+
+ @Override
+ public void addException(String resource, int n, Throwable throwable) {
+ Metrics.counter(EXCEPTION_REQUESTS_TOTAL, DEFAULT_TAT_NAME, resource).increment(n);
+ }
+
+ @Override
+ public void addRt(String resource, long rt, Object... args) {
+ Metrics.timer(REQUESTS_LATENCY_SECONDS, DEFAULT_TAT_NAME, resource).record(rt, TimeUnit.MICROSECONDS);
+ }
+
+ @Override
+ public void increaseThreadNum(String resource, Object... args) {
+ Tags tags = Tags.of(DEFAULT_TAT_NAME, resource);
+ Metrics.gauge(CURRENT_THREADS, tags, CURRENT_THREAD_COUNT, AtomicLong::incrementAndGet);
+ }
+
+ @Override
+ public void decreaseThreadNum(String resource, Object... args) {
+ Tags tags = Tags.of(DEFAULT_TAT_NAME, resource);
+ Metrics.gauge(CURRENT_THREADS, tags, CURRENT_THREAD_COUNT, AtomicLong::decrementAndGet);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-mongo/pom.xml b/Source/BladeX-Tool/blade-starter-mongo/pom.xml
new file mode 100644
index 0000000..62355db
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mongo/pom.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-mongo</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-tool</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-data-mongodb</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-json</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/config/MongoConfiguration.java b/Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/config/MongoConfiguration.java
new file mode 100644
index 0000000..e578acb
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/config/MongoConfiguration.java
@@ -0,0 +1,28 @@
+package org.springblade.core.mongo.config;
+
+import org.springblade.core.mongo.converter.DBObjectToJsonNodeConverter;
+import org.springblade.core.mongo.converter.JsonNodeToDocumentConverter;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * mongo 閰嶇疆
+ *
+ * @author L.cm
+ */
+@AutoConfiguration
+public class MongoConfiguration {
+
+ @Bean
+ public MongoCustomConversions customConversions() {
+ List<Converter<?,?>> converters = new ArrayList<>(2);
+ converters.add(DBObjectToJsonNodeConverter.INSTANCE);
+ converters.add(JsonNodeToDocumentConverter.INSTANCE);
+ return new MongoCustomConversions(converters);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/converter/DBObjectToJsonNodeConverter.java b/Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/converter/DBObjectToJsonNodeConverter.java
new file mode 100644
index 0000000..da97877
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/converter/DBObjectToJsonNodeConverter.java
@@ -0,0 +1,30 @@
+package org.springblade.core.mongo.converter;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.bson.BasicBSONObject;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.data.convert.ReadingConverter;
+import org.springframework.lang.Nullable;
+
+/**
+ * mongo DBObject 杞� jsonNode
+ *
+ * @author L.cm
+ */
+@ReadingConverter
+public enum DBObjectToJsonNodeConverter implements Converter<BasicBSONObject, JsonNode> {
+ /**
+ * 瀹炰緥
+ */
+ INSTANCE;
+
+ @Override
+ public JsonNode convert(@Nullable BasicBSONObject source) {
+ if (source == null) {
+ return null;
+ }
+ return JsonUtil.getInstance().valueToTree(source);
+ }
+}
+
diff --git a/Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/converter/JsonNodeToDocumentConverter.java b/Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/converter/JsonNodeToDocumentConverter.java
new file mode 100644
index 0000000..5ec3067
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/converter/JsonNodeToDocumentConverter.java
@@ -0,0 +1,25 @@
+package org.springblade.core.mongo.converter;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.bson.Document;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.data.convert.WritingConverter;
+import org.springframework.lang.Nullable;
+
+/**
+ * JsonNode 杞� mongo Document
+ *
+ * @author L.cm
+ */
+@WritingConverter
+public enum JsonNodeToDocumentConverter implements Converter<ObjectNode, Document> {
+ /**
+ * 瀹炰緥
+ */
+ INSTANCE;
+
+ @Override
+ public Document convert(@Nullable ObjectNode source) {
+ return source == null ? null : Document.parse(source.toString());
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/utils/JsonNodeInfo.java b/Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/utils/JsonNodeInfo.java
new file mode 100644
index 0000000..7d4ae04
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/utils/JsonNodeInfo.java
@@ -0,0 +1,85 @@
+package org.springblade.core.mongo.utils;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Getter;
+import org.springframework.util.Assert;
+
+import java.util.LinkedList;
+import java.util.StringJoiner;
+
+/**
+ * json tree 鑺傜偣淇℃伅
+ *
+ * @author L.cm
+ */
+public class JsonNodeInfo {
+ /**
+ * mongo keys: class1.class2.item
+ */
+ private volatile String nodeKeys;
+ /**
+ * jsonPath璇硶锛�/class1/class2/item
+ */
+ private volatile String nodePath;
+ /**
+ * 鑺傜偣鍏崇郴
+ */
+ @Getter
+ private final LinkedList<String> elements;
+ /**
+ * tree 鐨� 鍙跺瓙鑺傜偣锛屾澶勪负寮曠敤
+ */
+ @Getter
+ private final JsonNode leafNode;
+
+ public JsonNodeInfo(LinkedList<String> elements, JsonNode leafNode) {
+ Assert.notNull(elements, "elements can not be null.");
+ this.nodeKeys = null;
+ this.nodePath = null;
+ this.elements = elements;
+ this.leafNode = leafNode;
+ }
+
+ /**
+ * 鑾峰彇 mongo db鐨� key 璇硶
+ * @return mongo db鐨� key 璇硶
+ */
+ public String getNodeKeys() {
+ if (nodeKeys == null) {
+ synchronized (this) {
+ if (nodeKeys == null) {
+ StringJoiner nodeKeysJoiner = new StringJoiner(".");
+ elements.forEach(nodeKeysJoiner::add);
+ nodeKeys = nodeKeysJoiner.toString();
+ }
+ }
+ }
+ return nodeKeys;
+ }
+
+ /**
+ * 鑾峰彇 json path 璇硶璺緞
+ * @return jsonPath 璺緞
+ */
+ public String getNodePath() {
+ if (nodePath == null) {
+ synchronized (this) {
+ if (nodePath == null) {
+ StringJoiner nodePathJoiner = new StringJoiner("/", "/", "");
+ elements.forEach(nodePathJoiner::add);
+ nodePath = nodePathJoiner.toString();
+ }
+ }
+ }
+ return nodePath;
+ }
+
+ /**
+ * 鑾峰彇绗竴涓厓绱�
+ * @return element
+ */
+ public String getFirst() {
+ return elements.getFirst();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/utils/MongoJsonUtils.java b/Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/utils/MongoJsonUtils.java
new file mode 100644
index 0000000..31acdab
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mongo/src/main/java/org/springblade/core/mongo/utils/MongoJsonUtils.java
@@ -0,0 +1,126 @@
+package org.springblade.core.mongo.utils;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import java.util.*;
+
+/**
+ * 澶勭悊 mongo json 鏁版嵁缁撴瀯
+ *
+ * @author L.cm
+ */
+public class MongoJsonUtils {
+
+ /**
+ * 鑾峰彇鎵�鏈夌殑鍙跺瓙鑺傜偣鍜岃矾寰勪俊鎭�
+ *
+ * @param jsonNode jsonTree
+ * @return tree鍙跺瓙淇℃伅
+ */
+ public static List<JsonNodeInfo> getLeafNodes(JsonNode jsonNode) {
+ if (jsonNode == null || !jsonNode.isObject()) {
+ return Collections.emptyList();
+ }
+ List<JsonNodeInfo> list = new ArrayList<>();
+ // 鍙屽悜鐨勯槦鍒� Deque 浠f浛 Stack锛孲tack 鎬ц兘涓嶅ソ
+ LinkedList<String> deque = new LinkedList<>();
+ // 閫掑綊鑾峰彇鍙跺瓙 馃崈馃崈馃崈 鑺傜偣
+ getLeafNodes(jsonNode, null, deque, list);
+ return list;
+ }
+
+ private static void getLeafNodes(JsonNode jsonNode, JsonNode parentNode, LinkedList<String> deque, List<JsonNodeInfo> list) {
+ Iterator<Map.Entry<String, JsonNode>> iterator;
+ if (parentNode == null) {
+ iterator = jsonNode.fields();
+ } else {
+ iterator = parentNode.fields();
+ }
+ // tree 瀛愯妭鐐�
+ while (iterator.hasNext()) {
+ Map.Entry<String, JsonNode> entry = iterator.next();
+ String fieldName = entry.getKey();
+ JsonNode nextNode = entry.getValue();
+ // 濡傛灉涓嶆槸鍊艰妭鐐�
+ if (nextNode.isObject()) {
+ // 娣诲姞鍒伴槦鍒楀熬锛屽厛杩涘厛鍑�
+ deque.addLast(fieldName);
+ getLeafNodes(parentNode, nextNode, deque, list);
+ }
+ // 濡傛灉鏄�艰妭鐐癸紝涔熷氨鏄埌鍙跺瓙鑺傜偣浜嗭紝鍙栧彾瀛愯妭鐐逛笂绾у嵆鍙�
+ if (nextNode.isValueNode()) {
+ // 灏佽鑺傜偣鍒楄〃
+ LinkedList<String> elements = new LinkedList<>(deque);
+ // tree 鐨� 鍙跺瓙鑺傜偣锛屾澶勪负寮曠敤
+ list.add(new JsonNodeInfo(elements, parentNode));
+ break;
+ }
+ // 鏍堥潪绌烘椂寮瑰嚭
+ if (!deque.isEmpty()) {
+ deque.removeLast();
+ }
+ }
+ }
+
+ /**
+ * 鏋勫缓鏍戝舰鑺傜偣
+ *
+ * @param jsonNode 鐖剁骇鑺傜偣
+ * @param elements tree鑺傜偣鍒楄〃
+ * @return JsonNode 鍙跺瓙鑺傜偣锛岃繑鍥炵敤浜庡鏁版嵁
+ */
+ public static ObjectNode buildNode(ObjectNode jsonNode, List<String> elements) {
+ ObjectNode newNode = jsonNode;
+ for (String element : elements) {
+ // 濡傛灉宸茬粡瀛樺湪鑺傜偣锛岃繖涓嶇敓鎴愭柊鐨�
+ if (newNode.has(element)) {
+ newNode = (ObjectNode) newNode.get(element);
+ } else {
+ newNode = newNode.putObject(element);
+ }
+ }
+ return newNode;
+ }
+
+ /**
+ * 鑾峰彇鎵�鏈� 馃崈馃崈馃崈 鑺傜偣鐨勫�硷紝骞舵瀯寤烘垚 mongodb update 璇彞
+ * @param prefix 鍓嶇紑
+ * @param nodeKeys mongo keys
+ * @param objectNode tree 馃崈 鑺傜偣
+ * @return tree 鑺傜偣淇℃伅
+ */
+ public static Map<String, Object> getAllUpdate(String prefix, String nodeKeys, ObjectNode objectNode) {
+ Map<String, Object> values = new HashMap<>(8);
+ Iterator<String> iterator = objectNode.fieldNames();
+ while (iterator.hasNext()) {
+ String fieldName = iterator.next();
+ JsonNode valueNode = objectNode.get(fieldName);
+ if (valueNode.isValueNode()) {
+ Object value;
+ if (valueNode.isShort()) {
+ value = valueNode.shortValue();
+ } else if (valueNode.isInt()) {
+ value = valueNode.intValue();
+ } else if (valueNode.isLong()) {
+ value = valueNode.longValue();
+ } else if (valueNode.isBoolean()) {
+ value = valueNode.booleanValue();
+ } else if (valueNode.isFloat()) {
+ value = valueNode.floatValue();
+ } else if (valueNode.isDouble()) {
+ value = valueNode.doubleValue();
+ } else if (valueNode.isMissingNode()) {
+ value = null;
+ } else {
+ value = valueNode.textValue();
+ }
+ if (value != null) {
+ String valueKey = prefix + '.' + nodeKeys + '.' + fieldName;
+ values.put(valueKey, value);
+ }
+ }
+ }
+ return values;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/pom.xml b/Source/BladeX-Tool/blade-starter-mybatis/pom.xml
new file mode 100644
index 0000000..5079a08
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/pom.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-mybatis</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!--Mybatis-->
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.mybatis</groupId>
+ <artifactId>mybatis-typehandlers-jsr310</artifactId>
+ </dependency>
+ <!--Jdbc-->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-jdbc</artifactId>
+ <exclusions>
+ <exclusion>
+ <artifactId>tomcat-jdbc</artifactId>
+ <groupId>org.apache.tomcat</groupId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <!-- Druid -->
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>druid</artifactId>
+ </dependency>
+ <!--Blade-->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-auth</artifactId>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/BladeMetaObjectHandler.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/BladeMetaObjectHandler.java
new file mode 100644
index 0000000..e91e65a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/BladeMetaObjectHandler.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.mp;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.reflection.MetaObject;
+
+/**
+ * mybatisplus鑷畾涔夊~鍏�
+ *
+ * @author Chill
+ */
+@Slf4j
+public class BladeMetaObjectHandler implements MetaObjectHandler {
+
+ @Override
+ public void insertFill(MetaObject metaObject) {
+
+ }
+
+ @Override
+ public void updateFill(MetaObject metaObject) {
+
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/base/BaseEntity.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/base/BaseEntity.java
new file mode 100644
index 0000000..cc71e77
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/base/BaseEntity.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.mp.base;
+
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 鍩虹瀹炰綋绫�
+ *
+ * @author Chill
+ */
+@Data
+public class BaseEntity implements Serializable {
+ /**
+ * 涓婚敭id
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ @ApiModelProperty(value = "涓婚敭id")
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /**
+ * 鍒涘缓浜�
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ @ApiModelProperty(value = "鍒涘缓浜�")
+ private Long createUser;
+
+ /**
+ * 鍒涘缓閮ㄩ棬
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ @ApiModelProperty(value = "鍒涘缓閮ㄩ棬")
+ private Long createDept;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+
+ /**
+ * 鏇存柊浜�
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ @ApiModelProperty(value = "鏇存柊浜�")
+ private Long updateUser;
+
+ /**
+ * 鏇存柊鏃堕棿
+ */
+ @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @ApiModelProperty(value = "鏇存柊鏃堕棿")
+ private Date updateTime;
+
+ /**
+ * 鐘舵�乕1:姝e父]
+ */
+ @ApiModelProperty(value = "涓氬姟鐘舵��")
+ private Integer status;
+
+ /**
+ * 鐘舵�乕0:鏈垹闄�,1:鍒犻櫎]
+ */
+ @TableLogic
+ @ApiModelProperty(value = "鏄惁宸插垹闄�")
+ private Integer isDeleted;
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/base/BaseService.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/base/BaseService.java
new file mode 100644
index 0000000..ec74278
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/base/BaseService.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.mp.base;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import javax.validation.constraints.NotEmpty;
+import java.util.List;
+
+/**
+ * 鍩虹涓氬姟鎺ュ彛
+ *
+ * @param <T>
+ * @author Chill
+ */
+public interface BaseService<T> extends IService<T> {
+
+ /**
+ * 閫昏緫鍒犻櫎
+ *
+ * @param ids id闆嗗悎
+ * @return
+ */
+ boolean deleteLogic(@NotEmpty List<Long> ids);
+
+ /**
+ * 鍙樻洿鐘舵��
+ *
+ * @param ids id闆嗗悎
+ * @param status 鐘舵�佸��
+ * @return
+ */
+ boolean changeStatus(@NotEmpty List<Long> ids, Integer status);
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/base/BaseServiceImpl.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/base/BaseServiceImpl.java
new file mode 100644
index 0000000..4fee53b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/base/BaseServiceImpl.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.mp.base;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.SneakyThrows;
+import org.springblade.core.secure.BladeUser;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.constant.BladeConstant;
+import org.springblade.core.tool.utils.*;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotEmpty;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 涓氬姟灏佽鍩虹绫�
+ *
+ * @param <M> mapper
+ * @param <T> model
+ * @author Chill
+ */
+@Validated
+public class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseEntity> extends ServiceImpl<M, T> implements BaseService<T> {
+
+ @Override
+ public boolean save(T entity) {
+ this.resolveEntity(entity);
+ return super.save(entity);
+ }
+
+ @Override
+ public boolean saveBatch(Collection<T> entityList, int batchSize) {
+ entityList.forEach(this::resolveEntity);
+ return super.saveBatch(entityList, batchSize);
+ }
+
+ @Override
+ public boolean updateById(T entity) {
+ this.resolveEntity(entity);
+ return super.updateById(entity);
+ }
+
+ @Override
+ public boolean updateBatchById(Collection<T> entityList, int batchSize) {
+ entityList.forEach(this::resolveEntity);
+ return super.updateBatchById(entityList, batchSize);
+ }
+
+ @Override
+ public boolean saveOrUpdate(T entity) {
+ if (entity.getId() == null) {
+ return this.save(entity);
+ } else {
+ return this.updateById(entity);
+ }
+ }
+
+ @Override
+ public boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize) {
+ entityList.forEach(this::resolveEntity);
+ return super.saveOrUpdateBatch(entityList, batchSize);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean deleteLogic(@NotEmpty List<Long> ids) {
+ BladeUser user = AuthUtil.getUser();
+ List<T> list = new ArrayList<>();
+ ids.forEach(id -> {
+ T entity = BeanUtil.newInstance(currentModelClass());
+ if (user != null) {
+ entity.setUpdateUser(user.getUserId());
+ }
+ entity.setUpdateTime(DateUtil.now());
+ entity.setId(id);
+ list.add(entity);
+ });
+ return super.updateBatchById(list) && super.removeByIds(ids);
+ }
+
+ @Override
+ public boolean changeStatus(@NotEmpty List<Long> ids, Integer status) {
+ BladeUser user = AuthUtil.getUser();
+ List<T> list = new ArrayList<>();
+ ids.forEach(id -> {
+ T entity = BeanUtil.newInstance(currentModelClass());
+ if (user != null) {
+ entity.setUpdateUser(user.getUserId());
+ }
+ entity.setUpdateTime(DateUtil.now());
+ entity.setId(id);
+ entity.setStatus(status);
+ list.add(entity);
+ });
+ return super.updateBatchById(list);
+ }
+
+ @SneakyThrows
+ private void resolveEntity(T entity) {
+ BladeUser user = AuthUtil.getUser();
+ Date now = DateUtil.now();
+ if (entity.getId() == null) {
+ // 澶勭悊鏂板閫昏緫
+ if (user != null) {
+ entity.setCreateUser(user.getUserId());
+ entity.setCreateDept(Func.firstLong(user.getDeptId()));
+ entity.setUpdateUser(user.getUserId());
+ }
+ if (entity.getStatus() == null) {
+ entity.setStatus(BladeConstant.DB_STATUS_NORMAL);
+ }
+ entity.setCreateTime(now);
+ } else if (user != null) {
+ // 澶勭悊淇敼閫昏緫
+ entity.setUpdateUser(user.getUserId());
+ }
+ // 澶勭悊閫氱敤閫昏緫
+ entity.setUpdateTime(now);
+ entity.setIsDeleted(BladeConstant.DB_NOT_DELETED);
+ // 澶勭悊澶氱鎴烽�昏緫锛岃嫢瀛楁鍊间负绌猴紝鍒欎笉杩涜鎿嶄綔
+ Field field = ReflectUtil.getField(entity.getClass(), BladeConstant.DB_TENANT_KEY);
+ if (ObjectUtil.isNotEmpty(field)) {
+ Method getTenantId = ClassUtil.getMethod(entity.getClass(), BladeConstant.DB_TENANT_KEY_GET_METHOD);
+ String tenantId = String.valueOf(getTenantId.invoke(entity));
+ if (ObjectUtil.isEmpty(tenantId)) {
+ Method setTenantId = ClassUtil.getMethod(entity.getClass(), BladeConstant.DB_TENANT_KEY_SET_METHOD, String.class);
+ setTenantId.invoke(entity, (Object) null);
+ }
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/config/MybatisPlusConfiguration.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/config/MybatisPlusConfiguration.java
new file mode 100644
index 0000000..6263cfa
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/config/MybatisPlusConfiguration.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.mp.config;
+
+import com.baomidou.mybatisplus.core.injector.ISqlInjector;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
+import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
+import lombok.AllArgsConstructor;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.StringValue;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springblade.core.launch.props.BladePropertySource;
+import org.springblade.core.mp.injector.BladeSqlInjector;
+import org.springblade.core.mp.intercept.QueryInterceptor;
+import org.springblade.core.mp.plugins.BladePaginationInterceptor;
+import org.springblade.core.mp.plugins.SqlLogInterceptor;
+import org.springblade.core.mp.props.MybatisPlusProperties;
+import org.springblade.core.mp.resolver.PageArgumentResolver;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.constant.BladeConstant;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.ObjectUtil;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.util.List;
+
+/**
+ * mybatis-plus 閰嶇疆
+ *
+ * @author Chill
+ */
+@AutoConfiguration
+@AllArgsConstructor
+@MapperScan("org.springblade.**.mapper.**")
+@EnableConfigurationProperties(MybatisPlusProperties.class)
+@BladePropertySource(value = "classpath:/blade-mybatis.yml")
+public class MybatisPlusConfiguration implements WebMvcConfigurer {
+
+ /**
+ * 绉熸埛鎷︽埅鍣�
+ */
+ @Bean
+ @ConditionalOnMissingBean(TenantLineInnerInterceptor.class)
+ public TenantLineInnerInterceptor tenantLineInnerInterceptor() {
+ return new TenantLineInnerInterceptor(new TenantLineHandler() {
+ @Override
+ public Expression getTenantId() {
+ return new StringValue(Func.toStr(AuthUtil.getTenantId(), BladeConstant.ADMIN_TENANT_ID));
+ }
+
+ @Override
+ public boolean ignoreTable(String tableName) {
+ return true;
+ }
+ });
+ }
+
+ /**
+ * mybatis-plus 鎷︽埅鍣ㄩ泦鍚�
+ */
+ @Bean
+ @ConditionalOnMissingBean(MybatisPlusInterceptor.class)
+ public MybatisPlusInterceptor mybatisPlusInterceptor(ObjectProvider<QueryInterceptor[]> queryInterceptors,
+ TenantLineInnerInterceptor tenantLineInnerInterceptor,
+ MybatisPlusProperties mybatisPlusProperties) {
+ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+ // 閰嶇疆绉熸埛鎷︽埅鍣�
+ if (mybatisPlusProperties.getTenantMode()) {
+ interceptor.addInnerInterceptor(tenantLineInnerInterceptor);
+ }
+ // 閰嶇疆鍒嗛〉鎷︽埅鍣�
+ BladePaginationInterceptor paginationInterceptor = new BladePaginationInterceptor();
+ // 閰嶇疆鑷畾涔夋煡璇㈡嫤鎴櫒
+ QueryInterceptor[] queryInterceptorArray = queryInterceptors.getIfAvailable();
+ if (ObjectUtil.isNotEmpty(queryInterceptorArray)) {
+ AnnotationAwareOrderComparator.sort(queryInterceptorArray);
+ paginationInterceptor.setQueryInterceptors(queryInterceptorArray);
+ }
+ paginationInterceptor.setMaxLimit(mybatisPlusProperties.getPageLimit());
+ paginationInterceptor.setOverflow(mybatisPlusProperties.getOverflow());
+ paginationInterceptor.setOptimizeJoin(mybatisPlusProperties.getOptimizeJoin());
+ interceptor.addInnerInterceptor(paginationInterceptor);
+ return interceptor;
+ }
+
+ /**
+ * sql 鏃ュ織
+ */
+ @Bean
+ public SqlLogInterceptor sqlLogInterceptor(MybatisPlusProperties mybatisPlusProperties) {
+ return new SqlLogInterceptor(mybatisPlusProperties);
+ }
+
+ /**
+ * sql 娉ㄥ叆
+ */
+ @Bean
+ @ConditionalOnMissingBean(ISqlInjector.class)
+ public ISqlInjector sqlInjector() {
+ return new BladeSqlInjector();
+ }
+
+ /**
+ * page 瑙f瀽鍣�
+ */
+ @Override
+ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
+ argumentResolvers.add(new PageArgumentResolver());
+ }
+
+}
+
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/BladeSqlInjector.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/BladeSqlInjector.java
new file mode 100644
index 0000000..c140dde
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/BladeSqlInjector.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.mp.injector;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.core.injector.AbstractMethod;
+import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
+import com.baomidou.mybatisplus.core.metadata.TableInfo;
+import com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn;
+import org.springblade.core.mp.injector.methods.InsertIgnore;
+import org.springblade.core.mp.injector.methods.Replace;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 鑷畾涔夌殑 sql 娉ㄥ叆
+ *
+ * @author L.cm
+ */
+public class BladeSqlInjector extends DefaultSqlInjector {
+
+ @Override
+ public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
+ List<AbstractMethod> methodList = new ArrayList<>();
+ methodList.add(new InsertIgnore());
+ methodList.add(new Replace());
+ methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.UPDATE));
+ methodList.addAll(super.getMethodList(mapperClass, tableInfo));
+ return Collections.unmodifiableList(methodList);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/BladeSqlMethod.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/BladeSqlMethod.java
new file mode 100644
index 0000000..89c54e7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/BladeSqlMethod.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.mp.injector;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 鎵╁睍鐨勮嚜瀹氫箟鏂规硶
+ *
+ * AbstractInsertMethod
+ *
+ * @author L.cm
+ */
+@Getter
+@AllArgsConstructor
+public enum BladeSqlMethod {
+
+ /**
+ * 鎻掑叆濡傛灉涓凡缁忓瓨鍦ㄧ浉鍚岀殑璁板綍锛屽垯蹇界暐褰撳墠鏂版暟鎹�
+ */
+ INSERT_IGNORE_ONE("insertIgnore", "鎻掑叆涓�鏉℃暟鎹紙閫夋嫨瀛楁鎻掑叆锛�", "<script>\nINSERT IGNORE INTO %s %s VALUES %s\n</script>"),
+
+ /**
+ * 琛ㄧず鎻掑叆鏇挎崲鏁版嵁锛岄渶姹傝〃涓湁PrimaryKey锛屾垨鑰卽nique绱㈠紩锛屽鏋滄暟鎹簱宸茬粡瀛樺湪鏁版嵁锛屽垯鐢ㄦ柊鏁版嵁鏇挎崲锛屽鏋滄病鏈夋暟鎹晥鏋滃垯鍜宨nsert into涓�鏍凤紱
+ */
+ REPLACE_ONE("replace", "鎻掑叆涓�鏉℃暟鎹紙閫夋嫨瀛楁鎻掑叆锛�", "<script>\nREPLACE INTO %s %s VALUES %s\n</script>");
+
+ private final String method;
+ private final String desc;
+ private final String sql;
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/methods/AbstractInsertMethod.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/methods/AbstractInsertMethod.java
new file mode 100644
index 0000000..e0a7c32
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/methods/AbstractInsertMethod.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.mp.injector.methods;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.core.injector.AbstractMethod;
+import com.baomidou.mybatisplus.core.metadata.TableInfo;
+import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
+import lombok.RequiredArgsConstructor;
+import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
+import org.apache.ibatis.executor.keygen.KeyGenerator;
+import org.apache.ibatis.executor.keygen.NoKeyGenerator;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.SqlSource;
+import org.springblade.core.mp.injector.BladeSqlMethod;
+
+/**
+ * 鎶借薄鐨� 鎻掑叆涓�鏉℃暟鎹紙閫夋嫨瀛楁鎻掑叆锛�
+ *
+ * @author L.cm
+ */
+@RequiredArgsConstructor
+public class AbstractInsertMethod extends AbstractMethod {
+ private final BladeSqlMethod sqlMethod;
+
+ @Override
+ public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
+ KeyGenerator keyGenerator = new NoKeyGenerator();
+ String columnScript = SqlScriptUtils.convertTrim(tableInfo.getAllInsertSqlColumnMaybeIf(null),
+ LEFT_BRACKET, RIGHT_BRACKET, null, COMMA);
+ String valuesScript = SqlScriptUtils.convertTrim(tableInfo.getAllInsertSqlPropertyMaybeIf(null),
+ LEFT_BRACKET, RIGHT_BRACKET, null, COMMA);
+ String keyProperty = null;
+ String keyColumn = null;
+ // 琛ㄥ寘鍚富閿鐞嗛�昏緫,濡傛灉涓嶅寘鍚富閿綋鏅�氬瓧娈靛鐞�
+ if (StringUtils.isNotBlank(tableInfo.getKeyProperty())) {
+ if (tableInfo.getIdType() == IdType.AUTO) {
+ // 鑷涓婚敭
+ keyGenerator = new Jdbc3KeyGenerator();
+ keyProperty = tableInfo.getKeyProperty();
+ keyColumn = tableInfo.getKeyColumn();
+ } else {
+ if (null != tableInfo.getKeySequence()) {
+ keyGenerator = TableInfoHelper.genKeyGenerator(sqlMethod.getMethod(), tableInfo, builderAssistant);
+ keyProperty = tableInfo.getKeyProperty();
+ keyColumn = tableInfo.getKeyColumn();
+ }
+ }
+ }
+ String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
+ SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
+ return this.addInsertMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource, keyGenerator, keyProperty, keyColumn);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/methods/InsertIgnore.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/methods/InsertIgnore.java
new file mode 100644
index 0000000..6a29762
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/methods/InsertIgnore.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.mp.injector.methods;
+
+
+import org.springblade.core.mp.injector.BladeSqlMethod;
+
+/**
+ * 鎻掑叆涓�鏉℃暟鎹紙閫夋嫨瀛楁鎻掑叆锛夋彃鍏ュ鏋滀腑宸茬粡瀛樺湪鐩稿悓鐨勮褰曪紝鍒欏拷鐣ュ綋鍓嶆柊鏁版嵁
+ *
+ * @author L.cm
+ */
+public class InsertIgnore extends AbstractInsertMethod {
+
+ public InsertIgnore() {
+ super(BladeSqlMethod.INSERT_IGNORE_ONE);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/methods/Replace.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/methods/Replace.java
new file mode 100644
index 0000000..415caa0
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/injector/methods/Replace.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.mp.injector.methods;
+
+
+import org.springblade.core.mp.injector.BladeSqlMethod;
+
+/**
+ * 鎻掑叆涓�鏉℃暟鎹紙閫夋嫨瀛楁鎻掑叆锛�
+ * <p>
+ * 琛ㄧず鎻掑叆鏇挎崲鏁版嵁锛岄渶姹傝〃涓湁PrimaryKey锛屾垨鑰卽nique绱㈠紩锛屽鏋滄暟鎹簱宸茬粡瀛樺湪鏁版嵁锛屽垯鐢ㄦ柊鏁版嵁鏇挎崲锛屽鏋滄病鏈夋暟鎹晥鏋滃垯鍜宨nsert into涓�鏍凤紱
+ * </p>
+ *
+ * @author L.cm
+ */
+public class Replace extends AbstractInsertMethod {
+
+ public Replace() {
+ super(BladeSqlMethod.REPLACE_ONE);
+ }
+}
+
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/intercept/QueryInterceptor.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/intercept/QueryInterceptor.java
new file mode 100644
index 0000000..f101a81
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/intercept/QueryInterceptor.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.mp.intercept;
+
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
+import org.springframework.core.Ordered;
+
+/**
+ * 鑷畾涔� mybatis plus 鏌ヨ鎷︽埅鍣�
+ *
+ * @author L.cm
+ */
+@SuppressWarnings({"rawtypes"})
+public interface QueryInterceptor extends Ordered {
+
+ /**
+ * 鎷︽埅澶勭悊
+ *
+ * @param executor
+ * @param ms
+ * @param parameter
+ * @param rowBounds
+ * @param resultHandler
+ * @param boundSql
+ */
+ void intercept(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql);
+
+ /**
+ * 鎺掑簭
+ *
+ * @return int
+ */
+ @Override
+ default int getOrder() {
+ return Ordered.LOWEST_PRECEDENCE;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/mapper/BladeMapper.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/mapper/BladeMapper.java
new file mode 100644
index 0000000..580042b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/mapper/BladeMapper.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.mp.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import java.util.List;
+
+/**
+ * 鑷畾涔夌殑 Mapper
+ *
+ * @author L.cm
+ */
+public interface BladeMapper<T> extends BaseMapper<T> {
+
+ /**
+ * 鎻掑叆濡傛灉涓凡缁忓瓨鍦ㄧ浉鍚岀殑璁板綍锛屽垯蹇界暐褰撳墠鏂版暟鎹�
+ *
+ * @param entity 瀹炰綋瀵硅薄
+ * @return 鏇存敼鐨勬潯鏁�
+ */
+ int insertIgnore(T entity);
+
+ /**
+ * 琛ㄧず鎻掑叆鏇挎崲鏁版嵁锛岄渶姹傝〃涓湁PrimaryKey锛屾垨鑰卽nique绱㈠紩锛屽鏋滄暟鎹簱宸茬粡瀛樺湪鏁版嵁锛屽垯鐢ㄦ柊鏁版嵁鏇挎崲锛屽鏋滄病鏈夋暟鎹晥鏋滃垯鍜宨nsert into涓�鏍凤紱
+ *
+ * @param entity 瀹炰綋瀵硅薄
+ * @return 鏇存敼鐨勬潯鏁�
+ */
+ int replace(T entity);
+
+ /**
+ * 鎻掑叆锛堟壒閲忥級
+ *
+ * @param entityList 瀹炰綋瀵硅薄闆嗗悎
+ * @return 鎴愬姛琛屾暟
+ */
+ int insertBatchSomeColumn(List<T> entityList);
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/plugins/BladePaginationInterceptor.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/plugins/BladePaginationInterceptor.java
new file mode 100644
index 0000000..6b6c059
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/plugins/BladePaginationInterceptor.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.mp.plugins;
+
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import lombok.Setter;
+import lombok.SneakyThrows;
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
+import org.springblade.core.mp.intercept.QueryInterceptor;
+
+/**
+ * 鎷撳睍鍒嗛〉鎷︽埅鍣�
+ *
+ * @author Chill
+ */
+@Setter
+public class BladePaginationInterceptor extends PaginationInnerInterceptor {
+
+ /**
+ * 鏌ヨ鎷︽埅鍣�
+ */
+ private QueryInterceptor[] queryInterceptors;
+
+ @SneakyThrows
+ @Override
+ public boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
+ QueryInterceptorExecutor.exec(queryInterceptors, executor, ms, parameter, rowBounds, resultHandler, boundSql);
+ return super.willDoQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/plugins/QueryInterceptorExecutor.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/plugins/QueryInterceptorExecutor.java
new file mode 100644
index 0000000..98f54cc
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/plugins/QueryInterceptorExecutor.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.mp.plugins;
+
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
+import org.springblade.core.mp.intercept.QueryInterceptor;
+import org.springblade.core.tool.utils.ObjectUtil;
+
+/**
+ * 鏌ヨ鎷︽埅鍣ㄦ墽琛屽櫒
+ *
+ * <p>
+ * 鐩殑锛氭娊鍙栨鏂规硶鏄负浜嗗悗鏈熸柟渚垮悓姝ユ洿鏂� {@link BladePaginationInterceptor}
+ * </p>
+ *
+ * @author L.cm
+ */
+@SuppressWarnings({"rawtypes"})
+public class QueryInterceptorExecutor {
+
+ /**
+ * 鎵ц鏌ヨ鎷︽埅鍣�
+ */
+ static void exec(QueryInterceptor[] interceptors, Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws Throwable {
+ if (ObjectUtil.isEmpty(interceptors)) {
+ return;
+ }
+ for (QueryInterceptor interceptor : interceptors) {
+ interceptor.intercept(executor, ms, parameter, rowBounds, resultHandler, boundSql);
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/plugins/SqlLogInterceptor.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/plugins/SqlLogInterceptor.java
new file mode 100644
index 0000000..234ab4c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/plugins/SqlLogInterceptor.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.mp.plugins;
+
+import com.alibaba.druid.DbType;
+import com.alibaba.druid.filter.FilterChain;
+import com.alibaba.druid.filter.FilterEventAdapter;
+import com.alibaba.druid.proxy.jdbc.JdbcParameter;
+import com.alibaba.druid.proxy.jdbc.ResultSetProxy;
+import com.alibaba.druid.proxy.jdbc.StatementProxy;
+import com.alibaba.druid.sql.SQLUtils;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.mp.props.MybatisPlusProperties;
+import org.springblade.core.tool.utils.StringUtil;
+
+import java.time.temporal.TemporalAccessor;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 鎵撳嵃鍙墽琛岀殑 sql 鏃ュ織
+ *
+ * @author L.cm锛孋hill
+ */
+@Slf4j
+public class SqlLogInterceptor extends FilterEventAdapter {
+ private static final SQLUtils.FormatOption FORMAT_OPTION = new SQLUtils.FormatOption(false, false);
+
+ private static final List<String> SQL_LOG_EXCLUDE = new ArrayList<>(Arrays.asList("ACT_RU_JOB", "ACT_RU_TIMER_JOB"));
+
+ private final MybatisPlusProperties properties;
+
+ public SqlLogInterceptor(MybatisPlusProperties properties) {
+ this.properties = properties;
+ if (properties.getSqlLogExclude().size() > 0) {
+ SQL_LOG_EXCLUDE.addAll(properties.getSqlLogExclude());
+ }
+ }
+
+ @Override
+ protected void statementExecuteBefore(StatementProxy statement, String sql) {
+ statement.setLastExecuteStartNano();
+ }
+
+ @Override
+ protected void statementExecuteBatchBefore(StatementProxy statement) {
+ statement.setLastExecuteStartNano();
+ }
+
+ @Override
+ protected void statementExecuteUpdateBefore(StatementProxy statement, String sql) {
+ statement.setLastExecuteStartNano();
+ }
+
+ @Override
+ protected void statementExecuteQueryBefore(StatementProxy statement, String sql) {
+ statement.setLastExecuteStartNano();
+ }
+
+ @Override
+ protected void statementExecuteAfter(StatementProxy statement, String sql, boolean firstResult) {
+ statement.setLastExecuteTimeNano();
+ }
+
+ @Override
+ protected void statementExecuteBatchAfter(StatementProxy statement, int[] result) {
+ statement.setLastExecuteTimeNano();
+ }
+
+ @Override
+ protected void statementExecuteQueryAfter(StatementProxy statement, String sql, ResultSetProxy resultSet) {
+ statement.setLastExecuteTimeNano();
+ }
+
+ @Override
+ protected void statementExecuteUpdateAfter(StatementProxy statement, String sql, int updateCount) {
+ statement.setLastExecuteTimeNano();
+ }
+
+ @Override
+ @SneakyThrows
+ public void statement_close(FilterChain chain, StatementProxy statement) {
+ // 鏄惁寮�鍚棩蹇�
+ if (!properties.getSqlLog()) {
+ chain.statement_close(statement);
+ return;
+ }
+ // 鏄惁寮�鍚皟璇�
+ if (!log.isInfoEnabled()) {
+ chain.statement_close(statement);
+ return;
+ }
+ // 鎵撳嵃鍙墽琛岀殑 sql
+ String sql = statement.getBatchSql();
+ // sql 涓虹┖鐩存帴杩斿洖
+ if (StringUtil.isEmpty(sql)) {
+ chain.statement_close(statement);
+ return;
+ }
+ // sql 鍖呭惈鎺掗櫎鐨勫叧閿瓧鐩存帴杩斿洖
+ if (excludeSql(sql)) {
+ chain.statement_close(statement);
+ return;
+ }
+ int parametersSize = statement.getParametersSize();
+ List<Object> parameters = new ArrayList<>(parametersSize);
+ for (int i = 0; i < parametersSize; ++i) {
+ // 杞崲鍙傛暟锛屽鐞� java8 鏃堕棿
+ parameters.add(getJdbcParameter(statement.getParameter(i)));
+ }
+ String dbType = statement.getConnectionProxy().getDirectDataSource().getDbType();
+ String formattedSql = SQLUtils.format(sql, DbType.of(dbType), parameters, FORMAT_OPTION);
+ printSql(formattedSql, statement);
+ chain.statement_close(statement);
+ }
+
+ private static Object getJdbcParameter(JdbcParameter jdbcParam) {
+ if (jdbcParam == null) {
+ return null;
+ }
+ Object value = jdbcParam.getValue();
+ // 澶勭悊 java8 鏃堕棿
+ if (value instanceof TemporalAccessor) {
+ return value.toString();
+ }
+ return value;
+ }
+
+ private static void printSql(String sql, StatementProxy statement) {
+ // 鎵撳嵃 sql
+ String sqlLogger = "\n\n============== Sql Start ==============" +
+ "\nExecute SQL : {}" +
+ "\nExecute Time: {}" +
+ "\n============== Sql End ==============\n";
+ log.info(sqlLogger, sql.trim(), StringUtil.format(statement.getLastExecuteTimeNano()));
+ }
+
+ private static boolean excludeSql(String sql) {
+ // 鍒ゆ柇鍏抽敭瀛�
+ for (String exclude : SQL_LOG_EXCLUDE) {
+ if (sql.contains(exclude)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/props/MybatisPlusProperties.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/props/MybatisPlusProperties.java
new file mode 100644
index 0000000..80a065c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/props/MybatisPlusProperties.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.mp.props;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * MybatisPlus閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@Data
+@ConfigurationProperties(prefix = "blade.mybatis-plus")
+public class MybatisPlusProperties {
+
+ /**
+ * 寮�鍚鎴锋ā寮�
+ */
+ private Boolean tenantMode = true;
+
+ /**
+ * 寮�鍚痵ql鏃ュ織
+ */
+ private Boolean sqlLog = true;
+
+ /**
+ * sql鏃ュ織蹇界暐鎵撳嵃鍏抽敭瀛�
+ */
+ private List<String> sqlLogExclude = new ArrayList<>();
+
+ /**
+ * 鍒嗛〉鏈�澶ф暟
+ */
+ private Long pageLimit = 500L;
+
+ /**
+ * 婧㈠嚭鎬婚〉鏁板悗鏄惁杩涜澶勭悊
+ */
+ protected Boolean overflow = false;
+
+ /**
+ * join浼樺寲
+ */
+ private Boolean optimizeJoin = false;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/resolver/PageArgumentResolver.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/resolver/PageArgumentResolver.java
new file mode 100644
index 0000000..0f173c6
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/resolver/PageArgumentResolver.java
@@ -0,0 +1,75 @@
+package org.springblade.core.mp.resolver;
+
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.springblade.core.tool.utils.ObjectUtil;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.core.MethodParameter;
+import org.springframework.lang.NonNull;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.ModelAndViewContainer;
+
+/**
+ * 瑙e喅 Mybatis Plus page SQL娉ㄥ叆闂
+ *
+ * @author L.cm
+ */
+public class PageArgumentResolver implements HandlerMethodArgumentResolver {
+ private static final String ORDER_ASC = "asc";
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return Page.class.equals(parameter.getParameterType());
+ }
+
+ /**
+ * page 鍙傛暟瑙f瀽
+ *
+ * @param parameter MethodParameter
+ * @param mavContainer ModelAndViewContainer
+ * @param request NativeWebRequest
+ * @param binderFactory WebDataBinderFactory
+ * @return 妫�鏌ュ悗鏂扮殑page瀵硅薄
+ */
+ @Override
+ public Object resolveArgument(@NonNull MethodParameter parameter, ModelAndViewContainer mavContainer,
+ NativeWebRequest request, WebDataBinderFactory binderFactory) {
+ // 鍒嗛〉鍙傛暟 page: 0, size: 10, sort=id%2Cdesc
+ String pageParam = request.getParameter("page");
+ String sizeParam = request.getParameter("size");
+ String[] sortParam = request.getParameterValues("sort");
+ Page<?> page = new Page<>();
+ if (StringUtil.isNotBlank(pageParam)) {
+ page.setCurrent(Long.parseLong(pageParam));
+ }
+ if (StringUtil.isNotBlank(sizeParam)) {
+ page.setSize(Long.parseLong(sizeParam));
+ }
+ if (ObjectUtil.isEmpty(sortParam)) {
+ return page;
+ }
+ for (String param : sortParam) {
+ if (StringUtil.isBlank(param)) {
+ continue;
+ }
+ String[] split = param.split(StringPool.COMMA);
+ // 娓呯悊瀛楃涓�
+ OrderItem orderItem = new OrderItem();
+ orderItem.setColumn(StringUtil.cleanIdentifier(split[0]));
+ orderItem.setAsc(isOrderAsc(split));
+ page.addOrder(orderItem);
+ }
+ return page;
+ }
+
+ private static boolean isOrderAsc(String[] split) {
+ // 榛樿 desc
+ if (split.length < 2) {
+ return false;
+ }
+ return ORDER_ASC.equalsIgnoreCase(split[1]);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/service/BladeService.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/service/BladeService.java
new file mode 100644
index 0000000..81b6b9e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/service/BladeService.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.mp.service;
+
+import org.springblade.core.mp.base.BaseService;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 鑷畾涔夌殑 Service
+ *
+ * @author L.cm, chill
+ */
+public interface BladeService<T> extends BaseService<T> {
+
+ /**
+ * 鎻掑叆濡傛灉涓凡缁忓瓨鍦ㄧ浉鍚岀殑璁板綍锛屽垯蹇界暐褰撳墠鏂版暟鎹�
+ *
+ * @param entity entity
+ * @return 鏄惁鎴愬姛
+ */
+ boolean saveIgnore(T entity);
+
+ /**
+ * 琛ㄧず鎻掑叆鏇挎崲鏁版嵁锛岄渶姹傝〃涓湁PrimaryKey锛屾垨鑰卽nique绱㈠紩锛屽鏋滄暟鎹簱宸茬粡瀛樺湪鏁版嵁锛屽垯鐢ㄦ柊鏁版嵁鏇挎崲锛屽鏋滄病鏈夋暟鎹晥鏋滃垯鍜宨nsert into涓�鏍凤紱
+ *
+ * @param entity entity
+ * @return 鏄惁鎴愬姛
+ */
+ boolean saveReplace(T entity);
+
+ /**
+ * 鎻掑叆锛堟壒閲忥級,鎻掑叆濡傛灉涓凡缁忓瓨鍦ㄧ浉鍚岀殑璁板綍锛屽垯蹇界暐褰撳墠鏂版暟鎹�
+ *
+ * @param entityList 瀹炰綋瀵硅薄闆嗗悎
+ * @param batchSize 鎵规澶у皬
+ * @return 鏄惁鎴愬姛
+ */
+ boolean saveIgnoreBatch(Collection<T> entityList, int batchSize);
+
+ /**
+ * 鎻掑叆锛堟壒閲忥級,鎻掑叆濡傛灉涓凡缁忓瓨鍦ㄧ浉鍚岀殑璁板綍锛屽垯蹇界暐褰撳墠鏂版暟鎹�
+ *
+ * @param entityList 瀹炰綋瀵硅薄闆嗗悎
+ * @return 鏄惁鎴愬姛
+ */
+ @Transactional(rollbackFor = Exception.class)
+ default boolean saveIgnoreBatch(Collection<T> entityList) {
+ return saveIgnoreBatch(entityList, 1000);
+ }
+
+ /**
+ * 鎻掑叆锛堟壒閲忥級,琛ㄧず鎻掑叆鏇挎崲鏁版嵁锛岄渶姹傝〃涓湁PrimaryKey锛屾垨鑰卽nique绱㈠紩锛屽鏋滄暟鎹簱宸茬粡瀛樺湪鏁版嵁锛屽垯鐢ㄦ柊鏁版嵁鏇挎崲锛屽鏋滄病鏈夋暟鎹晥鏋滃垯鍜宨nsert into涓�鏍凤紱
+ *
+ * @param entityList 瀹炰綋瀵硅薄闆嗗悎
+ * @param batchSize 鎵规澶у皬
+ * @return 鏄惁鎴愬姛
+ */
+ boolean saveReplaceBatch(Collection<T> entityList, int batchSize);
+
+ /**
+ * 鎻掑叆锛堟壒閲忥級,琛ㄧず鎻掑叆鏇挎崲鏁版嵁锛岄渶姹傝〃涓湁PrimaryKey锛屾垨鑰卽nique绱㈠紩锛屽鏋滄暟鎹簱宸茬粡瀛樺湪鏁版嵁锛屽垯鐢ㄦ柊鏁版嵁鏇挎崲锛屽鏋滄病鏈夋暟鎹晥鏋滃垯鍜宨nsert into涓�鏍凤紱
+ *
+ * @param entityList 瀹炰綋瀵硅薄闆嗗悎
+ * @return 鏄惁鎴愬姛
+ */
+ @Transactional(rollbackFor = Exception.class)
+ default boolean saveReplaceBatch(Collection<T> entityList) {
+ return saveReplaceBatch(entityList, 1000);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/service/impl/BladeServiceImpl.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/service/impl/BladeServiceImpl.java
new file mode 100644
index 0000000..e05bf52
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/service/impl/BladeServiceImpl.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.mp.service.impl;
+
+import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
+import org.springblade.core.mp.base.BaseEntity;
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.core.mp.injector.BladeSqlMethod;
+import org.springblade.core.mp.mapper.BladeMapper;
+import org.springblade.core.mp.service.BladeService;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.Collection;
+
+/**
+ * BladeService 瀹炵幇绫伙紙 娉涘瀷锛歁 鏄� mapper 瀵硅薄锛孴 鏄疄浣� 锛� PK 鏄富閿硾鍨� 锛�
+ *
+ * @author L.cm, chill
+ */
+@Validated
+public class BladeServiceImpl<M extends BladeMapper<T>, T extends BaseEntity> extends BaseServiceImpl<M, T> implements BladeService<T> {
+
+ @Override
+ public boolean saveIgnore(T entity) {
+ return SqlHelper.retBool(baseMapper.insertIgnore(entity));
+ }
+
+ @Override
+ public boolean saveReplace(T entity) {
+ return SqlHelper.retBool(baseMapper.replace(entity));
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public boolean saveIgnoreBatch(Collection<T> entityList, int batchSize) {
+ return saveBatch(entityList, batchSize, BladeSqlMethod.INSERT_IGNORE_ONE);
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public boolean saveReplaceBatch(Collection<T> entityList, int batchSize) {
+ return saveBatch(entityList, batchSize, BladeSqlMethod.REPLACE_ONE);
+ }
+
+ private boolean saveBatch(Collection<T> entityList, int batchSize, BladeSqlMethod sqlMethod) {
+ String sqlStatement = bladeSqlStatement(sqlMethod);
+ executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
+ return true;
+ }
+
+ /**
+ * 鑾峰彇 bladeSqlStatement
+ *
+ * @param sqlMethod ignore
+ * @return sql
+ */
+ protected String bladeSqlStatement(BladeSqlMethod sqlMethod) {
+ return SqlHelper.table(currentModelClass()).getSqlStatement(sqlMethod.getMethod());
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/BaseEntityWrapper.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/BaseEntityWrapper.java
new file mode 100644
index 0000000..02fafd8
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/BaseEntityWrapper.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.mp.support;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 瑙嗗浘鍖呰鍩虹被
+ *
+ * @author Chill
+ */
+public abstract class BaseEntityWrapper<E, V> {
+
+ /**
+ * 鍗曚釜瀹炰綋绫诲寘瑁�
+ *
+ * @param entity 瀹炰綋绫�
+ * @return V
+ */
+ public abstract V entityVO(E entity);
+
+ /**
+ * 瀹炰綋绫婚泦鍚堝寘瑁�
+ *
+ * @param list 瀹炰綋绫婚泦鍚�
+ * @return List<V>
+ */
+ public List<V> listVO(List<E> list) {
+ return list.stream().map(this::entityVO).collect(Collectors.toList());
+ }
+
+ /**
+ * 鍒嗛〉瀹炰綋绫婚泦鍚堝寘瑁�
+ *
+ * @param pages 鍒嗛〉瀵硅薄
+ * @return IPage<V>
+ */
+ public IPage<V> pageVO(IPage<E> pages) {
+ List<V> records = listVO(pages.getRecords());
+ IPage<V> pageVo = new Page<>(pages.getCurrent(), pages.getSize(), pages.getTotal());
+ pageVo.setRecords(records);
+ return pageVo;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/BladePage.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/BladePage.java
new file mode 100644
index 0000000..26b6735
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/BladePage.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.mp.support;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 鍒嗛〉妯″瀷
+ *
+ * @author Chill
+ */
+@Data
+public class BladePage<T> implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏌ヨ鏁版嵁鍒楄〃
+ */
+ private List<T> records = Collections.emptyList();
+
+ /**
+ * 鎬绘暟
+ */
+ private long total = 0;
+ /**
+ * 姣忛〉鏄剧ず鏉℃暟锛岄粯璁� 10
+ */
+ private long size = 10;
+
+ /**
+ * 褰撳墠椤�
+ */
+ private long current = 1;
+
+ /**
+ * mybatis-plus鍒嗛〉妯″瀷杞寲
+ *
+ * @param page 鍒嗛〉瀹炰綋绫�
+ * @return BladePage<T>
+ */
+ public static <T> BladePage<T> of(IPage<T> page) {
+ BladePage<T> bladePage = new BladePage<>();
+ bladePage.setRecords(page.getRecords());
+ bladePage.setTotal(page.getTotal());
+ bladePage.setSize(page.getSize());
+ bladePage.setCurrent(page.getCurrent());
+ return bladePage;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/Condition.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/Condition.java
new file mode 100644
index 0000000..0047a31
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/Condition.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.mp.support;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.springblade.core.launch.constant.TokenConstant;
+import org.springblade.core.tool.support.Kv;
+import org.springblade.core.tool.utils.BeanUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringUtil;
+
+import java.util.Map;
+
+/**
+ * 鍒嗛〉宸ュ叿
+ *
+ * @author Chill
+ */
+public class Condition {
+
+ /**
+ * 杞寲鎴恗ybatis plus涓殑Page
+ *
+ * @param query 鏌ヨ鏉′欢
+ * @return IPage
+ */
+ public static <T> IPage<T> getPage(Query query) {
+ Page<T> page = new Page<>(Func.toInt(query.getCurrent(), 1), Func.toInt(query.getSize(), 10));
+ String[] ascArr = Func.toStrArray(query.getAscs());
+ for (String asc : ascArr) {
+ page.addOrder(OrderItem.asc(StringUtil.cleanIdentifier(asc)));
+ }
+ String[] descArr = Func.toStrArray(query.getDescs());
+ for (String desc : descArr) {
+ page.addOrder(OrderItem.desc(StringUtil.cleanIdentifier(desc)));
+ }
+ return page;
+ }
+
+ /**
+ * 鑾峰彇mybatis plus涓殑QueryWrapper
+ *
+ * @param entity 瀹炰綋
+ * @param <T> 绫诲瀷
+ * @return QueryWrapper
+ */
+ public static <T> QueryWrapper<T> getQueryWrapper(T entity) {
+ return new QueryWrapper<>(entity);
+ }
+
+ /**
+ * 鑾峰彇mybatis plus涓殑QueryWrapper
+ *
+ * @param query 鏌ヨ鏉′欢
+ * @param clazz 瀹炰綋绫�
+ * @param <T> 绫诲瀷
+ * @return QueryWrapper
+ */
+ public static <T> QueryWrapper<T> getQueryWrapper(Map<String, Object> query, Class<T> clazz) {
+ Kv exclude = Kv.create().set(TokenConstant.HEADER, TokenConstant.HEADER)
+ .set("current", "current").set("size", "size").set("ascs", "ascs").set("descs", "descs");
+ return getQueryWrapper(query, exclude, clazz);
+ }
+
+ /**
+ * 鑾峰彇mybatis plus涓殑QueryWrapper
+ *
+ * @param query 鏌ヨ鏉′欢
+ * @param exclude 鎺掗櫎鐨勬煡璇㈡潯浠�
+ * @param clazz 瀹炰綋绫�
+ * @param <T> 绫诲瀷
+ * @return QueryWrapper
+ */
+ public static <T> QueryWrapper<T> getQueryWrapper(Map<String, Object> query, Map<String, Object> exclude, Class<T> clazz) {
+ exclude.forEach((k, v) -> query.remove(k));
+ QueryWrapper<T> qw = new QueryWrapper<>();
+ qw.setEntity(BeanUtil.newInstance(clazz));
+ SqlKeyword.buildCondition(query, qw);
+ return qw;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/Query.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/Query.java
new file mode 100644
index 0000000..ee745f0
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/Query.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.mp.support;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * 鍒嗛〉宸ュ叿
+ *
+ * @author Chill
+ */
+@Data
+@Accessors(chain = true)
+@ApiModel(description = "鏌ヨ鏉′欢")
+public class Query {
+
+ /**
+ * 褰撳墠椤�
+ */
+ @ApiModelProperty(value = "褰撳墠椤�")
+ private Integer current;
+
+ /**
+ * 姣忛〉鐨勬暟閲�
+ */
+ @ApiModelProperty(value = "姣忛〉鐨勬暟閲�")
+ private Integer size;
+
+ /**
+ * 姝f帓搴忚鍒�
+ */
+ @ApiModelProperty(hidden = true)
+ private String ascs;
+
+ /**
+ * 鍊掓帓搴忚鍒�
+ */
+ @ApiModelProperty(hidden = true)
+ private String descs;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/SqlKeyword.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/SqlKeyword.java
new file mode 100644
index 0000000..2627533
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/support/SqlKeyword.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.mp.support;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+
+import java.util.Map;
+
+/**
+ * 瀹氫箟甯哥敤鐨� sql鍏抽敭瀛�
+ *
+ * @author Chill
+ */
+public class SqlKeyword {
+ private final static String SQL_REGEX = "'|%|--|insert|delete|select|count|group|union|drop|truncate|alter|grant|execute|exec|xp_cmdshell|call|declare|sql";
+
+ private static final String EQUAL = "_equal";
+ private static final String NOT_EQUAL = "_notequal";
+ private static final String LIKE = "_like";
+ private static final String LIKE_LEFT = "_likeleft";
+ private static final String LIKE_RIGHT = "_likeright";
+ private static final String NOT_LIKE = "_notlike";
+ private static final String GE = "_ge";
+ private static final String LE = "_le";
+ private static final String GT = "_gt";
+ private static final String LT = "_lt";
+ private static final String DATE_GE = "_datege";
+ private static final String DATE_GT = "_dategt";
+ private static final String DATE_EQUAL = "_dateequal";
+ private static final String DATE_LT = "_datelt";
+ private static final String DATE_LE = "_datele";
+ private static final String IS_NULL = "_null";
+ private static final String NOT_NULL = "_notnull";
+ private static final String IGNORE = "_ignore";
+
+ /**
+ * 鏉′欢鏋勯�犲櫒
+ *
+ * @param query 鏌ヨ瀛楁
+ * @param qw 鏌ヨ鍖呰绫�
+ */
+ public static void buildCondition(Map<String, Object> query, QueryWrapper<?> qw) {
+ if (Func.isEmpty(query)) {
+ return;
+ }
+ query.forEach((k, v) -> {
+ if (Func.hasEmpty(k, v) || k.endsWith(IGNORE)) {
+ return;
+ }
+ if (k.endsWith(EQUAL)) {
+ qw.eq(getColumn(k, EQUAL), v);
+ } else if (k.endsWith(NOT_EQUAL)) {
+ qw.ne(getColumn(k, NOT_EQUAL), v);
+ } else if (k.endsWith(LIKE_LEFT)) {
+ qw.likeLeft(getColumn(k, LIKE_LEFT), v);
+ } else if (k.endsWith(LIKE_RIGHT)) {
+ qw.likeRight(getColumn(k, LIKE_RIGHT), v);
+ } else if (k.endsWith(NOT_LIKE)) {
+ qw.notLike(getColumn(k, NOT_LIKE), v);
+ } else if (k.endsWith(GE)) {
+ qw.ge(getColumn(k, GE), v);
+ } else if (k.endsWith(LE)) {
+ qw.le(getColumn(k, LE), v);
+ } else if (k.endsWith(GT)) {
+ qw.gt(getColumn(k, GT), v);
+ } else if (k.endsWith(LT)) {
+ qw.lt(getColumn(k, LT), v);
+ } else if (k.endsWith(DATE_GE)) {
+ qw.ge(getColumn(k, DATE_GE), DateUtil.parse(String.valueOf(v), DateUtil.PATTERN_DATETIME));
+ } else if (k.endsWith(DATE_GT)) {
+ qw.gt(getColumn(k, DATE_GT), DateUtil.parse(String.valueOf(v), DateUtil.PATTERN_DATETIME));
+ } else if (k.endsWith(DATE_EQUAL)) {
+ qw.eq(getColumn(k, DATE_EQUAL), DateUtil.parse(String.valueOf(v), DateUtil.PATTERN_DATETIME));
+ } else if (k.endsWith(DATE_LE)) {
+ qw.le(getColumn(k, DATE_LE), DateUtil.parse(String.valueOf(v), DateUtil.PATTERN_DATETIME));
+ } else if (k.endsWith(DATE_LT)) {
+ qw.lt(getColumn(k, DATE_LT), DateUtil.parse(String.valueOf(v), DateUtil.PATTERN_DATETIME));
+ } else if (k.endsWith(IS_NULL)) {
+ qw.isNull(getColumn(k, IS_NULL));
+ } else if (k.endsWith(NOT_NULL)) {
+ qw.isNotNull(getColumn(k, NOT_NULL));
+ } else {
+ qw.like(getColumn(k, LIKE), v);
+ }
+ });
+ }
+
+ /**
+ * 鑾峰彇鏁版嵁搴撳瓧娈�
+ *
+ * @param column 瀛楁鍚�
+ * @param keyword 鍏抽敭瀛�
+ * @return
+ */
+ private static String getColumn(String column, String keyword) {
+ return StringUtil.humpToUnderline(StringUtil.removeSuffix(column, keyword));
+ }
+
+ /**
+ * 鎶奡QL鍏抽敭瀛楁浛鎹负绌哄瓧绗︿覆
+ *
+ * @param param 鍏抽敭瀛�
+ * @return string
+ */
+ public static String filter(String param) {
+ if (param == null) {
+ return null;
+ }
+ return param.replaceAll("(?i)" + SQL_REGEX, StringPool.EMPTY);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/utils/PageUtil.java b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/utils/PageUtil.java
new file mode 100644
index 0000000..124eba3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/java/org/springblade/core/mp/utils/PageUtil.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.mp.utils;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.springblade.core.tool.utils.BeanUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * 鍒嗛〉宸ュ叿绫�
+ *
+ * @author L.cm
+ */
+public class PageUtil {
+
+ /**
+ * 2涓� IPage 杞� Page
+ *
+ * @param page IPage
+ * @param target 闇�瑕乧opy杞崲鐨勭被鍨�
+ * @param <T> 娉涘瀷
+ * @return PageResult
+ */
+ public static <T> Page<T> toPage(IPage<?> page, Class<T> target) {
+ List<T> records = BeanUtil.copy(page.getRecords(), target);
+ return toPage(page, records);
+ }
+
+ /**
+ * 2涓� IPage 杞� Page
+ *
+ * @param page IPage
+ * @param records 杞崲杩囩殑list妯″瀷
+ * @param <T> 娉涘瀷
+ * @return PageResult
+ */
+ public static <T> Page<T> toPage(IPage<?> page, List<T> records) {
+ Page<T> pageResult = new Page<>();
+ pageResult.setCurrent(page.getCurrent());
+ pageResult.setSize(page.getSize());
+ pageResult.setPages(page.getPages());
+ pageResult.setTotal(page.getTotal());
+ pageResult.setRecords(records);
+ return pageResult;
+ }
+
+ /**
+ * Page 杞崲
+ *
+ * @param page IPage
+ * @param function 杞崲杩囩殑鍑芥暟
+ * @param <T> 娉涘瀷
+ * @return PageResult
+ */
+ public static <T, R> Page<R> toPage(IPage<T> page, Function<T, R> function) {
+ List<R> records = new ArrayList<>();
+ for (T record : page.getRecords()) {
+ records.add(function.apply(record));
+ }
+ return toPage(page, records);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-mybatis/src/main/resources/blade-mybatis.yml b/Source/BladeX-Tool/blade-starter-mybatis/src/main/resources/blade-mybatis.yml
new file mode 100644
index 0000000..0b39b1c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-mybatis/src/main/resources/blade-mybatis.yml
@@ -0,0 +1,35 @@
+#澶氭暟鎹簮Sql鏃ュ織閰嶇疆
+spring:
+ datasource:
+ dynamic:
+ druid:
+ proxy-filters:
+ - sqlLogInterceptor
+
+#mybatis-plus閰嶇疆
+mybatis-plus:
+ mapper-locations: classpath:org/springblade/**/mapper/*Mapper.xml
+ #瀹炰綋鎵弿锛屽涓猵ackage鐢ㄩ�楀彿鎴栬�呭垎鍙峰垎闅�
+ typeAliasesPackage: org.springblade.**.entity
+ #typeEnumsPackage: org.springblade.dashboard.entity.enums
+ global-config:
+ # 鍏抽棴MP3.0鑷甫鐨刡anner
+ banner: false
+ db-config:
+ #涓婚敭绫诲瀷 0:"鏁版嵁搴揑D鑷", 1:"涓嶆搷浣�", 2:"鐢ㄦ埛杈撳叆ID",3:"鏁板瓧鍨媠nowflake", 4:"鍏ㄥ眬鍞竴ID UUID", 5:"瀛楃涓插瀷snowflake";
+ id-type: assign_id
+ #瀛楁绛栫暐
+ insert-strategy: not_null
+ update-strategy: not_null
+ where-strategy: not_null
+ #椹煎嘲涓嬪垝绾胯浆鎹�
+ table-underline: true
+ # 閫昏緫鍒犻櫎閰嶇疆
+ # 閫昏緫鍒犻櫎鍏ㄥ眬鍊硷紙1琛ㄧず宸插垹闄わ紝杩欎篃鏄疢ybatis Plus鐨勯粯璁ら厤缃級
+ logic-delete-value: 1
+ # 閫昏緫鏈垹闄ゅ叏灞�鍊硷紙0琛ㄧず鏈垹闄わ紝杩欎篃鏄疢ybatis Plus鐨勯粯璁ら厤缃級
+ logic-not-delete-value: 0
+ configuration:
+ map-underscore-to-camel-case: true
+ cache-enabled: false
+ jdbc-type-for-null: 'null'
diff --git a/Source/BladeX-Tool/blade-starter-oss/pom.xml b/Source/BladeX-Tool/blade-starter-oss/pom.xml
new file mode 100644
index 0000000..7d65994
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/pom.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-oss</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-secure</artifactId>
+ </dependency>
+ <!--AliOss-->
+ <dependency>
+ <groupId>com.aliyun.oss</groupId>
+ <artifactId>aliyun-sdk-oss</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!--MinIO-->
+ <dependency>
+ <groupId>io.minio</groupId>
+ <artifactId>minio</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!--QiNiu-->
+ <dependency>
+ <groupId>com.qiniu</groupId>
+ <artifactId>qiniu-java-sdk</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!--鑵捐COS-->
+ <dependency>
+ <groupId>com.qcloud</groupId>
+ <artifactId>cos_api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!--鍗庝负浜慜bs-->
+ <dependency>
+ <groupId>com.huaweicloud</groupId>
+ <artifactId>esdk-obs-java</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/AliossTemplate.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/AliossTemplate.java
new file mode 100644
index 0000000..62d78f3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/AliossTemplate.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss;
+
+import com.aliyun.oss.OSSClient;
+import com.aliyun.oss.common.utils.BinaryUtil;
+import com.aliyun.oss.model.MatchMode;
+import com.aliyun.oss.model.ObjectMetadata;
+import com.aliyun.oss.model.PolicyConditions;
+import com.aliyun.oss.model.PutObjectResult;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.springblade.core.oss.model.BladeFile;
+import org.springblade.core.oss.model.OssFile;
+import org.springblade.core.oss.props.OssProperties;
+import org.springblade.core.oss.rule.OssRule;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.utils.StringPool;
+import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * AliossTemplate
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+public class AliossTemplate implements OssTemplate {
+ private final OSSClient ossClient;
+ private final OssProperties ossProperties;
+ private final OssRule ossRule;
+
+ @Override
+ @SneakyThrows
+ public void makeBucket(String bucketName) {
+ if (!bucketExists(bucketName)) {
+ ossClient.createBucket(getBucketName(bucketName));
+ }
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeBucket(String bucketName) {
+ ossClient.deleteBucket(getBucketName(bucketName));
+ }
+
+ @Override
+ @SneakyThrows
+ public boolean bucketExists(String bucketName) {
+ return ossClient.doesBucketExist(getBucketName(bucketName));
+ }
+
+ @Override
+ @SneakyThrows
+ public void copyFile(String bucketName, String fileName, String destBucketName) {
+ ossClient.copyObject(getBucketName(bucketName), fileName, getBucketName(destBucketName), fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public void copyFile(String bucketName, String fileName, String destBucketName, String destFileName) {
+ ossClient.copyObject(getBucketName(bucketName), fileName, getBucketName(destBucketName), destFileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public OssFile statFile(String fileName) {
+ return statFile(ossProperties.getBucketName(), fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public OssFile statFile(String bucketName, String fileName) {
+ ObjectMetadata stat = ossClient.getObjectMetadata(getBucketName(bucketName), fileName);
+ OssFile ossFile = new OssFile();
+ ossFile.setName(fileName);
+ ossFile.setLink(fileLink(ossFile.getName()));
+ ossFile.setHash(stat.getContentMD5());
+ ossFile.setLength(stat.getContentLength());
+ ossFile.setPutTime(stat.getLastModified());
+ ossFile.setContentType(stat.getContentType());
+ return ossFile;
+ }
+
+ @Override
+ @SneakyThrows
+ public String filePath(String fileName) {
+ return getOssHost().concat(StringPool.SLASH).concat(fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public String filePath(String bucketName, String fileName) {
+ return getOssHost(bucketName).concat(StringPool.SLASH).concat(fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public String fileLink(String fileName) {
+ return getOssHost().concat(StringPool.SLASH).concat(fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public String fileLink(String bucketName, String fileName) {
+ return getOssHost(bucketName).concat(StringPool.SLASH).concat(fileName);
+ }
+
+ /**
+ * 鏂囦欢瀵硅薄
+ *
+ * @param file 涓婁紶鏂囦欢绫�
+ * @return
+ */
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(MultipartFile file) {
+ return putFile(ossProperties.getBucketName(), file.getOriginalFilename(), file);
+ }
+
+ /**
+ * @param fileName 涓婁紶鏂囦欢鍚�
+ * @param file 涓婁紶鏂囦欢绫�
+ * @return
+ */
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(String fileName, MultipartFile file) {
+ return putFile(ossProperties.getBucketName(), fileName, file);
+ }
+
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(String bucketName, String fileName, MultipartFile file) {
+ return putFile(bucketName, fileName, file.getInputStream());
+ }
+
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(String fileName, InputStream stream) {
+ return putFile(ossProperties.getBucketName(), fileName, stream);
+ }
+
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(String bucketName, String fileName, InputStream stream) {
+ return put(bucketName, stream, fileName, false);
+ }
+
+ @SneakyThrows
+ public BladeFile put(String bucketName, InputStream stream, String key, boolean cover) {
+ makeBucket(bucketName);
+ String originalName = key;
+ key = getFileName(key);
+ // 瑕嗙洊涓婁紶
+ if (cover) {
+ ossClient.putObject(getBucketName(bucketName), key, stream);
+ } else {
+ PutObjectResult response = ossClient.putObject(getBucketName(bucketName), key, stream);
+ int retry = 0;
+ int retryCount = 5;
+ while (StringUtils.isEmpty(response.getETag()) && retry < retryCount) {
+ response = ossClient.putObject(getBucketName(bucketName), key, stream);
+ retry++;
+ }
+ }
+ BladeFile file = new BladeFile();
+ file.setOriginalName(originalName);
+ file.setName(key);
+ file.setDomain(getOssHost(bucketName));
+ file.setLink(fileLink(bucketName, key));
+ return file;
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeFile(String fileName) {
+ ossClient.deleteObject(getBucketName(), fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeFile(String bucketName, String fileName) {
+ ossClient.deleteObject(getBucketName(bucketName), fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeFiles(List<String> fileNames) {
+ fileNames.forEach(this::removeFile);
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeFiles(String bucketName, List<String> fileNames) {
+ fileNames.forEach(fileName -> removeFile(getBucketName(bucketName), fileName));
+ }
+
+ /**
+ * 鏍规嵁瑙勫垯鐢熸垚瀛樺偍妗跺悕绉拌鍒�
+ *
+ * @return String
+ */
+ private String getBucketName() {
+ return getBucketName(ossProperties.getBucketName());
+ }
+
+ /**
+ * 鏍规嵁瑙勫垯鐢熸垚瀛樺偍妗跺悕绉拌鍒�
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @return String
+ */
+ private String getBucketName(String bucketName) {
+ return ossRule.bucketName(bucketName);
+ }
+
+ /**
+ * 鏍规嵁瑙勫垯鐢熸垚鏂囦欢鍚嶇О瑙勫垯
+ *
+ * @param originalFilename 鍘熷鏂囦欢鍚�
+ * @return string
+ */
+ private String getFileName(String originalFilename) {
+ return ossRule.fileName(originalFilename);
+ }
+
+ public String getUploadToken() {
+ return getUploadToken(ossProperties.getBucketName());
+ }
+
+ /**
+ * TODO 杩囨湡鏃堕棿
+ * <p>
+ * 鑾峰彇涓婁紶鍑瘉锛屾櫘閫氫笂浼�
+ */
+ public String getUploadToken(String bucketName) {
+ // 榛樿杩囨湡鏃堕棿2灏忔椂
+ return getUploadToken(bucketName, ossProperties.getArgs().get("expireTime", 3600L));
+ }
+
+ /**
+ * TODO 涓婁紶澶у皬闄愬埗銆佸熀纭�璺緞
+ * <p>
+ * 鑾峰彇涓婁紶鍑瘉锛屾櫘閫氫笂浼�
+ */
+ public String getUploadToken(String bucketName, long expireTime) {
+ String baseDir = "upload";
+
+ long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
+ Date expiration = new Date(expireEndTime);
+
+ PolicyConditions policyConds = new PolicyConditions();
+ // 榛樿澶у皬闄愬埗10M
+ policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, ossProperties.getArgs().get("contentLengthRange", 10485760));
+ policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, baseDir);
+
+ String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
+ byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);
+ String encodedPolicy = BinaryUtil.toBase64String(binaryData);
+ String postSignature = ossClient.calculatePostSignature(postPolicy);
+
+ Map<String, String> respMap = new LinkedHashMap<>(16);
+ respMap.put("accessid", ossProperties.getAccessKey());
+ respMap.put("policy", encodedPolicy);
+ respMap.put("signature", postSignature);
+ respMap.put("dir", baseDir);
+ respMap.put("host", getOssHost(bucketName));
+ respMap.put("expire", String.valueOf(expireEndTime / 1000));
+ return JsonUtil.toJson(respMap);
+ }
+
+ /**
+ * 鑾峰彇鍩熷悕
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @return String
+ */
+ public String getOssHost(String bucketName) {
+ String prefix = ossProperties.getEndpoint().contains("https://") ? "https://" : "http://";
+ return prefix + getBucketName(bucketName) + StringPool.DOT + ossProperties.getEndpoint().replaceFirst(prefix, StringPool.EMPTY);
+ }
+
+ /**
+ * 鑾峰彇鍩熷悕
+ *
+ * @return String
+ */
+ public String getOssHost() {
+ return getOssHost(ossProperties.getBucketName());
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/HuaweiObsTemplate.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/HuaweiObsTemplate.java
new file mode 100644
index 0000000..4f92ea7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/HuaweiObsTemplate.java
@@ -0,0 +1,225 @@
+package org.springblade.core.oss;
+
+import com.obs.services.ObsClient;
+import com.obs.services.model.ObjectMetadata;
+import com.obs.services.model.PutObjectResult;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.springblade.core.oss.model.BladeFile;
+import org.springblade.core.oss.model.OssFile;
+import org.springblade.core.oss.props.OssProperties;
+import org.springblade.core.oss.rule.OssRule;
+import org.springblade.core.tool.utils.StringPool;
+import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * @author Tonny
+ */
+@AllArgsConstructor
+public class HuaweiObsTemplate implements OssTemplate {
+
+ private final ObsClient obsClient;
+ private final OssProperties ossProperties;
+ private final OssRule ossRule;
+
+ @Override
+ public void makeBucket(String bucketName) {
+ if (!bucketExists(bucketName)) {
+ obsClient.createBucket(getBucketName(bucketName));
+ }
+ }
+
+ @Override
+ public void removeBucket(String bucketName) {
+ obsClient.deleteBucket(getBucketName(bucketName));
+ }
+
+ @Override
+ public boolean bucketExists(String bucketName) {
+ return obsClient.headBucket(getBucketName(bucketName));
+ }
+
+ @Override
+ public void copyFile(String bucketName, String fileName, String destBucketName) {
+ obsClient.copyObject(getBucketName(bucketName), fileName, getBucketName(destBucketName), fileName);
+ }
+
+ @Override
+ public void copyFile(String bucketName, String fileName, String destBucketName, String destFileName) {
+ obsClient.copyObject(getBucketName(bucketName), fileName, getBucketName(destBucketName), destFileName);
+ }
+
+ @Override
+ public OssFile statFile(String fileName) {
+ return statFile(ossProperties.getBucketName(), fileName);
+ }
+
+ @Override
+ public OssFile statFile(String bucketName, String fileName) {
+ ObjectMetadata stat = obsClient.getObjectMetadata(getBucketName(bucketName), fileName);
+ OssFile ossFile = new OssFile();
+ ossFile.setName(fileName);
+ ossFile.setLink(fileLink(ossFile.getName()));
+ ossFile.setHash(stat.getContentMd5());
+ ossFile.setLength(stat.getContentLength());
+ ossFile.setPutTime(stat.getLastModified());
+ ossFile.setContentType(stat.getContentType());
+ return ossFile;
+ }
+
+ @Override
+ public String filePath(String fileName) {
+ return getOssHost(getBucketName()).concat(StringPool.SLASH).concat(fileName);
+ }
+
+ @Override
+ public String filePath(String bucketName, String fileName) {
+ return getOssHost(getBucketName(bucketName)).concat(StringPool.SLASH).concat(fileName);
+ }
+
+ @Override
+ public String fileLink(String fileName) {
+ return getOssHost().concat(StringPool.SLASH).concat(fileName);
+ }
+
+ @Override
+ public String fileLink(String bucketName, String fileName) {
+ return getOssHost(getBucketName(bucketName)).concat(StringPool.SLASH).concat(fileName);
+ }
+
+ @Override
+ public BladeFile putFile(MultipartFile file) {
+ return putFile(ossProperties.getBucketName(), file.getOriginalFilename(), file);
+ }
+
+ @Override
+ public BladeFile putFile(String fileName, MultipartFile file) {
+ return putFile(ossProperties.getBucketName(), fileName, file);
+ }
+
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(String bucketName, String fileName, MultipartFile file) {
+ return putFile(bucketName, fileName, file.getInputStream());
+ }
+
+ @Override
+ public BladeFile putFile(String fileName, InputStream stream) {
+ return putFile(ossProperties.getBucketName(), fileName, stream);
+ }
+
+ @Override
+ public BladeFile putFile(String bucketName, String fileName, InputStream stream) {
+ return put(bucketName, stream, fileName, false);
+ }
+
+ @Override
+ public void removeFile(String fileName) {
+ obsClient.deleteObject(getBucketName(), fileName);
+ }
+
+ @Override
+ public void removeFile(String bucketName, String fileName) {
+ obsClient.deleteObject(getBucketName(bucketName), fileName);
+ }
+
+ @Override
+ public void removeFiles(List<String> fileNames) {
+ fileNames.forEach(this::removeFile);
+ }
+
+ @Override
+ public void removeFiles(String bucketName, List<String> fileNames) {
+ fileNames.forEach(fileName -> removeFile(getBucketName(bucketName), fileName));
+ }
+
+ /**
+ * 涓婁紶鏂囦欢娴�
+ *
+ * @param bucketName
+ * @param stream
+ * @param key
+ * @param cover
+ * @return
+ */
+ @SneakyThrows
+ public BladeFile put(String bucketName, InputStream stream, String key, boolean cover) {
+ makeBucket(bucketName);
+
+ String originalName = key;
+
+ key = getFileName(key);
+
+ // 瑕嗙洊涓婁紶
+ if (cover) {
+ obsClient.putObject(getBucketName(bucketName), key, stream);
+ } else {
+ PutObjectResult response = obsClient.putObject(getBucketName(bucketName), key, stream);
+ int retry = 0;
+ int retryCount = 5;
+ while (StringUtils.isEmpty(response.getEtag()) && retry < retryCount) {
+ response = obsClient.putObject(getBucketName(bucketName), key, stream);
+ retry++;
+ }
+ }
+
+ BladeFile file = new BladeFile();
+ file.setOriginalName(originalName);
+ file.setName(key);
+ file.setLink(fileLink(bucketName, key));
+ return file;
+ }
+
+ /**
+ * 鏍规嵁瑙勫垯鐢熸垚鏂囦欢鍚嶇О瑙勫垯
+ *
+ * @param originalFilename 鍘熷鏂囦欢鍚�
+ * @return string
+ */
+ private String getFileName(String originalFilename) {
+ return ossRule.fileName(originalFilename);
+ }
+
+ /**
+ * 鏍规嵁瑙勫垯鐢熸垚瀛樺偍妗跺悕绉拌鍒� 鍗曠鎴�
+ *
+ * @return String
+ */
+ private String getBucketName() {
+ return getBucketName(ossProperties.getBucketName());
+ }
+
+ /**
+ * 鏍规嵁瑙勫垯鐢熸垚瀛樺偍妗跺悕绉拌鍒� 澶氱鎴�
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @return String
+ */
+ private String getBucketName(String bucketName) {
+ return ossRule.bucketName(bucketName);
+ }
+
+ /**
+ * 鑾峰彇鍩熷悕
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @return String
+ */
+ public String getOssHost(String bucketName) {
+ String prefix = ossProperties.getEndpoint().contains("https://") ? "https://" : "http://";
+ return prefix + getBucketName(bucketName) + StringPool.DOT + ossProperties.getEndpoint().replaceFirst(prefix, StringPool.EMPTY);
+ }
+
+ /**
+ * 鑾峰彇鍩熷悕
+ *
+ * @return String
+ */
+ public String getOssHost() {
+ return getOssHost(ossProperties.getBucketName());
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/MinioTemplate.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/MinioTemplate.java
new file mode 100644
index 0000000..1bc828a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/MinioTemplate.java
@@ -0,0 +1,418 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss;
+
+import io.minio.*;
+import io.minio.http.Method;
+import io.minio.messages.Bucket;
+import io.minio.messages.DeleteObject;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.springblade.core.oss.enums.PolicyType;
+import org.springblade.core.oss.model.BladeFile;
+import org.springblade.core.oss.model.OssFile;
+import org.springblade.core.oss.props.OssProperties;
+import org.springblade.core.oss.rule.OssRule;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringPool;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.InputStream;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/**
+ * MinIOTemplate
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+public class MinioTemplate implements OssTemplate {
+
+ /**
+ * MinIO瀹㈡埛绔�
+ */
+ private final MinioClient client;
+
+ /**
+ * 瀛樺偍妗跺懡鍚嶈鍒�
+ */
+ private final OssRule ossRule;
+
+ /**
+ * 閰嶇疆绫�
+ */
+ private final OssProperties ossProperties;
+
+
+ @Override
+ @SneakyThrows
+ public void makeBucket(String bucketName) {
+ if (
+ !client.bucketExists(
+ BucketExistsArgs.builder().bucket(getBucketName(bucketName)).build()
+ )
+ ) {
+ client.makeBucket(
+ MakeBucketArgs.builder().bucket(getBucketName(bucketName)).build()
+ );
+ client.setBucketPolicy(
+ SetBucketPolicyArgs.builder().bucket(getBucketName(bucketName)).config(getPolicyType(getBucketName(bucketName), PolicyType.READ)).build()
+ );
+ }
+ }
+
+ @SneakyThrows
+ public Bucket getBucket() {
+ return getBucket(getBucketName());
+ }
+
+ @SneakyThrows
+ public Bucket getBucket(String bucketName) {
+ Optional<Bucket> bucketOptional = client.listBuckets().stream().filter(bucket -> bucket.name().equals(getBucketName(bucketName))).findFirst();
+ return bucketOptional.orElse(null);
+ }
+
+ @SneakyThrows
+ public List<Bucket> listBuckets() {
+ return client.listBuckets();
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeBucket(String bucketName) {
+ client.removeBucket(
+ RemoveBucketArgs.builder().bucket(getBucketName(bucketName)).build()
+ );
+ }
+
+ @Override
+ @SneakyThrows
+ public boolean bucketExists(String bucketName) {
+ return client.bucketExists(
+ BucketExistsArgs.builder().bucket(getBucketName(bucketName)).build()
+ );
+ }
+
+ @Override
+ @SneakyThrows
+ public void copyFile(String bucketName, String fileName, String destBucketName) {
+ copyFile(bucketName, fileName, destBucketName, fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public void copyFile(String bucketName, String fileName, String destBucketName, String destFileName) {
+ client.copyObject(
+ CopyObjectArgs.builder()
+ .source(CopySource.builder().bucket(getBucketName(bucketName)).object(fileName).build())
+ .bucket(getBucketName(destBucketName))
+ .object(destFileName)
+ .build()
+ );
+ }
+
+ @Override
+ @SneakyThrows
+ public OssFile statFile(String fileName) {
+ return statFile(ossProperties.getBucketName(), fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public OssFile statFile(String bucketName, String fileName) {
+ StatObjectResponse stat = client.statObject(
+ StatObjectArgs.builder().bucket(getBucketName(bucketName)).object(fileName).build()
+ );
+ OssFile ossFile = new OssFile();
+ ossFile.setName(Func.isEmpty(stat.object()) ? fileName : stat.object());
+ ossFile.setLink(fileLink(ossFile.getName()));
+ ossFile.setHash(String.valueOf(stat.hashCode()));
+ ossFile.setLength(stat.size());
+ ossFile.setPutTime(DateUtil.toDate(stat.lastModified().toLocalDateTime()));
+ ossFile.setContentType(stat.contentType());
+ return ossFile;
+ }
+
+ @Override
+ public String filePath(String fileName) {
+ return getBucketName().concat(StringPool.SLASH).concat(fileName);
+ }
+
+ @Override
+ public String filePath(String bucketName, String fileName) {
+ return getBucketName(bucketName).concat(StringPool.SLASH).concat(fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public String fileLink(String fileName) {
+ return ossProperties.getEndpoint().concat(StringPool.SLASH).concat(getBucketName()).concat(StringPool.SLASH).concat(fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public String fileLink(String bucketName, String fileName) {
+ return ossProperties.getEndpoint().concat(StringPool.SLASH).concat(getBucketName(bucketName)).concat(StringPool.SLASH).concat(fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(MultipartFile file) {
+ return putFile(ossProperties.getBucketName(), file.getOriginalFilename(), file);
+ }
+
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(String fileName, MultipartFile file) {
+ return putFile(ossProperties.getBucketName(), fileName, file);
+ }
+
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(String bucketName, String fileName, MultipartFile file) {
+ return putFile(bucketName, file.getOriginalFilename(), file.getInputStream());
+ }
+
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(String fileName, InputStream stream) {
+ return putFile(ossProperties.getBucketName(), fileName, stream);
+ }
+
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(String bucketName, String fileName, InputStream stream) {
+ return putFile(bucketName, fileName, stream, "application/octet-stream");
+ }
+
+ @SneakyThrows
+ public BladeFile putFile(String bucketName, String fileName, InputStream stream, String contentType) {
+ makeBucket(bucketName);
+ String originalName = fileName;
+ fileName = getFileName(fileName);
+ client.putObject(
+ PutObjectArgs.builder()
+ .bucket(getBucketName(bucketName))
+ .object(fileName)
+ .stream(stream, stream.available(), -1)
+ .contentType(contentType)
+ .build()
+ );
+ BladeFile file = new BladeFile();
+ file.setOriginalName(originalName);
+ file.setName(fileName);
+ file.setDomain(getOssHost(bucketName));
+ file.setLink(fileLink(bucketName, fileName));
+ return file;
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeFile(String fileName) {
+ removeFile(ossProperties.getBucketName(), fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeFile(String bucketName, String fileName) {
+ client.removeObject(
+ RemoveObjectArgs.builder().bucket(getBucketName(bucketName)).object(fileName).build()
+ );
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeFiles(List<String> fileNames) {
+ removeFiles(ossProperties.getBucketName(), fileNames);
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeFiles(String bucketName, List<String> fileNames) {
+ Stream<DeleteObject> stream = fileNames.stream().map(DeleteObject::new);
+ client.removeObjects(RemoveObjectsArgs.builder().bucket(getBucketName(bucketName)).objects(stream::iterator).build());
+ }
+
+ /**
+ * 鏍规嵁瑙勫垯鐢熸垚瀛樺偍妗跺悕绉拌鍒�
+ *
+ * @return String
+ */
+ private String getBucketName() {
+ return getBucketName(ossProperties.getBucketName());
+ }
+
+ /**
+ * 鏍规嵁瑙勫垯鐢熸垚瀛樺偍妗跺悕绉拌鍒�
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @return String
+ */
+ private String getBucketName(String bucketName) {
+ return ossRule.bucketName(bucketName);
+ }
+
+ /**
+ * 鏍规嵁瑙勫垯鐢熸垚鏂囦欢鍚嶇О瑙勫垯
+ *
+ * @param originalFilename 鍘熷鏂囦欢鍚�
+ * @return string
+ */
+ private String getFileName(String originalFilename) {
+ return ossRule.fileName(originalFilename);
+ }
+
+ /**
+ * 鑾峰彇鏂囦欢澶栭摼
+ *
+ * @param bucketName bucket鍚嶇О
+ * @param fileName 鏂囦欢鍚嶇О
+ * @param expires 杩囨湡鏃堕棿 <=7 绉掔骇
+ * @return url
+ */
+ @SneakyThrows
+ public String getPresignedObjectUrl(String bucketName, String fileName, Integer expires) {
+ return client.getPresignedObjectUrl(
+ GetPresignedObjectUrlArgs.builder()
+ .method(Method.GET)
+ .bucket(getBucketName(bucketName))
+ .object(fileName)
+ .expiry(expires)
+ .build()
+ );
+ }
+
+ /**
+ * 鑾峰彇瀛樺偍妗剁瓥鐣�
+ *
+ * @param policyType 绛栫暐鏋氫妇
+ * @return String
+ */
+ public String getPolicyType(PolicyType policyType) {
+ return getPolicyType(getBucketName(), policyType);
+ }
+
+ /**
+ * 鑾峰彇瀛樺偍妗剁瓥鐣�
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @param policyType 绛栫暐鏋氫妇
+ * @return String
+ */
+ public static String getPolicyType(String bucketName, PolicyType policyType) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("{\n");
+ builder.append(" \"Statement\": [\n");
+ builder.append(" {\n");
+ builder.append(" \"Action\": [\n");
+
+ switch (policyType) {
+ case WRITE:
+ builder.append(" \"s3:GetBucketLocation\",\n");
+ builder.append(" \"s3:ListBucketMultipartUploads\"\n");
+ break;
+ case READ_WRITE:
+ builder.append(" \"s3:GetBucketLocation\",\n");
+ builder.append(" \"s3:ListBucket\",\n");
+ builder.append(" \"s3:ListBucketMultipartUploads\"\n");
+ break;
+ default:
+ builder.append(" \"s3:GetBucketLocation\"\n");
+ break;
+ }
+
+ builder.append(" ],\n");
+ builder.append(" \"Effect\": \"Allow\",\n");
+ builder.append(" \"Principal\": \"*\",\n");
+ builder.append(" \"Resource\": \"arn:aws:s3:::");
+ builder.append(bucketName);
+ builder.append("\"\n");
+ builder.append(" },\n");
+ if (PolicyType.READ.equals(policyType)) {
+ builder.append(" {\n");
+ builder.append(" \"Action\": [\n");
+ builder.append(" \"s3:ListBucket\"\n");
+ builder.append(" ],\n");
+ builder.append(" \"Effect\": \"Deny\",\n");
+ builder.append(" \"Principal\": \"*\",\n");
+ builder.append(" \"Resource\": \"arn:aws:s3:::");
+ builder.append(bucketName);
+ builder.append("\"\n");
+ builder.append(" },\n");
+
+ }
+ builder.append(" {\n");
+ builder.append(" \"Action\": ");
+
+ switch (policyType) {
+ case WRITE:
+ builder.append("[\n");
+ builder.append(" \"s3:AbortMultipartUpload\",\n");
+ builder.append(" \"s3:DeleteObject\",\n");
+ builder.append(" \"s3:ListMultipartUploadParts\",\n");
+ builder.append(" \"s3:PutObject\"\n");
+ builder.append(" ],\n");
+ break;
+ case READ_WRITE:
+ builder.append("[\n");
+ builder.append(" \"s3:AbortMultipartUpload\",\n");
+ builder.append(" \"s3:DeleteObject\",\n");
+ builder.append(" \"s3:GetObject\",\n");
+ builder.append(" \"s3:ListMultipartUploadParts\",\n");
+ builder.append(" \"s3:PutObject\"\n");
+ builder.append(" ],\n");
+ break;
+ default:
+ builder.append("\"s3:GetObject\",\n");
+ break;
+ }
+
+ builder.append(" \"Effect\": \"Allow\",\n");
+ builder.append(" \"Principal\": \"*\",\n");
+ builder.append(" \"Resource\": \"arn:aws:s3:::");
+ builder.append(bucketName);
+ builder.append("/*\"\n");
+ builder.append(" }\n");
+ builder.append(" ],\n");
+ builder.append(" \"Version\": \"2012-10-17\"\n");
+ builder.append("}\n");
+ return builder.toString();
+ }
+
+ /**
+ * 鑾峰彇鍩熷悕
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @return String
+ */
+ public String getOssHost(String bucketName) {
+ return ossProperties.getEndpoint() + StringPool.SLASH + getBucketName(bucketName);
+ }
+
+ /**
+ * 鑾峰彇鍩熷悕
+ *
+ * @return String
+ */
+ public String getOssHost() {
+ return getOssHost(ossProperties.getBucketName());
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/OssTemplate.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/OssTemplate.java
new file mode 100644
index 0000000..a65b4cf
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/OssTemplate.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss;
+
+
+import org.springblade.core.oss.model.BladeFile;
+import org.springblade.core.oss.model.OssFile;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * OssTemplate鎶借薄API
+ *
+ * @author Chill
+ */
+public interface OssTemplate {
+
+ /**
+ * 鍒涘缓 瀛樺偍妗�
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ */
+ void makeBucket(String bucketName);
+
+ /**
+ * 鍒犻櫎 瀛樺偍妗�
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ */
+ void removeBucket(String bucketName);
+
+ /**
+ * 瀛樺偍妗舵槸鍚﹀瓨鍦�
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @return boolean
+ */
+ boolean bucketExists(String bucketName);
+
+ /**
+ * 鎷疯礉鏂囦欢
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @param fileName 瀛樺偍妗舵枃浠跺悕绉�
+ * @param destBucketName 鐩爣瀛樺偍妗跺悕绉�
+ */
+ void copyFile(String bucketName, String fileName, String destBucketName);
+
+ /**
+ * 鎷疯礉鏂囦欢
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @param fileName 瀛樺偍妗舵枃浠跺悕绉�
+ * @param destBucketName 鐩爣瀛樺偍妗跺悕绉�
+ * @param destFileName 鐩爣瀛樺偍妗舵枃浠跺悕绉�
+ */
+ void copyFile(String bucketName, String fileName, String destBucketName, String destFileName);
+
+ /**
+ * 鑾峰彇鏂囦欢淇℃伅
+ *
+ * @param fileName 瀛樺偍妗舵枃浠跺悕绉�
+ * @return InputStream
+ */
+ OssFile statFile(String fileName);
+
+ /**
+ * 鑾峰彇鏂囦欢淇℃伅
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @param fileName 瀛樺偍妗舵枃浠跺悕绉�
+ * @return InputStream
+ */
+ OssFile statFile(String bucketName, String fileName);
+
+ /**
+ * 鑾峰彇鏂囦欢鐩稿璺緞
+ *
+ * @param fileName 瀛樺偍妗跺璞″悕绉�
+ * @return String
+ */
+ String filePath(String fileName);
+
+ /**
+ * 鑾峰彇鏂囦欢鐩稿璺緞
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @param fileName 瀛樺偍妗跺璞″悕绉�
+ * @return String
+ */
+ String filePath(String bucketName, String fileName);
+
+ /**
+ * 鑾峰彇鏂囦欢鍦板潃
+ *
+ * @param fileName 瀛樺偍妗跺璞″悕绉�
+ * @return String
+ */
+ String fileLink(String fileName);
+
+ /**
+ * 鑾峰彇鏂囦欢鍦板潃
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @param fileName 瀛樺偍妗跺璞″悕绉�
+ * @return String
+ */
+ String fileLink(String bucketName, String fileName);
+
+ /**
+ * 涓婁紶鏂囦欢
+ *
+ * @param file 涓婁紶鏂囦欢绫�
+ * @return BladeFile
+ */
+ BladeFile putFile(MultipartFile file);
+
+ /**
+ * 涓婁紶鏂囦欢
+ *
+ * @param file 涓婁紶鏂囦欢绫�
+ * @param fileName 涓婁紶鏂囦欢鍚�
+ * @return BladeFile
+ */
+ BladeFile putFile(String fileName, MultipartFile file);
+
+ /**
+ * 涓婁紶鏂囦欢
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @param fileName 涓婁紶鏂囦欢鍚�
+ * @param file 涓婁紶鏂囦欢绫�
+ * @return BladeFile
+ */
+ BladeFile putFile(String bucketName, String fileName, MultipartFile file);
+
+ /**
+ * 涓婁紶鏂囦欢
+ *
+ * @param fileName 瀛樺偍妗跺璞″悕绉�
+ * @param stream 鏂囦欢娴�
+ * @return BladeFile
+ */
+ BladeFile putFile(String fileName, InputStream stream);
+
+ /**
+ * 涓婁紶鏂囦欢
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @param fileName 瀛樺偍妗跺璞″悕绉�
+ * @param stream 鏂囦欢娴�
+ * @return BladeFile
+ */
+ BladeFile putFile(String bucketName, String fileName, InputStream stream);
+
+ /**
+ * 鍒犻櫎鏂囦欢
+ *
+ * @param fileName 瀛樺偍妗跺璞″悕绉�
+ */
+ void removeFile(String fileName);
+
+ /**
+ * 鍒犻櫎鏂囦欢
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @param fileName 瀛樺偍妗跺璞″悕绉�
+ */
+ void removeFile(String bucketName, String fileName);
+
+ /**
+ * 鎵归噺鍒犻櫎鏂囦欢
+ *
+ * @param fileNames 瀛樺偍妗跺璞″悕绉伴泦鍚�
+ */
+ void removeFiles(List<String> fileNames);
+
+ /**
+ * 鎵归噺鍒犻櫎鏂囦欢
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @param fileNames 瀛樺偍妗跺璞″悕绉伴泦鍚�
+ */
+ void removeFiles(String bucketName, List<String> fileNames);
+
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/QiniuTemplate.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/QiniuTemplate.java
new file mode 100644
index 0000000..865c0fd
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/QiniuTemplate.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss;
+
+import com.qiniu.common.Zone;
+import com.qiniu.http.Response;
+import com.qiniu.storage.BucketManager;
+import com.qiniu.storage.UploadManager;
+import com.qiniu.storage.model.FileInfo;
+import com.qiniu.util.Auth;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.springblade.core.oss.model.BladeFile;
+import org.springblade.core.oss.model.OssFile;
+import org.springblade.core.oss.props.OssProperties;
+import org.springblade.core.oss.rule.OssRule;
+import org.springblade.core.tool.utils.CollectionUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringPool;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.InputStream;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * QiniuTemplate
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+public class QiniuTemplate implements OssTemplate {
+ private final Auth auth;
+ private final UploadManager uploadManager;
+ private final BucketManager bucketManager;
+ private final OssProperties ossProperties;
+ private final OssRule ossRule;
+
+ @Override
+ @SneakyThrows
+ public void makeBucket(String bucketName) {
+ if (!CollectionUtil.contains(bucketManager.buckets(), getBucketName(bucketName))) {
+ bucketManager.createBucket(getBucketName(bucketName), Zone.autoZone().getRegion());
+ }
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeBucket(String bucketName) {
+
+ }
+
+ @Override
+ @SneakyThrows
+ public boolean bucketExists(String bucketName) {
+ return CollectionUtil.contains(bucketManager.buckets(), getBucketName(bucketName));
+ }
+
+ @Override
+ @SneakyThrows
+ public void copyFile(String bucketName, String fileName, String destBucketName) {
+ bucketManager.copy(getBucketName(bucketName), fileName, getBucketName(destBucketName), fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public void copyFile(String bucketName, String fileName, String destBucketName, String destFileName) {
+ bucketManager.copy(getBucketName(bucketName), fileName, getBucketName(destBucketName), destFileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public OssFile statFile(String fileName) {
+ return statFile(ossProperties.getBucketName(), fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public OssFile statFile(String bucketName, String fileName) {
+ FileInfo stat = bucketManager.stat(getBucketName(bucketName), fileName);
+ OssFile ossFile = new OssFile();
+ ossFile.setName(Func.isEmpty(stat.key) ? fileName : stat.key);
+ ossFile.setLink(fileLink(ossFile.getName()));
+ ossFile.setHash(stat.hash);
+ ossFile.setLength(stat.fsize);
+ ossFile.setPutTime(new Date(stat.putTime / 10000));
+ ossFile.setContentType(stat.mimeType);
+ return ossFile;
+ }
+
+ @Override
+ @SneakyThrows
+ public String filePath(String fileName) {
+ return getBucketName().concat(StringPool.SLASH).concat(fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public String filePath(String bucketName, String fileName) {
+ return getBucketName(bucketName).concat(StringPool.SLASH).concat(fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public String fileLink(String fileName) {
+ return ossProperties.getEndpoint().concat(StringPool.SLASH).concat(fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public String fileLink(String bucketName, String fileName) {
+ return ossProperties.getEndpoint().concat(StringPool.SLASH).concat(fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(MultipartFile file) {
+ return putFile(ossProperties.getBucketName(), file.getOriginalFilename(), file);
+ }
+
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(String fileName, MultipartFile file) {
+ return putFile(ossProperties.getBucketName(), fileName, file);
+ }
+
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(String bucketName, String fileName, MultipartFile file) {
+ return putFile(bucketName, fileName, file.getInputStream());
+ }
+
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(String fileName, InputStream stream) {
+ return putFile(ossProperties.getBucketName(), fileName, stream);
+ }
+
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(String bucketName, String fileName, InputStream stream) {
+ return put(bucketName, stream, fileName, false);
+ }
+
+ @SneakyThrows
+ public BladeFile put(String bucketName, InputStream stream, String key, boolean cover) {
+ makeBucket(bucketName);
+ String originalName = key;
+ key = getFileName(key);
+ // 瑕嗙洊涓婁紶
+ if (cover) {
+ uploadManager.put(stream, key, getUploadToken(bucketName, key), null, null);
+ } else {
+ Response response = uploadManager.put(stream, key, getUploadToken(bucketName), null, null);
+ int retry = 0;
+ int retryCount = 5;
+ while (response.needRetry() && retry < retryCount) {
+ response = uploadManager.put(stream, key, getUploadToken(bucketName), null, null);
+ retry++;
+ }
+ }
+ BladeFile file = new BladeFile();
+ file.setOriginalName(originalName);
+ file.setName(key);
+ file.setDomain(getOssHost());
+ file.setLink(fileLink(bucketName, key));
+ return file;
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeFile(String fileName) {
+ bucketManager.delete(getBucketName(), fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeFile(String bucketName, String fileName) {
+ bucketManager.delete(getBucketName(bucketName), fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeFiles(List<String> fileNames) {
+ fileNames.forEach(this::removeFile);
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeFiles(String bucketName, List<String> fileNames) {
+ fileNames.forEach(fileName -> removeFile(getBucketName(bucketName), fileName));
+ }
+
+ /**
+ * 鏍规嵁瑙勫垯鐢熸垚瀛樺偍妗跺悕绉拌鍒�
+ *
+ * @return String
+ */
+ private String getBucketName() {
+ return getBucketName(ossProperties.getBucketName());
+ }
+
+ /**
+ * 鏍规嵁瑙勫垯鐢熸垚瀛樺偍妗跺悕绉拌鍒�
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @return String
+ */
+ private String getBucketName(String bucketName) {
+ return ossRule.bucketName(bucketName);
+ }
+
+ /**
+ * 鏍规嵁瑙勫垯鐢熸垚鏂囦欢鍚嶇О瑙勫垯
+ *
+ * @param originalFilename 鍘熷鏂囦欢鍚�
+ * @return string
+ */
+ private String getFileName(String originalFilename) {
+ return ossRule.fileName(originalFilename);
+ }
+
+ /**
+ * 鑾峰彇涓婁紶鍑瘉锛屾櫘閫氫笂浼�
+ */
+ public String getUploadToken(String bucketName) {
+ return auth.uploadToken(getBucketName(bucketName));
+ }
+
+ /**
+ * 鑾峰彇涓婁紶鍑瘉锛岃鐩栦笂浼�
+ */
+ private String getUploadToken(String bucketName, String key) {
+ return auth.uploadToken(getBucketName(bucketName), key);
+ }
+
+ /**
+ * 鑾峰彇鍩熷悕
+ *
+ * @return String
+ */
+ public String getOssHost() {
+ return ossProperties.getEndpoint();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/TencentCosTemplate.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/TencentCosTemplate.java
new file mode 100644
index 0000000..50d0afc
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/TencentCosTemplate.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss;
+
+import com.qcloud.cos.COSClient;
+import com.qcloud.cos.model.CannedAccessControlList;
+import com.qcloud.cos.model.ObjectMetadata;
+import com.qcloud.cos.model.PutObjectResult;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.springblade.core.oss.model.BladeFile;
+import org.springblade.core.oss.model.OssFile;
+import org.springblade.core.oss.props.OssProperties;
+import org.springblade.core.oss.rule.OssRule;
+import org.springblade.core.tool.utils.StringPool;
+import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * <p>
+ * 鑵捐浜� COS 鎿嶄綔
+ * </p>
+ *
+ * @author yangkai.shen
+ * @date Created in 2020/1/7 17:24
+ */
+@AllArgsConstructor
+public class TencentCosTemplate implements OssTemplate {
+ private final COSClient cosClient;
+ private final OssProperties ossProperties;
+ private final OssRule ossRule;
+
+ @Override
+ @SneakyThrows
+ public void makeBucket(String bucketName) {
+ if (!bucketExists(bucketName)) {
+ cosClient.createBucket(getBucketName(bucketName));
+ // TODO: 鏉冮檺鏄惁闇�瑕佷慨鏀逛负绉佹湁锛屽綋鍓嶄负 鍏湁璇汇�佺鏈夊啓
+ cosClient.setBucketAcl(getBucketName(bucketName), CannedAccessControlList.PublicRead);
+ }
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeBucket(String bucketName) {
+ cosClient.deleteBucket(getBucketName(bucketName));
+ }
+
+ @Override
+ @SneakyThrows
+ public boolean bucketExists(String bucketName) {
+ return cosClient.doesBucketExist(getBucketName(bucketName));
+ }
+
+ @Override
+ @SneakyThrows
+ public void copyFile(String bucketName, String fileName, String destBucketName) {
+ cosClient.copyObject(getBucketName(bucketName), fileName, getBucketName(destBucketName), fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public void copyFile(String bucketName, String fileName, String destBucketName, String destFileName) {
+ cosClient.copyObject(getBucketName(bucketName), fileName, getBucketName(destBucketName), destFileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public OssFile statFile(String fileName) {
+ return statFile(ossProperties.getBucketName(), fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public OssFile statFile(String bucketName, String fileName) {
+ ObjectMetadata stat = cosClient.getObjectMetadata(getBucketName(bucketName), fileName);
+ OssFile ossFile = new OssFile();
+ ossFile.setName(fileName);
+ ossFile.setLink(fileLink(ossFile.getName()));
+ ossFile.setHash(stat.getContentMD5());
+ ossFile.setLength(stat.getContentLength());
+ ossFile.setPutTime(stat.getLastModified());
+ ossFile.setContentType(stat.getContentType());
+ return ossFile;
+ }
+
+ @Override
+ @SneakyThrows
+ public String filePath(String fileName) {
+ return getOssHost().concat(StringPool.SLASH).concat(fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public String filePath(String bucketName, String fileName) {
+ return getOssHost(bucketName).concat(StringPool.SLASH).concat(fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public String fileLink(String fileName) {
+ return getOssHost().concat(StringPool.SLASH).concat(fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public String fileLink(String bucketName, String fileName) {
+ return getOssHost(bucketName).concat(StringPool.SLASH).concat(fileName);
+ }
+
+ /**
+ * 鏂囦欢瀵硅薄
+ *
+ * @param file 涓婁紶鏂囦欢绫�
+ * @return
+ */
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(MultipartFile file) {
+ return putFile(ossProperties.getBucketName(), file.getOriginalFilename(), file);
+ }
+
+ /**
+ * @param fileName 涓婁紶鏂囦欢鍚�
+ * @param file 涓婁紶鏂囦欢绫�
+ * @return
+ */
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(String fileName, MultipartFile file) {
+ return putFile(ossProperties.getBucketName(), fileName, file);
+ }
+
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(String bucketName, String fileName, MultipartFile file) {
+ return putFile(bucketName, fileName, file.getInputStream());
+ }
+
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(String fileName, InputStream stream) {
+ return putFile(ossProperties.getBucketName(), fileName, stream);
+ }
+
+ @Override
+ @SneakyThrows
+ public BladeFile putFile(String bucketName, String fileName, InputStream stream) {
+ return put(bucketName, stream, fileName, false);
+ }
+
+ @SneakyThrows
+ public BladeFile put(String bucketName, InputStream stream, String key, boolean cover) {
+ makeBucket(bucketName);
+ String originalName = key;
+ key = getFileName(key);
+ // 璁剧疆娴佺殑闀垮害閬垮厤oom
+ ObjectMetadata objectMetadata = new ObjectMetadata();
+ objectMetadata.setContentLength(500);
+ // 瑕嗙洊涓婁紶
+ if (cover) {
+ cosClient.putObject(getBucketName(bucketName), key, stream, objectMetadata);
+ } else {
+ PutObjectResult response = cosClient.putObject(getBucketName(bucketName), key, stream, objectMetadata);
+ int retry = 0;
+ int retryCount = 5;
+ while (StringUtils.isEmpty(response.getETag()) && retry < retryCount) {
+ response = cosClient.putObject(getBucketName(bucketName), key, stream, objectMetadata);
+ retry++;
+ }
+ }
+ BladeFile file = new BladeFile();
+ file.setOriginalName(originalName);
+ file.setName(key);
+ file.setDomain(getOssHost(bucketName));
+ file.setLink(fileLink(bucketName, key));
+ return file;
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeFile(String fileName) {
+ cosClient.deleteObject(getBucketName(), fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeFile(String bucketName, String fileName) {
+ cosClient.deleteObject(getBucketName(bucketName), fileName);
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeFiles(List<String> fileNames) {
+ fileNames.forEach(this::removeFile);
+ }
+
+ @Override
+ @SneakyThrows
+ public void removeFiles(String bucketName, List<String> fileNames) {
+ fileNames.forEach(fileName -> removeFile(getBucketName(bucketName), fileName));
+ }
+
+ /**
+ * 鏍规嵁瑙勫垯鐢熸垚瀛樺偍妗跺悕绉拌鍒�
+ *
+ * @return String
+ */
+ private String getBucketName() {
+ return getBucketName(ossProperties.getBucketName());
+ }
+
+ /**
+ * 鏍规嵁瑙勫垯鐢熸垚瀛樺偍妗跺悕绉拌鍒�
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @return String
+ */
+ private String getBucketName(String bucketName) {
+ return ossRule.bucketName(bucketName).concat(StringPool.DASH).concat(ossProperties.getAppId());
+ }
+
+ /**
+ * 鏍规嵁瑙勫垯鐢熸垚鏂囦欢鍚嶇О瑙勫垯
+ *
+ * @param originalFilename 鍘熷鏂囦欢鍚�
+ * @return string
+ */
+ private String getFileName(String originalFilename) {
+ return ossRule.fileName(originalFilename);
+ }
+
+ /**
+ * 鑾峰彇鍩熷悕
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @return String
+ */
+ public String getOssHost(String bucketName) {
+ return "http://" + cosClient.getClientConfig().getEndpointBuilder().buildGeneralApiEndpoint(getBucketName(bucketName));
+ }
+
+ /**
+ * 鑾峰彇鍩熷悕
+ *
+ * @return String
+ */
+ public String getOssHost() {
+ return getOssHost(ossProperties.getBucketName());
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/AliossConfiguration.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/AliossConfiguration.java
new file mode 100644
index 0000000..fef0a57
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/AliossConfiguration.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss.config;
+
+import com.aliyun.oss.ClientConfiguration;
+import com.aliyun.oss.OSSClient;
+import com.aliyun.oss.common.auth.CredentialsProvider;
+import com.aliyun.oss.common.auth.DefaultCredentialProvider;
+import lombok.AllArgsConstructor;
+import org.springblade.core.oss.AliossTemplate;
+import org.springblade.core.oss.props.OssProperties;
+import org.springblade.core.oss.rule.OssRule;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * Alioss閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+@AutoConfiguration(after = OssConfiguration.class)
+@EnableConfigurationProperties(OssProperties.class)
+@ConditionalOnClass({OSSClient.class})
+@ConditionalOnProperty(value = "oss.name", havingValue = "alioss")
+public class AliossConfiguration {
+
+ private final OssProperties ossProperties;
+ private final OssRule ossRule;
+
+ @Bean
+ @ConditionalOnMissingBean(OSSClient.class)
+ public OSSClient ossClient() {
+ // 鍒涘缓ClientConfiguration銆侰lientConfiguration鏄疧SSClient鐨勯厤缃被锛屽彲閰嶇疆浠g悊銆佽繛鎺ヨ秴鏃躲�佹渶澶ц繛鎺ユ暟绛夊弬鏁般��
+ ClientConfiguration conf = new ClientConfiguration();
+ // 璁剧疆OSSClient鍏佽鎵撳紑鐨勬渶澶TTP杩炴帴鏁帮紝榛樿涓�1024涓��
+ conf.setMaxConnections(1024);
+ // 璁剧疆Socket灞備紶杈撴暟鎹殑瓒呮椂鏃堕棿锛岄粯璁や负50000姣銆�
+ conf.setSocketTimeout(50000);
+ // 璁剧疆寤虹珛杩炴帴鐨勮秴鏃舵椂闂达紝榛樿涓�50000姣銆�
+ conf.setConnectionTimeout(50000);
+ // 璁剧疆浠庤繛鎺ユ睜涓幏鍙栬繛鎺ョ殑瓒呮椂鏃堕棿锛堝崟浣嶏細姣锛夛紝榛樿涓嶈秴鏃躲��
+ conf.setConnectionRequestTimeout(1000);
+ // 璁剧疆杩炴帴绌洪棽瓒呮椂鏃堕棿銆傝秴鏃跺垯鍏抽棴杩炴帴锛岄粯璁や负60000姣銆�
+ conf.setIdleConnectionTime(60000);
+ // 璁剧疆澶辫触璇锋眰閲嶈瘯娆℃暟锛岄粯璁や负3娆°��
+ conf.setMaxErrorRetry(5);
+ CredentialsProvider credentialsProvider = new DefaultCredentialProvider(ossProperties.getAccessKey(), ossProperties.getSecretKey());
+ return new OSSClient(ossProperties.getEndpoint(), credentialsProvider, conf);
+ }
+
+ @Bean
+ @ConditionalOnBean({OSSClient.class})
+ @ConditionalOnMissingBean(AliossTemplate.class)
+ public AliossTemplate aliossTemplate(OSSClient ossClient) {
+ return new AliossTemplate(ossClient, ossProperties, ossRule);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/HuaweiObsConfiguration.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/HuaweiObsConfiguration.java
new file mode 100644
index 0000000..6cd5ca4
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/HuaweiObsConfiguration.java
@@ -0,0 +1,65 @@
+package org.springblade.core.oss.config;
+
+import com.aliyun.oss.OSSClient;
+import com.obs.services.ObsClient;
+import com.obs.services.ObsConfiguration;
+import lombok.AllArgsConstructor;
+import org.springblade.core.oss.HuaweiObsTemplate;
+import org.springblade.core.oss.props.OssProperties;
+import org.springblade.core.oss.rule.BladeOssRule;
+import org.springblade.core.oss.rule.OssRule;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * @author Tonny
+ */
+@AllArgsConstructor
+@AutoConfiguration(after = OssConfiguration.class)
+@EnableConfigurationProperties(OssProperties.class)
+@ConditionalOnClass({OSSClient.class})
+@ConditionalOnProperty(value = "oss.name", havingValue = "huaweiobs")
+public class HuaweiObsConfiguration {
+ private final OssProperties ossProperties;
+
+ @Bean
+ @ConditionalOnMissingBean(OssRule.class)
+ public OssRule ossRule() {
+ return new BladeOssRule(ossProperties.getTenantMode());
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(ObsClient.class)
+ public ObsClient ossClient() {
+ // 浣跨敤鍙畾鍒跺悇鍙傛暟鐨勯厤缃被锛圤bsConfiguration锛夊垱寤篛BS瀹㈡埛绔紙ObsClient锛夛紝鍒涘缓瀹屾垚鍚庝笉鏀寔鍐嶆淇敼鍙傛暟
+ ObsConfiguration conf = new ObsConfiguration ();
+
+ conf.setEndPoint(ossProperties.getEndpoint());
+ // 璁剧疆OSSClient鍏佽鎵撳紑鐨勬渶澶TTP杩炴帴鏁帮紝榛樿涓�1024涓��
+ conf.setMaxConnections(1024);
+ // 璁剧疆Socket灞備紶杈撴暟鎹殑瓒呮椂鏃堕棿锛岄粯璁や负50000姣銆�
+ conf.setSocketTimeout(50000);
+ // 璁剧疆寤虹珛杩炴帴鐨勮秴鏃舵椂闂达紝榛樿涓�50000姣銆�
+ conf.setConnectionTimeout(50000);
+ // 璁剧疆浠庤繛鎺ユ睜涓幏鍙栬繛鎺ョ殑瓒呮椂鏃堕棿锛堝崟浣嶏細姣锛夛紝榛樿涓嶈秴鏃躲��
+ conf.setConnectionRequestTimeout(1000);
+ // 璁剧疆杩炴帴绌洪棽瓒呮椂鏃堕棿銆傝秴鏃跺垯鍏抽棴杩炴帴锛岄粯璁や负60000姣銆�
+ conf.setIdleConnectionTime(60000);
+ // 璁剧疆澶辫触璇锋眰閲嶈瘯娆℃暟锛岄粯璁や负3娆°��
+ conf.setMaxErrorRetry(5);
+
+ return new ObsClient(ossProperties.getAccessKey(), ossProperties.getSecretKey(), conf);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(HuaweiObsTemplate.class)
+ @ConditionalOnBean({ObsClient.class, OssRule.class})
+ public HuaweiObsTemplate huaweiobsTemplate(ObsClient ossClient, OssRule ossRule) {
+ return new HuaweiObsTemplate(ossClient, ossProperties, ossRule);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/MinioConfiguration.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/MinioConfiguration.java
new file mode 100644
index 0000000..5dd7ab0
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/MinioConfiguration.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss.config;
+
+import io.minio.MinioClient;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.springblade.core.oss.MinioTemplate;
+import org.springblade.core.oss.props.OssProperties;
+import org.springblade.core.oss.rule.OssRule;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * Minio閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+@AutoConfiguration(after = OssConfiguration.class)
+@ConditionalOnClass({MinioClient.class})
+@EnableConfigurationProperties(OssProperties.class)
+@ConditionalOnProperty(value = "oss.name", havingValue = "minio")
+public class MinioConfiguration {
+
+ private final OssProperties ossProperties;
+ private final OssRule ossRule;
+
+
+ @Bean
+ @SneakyThrows
+ @ConditionalOnMissingBean(MinioClient.class)
+ public MinioClient minioClient() {
+ return MinioClient.builder()
+ .endpoint(ossProperties.getEndpoint())
+ .credentials(ossProperties.getAccessKey(), ossProperties.getSecretKey())
+ .build();
+ }
+
+ @Bean
+ @ConditionalOnBean({MinioClient.class})
+ @ConditionalOnMissingBean(MinioTemplate.class)
+ public MinioTemplate minioTemplate(MinioClient minioClient) {
+ return new MinioTemplate(minioClient, ossRule, ossProperties);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/OssConfiguration.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/OssConfiguration.java
new file mode 100644
index 0000000..fd4fda4
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/OssConfiguration.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss.config;
+
+import lombok.AllArgsConstructor;
+import org.springblade.core.oss.props.OssProperties;
+import org.springblade.core.oss.rule.BladeOssRule;
+import org.springblade.core.oss.rule.OssRule;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * Oss閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@AutoConfiguration
+@AllArgsConstructor
+@EnableConfigurationProperties(OssProperties.class)
+public class OssConfiguration {
+
+ private final OssProperties ossProperties;
+
+ @Bean
+ @ConditionalOnMissingBean(OssRule.class)
+ public OssRule ossRule() {
+ return new BladeOssRule(ossProperties.getTenantMode());
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/QiniuConfiguration.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/QiniuConfiguration.java
new file mode 100644
index 0000000..cc39b32
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/QiniuConfiguration.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss.config;
+
+import com.qiniu.storage.BucketManager;
+import com.qiniu.storage.Region;
+import com.qiniu.storage.UploadManager;
+import com.qiniu.util.Auth;
+import lombok.AllArgsConstructor;
+import org.springblade.core.oss.QiniuTemplate;
+import org.springblade.core.oss.props.OssProperties;
+import org.springblade.core.oss.rule.OssRule;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * Qiniu閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+@AutoConfiguration(after = OssConfiguration.class)
+@ConditionalOnClass({Auth.class, UploadManager.class, BucketManager.class})
+@EnableConfigurationProperties(OssProperties.class)
+@ConditionalOnProperty(value = "oss.name", havingValue = "qiniu")
+public class QiniuConfiguration {
+
+ private final OssProperties ossProperties;
+ private final OssRule ossRule;
+
+ @Bean
+ @ConditionalOnMissingBean(com.qiniu.storage.Configuration.class)
+ public com.qiniu.storage.Configuration qnConfiguration() {
+ return new com.qiniu.storage.Configuration(Region.autoRegion());
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(Auth.class)
+ public Auth auth() {
+ return Auth.create(ossProperties.getAccessKey(), ossProperties.getSecretKey());
+ }
+
+ @Bean
+ @ConditionalOnBean(com.qiniu.storage.Configuration.class)
+ public UploadManager uploadManager(com.qiniu.storage.Configuration cfg) {
+ return new UploadManager(cfg);
+ }
+
+ @Bean
+ @ConditionalOnBean(com.qiniu.storage.Configuration.class)
+ public BucketManager bucketManager(com.qiniu.storage.Configuration cfg) {
+ return new BucketManager(Auth.create(ossProperties.getAccessKey(), ossProperties.getSecretKey()), cfg);
+ }
+
+ @Bean
+ @ConditionalOnBean({Auth.class, UploadManager.class, BucketManager.class})
+ @ConditionalOnMissingBean(QiniuTemplate.class)
+ public QiniuTemplate qiniuTemplate(Auth auth, UploadManager uploadManager, BucketManager bucketManager) {
+ return new QiniuTemplate(auth, uploadManager, bucketManager, ossProperties, ossRule);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/TencentCosConfiguration.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/TencentCosConfiguration.java
new file mode 100644
index 0000000..010c7fd
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/config/TencentCosConfiguration.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss.config;
+
+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.region.Region;
+import lombok.AllArgsConstructor;
+import org.springblade.core.oss.TencentCosTemplate;
+import org.springblade.core.oss.props.OssProperties;
+import org.springblade.core.oss.rule.OssRule;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * <p>
+ * 鑵捐浜� COS 鑷姩瑁呴厤
+ * </p>
+ *
+ * @author yangkai.shen
+ * @date Created in 2020/1/7 17:24
+ */
+@AllArgsConstructor
+@AutoConfiguration(after = OssConfiguration.class)
+@ConditionalOnClass({COSClient.class})
+@EnableConfigurationProperties(OssProperties.class)
+@ConditionalOnProperty(value = "oss.name", havingValue = "tencentcos")
+public class TencentCosConfiguration {
+
+ private final OssProperties ossProperties;
+ private final OssRule ossRule;
+
+
+ @Bean
+ @ConditionalOnMissingBean(COSClient.class)
+ public COSClient ossClient() {
+ // 鍒濆鍖栫敤鎴疯韩浠戒俊鎭紙secretId, secretKey锛�
+ COSCredentials credentials = new BasicCOSCredentials(ossProperties.getAccessKey(), ossProperties.getSecretKey());
+ // 璁剧疆 bucket 鐨勫尯鍩�, COS 鍦板煙鐨勭畝绉拌鍙傜収 https://cloud.tencent.com/document/product/436/6224
+ Region region = new Region(ossProperties.getRegion());
+ // clientConfig 涓寘鍚簡璁剧疆 region, https(榛樿 http), 瓒呮椂, 浠g悊绛� set 鏂规硶, 浣跨敤鍙弬瑙佹簮鐮佹垨鑰呭父瑙侀棶棰� Java SDK 閮ㄥ垎銆�
+ ClientConfig clientConfig = new ClientConfig(region);
+ // 璁剧疆OSSClient鍏佽鎵撳紑鐨勬渶澶TTP杩炴帴鏁帮紝榛樿涓�1024涓��
+ clientConfig.setMaxConnectionsCount(1024);
+ // 璁剧疆Socket灞備紶杈撴暟鎹殑瓒呮椂鏃堕棿锛岄粯璁や负50000姣銆�
+ clientConfig.setSocketTimeout(50000);
+ // 璁剧疆寤虹珛杩炴帴鐨勮秴鏃舵椂闂达紝榛樿涓�50000姣銆�
+ clientConfig.setConnectionTimeout(50000);
+ // 璁剧疆浠庤繛鎺ユ睜涓幏鍙栬繛鎺ョ殑瓒呮椂鏃堕棿锛堝崟浣嶏細姣锛夛紝榛樿涓嶈秴鏃躲��
+ clientConfig.setConnectionRequestTimeout(1000);
+ return new COSClient(credentials, clientConfig);
+ }
+
+ @Bean
+ @ConditionalOnBean({COSClient.class})
+ @ConditionalOnMissingBean(TencentCosTemplate.class)
+ public TencentCosTemplate tencentCosTemplate(COSClient cosClient) {
+ return new TencentCosTemplate(cosClient, ossProperties, ossRule);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/enums/OssEnum.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/enums/OssEnum.java
new file mode 100644
index 0000000..791ac29
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/enums/OssEnum.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Oss鏋氫妇绫�
+ *
+ * @author Chill
+ */
+@Getter
+@AllArgsConstructor
+public enum OssEnum {
+
+ /**
+ * minio
+ */
+ MINIO("minio", 1),
+
+ /**
+ * qiniu
+ */
+ QINIU("qiniu", 2),
+
+ /**
+ * ali
+ */
+ ALI("ali", 3),
+
+ /**
+ * tencent
+ */
+ TENCENT("tencent", 4),
+
+ /**
+ * huawei
+ */
+ HUAWEI("huawei", 5);
+
+ /**
+ * 鍚嶇О
+ */
+ final String name;
+ /**
+ * 绫诲瀷
+ */
+ final int category;
+
+ /**
+ * 鍖归厤鏋氫妇鍊�
+ *
+ * @param name 鍚嶇О
+ * @return OssEnum
+ */
+ public static OssEnum of(String name) {
+ if (name == null) {
+ return null;
+ }
+ OssEnum[] values = OssEnum.values();
+ for (OssEnum ossEnum : values) {
+ if (ossEnum.name.equals(name)) {
+ return ossEnum;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/enums/OssStatusEnum.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/enums/OssStatusEnum.java
new file mode 100644
index 0000000..5bf190a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/enums/OssStatusEnum.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Oss绫诲瀷鏋氫妇
+ *
+ * @author Chill
+ */
+@Getter
+@AllArgsConstructor
+public enum OssStatusEnum {
+
+ /**
+ * 鍏抽棴
+ */
+ DISABLE(1),
+ /**
+ * 鍚敤
+ */
+ ENABLE(2),
+ ;
+
+ /**
+ * 绫诲瀷缂栧彿
+ */
+ final int num;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/enums/PolicyType.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/enums/PolicyType.java
new file mode 100644
index 0000000..956a871
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/enums/PolicyType.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * minio绛栫暐閰嶇疆
+ *
+ * @author SCMOX
+ */
+@Getter
+@AllArgsConstructor
+public enum PolicyType {
+
+ /**
+ * 鍙
+ */
+ READ("read", "鍙"),
+
+ /**
+ * 鍙啓
+ */
+ WRITE("write", "鍙啓"),
+
+ /**
+ * 璇诲啓
+ */
+ READ_WRITE("read_write", "璇诲啓");
+
+ /**
+ * 绫诲瀷
+ */
+ private final String type;
+ /**
+ * 鎻忚堪
+ */
+ private final String policy;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/model/BladeFile.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/model/BladeFile.java
new file mode 100644
index 0000000..c0313f5
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/model/BladeFile.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss.model;
+
+import lombok.Data;
+
+/**
+ * BladeFile
+ *
+ * @author Chill
+ */
+@Data
+public class BladeFile {
+ /**
+ * 鏂囦欢鍦板潃
+ */
+ private String link;
+ /**
+ * 鍩熷悕鍦板潃
+ */
+ private String domain;
+ /**
+ * 鏂囦欢鍚�
+ */
+ private String name;
+ /**
+ * 鍒濆鏂囦欢鍚�
+ */
+ private String originalName;
+ /**
+ * 闄勪欢琛↖D
+ */
+ private Long attachId;
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/model/MinioItem.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/model/MinioItem.java
new file mode 100644
index 0000000..7553e23
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/model/MinioItem.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss.model;
+
+import io.minio.messages.Item;
+import io.minio.messages.Owner;
+import lombok.Data;
+import org.springblade.core.tool.utils.DateUtil;
+
+import java.util.Date;
+
+/**
+ * MinioItem
+ *
+ * @author Chill
+ */
+@Data
+public class MinioItem {
+
+ private String objectName;
+ private Date lastModified;
+ private String etag;
+ private Long size;
+ private String storageClass;
+ private Owner owner;
+ private boolean isDir;
+ private String category;
+
+ public MinioItem(Item item) {
+ this.objectName = item.objectName();
+ this.lastModified = DateUtil.toDate(item.lastModified().toLocalDateTime());
+ this.etag = item.etag();
+ this.size = item.size();
+ this.storageClass = item.storageClass();
+ this.owner = item.owner();
+ this.isDir = item.isDir();
+ this.category = isDir ? "dir" : "file";
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/model/OssFile.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/model/OssFile.java
new file mode 100644
index 0000000..1684fdd
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/model/OssFile.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss.model;
+
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * OssFile
+ *
+ * @author Chill
+ */
+@Data
+public class OssFile {
+ /**
+ * 鏂囦欢鍦板潃
+ */
+ private String link;
+ /**
+ * 鏂囦欢鍚�
+ */
+ private String name;
+ /**
+ * 鏂囦欢hash鍊�
+ */
+ public String hash;
+ /**
+ * 鏂囦欢澶у皬
+ */
+ private long length;
+ /**
+ * 鏂囦欢涓婁紶鏃堕棿
+ */
+ private Date putTime;
+ /**
+ * 鏂囦欢contentType
+ */
+ private String contentType;
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/props/OssProperties.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/props/OssProperties.java
new file mode 100644
index 0000000..8e29e81
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/props/OssProperties.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss.props;
+
+import lombok.Data;
+import org.springblade.core.tool.support.Kv;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Minio鍙傛暟閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@Data
+@ConfigurationProperties(prefix = "oss")
+public class OssProperties {
+
+ /**
+ * 鏄惁鍚敤
+ */
+ private Boolean enabled;
+
+ /**
+ * 瀵硅薄瀛樺偍鍚嶇О
+ */
+ private String name;
+
+ /**
+ * 鏄惁寮�鍚鎴锋ā寮�
+ */
+ private Boolean tenantMode = false;
+
+ /**
+ * 瀵硅薄瀛樺偍鏈嶅姟鐨刄RL
+ */
+ private String endpoint;
+
+ /**
+ * 搴旂敤ID TencentCOS闇�瑕�
+ */
+ private String appId;
+
+ /**
+ * 鍖哄煙绠�绉� TencentCOS闇�瑕�
+ */
+ private String region;
+
+ /**
+ * Access key灏卞儚鐢ㄦ埛ID锛屽彲浠ュ敮涓�鏍囪瘑浣犵殑璐︽埛
+ */
+ private String accessKey;
+
+ /**
+ * Secret key鏄綘璐︽埛鐨勫瘑鐮�
+ */
+ private String secretKey;
+
+ /**
+ * 榛樿鐨勫瓨鍌ㄦ《鍚嶇О
+ */
+ private String bucketName = "bladex";
+
+ /**
+ * 鑷畾涔夊睘鎬�
+ */
+ private Kv args;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/rule/BladeOssRule.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/rule/BladeOssRule.java
new file mode 100644
index 0000000..f7e2162
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/rule/BladeOssRule.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss.rule;
+
+import lombok.AllArgsConstructor;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.core.tool.utils.FileUtil;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+
+/**
+ * 榛樿瀛樺偍妗剁敓鎴愯鍒�
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+public class BladeOssRule implements OssRule {
+
+ /**
+ * 绉熸埛妯″紡
+ */
+ private final Boolean tenantMode;
+
+ @Override
+ public String bucketName(String bucketName) {
+ String prefix = (tenantMode) ? AuthUtil.getTenantId().concat(StringPool.DASH) : StringPool.EMPTY;
+ return prefix + bucketName;
+ }
+
+ @Override
+ public String fileName(String originalFilename) {
+ return "upload" + StringPool.SLASH + DateUtil.today() + StringPool.SLASH + StringUtil.randomUUID() + StringPool.DOT + FileUtil.getFileExtension(originalFilename);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/rule/OssRule.java b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/rule/OssRule.java
new file mode 100644
index 0000000..4b912db
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-oss/src/main/java/org/springblade/core/oss/rule/OssRule.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.oss.rule;
+
+/**
+ * Oss閫氱敤瑙勫垯
+ *
+ * @author Chill
+ */
+public interface OssRule {
+
+ /**
+ * 鑾峰彇瀛樺偍妗惰鍒�
+ *
+ * @param bucketName 瀛樺偍妗跺悕绉�
+ * @return String
+ */
+ String bucketName(String bucketName);
+
+ /**
+ * 鑾峰彇鏂囦欢鍚嶈鍒�
+ *
+ * @param originalFilename 鏂囦欢鍚�
+ * @return String
+ */
+ String fileName(String originalFilename);
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-prometheus/pom.xml b/Source/BladeX-Tool/blade-starter-prometheus/pom.xml
new file mode 100644
index 0000000..99dc271
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-prometheus/pom.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-prometheus</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-webflux</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-commons</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.alibaba.cloud</groupId>
+ <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>com.alibaba.nacos</groupId>
+ <artifactId>nacos-client</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>com.alibaba.nacos</groupId>
+ <artifactId>nacos-client</artifactId>
+ </dependency>
+ <!-- Blade -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-launch</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-metrics</artifactId>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/config/PrometheusConfiguration.java b/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/config/PrometheusConfiguration.java
new file mode 100644
index 0000000..af26de8
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/config/PrometheusConfiguration.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.prometheus.config;
+
+import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
+import org.springblade.core.launch.props.BladePropertySource;
+import org.springblade.core.prometheus.endpoint.AgentEndpoint;
+import org.springblade.core.prometheus.endpoint.ServiceEndpoint;
+import org.springblade.core.prometheus.service.RegistrationService;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.cloud.client.discovery.DiscoveryClient;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * Prometheus閰嶇疆绫�
+ *
+ * @author L.cm
+ */
+@AutoConfiguration
+@BladePropertySource(value = "classpath:/blade-prometheus.yml")
+public class PrometheusConfiguration {
+
+ @Bean
+ public RegistrationService registrationService(DiscoveryClient discoveryClient) {
+ return new RegistrationService(discoveryClient);
+ }
+
+ @Bean
+ public AgentEndpoint agentController(NacosDiscoveryProperties properties) {
+ return new AgentEndpoint(properties);
+ }
+
+ @Bean
+ public ServiceEndpoint serviceController(RegistrationService registrationService) {
+ return new ServiceEndpoint(registrationService);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/Agent.java b/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/Agent.java
new file mode 100644
index 0000000..5439f23
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/Agent.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.prometheus.data;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Builder;
+import lombok.Getter;
+
+/**
+ * Agent
+ *
+ * @author L.cm
+ */
+@Getter
+@Builder
+public class Agent {
+
+ @JsonProperty("Config")
+ private Config config;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/ChangeItem.java b/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/ChangeItem.java
new file mode 100644
index 0000000..840ac91
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/ChangeItem.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.prometheus.data;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+
+/**
+ * ChangeItem
+ *
+ * @author L.cm
+ */
+@Getter
+@RequiredArgsConstructor
+public class ChangeItem<T> {
+ private final T item;
+ private final long changeIndex;
+}
diff --git a/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/Config.java b/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/Config.java
new file mode 100644
index 0000000..39beb8d
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/Config.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.prometheus.data;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Builder;
+import lombok.Getter;
+
+/**
+ * Config
+ *
+ * @author L.cm
+ */
+@Getter
+@Builder
+public class Config {
+
+ @JsonProperty("Datacenter")
+ private String dataCenter;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/Service.java b/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/Service.java
new file mode 100644
index 0000000..18e28df
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/Service.java
@@ -0,0 +1,49 @@
+package org.springblade.core.prometheus.data;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Builder;
+import lombok.Getter;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * model details see https://www.consul.io/api/catalog.html#serviceport
+ *
+ * @author consul
+ */
+@Getter
+@Builder
+public class Service {
+
+ @JsonProperty("Address")
+ private String address;
+
+ @JsonProperty("Node")
+ private String node;
+
+ @JsonProperty("ServiceAddress")
+ private String serviceAddress;
+
+ @JsonProperty("ServiceName")
+ private String serviceName;
+
+ @JsonProperty("ServiceID")
+ private String serviceId;
+
+ @JsonProperty("ServicePort")
+ private int servicePort;
+
+ @JsonProperty("NodeMeta")
+ private Map<String, String> nodeMeta;
+
+ @JsonProperty("ServiceMeta")
+ private Map<String, String> serviceMeta;
+
+ /**
+ * will be empty, eureka does not have the concept of service tags
+ */
+ @JsonProperty("ServiceTags")
+ private List<String> serviceTags;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/ServiceHealth.java b/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/ServiceHealth.java
new file mode 100644
index 0000000..6cbf611
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/data/ServiceHealth.java
@@ -0,0 +1,81 @@
+package org.springblade.core.prometheus.data;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Builder;
+import lombok.Getter;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * model details see https://www.consul.io/api/health.html#list-nodes-for-service
+ *
+ * @author consul
+ */
+@Getter
+@Builder
+public class ServiceHealth {
+
+ @JsonProperty("Node")
+ private Node node;
+
+ @JsonProperty("Service")
+ private Service service;
+
+ @JsonProperty("Checks")
+ private List<Check> checks;
+
+ @Getter
+ @Builder
+ public static class Node {
+
+ @JsonProperty("Node")
+ private String name;
+
+ @JsonProperty("Address")
+ private String address;
+
+ @JsonProperty("Meta")
+ private Map<String, String> meta;
+ }
+
+ @Getter
+ @Builder
+ public static class Service {
+
+ @JsonProperty("ID")
+ private String id;
+
+ @JsonProperty("Service")
+ private String name;
+
+ @JsonProperty("Tags")
+ private List<String> tags;
+
+ @JsonProperty("Address")
+ private String address;
+
+ @JsonProperty("Meta")
+ private Map<String, String> meta;
+
+ @JsonProperty("Port")
+ private int port;
+ }
+
+ @Getter
+ @Builder
+ public static class Check {
+
+ @JsonProperty("Node")
+ private String node;
+
+ @JsonProperty("CheckID")
+ private String checkId;
+
+ @JsonProperty("Name")
+ private String name;
+
+ @JsonProperty("Status")
+ private String status;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/endpoint/AgentEndpoint.java b/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/endpoint/AgentEndpoint.java
new file mode 100644
index 0000000..f21df56
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/endpoint/AgentEndpoint.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.prometheus.endpoint;
+
+import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
+import lombok.RequiredArgsConstructor;
+import org.springblade.core.auto.annotation.AutoIgnore;
+import org.springblade.core.prometheus.data.Agent;
+import org.springblade.core.prometheus.data.Config;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * consul agent api
+ *
+ * @author L.cm
+ */
+@AutoIgnore
+@RestController
+@RequiredArgsConstructor
+public class AgentEndpoint {
+ private final NacosDiscoveryProperties properties;
+
+ @GetMapping(value = "/v1/agent/self", produces = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseBody
+ public Agent getNodes() {
+ Config config = Config.builder().dataCenter(properties.getGroup()).build();
+ return Agent.builder().config(config).build();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/endpoint/ServiceEndpoint.java b/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/endpoint/ServiceEndpoint.java
new file mode 100644
index 0000000..690249d
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/endpoint/ServiceEndpoint.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.prometheus.endpoint;
+
+import lombok.RequiredArgsConstructor;
+import org.springblade.core.auto.annotation.AutoIgnore;
+import org.springblade.core.prometheus.data.Service;
+import org.springblade.core.prometheus.data.ServiceHealth;
+import org.springblade.core.prometheus.service.RegistrationService;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.Assert;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * consul catalog api
+ *
+ * @author L.cm
+ */
+@AutoIgnore
+@RestController
+@RequiredArgsConstructor
+public class ServiceEndpoint {
+ private static final String CONSUL_IDX_HEADER = "X-Consul-Index";
+ private static final String QUERY_PARAM_WAIT = "wait";
+ private static final String QUERY_PARAM_INDEX = "index";
+ private static final Pattern WAIT_PATTERN = Pattern.compile("(\\d*)(m|s|ms|h)");
+ private final RegistrationService registrationService;
+
+ @GetMapping(value = "/v1/catalog/services", produces = MediaType.APPLICATION_JSON_VALUE)
+ public Mono<ResponseEntity<Map<String, String[]>>> getServiceNames(
+ @RequestParam(name = QUERY_PARAM_WAIT, required = false) String wait,
+ @RequestParam(name = QUERY_PARAM_INDEX, required = false) Long index) {
+ return registrationService.getServiceNames(getWaitMillis(wait), index)
+ .map(item -> createResponseEntity(item.getItem(), item.getChangeIndex()));
+ }
+
+ @GetMapping(value = "/v1/catalog/service/{appName}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public Mono<ResponseEntity<List<Service>>> getService(@PathVariable("appName") String appName,
+ @RequestParam(value = QUERY_PARAM_WAIT, required = false) String wait,
+ @RequestParam(value = QUERY_PARAM_INDEX, required = false) Long index) {
+ Objects.requireNonNull(appName, "service name can not be null");
+ return registrationService.getService(appName, getWaitMillis(wait), index)
+ .map(item -> createResponseEntity(item.getItem(), item.getChangeIndex()));
+ }
+
+ @GetMapping(value = "/v1/health/service/{appName}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public Mono<ResponseEntity<List<ServiceHealth>>> getServiceHealth(@PathVariable("appName") String appName,
+ @RequestParam(value = QUERY_PARAM_WAIT, required = false) String wait,
+ @RequestParam(value = QUERY_PARAM_INDEX, required = false) Long index) {
+ Assert.isTrue(appName != null, "service name can not be null");
+ return registrationService.getService(appName, getWaitMillis(wait), index)
+ .map(item -> {
+ List<ServiceHealth> services = item.getItem().stream()
+ .map(registrationService::getServiceHealth).collect(toList());
+ return createResponseEntity(services, item.getChangeIndex());
+ });
+ }
+
+ private static MultiValueMap<String, String> createHeaders(long index) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.add(CONSUL_IDX_HEADER, String.valueOf(index));
+ return headers;
+ }
+
+ private static <T> ResponseEntity<T> createResponseEntity(T body, long index) {
+ return new ResponseEntity<>(body, createHeaders(index), HttpStatus.OK);
+ }
+
+ /**
+ * Details to the wait behaviour can be found
+ * https://www.consul.io/api/index.html#blocking-queries
+ */
+ private static long getWaitMillis(String wait) {
+ // default from consul docu
+ long millis = TimeUnit.MINUTES.toMillis(5);
+ if (wait != null) {
+ Matcher matcher = WAIT_PATTERN.matcher(wait);
+ if (matcher.matches()) {
+ long value = Long.parseLong(matcher.group(1));
+ TimeUnit timeUnit = parseTimeUnit(matcher.group(2));
+ millis = timeUnit.toMillis(value);
+ } else {
+ throw new IllegalArgumentException("Invalid wait pattern");
+ }
+ }
+ return millis + ThreadLocalRandom.current().nextInt(((int) millis / 16) + 1);
+ }
+
+ private static TimeUnit parseTimeUnit(String unit) {
+ switch (unit) {
+ case "h":
+ return TimeUnit.HOURS;
+ case "m":
+ return TimeUnit.MINUTES;
+ case "s":
+ return TimeUnit.SECONDS;
+ case "ms":
+ return TimeUnit.MILLISECONDS;
+ default:
+ throw new IllegalArgumentException("No valid time unit");
+ }
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/service/RegistrationService.java b/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/service/RegistrationService.java
new file mode 100644
index 0000000..1f0d0ee
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-prometheus/src/main/java/org/springblade/core/prometheus/service/RegistrationService.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+package org.springblade.core.prometheus.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.prometheus.data.ChangeItem;
+import org.springblade.core.prometheus.data.Service;
+import org.springblade.core.prometheus.data.ServiceHealth;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.discovery.DiscoveryClient;
+import reactor.core.publisher.Mono;
+
+import java.util.*;
+import java.util.function.Supplier;
+
+/**
+ * Returns Services and List of Service with its last changed
+ *
+ * @author L.cm
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class RegistrationService {
+ private static final String[] NO_SERVICE_TAGS = new String[0];
+ private final DiscoveryClient discoveryClient;
+
+ public Mono<ChangeItem<Map<String, String[]>>> getServiceNames(long waitMillis, Long index) {
+ return returnDeferred(waitMillis, index, () -> {
+ List<String> services = discoveryClient.getServices();
+ Set<String> set = new HashSet<>(services);
+ Map<String, String[]> result = new HashMap<>();
+ for (String item : set) {
+ result.put(item, NO_SERVICE_TAGS);
+ }
+ return result;
+ });
+ }
+
+ public Mono<ChangeItem<List<Service>>> getService(String appName, long waitMillis, Long index) {
+ return returnDeferred(waitMillis, index, () -> {
+ List<ServiceInstance> instances = discoveryClient.getInstances(appName);
+ List<Service> list = new ArrayList<>();
+ if (instances == null || instances.isEmpty()) {
+ return Collections.emptyList();
+ }
+ Set<ServiceInstance> instSet = new HashSet<>(instances);
+ for (ServiceInstance instance : instSet) {
+ Service service = Service.builder()
+ .address(instance.getHost())
+ .node(instance.getServiceId())
+ .serviceAddress(instance.getHost())
+ .servicePort(instance.getPort())
+ .serviceName(instance.getServiceId())
+ .serviceId(instance.getHost() + ":" + instance.getPort())
+ .nodeMeta(Collections.emptyMap())
+ .serviceMeta(instance.getMetadata())
+ .serviceTags(Collections.emptyList())
+ .build();
+ list.add(service);
+ }
+ return list;
+ });
+ }
+
+ public ServiceHealth getServiceHealth(Service instanceInfo) {
+ String address = instanceInfo.getAddress();
+ ServiceHealth.Node node = ServiceHealth.Node.builder()
+ .name(instanceInfo.getServiceName())
+ .address(address)
+ .meta(Collections.emptyMap())
+ .build();
+ ServiceHealth.Service service = ServiceHealth.Service.builder()
+ .id(instanceInfo.getServiceId())
+ .name(instanceInfo.getServiceName())
+ .tags(Collections.emptyList())
+ .address(address)
+ .meta(instanceInfo.getServiceMeta())
+ .port(instanceInfo.getServicePort())
+ .build();
+ ServiceHealth.Check check = ServiceHealth.Check.builder()
+ .node(instanceInfo.getServiceName())
+ .checkId("service:" + instanceInfo.getServiceId())
+ .name("Service '" + instanceInfo.getServiceId() + "' check")
+ // nacos 瀹炴椂鎬у緢楂橈紝鍙瀹氫负鍋ュ悍
+ .status("UP")
+ .build();
+ return ServiceHealth.builder()
+ .node(node)
+ .service(service)
+ .checks(Collections.singletonList(check))
+ .build();
+ }
+
+ private static <T> Mono<ChangeItem<T>> returnDeferred(long waitMillis, Long index, Supplier<T> fn) {
+ return Mono.just(new ChangeItem<>(fn.get(), System.currentTimeMillis()));
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-prometheus/src/main/resources/blade-prometheus.yml b/Source/BladeX-Tool/blade-starter-prometheus/src/main/resources/blade-prometheus.yml
new file mode 100644
index 0000000..01e80e4
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-prometheus/src/main/resources/blade-prometheus.yml
@@ -0,0 +1,8 @@
+management:
+ endpoints:
+ web:
+ exposure:
+ include: "*"
+ metrics:
+ tags:
+ application: ${spring.application.name}
diff --git a/Source/BladeX-Tool/blade-starter-redis/pom.xml b/Source/BladeX-Tool/blade-starter-redis/pom.xml
new file mode 100644
index 0000000..6809d6f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/pom.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-redis</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!--Blade-->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-tool</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-jwt</artifactId>
+ </dependency>
+ <!--Redis-->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-data-redis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.redisson</groupId>
+ <artifactId>redisson</artifactId>
+ </dependency>
+ <!-- protostuff -->
+ <dependency>
+ <groupId>io.protostuff</groupId>
+ <artifactId>protostuff-core</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>io.protostuff</groupId>
+ <artifactId>protostuff-runtime</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/BladeRedis.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/BladeRedis.java
new file mode 100644
index 0000000..b8e2826
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/BladeRedis.java
@@ -0,0 +1,824 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.cache;
+
+import lombok.Getter;
+import org.springblade.core.tool.utils.CollectionUtil;
+import org.springframework.data.redis.core.*;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+import java.time.Duration;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+/**
+ * redis 宸ュ叿
+ *
+ * @author L.cm
+ */
+@Getter
+@SuppressWarnings("unchecked")
+public class BladeRedis {
+ private final RedisTemplate<String, Object> redisTemplate;
+ private final StringRedisTemplate stringRedisTemplate;
+ private final ValueOperations<String, Object> valueOps;
+ private final HashOperations<String, Object, Object> hashOps;
+ private final ListOperations<String, Object> listOps;
+ private final SetOperations<String, Object> setOps;
+ private final ZSetOperations<String, Object> zSetOps;
+
+ public BladeRedis(RedisTemplate<String, Object> redisTemplate, StringRedisTemplate stringRedisTemplate) {
+ this.redisTemplate = redisTemplate;
+ this.stringRedisTemplate = stringRedisTemplate;
+ Assert.notNull(redisTemplate, "redisTemplate is null");
+ valueOps = redisTemplate.opsForValue();
+ hashOps = redisTemplate.opsForHash();
+ listOps = redisTemplate.opsForList();
+ setOps = redisTemplate.opsForSet();
+ zSetOps = redisTemplate.opsForZSet();
+ }
+
+ /**
+ * 璁剧疆缂撳瓨
+ *
+ * @param cacheKey 缂撳瓨key
+ * @param value 缂撳瓨value
+ */
+ public void set(CacheKey cacheKey, Object value) {
+ String key = cacheKey.getKey();
+ Duration expire = cacheKey.getExpire();
+ if (expire == null) {
+ set(key, value);
+ } else {
+ setEx(key, value, expire);
+ }
+ }
+
+ /**
+ * 瀛樻斁 key value 瀵瑰埌 redis銆�
+ */
+ public void set(String key, Object value) {
+ valueOps.set(key, value);
+ }
+
+ /**
+ * 瀛樻斁 key value 瀵瑰埌 redis锛屽苟灏� key 鐨勭敓瀛樻椂闂磋涓� seconds (浠ョ涓哄崟浣�)銆�
+ * 濡傛灉 key 宸茬粡瀛樺湪锛� SETEX 鍛戒护灏嗚鍐欐棫鍊笺��
+ */
+ public void setEx(String key, Object value, Duration timeout) {
+ valueOps.set(key, value, timeout);
+ }
+
+ /**
+ * 瀛樻斁 key value 瀵瑰埌 redis锛屽苟灏� key 鐨勭敓瀛樻椂闂磋涓� seconds (浠ョ涓哄崟浣�)銆�
+ * 濡傛灉 key 宸茬粡瀛樺湪锛� SETEX 鍛戒护灏嗚鍐欐棫鍊笺��
+ */
+ public void setEx(String key, Object value, Long seconds) {
+ valueOps.set(key, value, seconds, TimeUnit.SECONDS);
+ }
+
+ /**
+ * 杩斿洖 key 鎵�鍏宠仈鐨� value 鍊�
+ * 濡傛灉 key 涓嶅瓨鍦ㄩ偅涔堣繑鍥炵壒娈婂�� nil 銆�
+ */
+ @Nullable
+ public <T> T get(String key) {
+ return (T) valueOps.get(key);
+ }
+
+ /**
+ * 鑾峰彇cache 涓� null 鏃朵娇鐢ㄥ姞杞藉櫒锛岀劧鍚庤缃紦瀛�
+ *
+ * @param key cacheKey
+ * @param loader cache loader
+ * @param <T> 娉涘瀷
+ * @return 缁撴灉
+ */
+ @Nullable
+ public <T> T get(String key, Supplier<T> loader) {
+ T value = this.get(key);
+ if (value != null) {
+ return value;
+ }
+ value = loader.get();
+ if (value == null) {
+ return null;
+ }
+ this.set(key, value);
+ return value;
+ }
+
+ /**
+ * 杩斿洖 key 鎵�鍏宠仈鐨� value 鍊�
+ * 濡傛灉 key 涓嶅瓨鍦ㄩ偅涔堣繑鍥炵壒娈婂�� nil 銆�
+ */
+ @Nullable
+ public <T> T get(CacheKey cacheKey) {
+ return (T) valueOps.get(cacheKey.getKey());
+ }
+
+ /**
+ * 鑾峰彇cache 涓� null 鏃朵娇鐢ㄥ姞杞藉櫒锛岀劧鍚庤缃紦瀛�
+ *
+ * @param cacheKey cacheKey
+ * @param loader cache loader
+ * @param <T> 娉涘瀷
+ * @return 缁撴灉
+ */
+ @Nullable
+ public <T> T get(CacheKey cacheKey, Supplier<T> loader) {
+ String key = cacheKey.getKey();
+ T value = this.get(key);
+ if (value != null) {
+ return value;
+ }
+ value = loader.get();
+ if (value == null) {
+ return null;
+ }
+ this.set(cacheKey, value);
+ return value;
+ }
+
+ /**
+ * 鍒犻櫎缁欏畾鐨勪竴涓� key
+ * 涓嶅瓨鍦ㄧ殑 key 浼氳蹇界暐銆�
+ */
+ public Boolean del(String key) {
+ return redisTemplate.delete(key);
+ }
+
+ /**
+ * 鍒犻櫎缁欏畾鐨勪竴涓� key
+ * 涓嶅瓨鍦ㄧ殑 key 浼氳蹇界暐銆�
+ */
+ public Boolean del(CacheKey key) {
+ return redisTemplate.delete(key.getKey());
+ }
+
+ /**
+ * 鍒犻櫎缁欏畾鐨勫涓� key
+ * 涓嶅瓨鍦ㄧ殑 key 浼氳蹇界暐銆�
+ */
+ public Long del(String... keys) {
+ return del(Arrays.asList(keys));
+ }
+
+ /**
+ * 鍒犻櫎缁欏畾鐨勫涓� key
+ * 涓嶅瓨鍦ㄧ殑 key 浼氳蹇界暐銆�
+ */
+ public Long del(Collection<String> keys) {
+ return redisTemplate.delete(keys);
+ }
+
+ /**
+ * 鏌ユ壘鎵�鏈夌鍚堢粰瀹氭ā寮� pattern 鐨� key 銆�
+ * KEYS * 鍖归厤鏁版嵁搴撲腑鎵�鏈� key 銆�
+ * KEYS h?llo 鍖归厤 hello 锛� hallo 鍜� hxllo 绛夈��
+ * KEYS h*llo 鍖归厤 hllo 鍜� heeeeello 绛夈��
+ * KEYS h[ae]llo 鍖归厤 hello 鍜� hallo 锛屼絾涓嶅尮閰� hillo 銆�
+ * 鐗规畩绗﹀彿鐢� \ 闅斿紑
+ */
+ public Set<String> keys(String pattern) {
+ return redisTemplate.keys(pattern);
+ }
+
+ /**
+ * 鍚屾椂璁剧疆涓�涓垨澶氫釜 key-value 瀵广��
+ * 濡傛灉鏌愪釜缁欏畾 key 宸茬粡瀛樺湪锛岄偅涔� MSET 浼氱敤鏂板�艰鐩栧師鏉ョ殑鏃у�硷紝濡傛灉杩欎笉鏄綘鎵�甯屾湜鐨勬晥鏋滐紝璇疯�冭檻浣跨敤 MSETNX 鍛戒护锛氬畠鍙細鍦ㄦ墍鏈夌粰瀹� key 閮戒笉瀛樺湪鐨勬儏鍐典笅杩涜璁剧疆鎿嶄綔銆�
+ * MSET 鏄竴涓師瀛愭��(atomic)鎿嶄綔锛屾墍鏈夌粰瀹� key 閮戒細鍦ㄥ悓涓�鏃堕棿鍐呰璁剧疆锛屾煇浜涚粰瀹� key 琚洿鏂拌�屽彟涓�浜涚粰瀹� key 娌℃湁鏀瑰彉鐨勬儏鍐碉紝涓嶅彲鑳藉彂鐢熴��
+ * <pre>
+ * 渚嬪瓙锛�
+ * Cache cache = RedisKit.use(); // 浣跨敤 Redis 鐨� cache
+ * cache.mset("k1", "v1", "k2", "v2"); // 鏀惧叆澶氫釜 key value 閿�煎
+ * List list = cache.mget("k1", "k2"); // 鍒╃敤澶氫釜閿�煎緱鍒颁笂闈唬鐮佹斁鍏ョ殑鍊�
+ * </pre>
+ */
+ public void mSet(Object... keysValues) {
+ valueOps.multiSet(CollectionUtil.toMap(keysValues));
+ }
+
+ /**
+ * 杩斿洖鎵�鏈�(涓�涓垨澶氫釜)缁欏畾 key 鐨勫�笺��
+ * 濡傛灉缁欏畾鐨� key 閲岄潰锛屾湁鏌愪釜 key 涓嶅瓨鍦紝閭d箞杩欎釜 key 杩斿洖鐗规畩鍊� nil 銆傚洜姝わ紝璇ュ懡浠ゆ案涓嶅け璐ャ��
+ */
+ public List<Object> mGet(String... keys) {
+ return mGet(Arrays.asList(keys));
+ }
+
+ /**
+ * 杩斿洖鎵�鏈�(涓�涓垨澶氫釜)缁欏畾 key 鐨勫�笺��
+ * 濡傛灉缁欏畾鐨� key 閲岄潰锛屾湁鏌愪釜 key 涓嶅瓨鍦紝閭d箞杩欎釜 key 杩斿洖鐗规畩鍊� nil 銆傚洜姝わ紝璇ュ懡浠ゆ案涓嶅け璐ャ��
+ */
+ public List<Object> mGet(Collection<String> keys) {
+ return valueOps.multiGet(keys);
+ }
+
+ /**
+ * 灏� key 涓偍瀛樼殑鏁板瓧鍊煎噺涓�銆�
+ * 濡傛灉 key 涓嶅瓨鍦紝閭d箞 key 鐨勫�间細鍏堣鍒濆鍖栦负 0 锛岀劧鍚庡啀鎵ц DECR 鎿嶄綔銆�
+ * 濡傛灉鍊煎寘鍚敊璇殑绫诲瀷锛屾垨瀛楃涓茬被鍨嬬殑鍊间笉鑳借〃绀轰负鏁板瓧锛岄偅涔堣繑鍥炰竴涓敊璇��
+ * 鏈搷浣滅殑鍊奸檺鍒跺湪 64 浣�(bit)鏈夌鍙锋暟瀛楄〃绀轰箣鍐呫��
+ * 鍏充簬閫掑(increment) / 閫掑噺(decrement)鎿嶄綔鐨勬洿澶氫俊鎭紝璇峰弬瑙� INCR 鍛戒护銆�
+ */
+ public Long decr(String key) {
+ return stringRedisTemplate.opsForValue().decrement(key);
+ }
+
+ /**
+ * 灏� key 鎵�鍌ㄥ瓨鐨勫�煎噺鍘诲噺閲� decrement 銆�
+ * 濡傛灉 key 涓嶅瓨鍦紝閭d箞 key 鐨勫�间細鍏堣鍒濆鍖栦负 0 锛岀劧鍚庡啀鎵ц DECRBY 鎿嶄綔銆�
+ * 濡傛灉鍊煎寘鍚敊璇殑绫诲瀷锛屾垨瀛楃涓茬被鍨嬬殑鍊间笉鑳借〃绀轰负鏁板瓧锛岄偅涔堣繑鍥炰竴涓敊璇��
+ * 鏈搷浣滅殑鍊奸檺鍒跺湪 64 浣�(bit)鏈夌鍙锋暟瀛楄〃绀轰箣鍐呫��
+ * 鍏充簬鏇村閫掑(increment) / 閫掑噺(decrement)鎿嶄綔鐨勬洿澶氫俊鎭紝璇峰弬瑙� INCR 鍛戒护銆�
+ */
+ public Long decrBy(String key, long longValue) {
+ return stringRedisTemplate.opsForValue().decrement(key, longValue);
+ }
+
+ /**
+ * 灏� key 涓偍瀛樼殑鏁板瓧鍊煎涓�銆�
+ * 濡傛灉 key 涓嶅瓨鍦紝閭d箞 key 鐨勫�间細鍏堣鍒濆鍖栦负 0 锛岀劧鍚庡啀鎵ц INCR 鎿嶄綔銆�
+ * 濡傛灉鍊煎寘鍚敊璇殑绫诲瀷锛屾垨瀛楃涓茬被鍨嬬殑鍊间笉鑳借〃绀轰负鏁板瓧锛岄偅涔堣繑鍥炰竴涓敊璇��
+ * 鏈搷浣滅殑鍊奸檺鍒跺湪 64 浣�(bit)鏈夌鍙锋暟瀛楄〃绀轰箣鍐呫��
+ */
+ public Long incr(String key) {
+ return stringRedisTemplate.opsForValue().increment(key);
+ }
+
+ /**
+ * 灏� key 鎵�鍌ㄥ瓨鐨勫�煎姞涓婂閲� increment 銆�
+ * 濡傛灉 key 涓嶅瓨鍦紝閭d箞 key 鐨勫�间細鍏堣鍒濆鍖栦负 0 锛岀劧鍚庡啀鎵ц INCRBY 鍛戒护銆�
+ * 濡傛灉鍊煎寘鍚敊璇殑绫诲瀷锛屾垨瀛楃涓茬被鍨嬬殑鍊间笉鑳借〃绀轰负鏁板瓧锛岄偅涔堣繑鍥炰竴涓敊璇��
+ * 鏈搷浣滅殑鍊奸檺鍒跺湪 64 浣�(bit)鏈夌鍙锋暟瀛楄〃绀轰箣鍐呫��
+ * 鍏充簬閫掑(increment) / 閫掑噺(decrement)鎿嶄綔鐨勬洿澶氫俊鎭紝鍙傝 INCR 鍛戒护銆�
+ */
+ public Long incrBy(String key, long longValue) {
+ return stringRedisTemplate.opsForValue().increment(key, longValue);
+ }
+
+ /**
+ * 鑾峰彇璁版暟鍣ㄧ殑鍊�
+ */
+ public Long getCounter(String key) {
+ return Long.valueOf(String.valueOf(valueOps.get(key)));
+ }
+
+ /**
+ * 妫�鏌ョ粰瀹� key 鏄惁瀛樺湪銆�
+ */
+ public Boolean exists(String key) {
+ return redisTemplate.hasKey(key);
+ }
+
+ /**
+ * 浠庡綋鍓嶆暟鎹簱涓殢鏈鸿繑鍥�(涓嶅垹闄�)涓�涓� key 銆�
+ */
+ public String randomKey() {
+ return redisTemplate.randomKey();
+ }
+
+ /**
+ * 灏� key 鏀瑰悕涓� newkey 銆�
+ * 褰� key 鍜� newkey 鐩稿悓锛屾垨鑰� key 涓嶅瓨鍦ㄦ椂锛岃繑鍥炰竴涓敊璇��
+ * 褰� newkey 宸茬粡瀛樺湪鏃讹紝 RENAME 鍛戒护灏嗚鐩栨棫鍊笺��
+ */
+ public void rename(String oldkey, String newkey) {
+ redisTemplate.rename(oldkey, newkey);
+ }
+
+ /**
+ * 灏嗗綋鍓嶆暟鎹簱鐨� key 绉诲姩鍒扮粰瀹氱殑鏁版嵁搴� db 褰撲腑銆�
+ * 濡傛灉褰撳墠鏁版嵁搴�(婧愭暟鎹簱)鍜岀粰瀹氭暟鎹簱(鐩爣鏁版嵁搴�)鏈夌浉鍚屽悕瀛楃殑缁欏畾 key 锛屾垨鑰� key 涓嶅瓨鍦ㄤ簬褰撳墠鏁版嵁搴擄紝閭d箞 MOVE 娌℃湁浠讳綍鏁堟灉銆�
+ * 鍥犳锛屼篃鍙互鍒╃敤杩欎竴鐗规�э紝灏� MOVE 褰撲綔閿�(locking)鍘熻(primitive)銆�
+ */
+ public Boolean move(String key, int dbIndex) {
+ return redisTemplate.move(key, dbIndex);
+ }
+
+ /**
+ * 涓虹粰瀹� key 璁剧疆鐢熷瓨鏃堕棿锛屽綋 key 杩囨湡鏃�(鐢熷瓨鏃堕棿涓� 0 )锛屽畠浼氳鑷姩鍒犻櫎銆�
+ * 鍦� Redis 涓紝甯︽湁鐢熷瓨鏃堕棿鐨� key 琚О涓恒�庢槗澶辩殑銆�(volatile)銆�
+ */
+ public Boolean expire(String key, long seconds) {
+ return redisTemplate.expire(key, seconds, TimeUnit.SECONDS);
+ }
+
+ /**
+ * 涓虹粰瀹� key 璁剧疆鐢熷瓨鏃堕棿锛屽綋 key 杩囨湡鏃�(鐢熷瓨鏃堕棿涓� 0 )锛屽畠浼氳鑷姩鍒犻櫎銆�
+ * 鍦� Redis 涓紝甯︽湁鐢熷瓨鏃堕棿鐨� key 琚О涓恒�庢槗澶辩殑銆�(volatile)銆�
+ */
+ public Boolean expire(String key, Duration timeout) {
+ return expire(key, timeout.getSeconds());
+ }
+
+ /**
+ * EXPIREAT 鐨勪綔鐢ㄥ拰 EXPIRE 绫讳技锛岄兘鐢ㄤ簬涓� key 璁剧疆鐢熷瓨鏃堕棿銆備笉鍚屽湪浜� EXPIREAT 鍛戒护鎺ュ彈鐨勬椂闂村弬鏁版槸 UNIX 鏃堕棿鎴�(unix timestamp)銆�
+ */
+ public Boolean expireAt(String key, Date date) {
+ return redisTemplate.expireAt(key, date);
+ }
+
+ /**
+ * EXPIREAT 鐨勪綔鐢ㄥ拰 EXPIRE 绫讳技锛岄兘鐢ㄤ簬涓� key 璁剧疆鐢熷瓨鏃堕棿銆備笉鍚屽湪浜� EXPIREAT 鍛戒护鎺ュ彈鐨勬椂闂村弬鏁版槸 UNIX 鏃堕棿鎴�(unix timestamp)銆�
+ */
+ public Boolean expireAt(String key, long unixTime) {
+ return expireAt(key, new Date(unixTime));
+ }
+
+ /**
+ * 杩欎釜鍛戒护鍜� EXPIRE 鍛戒护鐨勪綔鐢ㄧ被浼硷紝浣嗘槸瀹冧互姣涓哄崟浣嶈缃� key 鐨勭敓瀛樻椂闂达紝鑰屼笉鍍� EXPIRE 鍛戒护閭f牱锛屼互绉掍负鍗曚綅銆�
+ */
+ public Boolean pexpire(String key, long milliseconds) {
+ return redisTemplate.expire(key, milliseconds, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * 灏嗙粰瀹� key 鐨勫�艰涓� value 锛屽苟杩斿洖 key 鐨勬棫鍊�(old value)銆�
+ * 褰� key 瀛樺湪浣嗕笉鏄瓧绗︿覆绫诲瀷鏃讹紝杩斿洖涓�涓敊璇��
+ */
+ public <T> T getSet(String key, Object value) {
+ return (T) valueOps.getAndSet(key, value);
+ }
+
+ /**
+ * 绉婚櫎缁欏畾 key 鐨勭敓瀛樻椂闂达紝灏嗚繖涓� key 浠庛�庢槗澶辩殑銆�(甯︾敓瀛樻椂闂� key )杞崲鎴愩�庢寔涔呯殑銆�(涓�涓笉甯︾敓瀛樻椂闂淬�佹案涓嶈繃鏈熺殑 key )銆�
+ */
+ public Boolean persist(String key) {
+ return redisTemplate.persist(key);
+ }
+
+ /**
+ * 杩斿洖 key 鎵�鍌ㄥ瓨鐨勫�肩殑绫诲瀷銆�
+ */
+ public String type(String key) {
+ return redisTemplate.type(key).code();
+ }
+
+ /**
+ * 浠ョ涓哄崟浣嶏紝杩斿洖缁欏畾 key 鐨勫墿浣欑敓瀛樻椂闂�(TTL, time to live)銆�
+ */
+ public Long ttl(String key) {
+ return redisTemplate.getExpire(key);
+ }
+
+ /**
+ * 杩欎釜鍛戒护绫讳技浜� TTL 鍛戒护锛屼絾瀹冧互姣涓哄崟浣嶈繑鍥� key 鐨勫墿浣欑敓瀛樻椂闂达紝鑰屼笉鏄儚 TTL 鍛戒护閭f牱锛屼互绉掍负鍗曚綅銆�
+ */
+ public Long pttl(String key) {
+ return redisTemplate.getExpire(key, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * 灏嗗搱甯岃〃 key 涓殑鍩� field 鐨勫�艰涓� value 銆�
+ * 濡傛灉 key 涓嶅瓨鍦紝涓�涓柊鐨勫搱甯岃〃琚垱寤哄苟杩涜 HSET 鎿嶄綔銆�
+ * 濡傛灉鍩� field 宸茬粡瀛樺湪浜庡搱甯岃〃涓紝鏃у�煎皢琚鐩栥��
+ */
+ public void hSet(String key, Object field, Object value) {
+ hashOps.put(key, field, value);
+ }
+
+ /**
+ * 鍚屾椂灏嗗涓� field-value (鍩�-鍊�)瀵硅缃埌鍝堝笇琛� key 涓��
+ * 姝ゅ懡浠や細瑕嗙洊鍝堝笇琛ㄤ腑宸插瓨鍦ㄧ殑鍩熴��
+ * 濡傛灉 key 涓嶅瓨鍦紝涓�涓┖鍝堝笇琛ㄨ鍒涘缓骞舵墽琛� HMSET 鎿嶄綔銆�
+ */
+ public void hMset(String key, Map<Object, Object> hash) {
+ hashOps.putAll(key, hash);
+ }
+
+ /**
+ * 杩斿洖鍝堝笇琛� key 涓粰瀹氬煙 field 鐨勫�笺��
+ */
+ public <T> T hGet(String key, Object field) {
+ return (T) hashOps.get(key, field);
+ }
+
+ /**
+ * 杩斿洖鍝堝笇琛� key 涓紝涓�涓垨澶氫釜缁欏畾鍩熺殑鍊笺��
+ * 濡傛灉缁欏畾鐨勫煙涓嶅瓨鍦ㄤ簬鍝堝笇琛紝閭d箞杩斿洖涓�涓� nil 鍊笺��
+ * 鍥犱负涓嶅瓨鍦ㄧ殑 key 琚綋浣滀竴涓┖鍝堝笇琛ㄦ潵澶勭悊锛屾墍浠ュ涓�涓笉瀛樺湪鐨� key 杩涜 HMGET 鎿嶄綔灏嗚繑鍥炰竴涓彧甯︽湁 nil 鍊肩殑琛ㄣ��
+ */
+ public List hmGet(String key, Object... fields) {
+ return hmGet(key, Arrays.asList(fields));
+ }
+
+ /**
+ * 杩斿洖鍝堝笇琛� key 涓紝涓�涓垨澶氫釜缁欏畾鍩熺殑鍊笺��
+ * 濡傛灉缁欏畾鐨勫煙涓嶅瓨鍦ㄤ簬鍝堝笇琛紝閭d箞杩斿洖涓�涓� nil 鍊笺��
+ * 鍥犱负涓嶅瓨鍦ㄧ殑 key 琚綋浣滀竴涓┖鍝堝笇琛ㄦ潵澶勭悊锛屾墍浠ュ涓�涓笉瀛樺湪鐨� key 杩涜 HMGET 鎿嶄綔灏嗚繑鍥炰竴涓彧甯︽湁 nil 鍊肩殑琛ㄣ��
+ */
+ public List hmGet(String key, Collection<Object> hashKeys) {
+ return hashOps.multiGet(key, hashKeys);
+ }
+
+ /**
+ * 鍒犻櫎鍝堝笇琛� key 涓殑涓�涓垨澶氫釜鎸囧畾鍩燂紝涓嶅瓨鍦ㄧ殑鍩熷皢琚拷鐣ャ��
+ */
+ public Long hDel(String key, Object... fields) {
+ return hashOps.delete(key, fields);
+ }
+
+ /**
+ * 鏌ョ湅鍝堝笇琛� key 涓紝缁欏畾鍩� field 鏄惁瀛樺湪銆�
+ */
+ public Boolean hExists(String key, Object field) {
+ return hashOps.hasKey(key, field);
+ }
+
+ /**
+ * 杩斿洖鍝堝笇琛� key 涓紝鎵�鏈夌殑鍩熷拰鍊笺��
+ * 鍦ㄨ繑鍥炲�奸噷锛岀揣璺熸瘡涓煙鍚�(field name)涔嬪悗鏄煙鐨勫��(value)锛屾墍浠ヨ繑鍥炲�肩殑闀垮害鏄搱甯岃〃澶у皬鐨勪袱鍊嶃��
+ */
+ public Map hGetAll(String key) {
+ return hashOps.entries(key);
+ }
+
+ /**
+ * 杩斿洖鍝堝笇琛� key 涓墍鏈夊煙鐨勫�笺��
+ */
+ public List hVals(String key) {
+ return hashOps.values(key);
+ }
+
+ /**
+ * 杩斿洖鍝堝笇琛� key 涓殑鎵�鏈夊煙銆�
+ * 搴曞眰瀹炵幇姝ゆ柟娉曞彇鍚嶄负 hfields 鏇翠负鍚堥�傦紝鍦ㄦ浠呬负涓庡簳灞備繚鎸佷竴鑷�
+ */
+ public Set<Object> hKeys(String key) {
+ return hashOps.keys(key);
+ }
+
+ /**
+ * 杩斿洖鍝堝笇琛� key 涓煙鐨勬暟閲忋��
+ */
+ public Long hLen(String key) {
+ return hashOps.size(key);
+ }
+
+ /**
+ * 涓哄搱甯岃〃 key 涓殑鍩� field 鐨勫�煎姞涓婂閲� increment 銆�
+ * 澧為噺涔熷彲浠ヤ负璐熸暟锛岀浉褰撲簬瀵圭粰瀹氬煙杩涜鍑忔硶鎿嶄綔銆�
+ * 濡傛灉 key 涓嶅瓨鍦紝涓�涓柊鐨勫搱甯岃〃琚垱寤哄苟鎵ц HINCRBY 鍛戒护銆�
+ * 濡傛灉鍩� field 涓嶅瓨鍦紝閭d箞鍦ㄦ墽琛屽懡浠ゅ墠锛屽煙鐨勫�艰鍒濆鍖栦负 0 銆�
+ * 瀵逛竴涓偍瀛樺瓧绗︿覆鍊肩殑鍩� field 鎵ц HINCRBY 鍛戒护灏嗛�犳垚涓�涓敊璇��
+ * 鏈搷浣滅殑鍊艰闄愬埗鍦� 64 浣�(bit)鏈夌鍙锋暟瀛楄〃绀轰箣鍐呫��
+ */
+ public Long hIncrBy(String key, Object field, long value) {
+ return hashOps.increment(key, field, value);
+ }
+
+ /**
+ * 涓哄搱甯岃〃 key 涓殑鍩� field 鍔犱笂娴偣鏁板閲� increment 銆�
+ * 濡傛灉鍝堝笇琛ㄤ腑娌℃湁鍩� field 锛岄偅涔� HINCRBYFLOAT 浼氬厛灏嗗煙 field 鐨勫�艰涓� 0 锛岀劧鍚庡啀鎵ц鍔犳硶鎿嶄綔銆�
+ * 濡傛灉閿� key 涓嶅瓨鍦紝閭d箞 HINCRBYFLOAT 浼氬厛鍒涘缓涓�涓搱甯岃〃锛屽啀鍒涘缓鍩� field 锛屾渶鍚庡啀鎵ц鍔犳硶鎿嶄綔銆�
+ * 褰撲互涓嬩换鎰忎竴涓潯浠跺彂鐢熸椂锛岃繑鍥炰竴涓敊璇細
+ * 1:鍩� field 鐨勫�间笉鏄瓧绗︿覆绫诲瀷(鍥犱负 redis 涓殑鏁板瓧鍜屾诞鐐规暟閮戒互瀛楃涓茬殑褰㈠紡淇濆瓨锛屾墍浠ュ畠浠兘灞炰簬瀛楃涓茬被鍨嬶級
+ * 2:鍩� field 褰撳墠鐨勫�兼垨缁欏畾鐨勫閲� increment 涓嶈兘瑙i噴(parse)涓哄弻绮惧害娴偣鏁�(double precision floating point number)
+ * HINCRBYFLOAT 鍛戒护鐨勮缁嗗姛鑳藉拰 INCRBYFLOAT 鍛戒护绫讳技锛岃鏌ョ湅 INCRBYFLOAT 鍛戒护鑾峰彇鏇村鐩稿叧淇℃伅銆�
+ */
+ public Double hIncrByFloat(String key, Object field, double value) {
+ return hashOps.increment(key, field, value);
+ }
+
+ /**
+ * 杩斿洖鍒楄〃 key 涓紝涓嬫爣涓� index 鐨勫厓绱犮��
+ * 涓嬫爣(index)鍙傛暟 start 鍜� stop 閮戒互 0 涓哄簳锛屼篃灏辨槸璇达紝浠� 0 琛ㄧず鍒楄〃鐨勭涓�涓厓绱狅紝
+ * 浠� 1 琛ㄧず鍒楄〃鐨勭浜屼釜鍏冪礌锛屼互姝ょ被鎺ㄣ��
+ * 浣犱篃鍙互浣跨敤璐熸暟涓嬫爣锛屼互 -1 琛ㄧず鍒楄〃鐨勬渶鍚庝竴涓厓绱狅紝 -2 琛ㄧず鍒楄〃鐨勫�掓暟绗簩涓厓绱狅紝浠ユ绫绘帹銆�
+ * 濡傛灉 key 涓嶆槸鍒楄〃绫诲瀷锛岃繑鍥炰竴涓敊璇��
+ */
+ public <T> T lIndex(String key, long index) {
+ return (T) listOps.index(key, index);
+ }
+
+ /**
+ * 杩斿洖鍒楄〃 key 鐨勯暱搴︺��
+ * 濡傛灉 key 涓嶅瓨鍦紝鍒� key 琚В閲婁负涓�涓┖鍒楄〃锛岃繑鍥� 0 .
+ * 濡傛灉 key 涓嶆槸鍒楄〃绫诲瀷锛岃繑鍥炰竴涓敊璇��
+ */
+ public Long lLen(String key) {
+ return listOps.size(key);
+ }
+
+ /**
+ * 绉婚櫎骞惰繑鍥炲垪琛� key 鐨勫ご鍏冪礌銆�
+ */
+ public <T> T lPop(String key) {
+ return (T) listOps.leftPop(key);
+ }
+
+ /**
+ * 灏嗕竴涓垨澶氫釜鍊� value 鎻掑叆鍒板垪琛� key 鐨勮〃澶�
+ * 濡傛灉鏈夊涓� value 鍊硷紝閭d箞鍚勪釜 value 鍊兼寜浠庡乏鍒板彸鐨勯『搴忎緷娆℃彃鍏ュ埌琛ㄥご锛� 姣斿璇达紝
+ * 瀵圭┖鍒楄〃 mylist 鎵ц鍛戒护 LPUSH mylist a b c 锛屽垪琛ㄧ殑鍊煎皢鏄� c b a 锛�
+ * 杩欑瓑鍚屼簬鍘熷瓙鎬у湴鎵ц LPUSH mylist a 銆� LPUSH mylist b 鍜� LPUSH mylist c 涓変釜鍛戒护銆�
+ * 濡傛灉 key 涓嶅瓨鍦紝涓�涓┖鍒楄〃浼氳鍒涘缓骞舵墽琛� LPUSH 鎿嶄綔銆�
+ * 褰� key 瀛樺湪浣嗕笉鏄垪琛ㄧ被鍨嬫椂锛岃繑鍥炰竴涓敊璇��
+ */
+ public Long lPush(String key, Object... values) {
+ return listOps.leftPush(key, values);
+ }
+
+ /**
+ * 灏嗗垪琛� key 涓嬫爣涓� index 鐨勫厓绱犵殑鍊艰缃负 value 銆�
+ * 褰� index 鍙傛暟瓒呭嚭鑼冨洿锛屾垨瀵逛竴涓┖鍒楄〃( key 涓嶅瓨鍦�)杩涜 LSET 鏃讹紝杩斿洖涓�涓敊璇��
+ * 鍏充簬鍒楄〃涓嬫爣鐨勬洿澶氫俊鎭紝璇峰弬鑰� LINDEX 鍛戒护銆�
+ */
+ public void lSet(String key, long index, Object value) {
+ listOps.set(key, index, value);
+ }
+
+ /**
+ * 鏍规嵁鍙傛暟 count 鐨勫�硷紝绉婚櫎鍒楄〃涓笌鍙傛暟 value 鐩哥瓑鐨勫厓绱犮��
+ * count 鐨勫�煎彲浠ユ槸浠ヤ笅鍑犵锛�
+ * count > 0 : 浠庤〃澶村紑濮嬪悜琛ㄥ熬鎼滅储锛岀Щ闄や笌 value 鐩哥瓑鐨勫厓绱狅紝鏁伴噺涓� count 銆�
+ * count < 0 : 浠庤〃灏惧紑濮嬪悜琛ㄥご鎼滅储锛岀Щ闄や笌 value 鐩哥瓑鐨勫厓绱狅紝鏁伴噺涓� count 鐨勭粷瀵瑰�笺��
+ * count = 0 : 绉婚櫎琛ㄤ腑鎵�鏈変笌 value 鐩哥瓑鐨勫�笺��
+ */
+ public Long lRem(String key, long count, Object value) {
+ return listOps.remove(key, count, value);
+ }
+
+ /**
+ * 杩斿洖鍒楄〃 key 涓寚瀹氬尯闂村唴鐨勫厓绱狅紝鍖洪棿浠ュ亸绉婚噺 start 鍜� stop 鎸囧畾銆�
+ * 涓嬫爣(index)鍙傛暟 start 鍜� stop 閮戒互 0 涓哄簳锛屼篃灏辨槸璇达紝浠� 0 琛ㄧず鍒楄〃鐨勭涓�涓厓绱狅紝浠� 1 琛ㄧず鍒楄〃鐨勭浜屼釜鍏冪礌锛屼互姝ょ被鎺ㄣ��
+ * 浣犱篃鍙互浣跨敤璐熸暟涓嬫爣锛屼互 -1 琛ㄧず鍒楄〃鐨勬渶鍚庝竴涓厓绱狅紝 -2 琛ㄧず鍒楄〃鐨勫�掓暟绗簩涓厓绱狅紝浠ユ绫绘帹銆�
+ * <pre>
+ * 渚嬪瓙锛�
+ * 鑾峰彇 list 涓墍鏈夋暟鎹細cache.lrange(listKey, 0, -1);
+ * 鑾峰彇 list 涓笅鏍� 1 鍒� 3 鐨勬暟鎹細 cache.lrange(listKey, 1, 3);
+ * </pre>
+ */
+ public List lRange(String key, long start, long end) {
+ return listOps.range(key, start, end);
+ }
+
+ /**
+ * 瀵逛竴涓垪琛ㄨ繘琛屼慨鍓�(trim)锛屽氨鏄锛岃鍒楄〃鍙繚鐣欐寚瀹氬尯闂村唴鐨勫厓绱狅紝涓嶅湪鎸囧畾鍖洪棿涔嬪唴鐨勫厓绱犻兘灏嗚鍒犻櫎銆�
+ * 涓句釜渚嬪瓙锛屾墽琛屽懡浠� LTRIM list 0 2 锛岃〃绀哄彧淇濈暀鍒楄〃 list 鐨勫墠涓変釜鍏冪礌锛屽叾浣欏厓绱犲叏閮ㄥ垹闄ゃ��
+ * 涓嬫爣(index)鍙傛暟 start 鍜� stop 閮戒互 0 涓哄簳锛屼篃灏辨槸璇达紝浠� 0 琛ㄧず鍒楄〃鐨勭涓�涓厓绱狅紝浠� 1 琛ㄧず鍒楄〃鐨勭浜屼釜鍏冪礌锛屼互姝ょ被鎺ㄣ��
+ * 浣犱篃鍙互浣跨敤璐熸暟涓嬫爣锛屼互 -1 琛ㄧず鍒楄〃鐨勬渶鍚庝竴涓厓绱狅紝 -2 琛ㄧず鍒楄〃鐨勫�掓暟绗簩涓厓绱狅紝浠ユ绫绘帹銆�
+ * 褰� key 涓嶆槸鍒楄〃绫诲瀷鏃讹紝杩斿洖涓�涓敊璇��
+ */
+ public void lTrim(String key, long start, long end) {
+ listOps.trim(key, start, end);
+ }
+
+ /**
+ * 绉婚櫎骞惰繑鍥炲垪琛� key 鐨勫熬鍏冪礌銆�
+ */
+ public <T> T rPop(String key) {
+ return (T) listOps.rightPop(key);
+ }
+
+ /**
+ * 灏嗕竴涓垨澶氫釜鍊� value 鎻掑叆鍒板垪琛� key 鐨勮〃灏�(鏈�鍙宠竟)銆�
+ * 濡傛灉鏈夊涓� value 鍊硷紝閭d箞鍚勪釜 value 鍊兼寜浠庡乏鍒板彸鐨勯『搴忎緷娆℃彃鍏ュ埌琛ㄥ熬锛氭瘮濡�
+ * 瀵逛竴涓┖鍒楄〃 mylist 鎵ц RPUSH mylist a b c 锛屽緱鍑虹殑缁撴灉鍒楄〃涓� a b c 锛�
+ * 绛夊悓浜庢墽琛屽懡浠� RPUSH mylist a 銆� RPUSH mylist b 銆� RPUSH mylist c 銆�
+ * 濡傛灉 key 涓嶅瓨鍦紝涓�涓┖鍒楄〃浼氳鍒涘缓骞舵墽琛� RPUSH 鎿嶄綔銆�
+ * 褰� key 瀛樺湪浣嗕笉鏄垪琛ㄧ被鍨嬫椂锛岃繑鍥炰竴涓敊璇��
+ */
+ public Long rPush(String key, Object... values) {
+ return listOps.rightPush(key, values);
+ }
+
+ /**
+ * 鍛戒护 RPOPLPUSH 鍦ㄤ竴涓師瀛愭椂闂村唴锛屾墽琛屼互涓嬩袱涓姩浣滐細
+ * 灏嗗垪琛� source 涓殑鏈�鍚庝竴涓厓绱�(灏惧厓绱�)寮瑰嚭锛屽苟杩斿洖缁欏鎴风銆�
+ * 灏� source 寮瑰嚭鐨勫厓绱犳彃鍏ュ埌鍒楄〃 destination 锛屼綔涓� destination 鍒楄〃鐨勭殑澶村厓绱犮��
+ */
+ public <T> T rPopLPush(String srcKey, String dstKey) {
+ return (T) listOps.rightPopAndLeftPush(srcKey, dstKey);
+ }
+
+ /**
+ * 灏嗕竴涓垨澶氫釜 member 鍏冪礌鍔犲叆鍒伴泦鍚� key 褰撲腑锛屽凡缁忓瓨鍦ㄤ簬闆嗗悎鐨� member 鍏冪礌灏嗚蹇界暐銆�
+ * 鍋囧 key 涓嶅瓨鍦紝鍒欏垱寤轰竴涓彧鍖呭惈 member 鍏冪礌浣滄垚鍛樼殑闆嗗悎銆�
+ * 褰� key 涓嶆槸闆嗗悎绫诲瀷鏃讹紝杩斿洖涓�涓敊璇��
+ */
+ public Long sAdd(String key, Object... members) {
+ return setOps.add(key, members);
+ }
+
+ /**
+ * 绉婚櫎骞惰繑鍥為泦鍚堜腑鐨勪竴涓殢鏈哄厓绱犮��
+ * 濡傛灉鍙兂鑾峰彇涓�涓殢鏈哄厓绱狅紝浣嗕笉鎯宠鍏冪礌浠庨泦鍚堜腑琚Щ闄ょ殑璇濓紝鍙互浣跨敤 SRANDMEMBER 鍛戒护銆�
+ */
+ public <T> T sPop(String key) {
+ return (T) setOps.pop(key);
+ }
+
+ /**
+ * 杩斿洖闆嗗悎 key 涓殑鎵�鏈夋垚鍛樸��
+ * 涓嶅瓨鍦ㄧ殑 key 琚涓虹┖闆嗗悎銆�
+ */
+ public Set sMembers(String key) {
+ return setOps.members(key);
+ }
+
+ /**
+ * 鍒ゆ柇 member 鍏冪礌鏄惁闆嗗悎 key 鐨勬垚鍛樸��
+ */
+ public boolean sIsMember(String key, Object member) {
+ return setOps.isMember(key, member);
+ }
+
+ /**
+ * 杩斿洖澶氫釜闆嗗悎鐨勪氦闆嗭紝澶氫釜闆嗗悎鐢� keys 鎸囧畾
+ */
+ public Set sInter(String key, String otherKey) {
+ return setOps.intersect(key, otherKey);
+ }
+
+ /**
+ * 杩斿洖澶氫釜闆嗗悎鐨勪氦闆嗭紝澶氫釜闆嗗悎鐢� keys 鎸囧畾
+ */
+ public Set sInter(String key, Collection<String> otherKeys) {
+ return setOps.intersect(key, otherKeys);
+ }
+
+ /**
+ * 杩斿洖闆嗗悎涓殑涓�涓殢鏈哄厓绱犮��
+ */
+ public <T> T sRandMember(String key) {
+ return (T) setOps.randomMember(key);
+ }
+
+ /**
+ * 杩斿洖闆嗗悎涓殑 count 涓殢鏈哄厓绱犮��
+ * 浠� Redis 2.6 鐗堟湰寮�濮嬶紝 SRANDMEMBER 鍛戒护鎺ュ彈鍙�夌殑 count 鍙傛暟锛�
+ * 濡傛灉 count 涓烘鏁帮紝涓斿皬浜庨泦鍚堝熀鏁帮紝閭d箞鍛戒护杩斿洖涓�涓寘鍚� count 涓厓绱犵殑鏁扮粍锛屾暟缁勪腑鐨勫厓绱犲悇涓嶇浉鍚屻��
+ * 濡傛灉 count 澶т簬绛変簬闆嗗悎鍩烘暟锛岄偅涔堣繑鍥炴暣涓泦鍚堛��
+ * 濡傛灉 count 涓鸿礋鏁帮紝閭d箞鍛戒护杩斿洖涓�涓暟缁勶紝鏁扮粍涓殑鍏冪礌鍙兘浼氶噸澶嶅嚭鐜板娆★紝鑰屾暟缁勭殑闀垮害涓� count 鐨勭粷瀵瑰�笺��
+ * 璇ユ搷浣滃拰 SPOP 鐩镐技锛屼絾 SPOP 灏嗛殢鏈哄厓绱犱粠闆嗗悎涓Щ闄ゅ苟杩斿洖锛岃�� SRANDMEMBER 鍒欎粎浠呰繑鍥為殢鏈哄厓绱狅紝鑰屼笉瀵归泦鍚堣繘琛屼换浣曟敼鍔ㄣ��
+ */
+ public List sRandMember(String key, int count) {
+ return setOps.randomMembers(key, count);
+ }
+
+ /**
+ * 绉婚櫎闆嗗悎 key 涓殑涓�涓垨澶氫釜 member 鍏冪礌锛屼笉瀛樺湪鐨� member 鍏冪礌浼氳蹇界暐銆�
+ */
+ public Long sRem(String key, Object... members) {
+ return setOps.remove(key, members);
+ }
+
+ /**
+ * 杩斿洖澶氫釜闆嗗悎鐨勫苟闆嗭紝澶氫釜闆嗗悎鐢� keys 鎸囧畾
+ * 涓嶅瓨鍦ㄧ殑 key 琚涓虹┖闆嗐��
+ */
+ public Set sUnion(String key, String otherKey) {
+ return setOps.union(key, otherKey);
+ }
+
+ /**
+ * 杩斿洖澶氫釜闆嗗悎鐨勫苟闆嗭紝澶氫釜闆嗗悎鐢� keys 鎸囧畾
+ * 涓嶅瓨鍦ㄧ殑 key 琚涓虹┖闆嗐��
+ */
+ public Set sUnion(String key, Collection<String> otherKeys) {
+ return setOps.union(key, otherKeys);
+ }
+
+ /**
+ * 杩斿洖涓�涓泦鍚堢殑鍏ㄩ儴鎴愬憳锛岃闆嗗悎鏄墍鏈夌粰瀹氶泦鍚堜箣闂寸殑宸泦銆�
+ * 涓嶅瓨鍦ㄧ殑 key 琚涓虹┖闆嗐��
+ */
+ public Set sDiff(String key, String otherKey) {
+ return setOps.difference(key, otherKey);
+ }
+
+ /**
+ * 杩斿洖涓�涓泦鍚堢殑鍏ㄩ儴鎴愬憳锛岃闆嗗悎鏄墍鏈夌粰瀹氶泦鍚堜箣闂寸殑宸泦銆�
+ * 涓嶅瓨鍦ㄧ殑 key 琚涓虹┖闆嗐��
+ */
+ public Set sDiff(String key, Collection<String> otherKeys) {
+ return setOps.difference(key, otherKeys);
+ }
+
+ /**
+ * 灏嗕竴涓垨澶氫釜 member 鍏冪礌鍙婂叾 score 鍊煎姞鍏ュ埌鏈夊簭闆� key 褰撲腑銆�
+ * 濡傛灉鏌愪釜 member 宸茬粡鏄湁搴忛泦鐨勬垚鍛橈紝閭d箞鏇存柊杩欎釜 member 鐨� score 鍊硷紝
+ * 骞堕�氳繃閲嶆柊鎻掑叆杩欎釜 member 鍏冪礌锛屾潵淇濊瘉璇� member 鍦ㄦ纭殑浣嶇疆涓娿��
+ */
+ public Boolean zAdd(String key, Object member, double score) {
+ return zSetOps.add(key, member, score);
+ }
+
+ /**
+ * 灏嗕竴涓垨澶氫釜 member 鍏冪礌鍙婂叾 score 鍊煎姞鍏ュ埌鏈夊簭闆� key 褰撲腑銆�
+ * 濡傛灉鏌愪釜 member 宸茬粡鏄湁搴忛泦鐨勬垚鍛橈紝閭d箞鏇存柊杩欎釜 member 鐨� score 鍊硷紝
+ * 骞堕�氳繃閲嶆柊鎻掑叆杩欎釜 member 鍏冪礌锛屾潵淇濊瘉璇� member 鍦ㄦ纭殑浣嶇疆涓娿��
+ */
+ public Long zAdd(String key, Map<Object, Double> scoreMembers) {
+ Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<>();
+ scoreMembers.forEach((k, v) -> {
+ tuples.add(new DefaultTypedTuple<>(k, v));
+ });
+ return zSetOps.add(key, tuples);
+ }
+
+ /**
+ * 杩斿洖鏈夊簭闆� key 鐨勫熀鏁般��
+ */
+ public Long zCard(String key) {
+ return zSetOps.zCard(key);
+ }
+
+ /**
+ * 杩斿洖鏈夊簭闆� key 涓紝 score 鍊煎湪 min 鍜� max 涔嬮棿(榛樿鍖呮嫭 score 鍊肩瓑浜� min 鎴� max )鐨勬垚鍛樼殑鏁伴噺銆�
+ * 鍏充簬鍙傛暟 min 鍜� max 鐨勮缁嗕娇鐢ㄦ柟娉曪紝璇峰弬鑰� ZRANGEBYSCORE 鍛戒护銆�
+ */
+ public Long zCount(String key, double min, double max) {
+ return zSetOps.count(key, min, max);
+ }
+
+ /**
+ * 涓烘湁搴忛泦 key 鐨勬垚鍛� member 鐨� score 鍊煎姞涓婂閲� increment 銆�
+ */
+ public Double zIncrBy(String key, Object member, double score) {
+ return zSetOps.incrementScore(key, member, score);
+ }
+
+ /**
+ * 杩斿洖鏈夊簭闆� key 涓紝鎸囧畾鍖洪棿鍐呯殑鎴愬憳銆�
+ * 鍏朵腑鎴愬憳鐨勪綅缃寜 score 鍊奸�掑(浠庡皬鍒板ぇ)鏉ユ帓搴忋��
+ * 鍏锋湁鐩稿悓 score 鍊肩殑鎴愬憳鎸夊瓧鍏稿簭(lexicographical order )鏉ユ帓鍒椼��
+ * 濡傛灉浣犻渶瑕佹垚鍛樻寜 score 鍊奸�掑噺(浠庡ぇ鍒板皬)鏉ユ帓鍒楋紝璇蜂娇鐢� ZREVRANGE 鍛戒护銆�
+ */
+ public Set zRange(String key, long start, long end) {
+ return zSetOps.range(key, start, end);
+ }
+
+ /**
+ * 杩斿洖鏈夊簭闆� key 涓紝鎸囧畾鍖洪棿鍐呯殑鎴愬憳銆�
+ * 鍏朵腑鎴愬憳鐨勪綅缃寜 score 鍊奸�掑噺(浠庡ぇ鍒板皬)鏉ユ帓鍒椼��
+ * 鍏锋湁鐩稿悓 score 鍊肩殑鎴愬憳鎸夊瓧鍏稿簭鐨勯�嗗簭(reverse lexicographical order)鎺掑垪銆�
+ * 闄や簡鎴愬憳鎸� score 鍊奸�掑噺鐨勬搴忔帓鍒楄繖涓�鐐瑰锛� ZREVRANGE 鍛戒护鐨勫叾浠栨柟闈㈠拰 ZRANGE 鍛戒护涓�鏍枫��
+ */
+ public Set zRevrange(String key, long start, long end) {
+ return zSetOps.reverseRange(key, start, end);
+ }
+
+ /**
+ * 杩斿洖鏈夊簭闆� key 涓紝鎵�鏈� score 鍊间粙浜� min 鍜� max 涔嬮棿(鍖呮嫭绛変簬 min 鎴� max )鐨勬垚鍛樸��
+ * 鏈夊簭闆嗘垚鍛樻寜 score 鍊奸�掑(浠庡皬鍒板ぇ)娆″簭鎺掑垪銆�
+ */
+ public Set zRangeByScore(String key, double min, double max) {
+ return zSetOps.rangeByScore(key, min, max);
+ }
+
+ /**
+ * 杩斿洖鏈夊簭闆� key 涓垚鍛� member 鐨勬帓鍚嶃�傚叾涓湁搴忛泦鎴愬憳鎸� score 鍊奸�掑(浠庡皬鍒板ぇ)椤哄簭鎺掑垪銆�
+ * 鎺掑悕浠� 0 涓哄簳锛屼篃灏辨槸璇达紝 score 鍊兼渶灏忕殑鎴愬憳鎺掑悕涓� 0 銆�
+ * 浣跨敤 ZREVRANK 鍛戒护鍙互鑾峰緱鎴愬憳鎸� score 鍊奸�掑噺(浠庡ぇ鍒板皬)鎺掑垪鐨勬帓鍚嶃��
+ */
+ public Long zRank(String key, Object member) {
+ return zSetOps.rank(key, member);
+ }
+
+ /**
+ * 杩斿洖鏈夊簭闆� key 涓垚鍛� member 鐨勬帓鍚嶃�傚叾涓湁搴忛泦鎴愬憳鎸� score 鍊奸�掑噺(浠庡ぇ鍒板皬)鎺掑簭銆�
+ * 鎺掑悕浠� 0 涓哄簳锛屼篃灏辨槸璇达紝 score 鍊兼渶澶х殑鎴愬憳鎺掑悕涓� 0 銆�
+ * 浣跨敤 ZRANK 鍛戒护鍙互鑾峰緱鎴愬憳鎸� score 鍊奸�掑(浠庡皬鍒板ぇ)鎺掑垪鐨勬帓鍚嶃��
+ */
+ public Long zRevrank(String key, Object member) {
+ return zSetOps.reverseRank(key, member);
+ }
+
+ /**
+ * 绉婚櫎鏈夊簭闆� key 涓殑涓�涓垨澶氫釜鎴愬憳锛屼笉瀛樺湪鐨勬垚鍛樺皢琚拷鐣ャ��
+ * 褰� key 瀛樺湪浣嗕笉鏄湁搴忛泦绫诲瀷鏃讹紝杩斿洖涓�涓敊璇��
+ */
+ public Long zRem(String key, Object... members) {
+ return zSetOps.remove(key, members);
+ }
+
+ /**
+ * 杩斿洖鏈夊簭闆� key 涓紝鎴愬憳 member 鐨� score 鍊笺��
+ * 濡傛灉 member 鍏冪礌涓嶆槸鏈夊簭闆� key 鐨勬垚鍛橈紝鎴� key 涓嶅瓨鍦紝杩斿洖 nil 銆�
+ */
+ public Double zScore(String key, Object member) {
+ return zSetOps.score(key, member);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/CacheKey.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/CacheKey.java
new file mode 100644
index 0000000..cf768eb
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/CacheKey.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.cache;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.ToString;
+import org.springframework.lang.Nullable;
+
+import java.time.Duration;
+
+/**
+ * cache key 灏佽
+ *
+ * @author L.cm
+ */
+@Getter
+@ToString
+@AllArgsConstructor
+public class CacheKey {
+ /**
+ * redis key
+ */
+ private final String key;
+ /**
+ * 瓒呮椂鏃堕棿 绉�
+ */
+ @Nullable
+ private Duration expire;
+
+ public CacheKey(String key) {
+ this.key = key;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/ICacheKey.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/ICacheKey.java
new file mode 100644
index 0000000..3ed6efb
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/ICacheKey.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.cache;
+
+
+import org.springblade.core.tool.utils.ObjectUtil;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.lang.Nullable;
+
+import java.time.Duration;
+
+/**
+ * cache key
+ *
+ * @author L.cm
+ */
+public interface ICacheKey {
+
+ /**
+ * 鑾峰彇鍓嶇紑
+ *
+ * @return key 鍓嶇紑
+ */
+ String getPrefix();
+
+ /**
+ * 瓒呮椂鏃堕棿
+ *
+ * @return 瓒呮椂鏃堕棿
+ */
+ @Nullable
+ default Duration getExpire() {
+ return null;
+ }
+
+ /**
+ * 缁勮 cache key
+ *
+ * @param suffix 鍙傛暟
+ * @return cache key
+ */
+ default CacheKey getKey(Object... suffix) {
+ String prefix = this.getPrefix();
+ // 鎷兼帴鍙傛暟
+ String key;
+ if (ObjectUtil.isEmpty(suffix)) {
+ key = prefix;
+ } else {
+ key = prefix.concat(StringUtil.join(suffix, StringPool.COLON));
+ }
+ Duration expire = this.getExpire();
+ return expire == null ? new CacheKey(key) : new CacheKey(key, expire);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/BladeRedisCacheAutoConfiguration.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/BladeRedisCacheAutoConfiguration.java
new file mode 100644
index 0000000..1c1ce1b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/BladeRedisCacheAutoConfiguration.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.config;
+
+import org.springblade.core.jwt.config.JwtRedisConfiguration;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizers;
+import org.springframework.boot.autoconfigure.cache.CacheProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.lang.Nullable;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 鎵╁睍redis-cache鏀寔娉ㄨВcacheName娣诲姞瓒呮椂鏃堕棿
+ * <p>
+ *
+ * @author L.cm
+ */
+@AutoConfiguration(before = JwtRedisConfiguration.class)
+@EnableConfigurationProperties(CacheProperties.class)
+public class BladeRedisCacheAutoConfiguration {
+
+ /**
+ * 搴忓垪鍖栨柟寮�
+ */
+ private final RedisSerializer<Object> redisSerializer;
+ private final CacheProperties cacheProperties;
+ private final CacheManagerCustomizers customizerInvoker;
+ @Nullable
+ private final RedisCacheConfiguration redisCacheConfiguration;
+
+ BladeRedisCacheAutoConfiguration(RedisSerializer<Object> redisSerializer,
+ CacheProperties cacheProperties,
+ CacheManagerCustomizers customizerInvoker,
+ ObjectProvider<RedisCacheConfiguration> redisCacheConfiguration) {
+ this.redisSerializer = redisSerializer;
+ this.cacheProperties = cacheProperties;
+ this.customizerInvoker = customizerInvoker;
+ this.redisCacheConfiguration = redisCacheConfiguration.getIfAvailable();
+ }
+
+ @Primary
+ @Bean("redisCacheManager")
+ public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
+ RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
+ RedisCacheConfiguration cacheConfiguration = this.determineConfiguration();
+ List<String> cacheNames = this.cacheProperties.getCacheNames();
+ Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>();
+ if (!cacheNames.isEmpty()) {
+ Map<String, RedisCacheConfiguration> cacheConfigMap = new LinkedHashMap<>(cacheNames.size());
+ cacheNames.forEach(it -> cacheConfigMap.put(it, cacheConfiguration));
+ initialCaches.putAll(cacheConfigMap);
+ }
+ boolean allowInFlightCacheCreation = true;
+ boolean enableTransactions = false;
+ RedisAutoCacheManager cacheManager = new RedisAutoCacheManager(redisCacheWriter, cacheConfiguration, initialCaches, allowInFlightCacheCreation);
+ cacheManager.setTransactionAware(enableTransactions);
+ return this.customizerInvoker.customize(cacheManager);
+ }
+
+ private RedisCacheConfiguration determineConfiguration() {
+ if (this.redisCacheConfiguration != null) {
+ return this.redisCacheConfiguration;
+ } else {
+ CacheProperties.Redis redisProperties = this.cacheProperties.getRedis();
+ RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
+ config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer));
+ if (redisProperties.getTimeToLive() != null) {
+ config = config.entryTtl(redisProperties.getTimeToLive());
+ }
+
+ if (redisProperties.getKeyPrefix() != null) {
+ config = config.prefixKeysWith(redisProperties.getKeyPrefix());
+ }
+
+ if (!redisProperties.isCacheNullValues()) {
+ config = config.disableCachingNullValues();
+ }
+
+ if (!redisProperties.isUseKeyPrefix()) {
+ config = config.disableKeyPrefix();
+ }
+
+ return config;
+ }
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/BladeRedisProperties.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/BladeRedisProperties.java
new file mode 100644
index 0000000..9d9d976
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/BladeRedisProperties.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.config;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * redis 閰嶇疆
+ *
+ * @author L.cm
+ */
+@Getter
+@Setter
+@ConfigurationProperties("blade.redis")
+public class BladeRedisProperties {
+
+ /**
+ * 搴忓垪鍖栨柟寮�
+ */
+ private SerializerType serializerType = SerializerType.ProtoStuff;
+
+ public enum SerializerType {
+ /**
+ * 榛樿:ProtoStuff 搴忓垪鍖�
+ */
+ ProtoStuff,
+ /**
+ * json 搴忓垪鍖�
+ */
+ JSON,
+ /**
+ * jdk 搴忓垪鍖�
+ */
+ JDK
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/BladeRedisSerializerConfigAble.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/BladeRedisSerializerConfigAble.java
new file mode 100644
index 0000000..0f40117
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/BladeRedisSerializerConfigAble.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.config;
+
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializer;
+
+/**
+ * redis 搴忓垪鍖�
+ *
+ * @author L.cm
+ */
+public interface BladeRedisSerializerConfigAble {
+
+ /**
+ * JSON搴忓垪鍖栫被鍨嬪瓧娈�
+ */
+ String TYPE_NAME = "@class";
+
+ /**
+ * 搴忓垪鍖栨帴鍙�
+ *
+ * @param properties 閰嶇疆
+ * @return RedisSerializer
+ */
+ RedisSerializer<Object> redisSerializer(BladeRedisProperties properties);
+
+ /**
+ * 榛樿鐨勫簭鍒楀寲鏂瑰紡
+ *
+ * @param properties 閰嶇疆
+ * @return RedisSerializer
+ */
+ default RedisSerializer<Object> defaultRedisSerializer(BladeRedisProperties properties) {
+ BladeRedisProperties.SerializerType serializerType = properties.getSerializerType();
+ if (BladeRedisProperties.SerializerType.JDK == serializerType) {
+ /**
+ * SpringBoot鎵╁睍浜咰lassLoader锛岃繘琛屽垎绂绘墦鍖呯殑鏃跺�欙紝浣跨敤鍒癑dkSerializationRedisSerializer鐨勫湴鏂�
+ * 浼氬洜涓篊lassLoader鐨勪笉鍚屽鑷村姞杞戒笉鍒癈lass
+ * 鎸囧畾浣跨敤椤圭洰鐨凜lassLoader
+ *
+ * JdkSerializationRedisSerializer榛樿浣跨敤{@link sun.misc.Launcher.AppClassLoader}
+ * SpringBoot榛樿浣跨敤{@link org.springframework.boot.loader.LaunchedURLClassLoader}
+ */
+ ClassLoader classLoader = this.getClass().getClassLoader();
+ return new JdkSerializationRedisSerializer(classLoader);
+ }
+ return new GenericJackson2JsonRedisSerializer(TYPE_NAME);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/ProtoStuffSerializerConfiguration.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/ProtoStuffSerializerConfiguration.java
new file mode 100644
index 0000000..49508cd
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/ProtoStuffSerializerConfiguration.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.config;
+
+import org.springblade.core.redis.serializer.ProtoStuffSerializer;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.redis.serializer.RedisSerializer;
+
+/**
+ * ProtoStuff 搴忓垪鍖栭厤缃�
+ *
+ * @author L.cm
+ */
+@AutoConfiguration(before = RedisTemplateConfiguration.class)
+@ConditionalOnClass(name = "io.protostuff.Schema")
+public class ProtoStuffSerializerConfiguration implements BladeRedisSerializerConfigAble {
+
+ @Bean
+ @ConditionalOnMissingBean
+ @Override
+ public RedisSerializer<Object> redisSerializer(BladeRedisProperties properties) {
+ if (BladeRedisProperties.SerializerType.ProtoStuff == properties.getSerializerType()) {
+ return new ProtoStuffSerializer();
+ }
+ return defaultRedisSerializer(properties);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RateLimiterAutoConfiguration.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RateLimiterAutoConfiguration.java
new file mode 100644
index 0000000..01e203d
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RateLimiterAutoConfiguration.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.config;
+
+import org.springblade.core.redis.ratelimiter.RedisRateLimiterAspect;
+import org.springblade.core.redis.ratelimiter.RedisRateLimiterClient;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.env.Environment;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.data.redis.core.script.RedisScript;
+import org.springframework.scripting.support.ResourceScriptSource;
+
+import java.util.List;
+
+/**
+ * 鍩轰簬 redis 鐨勫垎甯冨紡闄愭祦鑷姩閰嶇疆
+ *
+ * @author L.cm
+ */
+@AutoConfiguration
+@ConditionalOnProperty(value = "blade.redis.rate-limiter.enabled", havingValue = "true")
+public class RateLimiterAutoConfiguration {
+
+ @SuppressWarnings("unchecked")
+ private RedisScript<List<Long>> redisRateLimiterScript() {
+ DefaultRedisScript redisScript = new DefaultRedisScript<>();
+ redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("META-INF/scripts/blade_rate_limiter.lua")));
+ redisScript.setResultType(List.class);
+ return redisScript;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public RedisRateLimiterClient redisRateLimiter(StringRedisTemplate redisTemplate, Environment environment) {
+ RedisScript<List<Long>> redisRateLimiterScript = redisRateLimiterScript();
+ return new RedisRateLimiterClient(redisTemplate, redisRateLimiterScript, environment);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public RedisRateLimiterAspect redisRateLimiterAspect(RedisRateLimiterClient rateLimiterClient) {
+ return new RedisRateLimiterAspect(rateLimiterClient);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisAutoCacheManager.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisAutoCacheManager.java
new file mode 100644
index 0000000..d277c07
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisAutoCacheManager.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.config;
+
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.boot.convert.DurationStyle;
+import org.springframework.data.redis.cache.RedisCache;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.Map;
+
+/**
+ * redis cache 鎵╁睍cache name鑷姩鍖栭厤缃�
+ *
+ * @author L.cm
+ */
+public class RedisAutoCacheManager extends RedisCacheManager {
+
+ public RedisAutoCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration,
+ Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) {
+ super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation);
+ }
+
+ @NonNull
+ @Override
+ protected RedisCache createRedisCache(@NonNull String name, @Nullable RedisCacheConfiguration cacheConfig) {
+ if (StringUtil.isBlank(name) || !name.contains(StringPool.HASH)) {
+ return super.createRedisCache(name, cacheConfig);
+ }
+ String[] cacheArray = name.split(StringPool.HASH);
+ if (cacheArray.length < 2) {
+ return super.createRedisCache(name, cacheConfig);
+ }
+ String cacheName = cacheArray[0];
+ if (cacheConfig != null) {
+ Duration cacheAge = DurationStyle.detectAndParse(cacheArray[1], ChronoUnit.SECONDS);;
+ cacheConfig = cacheConfig.entryTtl(cacheAge);
+ }
+ return super.createRedisCache(cacheName, cacheConfig);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisCacheManagerConfig.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisCacheManagerConfig.java
new file mode 100644
index 0000000..59eed08
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisCacheManagerConfig.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.config;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;
+import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizers;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+
+import java.util.List;
+
+/**
+ * CacheManagerCustomizers閰嶇疆
+ *
+ * @author L.cm
+ */
+@AutoConfiguration
+@ConditionalOnMissingBean(CacheManagerCustomizers.class)
+public class RedisCacheManagerConfig {
+
+ @Bean
+ public CacheManagerCustomizers cacheManagerCustomizers(
+ ObjectProvider<List<CacheManagerCustomizer<?>>> customizers) {
+ return new CacheManagerCustomizers(customizers.getIfAvailable());
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisTemplateConfiguration.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisTemplateConfiguration.java
new file mode 100644
index 0000000..43fb6bb
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisTemplateConfiguration.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.config;
+
+import org.springblade.core.jwt.config.JwtRedisConfiguration;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.redis.serializer.RedisKeySerializer;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.data.redis.serializer.RedisSerializer;
+
+/**
+ * RedisTemplate 閰嶇疆
+ *
+ * @author L.cm
+ */
+@EnableCaching
+@AutoConfiguration(before = {JwtRedisConfiguration.class, RedisAutoConfiguration.class})
+@EnableConfigurationProperties(BladeRedisProperties.class)
+public class RedisTemplateConfiguration implements BladeRedisSerializerConfigAble {
+
+ /**
+ * value 鍊� 搴忓垪鍖�
+ *
+ * @return RedisSerializer
+ */
+ @Bean
+ @ConditionalOnMissingBean(RedisSerializer.class)
+ @Override
+ public RedisSerializer<Object> redisSerializer(BladeRedisProperties properties) {
+ return defaultRedisSerializer(properties);
+ }
+
+ @Bean(name = "redisTemplate")
+ @ConditionalOnMissingBean(name = "redisTemplate")
+ public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory, RedisSerializer<Object> redisSerializer) {
+ RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
+ // key 搴忓垪鍖�
+ RedisKeySerializer keySerializer = new RedisKeySerializer();
+ redisTemplate.setKeySerializer(keySerializer);
+ redisTemplate.setHashKeySerializer(keySerializer);
+ // value 搴忓垪鍖�
+ redisTemplate.setValueSerializer(redisSerializer);
+ redisTemplate.setHashValueSerializer(redisSerializer);
+ redisTemplate.setConnectionFactory(redisConnectionFactory);
+ return redisTemplate;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(ValueOperations.class)
+ public ValueOperations valueOperations(RedisTemplate redisTemplate) {
+ return redisTemplate.opsForValue();
+ }
+
+ @Bean
+ public BladeRedis bladeRedis(RedisTemplate<String, Object> redisTemplate, StringRedisTemplate stringRedisTemplate) {
+ return new BladeRedis(redisTemplate, stringRedisTemplate);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/BladeLockAutoConfiguration.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/BladeLockAutoConfiguration.java
new file mode 100644
index 0000000..848e827
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/BladeLockAutoConfiguration.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.lock;
+
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.config.*;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * 鍒嗗竷寮忛攣鑷姩鍖栭厤缃�
+ *
+ * @author L.cm
+ */
+@AutoConfiguration
+@ConditionalOnClass(RedissonClient.class)
+@EnableConfigurationProperties(BladeLockProperties.class)
+@ConditionalOnProperty(value = "blade.lock.enabled", havingValue = "true")
+public class BladeLockAutoConfiguration {
+
+ private static Config singleConfig(BladeLockProperties properties) {
+ Config config = new Config();
+ SingleServerConfig serversConfig = config.useSingleServer();
+ serversConfig.setAddress(properties.getAddress());
+ String password = properties.getPassword();
+ if (StringUtil.isNotBlank(password)) {
+ serversConfig.setPassword(password);
+ }
+ serversConfig.setDatabase(properties.getDatabase());
+ serversConfig.setConnectionPoolSize(properties.getPoolSize());
+ serversConfig.setConnectionMinimumIdleSize(properties.getIdleSize());
+ serversConfig.setIdleConnectionTimeout(properties.getConnectionTimeout());
+ serversConfig.setConnectTimeout(properties.getConnectionTimeout());
+ serversConfig.setTimeout(properties.getTimeout());
+ return config;
+ }
+
+ private static Config masterSlaveConfig(BladeLockProperties properties) {
+ Config config = new Config();
+ MasterSlaveServersConfig serversConfig = config.useMasterSlaveServers();
+ serversConfig.setMasterAddress(properties.getMasterAddress());
+ serversConfig.addSlaveAddress(properties.getSlaveAddress());
+ String password = properties.getPassword();
+ if (StringUtil.isNotBlank(password)) {
+ serversConfig.setPassword(password);
+ }
+ serversConfig.setDatabase(properties.getDatabase());
+ serversConfig.setMasterConnectionPoolSize(properties.getPoolSize());
+ serversConfig.setMasterConnectionMinimumIdleSize(properties.getIdleSize());
+ serversConfig.setSlaveConnectionPoolSize(properties.getPoolSize());
+ serversConfig.setSlaveConnectionMinimumIdleSize(properties.getIdleSize());
+ serversConfig.setIdleConnectionTimeout(properties.getConnectionTimeout());
+ serversConfig.setConnectTimeout(properties.getConnectionTimeout());
+ serversConfig.setTimeout(properties.getTimeout());
+ return config;
+ }
+
+ private static Config sentinelConfig(BladeLockProperties properties) {
+ Config config = new Config();
+ SentinelServersConfig serversConfig = config.useSentinelServers();
+ serversConfig.setMasterName(properties.getMasterName());
+ serversConfig.addSentinelAddress(properties.getSentinelAddress());
+ String password = properties.getPassword();
+ if (StringUtil.isNotBlank(password)) {
+ serversConfig.setPassword(password);
+ }
+ serversConfig.setDatabase(properties.getDatabase());
+ serversConfig.setMasterConnectionPoolSize(properties.getPoolSize());
+ serversConfig.setMasterConnectionMinimumIdleSize(properties.getIdleSize());
+ serversConfig.setSlaveConnectionPoolSize(properties.getPoolSize());
+ serversConfig.setSlaveConnectionMinimumIdleSize(properties.getIdleSize());
+ serversConfig.setIdleConnectionTimeout(properties.getConnectionTimeout());
+ serversConfig.setConnectTimeout(properties.getConnectionTimeout());
+ serversConfig.setTimeout(properties.getTimeout());
+ return config;
+ }
+
+ private static Config clusterConfig(BladeLockProperties properties) {
+ Config config = new Config();
+ ClusterServersConfig serversConfig = config.useClusterServers();
+ serversConfig.addNodeAddress(properties.getNodeAddress());
+ String password = properties.getPassword();
+ if (StringUtil.isNotBlank(password)) {
+ serversConfig.setPassword(password);
+ }
+ serversConfig.setMasterConnectionPoolSize(properties.getPoolSize());
+ serversConfig.setMasterConnectionMinimumIdleSize(properties.getIdleSize());
+ serversConfig.setSlaveConnectionPoolSize(properties.getPoolSize());
+ serversConfig.setSlaveConnectionMinimumIdleSize(properties.getIdleSize());
+ serversConfig.setIdleConnectionTimeout(properties.getConnectionTimeout());
+ serversConfig.setConnectTimeout(properties.getConnectionTimeout());
+ serversConfig.setTimeout(properties.getTimeout());
+ return config;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public RedisLockClient redisLockClient(BladeLockProperties properties) {
+ return new RedisLockClientImpl(redissonClient(properties));
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public RedisLockAspect redisLockAspect(RedisLockClient redisLockClient) {
+ return new RedisLockAspect(redisLockClient);
+ }
+
+ private static RedissonClient redissonClient(BladeLockProperties properties) {
+ BladeLockProperties.Mode mode = properties.getMode();
+ Config config;
+ switch (mode) {
+ case sentinel:
+ config = sentinelConfig(properties);
+ break;
+ case cluster:
+ config = clusterConfig(properties);
+ break;
+ case master:
+ config = masterSlaveConfig(properties);
+ break;
+ case single:
+ config = singleConfig(properties);
+ break;
+ default:
+ config = new Config();
+ break;
+ }
+ return Redisson.create(config);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/BladeLockProperties.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/BladeLockProperties.java
new file mode 100644
index 0000000..8667445
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/BladeLockProperties.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.lock;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * 鍒嗗竷寮忛攣閰嶇疆
+ *
+ * @author L.cm
+ */
+@Getter
+@Setter
+@ConfigurationProperties(BladeLockProperties.PREFIX)
+public class BladeLockProperties {
+ public static final String PREFIX = "blade.lock";
+
+ /**
+ * 鏄惁寮�鍚細榛樿涓猴細false锛屼究浜庣敓鎴愰厤缃彁绀恒��
+ */
+ private Boolean enabled = Boolean.FALSE;
+ /**
+ * 鍗曟満閰嶇疆锛歳edis 鏈嶅姟鍦板潃
+ */
+ private String address = "redis://127.0.0.1:6379";
+ /**
+ * 瀵嗙爜閰嶇疆
+ */
+ private String password;
+ /**
+ * db
+ */
+ private Integer database = 0;
+ /**
+ * 杩炴帴姹犲ぇ灏�
+ */
+ private Integer poolSize = 20;
+ /**
+ * 鏈�灏忕┖闂茶繛鎺ユ暟
+ */
+ private Integer idleSize = 5;
+ /**
+ * 杩炴帴绌洪棽瓒呮椂锛屽崟浣嶏細姣
+ */
+ private Integer idleTimeout = 60000;
+ /**
+ * 杩炴帴瓒呮椂锛屽崟浣嶏細姣
+ */
+ private Integer connectionTimeout = 3000;
+ /**
+ * 鍛戒护绛夊緟瓒呮椂锛屽崟浣嶏細姣
+ */
+ private Integer timeout = 10000;
+ /**
+ * 闆嗙兢妯″紡锛屽崟鏈猴細single锛屼富浠庯細master锛屽摠鍏垫ā寮忥細sentinel锛岄泦缇ゆā寮忥細cluster
+ */
+ private Mode mode = Mode.single;
+ /**
+ * 涓讳粠妯″紡锛屼富鍦板潃
+ */
+ private String masterAddress;
+ /**
+ * 涓讳粠妯″紡锛屼粠鍦板潃
+ */
+ private String[] slaveAddress;
+ /**
+ * 鍝ㄥ叺妯″紡锛氫富鍚嶇О
+ */
+ private String masterName;
+ /**
+ * 鍝ㄥ叺妯″紡鍦板潃
+ */
+ private String[] sentinelAddress;
+ /**
+ * 闆嗙兢妯″紡鑺傜偣鍦板潃
+ */
+ private String[] nodeAddress;
+
+ public enum Mode {
+ /**
+ * 闆嗙兢妯″紡锛屽崟鏈猴細single锛屼富浠庯細master锛屽摠鍏垫ā寮忥細sentinel锛岄泦缇ゆā寮忥細cluster
+ */
+ single,
+ master,
+ sentinel,
+ cluster
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/LockType.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/LockType.java
new file mode 100644
index 0000000..1cf6954
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/LockType.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.lock;
+
+/**
+ * 閿佺被鍨�
+ *
+ * @author lcm
+ */
+public enum LockType {
+ /**
+ * 閲嶅叆閿�
+ */
+ REENTRANT,
+ /**
+ * 鍏钩閿�
+ */
+ FAIR
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/RedisLock.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/RedisLock.java
new file mode 100644
index 0000000..d8d20c6
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/RedisLock.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.lock;
+
+import java.lang.annotation.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 鍒嗗竷寮忛攣娉ㄨВ锛宺edisson锛屾敮鎸佺殑閿佺殑绉嶇被鏈夊緢澶氾紝閫傚悎娉ㄨВ褰㈠紡鐨勫彧鏈夐噸鍏ラ攣銆佸叕骞抽攣
+ *
+ * <p>
+ * 1. 鍙噸鍏ラ攣锛圧eentrant Lock锛�
+ * 2. 鍏钩閿侊紙Fair Lock锛�
+ * 3. 鑱旈攣锛圡ultiLock锛�
+ * 4. 绾㈤攣锛圧edLock锛�
+ * 5. 璇诲啓閿侊紙ReadWriteLock锛�
+ * </p>
+ *
+ * @author L.cm
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Documented
+public @interface RedisLock {
+
+ /**
+ * 鍒嗗竷寮忛攣鐨� key锛屽繀椤伙細璇蜂繚鎸佸敮涓�鎬�
+ *
+ * @return key
+ */
+ String value();
+
+ /**
+ * 鍒嗗竷寮忛攣鍙傛暟锛屽彲閫夛紝鏀寔 spring el # 璇诲彇鏂规硶鍙傛暟鍜� @ 璇诲彇 spring bean
+ *
+ * @return param
+ */
+ String param() default "";
+
+ /**
+ * 绛夊緟閿佽秴鏃舵椂闂达紝榛樿30
+ *
+ * @return int
+ */
+ long waitTime() default 30;
+
+ /**
+ * 鑷姩瑙i攣鏃堕棿锛岃嚜鍔ㄨВ閿佹椂闂翠竴瀹氬緱澶т簬鏂规硶鎵ц鏃堕棿锛屽惁鍒欎細瀵艰嚧閿佹彁鍓嶉噴鏀撅紝榛樿100
+ *
+ * @return int
+ */
+ long leaseTime() default 100;
+
+ /**
+ * 鏃堕棿鍗曟俯锛岄粯璁や负绉�
+ *
+ * @return 鏃堕棿鍗曚綅
+ */
+ TimeUnit timeUnit() default TimeUnit.SECONDS;
+
+ /**
+ * 榛樿鍏钩閿�
+ *
+ * @return LockType
+ */
+ LockType type() default LockType.FAIR;
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/RedisLockAspect.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/RedisLockAspect.java
new file mode 100644
index 0000000..c0a03f7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/RedisLockAspect.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.lock;
+
+import lombok.RequiredArgsConstructor;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springblade.core.tool.spel.BladeExpressionEvaluator;
+import org.springblade.core.tool.utils.CharPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.expression.AnnotatedElementKey;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.util.Assert;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * redis 鍒嗗竷寮忛攣
+ *
+ * @author L.cm
+ */
+@Aspect
+@RequiredArgsConstructor
+public class RedisLockAspect implements ApplicationContextAware {
+
+ /**
+ * 琛ㄨ揪寮忓鐞�
+ */
+ private static final BladeExpressionEvaluator EVALUATOR = new BladeExpressionEvaluator();
+ /**
+ * redis 闄愭祦鏈嶅姟
+ */
+ private final RedisLockClient redisLockClient;
+ private ApplicationContext applicationContext;
+
+ /**
+ * AOP 鐜垏 娉ㄨВ @RedisLock
+ */
+ @Around("@annotation(redisLock)")
+ public Object aroundRedisLock(ProceedingJoinPoint point, RedisLock redisLock) {
+ String lockName = redisLock.value();
+ Assert.hasText(lockName, "@RedisLock value must have length; it must not be null or empty");
+ // el 琛ㄨ揪寮�
+ String lockParam = redisLock.param();
+ // 琛ㄨ揪寮忎笉涓虹┖
+ String lockKey;
+ if (StringUtil.isNotBlank(lockParam)) {
+ String evalAsText = evalLockParam(point, lockParam);
+ lockKey = lockName + CharPool.COLON + evalAsText;
+ } else {
+ lockKey = lockName;
+ }
+ LockType lockType = redisLock.type();
+ long waitTime = redisLock.waitTime();
+ long leaseTime = redisLock.leaseTime();
+ TimeUnit timeUnit = redisLock.timeUnit();
+ return redisLockClient.lock(lockKey, lockType, waitTime, leaseTime, timeUnit, point::proceed);
+ }
+
+ /**
+ * 璁$畻鍙傛暟琛ㄨ揪寮�
+ *
+ * @param point ProceedingJoinPoint
+ * @param lockParam lockParam
+ * @return 缁撴灉
+ */
+ private String evalLockParam(ProceedingJoinPoint point, String lockParam) {
+ MethodSignature ms = (MethodSignature) point.getSignature();
+ Method method = ms.getMethod();
+ Object[] args = point.getArgs();
+ Object target = point.getTarget();
+ Class<?> targetClass = target.getClass();
+ EvaluationContext context = EVALUATOR.createContext(method, args, target, targetClass, applicationContext);
+ AnnotatedElementKey elementKey = new AnnotatedElementKey(method, targetClass);
+ return EVALUATOR.evalAsText(lockParam, elementKey, context);
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/RedisLockClient.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/RedisLockClient.java
new file mode 100644
index 0000000..51dba71
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/RedisLockClient.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.lock;
+
+import org.springblade.core.tool.function.CheckedSupplier;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 閿佸鎴风
+ *
+ * @author L.cm
+ */
+public interface RedisLockClient {
+
+ /**
+ * 灏濊瘯鑾峰彇閿�
+ *
+ * @param lockName 閿佸悕
+ * @param lockType 閿佺被鍨�
+ * @param waitTime 绛夊緟鏃堕棿
+ * @param leaseTime 鑷姩瑙i攣鏃堕棿锛岃嚜鍔ㄨВ閿佹椂闂翠竴瀹氬緱澶т簬鏂规硶鎵ц鏃堕棿
+ * @param timeUnit 鏃堕棿鍙傛暟
+ * @return 鏄惁鎴愬姛
+ * @throws InterruptedException InterruptedException
+ */
+ boolean tryLock(String lockName, LockType lockType, long waitTime, long leaseTime, TimeUnit timeUnit) throws InterruptedException;
+
+ /**
+ * 瑙i攣
+ *
+ * @param lockName 閿佸悕
+ * @param lockType 閿佺被鍨�
+ */
+ void unLock(String lockName, LockType lockType);
+
+ /**
+ * 鑷畾鑾峰彇閿佸悗鎵ц鏂规硶
+ *
+ * @param lockName 閿佸悕
+ * @param lockType 閿佺被鍨�
+ * @param waitTime 绛夊緟閿佽秴鏃舵椂闂�
+ * @param leaseTime 鑷姩瑙i攣鏃堕棿锛岃嚜鍔ㄨВ閿佹椂闂翠竴瀹氬緱澶т簬鏂规硶鎵ц鏃堕棿锛屽惁鍒欎細瀵艰嚧閿佹彁鍓嶉噴鏀撅紝榛樿100
+ * @param timeUnit 鏃堕棿鍗曚綅
+ * @param supplier 鑾峰彇閿佸悗鐨勫洖璋�
+ * @return 杩斿洖鐨勬暟鎹�
+ */
+ <T> T lock(String lockName, LockType lockType, long waitTime, long leaseTime, TimeUnit timeUnit, CheckedSupplier<T> supplier);
+
+ /**
+ * 鍏钩閿�
+ *
+ * @param lockName 閿佸悕
+ * @param waitTime 绛夊緟閿佽秴鏃舵椂闂�
+ * @param leaseTime 鑷姩瑙i攣鏃堕棿锛岃嚜鍔ㄨВ閿佹椂闂翠竴瀹氬緱澶т簬鏂规硶鎵ц鏃堕棿锛屽惁鍒欎細瀵艰嚧閿佹彁鍓嶉噴鏀撅紝榛樿100
+ * @param supplier 鑾峰彇閿佸悗鐨勫洖璋�
+ * @return 杩斿洖鐨勬暟鎹�
+ */
+ default <T> T lockFair(String lockName, long waitTime, long leaseTime, CheckedSupplier<T> supplier) {
+ return lock(lockName, LockType.FAIR, waitTime, leaseTime, TimeUnit.SECONDS, supplier);
+ }
+
+ /**
+ * 鍙噸鍏ラ攣
+ *
+ * @param lockName 閿佸悕
+ * @param waitTime 绛夊緟閿佽秴鏃舵椂闂�
+ * @param leaseTime 鑷姩瑙i攣鏃堕棿锛岃嚜鍔ㄨВ閿佹椂闂翠竴瀹氬緱澶т簬鏂规硶鎵ц鏃堕棿锛屽惁鍒欎細瀵艰嚧閿佹彁鍓嶉噴鏀撅紝榛樿100
+ * @param supplier 鑾峰彇閿佸悗鐨勫洖璋�
+ * @return 杩斿洖鐨勬暟鎹�
+ */
+ default <T> T lockReentrant(String lockName, long waitTime, long leaseTime, CheckedSupplier<T> supplier) {
+ return lock(lockName, LockType.REENTRANT, waitTime, leaseTime, TimeUnit.SECONDS, supplier);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/RedisLockClientImpl.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/RedisLockClientImpl.java
new file mode 100644
index 0000000..597c1c5
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/lock/RedisLockClientImpl.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.lock;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.springblade.core.tool.function.CheckedSupplier;
+import org.springblade.core.tool.utils.Exceptions;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 閿佸鎴风
+ *
+ * @author L.cm
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class RedisLockClientImpl implements RedisLockClient {
+ private final RedissonClient redissonClient;
+
+ @Override
+ public boolean tryLock(String lockName, LockType lockType, long waitTime, long leaseTime, TimeUnit timeUnit) throws InterruptedException {
+ RLock lock = getLock(lockName, lockType);
+ return lock.tryLock(waitTime, leaseTime, timeUnit);
+ }
+
+ @Override
+ public void unLock(String lockName, LockType lockType) {
+ RLock lock = getLock(lockName, lockType);
+ // 浠呬粎鍦ㄥ凡缁忛攣瀹氬拰褰撳墠绾跨▼鎸佹湁閿佹椂瑙i攣
+ if (lock.isLocked() && lock.isHeldByCurrentThread()) {
+ lock.unlock();
+ }
+ }
+
+ private RLock getLock(String lockName, LockType lockType) {
+ RLock lock;
+ if (LockType.REENTRANT == lockType) {
+ lock = redissonClient.getLock(lockName);
+ } else {
+ lock = redissonClient.getFairLock(lockName);
+ }
+ return lock;
+ }
+
+ @Override
+ public <T> T lock(String lockName, LockType lockType, long waitTime, long leaseTime, TimeUnit timeUnit, CheckedSupplier<T> supplier) {
+ try {
+ boolean result = this.tryLock(lockName, lockType, waitTime, leaseTime, timeUnit);
+ if (result) {
+ return supplier.get();
+ }
+ } catch (Throwable e) {
+ throw Exceptions.unchecked(e);
+ } finally {
+ this.unLock(lockName, lockType);
+ }
+ return null;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RateLimiter.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RateLimiter.java
new file mode 100644
index 0000000..e4c7c76
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RateLimiter.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.ratelimiter;
+
+import java.lang.annotation.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 鍒嗗竷寮� 闄愭祦娉ㄨВ锛岄粯璁ら�熺巼涓� 600/ms
+ *
+ * @author L.cm
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Documented
+public @interface RateLimiter {
+
+ /**
+ * 闄愭祦鐨� key 鏀寔锛屽繀椤伙細璇蜂繚鎸佸敮涓�鎬�
+ *
+ * @return key
+ */
+ String value();
+
+ /**
+ * 闄愭祦鐨勫弬鏁帮紝鍙�夛紝鏀寔 spring el # 璇诲彇鏂规硶鍙傛暟鍜� @ 璇诲彇 spring bean
+ *
+ * @return param
+ */
+ String param() default "";
+
+ /**
+ * 鏀寔鐨勬渶澶ц姹傦紝榛樿: 100
+ *
+ * @return 璇锋眰鏁�
+ */
+ long max() default 100L;
+
+ /**
+ * 鎸佺画鏃堕棿锛岄粯璁�: 3600
+ *
+ * @return 鎸佺画鏃堕棿
+ */
+ long ttl() default 1L;
+
+ /**
+ * 鏃堕棿鍗曚綅锛岄粯璁や负鍒�
+ *
+ * @return TimeUnit
+ */
+ TimeUnit timeUnit() default TimeUnit.MINUTES;
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RateLimiterClient.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RateLimiterClient.java
new file mode 100644
index 0000000..5a13545
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RateLimiterClient.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.ratelimiter;
+
+
+import org.springblade.core.tool.function.CheckedSupplier;
+import org.springblade.core.tool.utils.Exceptions;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * RateLimiter 闄愭祦 Client
+ *
+ * @author L.cm
+ */
+public interface RateLimiterClient {
+
+ /**
+ * 鏈嶅姟鏄惁琚檺娴�
+ *
+ * @param key 鑷畾涔夌殑key锛岃淇濊瘉鍞竴
+ * @param max 鏀寔鐨勬渶澶ц姹�
+ * @param ttl 鏃堕棿,鍗曚綅榛樿涓虹锛坰econds锛�
+ * @return 鏄惁鍏佽
+ */
+ default boolean isAllowed(String key, long max, long ttl) {
+ return this.isAllowed(key, max, ttl, TimeUnit.SECONDS);
+ }
+
+ /**
+ * 鏈嶅姟鏄惁琚檺娴�
+ *
+ * @param key 鑷畾涔夌殑key锛岃淇濊瘉鍞竴
+ * @param max 鏀寔鐨勬渶澶ц姹�
+ * @param ttl 鏃堕棿
+ * @param timeUnit 鏃堕棿鍗曚綅
+ * @return 鏄惁鍏佽
+ */
+ boolean isAllowed(String key, long max, long ttl, TimeUnit timeUnit);
+
+ /**
+ * 鏈嶅姟闄愭祦锛岃闄愬埗鏃舵姏鍑� RateLimiterException 寮傚父锛岄渶瑕佽嚜琛屽鐞嗗紓甯�
+ *
+ * @param key 鑷畾涔夌殑key锛岃淇濊瘉鍞竴
+ * @param max 鏀寔鐨勬渶澶ц姹�
+ * @param ttl 鏃堕棿
+ * @param supplier Supplier 鍑芥暟寮�
+ * @return 鍑芥暟鎵ц缁撴灉
+ */
+ default <T> T allow(String key, long max, long ttl, CheckedSupplier<T> supplier) {
+ return allow(key, max, ttl, TimeUnit.SECONDS, supplier);
+ }
+
+ /**
+ * 鏈嶅姟闄愭祦锛岃闄愬埗鏃舵姏鍑� RateLimiterException 寮傚父锛岄渶瑕佽嚜琛屽鐞嗗紓甯�
+ *
+ * @param key 鑷畾涔夌殑key锛岃淇濊瘉鍞竴
+ * @param max 鏀寔鐨勬渶澶ц姹�
+ * @param ttl 鏃堕棿
+ * @param timeUnit 鏃堕棿鍗曚綅
+ * @param supplier Supplier 鍑芥暟寮�
+ * @param <T>
+ * @return 鍑芥暟鎵ц缁撴灉
+ */
+ default <T> T allow(String key, long max, long ttl, TimeUnit timeUnit, CheckedSupplier<T> supplier) {
+ boolean isAllowed = this.isAllowed(key, max, ttl, timeUnit);
+ if (isAllowed) {
+ try {
+ return supplier.get();
+ } catch (Throwable e) {
+ throw Exceptions.unchecked(e);
+ }
+ }
+ throw new RateLimiterException(key, max, ttl, timeUnit);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RateLimiterException.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RateLimiterException.java
new file mode 100644
index 0000000..90bc428
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RateLimiterException.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.ratelimiter;
+
+import lombok.Getter;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 闄愭祦寮傚父
+ *
+ * @author L.cm
+ */
+@Getter
+public class RateLimiterException extends RuntimeException {
+ private final String key;
+ private final long max;
+ private final long ttl;
+ private final TimeUnit timeUnit;
+
+ public RateLimiterException(String key, long max, long ttl, TimeUnit timeUnit) {
+ super(String.format("鎮ㄧ殑璁块棶娆℃暟宸茶秴闄愶細%s锛岄�熺巼锛�%d/%ds", key, max, timeUnit.toSeconds(ttl)));
+ this.key = key;
+ this.max = max;
+ this.ttl = ttl;
+ this.timeUnit = timeUnit;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RedisRateLimiterAspect.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RedisRateLimiterAspect.java
new file mode 100644
index 0000000..ab3f751
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RedisRateLimiterAspect.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.ratelimiter;
+
+import lombok.RequiredArgsConstructor;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springblade.core.tool.spel.BladeExpressionEvaluator;
+import org.springblade.core.tool.utils.CharPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.expression.AnnotatedElementKey;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.lang.NonNull;
+import org.springframework.util.Assert;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * redis 闄愭祦
+ *
+ * @author L.cm
+ */
+@Aspect
+@RequiredArgsConstructor
+public class RedisRateLimiterAspect implements ApplicationContextAware {
+ /**
+ * 琛ㄨ揪寮忓鐞�
+ */
+ private final BladeExpressionEvaluator evaluator = new BladeExpressionEvaluator();
+ /**
+ * redis 闄愭祦鏈嶅姟
+ */
+ private final RedisRateLimiterClient rateLimiterClient;
+ private ApplicationContext applicationContext;
+
+ /**
+ * AOP 鐜垏 娉ㄨВ @RateLimiter
+ */
+ @Around("@annotation(limiter)")
+ public Object aroundRateLimiter(ProceedingJoinPoint point, RateLimiter limiter) throws Throwable {
+ String limitKey = limiter.value();
+ Assert.hasText(limitKey, "@RateLimiter value must have length; it must not be null or empty");
+ // el 琛ㄨ揪寮�
+ String limitParam = limiter.param();
+ // 琛ㄨ揪寮忎笉涓虹┖
+ String rateKey;
+ if (StringUtil.isNotBlank(limitParam)) {
+ String evalAsText = evalLimitParam(point, limitParam);
+ rateKey = limitKey + CharPool.COLON + evalAsText;
+ } else {
+ rateKey = limitKey;
+ }
+ long max = limiter.max();
+ long ttl = limiter.ttl();
+ TimeUnit timeUnit = limiter.timeUnit();
+ return rateLimiterClient.allow(rateKey, max, ttl, timeUnit, point::proceed);
+ }
+
+ /**
+ * 璁$畻鍙傛暟琛ㄨ揪寮�
+ *
+ * @param point ProceedingJoinPoint
+ * @param limitParam limitParam
+ * @return 缁撴灉
+ */
+ private String evalLimitParam(ProceedingJoinPoint point, String limitParam) {
+ MethodSignature ms = (MethodSignature) point.getSignature();
+ Method method = ms.getMethod();
+ Object[] args = point.getArgs();
+ Object target = point.getTarget();
+ Class<?> targetClass = target.getClass();
+ EvaluationContext context = evaluator.createContext(method, args, target, targetClass, applicationContext);
+ AnnotatedElementKey elementKey = new AnnotatedElementKey(method, targetClass);
+ return evaluator.evalAsText(limitParam, elementKey, context);
+ }
+
+ @Override
+ public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RedisRateLimiterClient.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RedisRateLimiterClient.java
new file mode 100644
index 0000000..5e7bd94
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/ratelimiter/RedisRateLimiterClient.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.ratelimiter;
+
+import lombok.RequiredArgsConstructor;
+import org.springblade.core.tool.utils.CharPool;
+import org.springframework.core.env.Environment;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.core.script.RedisScript;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * redis 闄愭祦鏈嶅姟
+ *
+ * @author dream.lu
+ */
+@RequiredArgsConstructor
+public class RedisRateLimiterClient implements RateLimiterClient {
+ /**
+ * redis 闄愭祦 key 鍓嶇紑
+ */
+ private static final String REDIS_KEY_PREFIX = "limiter:";
+ /**
+ * 澶辫触鐨勯粯璁よ繑鍥炲��
+ */
+ private static final long FAIL_CODE = 0;
+ /**
+ * redisTemplate
+ */
+ private final StringRedisTemplate redisTemplate;
+ /**
+ * redisScript
+ */
+ private final RedisScript<List<Long>> script;
+ /**
+ * env
+ */
+ private final Environment environment;
+
+ @Override
+ public boolean isAllowed(String key, long max, long ttl, TimeUnit timeUnit) {
+ // redis key
+ String redisKeyBuilder = REDIS_KEY_PREFIX +
+ getApplicationName(environment) + CharPool.COLON + key;
+ List<String> keys = Collections.singletonList(redisKeyBuilder);
+ // 姣锛岃�冭檻涓讳粠绛栫暐鍜岃剼鏈洖鏀炬満鍒讹紝杩欎釜time鐢卞鎴风鑾峰彇浼犲叆
+ long now = System.currentTimeMillis();
+ // 杞负姣锛宲expire
+ long ttlMillis = timeUnit.toMillis(ttl);
+ // 鎵ц鍛戒护
+ List<Long> results = this.redisTemplate.execute(this.script, keys, max + "", ttlMillis + "", now + "");
+ // 缁撴灉涓虹┖杩斿洖澶辫触
+ if (results == null || results.isEmpty()) {
+ return false;
+ }
+ // 鍒ゆ柇杩斿洖鎴愬姛
+ Long result = results.get(0);
+ return result != FAIL_CODE;
+ }
+
+ private static String getApplicationName(Environment environment) {
+ return environment.getProperty("spring.application.name", "");
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/BytesWrapper.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/BytesWrapper.java
new file mode 100644
index 0000000..fabf185
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/BytesWrapper.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.serializer;
+
+/**
+ * redis搴忓垪鍖栬緟鍔╃被.鍗曠函鐨勬硾鍨嬫棤娉曞畾涔夐�氱敤schema锛屽師鍥犳槸鏃犳硶閫氳繃娉涘瀷T寰楀埌Class
+ *
+ * @author L.cm
+ */
+public class BytesWrapper<T> implements Cloneable {
+ private T value;
+
+ public BytesWrapper() {
+ }
+
+ public BytesWrapper(T value) {
+ this.value = value;
+ }
+
+ public void setValue(T value) {
+ this.value = value;
+ }
+
+ public T getValue() {
+ return value;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public BytesWrapper<T> clone() {
+ try {
+ return (BytesWrapper) super.clone();
+ } catch (CloneNotSupportedException e) {
+ return new BytesWrapper<>();
+ }
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/ProtoStuffSerializer.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/ProtoStuffSerializer.java
new file mode 100644
index 0000000..03d9ada
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/ProtoStuffSerializer.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.serializer;
+
+import io.protostuff.LinkedBuffer;
+import io.protostuff.ProtostuffIOUtil;
+import io.protostuff.Schema;
+import io.protostuff.runtime.RuntimeSchema;
+import org.springblade.core.tool.utils.ObjectUtil;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+
+/**
+ * ProtoStuff 搴忓垪鍖�
+ *
+ * @author L.cm
+ */
+public class ProtoStuffSerializer implements RedisSerializer<Object> {
+ private final Schema<BytesWrapper> schema;
+
+ public ProtoStuffSerializer() {
+ this.schema = RuntimeSchema.getSchema(BytesWrapper.class);
+ }
+
+ @Override
+ public byte[] serialize(Object object) throws SerializationException {
+ if (object == null) {
+ return null;
+ }
+ LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
+ try {
+ return ProtostuffIOUtil.toByteArray(new BytesWrapper<>(object), schema, buffer);
+ } finally {
+ buffer.clear();
+ }
+ }
+
+ @Override
+ public Object deserialize(byte[] bytes) throws SerializationException {
+ if (ObjectUtil.isEmpty(bytes)) {
+ return null;
+ }
+ BytesWrapper<Object> wrapper = new BytesWrapper<>();
+ ProtostuffIOUtil.mergeFrom(bytes, wrapper, schema);
+ return wrapper.getValue();
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/RedisKeySerializer.java b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/RedisKeySerializer.java
new file mode 100644
index 0000000..8a52ec1
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/RedisKeySerializer.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2018-2028, DreamLu All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: DreamLu 鍗㈡槬姊� (596392912@qq.com)
+ */
+
+package org.springblade.core.redis.serializer;
+
+import org.springframework.cache.interceptor.SimpleKey;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.data.redis.serializer.RedisSerializer;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+
+/**
+ * 灏唕edis key搴忓垪鍖栦负瀛楃涓�
+ *
+ * <p>
+ * spring cache涓殑绠�鍗曞熀鏈被鍨嬬洿鎺ヤ娇鐢� StringRedisSerializer 浼氭湁闂
+ * </p>
+ *
+ * @author L.cm
+ */
+public class RedisKeySerializer implements RedisSerializer<Object> {
+ private final Charset charset;
+ private final ConversionService converter;
+
+ public RedisKeySerializer() {
+ this(StandardCharsets.UTF_8);
+ }
+
+ public RedisKeySerializer(Charset charset) {
+ Objects.requireNonNull(charset, "Charset must not be null");
+ this.charset = charset;
+ this.converter = DefaultConversionService.getSharedInstance();
+ }
+
+ @Override
+ public Object deserialize(byte[] bytes) {
+ // redis keys 浼氱敤鍒板弽搴忓垪鍖�
+ if (bytes == null) {
+ return null;
+ }
+ return new String(bytes, charset);
+ }
+
+ @Override
+ public byte[] serialize(Object object) {
+ Objects.requireNonNull(object, "redis key is null");
+ String key;
+ if (object instanceof SimpleKey) {
+ key = "";
+ } else if (object instanceof String) {
+ key = (String) object;
+ } else {
+ key = converter.convert(object, String.class);
+ }
+ return key.getBytes(this.charset);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/resources/META-INF/scripts/blade_rate_limiter.lua b/Source/BladeX-Tool/blade-starter-redis/src/main/resources/META-INF/scripts/blade_rate_limiter.lua
new file mode 100644
index 0000000..26be751
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/resources/META-INF/scripts/blade_rate_limiter.lua
@@ -0,0 +1,28 @@
+-- lua 涓嬫爣浠� 1 寮�濮�
+-- 闄愭祦 key
+local key = KEYS[1]
+-- 闄愭祦澶у皬
+local max = tonumber(ARGV[1])
+-- 瓒呮椂鏃堕棿
+local ttl = tonumber(ARGV[2])
+-- 鑰冭檻涓讳粠绛栫暐鍜岃剼鏈洖鏀炬満鍒讹紝杩欎釜time鐢卞鎴风鑾峰彇浼犲叆
+local now = tonumber(ARGV[3])
+-- 宸茬粡杩囨湡鐨勬椂闂寸偣
+local expired = now - (ttl * 1000)
+
+-- 娓呴櫎杩囨湡鐨勬暟鎹�,绉婚櫎鎸囧畾鍒嗘暟锛坰core锛夊尯闂村唴鐨勬墍鏈夋垚鍛�
+redis.call('zremrangebyscore', key, 0, expired)
+-- 鑾峰彇褰撳墠娴侀噺澶у皬
+local currentLimit = tonumber(redis.call('zcard', key))
+
+local nextLimit = currentLimit + 1
+if nextLimit > max then
+ -- 杈惧埌闄愭祦澶у皬 杩斿洖 0
+ return 0;
+else
+ -- 娌℃湁杈惧埌闃堝�� value + 1
+ redis.call("zadd", key, now, now)
+ -- 绉掍负鍗曚綅璁剧疆 key 鐨勭敓瀛樻椂闂�
+ redis.call("pexpire", key, ttl)
+ return nextLimit
+end
diff --git a/Source/BladeX-Tool/blade-starter-redis/src/main/resources/additional-spring-configuration-metadata.json b/Source/BladeX-Tool/blade-starter-redis/src/main/resources/additional-spring-configuration-metadata.json
new file mode 100644
index 0000000..c7329f4
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-redis/src/main/resources/additional-spring-configuration-metadata.json
@@ -0,0 +1,11 @@
+{
+ "properties": [
+ {
+ "name": "blade.redis.rate-limiter.enabled",
+ "type": "java.lang.Boolean",
+ "description": "鏄惁寮�鍚� redis 鍒嗗竷寮忛檺娴�.",
+ "defaultValue": "false"
+ }
+ ]
+}
+
diff --git a/Source/BladeX-Tool/blade-starter-report/pom.xml b/Source/BladeX-Tool/blade-starter-report/pom.xml
new file mode 100644
index 0000000..69cbe04
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-report/pom.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-report</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-mybatis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.bstek.ureport</groupId>
+ <artifactId>ureport2-console</artifactId>
+ <version>2.2.9</version>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/config/ReportConfiguration.java b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/config/ReportConfiguration.java
new file mode 100644
index 0000000..0559227
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/config/ReportConfiguration.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.report.config;
+
+import com.bstek.ureport.UReportPropertyPlaceholderConfigurer;
+import com.bstek.ureport.console.UReportServlet;
+import com.bstek.ureport.provider.report.ReportProvider;
+import org.springblade.core.report.props.ReportDatabaseProperties;
+import org.springblade.core.report.props.ReportProperties;
+import org.springblade.core.report.provider.DatabaseProvider;
+import org.springblade.core.report.provider.ReportPlaceholderProvider;
+import org.springblade.core.report.service.IReportFileService;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.web.servlet.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ImportResource;
+import org.springframework.core.annotation.Order;
+
+import javax.servlet.Servlet;
+
+/**
+ * UReport閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@Order
+@AutoConfiguration
+@ConditionalOnProperty(value = "report.enabled", havingValue = "true", matchIfMissing = true)
+@EnableConfigurationProperties({ReportProperties.class, ReportDatabaseProperties.class})
+@ImportResource("classpath:ureport-console-context.xml")
+public class ReportConfiguration {
+
+ @Bean
+ public ServletRegistrationBean<Servlet> registrationBean() {
+ return new ServletRegistrationBean<>(new UReportServlet(), "/ureport/*");
+ }
+
+ @Bean
+ public UReportPropertyPlaceholderConfigurer uReportPropertyPlaceholderConfigurer(ReportProperties properties) {
+ return new ReportPlaceholderProvider(properties);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public ReportProvider reportProvider(ReportDatabaseProperties properties, IReportFileService service) {
+ return new DatabaseProvider(properties, service);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/datasource/ReportDataSource.java b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/datasource/ReportDataSource.java
new file mode 100644
index 0000000..45921bf
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/datasource/ReportDataSource.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.report.datasource;
+
+import com.bstek.ureport.definition.datasource.BuildinDatasource;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * UReport鏁版嵁婧愰厤缃�
+ *
+ * @author Chill
+ */
+@Slf4j
+@AllArgsConstructor
+public class ReportDataSource implements BuildinDatasource {
+ private static final String NAME = "ReportDataSource";
+
+ private final DataSource dataSource;
+
+ @Override
+ public String name() {
+ return NAME;
+ }
+
+ @Override
+ public Connection getConnection() {
+ try {
+ return dataSource.getConnection();
+ } catch (SQLException e) {
+ log.error("report鏁版嵁婧愰摼鎺ュけ璐�");
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/endpoint/ReportBootEndpoint.java b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/endpoint/ReportBootEndpoint.java
new file mode 100644
index 0000000..71891e3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/endpoint/ReportBootEndpoint.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.report.endpoint;
+
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.report.service.IReportFileService;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import springfox.documentation.annotations.ApiIgnore;
+
+/**
+ * UReport Boot鐗� API绔偣
+ *
+ * @author Chill
+ */
+@ApiIgnore
+@RestController
+@RequestMapping(AppConstant.APPLICATION_REPORT_NAME + "/report/rest")
+public class ReportBootEndpoint extends ReportEndpoint {
+
+ public ReportBootEndpoint(IReportFileService service) {
+ super(service);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/endpoint/ReportEndpoint.java b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/endpoint/ReportEndpoint.java
new file mode 100644
index 0000000..b8c4d9b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/endpoint/ReportEndpoint.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.report.endpoint;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.AllArgsConstructor;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.report.entity.ReportFileEntity;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.report.service.IReportFileService;
+import org.springframework.web.bind.annotation.*;
+import springfox.documentation.annotations.ApiIgnore;
+
+import java.util.Map;
+
+/**
+ * UReport API绔偣
+ *
+ * @author Chill
+ */
+@ApiIgnore
+@RestController
+@AllArgsConstructor
+@RequestMapping("/report/rest")
+public class ReportEndpoint {
+
+ private final IReportFileService service;
+
+ /**
+ * 璇︽儏
+ */
+ @GetMapping("/detail")
+ public R<ReportFileEntity> detail(ReportFileEntity file) {
+ ReportFileEntity detail = service.getOne(Condition.getQueryWrapper(file));
+ return R.data(detail);
+ }
+
+ /**
+ * 鍒嗛〉
+ */
+ @GetMapping("/list")
+ public R<IPage<ReportFileEntity>> list(@RequestParam Map<String, Object> file, Query query) {
+ IPage<ReportFileEntity> pages = service.page(Condition.getPage(query), Condition.getQueryWrapper(file, ReportFileEntity.class));
+ return R.data(pages);
+ }
+
+ /**
+ * 鍒犻櫎
+ */
+ @PostMapping("/remove")
+ public R remove(@RequestParam String ids) {
+ boolean temp = service.removeByIds(Func.toLongList(ids));
+ return R.status(temp);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/entity/ReportFileEntity.java b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/entity/ReportFileEntity.java
new file mode 100644
index 0000000..1446cbb
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/entity/ReportFileEntity.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.report.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * UReport瀹炰綋绫�
+ *
+ * @author Chill
+ */
+@Data
+@TableName("blade_report_file")
+public class ReportFileEntity implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 涓婚敭
+ */
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private Long id;
+ /**
+ * 鏂囦欢鍚�
+ */
+ private String name;
+ /**
+ * 鏂囦欢鍐呭
+ */
+ private byte[] content;
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ private Date createTime;
+ /**
+ * 鏇存柊鏃堕棿
+ */
+ private Date updateTime;
+ /**
+ * 鏄惁宸插垹闄�
+ */
+ @TableLogic
+ private Integer isDeleted;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/mapper/ReportFileMapper.java b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/mapper/ReportFileMapper.java
new file mode 100644
index 0000000..237582f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/mapper/ReportFileMapper.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.report.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springblade.core.report.entity.ReportFileEntity;
+
+/**
+ * UReport Mapper
+ *
+ * @author Chill
+ */
+public interface ReportFileMapper extends BaseMapper<ReportFileEntity> {
+}
diff --git a/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/props/ReportDatabaseProperties.java b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/props/ReportDatabaseProperties.java
new file mode 100644
index 0000000..2c0ea89
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/props/ReportDatabaseProperties.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.report.props;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * UReport閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@Data
+@ConfigurationProperties(prefix = "report.database.provider")
+public class ReportDatabaseProperties {
+ private String name = "鏁版嵁搴撴枃浠剁郴缁�";
+ private String prefix = "blade-";
+ private boolean disabled = false;
+}
diff --git a/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/props/ReportProperties.java b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/props/ReportProperties.java
new file mode 100644
index 0000000..5ba09b9
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/props/ReportProperties.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.report.props;
+
+import lombok.Data;
+import org.springblade.core.tool.utils.StringPool;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * UReport閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@Data
+@ConfigurationProperties(prefix = "report")
+public class ReportProperties {
+ private Boolean enabled = true;
+ private Boolean disableHttpSessionReportCache = false;
+ private Boolean disableFileProvider = true;
+ private String fileStoreDir = StringPool.EMPTY;
+ private Boolean debug = false;
+}
diff --git a/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/provider/DatabaseProvider.java b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/provider/DatabaseProvider.java
new file mode 100644
index 0000000..2790e2e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/provider/DatabaseProvider.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.report.provider;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.bstek.ureport.provider.report.ReportFile;
+import com.bstek.ureport.provider.report.ReportProvider;
+import lombok.AllArgsConstructor;
+import org.springblade.core.tool.constant.BladeConstant;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.core.report.entity.ReportFileEntity;
+import org.springblade.core.report.props.ReportDatabaseProperties;
+import org.springblade.core.report.service.IReportFileService;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 鏁版嵁搴撴枃浠跺鐞�
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+public class DatabaseProvider implements ReportProvider {
+
+ private final ReportDatabaseProperties properties;
+ private final IReportFileService service;
+
+ @Override
+ public InputStream loadReport(String file) {
+ ReportFileEntity reportFileEntity = service.getOne(Wrappers.<ReportFileEntity>lambdaQuery().eq(ReportFileEntity::getName, getFileName(file)));
+ byte[] content = reportFileEntity.getContent();
+ return new ByteArrayInputStream(content);
+ }
+
+ @Override
+ public void deleteReport(String file) {
+ service.remove(Wrappers.<ReportFileEntity>lambdaUpdate().eq(ReportFileEntity::getName, getFileName(file)));
+ }
+
+ @Override
+ public List<ReportFile> getReportFiles() {
+ List<ReportFileEntity> list = service.list();
+ List<ReportFile> reportFiles = new ArrayList<>();
+ list.forEach(reportFileEntity -> reportFiles.add(new ReportFile(reportFileEntity.getName(), reportFileEntity.getUpdateTime())));
+ return reportFiles;
+ }
+
+ @Override
+ public void saveReport(String file, String content) {
+ String fileName = getFileName(file);
+ ReportFileEntity reportFileEntity = service.getOne(Wrappers.<ReportFileEntity>lambdaQuery().eq(ReportFileEntity::getName, fileName));
+ Date now = DateUtil.now();
+ if (reportFileEntity == null) {
+ reportFileEntity = new ReportFileEntity();
+ reportFileEntity.setName(fileName);
+ reportFileEntity.setContent(content.getBytes());
+ reportFileEntity.setCreateTime(now);
+ reportFileEntity.setIsDeleted(BladeConstant.DB_NOT_DELETED);
+ } else {
+ reportFileEntity.setContent(content.getBytes());
+ }
+ reportFileEntity.setUpdateTime(now);
+ service.saveOrUpdate(reportFileEntity);
+ }
+
+ @Override
+ public String getName() {
+ return properties.getName();
+ }
+
+ @Override
+ public boolean disabled() {
+ return properties.isDisabled();
+ }
+
+ @Override
+ public String getPrefix() {
+ return properties.getPrefix();
+ }
+
+ /**
+ * 鑾峰彇鏍囧噯鏍煎紡鏂囦欢鍚�
+ *
+ * @param name 鍘熸枃浠跺悕
+ */
+ private String getFileName(String name) {
+ if (name.startsWith(getPrefix())) {
+ name = name.substring(getPrefix().length());
+ }
+ return name;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/provider/ReportPlaceholderProvider.java b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/provider/ReportPlaceholderProvider.java
new file mode 100644
index 0000000..ef69838
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/provider/ReportPlaceholderProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.report.provider;
+
+import com.bstek.ureport.UReportPropertyPlaceholderConfigurer;
+import org.springblade.core.report.props.ReportProperties;
+
+import java.util.Properties;
+
+/**
+ * UReport鑷畾涔夐厤缃�
+ *
+ * @author Chill
+ */
+public class ReportPlaceholderProvider extends UReportPropertyPlaceholderConfigurer {
+
+ public ReportPlaceholderProvider(ReportProperties properties) {
+ Properties props = new Properties();
+ props.setProperty("ureport.disableHttpSessionReportCache", properties.getDisableHttpSessionReportCache().toString());
+ props.setProperty("ureport.disableFileProvider", properties.getDisableFileProvider().toString());
+ props.setProperty("ureport.fileStoreDir", properties.getFileStoreDir());
+ props.setProperty("ureport.debug", properties.getDebug().toString());
+ this.setProperties(props);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/service/IReportFileService.java b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/service/IReportFileService.java
new file mode 100644
index 0000000..d971b3a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/service/IReportFileService.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.report.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.springblade.core.report.entity.ReportFileEntity;
+
+/**
+ * UReport Service
+ *
+ * @author Chill
+ */
+public interface IReportFileService extends IService<ReportFileEntity> {
+}
diff --git a/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/service/impl/ReportFileServiceImpl.java b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/service/impl/ReportFileServiceImpl.java
new file mode 100644
index 0000000..a42872f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-report/src/main/java/org/springblade/core/report/service/impl/ReportFileServiceImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.report.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springblade.core.report.entity.ReportFileEntity;
+import org.springblade.core.report.mapper.ReportFileMapper;
+import org.springblade.core.report.service.IReportFileService;
+import org.springframework.stereotype.Service;
+
+/**
+ * UReport Service
+ *
+ * @author Chill
+ */
+@Service
+public class ReportFileServiceImpl extends ServiceImpl<ReportFileMapper, ReportFileEntity> implements IReportFileService {
+}
diff --git a/Source/BladeX-Tool/blade-starter-sms/pom.xml b/Source/BladeX-Tool/blade-starter-sms/pom.xml
new file mode 100644
index 0000000..519bb77
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-sms</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-tool</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-redis</artifactId>
+ </dependency>
+ <dependency>
+ <artifactId>jackson-databind</artifactId>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ </dependency>
+ <dependency>
+ <groupId>com.aliyun</groupId>
+ <artifactId>aliyun-java-sdk-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.qiniu</groupId>
+ <artifactId>qiniu-java-sdk</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.github.qcloudsms</groupId>
+ <artifactId>qcloudsms</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yunpian.sdk</groupId>
+ <artifactId>yunpian-java-sdk</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/AliSmsTemplate.java b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/AliSmsTemplate.java
new file mode 100644
index 0000000..9b71227
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/AliSmsTemplate.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.sms;
+
+import com.aliyuncs.CommonRequest;
+import com.aliyuncs.CommonResponse;
+import com.aliyuncs.IAcsClient;
+import com.aliyuncs.exceptions.ClientException;
+import com.aliyuncs.http.MethodType;
+import lombok.AllArgsConstructor;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.sms.model.SmsCode;
+import org.springblade.core.sms.model.SmsData;
+import org.springblade.core.sms.model.SmsResponse;
+import org.springblade.core.sms.props.SmsProperties;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.http.HttpStatus;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * 闃块噷浜戠煭淇″彂閫佺被
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+public class AliSmsTemplate implements SmsTemplate {
+
+ private static final int SUCCESS = 200;
+ private static final String FAIL = "fail";
+ private static final String OK = "ok";
+ private static final String DOMAIN = "dysmsapi.aliyuncs.com";
+ private static final String VERSION = "2017-05-25";
+ private static final String ACTION = "SendSms";
+
+ private final SmsProperties smsProperties;
+ private final IAcsClient acsClient;
+ private final BladeRedis bladeRedis;
+
+ @Override
+ public SmsResponse sendMessage(SmsData smsData, Collection<String> phones) {
+ CommonRequest request = new CommonRequest();
+ request.setSysMethod(MethodType.POST);
+ request.setSysDomain(DOMAIN);
+ request.setSysVersion(VERSION);
+ request.setSysAction(ACTION);
+ request.putQueryParameter("PhoneNumbers", StringUtil.join(phones));
+ request.putQueryParameter("TemplateCode", smsProperties.getTemplateId());
+ request.putQueryParameter("TemplateParam", JsonUtil.toJson(smsData.getParams()));
+ request.putQueryParameter("SignName", smsProperties.getSignName());
+ try {
+ CommonResponse response = acsClient.getCommonResponse(request);
+ Map<String, Object> data = JsonUtil.toMap(response.getData());
+ String code = FAIL;
+ if (data != null) {
+ code = String.valueOf(data.get("Code"));
+ }
+ return new SmsResponse(response.getHttpStatus() == SUCCESS && code.equalsIgnoreCase(OK), response.getHttpStatus(), response.getData());
+ } catch (ClientException e) {
+ e.printStackTrace();
+ return new SmsResponse(Boolean.FALSE, HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
+ }
+ }
+
+ @Override
+ public SmsCode sendValidate(SmsData smsData, String phone) {
+ SmsCode smsCode = new SmsCode();
+ boolean temp = sendSingle(smsData, phone);
+ if (temp && StringUtil.isNotBlank(smsData.getKey())) {
+ String id = StringUtil.randomUUID();
+ String value = smsData.getParams().get(smsData.getKey());
+ bladeRedis.setEx(cacheKey(phone, id), value, Duration.ofMinutes(30));
+ smsCode.setId(id).setValue(value);
+ } else {
+ smsCode.setSuccess(Boolean.FALSE);
+ }
+ return smsCode;
+ }
+
+ @Override
+ public boolean validateMessage(SmsCode smsCode) {
+ String id = smsCode.getId();
+ String value = smsCode.getValue();
+ String phone = smsCode.getPhone();
+ String cache = bladeRedis.get(cacheKey(phone, id));
+ if (StringUtil.isNotBlank(value) && StringUtil.equalsIgnoreCase(cache, value)) {
+ bladeRedis.del(cacheKey(phone, id));
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/QiniuSmsTemplate.java b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/QiniuSmsTemplate.java
new file mode 100644
index 0000000..0dcc91a
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/QiniuSmsTemplate.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.sms;
+
+import com.qiniu.common.QiniuException;
+import com.qiniu.http.Response;
+import com.qiniu.sms.SmsManager;
+import lombok.AllArgsConstructor;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.sms.model.SmsCode;
+import org.springblade.core.sms.model.SmsData;
+import org.springblade.core.sms.model.SmsResponse;
+import org.springblade.core.sms.props.SmsProperties;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.http.HttpStatus;
+
+import java.time.Duration;
+import java.util.Collection;
+
+/**
+ * 涓冪墰浜戠煭淇″彂閫佺被
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+public class QiniuSmsTemplate implements SmsTemplate {
+
+ private final SmsProperties smsProperties;
+ private final SmsManager smsManager;
+ private final BladeRedis bladeRedis;
+
+ @Override
+ public SmsResponse sendMessage(SmsData smsData, Collection<String> phones) {
+ try {
+ Response response = smsManager.sendMessage(smsProperties.getTemplateId(), StringUtil.toStringArray(phones), smsData.getParams());
+ return new SmsResponse(response.isOK(), response.statusCode, response.toString());
+ } catch (QiniuException e) {
+ e.printStackTrace();
+ return new SmsResponse(Boolean.FALSE, HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
+ }
+ }
+
+ @Override
+ public SmsCode sendValidate(SmsData smsData, String phone) {
+ SmsCode smsCode = new SmsCode();
+ boolean temp = sendSingle(smsData, phone);
+ if (temp && StringUtil.isNotBlank(smsData.getKey())) {
+ String id = StringUtil.randomUUID();
+ String value = smsData.getParams().get(smsData.getKey());
+ bladeRedis.setEx(cacheKey(phone, id), value, Duration.ofMinutes(30));
+ smsCode.setId(id).setValue(value);
+ } else {
+ smsCode.setSuccess(Boolean.FALSE);
+ }
+ return smsCode;
+ }
+
+ @Override
+ public boolean validateMessage(SmsCode smsCode) {
+ String id = smsCode.getId();
+ String value = smsCode.getValue();
+ String phone = smsCode.getPhone();
+ String cache = bladeRedis.get(cacheKey(phone, id));
+ if (StringUtil.isNotBlank(value) && StringUtil.equalsIgnoreCase(cache, value)) {
+ bladeRedis.del(cacheKey(phone, id));
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/SmsTemplate.java b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/SmsTemplate.java
new file mode 100644
index 0000000..ae3f87c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/SmsTemplate.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.sms;
+
+import org.springblade.core.sms.model.SmsCode;
+import org.springblade.core.sms.model.SmsData;
+import org.springblade.core.sms.model.SmsInfo;
+import org.springblade.core.sms.model.SmsResponse;
+import org.springblade.core.tool.utils.StringPool;
+import org.springframework.util.StringUtils;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import static org.springblade.core.sms.constant.SmsConstant.CAPTCHA_KEY;
+
+/**
+ * 鐭俊閫氱敤灏佽
+ *
+ * @author Chill
+ */
+public interface SmsTemplate {
+
+ /**
+ * 缂撳瓨閿��
+ *
+ * @param phone 鎵嬫満鍙�
+ * @param id 閿��
+ * @return 缂撳瓨閿�艰繑鍥�
+ */
+ default String cacheKey(String phone, String id) {
+ return CAPTCHA_KEY + phone + StringPool.COLON + id;
+ }
+
+ /**
+ * 鍙戦�佺煭淇�
+ *
+ * @param smsInfo 鐭俊淇℃伅
+ * @return 鍙戦�佽繑鍥�
+ */
+ default boolean send(SmsInfo smsInfo) {
+ return sendMulti(smsInfo.getSmsData(), smsInfo.getPhones());
+ }
+
+ /**
+ * 鍙戦�佺煭淇�
+ *
+ * @param smsData 鐭俊鍐呭
+ * @param phone 鎵嬫満鍙�
+ * @return 鍙戦�佽繑鍥�
+ */
+ default boolean sendSingle(SmsData smsData, String phone) {
+ if (StringUtils.isEmpty(phone)) {
+ return Boolean.FALSE;
+ }
+ return sendMulti(smsData, Collections.singletonList(phone));
+ }
+
+ /**
+ * 鍙戦�佺煭淇�
+ *
+ * @param smsData 鐭俊鍐呭
+ * @param phones 鎵嬫満鍙峰垪琛�
+ * @return 鍙戦�佽繑鍥�
+ */
+ default boolean sendMulti(SmsData smsData, Collection<String> phones) {
+ SmsResponse response = sendMessage(smsData, phones);
+ return response.isSuccess();
+ }
+
+ /**
+ * 鍙戦�佺煭淇�
+ *
+ * @param smsData 鐭俊鍐呭
+ * @param phones 鎵嬫満鍙峰垪琛�
+ * @return 鍙戦�佽繑鍥�
+ */
+ SmsResponse sendMessage(SmsData smsData, Collection<String> phones);
+
+ /**
+ * 鍙戦�侀獙璇佺爜
+ *
+ * @param smsData 鐭俊鍐呭
+ * @param phone 鎵嬫満鍙�
+ * @return 鍙戦�佽繑鍥�
+ */
+ SmsCode sendValidate(SmsData smsData, String phone);
+
+ /**
+ * 鏍¢獙楠岃瘉鐮�
+ *
+ * @param smsCode 楠岃瘉鐮佸唴瀹�
+ * @return 鏄惁鏍¢獙鎴愬姛
+ */
+ boolean validateMessage(SmsCode smsCode);
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/TencentSmsTemplate.java b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/TencentSmsTemplate.java
new file mode 100644
index 0000000..89527e2
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/TencentSmsTemplate.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.sms;
+
+import com.github.qcloudsms.SmsMultiSender;
+import com.github.qcloudsms.SmsMultiSenderResult;
+import com.github.qcloudsms.httpclient.HTTPException;
+import lombok.AllArgsConstructor;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.sms.model.SmsCode;
+import org.springblade.core.sms.model.SmsData;
+import org.springblade.core.sms.model.SmsResponse;
+import org.springblade.core.sms.props.SmsProperties;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.http.HttpStatus;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.Collection;
+
+/**
+ * 鑵捐浜戠煭淇″彂閫佺被
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+public class TencentSmsTemplate implements SmsTemplate {
+
+ private static final int SUCCESS = 0;
+ private static final String NATION_CODE = "86";
+
+ private final SmsProperties smsProperties;
+ private final SmsMultiSender smsSender;
+ private final BladeRedis bladeRedis;
+
+
+ @Override
+ public SmsResponse sendMessage(SmsData smsData, Collection<String> phones) {
+ try {
+ Collection<String> values = smsData.getParams().values();
+ String[] params = StringUtil.toStringArray(values);
+ SmsMultiSenderResult senderResult = smsSender.sendWithParam(
+ NATION_CODE,
+ StringUtil.toStringArray(phones),
+ Func.toInt(smsProperties.getTemplateId()),
+ params,
+ smsProperties.getSignName(),
+ StringPool.EMPTY, StringPool.EMPTY
+ );
+ return new SmsResponse(senderResult.result == SUCCESS, senderResult.result, senderResult.toString());
+ } catch (HTTPException | IOException e) {
+ e.printStackTrace();
+ return new SmsResponse(Boolean.FALSE, HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
+ }
+ }
+
+ @Override
+ public SmsCode sendValidate(SmsData smsData, String phone) {
+ SmsCode smsCode = new SmsCode();
+ boolean temp = sendSingle(smsData, phone);
+ if (temp && StringUtil.isNotBlank(smsData.getKey())) {
+ String id = StringUtil.randomUUID();
+ String value = smsData.getParams().get(smsData.getKey());
+ bladeRedis.setEx(cacheKey(phone, id), value, Duration.ofMinutes(30));
+ smsCode.setId(id).setValue(value);
+ } else {
+ smsCode.setSuccess(Boolean.FALSE);
+ }
+ return smsCode;
+ }
+
+ @Override
+ public boolean validateMessage(SmsCode smsCode) {
+ String id = smsCode.getId();
+ String value = smsCode.getValue();
+ String phone = smsCode.getPhone();
+ String cache = bladeRedis.get(cacheKey(phone, id));
+ if (StringUtil.isNotBlank(value) && StringUtil.equalsIgnoreCase(cache, value)) {
+ bladeRedis.del(cacheKey(phone, id));
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/YunpianSmsTemplate.java b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/YunpianSmsTemplate.java
new file mode 100644
index 0000000..52e43fd
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/YunpianSmsTemplate.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.sms;
+
+import com.yunpian.sdk.YunpianClient;
+import com.yunpian.sdk.constant.Code;
+import com.yunpian.sdk.model.Result;
+import com.yunpian.sdk.model.SmsBatchSend;
+import lombok.AllArgsConstructor;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.sms.model.SmsCode;
+import org.springblade.core.sms.model.SmsData;
+import org.springblade.core.sms.model.SmsResponse;
+import org.springblade.core.sms.props.SmsProperties;
+import org.springblade.core.tool.support.Kv;
+import org.springblade.core.tool.utils.PlaceholderUtil;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * 浜戠墖鐭俊鍙戦�佺被
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+public class YunpianSmsTemplate implements SmsTemplate {
+
+ private final SmsProperties smsProperties;
+ private final YunpianClient client;
+ private final BladeRedis bladeRedis;
+
+ @Override
+ public SmsResponse sendMessage(SmsData smsData, Collection<String> phones) {
+ String templateId = smsProperties.getTemplateId();
+ // 浜戠墖鐭俊妯℃澘鍐呭鏇挎崲, 鍗犱綅绗︽牸寮忎负瀹樻柟榛樿鐨� #code#
+ String templateText = PlaceholderUtil.getResolver(StringPool.HASH, StringPool.HASH).resolveByMap(
+ templateId, Kv.create().setAll(smsData.getParams())
+ );
+ Map<String, String> param = client.newParam(2);
+ param.put(YunpianClient.MOBILE, StringUtil.join(phones));
+ param.put(YunpianClient.TEXT, templateText);
+ Result<SmsBatchSend> result = client.sms().multi_send(param);
+ return new SmsResponse(result.getCode() == Code.OK, result.getCode(), result.toString());
+ }
+
+ @Override
+ public SmsCode sendValidate(SmsData smsData, String phone) {
+ SmsCode smsCode = new SmsCode();
+ boolean temp = sendSingle(smsData, phone);
+ if (temp && StringUtil.isNotBlank(smsData.getKey())) {
+ String id = StringUtil.randomUUID();
+ String value = smsData.getParams().get(smsData.getKey());
+ bladeRedis.setEx(cacheKey(phone, id), value, Duration.ofMinutes(30));
+ smsCode.setId(id).setValue(value);
+ } else {
+ smsCode.setSuccess(Boolean.FALSE);
+ }
+ return smsCode;
+ }
+
+ @Override
+ public boolean validateMessage(SmsCode smsCode) {
+ String id = smsCode.getId();
+ String value = smsCode.getValue();
+ String phone = smsCode.getPhone();
+ String cache = bladeRedis.get(cacheKey(phone, id));
+ if (StringUtil.isNotBlank(value) && StringUtil.equalsIgnoreCase(cache, value)) {
+ bladeRedis.del(cacheKey(phone, id));
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/AliSmsConfiguration.java b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/AliSmsConfiguration.java
new file mode 100644
index 0000000..6018eee
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/AliSmsConfiguration.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.sms.config;
+
+import com.aliyuncs.DefaultAcsClient;
+import com.aliyuncs.IAcsClient;
+import com.aliyuncs.profile.DefaultProfile;
+import com.aliyuncs.profile.IClientProfile;
+import lombok.AllArgsConstructor;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.sms.AliSmsTemplate;
+import org.springblade.core.sms.props.SmsProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * 闃块噷浜戠煭淇¢厤缃被
+ *
+ * @author Chill
+ */
+@AutoConfiguration
+@AllArgsConstructor
+@ConditionalOnClass(IAcsClient.class)
+@EnableConfigurationProperties(SmsProperties.class)
+@ConditionalOnProperty(value = "sms.name", havingValue = "aliyun")
+public class AliSmsConfiguration {
+
+ private final BladeRedis bladeRedis;
+
+ @Bean
+ public AliSmsTemplate aliSmsTemplate(SmsProperties smsProperties) {
+ IClientProfile profile = DefaultProfile.getProfile(smsProperties.getRegionId(), smsProperties.getAccessKey(), smsProperties.getSecretKey());
+ IAcsClient acsClient = new DefaultAcsClient(profile);
+ return new AliSmsTemplate(smsProperties, acsClient, bladeRedis);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/QiniuSmsConfiguration.java b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/QiniuSmsConfiguration.java
new file mode 100644
index 0000000..08eb608
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/QiniuSmsConfiguration.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.sms.config;
+
+import com.qiniu.sms.SmsManager;
+import com.qiniu.util.Auth;
+import lombok.AllArgsConstructor;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.sms.QiniuSmsTemplate;
+import org.springblade.core.sms.props.SmsProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * 闃块噷浜戠煭淇¢厤缃被
+ *
+ * @author Chill
+ */
+@AutoConfiguration
+@AllArgsConstructor
+@ConditionalOnClass(SmsManager.class)
+@EnableConfigurationProperties(SmsProperties.class)
+@ConditionalOnProperty(value = "sms.name", havingValue = "qiniu")
+public class QiniuSmsConfiguration {
+
+ private final BladeRedis bladeRedis;
+
+ @Bean
+ public QiniuSmsTemplate qiniuSmsTemplate(SmsProperties smsProperties) {
+ Auth auth = Auth.create(smsProperties.getAccessKey(), smsProperties.getSecretKey());
+ SmsManager smsManager = new SmsManager(auth);
+ return new QiniuSmsTemplate(smsProperties, smsManager, bladeRedis);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/SmsConfiguration.java b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/SmsConfiguration.java
new file mode 100644
index 0000000..0f7ff68
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/SmsConfiguration.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.sms.config;
+
+import lombok.AllArgsConstructor;
+import org.springblade.core.sms.props.SmsProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+
+/**
+ * Sms閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@AutoConfiguration
+@AllArgsConstructor
+@EnableConfigurationProperties(SmsProperties.class)
+public class SmsConfiguration {
+}
diff --git a/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/TencentSmsConfiguration.java b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/TencentSmsConfiguration.java
new file mode 100644
index 0000000..882da14
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/TencentSmsConfiguration.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.sms.config;
+
+import com.github.qcloudsms.SmsMultiSender;
+import lombok.AllArgsConstructor;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.sms.TencentSmsTemplate;
+import org.springblade.core.sms.props.SmsProperties;
+import org.springblade.core.tool.utils.Func;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * 鑵捐浜戠煭淇¢厤缃被
+ *
+ * @author Chill
+ */
+@AutoConfiguration
+@AllArgsConstructor
+@ConditionalOnClass(SmsMultiSender.class)
+@EnableConfigurationProperties(SmsProperties.class)
+@ConditionalOnProperty(value = "sms.name", havingValue = "tencent")
+public class TencentSmsConfiguration {
+
+ private final BladeRedis bladeRedis;
+
+ @Bean
+ public TencentSmsTemplate tencentSmsTemplate(SmsProperties smsProperties) {
+ SmsMultiSender smsSender = new SmsMultiSender(Func.toInt(smsProperties.getAccessKey()), smsProperties.getSecretKey());
+ return new TencentSmsTemplate(smsProperties, smsSender, bladeRedis);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/YunpianSmsConfiguration.java b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/YunpianSmsConfiguration.java
new file mode 100644
index 0000000..ecdcf2c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/config/YunpianSmsConfiguration.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.sms.config;
+
+import com.yunpian.sdk.YunpianClient;
+import lombok.AllArgsConstructor;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.sms.YunpianSmsTemplate;
+import org.springblade.core.sms.props.SmsProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * 浜戠墖鐭俊閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@AutoConfiguration
+@AllArgsConstructor
+@ConditionalOnClass(YunpianClient.class)
+@EnableConfigurationProperties(SmsProperties.class)
+@ConditionalOnProperty(value = "sms.name", havingValue = "yunpian")
+public class YunpianSmsConfiguration {
+
+ private final BladeRedis bladeRedis;
+
+ @Bean
+ public YunpianSmsTemplate yunpianSmsTemplate(SmsProperties smsProperties) {
+ YunpianClient client = new YunpianClient(smsProperties.getAccessKey()).init();
+ return new YunpianSmsTemplate(smsProperties, client, bladeRedis);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/constant/SmsConstant.java b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/constant/SmsConstant.java
new file mode 100644
index 0000000..6441daa
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/constant/SmsConstant.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.sms.constant;
+
+/**
+ * 鐭俊鏈嶅姟甯搁噺
+ *
+ * @author Chill
+ */
+public interface SmsConstant {
+
+ /**
+ * 閫氱敤缂撳瓨key
+ */
+ String CAPTCHA_KEY = "blade:sms::captcha:";
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/enums/SmsEnum.java b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/enums/SmsEnum.java
new file mode 100644
index 0000000..28370c1
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/enums/SmsEnum.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.sms.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Sms鏋氫妇绫�
+ *
+ * @author Chill
+ */
+@Getter
+@AllArgsConstructor
+public enum SmsEnum {
+
+ /**
+ * yunpian
+ */
+ YUNPIAN("yunpian", 1),
+
+ /**
+ * qiniu
+ */
+ QINIU("qiniu", 2),
+
+ /**
+ * ali
+ */
+ ALI("ali", 3),
+
+ /**
+ * tencent
+ */
+ TENCENT("tencent", 4),
+ ;
+
+ /**
+ * 鍚嶇О
+ */
+ final String name;
+ /**
+ * 绫诲瀷
+ */
+ final int category;
+
+ /**
+ * 鍖归厤鏋氫妇鍊�
+ *
+ * @param name 鍚嶇О
+ * @return OssEnum
+ */
+ public static SmsEnum of(String name) {
+ if (name == null) {
+ return null;
+ }
+ SmsEnum[] values = SmsEnum.values();
+ for (SmsEnum smsEnum : values) {
+ if (smsEnum.name.equals(name)) {
+ return smsEnum;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/enums/SmsStatusEnum.java b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/enums/SmsStatusEnum.java
new file mode 100644
index 0000000..dca9241
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/enums/SmsStatusEnum.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.sms.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Sms绫诲瀷鏋氫妇
+ *
+ * @author Chill
+ */
+@Getter
+@AllArgsConstructor
+public enum SmsStatusEnum {
+
+ /**
+ * 鍏抽棴
+ */
+ DISABLE(1),
+ /**
+ * 鍚敤
+ */
+ ENABLE(2),
+ ;
+
+ /**
+ * 绫诲瀷缂栧彿
+ */
+ final int num;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/model/SmsCode.java b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/model/SmsCode.java
new file mode 100644
index 0000000..cc4f27e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/model/SmsCode.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.sms.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+
+/**
+ * 鏍¢獙淇℃伅
+ *
+ * @author Chill
+ */
+@Data
+@Accessors(chain = true)
+public class SmsCode implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏄惁鎴愬姛
+ */
+ private boolean success = Boolean.TRUE;
+
+ /**
+ * 鍙橀噺phone,鐢ㄤ簬redis杩涜姣斿
+ */
+ private String phone;
+
+ /**
+ * 鍙橀噺id,鐢ㄤ簬redis杩涜姣斿
+ */
+ private String id;
+
+ /**
+ * 鍙橀噺鍊�,鐢ㄤ簬redis杩涜姣斿
+ */
+ @JsonIgnore
+ private String value;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/model/SmsData.java b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/model/SmsData.java
new file mode 100644
index 0000000..e38aa31
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/model/SmsData.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.sms.model;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * 閫氱煡鍐呭
+ *
+ * @author Chill
+ */
+@Data
+@Accessors(chain = true)
+public class SmsData implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏋勯�犲櫒
+ *
+ * @param params 鍙傛暟鍒楄〃
+ */
+ public SmsData(Map<String, String> params) {
+ this.params = params;
+ }
+
+ /**
+ * 鍙橀噺key,鐢ㄤ簬浠庡弬鏁板垪琛ㄨ幏鍙栧彉閲忓��
+ */
+ private String key;
+
+ /**
+ * 鍙傛暟鍒楄〃
+ */
+ private Map<String, String> params;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/model/SmsInfo.java b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/model/SmsInfo.java
new file mode 100644
index 0000000..655021b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/model/SmsInfo.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.sms.model;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * 閫氱煡淇℃伅
+ *
+ * @author Chill
+ */
+@Data
+@Accessors(chain = true)
+public class SmsInfo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 閫氱煡鍐呭
+ */
+ private SmsData smsData;
+
+ /**
+ * 鍙风爜鍒楄〃
+ */
+ private Collection<String> phones;
+}
diff --git a/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/model/SmsResponse.java b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/model/SmsResponse.java
new file mode 100644
index 0000000..78746a6
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/model/SmsResponse.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.sms.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 鐭俊杩斿洖闆嗗悎
+ *
+ * @author Chill
+ */
+@Data
+@AllArgsConstructor
+public class SmsResponse implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏄惁鎴愬姛
+ */
+ private boolean success;
+
+ /**
+ * 鐘舵�佺爜
+ */
+ private Integer code;
+
+ /**
+ * 杩斿洖娑堟伅
+ */
+ private String msg;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/props/SmsProperties.java b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/props/SmsProperties.java
new file mode 100644
index 0000000..360e503
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-sms/src/main/java/org/springblade/core/sms/props/SmsProperties.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.sms.props;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * 浜戠煭淇¢厤缃�
+ *
+ * @author Chill
+ */
+@Data
+@ConfigurationProperties(prefix = "sms")
+public class SmsProperties {
+
+ /**
+ * 鏄惁鍚敤
+ */
+ private Boolean enabled;
+
+ /**
+ * 鐭俊鏈嶅姟鍚嶇О
+ */
+ private String name;
+
+ /**
+ * 鐭俊妯℃澘ID
+ */
+ private String templateId;
+
+ /**
+ * regionId
+ */
+ private String regionId = "cn-hangzhou";
+
+ /**
+ * accessKey
+ */
+ private String accessKey;
+
+ /**
+ * secretKey
+ */
+ private String secretKey;
+
+ /**
+ * 鐭俊绛惧悕
+ */
+ private String signName;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-social/pom.xml b/Source/BladeX-Tool/blade-starter-social/pom.xml
new file mode 100644
index 0000000..3ce3508
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-social/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-social</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!--Blade-->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-redis</artifactId>
+ </dependency>
+ <!-- 绗笁鏂圭櫥闄� -->
+ <dependency>
+ <groupId>me.zhyd.oauth</groupId>
+ <artifactId>JustAuth</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-social/src/main/java/org/springblade/core/social/cache/AuthStateRedisCache.java b/Source/BladeX-Tool/blade-starter-social/src/main/java/org/springblade/core/social/cache/AuthStateRedisCache.java
new file mode 100644
index 0000000..82abc72
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-social/src/main/java/org/springblade/core/social/cache/AuthStateRedisCache.java
@@ -0,0 +1,69 @@
+package org.springblade.core.social.cache;
+
+import lombok.AllArgsConstructor;
+import me.zhyd.oauth.cache.AuthCacheConfig;
+import me.zhyd.oauth.cache.AuthStateCache;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ValueOperations;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 鎵╁睍Redis鐗堢殑state缂撳瓨
+ *
+ * @author yadong.zhang, Chill
+ */
+@AllArgsConstructor
+public class AuthStateRedisCache implements AuthStateCache {
+
+ private final RedisTemplate<String, Object> redisTemplate;
+
+ private final ValueOperations<String, Object> valueOperations;
+
+
+ /**
+ * 瀛樺叆缂撳瓨锛岄粯璁�3鍒嗛挓
+ *
+ * @param key 缂撳瓨key
+ * @param value 缂撳瓨鍐呭
+ */
+ @Override
+ public void cache(String key, String value) {
+ valueOperations.set(key, value, AuthCacheConfig.timeout, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * 瀛樺叆缂撳瓨
+ *
+ * @param key 缂撳瓨key
+ * @param value 缂撳瓨鍐呭
+ * @param timeout 鎸囧畾缂撳瓨杩囨湡鏃堕棿锛堟绉掞級
+ */
+ @Override
+ public void cache(String key, String value, long timeout) {
+ valueOperations.set(key, value, timeout, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * 鑾峰彇缂撳瓨鍐呭
+ *
+ * @param key 缂撳瓨key
+ * @return 缂撳瓨鍐呭
+ */
+ @Override
+ public String get(String key) {
+ return String.valueOf(valueOperations.get(key));
+ }
+
+ /**
+ * 鏄惁瀛樺湪key锛屽鏋滃搴攌ey鐨剉alue鍊煎凡杩囨湡锛屼篃杩斿洖false
+ *
+ * @param key 缂撳瓨key
+ * @return true锛氬瓨鍦╧ey锛屽苟涓攙alue娌¤繃鏈燂紱false锛歬ey涓嶅瓨鍦ㄦ垨鑰呭凡杩囨湡
+ */
+ @Override
+ public boolean containsKey(String key) {
+ return redisTemplate.hasKey(key);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-social/src/main/java/org/springblade/core/social/config/SocialConfiguration.java b/Source/BladeX-Tool/blade-starter-social/src/main/java/org/springblade/core/social/config/SocialConfiguration.java
new file mode 100644
index 0000000..5913a0f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-social/src/main/java/org/springblade/core/social/config/SocialConfiguration.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.social.config;
+
+import com.xkcoding.http.HttpUtil;
+import com.xkcoding.http.support.Http;
+import com.xkcoding.http.support.httpclient.HttpClientImpl;
+import me.zhyd.oauth.cache.AuthStateCache;
+import org.springblade.core.launch.props.BladePropertySource;
+import org.springblade.core.redis.config.RedisTemplateConfiguration;
+import org.springblade.core.social.cache.AuthStateRedisCache;
+import org.springblade.core.social.props.SocialProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.redis.core.RedisTemplate;
+
+/**
+ * SocialConfiguration
+ *
+ * @author Chill
+ */
+@EnableConfigurationProperties(SocialProperties.class)
+@AutoConfiguration(after = RedisTemplateConfiguration.class)
+@BladePropertySource(value = "classpath:/blade-social.yml")
+public class SocialConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean(Http.class)
+ public Http simpleHttp() {
+ HttpClientImpl httpClient = new HttpClientImpl();
+ HttpUtil.setHttp(httpClient);
+ return httpClient;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(AuthStateCache.class)
+ public AuthStateCache authStateCache(RedisTemplate<String, Object> redisTemplate) {
+ return new AuthStateRedisCache(redisTemplate, redisTemplate.opsForValue());
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-social/src/main/java/org/springblade/core/social/props/SocialProperties.java b/Source/BladeX-Tool/blade-starter-social/src/main/java/org/springblade/core/social/props/SocialProperties.java
new file mode 100644
index 0000000..55a4475
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-social/src/main/java/org/springblade/core/social/props/SocialProperties.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.social.props;
+
+import com.google.common.collect.Maps;
+import lombok.Getter;
+import lombok.Setter;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.config.AuthDefaultSource;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.Map;
+
+/**
+ * SocialProperties
+ *
+ * @author Chill
+ */
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "social")
+public class SocialProperties {
+
+ /**
+ * 鍚敤
+ */
+ private Boolean enabled = false;
+
+ /**
+ * 鍩熷悕鍦板潃
+ */
+ private String domain;
+
+ /**
+ * 绫诲瀷
+ */
+ private Map<AuthDefaultSource, AuthConfig> oauth = Maps.newHashMap();
+
+ /**
+ * 鍒悕
+ */
+ private Map<String, String> alias = Maps.newHashMap();
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-social/src/main/java/org/springblade/core/social/utils/SocialUtil.java b/Source/BladeX-Tool/blade-starter-social/src/main/java/org/springblade/core/social/utils/SocialUtil.java
new file mode 100644
index 0000000..f1ae83b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-social/src/main/java/org/springblade/core/social/utils/SocialUtil.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.social.utils;
+
+import me.zhyd.oauth.cache.AuthStateCache;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.config.AuthDefaultSource;
+import me.zhyd.oauth.exception.AuthException;
+import me.zhyd.oauth.request.*;
+import org.springblade.core.social.props.SocialProperties;
+import org.springblade.core.tool.utils.SpringUtil;
+
+import java.util.Objects;
+
+/**
+ * SocialUtil
+ *
+ * @author Chill
+ */
+public class SocialUtil {
+
+ /**
+ * 鑷畾涔塻tate缂撳瓨
+ */
+ private static AuthStateCache authStateCache;
+
+ public static AuthStateCache getAuthStateCache() {
+ if (authStateCache == null) {
+ authStateCache = SpringUtil.getBean(AuthStateCache.class);
+ }
+ return authStateCache;
+ }
+
+ /**
+ * 鏍规嵁鍏蜂綋鐨勬巿鏉冩潵婧愶紝鑾峰彇鎺堟潈璇锋眰宸ュ叿绫�
+ *
+ * @param source 鎺堟潈鏉ユ簮
+ * @return AuthRequest
+ */
+ public static AuthRequest getAuthRequest(String source, SocialProperties socialProperties) {
+ AuthDefaultSource authSource = Objects.requireNonNull(AuthDefaultSource.valueOf(source.toUpperCase()));
+ AuthConfig authConfig = socialProperties.getOauth().get(authSource);
+ if (authConfig == null) {
+ throw new AuthException("鏈幏鍙栧埌鏈夋晥鐨凙uth閰嶇疆");
+ }
+ AuthRequest authRequest = null;
+ switch (authSource) {
+ case GITHUB:
+ authRequest = new AuthGithubRequest(authConfig, getAuthStateCache());
+ break;
+ case GITEE:
+ authRequest = new AuthGiteeRequest(authConfig, getAuthStateCache());
+ break;
+ case OSCHINA:
+ authRequest = new AuthOschinaRequest(authConfig, getAuthStateCache());
+ break;
+ case QQ:
+ authRequest = new AuthQqRequest(authConfig, getAuthStateCache());
+ break;
+ case WECHAT_OPEN:
+ authRequest = new AuthWeChatOpenRequest(authConfig, getAuthStateCache());
+ break;
+ case WECHAT_ENTERPRISE:
+ authRequest = new AuthWeChatEnterpriseQrcodeRequest(authConfig, getAuthStateCache());
+ break;
+ case WECHAT_ENTERPRISE_WEB:
+ authRequest = new AuthWeChatEnterpriseWebRequest(authConfig, getAuthStateCache());
+ break;
+ case WECHAT_MP:
+ authRequest = new AuthWeChatMpRequest(authConfig, getAuthStateCache());
+ break;
+ case DINGTALK:
+ authRequest = new AuthDingTalkRequest(authConfig, getAuthStateCache());
+ break;
+ case ALIPAY:
+ // 鏀粯瀹濆湪鍒涘缓鍥炶皟鍦板潃鏃讹紝涓嶅厑璁镐娇鐢╨ocalhost鎴栬��127.0.0.1锛屾墍浠ヨ繖鍎跨殑鍥炶皟鍦板潃浣跨敤鐨勫眬鍩熺綉鍐呯殑ip
+ authRequest = new AuthAlipayRequest(authConfig, getAuthStateCache());
+ break;
+ case BAIDU:
+ authRequest = new AuthBaiduRequest(authConfig, getAuthStateCache());
+ break;
+ case WEIBO:
+ authRequest = new AuthWeiboRequest(authConfig, getAuthStateCache());
+ break;
+ case CODING:
+ authRequest = new AuthCodingRequest(authConfig, getAuthStateCache());
+ break;
+ case CSDN:
+ authRequest = new AuthCsdnRequest(authConfig, getAuthStateCache());
+ break;
+ case TAOBAO:
+ authRequest = new AuthTaobaoRequest(authConfig, getAuthStateCache());
+ break;
+ case GOOGLE:
+ authRequest = new AuthGoogleRequest(authConfig, getAuthStateCache());
+ break;
+ case FACEBOOK:
+ authRequest = new AuthFacebookRequest(authConfig, getAuthStateCache());
+ break;
+ case DOUYIN:
+ authRequest = new AuthDouyinRequest(authConfig, getAuthStateCache());
+ break;
+ case LINKEDIN:
+ authRequest = new AuthLinkedinRequest(authConfig, getAuthStateCache());
+ break;
+ case MICROSOFT:
+ authRequest = new AuthMicrosoftRequest(authConfig, getAuthStateCache());
+ break;
+ case MI:
+ authRequest = new AuthMiRequest(authConfig, getAuthStateCache());
+ break;
+ case TOUTIAO:
+ authRequest = new AuthToutiaoRequest(authConfig, getAuthStateCache());
+ break;
+ case TEAMBITION:
+ authRequest = new AuthTeambitionRequest(authConfig, getAuthStateCache());
+ break;
+ case PINTEREST:
+ authRequest = new AuthPinterestRequest(authConfig, getAuthStateCache());
+ break;
+ case RENREN:
+ authRequest = new AuthRenrenRequest(authConfig, getAuthStateCache());
+ break;
+ case STACK_OVERFLOW:
+ authRequest = new AuthStackOverflowRequest(authConfig, getAuthStateCache());
+ break;
+ case HUAWEI:
+ authRequest = new AuthHuaweiRequest(authConfig, getAuthStateCache());
+ break;
+ case KUJIALE:
+ authRequest = new AuthKujialeRequest(authConfig, getAuthStateCache());
+ break;
+ case GITLAB:
+ authRequest = new AuthGitlabRequest(authConfig, getAuthStateCache());
+ break;
+ case MEITUAN:
+ authRequest = new AuthMeituanRequest(authConfig, getAuthStateCache());
+ break;
+ case ELEME:
+ authRequest = new AuthElemeRequest(authConfig, getAuthStateCache());
+ break;
+ case TWITTER:
+ authRequest = new AuthTwitterRequest(authConfig, getAuthStateCache());
+ break;
+ default:
+ break;
+ }
+ if (null == authRequest) {
+ throw new AuthException("鏈幏鍙栧埌鏈夋晥鐨凙uth閰嶇疆");
+ }
+ return authRequest;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-social/src/main/resources/blade-social.yml b/Source/BladeX-Tool/blade-starter-social/src/main/resources/blade-social.yml
new file mode 100644
index 0000000..694b1b6
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-social/src/main/resources/blade-social.yml
@@ -0,0 +1,3 @@
+blade:
+ social:
+ enabled: false
diff --git a/Source/BladeX-Tool/blade-starter-swagger/pom.xml b/Source/BladeX-Tool/blade-starter-swagger/pom.xml
new file mode 100644
index 0000000..7c6d9ed
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-swagger/pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-swagger</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!--Blade-->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-tool</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-auth</artifactId>
+ </dependency>
+ <!--Swagger-->
+ <dependency>
+ <groupId>com.github.xiaoymin</groupId>
+ <artifactId>knife4j-micro-spring-boot-starter</artifactId>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/EnableSwagger.java b/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/EnableSwagger.java
new file mode 100644
index 0000000..e11433b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/EnableSwagger.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.swagger;
+
+import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
+
+import java.lang.annotation.*;
+
+/**
+ * Swagger閰嶇疆寮�鍏�
+ *
+ * @author Chill
+ */
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@EnableSwagger2WebMvc
+public @interface EnableSwagger {
+}
diff --git a/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerAutoConfiguration.java b/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerAutoConfiguration.java
new file mode 100644
index 0000000..1c17b1e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerAutoConfiguration.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.swagger;
+
+
+import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
+import com.google.common.collect.Lists;
+import lombok.AllArgsConstructor;
+import org.springblade.core.launch.props.BladeProperties;
+import org.springblade.core.secure.BladeUser;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
+import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.service.*;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spi.service.contexts.SecurityContext;
+import springfox.documentation.spring.web.plugins.ApiSelectorBuilder;
+import springfox.documentation.spring.web.plugins.Docket;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * swagger閰嶇疆
+ *
+ * @author Chill
+ */
+@EnableSwagger
+@AutoConfiguration
+@AllArgsConstructor
+@Import(BeanValidatorPluginsConfiguration.class)
+public class SwaggerAutoConfiguration {
+
+ private static final String DEFAULT_BASE_PATH = "/**";
+ private static final List<String> DEFAULT_EXCLUDE_PATH = Arrays.asList("/error", "/actuator/**");
+
+ /**
+ * 寮曞叆Knife4j鎵╁睍绫�
+ */
+ private final OpenApiExtensionResolver openApiExtensionResolver;
+
+ /**
+ * 寮曞叆Blade鐜鍙橀噺
+ */
+ private final BladeProperties bladeProperties;
+
+ @Bean
+ @ConditionalOnMissingBean
+ public SwaggerProperties swaggerProperties() {
+ return new SwaggerProperties();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public Docket api(SwaggerProperties swaggerProperties) {
+ if (swaggerProperties.getBasePath().size() == 0) {
+ swaggerProperties.getBasePath().add(DEFAULT_BASE_PATH);
+ }
+ if (swaggerProperties.getExcludePath().size() == 0) {
+ swaggerProperties.getExcludePath().addAll(DEFAULT_EXCLUDE_PATH);
+ }
+ ApiSelectorBuilder apis = new Docket(DocumentationType.SWAGGER_2)
+ .host(swaggerProperties.getHost())
+ .apiInfo(apiInfo(swaggerProperties)).ignoredParameterTypes(BladeUser.class).select()
+ .apis(SwaggerUtil.basePackages(swaggerProperties.getBasePackages()));
+ swaggerProperties.getBasePath().forEach(p -> apis.paths(PathSelectors.ant(p)));
+ swaggerProperties.getExcludePath().forEach(p -> apis.paths(PathSelectors.ant(p).negate()));
+ return apis.build().securityContexts(securityContexts(swaggerProperties)).securitySchemes(securitySchemas(swaggerProperties))
+ .extensions(openApiExtensionResolver.buildExtensions(bladeProperties.getName()));
+ }
+
+ /**
+ * 閰嶇疆榛樿鐨勫叏灞�閴存潈绛栫暐鐨勫紑鍏筹紝閫氳繃姝e垯琛ㄨ揪寮忚繘琛屽尮閰嶏紱榛樿鍖归厤鎵�鏈塙RL
+ */
+ private List<SecurityContext> securityContexts(SwaggerProperties swaggerProperties) {
+ return Collections.singletonList(SecurityContext.builder()
+ .securityReferences(defaultAuth(swaggerProperties))
+ .forPaths(PathSelectors.regex(swaggerProperties.getAuthorization().getAuthRegex()))
+ .build());
+ }
+
+ /**
+ * 榛樿鐨勫叏灞�閴存潈绛栫暐
+ */
+ private List<SecurityReference> defaultAuth(SwaggerProperties swaggerProperties) {
+ List<AuthorizationScope> authorizationScopeList = new ArrayList<>();
+ List<SecurityReference> securityReferenceList = new ArrayList<>();
+ List<SwaggerProperties.AuthorizationScope> swaggerScopeList = swaggerProperties.getAuthorization().getAuthorizationScopeList();
+ swaggerScopeList.forEach(authorizationScope -> authorizationScopeList.add(new AuthorizationScope(authorizationScope.getScope(), authorizationScope.getDescription())));
+ if (authorizationScopeList.size() == 0) {
+ authorizationScopeList.add(new AuthorizationScope("global", "accessEverywhere"));
+ }
+ AuthorizationScope[] authorizationScopes = authorizationScopeList.toArray(new AuthorizationScope[0]);
+ swaggerScopeList.forEach(authorizationScope -> securityReferenceList.add(new SecurityReference(authorizationScope.getName(), authorizationScopes)));
+ if (securityReferenceList.size() == 0) {
+ securityReferenceList.add(new SecurityReference(SwaggerUtil.clientInfo().getName(), authorizationScopes));
+ securityReferenceList.add(new SecurityReference(SwaggerUtil.bladeAuth().getName(), authorizationScopes));
+ securityReferenceList.add(new SecurityReference(SwaggerUtil.bladeTenant().getName(), authorizationScopes));
+ }
+ return securityReferenceList;
+ }
+
+ /**
+ * 閰嶇疆瀹夊叏绛栫暐
+ */
+ private List<SecurityScheme> securitySchemas(SwaggerProperties swaggerProperties) {
+ List<SwaggerProperties.AuthorizationApiKey> swaggerApiKeyList = swaggerProperties.getAuthorization().getAuthorizationApiKeyList();
+ if (swaggerApiKeyList.size() == 0) {
+ return Lists.newArrayList(SwaggerUtil.clientInfo(), SwaggerUtil.bladeAuth(), SwaggerUtil.bladeTenant());
+ } else {
+ List<SecurityScheme> securitySchemeList = new ArrayList<>();
+ swaggerApiKeyList.forEach(authorizationApiKey -> securitySchemeList.add(new ApiKey(authorizationApiKey.getName(), authorizationApiKey.getKeyName(), authorizationApiKey.getPassAs())));
+ return securitySchemeList;
+ }
+ }
+
+ /**
+ * 閰嶇疆鍩烘湰淇℃伅
+ */
+ private ApiInfo apiInfo(SwaggerProperties swaggerProperties) {
+ return new ApiInfoBuilder()
+ .title(swaggerProperties.getTitle())
+ .description(swaggerProperties.getDescription())
+ .license(swaggerProperties.getLicense())
+ .licenseUrl(swaggerProperties.getLicenseUrl())
+ .termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl())
+ .contact(new Contact(swaggerProperties.getContact().getName(), swaggerProperties.getContact().getUrl(), swaggerProperties.getContact().getEmail()))
+ .version(swaggerProperties.getVersion())
+ .build();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerHandlerConfiguration.java b/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerHandlerConfiguration.java
new file mode 100644
index 0000000..2ea586f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerHandlerConfiguration.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.swagger;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
+import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
+
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 瑙e喅swagger2涓庢渶鏂扮増springboot鍐茬獊鐨勯棶棰�
+ *
+ * @author Chill
+ */
+@AutoConfiguration
+public class SwaggerHandlerConfiguration {
+
+ @Bean
+ public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
+ return new BeanPostProcessor() {
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+ if (bean instanceof WebMvcRequestHandlerProvider) {
+ customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
+ }
+ return bean;
+ }
+
+ private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
+ List<T> copy = mappings.stream()
+ .filter(mapping -> mapping.getPatternParser() == null)
+ .collect(Collectors.toList());
+ mappings.clear();
+ mappings.addAll(copy);
+ }
+ };
+ }
+
+ private static List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
+ try {
+ Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
+ field.setAccessible(true);
+ return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerLauncherServiceImpl.java b/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerLauncherServiceImpl.java
new file mode 100644
index 0000000..11f7525
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerLauncherServiceImpl.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.swagger;
+
+import org.springblade.core.auto.service.AutoService;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.launch.service.LauncherService;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.core.Ordered;
+
+import java.util.Properties;
+
+/**
+ * 鍒濆鍖朣wagger閰嶇疆
+ *
+ * @author Chill
+ */
+@AutoService(LauncherService.class)
+public class SwaggerLauncherServiceImpl implements LauncherService {
+ @Override
+ public void launcher(SpringApplicationBuilder builder, String appName, String profile, boolean isLocalDev) {
+ Properties props = System.getProperties();
+ if (profile.equals(AppConstant.PROD_CODE)) {
+ props.setProperty("knife4j.production", "true");
+ }
+ props.setProperty("knife4j.enable", "true");
+ props.setProperty("spring.mvc.pathmatch.matching-strategy", "ANT_PATH_MATCHER");
+ }
+
+ @Override
+ public int getOrder() {
+ return Ordered.LOWEST_PRECEDENCE;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerProperties.java b/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerProperties.java
new file mode 100644
index 0000000..2d44f38
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerProperties.java
@@ -0,0 +1,177 @@
+/*
+ *
+ * Copyright (c) 2018-2025, lengleng All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: lengleng (wangiegie@gmail.com)
+ *
+ */
+package org.springblade.core.swagger;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * SwaggerProperties
+ *
+ * @author Chill
+ */
+@Data
+@ConfigurationProperties("swagger")
+public class SwaggerProperties {
+ /**
+ * swagger浼氳В鏋愮殑鍖呰矾寰�
+ **/
+ private List<String> basePackages = new ArrayList<>(Collections.singletonList(AppConstant.BASE_PACKAGES));
+ /**
+ * swagger浼氳В鏋愮殑url瑙勫垯
+ **/
+ private List<String> basePath = new ArrayList<>();
+ /**
+ * 鍦╞asePath鍩虹涓婇渶瑕佹帓闄ょ殑url瑙勫垯
+ **/
+ private List<String> excludePath = new ArrayList<>();
+ /**
+ * 鏍囬
+ **/
+ private String title = "BladeX 鎺ュ彛鏂囨。绯荤粺";
+ /**
+ * 鎻忚堪
+ **/
+ private String description = "BladeX 鎺ュ彛鏂囨。绯荤粺";
+ /**
+ * 鐗堟湰
+ **/
+ private String version = AppConstant.APPLICATION_VERSION;
+ /**
+ * 璁稿彲璇�
+ **/
+ private String license = "Powered By BladeX";
+ /**
+ * 璁稿彲璇乁RL
+ **/
+ private String licenseUrl = "https://bladex.vip";
+ /**
+ * 鏈嶅姟鏉℃URL
+ **/
+ private String termsOfServiceUrl = "https://bladex.vip";
+
+ /**
+ * host淇℃伅
+ **/
+ private String host = "";
+ /**
+ * 鑱旂郴浜轰俊鎭�
+ */
+ private Contact contact = new Contact();
+ /**
+ * 鍏ㄥ眬缁熶竴閴存潈閰嶇疆
+ **/
+ private Authorization authorization = new Authorization();
+
+ @Data
+ @NoArgsConstructor
+ public static class Contact {
+
+ /**
+ * 鑱旂郴浜�
+ **/
+ private String name = "chillzhuang";
+ /**
+ * 鑱旂郴浜簎rl
+ **/
+ private String url = "https://gitee.com/smallc";
+ /**
+ * 鑱旂郴浜篹mail
+ **/
+ private String email = "smallchill@163.com";
+
+ }
+
+ @Data
+ @NoArgsConstructor
+ public static class Authorization {
+
+ /**
+ * 閴存潈绛栫暐ID锛岄渶瑕佸拰SecurityReferences ID淇濇寔涓�鑷�
+ */
+ private String name = "";
+
+ /**
+ * 闇�瑕佸紑鍚壌鏉僓RL鐨勬鍒�
+ */
+ private String authRegex = "^.*$";
+
+ /**
+ * 閴存潈浣滅敤鍩熷垪琛�
+ */
+ private List<AuthorizationScope> authorizationScopeList = new ArrayList<>();
+
+ /**
+ * 閴存潈璇锋眰澶村弬鏁板垪琛�
+ */
+ private List<AuthorizationApiKey> authorizationApiKeyList = new ArrayList<>();
+
+ /**
+ * 鎺ュ彛鍖归厤鍦板潃
+ */
+ private List<String> tokenUrlList = new ArrayList<>();
+ }
+
+ @Data
+ @NoArgsConstructor
+ public static class AuthorizationScope {
+
+ /**
+ * 閴存潈绛栫暐鍚�, 闇�瑕佸拰ApiKey鐨刵ame淇濇寔涓�鑷�
+ */
+ private String name = "";
+ /**
+ * 浣滅敤鍩熷悕绉�
+ */
+ private String scope = "";
+
+ /**
+ * 浣滅敤鍩熸弿杩�
+ */
+ private String description = "";
+
+ }
+
+ @Data
+ @NoArgsConstructor
+ public static class AuthorizationApiKey {
+
+ /**
+ * 鍙傛暟鍚�
+ */
+ private String name = "";
+
+ /**
+ * 鍙傛暟鍊�
+ */
+ private String keyName = "";
+
+ /**
+ * 鍙傛暟浣滅敤鍩�
+ */
+ private String passAs = "";
+
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerUtil.java b/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerUtil.java
new file mode 100644
index 0000000..feb569c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerUtil.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.swagger;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import org.springblade.core.launch.constant.TokenConstant;
+import springfox.documentation.RequestHandler;
+import springfox.documentation.service.ApiKey;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * Swagger宸ュ叿绫�
+ *
+ * @author Chill
+ */
+public class SwaggerUtil {
+
+ /**
+ * 鑾峰彇鍖呴泦鍚�
+ *
+ * @param basePackages 澶氫釜鍖呭悕闆嗗悎
+ */
+ public static Predicate<RequestHandler> basePackages(final List<String> basePackages) {
+ return input -> declaringClass(input).transform(handlerPackage(basePackages)).or(true);
+ }
+
+ private static Function<Class<?>, Boolean> handlerPackage(final List<String> basePackages) {
+ return input -> {
+ // 寰幆鍒ゆ柇鍖归厤
+ for (String strPackage : basePackages) {
+ boolean isMatch = input.getPackage().getName().startsWith(strPackage);
+ if (isMatch) {
+ return true;
+ }
+ }
+ return false;
+ };
+ }
+
+ private static Optional<? extends Class<?>> declaringClass(RequestHandler input) {
+ return Optional.fromNullable(input.declaringClass());
+ }
+
+
+ public static ApiKey clientInfo() {
+ return new ApiKey("ClientInfo", "Authorization", "header");
+ }
+
+ public static ApiKey bladeAuth() {
+ return new ApiKey("BladeAuth", TokenConstant.HEADER, "header");
+ }
+
+ public static ApiKey bladeTenant() {
+ return new ApiKey("TenantId", "Tenant-Id", "header");
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerWebConfiguration.java b/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerWebConfiguration.java
new file mode 100644
index 0000000..8faf1ad
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-swagger/src/main/java/org/springblade/core/swagger/SwaggerWebConfiguration.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.swagger;
+
+
+import org.springblade.core.launch.props.BladePropertySource;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * swagger璧勬簮閰嶇疆
+ *
+ * @author Chill
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(SwaggerProperties.class)
+@BladePropertySource(value = "classpath:/blade-swagger.yml")
+public class SwaggerWebConfiguration implements WebMvcConfigurer {
+
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
+ registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-swagger/src/main/resources/blade-swagger.yml b/Source/BladeX-Tool/blade-starter-swagger/src/main/resources/blade-swagger.yml
new file mode 100644
index 0000000..3dc5b44
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-swagger/src/main/resources/blade-swagger.yml
@@ -0,0 +1,11 @@
+#swagger鍏叡淇℃伅
+swagger:
+ title: BladeX 鎺ュ彛鏂囨。绯荤粺
+ description: BladeX 鎺ュ彛鏂囨。绯荤粺
+ license: Powered By BladeX
+ license-url: https://bladex.vip
+ terms-of-service-url: https://bladex.vip
+ contact:
+ name: smallchill
+ email: smallchill@163.com
+ url: https://gitee.com/smallc
diff --git a/Source/BladeX-Tool/blade-starter-tenant/pom.xml b/Source/BladeX-Tool/blade-starter-tenant/pom.xml
new file mode 100644
index 0000000..eb3b02c
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-tenant</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!--Blade-->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-mybatis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-cache</artifactId>
+ </dependency>
+ <!-- Druid -->
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>druid-spring-boot-starter</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!--Dynamic-->
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantHandler.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantHandler.java
new file mode 100644
index 0000000..ef2d7cb
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantHandler.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant;
+
+import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
+import com.baomidou.mybatisplus.core.metadata.TableInfo;
+import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
+import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.StringValue;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tenant.annotation.TableExclude;
+import org.springblade.core.tool.constant.BladeConstant;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.beans.factory.SmartInitializingSingleton;
+import org.springframework.context.ApplicationContext;
+
+import java.util.*;
+
+/**
+ * 绉熸埛淇℃伅澶勭悊鍣�
+ *
+ * @author Chill, L.cm
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class BladeTenantHandler implements TenantLineHandler, SmartInitializingSingleton {
+ /**
+ * 鍖归厤鐨勫绉熸埛琛�
+ */
+ private final List<String> tenantTableList = new ArrayList<>();
+ /**
+ * 闇�瑕佹帓闄よ繘琛岃嚜瀹氫箟鐨勫绉熸埛琛�
+ */
+ private final List<String> excludeTableList = Arrays.asList("blade_user", "blade_dept", "blade_role", "blade_tenant", "act_de_model");
+ /**
+ * 澶氱鎴烽厤缃�
+ */
+ private final BladeTenantProperties tenantProperties;
+
+ /**
+ * 鑾峰彇绉熸埛ID
+ *
+ * @return 绉熸埛ID
+ */
+ @Override
+ public Expression getTenantId() {
+ return new StringValue(Func.toStr(AuthUtil.getTenantId(), BladeConstant.ADMIN_TENANT_ID));
+ }
+
+ /**
+ * 鑾峰彇绉熸埛瀛楁鍚嶇О
+ *
+ * @return 绉熸埛瀛楁鍚嶇О
+ */
+ @Override
+ public String getTenantIdColumn() {
+ return tenantProperties.getColumn();
+ }
+
+ /**
+ * 鏍规嵁琛ㄥ悕鍒ゆ柇鏄惁蹇界暐鎷兼帴澶氱鎴锋潯浠�
+ * 榛樿閮借杩涜瑙f瀽骞舵嫾鎺ュ绉熸埛鏉′欢
+ *
+ * @param tableName 琛ㄥ悕
+ * @return 鏄惁蹇界暐, true:琛ㄧず蹇界暐锛宖alse:闇�瑕佽В鏋愬苟鎷兼帴澶氱鎴锋潯浠�
+ */
+ @Override
+ public boolean ignoreTable(String tableName) {
+ if (BladeTenantHolder.isIgnore()) {
+ return true;
+ }
+ return !(tenantTableList.contains(tableName) && StringUtil.isNotBlank(AuthUtil.getTenantId()));
+ }
+
+ @Override
+ public void afterSingletonsInstantiated() {
+ ApplicationContext context = SpringUtil.getContext();
+ if (tenantProperties.getAnnotationExclude() && context != null) {
+ Map<String, Object> tables = context.getBeansWithAnnotation(TableExclude.class);
+ List<String> excludeTables = tenantProperties.getExcludeTables();
+ for (Object o : tables.values()) {
+ TableExclude annotation = o.getClass().getAnnotation(TableExclude.class);
+ String value = annotation.value();
+ excludeTables.add(value);
+ }
+ }
+ List<TableInfo> tableInfos = TableInfoHelper.getTableInfos();
+ tableFor:
+ for (TableInfo tableInfo : tableInfos) {
+ String tableName = tableInfo.getTableName();
+ if (tenantProperties.getExcludeTables().contains(tableName) ||
+ excludeTableList.contains(tableName.toLowerCase()) ||
+ excludeTableList.contains(tableName.toUpperCase())) {
+ continue;
+ }
+ List<TableFieldInfo> fieldList = tableInfo.getFieldList();
+ for (TableFieldInfo fieldInfo : fieldList) {
+ String column = fieldInfo.getColumn();
+ if (tenantProperties.getColumn().equals(column)) {
+ tenantTableList.add(tableName);
+ continue tableFor;
+ }
+ }
+ }
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantHolder.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantHolder.java
new file mode 100644
index 0000000..a6b9b2e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantHolder.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant;
+
+import org.springframework.core.NamedThreadLocal;
+
+/**
+ * 绉熸埛绾跨▼澶勭悊
+ *
+ * @author Chill
+ */
+public class BladeTenantHolder {
+
+ private static final ThreadLocal<Boolean> TENANT_KEY_HOLDER = new NamedThreadLocal<Boolean>("blade-tenant") {
+ @Override
+ protected Boolean initialValue() {
+ return Boolean.FALSE;
+ }
+ };
+
+ public static void setIgnore(Boolean ignore) {
+ TENANT_KEY_HOLDER.set(ignore);
+ }
+
+ public static Boolean isIgnore() {
+ return TENANT_KEY_HOLDER.get();
+ }
+
+
+ public static void clear() {
+ TENANT_KEY_HOLDER.remove();
+ }
+
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantId.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantId.java
new file mode 100644
index 0000000..b6006d0
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantId.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant;
+
+import org.springblade.core.tool.utils.RandomType;
+import org.springblade.core.tool.utils.StringUtil;
+
+/**
+ * blade绉熸埛id鐢熸垚鍣�
+ *
+ * @author Chill
+ */
+public class BladeTenantId implements TenantId {
+ @Override
+ public String generate() {
+ return StringUtil.random(6, RandomType.INT);
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantInterceptor.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantInterceptor.java
new file mode 100644
index 0000000..9667309
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantInterceptor.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant;
+
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
+import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
+import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import net.sf.jsqlparser.expression.*;
+import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
+import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
+import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
+import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
+import net.sf.jsqlparser.expression.operators.relational.ItemsList;
+import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList;
+import net.sf.jsqlparser.schema.Column;
+import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.statement.delete.Delete;
+import net.sf.jsqlparser.statement.insert.Insert;
+import net.sf.jsqlparser.statement.select.*;
+import net.sf.jsqlparser.statement.update.Update;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.utils.CollectionUtil;
+import org.springblade.core.tool.utils.StringPool;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 绉熸埛鎷︽埅鍣�
+ *
+ * @author Chill
+ */
+@Data
+@ToString(callSuper = true)
+@EqualsAndHashCode(callSuper = true)
+public class BladeTenantInterceptor extends TenantLineInnerInterceptor {
+
+ /**
+ * 绉熸埛澶勭悊鍣�
+ */
+ private TenantLineHandler tenantLineHandler;
+ /**
+ * 绉熸埛閰嶇疆鏂囦欢
+ */
+ private BladeTenantProperties tenantProperties;
+ /**
+ * 瓒呯闇�瑕佸惎鐢ㄧ鎴疯繃婊ょ殑琛�
+ */
+ private List<String> adminTenantTables = Arrays.asList("blade_top_menu", "blade_dict_biz");
+
+ @Override
+ public void setTenantLineHandler(TenantLineHandler tenantLineHandler) {
+ super.setTenantLineHandler(tenantLineHandler);
+ this.tenantLineHandler = tenantLineHandler;
+ }
+
+ @Override
+ protected void processInsert(Insert insert, int index, String sql, Object obj) {
+ // 鏈惎鐢ㄧ鎴峰寮猴紝鍒欎娇鐢ㄥ師鐗堥�昏緫
+ if (!tenantProperties.getEnhance()) {
+ super.processInsert(insert, index, sql, obj);
+ return;
+ }
+ if (tenantLineHandler.ignoreTable(insert.getTable().getName())) {
+ // 杩囨护閫�鍑烘墽琛�
+ return;
+ }
+ List<Column> columns = insert.getColumns();
+ if (CollectionUtils.isEmpty(columns)) {
+ // 閽堝涓嶇粰鍒楀悕鐨刬nsert 涓嶅鐞�
+ return;
+ }
+ String tenantIdColumn = tenantLineHandler.getTenantIdColumn();
+ if (columns.stream().map(Column::getColumnName).anyMatch(i -> i.equals(tenantIdColumn))) {
+ // 閽堝宸茬粰鍑虹鎴峰垪鐨刬nsert 涓嶅鐞�
+ return;
+ }
+ columns.add(new Column(tenantIdColumn));
+
+ // fixed gitee pulls/141 duplicate update
+ List<Expression> duplicateUpdateColumns = insert.getDuplicateUpdateExpressionList();
+ if (CollectionUtils.isNotEmpty(duplicateUpdateColumns)) {
+ EqualsTo equalsTo = new EqualsTo();
+ equalsTo.setLeftExpression(new StringValue(tenantIdColumn));
+ equalsTo.setRightExpression(tenantLineHandler.getTenantId());
+ duplicateUpdateColumns.add(equalsTo);
+ }
+
+ Select select = insert.getSelect();
+ if (select != null) {
+ this.processInsertSelect(select.getSelectBody());
+ } else if (insert.getItemsList() != null) {
+ // fixed github pull/295
+ ItemsList itemsList = insert.getItemsList();
+ if (itemsList instanceof MultiExpressionList) {
+ ((MultiExpressionList) itemsList).getExpressionLists().forEach(el -> el.getExpressions().add(tenantLineHandler.getTenantId()));
+ } else {
+ ((ExpressionList) itemsList).getExpressions().add(tenantLineHandler.getTenantId());
+ }
+ } else {
+ throw ExceptionUtils.mpe("Failed to process multiple-table update, please exclude the tableName or statementId");
+ }
+ }
+
+ /**
+ * 澶勭悊 PlainSelect
+ */
+ @Override
+ protected void processPlainSelect(PlainSelect plainSelect) {
+ //#3087 github
+ List<SelectItem> selectItems = plainSelect.getSelectItems();
+ if (CollectionUtils.isNotEmpty(selectItems)) {
+ selectItems.forEach(this::processSelectItem);
+ }
+
+ // 澶勭悊 where 涓殑瀛愭煡璇�
+ Expression where = plainSelect.getWhere();
+ processWhereSubSelect(where);
+
+ // 澶勭悊 fromItem
+ FromItem fromItem = plainSelect.getFromItem();
+ List<Table> list = processFromItem(fromItem);
+ List<Table> mainTables = new ArrayList<>(list);
+
+ // 澶勭悊 join
+ List<Join> joins = plainSelect.getJoins();
+ if (CollectionUtils.isNotEmpty(joins)) {
+ mainTables = processJoins(mainTables, joins);
+ }
+
+ // 褰撴湁 mainTable 鏃讹紝杩涜 where 鏉′欢杩藉姞
+ if (CollectionUtils.isNotEmpty(mainTables) && !doTenantFilters(mainTables)) {
+ plainSelect.setWhere(builderExpression(where, mainTables));
+ }
+ }
+
+ /**
+ * update 璇彞澶勭悊
+ */
+ @Override
+ protected void processUpdate(Update update, int index, String sql, Object obj) {
+ final Table table = update.getTable();
+ if (tenantLineHandler.ignoreTable(table.getName())) {
+ // 杩囨护閫�鍑烘墽琛�
+ return;
+ }
+ if (doTenantFilter(table.getName())) {
+ // 杩囨护閫�鍑烘墽琛�
+ return;
+ }
+ update.setWhere(this.andExpression(table, update.getWhere()));
+ }
+
+ /**
+ * delete 璇彞澶勭悊
+ */
+ @Override
+ protected void processDelete(Delete delete, int index, String sql, Object obj) {
+ final Table table = delete.getTable();
+ if (tenantLineHandler.ignoreTable(table.getName())) {
+ // 杩囨护閫�鍑烘墽琛�
+ return;
+ }
+ if (doTenantFilter(table.getName())) {
+ // 杩囨护閫�鍑烘墽琛�
+ return;
+ }
+ delete.setWhere(this.andExpression(table, delete.getWhere()));
+ }
+
+ /**
+ * delete update 璇彞 where 澶勭悊
+ */
+ @Override
+ protected BinaryExpression andExpression(Table table, Expression where) {
+ //鑾峰緱鏉′欢琛ㄨ揪寮�
+ EqualsTo equalsTo = new EqualsTo();
+ Expression leftExpression = this.getAliasColumn(table);
+ Expression rightExpression = tenantLineHandler.getTenantId();
+ // 鑻ユ槸瓒呯鍒欎笉杩涜杩囨护
+ if (doTenantFilter(table.getName())) {
+ leftExpression = rightExpression = new StringValue(StringPool.ONE);
+ }
+ equalsTo.setLeftExpression(leftExpression);
+ equalsTo.setRightExpression(rightExpression);
+ if (null != where) {
+ if (where instanceof OrExpression) {
+ return new AndExpression(equalsTo, new Parenthesis(where));
+ } else {
+ return new AndExpression(equalsTo, where);
+ }
+ }
+ return equalsTo;
+ }
+
+ /**
+ * 澧炲己鎻掍欢浣胯秴绾х鐞嗗憳鍙互鐪嬪埌鎵�鏈夌鎴锋暟鎹�
+ */
+ @Override
+ protected Expression builderExpression(Expression currentExpression, List<Table> tables) {
+ // 娌℃湁琛ㄩ渶瑕佸鐞嗙洿鎺ヨ繑鍥�
+ if (CollectionUtils.isEmpty(tables)) {
+ return currentExpression;
+ }
+ // 绉熸埛
+ Expression tenantId = tenantLineHandler.getTenantId();
+ // 鏋勯�犳瘡寮犺〃鐨勬潯浠�
+ List<EqualsTo> equalsTos = tables.stream()
+ // 绉熸埛蹇界暐琛�
+ .filter(x -> !tenantLineHandler.ignoreTable(x.getName()))
+ // 瓒呯蹇界暐琛�
+ .filter(x -> !doTenantFilter(x.getName()))
+ .map(item -> new EqualsTo(getAliasColumn(item), tenantId))
+ .collect(Collectors.toList());
+
+ if (CollectionUtils.isEmpty(equalsTos)) {
+ return currentExpression;
+ }
+
+ // 娉ㄥ叆鐨勮〃杈惧紡
+ Expression injectExpression = equalsTos.get(0);
+ // 濡傛灉鏈夊琛紝鍒欑敤 and 杩炴帴
+ if (equalsTos.size() > 1) {
+ for (int i = 1; i < equalsTos.size(); i++) {
+ injectExpression = new AndExpression(injectExpression, equalsTos.get(i));
+ }
+ }
+
+ if (currentExpression == null) {
+ return injectExpression;
+ }
+ if (currentExpression instanceof OrExpression) {
+ return new AndExpression(new Parenthesis(currentExpression), injectExpression);
+ } else {
+ return new AndExpression(currentExpression, injectExpression);
+ }
+
+ }
+
+ private List<Table> processFromItem(FromItem fromItem) {
+ // 澶勭悊鎷彿鎷捣鏉ョ殑琛ㄨ揪寮�
+ while (fromItem instanceof ParenthesisFromItem) {
+ fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
+ }
+
+ List<Table> mainTables = new ArrayList<>();
+ // 鏃� join 鏃剁殑澶勭悊閫昏緫
+ if (fromItem instanceof Table) {
+ Table fromTable = (Table) fromItem;
+ mainTables.add(fromTable);
+ } else if (fromItem instanceof SubJoin) {
+ // SubJoin 绫诲瀷鍒欒繕闇�瑕佹坊鍔犱笂 where 鏉′欢
+ List<Table> tables = processSubJoin((SubJoin) fromItem);
+ mainTables.addAll(tables);
+ } else {
+ // 澶勭悊涓� fromItem
+ processOtherFromItem(fromItem);
+ }
+ return mainTables;
+ }
+
+ /**
+ * 澶勭悊 sub join
+ *
+ * @param subJoin subJoin
+ * @return Table subJoin 涓殑涓昏〃
+ */
+ private List<Table> processSubJoin(SubJoin subJoin) {
+ List<Table> mainTables = new ArrayList<>();
+ if (subJoin.getJoinList() != null) {
+ List<Table> list = processFromItem(subJoin.getLeft());
+ mainTables.addAll(list);
+ mainTables = processJoins(mainTables, subJoin.getJoinList());
+ }
+ return mainTables;
+ }
+
+ /**
+ * 澶勭悊 joins
+ *
+ * @param mainTables 鍙互涓� null
+ * @param joins join 闆嗗悎
+ * @return List<Table> 鍙宠繛鎺ユ煡璇㈢殑 Table 鍒楄〃
+ */
+ private List<Table> processJoins(List<Table> mainTables, List<Join> joins) {
+ // join 琛ㄨ揪寮忎腑鏈�缁堢殑涓昏〃
+ Table mainTable = null;
+ // 褰撳墠 join 鐨勫乏琛�
+ Table leftTable = null;
+
+ if (mainTables == null) {
+ mainTables = new ArrayList<>();
+ } else if (mainTables.size() == 1) {
+ mainTable = mainTables.get(0);
+ leftTable = mainTable;
+ }
+
+ //瀵逛簬 on 琛ㄨ揪寮忓啓鍦ㄦ渶鍚庣殑 join锛岄渶瑕佽褰曚笅鍓嶉潰澶氫釜 on 鐨勮〃鍚�
+ Deque<List<Table>> onTableDeque = new LinkedList<>();
+ for (Join join : joins) {
+ // 澶勭悊 on 琛ㄨ揪寮�
+ FromItem joinItem = join.getRightItem();
+
+ // 鑾峰彇褰撳墠 join 鐨勮〃锛宻ubJoint 鍙互鐪嬩綔鏄竴寮犺〃
+ List<Table> joinTables = null;
+ if (joinItem instanceof Table) {
+ joinTables = new ArrayList<>();
+ joinTables.add((Table) joinItem);
+ } else if (joinItem instanceof SubJoin) {
+ joinTables = processSubJoin((SubJoin) joinItem);
+ }
+
+ if (joinTables != null) {
+
+ // 濡傛灉鏄殣寮忓唴杩炴帴
+ if (join.isSimple()) {
+ mainTables.addAll(joinTables);
+ continue;
+ }
+
+ // 褰撳墠琛ㄦ槸鍚﹀拷鐣�
+ Table joinTable = joinTables.get(0);
+
+ List<Table> onTables = null;
+ // 濡傛灉涓嶈蹇界暐锛屼笖鏄彸杩炴帴锛屽垯璁板綍涓嬪綋鍓嶈〃
+ if (join.isRight()) {
+ mainTable = joinTable;
+ if (leftTable != null) {
+ onTables = Collections.singletonList(leftTable);
+ }
+ } else if (join.isLeft()) {
+ onTables = Collections.singletonList(joinTable);
+ } else if (join.isInner()) {
+ if (mainTable == null) {
+ onTables = Collections.singletonList(joinTable);
+ } else {
+ onTables = Arrays.asList(mainTable, joinTable);
+ }
+ mainTable = null;
+ }
+
+ mainTables = new ArrayList<>();
+ if (mainTable != null) {
+ mainTables.add(mainTable);
+ }
+
+ // 鑾峰彇 join 灏剧紑鐨� on 琛ㄨ揪寮忓垪琛�
+ Collection<Expression> originOnExpressions = join.getOnExpressions();
+ // 姝e父 join on 琛ㄨ揪寮忓彧鏈変竴涓紝绔嬪埢澶勭悊
+ if (originOnExpressions.size() == 1 && onTables != null) {
+ List<Expression> onExpressions = new LinkedList<>();
+ onExpressions.add(builderExpression(originOnExpressions.iterator().next(), onTables));
+ join.setOnExpressions(onExpressions);
+ leftTable = joinTable;
+ continue;
+ }
+ // 琛ㄥ悕鍘嬫爤锛屽拷鐣ョ殑琛ㄥ帇鍏� null锛屼互渚垮悗缁笉澶勭悊
+ onTableDeque.push(onTables);
+ // 灏剧紑澶氫釜 on 琛ㄨ揪寮忕殑鏃跺�欑粺涓�澶勭悊
+ if (originOnExpressions.size() > 1) {
+ Collection<Expression> onExpressions = new LinkedList<>();
+ for (Expression originOnExpression : originOnExpressions) {
+ List<Table> currentTableList = onTableDeque.poll();
+ if (CollectionUtils.isEmpty(currentTableList)) {
+ onExpressions.add(originOnExpression);
+ } else {
+ onExpressions.add(builderExpression(originOnExpression, currentTableList));
+ }
+ }
+ join.setOnExpressions(onExpressions);
+ }
+ leftTable = joinTable;
+ } else {
+ processOtherFromItem(joinItem);
+ leftTable = null;
+ }
+ }
+
+ return mainTables;
+ }
+
+ /**
+ * 鍒ゆ柇褰撳墠鎿嶄綔鏄惁闇�瑕佽繘琛岃繃婊�
+ *
+ * @param tableName 琛ㄥ悕
+ */
+ public boolean doTenantFilter(String tableName) {
+ return AuthUtil.isAdministrator() && !adminTenantTables.contains(tableName);
+ }
+
+ /**
+ * 鍒ゆ柇褰撳墠鎿嶄綔鏄惁闇�瑕佽繘琛岃繃婊�
+ *
+ * @param tables 琛ㄥ悕
+ */
+ public boolean doTenantFilters(List<Table> tables) {
+ List<String> tableNames = tables.stream().map(Table::getName).collect(Collectors.toList());
+ return AuthUtil.isAdministrator() && !CollectionUtil.containsAny(adminTenantTables, tableNames);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantProperties.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantProperties.java
new file mode 100644
index 0000000..1df7bc6
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/BladeTenantProperties.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 澶氱鎴烽厤缃�
+ *
+ * @author Chill
+ */
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "blade.tenant")
+public class BladeTenantProperties {
+
+ /**
+ * 鏄惁澧炲己澶氱鎴�
+ */
+ private Boolean enhance = Boolean.FALSE;
+
+ /**
+ * 鏄惁寮�鍚巿鏉冪爜鏍¢獙
+ */
+ private Boolean license = Boolean.FALSE;
+
+ /**
+ * 鏄惁寮�鍚姩鎬佹暟鎹簮鍔熻兘
+ */
+ private Boolean dynamicDatasource = Boolean.FALSE;
+
+ /**
+ * 鏄惁寮�鍚姩鎬佹暟鎹簮鍏ㄥ眬鎵弿
+ */
+ private Boolean dynamicGlobal = Boolean.FALSE;
+
+ /**
+ * 澶氱鎴峰瓧娈靛悕绉�
+ */
+ private String column = "tenant_id";
+
+ /**
+ * 鏄惁寮�鍚敞瑙f帓闄�
+ */
+ private Boolean annotationExclude = Boolean.FALSE;
+
+ /**
+ * 闇�瑕佹帓闄よ繘琛岃嚜瀹氫箟鐨勫绉熸埛琛�
+ */
+ private List<String> excludeTables = new ArrayList<>();
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/TenantId.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/TenantId.java
new file mode 100644
index 0000000..3e77dae
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/TenantId.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant;
+
+/**
+ * 绉熸埛id鐢熸垚鍣�
+ *
+ * @author Chill
+ */
+public interface TenantId {
+
+ /**
+ * 鐢熸垚鑷畾涔夌鎴穒d
+ *
+ * @return tenantId
+ */
+ String generate();
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/NonDS.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/NonDS.java
new file mode 100644
index 0000000..e895603
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/NonDS.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 鎺掗櫎绉熸埛鏁版嵁婧愯嚜鍔ㄥ垏鎹�.
+ *
+ * @author Chill
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface NonDS {
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/TableExclude.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/TableExclude.java
new file mode 100644
index 0000000..10e8073
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/TableExclude.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant.annotation;
+
+import org.springframework.stereotype.Component;
+
+import java.lang.annotation.*;
+
+/**
+ * 鎸囧畾绉熸埛琛ㄦ帓闄�.
+ *
+ * @author Chill
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
+@Component
+public @interface TableExclude {
+ String value() default "";
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/TenantDS.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/TenantDS.java
new file mode 100644
index 0000000..c1dee0b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/TenantDS.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant.annotation;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import org.springblade.core.tenant.dynamic.DsTenantIdProcessor;
+
+import java.lang.annotation.*;
+
+/**
+ * 鎸囧畾绉熸埛鍔ㄦ�佹暟鎹簮鍒囨崲.
+ *
+ * @author Chill
+ */
+@DS(DsTenantIdProcessor.TENANT_ID_KEY)
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface TenantDS {
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/TenantIgnore.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/TenantIgnore.java
new file mode 100644
index 0000000..5f47f48
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/TenantIgnore.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 鎺掗櫎绉熸埛閫昏緫.
+ *
+ * @author Chill
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface TenantIgnore {
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/TenantParamDS.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/TenantParamDS.java
new file mode 100644
index 0000000..dd64d67
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/annotation/TenantParamDS.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant.annotation;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+
+import java.lang.annotation.*;
+
+/**
+ * 鎸囧畾绉熸埛ID鍔ㄦ�佹暟鎹簮鍒囨崲.
+ *
+ * @author Chill
+ */
+@DS("#tenantId")
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface TenantParamDS {
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/aspect/BladeTenantAspect.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/aspect/BladeTenantAspect.java
new file mode 100644
index 0000000..190876b
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/aspect/BladeTenantAspect.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+
+package org.springblade.core.tenant.aspect;
+
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springblade.core.tenant.BladeTenantHolder;
+import org.springblade.core.tenant.annotation.TenantIgnore;
+
+/**
+ * 鑷畾涔夌鎴峰垏闈�
+ *
+ * @author Chill
+ */
+@Slf4j
+@Aspect
+public class BladeTenantAspect {
+
+ @Around("@annotation(tenantIgnore)")
+ public Object around(ProceedingJoinPoint point, TenantIgnore tenantIgnore) throws Throwable {
+ try {
+ //寮�鍚拷鐣�
+ BladeTenantHolder.setIgnore(Boolean.TRUE);
+ //鎵ц鏂规硶
+ return point.proceed();
+ } finally {
+ //鍏抽棴蹇界暐
+ BladeTenantHolder.clear();
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/config/TenantConfiguration.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/config/TenantConfiguration.java
new file mode 100644
index 0000000..69ad895
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/config/TenantConfiguration.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant.config;
+
+import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
+import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
+import lombok.AllArgsConstructor;
+import org.springblade.core.mp.config.MybatisPlusConfiguration;
+import org.springblade.core.tenant.*;
+import org.springblade.core.tenant.aspect.BladeTenantAspect;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+
+/**
+ * 澶氱鎴烽厤缃被
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+@AutoConfiguration(before = MybatisPlusConfiguration.class)
+@EnableConfigurationProperties(BladeTenantProperties.class)
+public class TenantConfiguration {
+
+ /**
+ * 鑷畾涔夊绉熸埛澶勭悊鍣�
+ *
+ * @param tenantProperties 澶氱鎴烽厤缃被
+ * @return TenantHandler
+ */
+ @Bean
+ @Primary
+ public TenantLineHandler bladeTenantHandler(BladeTenantProperties tenantProperties) {
+ return new BladeTenantHandler(tenantProperties);
+ }
+
+ /**
+ * 鑷畾涔夌鎴锋嫤鎴櫒
+ *
+ * @param tenantHandler 澶氱鎴峰鐞嗗櫒
+ * @param tenantProperties 澶氱鎴烽厤缃被
+ * @return BladeTenantInterceptor
+ */
+ @Bean
+ @Primary
+ public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantLineHandler tenantHandler, BladeTenantProperties tenantProperties) {
+ BladeTenantInterceptor tenantInterceptor = new BladeTenantInterceptor();
+ tenantInterceptor.setTenantLineHandler(tenantHandler);
+ tenantInterceptor.setTenantProperties(tenantProperties);
+ return tenantInterceptor;
+ }
+
+ /**
+ * 鑷畾涔夌鎴穒d鐢熸垚鍣�
+ *
+ * @return TenantId
+ */
+ @Bean
+ @ConditionalOnMissingBean(TenantId.class)
+ public TenantId tenantId() {
+ return new BladeTenantId();
+ }
+
+ /**
+ * 鑷畾涔夌鎴峰垏闈�
+ */
+ @Bean
+ public BladeTenantAspect bladeTenantAspect() {
+ return new BladeTenantAspect();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/config/TenantDataSourceConfiguration.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/config/TenantDataSourceConfiguration.java
new file mode 100644
index 0000000..f3f8be8
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/config/TenantDataSourceConfiguration.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant.config;
+
+import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
+import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
+import com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationAdvisor;
+import com.baomidou.dynamic.datasource.creator.DruidDataSourceCreator;
+import com.baomidou.dynamic.datasource.processor.DsHeaderProcessor;
+import com.baomidou.dynamic.datasource.processor.DsProcessor;
+import com.baomidou.dynamic.datasource.processor.DsSessionProcessor;
+import com.baomidou.dynamic.datasource.processor.DsSpelExpressionProcessor;
+import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
+import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
+import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration;
+import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceCreatorAutoConfiguration;
+import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
+import lombok.AllArgsConstructor;
+import lombok.RequiredArgsConstructor;
+import org.springblade.core.tenant.dynamic.*;
+import org.springframework.beans.factory.SmartInitializingSingleton;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.Primary;
+import org.springframework.context.annotation.Role;
+import org.springframework.core.annotation.Order;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import javax.sql.DataSource;
+
+import static org.springblade.core.tenant.constant.TenantBaseConstant.TENANT_DYNAMIC_DATASOURCE_PROP;
+import static org.springblade.core.tenant.constant.TenantBaseConstant.TENANT_DYNAMIC_GLOBAL_PROP;
+
+/**
+ * 澶氱鎴锋暟鎹簮閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@RequiredArgsConstructor
+@EnableConfigurationProperties({DataSourceProperties.class, DynamicDataSourceProperties.class})
+@AutoConfiguration(before = {DruidDataSourceAutoConfigure.class, DynamicDataSourceAutoConfiguration.class})
+@Import(value = {DynamicDataSourceCreatorAutoConfiguration.class})
+@ConditionalOnProperty(value = TENANT_DYNAMIC_DATASOURCE_PROP, havingValue = "true")
+public class TenantDataSourceConfiguration {
+
+ @Bean
+ @Primary
+ public DynamicDataSourceProvider dynamicDataSourceProvider(DataSourceProperties dataSourceProperties, DynamicDataSourceProperties dynamicDataSourceProperties) {
+ String driverClassName = dataSourceProperties.getDriverClassName();
+ String url = dataSourceProperties.getUrl();
+ String username = dataSourceProperties.getUsername();
+ String password = dataSourceProperties.getPassword();
+ DataSourceProperty master = dynamicDataSourceProperties.getDatasource().get(dynamicDataSourceProperties.getPrimary());
+ if (master != null) {
+ driverClassName = master.getDriverClassName();
+ url = master.getUrl();
+ username = master.getUsername();
+ password = master.getPassword();
+ }
+ return new TenantDataSourceJdbcProvider(dynamicDataSourceProperties, driverClassName, url, username, password);
+ }
+
+ @Bean
+ @Primary
+ public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider, DynamicDataSourceProperties dynamicDataSourceProperties) {
+ DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
+ dataSource.setPrimary(dynamicDataSourceProperties.getPrimary());
+ dataSource.setStrict(dynamicDataSourceProperties.getStrict());
+ dataSource.setStrategy(dynamicDataSourceProperties.getStrategy());
+ dataSource.setProvider(dynamicDataSourceProvider);
+ dataSource.setP6spy(dynamicDataSourceProperties.getP6spy());
+ dataSource.setSeata(dynamicDataSourceProperties.getSeata());
+ return dataSource;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public TenantDataSourceAnnotationInterceptor tenantDataSourceAnnotationInterceptor(DsProcessor dsProcessor, DynamicDataSourceProperties dynamicDataSourceProperties) {
+ return new TenantDataSourceAnnotationInterceptor(dynamicDataSourceProperties.isAllowedPublicOnly(), dsProcessor);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ @Role(value = BeanDefinition.ROLE_INFRASTRUCTURE)
+ public DynamicDataSourceAnnotationAdvisor dynamicDatasourceAnnotationAdvisor(TenantDataSourceAnnotationInterceptor tenantDataSourceAnnotationInterceptor, DynamicDataSourceProperties dynamicDataSourceProperties) {
+ DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(tenantDataSourceAnnotationInterceptor);
+ advisor.setOrder(dynamicDataSourceProperties.getOrder());
+ return advisor;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ @ConditionalOnProperty(value = TENANT_DYNAMIC_GLOBAL_PROP, havingValue = "true")
+ public TenantDataSourceGlobalInterceptor tenantDataSourceGlobalInterceptor() {
+ return new TenantDataSourceGlobalInterceptor();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ @Role(value = BeanDefinition.ROLE_INFRASTRUCTURE)
+ @ConditionalOnProperty(value = TENANT_DYNAMIC_GLOBAL_PROP, havingValue = "true")
+ public TenantDataSourceGlobalAdvisor tenantDataSourceGlobalAdvisor(TenantDataSourceGlobalInterceptor tenantDataSourceGlobalInterceptor, DynamicDataSourceProperties dynamicDataSourceProperties) {
+ TenantDataSourceGlobalAdvisor advisor = new TenantDataSourceGlobalAdvisor(tenantDataSourceGlobalInterceptor);
+ advisor.setOrder(dynamicDataSourceProperties.getOrder() + 1);
+ return advisor;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public DsProcessor dsProcessor() {
+ DsHeaderProcessor headerProcessor = new DsHeaderProcessor();
+ DsSessionProcessor sessionProcessor = new DsSessionProcessor();
+ DsTenantIdProcessor tenantIdProcessor = new DsTenantIdProcessor();
+ DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
+ headerProcessor.setNextProcessor(sessionProcessor);
+ sessionProcessor.setNextProcessor(tenantIdProcessor);
+ tenantIdProcessor.setNextProcessor(spelExpressionProcessor);
+ return headerProcessor;
+ }
+
+ @Order
+ @AutoConfiguration
+ @AllArgsConstructor
+ @ConditionalOnProperty(value = TENANT_DYNAMIC_DATASOURCE_PROP, havingValue = "true")
+ public static class TenantDataSourceAnnotationConfiguration implements SmartInitializingSingleton {
+
+ private final TenantDataSourceAnnotationInterceptor tenantDataSourceAnnotationInterceptor;
+
+ private final DataSource dataSource;
+ private final DruidDataSourceCreator dataSourceCreator;
+ private final JdbcTemplate jdbcTemplate;
+
+ @Override
+ public void afterSingletonsInstantiated() {
+ TenantDataSourceHolder tenantDataSourceHolder = new TenantDataSourceHolder(dataSource, dataSourceCreator, jdbcTemplate);
+ tenantDataSourceAnnotationInterceptor.setHolder(tenantDataSourceHolder);
+ }
+ }
+
+ @Order
+ @AutoConfiguration
+ @AllArgsConstructor
+ @ConditionalOnProperty(value = TENANT_DYNAMIC_GLOBAL_PROP, havingValue = "true")
+ public static class TenantDataSourceGlobalConfiguration implements SmartInitializingSingleton {
+
+ private final TenantDataSourceGlobalInterceptor tenantDataSourceGlobalInterceptor;
+
+ private final DataSource dataSource;
+ private final DruidDataSourceCreator dataSourceCreator;
+ private final JdbcTemplate jdbcTemplate;
+
+ @Override
+ public void afterSingletonsInstantiated() {
+ TenantDataSourceHolder tenantDataSourceHolder = new TenantDataSourceHolder(dataSource, dataSourceCreator, jdbcTemplate);
+ tenantDataSourceGlobalInterceptor.setHolder(tenantDataSourceHolder);
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/constant/TenantBaseConstant.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/constant/TenantBaseConstant.java
new file mode 100644
index 0000000..a1a40da
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/constant/TenantBaseConstant.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant.constant;
+
+/**
+ * 绉熸埛甯搁噺.
+ *
+ * @author Chill
+ */
+public interface TenantBaseConstant {
+
+ /**
+ * 绉熸埛鏁版嵁婧愮紦瀛樺悕
+ */
+ String TENANT_DATASOURCE_CACHE = "blade:datasource";
+
+ /**
+ * 绉熸埛鏁版嵁婧愮紦瀛橀敭
+ */
+ String TENANT_DATASOURCE_KEY = "tenant:id:";
+
+ /**
+ * 绉熸埛鏁版嵁婧愮紦瀛橀敭
+ */
+ String TENANT_DATASOURCE_EXIST_KEY = "tenant:exist:";
+
+ /**
+ * 绉熸埛鍔ㄦ�佹暟鎹簮閿�
+ */
+ String TENANT_DYNAMIC_DATASOURCE_PROP = "blade.tenant.dynamic-datasource";
+
+ /**
+ * 绉熸埛鍏ㄥ眬鍔ㄦ�佹暟鎹簮鍒囬潰閿�
+ */
+ String TENANT_DYNAMIC_GLOBAL_PROP = "blade.tenant.dynamic-global";
+
+ /**
+ * 绉熸埛鏄惁瀛樺湪鏁版嵁婧�
+ */
+ String TENANT_DATASOURCE_EXIST_STATEMENT = "select datasource_id from blade_tenant WHERE is_deleted = 0 AND tenant_id = ?";
+
+ /**
+ * 绉熸埛鏁版嵁婧愬熀纭�SQL
+ */
+ String TENANT_DATASOURCE_BASE_STATEMENT = "SELECT tenant_id as tenantId, driver_class as driverClass, url, username, password from blade_tenant tenant LEFT JOIN blade_datasource datasource ON tenant.datasource_id = datasource.id ";
+
+ /**
+ * 绉熸埛鍗曟暟鎹簮SQL
+ */
+ String TENANT_DATASOURCE_SINGLE_STATEMENT = TENANT_DATASOURCE_BASE_STATEMENT + "WHERE tenant.is_deleted = 0 AND tenant.tenant_id = ?";
+
+ /**
+ * 绉熸埛闆嗗姩鎬佹暟鎹簮SQL
+ */
+ String TENANT_DATASOURCE_GROUP_STATEMENT = TENANT_DATASOURCE_BASE_STATEMENT + "WHERE tenant.is_deleted = 0";
+
+ /**
+ * 绉熸埛鏈壘鍒拌繑鍥炰俊鎭�
+ */
+ String TENANT_DATASOURCE_NOT_FOUND = "鏈壘鍒扮鎴蜂俊鎭�,鏁版嵁婧愬姞杞藉け璐�!";
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/DsTenantIdProcessor.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/DsTenantIdProcessor.java
new file mode 100644
index 0000000..08b6990
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/DsTenantIdProcessor.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant.dynamic;
+
+import com.baomidou.dynamic.datasource.processor.DsProcessor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springblade.core.secure.utils.AuthUtil;
+
+/**
+ * 绉熸埛鍔ㄦ�佹暟鎹簮瑙f瀽鍣�
+ *
+ * @author Chill
+ */
+public class DsTenantIdProcessor extends DsProcessor {
+
+ public static final String TENANT_ID_KEY = "#token.tenantId";
+
+ @Override
+ public boolean matches(String key) {
+ return key.equals(TENANT_ID_KEY);
+ }
+
+ @Override
+ public String doDetermineDatasource(MethodInvocation invocation, String key) {
+ return AuthUtil.getTenantId();
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSource.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSource.java
new file mode 100644
index 0000000..77b3cd6
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSource.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant.dynamic;
+
+import lombok.Data;
+
+/**
+ * 绉熸埛鏁版嵁婧�
+ *
+ * @author Chill
+ */
+@Data
+public class TenantDataSource {
+
+ /**
+ * 绉熸埛ID
+ */
+ private String tenantId;
+ /**
+ * 鏁版嵁婧怚D
+ */
+ private String datasourceId;
+ /**
+ * 椹卞姩绫�
+ */
+ private String driverClass;
+ /**
+ * 鏁版嵁搴撻摼鎺�
+ */
+ private String url;
+ /**
+ * 鏁版嵁搴撹处鍙峰悕
+ */
+ private String username;
+ /**
+ * 鏁版嵁搴撳瘑鐮�
+ */
+ private String password;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceAnnotationInterceptor.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceAnnotationInterceptor.java
new file mode 100644
index 0000000..54ee169
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceAnnotationInterceptor.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant.dynamic;
+
+import com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationInterceptor;
+import com.baomidou.dynamic.datasource.processor.DsProcessor;
+import lombok.Setter;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tenant.exception.TenantDataSourceException;
+
+/**
+ * 绉熸埛鏁版嵁婧愬垏鎹㈡嫤鎴櫒
+ *
+ * @author Chill
+ */
+public class TenantDataSourceAnnotationInterceptor extends DynamicDataSourceAnnotationInterceptor {
+
+ @Setter
+ private TenantDataSourceHolder holder;
+
+ public TenantDataSourceAnnotationInterceptor(Boolean allowedPublicOnly, DsProcessor dsProcessor) {
+ super(allowedPublicOnly, dsProcessor);
+ }
+
+ @Override
+ public Object invoke(MethodInvocation invocation) throws Throwable {
+ try {
+ holder.handleDataSource(AuthUtil.getTenantId());
+ return super.invoke(invocation);
+ } catch (Exception exception) {
+ throw new TenantDataSourceException(exception.getMessage());
+ }
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceGlobalAdvisor.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceGlobalAdvisor.java
new file mode 100644
index 0000000..2010baa
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceGlobalAdvisor.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant.dynamic;
+
+import org.aopalliance.aop.Advice;
+import org.springframework.aop.Pointcut;
+import org.springframework.aop.aspectj.AspectJExpressionPointcut;
+import org.springframework.aop.support.AbstractPointcutAdvisor;
+import org.springframework.aop.support.ComposablePointcut;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.lang.NonNull;
+
+import static org.springblade.core.launch.constant.AppConstant.BASE_PACKAGES;
+
+/**
+ * 绉熸埛鏁版嵁婧愬叏灞�澶勭悊鍣�
+ *
+ * @author Chill
+ */
+public class TenantDataSourceGlobalAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
+
+ private final Advice advice;
+
+ private final Pointcut pointcut;
+
+ public TenantDataSourceGlobalAdvisor(@NonNull TenantDataSourceGlobalInterceptor tenantDataSourceGlobalInterceptor) {
+ this.advice = tenantDataSourceGlobalInterceptor;
+ this.pointcut = buildPointcut();
+ }
+
+ @NonNull
+ @Override
+ public Pointcut getPointcut() {
+ return this.pointcut;
+ }
+
+ @NonNull
+ @Override
+ public Advice getAdvice() {
+ return this.advice;
+ }
+
+ @Override
+ public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {
+ if (this.advice instanceof BeanFactoryAware) {
+ ((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);
+ }
+ }
+
+ private Pointcut buildPointcut() {
+ AspectJExpressionPointcut cut = new AspectJExpressionPointcut();
+ cut.setExpression(
+ "(@within(org.springframework.stereotype.Controller) || @within(org.springframework.web.bind.annotation.RestController)) && " +
+ "(!@annotation(" + BASE_PACKAGES + ".core.tenant.annotation.NonDS) && !@within(" + BASE_PACKAGES + ".core.tenant.annotation.NonDS))"
+ );
+ return new ComposablePointcut((Pointcut) cut);
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceGlobalInterceptor.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceGlobalInterceptor.java
new file mode 100644
index 0000000..7b7bc05
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceGlobalInterceptor.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant.dynamic;
+
+import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
+import lombok.Setter;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tenant.exception.TenantDataSourceException;
+import org.springblade.core.tool.utils.StringUtil;
+
+/**
+ * 绉熸埛鏁版嵁婧愬叏灞�鎷︽埅鍣�
+ *
+ * @author Chill
+ */
+public class TenantDataSourceGlobalInterceptor implements MethodInterceptor {
+
+ @Setter
+ private TenantDataSourceHolder holder;
+
+ @Override
+ public Object invoke(MethodInvocation invocation) throws Throwable {
+ String tenantId = AuthUtil.getTenantId();
+ try {
+ if (StringUtil.isNotBlank(tenantId)) {
+ holder.handleDataSource(tenantId);
+ DynamicDataSourceContextHolder.push(tenantId);
+ }
+ return invocation.proceed();
+ } catch (Exception exception) {
+ throw new TenantDataSourceException(exception.getMessage());
+ } finally {
+ if (StringUtil.isNotBlank(tenantId)) {
+ DynamicDataSourceContextHolder.poll();
+ }
+ }
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceHolder.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceHolder.java
new file mode 100644
index 0000000..74c11bd
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceHolder.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant.dynamic;
+
+import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
+import com.baomidou.dynamic.datasource.creator.DataSourceCreator;
+import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
+import lombok.AllArgsConstructor;
+import org.springblade.core.cache.utils.CacheUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.beans.BeanUtils;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import javax.sql.DataSource;
+import java.util.Set;
+
+import static org.springblade.core.tenant.constant.TenantBaseConstant.*;
+
+/**
+ * 绉熸埛鏁版嵁婧愭牳蹇冨鐞嗙被
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+public class TenantDataSourceHolder {
+
+ private final DataSource dataSource;
+ private final DataSourceCreator dataSourceCreator;
+ private final JdbcTemplate jdbcTemplate;
+
+ /**
+ * 鏁版嵁婧愮紦瀛樺鐞�
+ *
+ * @param tenantId 绉熸埛ID
+ */
+ public void handleDataSource(String tenantId) {
+ // 鑾峰彇鍌ㄥ瓨鐨勬暟鎹簮闆嗗悎
+ DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
+ Set<String> keys = ds.getCurrentDataSources().keySet();
+ // 閰嶇疆涓嶅瓨鍦ㄥ垯鍔ㄦ�佹坊鍔犳暟鎹簮锛屼互鎳掑姞杞界殑妯″紡瑙e喅鍒嗗竷寮忓満鏅殑閰嶇疆鍚屾
+ // 涓轰簡淇濊瘉鏁版嵁瀹屾暣鎬э紝閰嶇疆鍚庣敓鎴愭暟鎹簮缂撳瓨锛屽悗鍙颁究鏃犳硶淇敼鏇存崲鏁版嵁婧愶紝鑻ヤ竴瀹氳淇敼璇疯縼绉绘暟鎹悗閲嶅惎鏈嶅姟鎴栬嚜琛屼慨鏀瑰簳灞傞�昏緫
+ if (!keys.contains(tenantId)) {
+ TenantDataSource tenantDataSource = getDataSource(tenantId);
+ if (tenantDataSource != null) {
+ // 鍒涘缓鏁版嵁婧愰厤缃�
+ DataSourceProperty dataSourceProperty = new DataSourceProperty();
+ // 鎷疯礉鏁版嵁婧愰厤缃�
+ BeanUtils.copyProperties(tenantDataSource, dataSourceProperty);
+ // 鍏抽棴鎳掑姞杞�
+ dataSourceProperty.setLazy(Boolean.FALSE);
+ // 鍒涘缓鍔ㄦ�佹暟鎹簮
+ DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty);
+ // 娣诲姞鏈�鏂版暟鎹簮
+ ds.addDataSource(tenantId, dataSource);
+ }
+ }
+ }
+
+ /**
+ * 鍒ゆ柇绉熸埛鏄惁鏈夋暟鎹簮閰嶇疆
+ *
+ * @param tenantId 绉熸埛ID
+ */
+ private Boolean existDataSource(String tenantId) {
+ // 灏嗙鎴锋槸鍚﹂厤缃暟鎹簮杩涜缂撳瓨锛岃嫢閲嶆柊閰嶇疆浼氬皢姝ょ紦瀛樻竻绌哄苟鍦ㄤ笅娆¤姹傜殑鏃跺�欐噿鍔犺浇
+ // 鑻ョ鎴锋病鏈夐厤缃暟鎹簮鍒欎細鑷姩浣跨敤master鏁版嵁婧愶紝姝や妇鏄负浜嗛伩鍏嶅湪娌℃湁鏁版嵁搴撶殑鏃跺�欓绻佹煡璇㈠鑷寸紦瀛樺嚮绌�
+ Boolean exist = CacheUtil.get(TENANT_DATASOURCE_CACHE, TENANT_DATASOURCE_EXIST_KEY, tenantId, Boolean.class, Boolean.FALSE);
+ if (exist == null) {
+ TenantDataSource tenantDataSource = jdbcTemplate.queryForObject(TENANT_DATASOURCE_EXIST_STATEMENT, new String[]{tenantId}, new BeanPropertyRowMapper<>(TenantDataSource.class));
+ if (tenantDataSource != null && StringUtil.isNotBlank(tenantDataSource.getDatasourceId())) {
+ exist = Boolean.TRUE;
+ } else {
+ exist = Boolean.FALSE;
+ }
+ CacheUtil.put(TENANT_DATASOURCE_CACHE, TENANT_DATASOURCE_EXIST_KEY, tenantId, exist, Boolean.FALSE);
+ }
+ return exist;
+ }
+
+ /**
+ * 鑾峰彇瀵瑰簲鐨勬暟鎹簮閰嶇疆
+ *
+ * @param tenantId 绉熸埛ID
+ */
+ private TenantDataSource getDataSource(String tenantId) {
+ // 涓嶅瓨鍦ㄧ鎴锋暟鎹簮鍒欒繑鍥炵┖锛岄槻姝㈢紦瀛樺嚮绌�
+ if (!existDataSource(tenantId)) {
+ return null;
+ }
+ // 鑾峰彇绉熸埛鏁版嵁婧愪俊鎭�
+ TenantDataSource tenantDataSource = CacheUtil.get(TENANT_DATASOURCE_CACHE, TENANT_DATASOURCE_KEY, tenantId, TenantDataSource.class, Boolean.FALSE);
+ if (tenantDataSource == null) {
+ tenantDataSource = jdbcTemplate.queryForObject(TENANT_DATASOURCE_SINGLE_STATEMENT, new String[]{tenantId}, new BeanPropertyRowMapper<>(TenantDataSource.class));
+ if (tenantDataSource != null && StringUtil.isNoneBlank(tenantDataSource.getTenantId(), tenantDataSource.getDriverClass(), tenantDataSource.getUrl(), tenantDataSource.getUsername(), tenantDataSource.getPassword())) {
+ CacheUtil.put(TENANT_DATASOURCE_CACHE, TENANT_DATASOURCE_KEY, tenantId, tenantDataSource, Boolean.FALSE);
+ } else {
+ tenantDataSource = null;
+ }
+ }
+ return tenantDataSource;
+ }
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceJdbcProvider.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceJdbcProvider.java
new file mode 100644
index 0000000..af165ee
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/dynamic/TenantDataSourceJdbcProvider.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant.dynamic;
+
+import com.baomidou.dynamic.datasource.provider.AbstractJdbcDataSourceProvider;
+import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
+import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
+import org.springblade.core.tool.utils.StringUtil;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.springblade.core.tenant.constant.TenantBaseConstant.TENANT_DATASOURCE_GROUP_STATEMENT;
+
+/**
+ * 绉熸埛鏁版嵁婧愬垵濮嬪姞杞�
+ *
+ * @author Chill
+ */
+public class TenantDataSourceJdbcProvider extends AbstractJdbcDataSourceProvider {
+
+ private final String driverClassName;
+ private final String url;
+ private final String username;
+ private final String password;
+ private final DynamicDataSourceProperties dynamicDataSourceProperties;
+
+ public TenantDataSourceJdbcProvider(DynamicDataSourceProperties dynamicDataSourceProperties, String driverClassName, String url, String username, String password) {
+ super(driverClassName, url, username, password);
+ this.dynamicDataSourceProperties = dynamicDataSourceProperties;
+ this.driverClassName = driverClassName;
+ this.url = url;
+ this.username = username;
+ this.password = password;
+ }
+
+ @Override
+ protected Map<String, DataSourceProperty> executeStmt(Statement statement) throws SQLException {
+ // 鏋勫缓鏁版嵁婧愰泦鍚�
+ Map<String, DataSourceProperty> map = new HashMap<>(16);
+ // 鏋勫缓涓绘暟鎹簮
+ DataSourceProperty masterProperty = new DataSourceProperty();
+ masterProperty.setDriverClassName(driverClassName);
+ masterProperty.setUrl(url);
+ masterProperty.setUsername(username);
+ masterProperty.setPassword(password);
+ masterProperty.setDruid(dynamicDataSourceProperties.getDruid());
+ map.put(dynamicDataSourceProperties.getPrimary(), masterProperty);
+ // 鏋勫缓yml鏁版嵁婧�
+ Map<String, DataSourceProperty> datasource = dynamicDataSourceProperties.getDatasource();
+ if (datasource.size() > 0) {
+ map.putAll(datasource);
+ }
+ // 鏋勫缓鍔ㄦ�佹暟鎹簮
+ ResultSet rs = statement.executeQuery(TENANT_DATASOURCE_GROUP_STATEMENT);
+ while (rs.next()) {
+ String tenantId = rs.getString("tenantId");
+ String driver = rs.getString("driverClass");
+ String url = rs.getString("url");
+ String username = rs.getString("username");
+ String password = rs.getString("password");
+ if (StringUtil.isNoneBlank(tenantId, driver, url, username, password)) {
+ DataSourceProperty jdbcProperty = new DataSourceProperty();
+ jdbcProperty.setDriverClassName(driver);
+ jdbcProperty.setUrl(url);
+ jdbcProperty.setUsername(username);
+ jdbcProperty.setPassword(password);
+ jdbcProperty.setDruid(dynamicDataSourceProperties.getDruid());
+ map.put(tenantId, jdbcProperty);
+ }
+ }
+ return map;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/exception/TenantDataSourceException.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/exception/TenantDataSourceException.java
new file mode 100644
index 0000000..780243e
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/exception/TenantDataSourceException.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant.exception;
+
+/**
+ * 绉熸埛鏁版嵁婧愬紓甯�
+ *
+ * @author Chill
+ */
+public class TenantDataSourceException extends RuntimeException {
+
+ public TenantDataSourceException(String message) {
+ super(message);
+ }
+
+ /**
+ * 鎻愰珮鎬ц兘
+ *
+ * @return Throwable
+ */
+ @Override
+ public Throwable fillInStackTrace() {
+ return this;
+ }
+
+ public Throwable doFillInStackTrace() {
+ return super.fillInStackTrace();
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/launcher/TenantLauncherServiceImpl.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/launcher/TenantLauncherServiceImpl.java
new file mode 100644
index 0000000..155dafd
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/launcher/TenantLauncherServiceImpl.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant.launcher;
+
+import org.springblade.core.auto.service.AutoService;
+import org.springblade.core.launch.service.LauncherService;
+import org.springblade.core.launch.utils.PropsUtil;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.core.Ordered;
+
+import java.util.Properties;
+
+/**
+ * 鍒濆鍖栫鎴烽厤缃�
+ *
+ * @author Chill
+ */
+@AutoService(LauncherService.class)
+public class TenantLauncherServiceImpl implements LauncherService {
+ @Override
+ public void launcher(SpringApplicationBuilder builder, String appName, String profile, boolean isLocalDev) {
+ Properties props = System.getProperties();
+ //榛樿鍏抽棴mybatis-plus澶氭暟鎹簮鑷姩瑁呴厤
+ PropsUtil.setProperty(props, "spring.datasource.dynamic.enabled", "false");
+ }
+
+ @Override
+ public int getOrder() {
+ return Ordered.LOWEST_PRECEDENCE;
+ }
+}
diff --git a/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/mp/TenantEntity.java b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/mp/TenantEntity.java
new file mode 100644
index 0000000..c660978
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-tenant/src/main/java/org/springblade/core/tenant/mp/TenantEntity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.tenant.mp;
+
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.core.mp.base.BaseEntity;
+
+/**
+ * 绉熸埛鍩虹瀹炰綋绫�
+ *
+ * @author Chill
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class TenantEntity extends BaseEntity {
+
+ /**
+ * 绉熸埛ID
+ */
+ @ApiModelProperty(value = "绉熸埛ID")
+ private String tenantId;
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-trace/pom.xml b/Source/BladeX-Tool/blade-starter-trace/pom.xml
new file mode 100644
index 0000000..f7efd4f
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-trace/pom.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-trace</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!--Blade-->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-tool</artifactId>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-trace/src/main/java/org/springblade/core/trace/TraceAutoConfiguration.java b/Source/BladeX-Tool/blade-starter-trace/src/main/java/org/springblade/core/trace/TraceAutoConfiguration.java
new file mode 100644
index 0000000..da60342
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-trace/src/main/java/org/springblade/core/trace/TraceAutoConfiguration.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.trace;
+
+import org.springblade.core.launch.props.BladePropertySource;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+
+/**
+ * TraceAutoConfiguration
+ *
+ * @author Chill
+ */
+@AutoConfiguration
+@BladePropertySource(value = "classpath:/blade-trace.yml")
+public class TraceAutoConfiguration {
+}
diff --git a/Source/BladeX-Tool/blade-starter-trace/src/main/resources/blade-trace.yml b/Source/BladeX-Tool/blade-starter-trace/src/main/resources/blade-trace.yml
new file mode 100644
index 0000000..18350a8
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-trace/src/main/resources/blade-trace.yml
@@ -0,0 +1,5 @@
+#sleuth閰嶇疆
+spring:
+ sleuth:
+ sampler:
+ percentage: 1.0
diff --git a/Source/BladeX-Tool/blade-starter-transaction/pom.xml b/Source/BladeX-Tool/blade-starter-transaction/pom.xml
new file mode 100644
index 0000000..dcd1314
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-transaction/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>BladeX-Tool</artifactId>
+ <groupId>org.springblade</groupId>
+ <version>3.0.1.RELEASE</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>blade-starter-transaction</artifactId>
+ <name>${project.artifactId}</name>
+ <version>${project.parent.version}</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!-- Cloud-->
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-commons</artifactId>
+ </dependency>
+ <!-- Mybatis-->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-starter-mybatis</artifactId>
+ </dependency>
+ <!-- Seata-->
+ <dependency>
+ <groupId>com.alibaba.cloud</groupId>
+ <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.seata</groupId>
+ <artifactId>seata-spring-boot-starter</artifactId>
+ </dependency>
+ <!-- Auto -->
+ <dependency>
+ <groupId>org.springblade</groupId>
+ <artifactId>blade-core-auto</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+
+</project>
diff --git a/Source/BladeX-Tool/blade-starter-transaction/src/main/java/org/springblade/core/transaction/annotation/SeataCloudApplication.java b/Source/BladeX-Tool/blade-starter-transaction/src/main/java/org/springblade/core/transaction/annotation/SeataCloudApplication.java
new file mode 100644
index 0000000..47c40f3
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-transaction/src/main/java/org/springblade/core/transaction/annotation/SeataCloudApplication.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.transaction.annotation;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+import java.lang.annotation.*;
+
+/**
+ * Seata鍚姩娉ㄨВ閰嶇疆
+ *
+ * @author Chill
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@EnableDiscoveryClient
+@SpringBootApplication(exclude = {
+ DataSourceAutoConfiguration.class
+})
+public @interface SeataCloudApplication {
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-transaction/src/main/java/org/springblade/core/transaction/config/TransactionConfiguration.java b/Source/BladeX-Tool/blade-starter-transaction/src/main/java/org/springblade/core/transaction/config/TransactionConfiguration.java
new file mode 100644
index 0000000..5cb7385
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-transaction/src/main/java/org/springblade/core/transaction/config/TransactionConfiguration.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the dreamlu.net developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: Chill 搴勯獮 (smallchill@163.com)
+ */
+package org.springblade.core.transaction.config;
+
+import org.springblade.core.launch.props.BladePropertySource;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+
+/**
+ * Seata閰嶇疆绫�
+ *
+ * @author Chill
+ */
+@AutoConfiguration
+@Order(Ordered.HIGHEST_PRECEDENCE)
+@BladePropertySource(value = "classpath:/blade-transaction.yml")
+public class TransactionConfiguration {
+
+}
diff --git a/Source/BladeX-Tool/blade-starter-transaction/src/main/resources/blade-transaction.yml b/Source/BladeX-Tool/blade-starter-transaction/src/main/resources/blade-transaction.yml
new file mode 100644
index 0000000..94068b4
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-transaction/src/main/resources/blade-transaction.yml
@@ -0,0 +1,16 @@
+# seata閰嶇疆
+seata:
+ tx-service-group: ${blade.name}-group
+ registry:
+ type: file
+ config:
+ type: file
+ service:
+ grouplist:
+ default: 127.0.0.1:8091
+ vgroup-mapping:
+ blade-tx-group: default
+ disable-global-transaction: false
+ client:
+ rm:
+ report-success-enable: false
diff --git a/Source/BladeX-Tool/blade-starter-transaction/src/main/resources/file.conf b/Source/BladeX-Tool/blade-starter-transaction/src/main/resources/file.conf
new file mode 100644
index 0000000..2e9d0b7
--- /dev/null
+++ b/Source/BladeX-Tool/blade-starter-transaction/src/main/resources/file.conf
@@ -0,0 +1,3 @@
+service {
+ disableGlobalTransaction = false
+}
diff --git "a/Source/BladeX-Tool/doc/mvn/mvn\345\221\275\344\273\244.md" "b/Source/BladeX-Tool/doc/mvn/mvn\345\221\275\344\273\244.md"
new file mode 100644
index 0000000..804e751
--- /dev/null
+++ "b/Source/BladeX-Tool/doc/mvn/mvn\345\221\275\344\273\244.md"
@@ -0,0 +1 @@
+mvn install:install-file -Dfile=blade-core-1.0.jar -DgroupId=org.springblade -DartifactId=blade-core -Dversion=1.0 -Dpackaging=jar
\ No newline at end of file
diff --git a/Source/BladeX-Tool/pom.xml b/Source/BladeX-Tool/pom.xml
new file mode 100644
index 0000000..939febe
--- /dev/null
+++ b/Source/BladeX-Tool/pom.xml
@@ -0,0 +1,216 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.springblade</groupId>
+ <artifactId>BladeX-Tool</artifactId>
+ <version>3.0.1.RELEASE</version>
+ <packaging>pom</packaging>
+
+ <properties>
+ <java.version>1.8</java.version>
+ <maven.plugin.version>3.8.1</maven.plugin.version>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+
+ <spring.boot.version>2.7.1</spring.boot.version>
+ <spring.cloud.version>2021.0.3</spring.cloud.version>
+ <spring.platform.version>Cairo-SR8</spring.platform.version>
+ </properties>
+
+ <modules>
+ <module>blade-bom</module>
+ <module>blade-core-auto</module>
+ <module>blade-core-boot</module>
+ <module>blade-core-cloud</module>
+ <module>blade-core-context</module>
+ <module>blade-core-db</module>
+ <module>blade-core-launch</module>
+ <module>blade-core-log4j2</module>
+ <module>blade-core-secure</module>
+ <module>blade-core-test</module>
+ <module>blade-core-tool</module>
+ <module>blade-starter-actuate</module>
+ <module>blade-starter-api-crypto</module>
+ <module>blade-starter-auth</module>
+ <module>blade-starter-cache</module>
+ <module>blade-starter-datascope</module>
+ <module>blade-starter-develop</module>
+ <module>blade-starter-ehcache</module>
+ <module>blade-starter-excel</module>
+ <module>blade-starter-flowable</module>
+ <module>blade-starter-http</module>
+ <module>blade-starter-jwt</module>
+ <module>blade-starter-loadbalancer</module>
+ <module>blade-starter-log</module>
+ <module>blade-starter-metrics</module>
+ <module>blade-starter-mongo</module>
+ <module>blade-starter-mybatis</module>
+ <module>blade-starter-oss</module>
+ <module>blade-starter-prometheus</module>
+ <module>blade-starter-redis</module>
+ <module>blade-starter-report</module>
+ <module>blade-starter-sms</module>
+ <module>blade-starter-social</module>
+ <module>blade-starter-swagger</module>
+ <module>blade-starter-tenant</module>
+ <module>blade-starter-trace</module>
+ <module>blade-starter-transaction</module>
+ </modules>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.springblade.platform</groupId>
+ <artifactId>blade-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-dependencies</artifactId>
+ <version>${spring.boot.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-dependencies</artifactId>
+ <version>${spring.cloud.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.spring.platform</groupId>
+ <artifactId>platform-bom</artifactId>
+ <version>${spring.platform.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-aop</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-configuration-processor</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.retry</groupId>
+ <artifactId>spring-retry</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <finalName>${project.name}</finalName>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ </resource>
+ <resource>
+ <directory>src/main/java</directory>
+ <includes>
+ <include>**/*.xml</include>
+ </includes>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>${maven.plugin.version}</version>
+ <configuration>
+ <source>${java.version}</source>
+ <target>${java.version}</target>
+ <encoding>UTF-8</encoding>
+ <compilerArgs>
+ <arg>-parameters</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+ <!-- 鎵搄ar鍖� -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>3.1.0</version>
+ </plugin>
+ </plugins>
+ </build>
+
+ <repositories>
+ <repository>
+ <id>aliyun-repos</id>
+ <url>https://maven.aliyun.com/repository/public/</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ <repository>
+ <id>blade-release</id>
+ <name>Release Repository</name>
+ <url>http://nexus.javablade.com/repository/maven-releases/</url>
+ </repository>
+ </repositories>
+ <pluginRepositories>
+ <pluginRepository>
+ <id>aliyun-plugin</id>
+ <url>https://maven.aliyun.com/repository/public/</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </pluginRepository>
+ </pluginRepositories>
+
+ <distributionManagement>
+ <repository>
+ <id>blade-release</id>
+ <name>Release Repository</name>
+ <url>http://nexus.javablade.com/repository/maven-releases/</url>
+ </repository>
+ </distributionManagement>
+
+ <profiles>
+ <profile>
+ <id>develop</id>
+ <build>
+ <plugins>
+ <!-- 鎵搒ource鍖� -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>3.0.1</version>
+ <configuration>
+ <attach>true</attach>
+ </configuration>
+ <executions>
+ <execution>
+ <phase>compile</phase>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/Source/UBCS/ubcs-service-api/ubcs-code-api/src/main/java/com/vci/ubcs/code/vo/webserviceModel/mdm/MDMData.java b/Source/UBCS/ubcs-service-api/ubcs-code-api/src/main/java/com/vci/ubcs/code/vo/webserviceModel/mdm/MDMData.java
deleted file mode 100644
index 9689c2c..0000000
--- a/Source/UBCS/ubcs-service-api/ubcs-code-api/src/main/java/com/vci/ubcs/code/vo/webserviceModel/mdm/MDMData.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.vci.ubcs.code.vo.webserviceModel.mdm;
-
-import lombok.Data;
-import org.omg.CORBA.Object;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class MDMData implements Serializable {
- private List<Map<String,String>> map= new ArrayList<>();
-
- public List<Map<String,String>> getMap() {
- return map;
- }
-
- public void setMap(List<Map<String,String>> map) {
- this.map = map;
- }
-}
diff --git a/Source/UBCS/ubcs-service-api/ubcs-code-api/src/main/java/com/vci/ubcs/code/vo/webserviceModel/mdm/MDMParamData.java b/Source/UBCS/ubcs-service-api/ubcs-code-api/src/main/java/com/vci/ubcs/code/vo/webserviceModel/mdm/MDMParamData.java
deleted file mode 100644
index fdbafeb..0000000
--- a/Source/UBCS/ubcs-service-api/ubcs-code-api/src/main/java/com/vci/ubcs/code/vo/webserviceModel/mdm/MDMParamData.java
+++ /dev/null
@@ -1,113 +0,0 @@
-package com.vci.ubcs.code.vo.webserviceModel.mdm;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-public class MDMParamData implements Serializable {
- /**
- * 鏁版嵁瀹℃壒鐘舵��0锛氬鎵规甯革紝1锛氬鎵规嫆缁�
- */
- private int code;
-
- /***
- * 瀹℃壒鎷掔粷鏃剁殑璇︽儏淇℃伅
- */
- private String msg;
-
- /**
- * 闆嗘垚绯荤粺缂栫爜
- */
- private String systemCode;
-
- /**
- * 涓绘暟鎹ā鍨嬬殑缂栫爜
- */
- private String mdType;
-
- /***
- * 鏁版嵁鍒嗗彂鎵�瑙﹀彂鐨勭被鍨� 鎵嬪姩鍒嗗彂:distribute_manual,
- */
- private String action;
-
- /***
- * 娑堣垂绯荤粺涓厤缃殑鍒嗗彂浠ょ墝
- */
- private String distributeToken;
-
-
- /***
- * 鍒嗗彂浜哄憳鏁版嵁淇℃伅
- */
- private List<Map<String,String>> masterData=new ArrayList<>();
-
- public int getCode() {
- return code;
- }
-
- public void setCode(int code) {
- this.code = code;
- }
-
- public String getMsg() {
- return msg;
- }
-
- public void setMsg(String msg) {
- this.msg = msg;
- }
-
- public String getSystemCode() {
- return systemCode;
- }
-
- public void setSystemCode(String systemCode) {
- this.systemCode = systemCode;
- }
-
- public String getMdType() {
- return mdType;
- }
-
- public void setMdType(String mdType) {
- this.mdType = mdType;
- }
-
- public String getAction() {
- return action;
- }
-
- public void setAction(String action) {
- this.action = action;
- }
-
- public String getDistributeToken() {
- return distributeToken;
- }
-
- public void setDistributeToken(String distributeToken) {
- this.distributeToken = distributeToken;
- }
-
- public List<Map<String,String>> getMasterData() {
- return masterData;
- }
-
- public void setMasterData(List<Map<String,String>> masterData) {
- this.masterData = masterData;
- }
-
- @Override
- public String toString() {
- return "MDMParamData{" +
- "code=" + code +
- ", msg='" + msg + '\'' +
- ", systemCode='" + systemCode + '\'' +
- ", mdType='" + mdType + '\'' +
- ", action='" + action + '\'' +
- ", distributeToken='" + distributeToken + '\'' +
- ", masterData=" + masterData +
- '}';
- }
-}
diff --git a/Source/UBCS/ubcs-service-api/ubcs-code-api/src/main/java/com/vci/ubcs/code/vo/webserviceModel/mdm/MdmResultData.java b/Source/UBCS/ubcs-service-api/ubcs-code-api/src/main/java/com/vci/ubcs/code/vo/webserviceModel/mdm/MdmResultData.java
deleted file mode 100644
index 639655f..0000000
--- a/Source/UBCS/ubcs-service-api/ubcs-code-api/src/main/java/com/vci/ubcs/code/vo/webserviceModel/mdm/MdmResultData.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.vci.ubcs.code.vo.webserviceModel.mdm;
-
-import com.vci.ubcs.code.vo.webserviceModel.person.ResultMdMapping;
-
-import java.io.Serializable;
-import java.util.List;
-
-public class MdmResultData implements Serializable {
-
- /***
- * 鏍囪瘑澶勭悊鎴愬姛鎴栧け璐�
- */
- private boolean success;
- /***
- * 鎴愬姛鎴栧け璐ョ殑淇℃伅
- */
- private String message;
- /**
- * 鍒嗗彂鐨勬墍鏈夋暟鎹殑淇℃伅
- */
- private List<ResultMdMapping> mdMappings;
-
- public boolean isSuccess() {
- return success;
- }
-
- public void setSuccess(boolean success) {
- this.success = success;
- }
-
- public String getMessage() {
- return message;
- }
-
- public void setMessage(String message) {
- this.message = message;
- }
-
- public List<ResultMdMapping> getMdMappings() {
- return mdMappings;
- }
-
- public void setMdMappings(List<ResultMdMapping> mdMappings) {
- this.mdMappings = mdMappings;
- }
-
- @Override
- public String toString() {
- return "MdmResultData{" +
- "success=" + success +
- ", message='" + message + '\'' +
- ", mdMappings=" + mdMappings +
- '}';
- }
-}
diff --git a/Source/UBCS/ubcs-service-api/ubcs-code-api/src/main/java/com/vci/ubcs/code/vo/webserviceModel/mdm/data.json b/Source/UBCS/ubcs-service-api/ubcs-code-api/src/main/java/com/vci/ubcs/code/vo/webserviceModel/mdm/data.json
deleted file mode 100644
index 5014b28..0000000
--- a/Source/UBCS/ubcs-service-api/ubcs-code-api/src/main/java/com/vci/ubcs/code/vo/webserviceModel/mdm/data.json
+++ /dev/null
@@ -1,62 +0,0 @@
-{ "msg":"瀹℃壒鎷掔粷鏃剁殑璇︽儏淇℃伅",
- "distributeToken": "2",
- "code":"0",
- "systemCode":"ERP",
- "mdType":"product_info",
- "action":"distribute_subseribe",
- "masterData": [
- {
- "id": "xj000001",
- "dr": "0",
- "mdm_code": "xj000001",
- "industry": "",
- "product_family": "",
- "product_line": "product_line",
- "product_manu_symbol": "product_manu_symbol",
- "product_manufacture_code": "manufacture_code",
- "product_manufacture_name": "name",
- "product_model": "product_model",
- "product_model_name": "product_model_name",
- "product_model_symbol": "product_model_symbol",
- "product_type": "product_type",
- "project_code": "project_code",
- "project_name": "project_name",
- "project_symbol": "project_symbol",
- "pk_mdm": "xj0001",
- "mdm_version": "V1",
- "mdm_duplicate": "0",
- "creator": "zhangsan",
- "createtime": "2024-10-23",
- "modifier": "zhangsan",
- "modifytime": "2024-10-23",
- "mdm_datastatus": "3",
- "mdm_cleanstatus": "TRANSFER"
- },{
- "id": "xj000002",
- "dr": "0",
- "mdm_code": "xj_00002",
- "industry": "",
- "product_family": "",
- "product_line": "product_line",
- "product_manu_symbol": "product_manu_symbol1",
- "product_manufacture_code": "product_manufacture_code1",
- "product_manufacture_name": "product_manufacture_name1",
- "product_model": "product_model1",
- "product_model_name": "product_model_name1",
- "product_model_symbol": "product_model_symbol1",
- "product_type": "type",
- "project_code": "project_code1",
- "project_name": "project_name1",
- "project_symbol": "symbol",
- "pk_mdm": "xj0002",
- "mdm_version": "V1",
- "mdm_duplicate": "0",
- "creator": "zhangsan",
- "createtime": "2024-10-23",
- "modifier": "zhangsan",
- "modifytime": "2024-10-23",
- "mdm_datastatus": "3",
- "mdm_cleanstatus": "TRANSFER"
- }
- ]
-}
diff --git a/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/controller/CodeSyncUniversalController.java b/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/controller/CodeSyncUniversalController.java
index 0092afa..7e508cd 100644
--- a/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/controller/CodeSyncUniversalController.java
+++ b/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/controller/CodeSyncUniversalController.java
@@ -1,10 +1,6 @@
package com.vci.ubcs.code.controller;
-import com.vci.ubcs.code.service.CodeMdmInfaceI;
import com.vci.ubcs.code.service.UniversalInterfaceI;
-import com.vci.ubcs.code.vo.webserviceModel.mdm.MDMData;
-import com.vci.ubcs.code.vo.webserviceModel.mdm.MDMParamData;
-import com.vci.ubcs.code.vo.webserviceModel.mdm.MdmResultData;
import com.vci.ubcs.code.vo.webserviceModel.person.PersonData;
import com.vci.ubcs.code.vo.webserviceModel.person.ResultOrgData;
import org.apache.tools.ant.taskdefs.condition.Http;
@@ -39,12 +35,6 @@
*/
@Autowired
private UniversalInterfaceI universalInterfaceI;
-
- /**
- * 鎺ュ彛闆嗘垚鏈嶅姟
- */
- @Autowired
- private CodeMdmInfaceI codeMdmInfaceI;
/****
* 鐢宠鎺ュ彛
@@ -187,29 +177,6 @@
}
/**
- * 鎺ュ彈MDM浜у搧鍒嗗彂鏁版嵁
- * @param mdmParamData
- * @param request
- * @return
- */
- @PostMapping("/syncDataForProduct")
- public MdmResultData syncDataForProduct(@RequestBody MDMParamData mdmParamData,HttpServletRequest request){
-
- //this.setHttpToThreadLocal(request);
- ThreadLocal<HttpServletRequest> threadLocal = new ThreadLocal<>();
- threadLocal.set(request);
- codeMdmInfaceI.setThreadLocal(threadLocal);
- MdmResultData result = new MdmResultData();
- try {
- result= codeMdmInfaceI.syncDataForMDM(mdmParamData,"CPXH","CPXH");
- }catch (Throwable e){
- e.printStackTrace();
- logger.error("ResultOrgData->"+e.getMessage());
- }
- return result;
- }
-
- /**
* 璁剧疆request锛屽埌ThreadLocal涓�
* @param request
*/
@@ -217,20 +184,18 @@
ThreadLocal<HttpServletRequest> threadLocal = new ThreadLocal<>();
threadLocal.set(request);
universalInterfaceI.setThreadLocal(threadLocal);
- //codeMdmInfaceI.setThreadLocal(threadLocal);
}
-
-
/***
*
- * @param mdmData
+ * @param dataString
+ * @param dataType
* @param request
* @return
*/
@PostMapping("/test")
- public String test(@RequestBody MDMData mdmData, HttpServletRequest request){
- System.out.println("");
- return "";
+ public String test(@RequestParam("dataString")String dataString, @RequestParam("dataType")String dataType,HttpServletRequest request){
+ String result="{\"data\": {\"object\": {\"code\": \"0201040133\",\"oid\": \"0000001\",\"erroid\": \"0\",\"msg\": \"娴嬭瘯鎴愬姛\"}}}";
+ return result;
}
}
diff --git a/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/controller/RedirectViewController.java b/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/controller/RedirectViewController.java
deleted file mode 100644
index 60c2a83..0000000
--- a/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/controller/RedirectViewController.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.vci.ubcs.code.controller;
-
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.servlet.mvc.support.RedirectAttributes;
-import org.springframework.web.servlet.view.RedirectView;
-
-@RestController
-@RequestMapping("/redirectController")
-public class RedirectViewController {
- @GetMapping("/redirectWithUsingRedirectView")
- public RedirectView redirectWithUsingRedirectView(RedirectAttributes redirectAttributes){
- redirectAttributes.addAttribute("flashAttribute","redirectWithUsingRedirectView");
- redirectAttributes.addAttribute("attribute","redirectWithUsingRedirectView");
- return new RedirectView("redirectedUrl");
- }
-}
diff --git a/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/service/CodeMdmInfaceI.java b/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/service/CodeMdmInfaceI.java
deleted file mode 100644
index fa686d2..0000000
--- a/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/service/CodeMdmInfaceI.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.vci.ubcs.code.service;
-
-import com.vci.ubcs.code.vo.webserviceModel.mdm.MDMParamData;
-import com.vci.ubcs.code.vo.webserviceModel.mdm.MdmResultData;
-
-import javax.servlet.http.HttpServletRequest;
-
-public interface CodeMdmInfaceI {
-
- public void setThreadLocal(ThreadLocal<HttpServletRequest> threadLocal);
- /**
- * 涓巑dm闆嗘垚閫氱敤鎺ュ彛
- * @param mdmParamData
- * @param classifyCode
- * @return
- */
- public MdmResultData syncDataForMDM(MDMParamData mdmParamData, String library,String classifyCode) ;
-}
diff --git a/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/service/impl/CodeMdmInfaceImpl.java b/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/service/impl/CodeMdmInfaceImpl.java
deleted file mode 100644
index 5701c22..0000000
--- a/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/service/impl/CodeMdmInfaceImpl.java
+++ /dev/null
@@ -1,808 +0,0 @@
-package com.vci.ubcs.code.service.impl;
-
-import com.alibaba.cloud.commons.lang.StringUtils;
-import com.alibaba.fastjson.JSONObject;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.toolkit.Wrappers;
-import com.vci.ubcs.code.bo.CodeClassifyFullInfoBO;
-import com.vci.ubcs.code.constant.MdmDuckingConstant;
-import com.vci.ubcs.code.dto.CodeOrderDTO;
-import com.vci.ubcs.code.dto.CodeOrderSecDTO;
-import com.vci.ubcs.code.entity.CodeClassify;
-import com.vci.ubcs.code.entity.DockingLog;
-import com.vci.ubcs.code.entity.DockingSystemConfig;
-import com.vci.ubcs.code.enumpack.CodeDefaultLC;
-import com.vci.ubcs.code.enumpack.CodeSecTypeEnum;
-import com.vci.ubcs.code.enumpack.SysIntegrationDataFlowTypeEnum;
-import com.vci.ubcs.code.enumpack.sysIntegrationPushTypeEnum;
-import com.vci.ubcs.code.mapper.CommonsMapper;
-import com.vci.ubcs.code.service.*;
-import com.vci.ubcs.code.util.ClientBusinessObject;
-import com.vci.ubcs.code.util.gennerAttrMapUtil;
-import com.vci.ubcs.code.vo.pagemodel.*;
-import com.vci.ubcs.code.vo.webserviceModel.apply.ApplyDataVO;
-import com.vci.ubcs.code.vo.webserviceModel.apply.ApplyDatasVO;
-import com.vci.ubcs.code.vo.webserviceModel.apply.ClassfyVO;
-import com.vci.ubcs.code.vo.webserviceModel.apply.ProppertyVO;
-import com.vci.ubcs.code.vo.webserviceModel.attrmap.*;
-import com.vci.ubcs.code.vo.webserviceModel.mdm.MDMParamData;
-import com.vci.ubcs.code.vo.webserviceModel.mdm.MdmResultData;
-import com.vci.ubcs.code.vo.webserviceModel.person.EnumerableData;
-import com.vci.ubcs.code.vo.webserviceModel.person.ResultMdMapping;
-import com.vci.ubcs.code.vo.webserviceModel.result.xml.XMLResultDataObjectDetailDO;
-import com.vci.ubcs.code.webService.config.AttributeMapConfig;
-import com.vci.ubcs.code.webService.config.ClassifyConfig;
-import com.vci.ubcs.code.webService.config.MDMInterFaceConfig;
-import com.vci.ubcs.omd.feign.IBtmTypeClient;
-import com.vci.ubcs.omd.vo.BtmTypeVO;
-import com.vci.ubcs.starter.util.DefaultAttrAssimtUtil;
-import com.vci.ubcs.starter.web.util.BeanUtilForVCI;
-import com.vci.ubcs.starter.web.util.VciBaseUtil;
-import lombok.extern.slf4j.Slf4j;
-import org.springblade.core.log.exception.ServiceException;
-import org.springblade.core.secure.BladeUser;
-import org.springblade.core.secure.utils.AuthUtil;
-import org.springblade.core.tool.api.R;
-import org.springblade.core.tool.utils.Func;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Service;
-import org.springframework.util.CollectionUtils;
-
-import javax.annotation.Resource;
-import javax.jws.WebMethod;
-import javax.servlet.http.HttpServletRequest;
-import javax.xml.ws.WebServiceContext;
-import javax.xml.ws.handler.MessageContext;
-import java.util.*;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.ForkJoinPool;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.stream.Collectors;
-
-import static com.vci.ubcs.code.constant.MdmEngineConstant.CODE_SEC_LENGTH_FIELD;
-import static com.vci.ubcs.code.constant.MdmEngineConstant.DEFAULT_SYNC_ATTR_LIST;
-import static com.vci.ubcs.code.enumpack.CodeSecTypeEnum.CODE_SERIAL_SEC;
-
-/***
- * 缁熶竴鎺ュ彛
- */
-@Service
-@Slf4j
-public class CodeMdmInfaceImpl implements CodeMdmInfaceI {
-
-
- @Autowired(required = false)
- private AttributeMapConfig attributeMapConfig;
-
- /****
- * 鍏充簬璺烳DM闆嗘垚閰嶇疆
- */
- @Autowired(required = false)
- private MDMInterFaceConfig mdmInterFaceConfig;
- /**
- * 涓婚搴撳垎绫荤殑鏈嶅姟
- */
- @Autowired(required = false)
- private ICodeClassifyService classifyService;
- /**
- * 涓氬姟绫诲瀷鐨勬湇鍔�
- */
- @Autowired
- private IBtmTypeClient btmTypeClient;
- /**
- * 閫氱敤鏌ヨ
- */
- @Resource
- private CommonsMapper commonsMapper;
-
- /**
- * 涓绘暟鎹紩鎿庣殑鏈嶅姟
- */
- @Resource
- private MdmEngineService engineService;
-
- /**
- * 瀵嗙骇鐨勬湇鍔�
- */
- @Resource
- private MdmIOService mdmIOService;
-
- @Resource
- private IDockingSystemConfigService dockingSystemConfigService;
-
-
- /***
- * 闆嗘垚鎺ュ彛鏃ュ織鏈嶅姟鐨勯厤缃�
- */
- @Resource
- private IDockingLogeService dockingLogeService;
-
- @Resource
- private IPasswordFreeLoginService passwordFreeLoginService;
- /***
- * 鏄惁鏍¢獙鎺ュ彛绠$悊
- */
- @Value("${code.universalinterface.checkSystemConfig:true}")
- public boolean CODE_CHECKCONFIG;
-
- /**
- * 鑷畾涔夊苟鍙慒orkJoinPool
- */
- private static final ForkJoinPool customForkJoinPool = new ForkJoinPool(Runtime.getRuntime().availableProcessors() - 1);
-
- private static String separator="##VCI##";
- private String errorid="0";
- private String msg="鎴愬姛";
- private String objerrorCode="0";
- private String objerrorMsg="鎴愬姛";
-
- private final ThreadLocal<HttpServletRequest> threadLocal = new ThreadLocal<>();
-
- @Resource
- private WebServiceContext webServiceContext;
- @Override
- public void setThreadLocal(ThreadLocal<HttpServletRequest> requestThreadLocal){
- this.threadLocal.set(requestThreadLocal.get());
- requestThreadLocal.remove();
- }
- @WebMethod
- private HttpServletRequest getRequest() {
- //rest璇锋眰鏂瑰紡鑾峰彇request
- HttpServletRequest request = this.threadLocal.get();
- if(Func.isEmpty(request)){
- try {
- // webservice璇锋眰鏂瑰紡鑾峰彇HttpServletRequest瀵硅薄
- request = (HttpServletRequest)webServiceContext.getMessageContext().get(MessageContext.SERVLET_REQUEST);
- }catch (Exception e){
- throw new ServiceException("鑾峰彇httpServletRequest澶辫触锛屽師鍥�:"+e.getMessage());
- }
- }else {
- this.threadLocal.remove();
- }
- return request;
- }
- @Override
- public MdmResultData syncDataForMDM(MDMParamData mdmParamData,String library,String classifyCode) {
- boolean isCodeOrGroupCode=false;
- MdmResultData mdmResultData=new MdmResultData();
- String systemCode=mdmParamData.getSystemCode();
- List<ResultMdMapping> resultMdMappingList=new ArrayList<>();
- String message="";
- AtomicBoolean success = new AtomicBoolean(true);
- List<Map<String,String>> masterDataList = new ArrayList<>();
- AtomicReference<ClassifyConfig> currentClassifyConfig = new AtomicReference<>(new ClassifyConfig());
- try {
- if (mdmParamData == null) {
- throw new Throwable("浼犲叆鍙傛暟涓簄ull");
- }
- try {
- passwordFreeLoginService.pwdFreeLoginByBoolean(systemCode.toLowerCase(), this.getRequest());
- } catch (Throwable e) {
- throw new Throwable("鐢ㄦ埛閴存潈澶辫触");
- }
- List<ClassifyConfig> classifyConfigList=mdmInterFaceConfig.getClassifyconfigs();
- if(StringUtils.isNotBlank(library)&&StringUtils.isNotBlank(classifyCode)){
- String finalLibrary = library;
- classifyConfigList.stream().forEach(classifyConfig -> {
- String currentClassifyCode=classifyConfig.getClassCode();
- String currentLibrary=classifyConfig.getLibrary();
- if(finalLibrary.equals(currentLibrary)&&classifyCode.equals(currentClassifyCode)){
- currentClassifyConfig.set(classifyConfig);
- }
- });
- }else{
- Map<String,List<ClassifyConfig>> fieldClassifyConfigMap=new HashMap<>();
- classifyConfigList.stream().forEach(classifyConfig -> {
- String sourceKey=classifyConfig.getSourceKey();//鍒嗙被鏍囪瘑瀛楁
- //String sourceClassifyCode=classifyConfig.getSourceClassifyCode();//鍒嗙被鏍囪瘑
- List<ClassifyConfig> classifyConfigs = new ArrayList<>();
- if(fieldClassifyConfigMap.containsKey(sourceKey)) {
- List<ClassifyConfig> oldClassifyConfigs= fieldClassifyConfigMap.get(sourceKey);
- classifyConfigs.addAll(oldClassifyConfigs);
- }
- classifyConfigs.add(classifyConfig);
- fieldClassifyConfigMap.put(sourceKey,classifyConfigs);
- });
- masterDataList.stream().forEach(masterData -> {
- masterData.forEach((field, vaule) -> {
- if (fieldClassifyConfigMap.containsKey(field)) {
- vaule=StringUtils.isBlank(vaule) ? "" : vaule;
- List<ClassifyConfig> classifyConfigs = fieldClassifyConfigMap.get(field);
- Map<String, ClassifyConfig> classifyConfigMap = classifyConfigs.stream().filter(classify -> classify != null && StringUtils.isNotBlank(classify.getSourceClassifyCode())).collect(Collectors.toList()).stream().collect(Collectors.toMap(s -> s.getSourceClassifyCode(), t -> t));
- if (classifyConfigMap.containsKey(vaule)) {
- currentClassifyConfig.set(classifyConfigMap.get(vaule));
- }
- }
- });
- });
- }
- library= currentClassifyConfig.get().getLibrary();
- if (StringUtils.isBlank(library)) {
- success.set(false);
- log.info("涓嶮DM闆嗘垚閰嶇疆缂哄皯搴撹妭鐐逛俊鎭�,library->" + library);
- throw new Throwable("涓嶮DM闆嗘垚閰嶇疆缂哄皯搴撹妭鐐逛俊鎭�,library->" + library);
- }
- String classCode= currentClassifyConfig.get().getClassCode();
- ClassfyVO classfyVO = new ClassfyVO();
- classfyVO.setLibrary(library);
- classfyVO.setClassCode(classCode);
- CodeClassifyVO codeClassifyVO = this.getClassfy(classfyVO, library);
- log.info("end锛氬垎绫绘煡璇㈠畬姣�");
- //鑾峰彇鍒嗙被妯℃澘淇℃伅
- if (codeClassifyVO == null || StringUtils.isBlank(codeClassifyVO.getOid())) {
- success.set(false);
- throw new Throwable("鏍规嵁閰嶇疆鍒嗙被鐨勫垎绫荤紪鍙凤紝鏈幏鍙栧埌鍒嗙被淇℃伅");
- }
- CodeClassifyTemplateVO templateVO = engineService.getUsedTemplateByClassifyOid(codeClassifyVO.getOid());
- if (templateVO == null || StringUtils.isBlank(templateVO.getOid())) {
- success.set(false);
- throw new Throwable("鏍规嵁浼犺緭鐨勫垎绫伙紝鏈幏鍙朚DM绯荤粺涓搴旀ā鏉�");
- }
- if(CODE_CHECKCONFIG) {
- //鏍¢獙鏄惁閰嶇疆
- DockingSystemConfig dockingSystemConfig=null;
- dockingSystemConfig=checkIspass(systemCode, SysIntegrationDataFlowTypeEnum.ACCEPT.getValue(), sysIntegrationPushTypeEnum.ACCPET_APPCODE.getValue(),codeClassifyVO.getOid());
- if(dockingSystemConfig==null||StringUtils.isBlank(dockingSystemConfig.getOid())){
- throw new Throwable("绯荤粺鏍囪瘑涓恒��"+ systemCode +"銆戯紝闆嗘垚鍒嗙被涓恒��"+codeClassifyVO.getName()+"銆戜互涓婂垎绫伙紝"+sysIntegrationPushTypeEnum.ACCPET_APPCODE.getText()+"鎺ュ彛閰嶇疆宸插仠鐢ㄦ垨鑰呮湭閰嶇疆锛岃鑱旂郴缂栫爜绠$悊鍛橈紒");
- }
- isCodeOrGroupCode="true".equals(dockingSystemConfig.getIsGroupCodeFlag())?true:false;
- }
- List<CodeClassifyTemplateAttrVO> attrVOS = templateVO.getAttributes().stream().filter(s -> !DEFAULT_SYNC_ATTR_LIST.contains(s.getId()) &&
- ((Func.isNotEmpty(s.getClassifyInvokeAttr()) || Func.isNotEmpty(s.getClassifyInvokeAttrName())) || VciBaseUtil.getBoolean(s.getFormDisplayFlag()))
- ).collect(Collectors.toList());
- R<BtmTypeVO> r = btmTypeClient.getAllAttributeByBtmId(templateVO.getBtmTypeId());
- if (!r.isSuccess()) {
- throw new Throwable(r.getMsg());
- }
- BtmTypeVO btmTypeVO = r.getData();
- if (btmTypeVO == null) {
- throw new Throwable("鏍规嵁涓氬姟绫诲瀷鏈煡璇㈠埌涓氬姟绫诲瀷瀵硅薄锛�");
- }
- String tableName = btmTypeVO.getTableName();
- if (com.alibaba.nacos.common.utils.StringUtils.isBlank(tableName)) {
- throw new Throwable("鏍规嵁涓氬姟绫诲瀷鏈煡璇㈠埌涓氬姟绫诲瀷鐩稿叧鑱旂殑琛�");
- }
- masterDataList = mdmParamData.getMasterData();
- List<String> codeList = new ArrayList<>();
- List<ApplyDataVO> applyDataVOList = new ArrayList<>();
- List<ApplyDataVO> deleteDataVOList = new ArrayList<>();
- List<String> fields = Func.toStrList(currentClassifyConfig.get().getEnumFields());
- masterDataList.stream().forEach(masterData -> {
- ApplyDataVO object = new ApplyDataVO();
- List<ProppertyVO> proppertyVOList = new ArrayList<>();
- masterData.forEach((field, vaule) -> {
- /***
- * 鏍规嵁閰嶇疆鏂囦欢鑾峰彇鏋氫妇鍊�
- */
- if(fields.contains(field)){//濡傛灉鏄灇涓撅紝鍒欓渶瑕佽浆鎹㈡灇涓惧��
- String enumFiled = masterData.get(field);
- EnumerableData enumerableData = JSONObject.toJavaObject(JSONObject.parseObject(enumFiled), EnumerableData.class);
- String enumCode = enumerableData.getCode();
- //String enumCodeValue=enumerableData.getName();
- ProppertyVO proppertyVO = new ProppertyVO();
- proppertyVO.setKey(field);
- proppertyVO.setValue(enumCode);
- proppertyVOList.add(proppertyVO);
- }else {
- ProppertyVO proppertyVO = new ProppertyVO();
- proppertyVO.setKey(field);
- proppertyVO.setValue(vaule);
- proppertyVOList.add(proppertyVO);
- }
-
- });
-
- Map<String,String> fixedFieldMap = currentClassifyConfig.get().getFixedFieldMap();
- if(fixedFieldMap.containsKey("creator")){
- String fixedField=fixedFieldMap.getOrDefault("creator","");
- object.setCreator(masterData.getOrDefault(fixedField,"").toString());//鍒涘缓鑰�
- }
- if(fixedFieldMap.containsKey("modifier")) {
- String fixedField=fixedFieldMap.getOrDefault("modifier","");
- object.setEditor(masterData.getOrDefault(fixedField, "").toString());//淇敼鑰�
- }
- if(fixedFieldMap.containsKey("id")) {
- String fixedField=fixedFieldMap.getOrDefault("id","");
- object.setId(masterData.getOrDefault(fixedField, "").toString());//涓婚敭
- }
- object.setStatus(CodeDefaultLC.RELEASED.getValue());//鐘舵�佸垯闇�瑕佸垽鏂�
- String dr="0";
- if(fixedFieldMap.containsKey("dr")) {
- String fixedField=fixedFieldMap.getOrDefault("dr","");
- dr = masterData.getOrDefault(fixedField, "").toString();
- }
- object.setCode(masterData.getOrDefault(currentClassifyConfig.get().getSourceCodeKey(),"").toString());//缂栫爜
- object.setProp(proppertyVOList);
-
- if (dr.equals(1)) {
- //鎿嶄綔绫诲瀷
- object.setOperate("delete");
- deleteDataVOList.add(object);
- } else {
- //String worker_category=personMasterData.getWorker_category();
- object.setOperate("create");
- applyDataVOList.add(object);
- }
- codeList.add(object.getCode());
-
- });
- String targetCodeKey=currentClassifyConfig.get().getTargetCodeKey();
- StringBuffer sb = new StringBuffer();
- sb.append(" select * from ");
- sb.append(tableName);
- sb.append(" where 1=1 ");
- sb.append(" and lastr=1 and lastv=1");
- sb.append(" and "+targetCodeKey+" in (");
- sb.append(VciBaseUtil.toInSql(codeList.toArray(new String[0])));
- sb.append(")");
- List<Map<String, String>> dataMapList = commonsMapper.queryByOnlySqlForMap(sb.toString());
- DefaultAttrAssimtUtil.mapToLowerCase(dataMapList, true);
- List<ClientBusinessObject> cboList = ChangeMapTOClientBusinessObjects(dataMapList);
- ApplyDatasVO applyDatasVO = new ApplyDatasVO();
- ApplyDatasVO editDatasVO = new ApplyDatasVO();
- if (!CollectionUtils.isEmpty(cboList)) {
- //鏍规嵁MDM缂栫爜鍘诲垽鏂暟鎹槸鍚﹂噸澶�.
- Map<String, ClientBusinessObject> oldpplyDataVOMap = cboList.stream().filter(data -> data != null && StringUtils.isNotBlank(data.getAttributeValue(targetCodeKey))).collect(Collectors.toList()).stream().collect(Collectors.toMap(s -> s.getAttributeValue(targetCodeKey).toLowerCase(Locale.ROOT), t -> t));
- //鏁版嵁搴撲笉瀛樺湪鐨�
- List<ApplyDataVO> applyApplyDataVOList = applyDataVOList.stream().filter(cbo -> {
- String code = cbo.getCode();
- return !oldpplyDataVOMap.containsKey(code);
- }).collect(Collectors.toList());
- applyDatasVO.setObject(applyApplyDataVOList);
- //鏁版嵁搴撳瓨鍦ㄧ殑
- List<ApplyDataVO> editApplyDataVOList = applyDataVOList.stream().filter(cbo -> {
- String code = cbo.getCode();
- if (oldpplyDataVOMap.containsKey(code)) {
- cbo.setOperate("update");
- }
- return oldpplyDataVOMap.containsKey(code);
- }).collect(Collectors.toList());
- editApplyDataVOList.addAll(deleteDataVOList);
- editDatasVO.setObject(editApplyDataVOList);
- } else {
- applyDatasVO.setObject(applyDataVOList);
- }
-
- LinkedList<XMLResultDataObjectDetailDO> resultDataObjectDetailDOs = new LinkedList<>();
- if (editDatasVO.getObject() != null && editDatasVO.getObject().size() > 0) {
- DataObjectVO dataObjectVO = new DataObjectVO();
- this.getConfigDatas(systemCode, library, editDatasVO, attrVOS, dataObjectVO);
- log.info("start锛氫慨鏀规暟鎹墽琛屽畬姣�");
- //boolean personApplyGroupCode = personAndDeptConfig.isPersonApplyGroupCode();
- mdmIOService.batchSyncEditDatas(codeClassifyVO, dataObjectVO, resultDataObjectDetailDOs, isCodeOrGroupCode);
- log.info("end锛氫慨鏀规暟鎹墽琛屽畬姣�");
- }
- if (applyDatasVO.getObject() != null && applyDatasVO.getObject().size() > 0) {
- DataObjectVO dataObjectVO = new DataObjectVO();
- this.getConfigDatas(systemCode, library, applyDatasVO, attrVOS, dataObjectVO);
-
- CodeClassifyFullInfoBO classifyFullInfo = classifyService.getClassifyFullInfo(codeClassifyVO.getOid());
- CodeRuleVO ruleVO = engineService.getCodeRuleByClassifyFullInfo(classifyFullInfo);
- if (ruleVO == null || "".equals(ruleVO.getOid())) {
- throw new Throwable("缂栫爜瑙勫垯");
- }
- List<XMLResultDataObjectDetailDO> xDOs = new CopyOnWriteArrayList<>();
- final List<RowDatas> rowDatas = dataObjectVO.getRowData();
- boolean finalIsCodeOrGroupCode = isCodeOrGroupCode;
- rowDatas.parallelStream().forEach(rowData -> {
- String mesg = "";
- try {
- CodeOrderDTO orderDTO = new CodeOrderDTO();
- List<CodeOrderSecDTO> codeOrderSecDTOList = new ArrayList<>();
- orderDTO.setCodeClassifyOid(codeClassifyVO.getOid());//鍒嗙被涓婚敭
- orderDTO.setTemplateOid(templateVO.getOid());
- orderDTO.setCreator(rowData.getCreator());
- orderDTO.setLastModifier(rowData.getEditor());
- orderDTO.setLcStatus(rowData.getStatus());
- if (!CollectionUtils.isEmpty(ruleVO.getSecVOList())) {//绠楄鍒�
- boolean usedFlag=currentClassifyConfig.get().isUsedFlag();//鏄惁渚濇嵁缂栫爜瑙勫垯
- String codeFilter=currentClassifyConfig.get().getCodeFilter();//鏍规嵁缂栫爜鍊兼埅鍙�
- if(usedFlag){//濡傛灉渚濇嵁MDM缂栫爜涓虹紪鐮佺郴缁熺紪鐮佺殑璇濓紝鍒欐牴鎹涓烘槸鍙彉鐮佹
- if(ruleVO.getSecVOList().size()==1) {
- ruleVO.getSecVOList().stream().forEach(codeBasicSecVO -> {
- if (codeBasicSecVO.getSecType().equals("codevariablesec")) {//瑙勫垯涔嬪畾涔変负鍙彉鐮佹瀛樺偍涓绘暟鎹紶閫掕繃鏉ョ殑鏁版嵁
- CodeOrderSecDTO CodeOrderSecDTO = new CodeOrderSecDTO();
- CodeOrderSecDTO.setSecOid(codeBasicSecVO.getOid());
- CodeOrderSecDTO.setSecValue(rowData.getCode());
- codeOrderSecDTOList.add(CodeOrderSecDTO);
- }
- });
- }else{
- mesg="鏍规嵁閰嶇疆缂栫爜渚濇嵁闆嗘垚绯荤粺:["+systemCode+"]鐨勬暟鎹紪鐮佷负渚濇嵁锛岀紪鐮佽鍒欏簲涓哄彲鍙樼爜娈�";
- new Throwable("鏍规嵁閰嶇疆缂栫爜渚濇嵁闆嗘垚绯荤粺:["+systemCode+"]鐨勬暟鎹紪鐮佷负渚濇嵁锛岀紪鐮佽鍒欏簲涓哄彲鍙樼爜娈�");
- }
- }else {//濡傛灉涓嶄緷鎹甅DM缂栫爜涓虹紪鐮佺郴缁熺紪鐮佺殑璇濓紝鍒欐牴鎹厤缃敓鎴愮紪鐮佽鍒�
- String[] secValues = currentClassifyConfig.get().getSecValueFilter().split("#");
- final int[] index = {0};
- try {
- ruleVO.getSecVOList().stream().forEach(codeBasicSecVO -> {
- if (!CODE_SERIAL_SEC.getValue().equals(codeBasicSecVO.getSecType())) {
- CodeOrderSecDTO CodeOrderSecDTO = new CodeOrderSecDTO();
- CodeOrderSecDTO.setSecOid(codeBasicSecVO.getOid());
- CodeOrderSecDTO.setSecValue(secValues[index[0]]);
- codeOrderSecDTOList.add(CodeOrderSecDTO);
- index[0]++;
- }
- });
- }catch (Throwable e){
- mesg="璁$畻鐮佹鐮佸�煎嚭鐜板紓甯�:"+e.getMessage();
- new Throwable("璁$畻鐮佹鐮佸�煎嚭鐜板紓甯�:"+e.getMessage());
- }
- }
-
- }
- if(CollectionUtils.isEmpty(codeOrderSecDTOList)){
- throw new Exception(mesg);
- }
- orderDTO.setCodeRuleOid(ruleVO.getOid());
- orderDTO.setSecDTOList(codeOrderSecDTOList);//瀛樺偍缂栫爜
- orderDTO.setData(rowData.getFiledValue());
- String code = engineService.addSaveCodeNotauthUser(orderDTO, false);
- if (StringUtils.isNotBlank(code)) {
- StringBuffer sqlsb = new StringBuffer();
- sqlsb.append(" select * from ");
- sqlsb.append(tableName);
- sqlsb.append(" where 1=1 ");
- sqlsb.append(" and lastr=1 and lastv=1");
- sqlsb.append(" and id in (");
- sqlsb.append(VciBaseUtil.toInSql(code));
- sqlsb.append(")");
- List<Map<String, String>> newDataMapList = commonsMapper.queryByOnlySqlForMap(sqlsb.toString());
- if (!CollectionUtils.isEmpty(newDataMapList)) {
- String oid = StringUtils.isBlank(newDataMapList.get(0).get("OID")) ? "" : newDataMapList.get(0).get("OID");
- List<String> oidList = new ArrayList<>();
- oidList.add(oid);
- //濡傛灉鏈夌敵璇峰氨鍘昏皟鐢ㄧ敵璇烽泦鍥㈢爜
- if (finalIsCodeOrGroupCode) {
- mdmIOService.sendApplyGroupcode(oidList, templateVO.getBtmTypeId(), sysIntegrationPushTypeEnum.ACCPET_APPCODE.getValue());
- success.set(true);
- mesg = "鏁版嵁淇濆瓨鎴愬姛锛岀瓑寰呯敵璇烽泦鍥㈢爜";
- }
- }
- }
- } catch (Exception e) {
- mesg="闆嗘垚绯荤粺锛氥��"+systemCode+"銆戠敵璇风紪鐮佸け璐�:" + e.getMessage();
- mesg = e.getMessage();
- e.printStackTrace();
- throw new ServiceException(e.getMessage());
- } finally {
- XMLResultDataObjectDetailDO x = new XMLResultDataObjectDetailDO();
- x.setId(rowData.getOid());
- x.setCode(rowData.getCode());
- x.setMsg(mesg);
- x.setErrorid("1");
- xDOs.add(x);
- }
- });
- resultDataObjectDetailDOs.addAll(xDOs);
- boolean finalSuccess1 = success.get();
- String finalMessage1 = message;
- // 闄愬埗绾跨▼骞惰鏁伴噺
- customForkJoinPool.submit(() -> {
- resultDataObjectDetailDOs.stream().forEach(resultDataObjectDetailDO -> {
- ResultMdMapping resultMdMapping = new ResultMdMapping();
- resultMdMapping.setBusiDataId(resultDataObjectDetailDO.getId());
- resultMdMapping.setSuccess(finalSuccess1);
- resultMdMapping.setEntityCode(" ");
- resultMdMapping.setMdmCode(resultDataObjectDetailDO.getCode());
- resultMdMapping.setMessage(finalMessage1);
- resultMdMapping.setSubMdMappings(null);
- resultMdMappingList.add(resultMdMapping);
- });
- }).join();
- log.info("end锛氱敵璇疯幏鍙栧畬姣�");
- }
- } catch (Throwable e) {
- success.set(false);
- message = "闆嗘垚绯荤粺锛氥��"+systemCode+"銆戞墽琛岄泦鎴愬け璐�:" + e.getMessage();
- msg ="闆嗘垚绯荤粺锛氥��"+systemCode+"銆戞墽琛岄泦鎴愬け璐�" + e.getMessage();
- //缁勭粐杩斿洖缁撴灉
- boolean finalSuccess = success.get();
- String finalMessage = message;
- masterDataList.stream().forEach(masterData -> {
- Map<String, String> dataMap = VciBaseUtil.objectToMapString(masterData);
- ResultMdMapping resultMdMapping = new ResultMdMapping();
- resultMdMapping.setBusiDataId(dataMap.getOrDefault("id",""));
- resultMdMapping.setSuccess(finalSuccess);
- resultMdMapping.setEntityCode(" ");
- resultMdMapping.setMdmCode(dataMap.getOrDefault(currentClassifyConfig.get(),""));
- resultMdMapping.setMessage(finalMessage);
- resultMdMapping.setSubMdMappings(null);
- });
- } finally {
- mdmResultData.setMessage(message);
- mdmResultData.setSuccess(success.get());
- mdmResultData.setMdMappings(resultMdMappingList);
- //Object object = JSONObject.toJSON(resultOrgData);
- }
- String resultStr = JSONObject.toJSONString(mdmResultData);
- String data = JSONObject.toJSONString(mdmParamData);
- try {
- //璁板綍鏃ュ織
- this.saveLogs(systemCode, systemCode, data, resultStr, success.get(), msg, "syncDataForMDM");
- } catch (Throwable e) {
- e.printStackTrace();
- }
- return mdmResultData;
- }
- private List<ClientBusinessObject> ChangeMapTOClientBusinessObjects(List<Map<String,String>> oldDataMap){
- List<ClientBusinessObject> clientBusinessObjectList=new ArrayList<>();
- DefaultAttrAssimtUtil.mapToLowerCase(oldDataMap,true);
- final BladeUser user = AuthUtil.getUser();
- oldDataMap.stream().forEach(dataMap->{
- ClientBusinessObject clientBusinessObject=new ClientBusinessObject();
- DefaultAttrAssimtUtil.copplyDefaultAttrAssimt(dataMap,clientBusinessObject,false,user);
- for (String key:dataMap.keySet()){
- Object value= dataMap.getOrDefault(key,"");
- clientBusinessObject.setAttributeValue(key.toLowerCase(Locale.ROOT),value==null?"":value.toString());
- }
- clientBusinessObjectList.add(clientBusinessObject);
- });
- return clientBusinessObjectList;
- }
- /***
- * 鏌ヨ鏍¢獙鍒嗙被淇℃伅
- * @param classfyVO
- */
- private CodeClassifyVO getClassfy(ClassfyVO classfyVO,String libray) throws Throwable{
- CodeClassifyVO classifyVO = new CodeClassifyVO();
- try {
- String classCode = classfyVO.getClassCode();
- String className = classfyVO.getFullclsfNamePath();
- //鏍规嵁鍒嗙被浠e彿鏌ヨ鍒嗙被淇℃伅
- if (StringUtils.isNotBlank(classfyVO.getClassCode())) {
- Map<String, String> conditionMap = new HashMap<>();
- List<CodeClassify> codeClassifyList = classifyService.selectByWrapper(Wrappers.<CodeClassify>query().lambda().eq(CodeClassify::getId, classCode));
- final CodeClassify[] newCodeClassify = {new CodeClassify()};
- if (!CollectionUtils.isEmpty(codeClassifyList)) {
- codeClassifyList.stream().forEach(codeClassify -> {
- CodeClassifyVO codeClassifyVO= classifyService.getTopClassifyVO(codeClassify.getOid());
- if(codeClassifyVO.getId().toUpperCase(Locale.ROOT).equals(libray.toUpperCase(Locale.ROOT))){
- newCodeClassify[0] =codeClassify;
- }
- });
- classifyVO = new CodeClassifyVO();
- BeanUtilForVCI.copyPropertiesIgnoreCase(newCodeClassify[0], classifyVO);
- //灏咲TO杞崲涓篋O
- if(StringUtils.isBlank(classifyVO.getOid())){
- throw new Throwable("鐢宠缂栫爜鐨勫垎绫伙細銆�"+classCode+"銆戜笉灞炰簬搴撹妭鐐广��"+libray+"銆戯紝璇锋鏌ュ弬鏁板垎绫昏妭鐐�/搴撹妭鐐逛俊鎭槸鍚﹀尮閰�");
- }
- }else{
- throw new Throwable("鏍规嵁鍒嗙被浠e彿鏈煡璇㈠埌鐩稿簲鐨勫垎绫讳俊鎭�");
- }
- } else {
- classifyVO = classifyService.getObjectByClsfNamePath(className.replace(separator, "/"));
- if(StringUtils.isBlank(classifyVO.getOid())){
- throw new Throwable("鏍规嵁鍒嗙被鍚嶇О璺緞鏈煡璇㈠埌鐩稿簲鐨勫垎绫讳俊鎭�");
- }
- }
- }catch (Throwable e){
- objerrorCode="100";
- throw new Throwable("鑾峰彇鍒嗙被淇℃伅澶辫触:"+e.getMessage());
- }
- return classifyVO;
- }
- public void getConfigDatas(String systemId,String libray, ApplyDatasVO applyDatasVO,List<CodeClassifyTemplateAttrVO> codeClassifyTemplateAttrVOList,DataObjectVO dataObjectVO) throws Throwable {
-
- LinkedHashMap<String,LinkedHashMap<String,String>> dataKeyValueMap=new LinkedHashMap<>();
- //濡傛灉灏嗘暟鎹浆鎹㈡垚鎵�闇�瑕佺殑鏁版嵁瀵硅薄
- Map<String, String> attrMapConfigMap=new HashMap<>();
- Map<String, String> propMaps=new HashMap<>();
- log.info("寮�濮嬭鍙栫郴缁熼厤缃枃浠� start");
- Map<String, String> stringStringMap=attributeMapConfig.getSystem_attrmap();
- log.info("闆嗘垚绯荤粺灞炴�ф槧灏勯厤缃枃浠舵潯鐩暟-銆�"+stringStringMap.size());
- if(!CollectionUtils.isEmpty(stringStringMap)) {
- List<LibraryClsfDO> libraryClsfDOList=new ArrayList<>();
- try {
- log.info("info锛氶渶瑕佽鍙栭厤缃枃浠�");
- LibraryDO libraryDO = gennerAttrMapUtil.getNewInstance().gennerAttrMapBySystem(systemId, stringStringMap);
- libraryClsfDOList = libraryDO.getClsf();
- } catch (Throwable e) {
- objerrorCode = "1";
- e.printStackTrace();
- throw new Throwable("闆嗘垚绯荤粺鏍囪瘑涓猴細銆�" + systemId + "銆戯紝鍒嗙被搴撲负:銆�" + libray + "銆戠殑闆嗘垚灞炴�ч厤缃枃浠惰鍙栧け璐�");
- }
- // String path = stringStringMap.get(systemId);
- // 蹇界暐key澶у皬鍐欙紝鑾峰彇閰嶇疆鐨勬槧灏勬枃浠惰矾寰�
- String path = VciBaseUtil.getMapStrValueIgnoreCase(stringStringMap,systemId);
- if (!CollectionUtils.isEmpty(libraryClsfDOList)) {
- Map<String, List<ClsfAttrMappingDO>> libPropMaps = libraryClsfDOList.stream().collect(Collectors.toMap(LibraryClsfDO::getLibrary, LibraryClsfDO::getProp, (key1, key2) -> key2));
- log.info("鏍规嵁鍙傛暟锛歭ibray锛�-銆�" + libray + "浠庨厤缃枃浠朵腑鎵惧搴斿睘鎬ф槧灏勯厤缃�");
- if (libPropMaps.containsKey(libray.toUpperCase(Locale.ROOT))) {
- log.info("鏍规嵁鍙傛暟锛歭ibray锛�-銆�" + libray + "鍖归厤鍒扮浉搴旂殑灞炴�ф槧灏勪俊鎭�");
- List<ClsfAttrMappingDO> clsfAttrMappingDOList = libPropMaps.get(libray.toUpperCase(Locale.ROOT));
- propMaps = clsfAttrMappingDOList.stream().collect(Collectors.toMap(ClsfAttrMappingDO::getSourceKey, ClsfAttrMappingDO::getTargetKey, (key1, key2) -> key2));
- log.info("鏍规嵁鍙傛暟锛歭ibray锛�-銆�" + libray + "鍖归厤鍒扮浉搴旂殑灞炴�ф槧灏勪俊鎭�,灞炴�ф槧灏勬潯鐩暟+" + clsfAttrMappingDOList.size());
- } else {
- objerrorCode = "1";
- throw new Throwable("鏍规嵁绯荤粺鏍囪瘑銆�" + systemId + "銆戞壘鍒板搴旂殑閰嶇疆鏂囦欢:銆�" + path + "銆戯紝浣嗘湭鑾峰彇鍒板搴旂殑搴撱��" + libray + "銆戝睘鎬ф槧灏勪俊鎭厤缃�");
- }
- }else{
- objerrorCode = "1";
- throw new Throwable("鏍规嵁绯荤粺鏍囪瘑銆�" + systemId + "銆戞壘鍒板搴旂殑閰嶇疆鏂囦欢:銆�" + path + "銆戯紝浣嗘湭鑾峰彇鍒板搴旂殑搴撱��" + libray + "銆戝睘鎬ф槧灏勪俊鎭厤缃�");
- }
- }else{
- objerrorCode = "1";
- throw new Throwable("鏈幏鍙栧埌闆嗘垚灞炴�ф槧灏勭郴缁熼厤缃俊鎭�");
- }
- log.info("鏍规嵁鍙傛暟锛歭ibray锛�-銆�"+libray+"浠庨厤缃枃浠朵腑鎵惧搴斿睘鎬ф槧灏勯厤缃� end ");
- LinkedList<String> rowNameList=new LinkedList<>();
- LinkedHashMap<String,Integer> filedIndexMap=new LinkedHashMap<>();
- //鏍规嵁鍒嗙被妯℃澘缁勭粐鏁版嵁
- final int[] index = {0};
- try {
- //闄ゅ幓榛樿鐨勫睘鎬�.杩樻湁鍙湁鍏锋湁鍒嗙被娉ㄥ叆鐨勬墠杩囨护鍑烘潵
- codeClassifyTemplateAttrVOList = codeClassifyTemplateAttrVOList.stream().filter(
- s ->!DEFAULT_SYNC_ATTR_LIST.contains(s.getId()) &&
- ((Func.isNotEmpty(s.getClassifyInvokeAttr()) || Func.isNotEmpty(s.getClassifyInvokeAttrName())) || VciBaseUtil.getBoolean(s.getFormDisplayFlag()))
- ).collect(Collectors.toList());
- codeClassifyTemplateAttrVOList.stream().forEach(codeClassifyTemplateAttrVO -> {
- String attrName = codeClassifyTemplateAttrVO.getName();
- String field = codeClassifyTemplateAttrVO.getId();
- rowNameList.add(attrName);
- filedIndexMap.put(field, index[0]++);
- });
- dataObjectVO.setColName(rowNameList);//鏀惧叆灞炴��
- attrMapConfigMap.putAll(propMaps);
- LinkedList<RowDatas> rowDataList = new LinkedList<>();
- List<ApplyDataVO> applyDataVOList=new ArrayList<>();
-
- if(!CollectionUtils.isEmpty(applyDatasVO.getObject())){
- applyDataVOList=applyDatasVO.getObject();
- }
- //Map<String, List<ProppertyVO>> dataPropMap = applyDataVOList.stream().collect(Collectors.toMap(ApplyDataVO::getId, ApplyDataVO::getProp, (key1, key2) -> key2));
- final int[] rowIndex = {0};
- applyDataVOList.stream().forEach(applyDataVO -> {
- rowIndex[0]++;
- RowDatas rowDatas = new RowDatas();
- rowDatas.setOid(applyDataVO.getId());
- rowDatas.setCreator(applyDataVO.getCreator());
- rowDatas.setEditor(applyDataVO.getEditor());
- rowDatas.setCode(applyDataVO.getCode());
- rowDatas.setOperation(applyDataVO.getOperate());
- rowDatas.setStatus(applyDataVO.getStatus());
- rowDatas.setRowIndex(rowIndex[0] + "");
- List<ProppertyVO> proppertyVOList = applyDataVO.getProp();
-
- LinkedHashMap<Integer, String> integerValueMap = new LinkedHashMap<>();
- Map<String, String> filedValueMap = new HashMap<>();
- if (!CollectionUtils.isEmpty(proppertyVOList)) {
- Map<String, String> sourceKeyValueMap = proppertyVOList.stream().collect(Collectors.toMap(ProppertyVO::getKey, ProppertyVO::getValue, (key1, key2) -> key2));
- Map<String, String> keyValueMap = new HashMap<>();
- //鍒ゆ柇attrMapConfigMap鏄惁鏈夊�硷紝濡傛灉娌℃湁鍒欒鏄庡熀纭�榛樿鐨勬槸缂栫爜绯荤粺瀛楁
- if (!CollectionUtils.isEmpty(attrMapConfigMap)) {
- sourceKeyValueMap.keySet().forEach(sourceKey -> {
- String dataValue = sourceKeyValueMap.get(sourceKey);
- if (attrMapConfigMap.containsKey(sourceKey)) {
- String targetKey = attrMapConfigMap.get(sourceKey);
- keyValueMap.put(targetKey, StringUtils.isBlank(dataValue)?"":dataValue);
- }
- });
- } else {
- sourceKeyValueMap.forEach((filed,value)->{
- keyValueMap.put(filed,StringUtils.isBlank(value)?"":value) ;
- });
- }
-
- filedIndexMap.forEach((attrKey, column) -> {
- String keyValue = "";
- if (keyValueMap.containsKey(attrKey)) {
- keyValue =StringUtils.isBlank(keyValueMap.get(attrKey))?"":keyValueMap.get(attrKey);
- }
- integerValueMap.put(column, keyValue);
- filedValueMap.put(attrKey, keyValue);
- });
- }
- rowDatas.setData(integerValueMap);
- rowDatas.setFiledValue(filedValueMap);
- rowDataList.add(rowDatas);
- });
- dataObjectVO.setRowData(rowDataList);
- }catch (Throwable e){
- objerrorCode="1";
- throw new Throwable("缁勭粐鏁版嵁鏄犲皠鍊煎け璐�");
- }
- }
- /*private void codeValueList(List<CodeBasicSecVO> secVOList,String codeseclengthfield ){
- if(StringUtils.isNotBlank(codeseclengthfield)) {
-
- String[] secLengths = codeseclengthfield.split("#");
- for (int i = 0; i < secLengths.length; i++) {
- CodeBasicSecVO secVO = secVOList.get(i);
- String thisSecValue = "";
- *//*if(i == 0){
- thisSecValue = seclenghStr.contains("#")?code.substring(0,VciBaseUtil.getInt(secLengths[i])):code;
- } else if(i == secLengths.length-1){
- //鏈�鍚�
- thisSecValue = seclenghStr.contains("#")?code.substring(VciBaseUtil.getInt(secLengths[i-1]),code.length()):code;
- }else {*//*
- int start = 0;
- for (int j = 0; j < i; j++) {
- start += VciBaseUtil.getInt(secLengths[j]);
- }
- thisSecValue = code.substring(start, start + VciBaseUtil.getInt(secLengths[i]));
- // }
- if (VciBaseUtil.getBoolean(secVO.getSerialDependFlag())) {
- serialUnitList.add(thisSecValue);
- serialSecOidIndexMap.put(secVO.getOid(), i);
- }
- if (CODE_SERIAL_SEC.getValue().equalsIgnoreCase(secVO.getSecType())) {
- serialValueMap.put(secVO.getOid(), thisSecValue);
- }
- codeValueList.add(thisSecValue);
- }
- }else{
-
- }
- }*/
- /***
- * 鏍¢獙鏄惁鍋氫簡閰嶇疆
- * @param systemId,绯荤粺鏍囪瘑
- * @param type:鎺ュ彛绫诲瀷
- * @param operationType:鎺ュ彛鎿嶄綔绫诲瀷
- * @return
- */
- private DockingSystemConfig checkIspass(String systemId,String type,String operationType,String classOid){
- log.info("systemId锛�"+systemId+",type:"+SysIntegrationDataFlowTypeEnum.getTextByValue(type)+",operationType:"+sysIntegrationPushTypeEnum.getTextByValue(operationType)+",classOid:"+classOid);
- //CodeClassifyVO codeClassifyVO= classifyService.getObjectByOid(classOid);
- List<CodeClassify> codeClassifyList= classifyService.selectAllLevelParentByOid(classOid);
- List<String> classifyOids=new ArrayList<>();
- if(!CollectionUtils.isEmpty(codeClassifyList)) {
- classifyOids = codeClassifyList.stream().map(CodeClassify::getOid).collect(Collectors.toList());
- }else{
- return null;
- }
- //classOid=codeClassifyVO.getOid();
- //鏍规嵁绫诲瀷鍘绘煡璇㈤渶瑕侀泦鎴愮殑鍒嗙被鎴栬�呮暟鎹�
- LambdaQueryWrapper<DockingSystemConfig> queryWrapper = Wrappers.<DockingSystemConfig>lambdaQuery();
- queryWrapper.eq(DockingSystemConfig::getUsedFlag, MdmDuckingConstant.SEND_FLAG_TRUE);
- queryWrapper.eq(DockingSystemConfig::getSysBaseId,systemId);
- queryWrapper.eq(DockingSystemConfig::getDataFlowType,type);
- queryWrapper.eq(DockingSystemConfig::getPushType,operationType);
- if(StringUtils.isNotBlank(classOid)){
- queryWrapper.in(DockingSystemConfig::getClassifyOid,classifyOids);
- }
- List<DockingSystemConfig> dockingSystemConfigList=new ArrayList<>();
- dockingSystemConfigList= dockingSystemConfigList=dockingSystemConfigService.list(queryWrapper);
- if(!CollectionUtils.isEmpty(dockingSystemConfigList)){
- return dockingSystemConfigList.get(0);
- }else{
- return null;
- }
- }
- /***
- * 璁板綍鏃ュ織淇℃伅
- * @param systemId
- * @param parmaData
- * @param result
- * @return
- */
- private void saveLogs(String systemId,String systemName,String parmaData, String result,boolean isSucess,String msg,String operation){
- //璁板綍鏃ュ織淇℃伅
- DockingLog dockingLoge=new DockingLog();
- //String oid=redisService.getUUIDEveryDay();
- dockingLoge.setSystemCode(StringUtils.isBlank(systemId)?"-":systemId);//璁剧疆绯荤粺鏍囪瘑
- dockingLoge.setSystemName(StringUtils.isBlank(systemName)?"-":systemName);
- dockingLoge.setMsg(msg);//鏃ュ織娑堟伅
- dockingLoge.setClassifyId("-");//鍒嗙被缂栧彿
- dockingLoge.setClassifyName("-");//鍒嗙被鍚嶇О
- dockingLoge.setClassifyOid("-");//鍒嗙被涓婚敭
- dockingLoge.setUniqueCode("-");//鍞竴鏍囪瘑
- dockingLoge.setSystemOid("-");//绯荤粺鏍囪瘑
-// dockingLogeDO.setName(operation);
- //dockingLogeDO.setOid(oid);//鏃ュ織涓婚敭
- dockingLoge.setParamString(parmaData);//鍙傛暟淇℃伅
- dockingLoge.setReturnString(result);//杩斿洖淇℃伅
- dockingLoge.setType(operation);//鏃ュ織鎿嶄綔绫诲瀷
- dockingLoge.setCreateTime(new Date());
- if(isSucess) {
- dockingLoge.setInterfaceStatus("true");//鎺ュ彛闆嗘垚鐘舵��
- }else{
- dockingLoge.setInterfaceStatus("false");//鎺ュ彛闆嗘垚鐘舵��
- }
- dockingLogeService.save(dockingLoge);
- log.info("闆嗘垚鎺ㄩ�佹暟鎹�,systemId:"+systemId+",systemname:"+systemName+",operation:"+operation+",param:"+parmaData);
- }
-}
diff --git a/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/webService/config/ClassifyConfig.java b/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/webService/config/ClassifyConfig.java
deleted file mode 100644
index 8117daa..0000000
--- a/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/webService/config/ClassifyConfig.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.vci.ubcs.code.webService.config;
-
-import lombok.Data;
-import java.util.Map;
-/***
- * 鍒嗙被瀵硅薄
- */
-@Data
-public class ClassifyConfig {
- /***
- * 鐮佹鐮佸��
- */
- private String secValueFilter;
- /***
- * 缂栫爜鎴彇渚濇嵁
- */
- private String codeFilter;
- /***
- * mdm鍒嗙被鏍囪瘑瀛楁
- */
- private String sourceKey;
- /***
- * mdm缂栫爜鏍囪瘑瀛楁
- */
- private String sourceCodeKey;
- /**
- * 瀛樺偍MDM缂栫爜鐨勫瓧娈�
- */
- private String targetCodeKey;
- /***
- * 搴撹妭鐐�
- */
- private String library;
- /**
- * MDM鎺ュ彛鍒嗙被浼犻�掔殑鏍囪瘑
- */
- private String sourceClassifyCode;
- /**
- * 缂栫爜搴旇瀛樺叆鐨勫湴鏂圭紪鍙�
- */
- private String classCode;
- /**
- * 鍒嗙被璺緞.
- */
- private String classNamePath;
- /***
- * 涓氬姟绫诲瀷鍚嶇О
- */
- private String btmName;
-
- /**
- * 鍥哄畾瀛楁
- */
- private Map<String,String> fixedFieldMap;
-
- /***
- * 鏄惁鐢ㄤ簬MDM浼犻�掕繃鏉ョ殑缂栫爜
- */
- private boolean usedFlag;
-
- /***
- * 鎺ュ彛鏋氫妇鍊煎瓧娈�
- */
- private String enumFields;
-}
diff --git a/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/webService/config/MDMInterFaceConfig.java b/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/webService/config/MDMInterFaceConfig.java
deleted file mode 100644
index 0b7767c..0000000
--- a/Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/webService/config/MDMInterFaceConfig.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.vci.ubcs.code.webService.config;
-
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.stereotype.Component;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/***
- * 涓嶮DM绯荤粺鍒嗗彂闆嗘垚閰嶇疆
- */
-@ConfigurationProperties(prefix="mdm.config")
-@Component
-@Data
-public class MDMInterFaceConfig {
- /***
- * 绯荤粺鍒嗙被缂栧彿瀵圭О
- */
- private List<ClassifyConfig> classifyconfigs=new ArrayList<>();
-
-}
--
Gitblit v1.9.3