Browse Source

:tada: 3.5.0.RELEASE 新增报文加密 令牌签名校验提示

smallchill 2 years ago
parent
commit
d178675c53
74 changed files with 2727 additions and 216 deletions
  1. 1 1
      blade-core-boot/pom.xml
  2. 1 1
      blade-core-boot/src/main/resources/bootstrap.yml
  3. 1 1
      blade-core-cloud/pom.xml
  4. 29 0
      blade-core-crypto/pom.xml
  5. 32 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecrypt.java
  6. 28 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecryptAes.java
  7. 28 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecryptDes.java
  8. 33 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncrypt.java
  9. 28 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncryptAes.java
  10. 28 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncryptDes.java
  11. 25 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/bean/CryptoInfoBean.java
  12. 20 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/bean/DecryptHttpInputMessage.java
  13. 30 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/config/ApiCryptoConfiguration.java
  14. 46 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/config/ApiCryptoProperties.java
  15. 66 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiDecryptParamResolver.java
  16. 87 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiDecryptRequestBodyAdvice.java
  17. 64 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiEncryptResponseBodyAdvice.java
  18. 20 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/enums/CryptoType.java
  19. 13 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/exception/DecryptBodyFailException.java
  20. 17 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/exception/EncryptBodyFailException.java
  21. 14 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/exception/EncryptMethodNotFoundException.java
  22. 14 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/exception/KeyNotConfiguredException.java
  23. 117 0
      blade-core-crypto/src/main/java/org/springblade/core/api/crypto/util/ApiCryptoUtil.java
  24. 1 1
      blade-core-datascope/pom.xml
  25. 1 2
      blade-core-develop/pom.xml
  26. 1 1
      blade-core-launch/pom.xml
  27. 1 1
      blade-core-launch/src/main/java/org/springblade/core/launch/constant/AppConstant.java
  28. 9 1
      blade-core-launch/src/main/java/org/springblade/core/launch/constant/TokenConstant.java
  29. 1 1
      blade-core-loadbalancer/pom.xml
  30. 1 2
      blade-core-log/pom.xml
  31. 6 1
      blade-core-log/src/main/java/org/springblade/core/log/config/BladeErrorMvcAutoConfiguration.java
  32. 13 5
      blade-core-log/src/main/java/org/springblade/core/log/config/BladeLogToolAutoConfiguration.java
  33. 7 1
      blade-core-log/src/main/java/org/springblade/core/log/error/BladeErrorAttributes.java
  34. 8 1
      blade-core-log/src/main/java/org/springblade/core/log/error/BladeRestExceptionTranslator.java
  35. 48 0
      blade-core-log/src/main/java/org/springblade/core/log/props/BladeLogProperties.java
  36. 1 1
      blade-core-mybatis/pom.xml
  37. 14 15
      blade-core-mybatis/src/main/java/org/springblade/core/mp/intercept/QueryInterceptor.java
  38. 14 8
      blade-core-oss/pom.xml
  39. 36 9
      blade-core-oss/src/main/java/org/springblade/core/oss/AliossTemplate.java
  40. 417 0
      blade-core-oss/src/main/java/org/springblade/core/oss/MinioTemplate.java
  41. 202 0
      blade-core-oss/src/main/java/org/springblade/core/oss/OssTemplate.java
  42. 50 60
      blade-core-oss/src/main/java/org/springblade/core/oss/QiniuTemplate.java
  43. 7 13
      blade-core-oss/src/main/java/org/springblade/core/oss/config/AliossConfiguration.java
  44. 65 0
      blade-core-oss/src/main/java/org/springblade/core/oss/config/MinioConfiguration.java
  45. 43 0
      blade-core-oss/src/main/java/org/springblade/core/oss/config/OssConfiguration.java
  46. 13 16
      blade-core-oss/src/main/java/org/springblade/core/oss/config/QiniuConfiguration.java
  47. 54 0
      blade-core-oss/src/main/java/org/springblade/core/oss/enums/PolicyType.java
  48. 4 0
      blade-core-oss/src/main/java/org/springblade/core/oss/model/BladeFile.java
  49. 1 1
      blade-core-oss/src/main/java/org/springblade/core/oss/props/OssProperties.java
  50. 1 1
      blade-core-report/pom.xml
  51. 1 1
      blade-core-secure/pom.xml
  52. 2 1
      blade-core-secure/src/main/java/org/springblade/core/secure/config/SecureConfiguration.java
  53. 50 0
      blade-core-secure/src/main/java/org/springblade/core/secure/props/BladeTokenProperties.java
  54. 25 0
      blade-core-secure/src/main/java/org/springblade/core/secure/utils/AuthUtil.java
  55. 41 7
      blade-core-secure/src/main/java/org/springblade/core/secure/utils/SecureUtil.java
  56. 1 2
      blade-core-social/pom.xml
  57. 1 1
      blade-core-swagger/pom.xml
  58. 1 1
      blade-core-swagger/src/main/java/org/springblade/core/swagger/SwaggerProperties.java
  59. 1 2
      blade-core-test/pom.xml
  60. 1 1
      blade-core-tool/pom.xml
  61. 3 0
      blade-core-tool/src/main/java/org/springblade/core/tool/config/JacksonConfiguration.java
  62. 3 1
      blade-core-tool/src/main/java/org/springblade/core/tool/config/MessageConfiguration.java
  63. 3 3
      blade-core-tool/src/main/java/org/springblade/core/tool/jackson/AbstractReadWriteJackson2HttpMessageConverter.java
  64. 37 0
      blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeJacksonProperties.java
  65. 123 4
      blade-core-tool/src/main/java/org/springblade/core/tool/jackson/JsonUtil.java
  66. 16 10
      blade-core-tool/src/main/java/org/springblade/core/tool/jackson/MappingApiJackson2HttpMessageConverter.java
  67. 9 0
      blade-core-tool/src/main/java/org/springblade/core/tool/support/Kv.java
  68. 215 34
      blade-core-tool/src/main/java/org/springblade/core/tool/utils/AesUtil.java
  69. 21 1
      blade-core-tool/src/main/java/org/springblade/core/tool/utils/ClassUtil.java
  70. 207 0
      blade-core-tool/src/main/java/org/springblade/core/tool/utils/DesUtil.java
  71. 163 0
      blade-core-tool/src/main/java/org/springblade/core/tool/utils/HexUtil.java
  72. 38 0
      blade-core-tool/src/main/java/org/springblade/core/tool/utils/Holder.java
  73. 1 1
      blade-core-transaction/pom.xml
  74. 3 2
      pom.xml

+ 1 - 1
blade-core-boot/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>org.springblade</groupId>
         <artifactId>blade-tool</artifactId>
-        <version>3.4.1</version>
+        <version>3.5.0</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
blade-core-boot/src/main/resources/bootstrap.yml

@@ -101,7 +101,7 @@ mybatis-plus:
 swagger:
   title: SpringBlade 接口文档系统
   description: SpringBlade 接口文档系统
-  version: 3.4.1
+  version: 3.5.0
   license: Powered By SpringBlade
   licenseUrl: https://bladex.vip
   terms-of-service-url: https://bladex.vip

+ 1 - 1
blade-core-cloud/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>blade-tool</artifactId>
         <groupId>org.springblade</groupId>
-        <version>3.4.1</version>
+        <version>3.5.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 29 - 0
blade-core-crypto/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>blade-tool</artifactId>
+        <groupId>org.springblade</groupId>
+        <version>3.5.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>blade-core-crypto</artifactId>
+    <name>${project.artifactId}</name>
+    <version>${blade.tool.version}</version>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-core-tool</artifactId>
+            <version>${blade.tool.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-context</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 32 - 0
blade-core-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>解密含有{@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 {
+
+	/**
+	 * 解密类型
+	 *
+	 * @return 类型
+	 */
+	CryptoType value();
+
+	/**
+	 * 私钥,用于某些需要单独配置私钥的方法,没有时读取全局配置的私钥
+	 *
+	 * @return 私钥
+	 */
+	String secretKey() default "";
+
+}

+ 28 - 0
blade-core-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 解密
+ *
+ * @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 "";
+
+}

+ 28 - 0
blade-core-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 解密
+ *
+ * @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 "";
+
+}

+ 33 - 0
blade-core-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 "";
+
+}

+ 28 - 0
blade-core-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 "";
+
+}

+ 28 - 0
blade-core-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 "";
+
+}

+ 25 - 0
blade-core-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;
+
+}

+ 20 - 0
blade-core-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>解密信息输入流</p>
+ *
+ * @author licoy.cn, L.cm
+ */
+@Getter
+@RequiredArgsConstructor
+public class DecryptHttpInputMessage implements HttpInputMessage {
+	private final InputStream body;
+	private final HttpHeaders headers;
+}

+ 30 - 0
blade-core-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));
+	}
+
+}

+ 46 - 0
blade-core-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;
+
+}

+ 66 - 0
blade-core-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiDecryptParamResolver.java

@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
+ * <p>
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.gnu.org/licenses/lgpl.html
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.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 参数 解析
+ *
+ * @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());
+	}
+}

+ 87 - 0
blade-core-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." +
+				" (解密错误,请检查选择的源数据的加密方式是否正确。)");
+		}
+		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;
+	}
+
+}

+ 64 - 0
blade-core-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();
+	}
+
+}

+ 20 - 0
blade-core-crypto/src/main/java/org/springblade/core/api/crypto/enums/CryptoType.java

@@ -0,0 +1,20 @@
+package org.springblade.core.api.crypto.enums;
+
+/**
+ * <p>加密方式</p>
+ *
+ * @author licoy.cn, L.cm
+ */
+public enum CryptoType {
+
+	/**
+	 * des
+	 */
+	DES,
+
+	/**
+	 * aes
+	 */
+	AES
+
+}

+ 13 - 0
blade-core-crypto/src/main/java/org/springblade/core/api/crypto/exception/DecryptBodyFailException.java

@@ -0,0 +1,13 @@
+package org.springblade.core.api.crypto.exception;
+
+/**
+ * <p>解密数据失败异常</p>
+ *
+ * @author licoy.cn
+ */
+public class DecryptBodyFailException extends RuntimeException {
+
+	public DecryptBodyFailException(String message) {
+		super(message);
+	}
+}

+ 17 - 0
blade-core-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);
+	}
+}

+ 14 - 0
blade-core-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. (加密方式未定义)");
+	}
+
+}

+ 14 - 0
blade-core-crypto/src/main/java/org/springblade/core/api/crypto/exception/KeyNotConfiguredException.java

@@ -0,0 +1,14 @@
+package org.springblade.core.api.crypto.exception;
+
+
+/**
+ * <p>未配置KEY运行时异常</p>
+ *
+ * @author licoy.cn, L.cm
+ */
+public class KeyNotConfiguredException extends RuntimeException {
+
+	public KeyNotConfiguredException(String message) {
+		super(message);
+	}
+}

+ 117 - 0
blade-core-crypto/src/main/java/org/springblade/core/api/crypto/util/ApiCryptoUtil.java

@@ -0,0 +1,117 @@
+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.AesUtil;
+import org.springblade.core.tool.utils.ClassUtil;
+import org.springblade.core.tool.utils.DesUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.core.MethodParameter;
+import org.springframework.lang.Nullable;
+
+/**
+ * <p>辅助检测工具类</p>
+ *
+ * @author licoy.cn, L.cm
+ */
+public class ApiCryptoUtil {
+
+	/**
+	 * 获取方法控制器上的加密注解信息
+	 *
+	 * @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());
+	}
+
+	/**
+	 * 获取方法控制器上的解密注解信息
+	 *
+	 * @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);
+		}
+		throw new EncryptBodyFailException();
+	}
+
+	/**
+	 * 选择加密方式并进行解密
+	 *
+	 * @param bodyData byte array
+	 * @param infoBean 加密信息
+	 * @return 解密结果
+	 */
+	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);
+		}
+		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;
+	}
+}

+ 1 - 1
blade-core-datascope/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>blade-tool</artifactId>
         <groupId>org.springblade</groupId>
-        <version>3.4.1</version>
+        <version>3.5.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 2
blade-core-develop/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>blade-tool</artifactId>
         <groupId>org.springblade</groupId>
-        <version>3.4.1</version>
+        <version>3.5.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -40,5 +40,4 @@
         </dependency>
     </dependencies>
 
-
 </project>

+ 1 - 1
blade-core-launch/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>blade-tool</artifactId>
         <groupId>org.springblade</groupId>
-        <version>3.4.1</version>
+        <version>3.5.0</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
blade-core-launch/src/main/java/org/springblade/core/launch/constant/AppConstant.java

@@ -25,7 +25,7 @@ public interface AppConstant {
 	/**
 	 * 应用版本
 	 */
-	String APPLICATION_VERSION = "3.4.1";
+	String APPLICATION_VERSION = "3.5.0";
 
 	/**
 	 * 基础包

+ 9 - 1
blade-core-launch/src/main/java/org/springblade/core/launch/constant/TokenConstant.java

@@ -22,7 +22,6 @@ package org.springblade.core.launch.constant;
  */
 public interface TokenConstant {
 
-	String SIGN_KEY = "bladexisapowerfulmicroservicearchitectureupgradedandoptimizedfromacommercialproject";
 	String AVATAR = "avatar";
 	String HEADER = "blade-auth";
 	String BEARER = "bearer";
@@ -44,4 +43,13 @@ public interface TokenConstant {
 	String DEFAULT_AVATAR = "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png";
 	Integer AUTH_LENGTH = 7;
 
+	/**
+	 * token签名
+	 */
+	String SIGN_KEY = "bladexisapowerfulmicroservicearchitectureupgradedandoptimizedfromacommercialproject";
+	/**
+	 * key安全长度,具体见:https://tools.ietf.org/html/rfc7518#section-3.2
+	 */
+	int SIGN_KEY_LENGTH = 32;
+
 }

+ 1 - 1
blade-core-loadbalancer/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>blade-tool</artifactId>
         <groupId>org.springblade</groupId>
-        <version>3.4.1</version>
+        <version>3.5.0</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>

+ 1 - 2
blade-core-log/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>blade-tool</artifactId>
         <groupId>org.springblade</groupId>
-        <version>3.4.1</version>
+        <version>3.5.0</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
@@ -39,5 +39,4 @@
             <version>${mybatis.plus.version}</version>
         </dependency>
     </dependencies>
-
 </project>

+ 6 - 1
blade-core-log/src/main/java/org/springblade/core/log/config/BladeErrorMvcAutoConfiguration.java

@@ -19,6 +19,7 @@ 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.springblade.core.log.props.BladeLogProperties;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.AutoConfigureBefore;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -28,6 +29,7 @@ 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.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
 import org.springframework.boot.web.servlet.error.ErrorAttributes;
 import org.springframework.boot.web.servlet.error.ErrorController;
@@ -45,15 +47,18 @@ import javax.servlet.Servlet;
 @AllArgsConstructor
 @ConditionalOnWebApplication
 @AutoConfigureBefore(ErrorMvcAutoConfiguration.class)
+@EnableConfigurationProperties(BladeLogProperties.class)
 @ConditionalOnClass({Servlet.class, DispatcherServlet.class})
 public class BladeErrorMvcAutoConfiguration {
 
 	private final ServerProperties serverProperties;
+	private final BladeLogProperties bladeLogProperties;
+
 
 	@Bean
 	@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
 	public DefaultErrorAttributes errorAttributes() {
-		return new BladeErrorAttributes();
+		return new BladeErrorAttributes(bladeLogProperties);
 	}
 
 	@Bean

+ 13 - 5
blade-core-log/src/main/java/org/springblade/core/log/config/BladeLogToolAutoConfiguration.java

@@ -25,8 +25,11 @@ 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.logger.BladeLogger;
+import org.springblade.core.log.props.BladeLogProperties;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 
 /**
@@ -44,28 +47,33 @@ public class BladeLogToolAutoConfiguration {
 	private final BladeProperties bladeProperties;
 
 	@Bean
+	@ConditionalOnProperty(value = BladeLogProperties.PREFIX + "api.enabled", havingValue = "true", matchIfMissing = true)
 	public ApiLogAspect apiLogAspect() {
 		return new ApiLogAspect();
 	}
 
 	@Bean
-	public BladeLogger bladeLogger() {
-		return new BladeLogger();
-	}
-
-	@Bean
+	@ConditionalOnProperty(value = BladeLogProperties.PREFIX + "api.enabled", havingValue = "true", matchIfMissing = true)
 	public ApiLogListener apiLogListener() {
 		return new ApiLogListener(logService, serverInfo, bladeProperties);
 	}
 
 	@Bean
+	@ConditionalOnProperty(value = BladeLogProperties.PREFIX + "error.enabled", havingValue = "true", matchIfMissing = true)
 	public ErrorLogListener errorEventListener() {
 		return new ErrorLogListener(logService, serverInfo, bladeProperties);
 	}
 
 	@Bean
+	@ConditionalOnProperty(value = BladeLogProperties.PREFIX + "usual.enabled", havingValue = "true", matchIfMissing = true)
 	public UsualLogListener bladeEventListener() {
 		return new UsualLogListener(logService, serverInfo, bladeProperties);
 	}
 
+	@Bean
+	@ConditionalOnProperty(value = BladeLogProperties.PREFIX + "usual.enabled", havingValue = "true", matchIfMissing = true)
+	public BladeLogger bladeLogger() {
+		return new BladeLogger();
+	}
+
 }

+ 7 - 1
blade-core-log/src/main/java/org/springblade/core/log/error/BladeErrorAttributes.java

@@ -15,7 +15,9 @@
  */
 package org.springblade.core.log.error;
 
+import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.log.props.BladeLogProperties;
 import org.springblade.core.log.publisher.ErrorLogPublisher;
 import org.springblade.core.tool.api.R;
 import org.springblade.core.tool.api.ResultCode;
@@ -34,7 +36,9 @@ import java.util.Map;
  * @author Chill
  */
 @Slf4j
+@RequiredArgsConstructor
 public class BladeErrorAttributes extends DefaultErrorAttributes {
+	private final BladeLogProperties bladeLogProperties;
 
 	@Override
 	public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
@@ -50,7 +54,9 @@ public class BladeErrorAttributes extends DefaultErrorAttributes {
 			result = R.fail(status, error.getMessage());
 		}
 		//发送服务异常事件
-		ErrorLogPublisher.publishEvent(error, requestUri);
+		if (bladeLogProperties.getError()) {
+			ErrorLogPublisher.publishEvent(error, requestUri);
+		}
 		return BeanUtil.toMap(result);
 	}
 

+ 8 - 1
blade-core-log/src/main/java/org/springblade/core/log/error/BladeRestExceptionTranslator.java

@@ -15,9 +15,11 @@
  */
 package org.springblade.core.log.error;
 
+import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.hibernate.validator.internal.engine.path.PathImpl;
 import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.log.props.BladeLogProperties;
 import org.springblade.core.log.publisher.ErrorLogPublisher;
 import org.springblade.core.secure.exception.SecureException;
 import org.springblade.core.tool.api.R;
@@ -59,8 +61,11 @@ import java.util.Set;
 @ConditionalOnClass({Servlet.class, DispatcherServlet.class})
 @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
 @RestControllerAdvice
+@RequiredArgsConstructor
 public class BladeRestExceptionTranslator {
 
+	private final BladeLogProperties bladeLogProperties;
+
 	@ExceptionHandler(MissingServletRequestParameterException.class)
 	@ResponseStatus(HttpStatus.BAD_REQUEST)
 	public R handleError(MissingServletRequestParameterException e) {
@@ -155,7 +160,9 @@ public class BladeRestExceptionTranslator {
 	public R handleError(Throwable e) {
 		log.error("服务器异常", e);
 		//发送服务异常事件
-		ErrorLogPublisher.publishEvent(e, UrlUtil.getPath(WebUtil.getRequest().getRequestURI()));
+		if (bladeLogProperties.getError()) {
+			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()));
 	}
 

+ 48 - 0
blade-core-log/src/main/java/org/springblade/core/log/props/BladeLogProperties.java

@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ * <p>
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.gnu.org/licenses/lgpl.html
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.log.props;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * 异步配置
+ *
+ * @author Chill
+ */
+@Getter
+@Setter
+@ConfigurationProperties(BladeLogProperties.PREFIX)
+public class BladeLogProperties {
+	/**
+	 * 前缀
+	 */
+	public static final String PREFIX = "blade.log";
+
+	/**
+	 * 是否开启 api 日志
+	 */
+	private Boolean api = Boolean.TRUE;
+	/**
+	 * 是否开启 error 日志
+	 */
+	private Boolean error = Boolean.TRUE;
+	/**
+	 * 是否开启 usual 日志
+	 */
+	private Boolean usual = Boolean.TRUE;
+}

+ 1 - 1
blade-core-mybatis/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>blade-tool</artifactId>
         <groupId>org.springblade</groupId>
-        <version>3.4.1</version>
+        <version>3.5.0</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>

+ 14 - 15
blade-core-mybatis/src/main/java/org/springblade/core/mp/intercept/QueryInterceptor.java

@@ -1,18 +1,17 @@
-/*
- *      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)
+/**
+ * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
+ * <p>
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.gnu.org/licenses/lgpl.html
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.mp.intercept;

+ 14 - 8
blade-core-oss/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>blade-tool</artifactId>
         <groupId>org.springblade</groupId>
-        <version>3.4.1</version>
+        <version>3.5.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -21,17 +21,23 @@
             <artifactId>blade-core-tool</artifactId>
             <version>${blade.tool.version}</version>
         </dependency>
+        <!--Aliyun-->
+        <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.2.18</version>
-        </dependency>
-        <!--alioss-->
-        <dependency>
-            <groupId>com.aliyun.oss</groupId>
-            <artifactId>aliyun-sdk-oss</artifactId>
-            <version>3.1.0</version>
+            <version>7.9.4</version>
         </dependency>
     </dependencies>
 

+ 36 - 9
blade-core-oss/src/main/java/org/springblade/core/oss/AliossTemplate.java

@@ -45,11 +45,12 @@ import java.util.Map;
  * @author Chill
  */
 @AllArgsConstructor
-public class AliossTemplate {
-	private OSSClient ossClient;
-	private OssProperties ossProperties;
-	private OssRule ossRule;
+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)) {
@@ -57,31 +58,37 @@ public class AliossTemplate {
 		}
 	}
 
+	@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);
@@ -95,46 +102,55 @@ public class AliossTemplate {
 		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);
 	}
 
+	@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);
@@ -160,25 +176,30 @@ public class AliossTemplate {
 		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));
@@ -227,11 +248,6 @@ public class AliossTemplate {
 		return getUploadToken(bucketName, ossProperties.getArgs().get("expireTime", 3600L));
 	}
 
-	/**
-	 * TODO 上传大小限制、基础路径
-	 * <p>
-	 * 获取上传凭证,普通上传
-	 */
 	public String getUploadToken(String bucketName, long expireTime) {
 		String baseDir = "upload";
 
@@ -258,11 +274,22 @@ public class AliossTemplate {
 		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());
 	}

+ 417 - 0
blade-core-oss/src/main/java/org/springblade/core/oss/MinioTemplate.java

@@ -0,0 +1,417 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ * <p>
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.gnu.org/licenses/lgpl.html
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.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    过期时间
+	 * @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());
+	}
+
+}

+ 202 - 0
blade-core-oss/src/main/java/org/springblade/core/oss/OssTemplate.java

@@ -0,0 +1,202 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ * <p>
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.gnu.org/licenses/lgpl.html
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.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);
+
+
+}

+ 50 - 60
blade-core-oss/src/main/java/org/springblade/core/oss/QiniuTemplate.java

@@ -33,8 +33,6 @@ import org.springblade.core.tool.utils.StringPool;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.InputStream;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
 import java.util.Date;
 import java.util.List;
 
@@ -44,57 +42,56 @@ import java.util.List;
  * @author Chill
  */
 @AllArgsConstructor
-public class QiniuTemplate {
-	private Auth auth;
-	private UploadManager uploadManager;
-	private BucketManager bucketManager;
-	private OssProperties ossProperties;
-	private OssRule ossRule;
-
-
+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.zone0().getRegion());
+			bucketManager.createBucket(getBucketName(bucketName), Zone.autoZone().getRegion());
 		}
 	}
 
-
+	@Override
 	@SneakyThrows
 	public void removeBucket(String bucketName) {
-		bucketManager.deleteBucket(getBucketName(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(stat.key);
 		ossFile.setName(Func.isEmpty(stat.key) ? fileName : stat.key);
 		ossFile.setLink(fileLink(ossFile.getName()));
 		ossFile.setHash(stat.hash);
@@ -104,80 +101,55 @@ public class QiniuTemplate {
 		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);
 	}
 
-
-	/**
-	 * 获取文件公开链接
-	 *
-	 * @param fileName 文件名
-	 * @return 文件公开链接
-	 */
-	public String publicFileLink(String fileName) {
-		return String.format("%s/%s", ossProperties.getEndpoint(), fileName);
-	}
-
-	/**
-	 * 获取文件私有链接
-	 *
-	 * @param fileName   文件名
-	 * @param expireTime 超时时间
-	 * @return 私有文件链接
-	 */
-	@SneakyThrows
-	public String privateFileLink(String fileName, Long expireTime) {
-		String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replace("+", "%20");
-		String publicUrl = String.format("%s/%s", ossProperties.getEndpoint(), encodedFileName);
-		return auth.privateDownloadUrl(publicUrl, expireTime);
-	}
-
-
+	@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);
+		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);
@@ -185,9 +157,8 @@ public class QiniuTemplate {
 
 	@SneakyThrows
 	public BladeFile put(String bucketName, InputStream stream, String key, boolean cover) {
-		BladeFile file = new BladeFile();
-		file.setOriginalName(key);
 		makeBucket(bucketName);
+		String originalName = key;
 		key = getFileName(key);
 		// 覆盖上传
 		if (cover) {
@@ -201,30 +172,33 @@ public class QiniuTemplate {
 				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));
@@ -261,6 +235,9 @@ public class QiniuTemplate {
 
 	/**
 	 * 获取上传凭证,普通上传
+	 *
+	 * @param bucketName 存储桶名称
+	 * @return string
 	 */
 	public String getUploadToken(String bucketName) {
 		return auth.uploadToken(getBucketName(bucketName));
@@ -268,9 +245,22 @@ public class QiniuTemplate {
 
 	/**
 	 * 获取上传凭证,覆盖上传
+	 *
+	 * @param bucketName 存储桶名称
+	 * @param key        key
+	 * @return string
 	 */
 	private String getUploadToken(String bucketName, String key) {
 		return auth.uploadToken(getBucketName(bucketName), key);
 	}
 
+	/**
+	 * 获取域名
+	 *
+	 * @return String
+	 */
+	public String getOssHost() {
+		return ossProperties.getEndpoint();
+	}
+
 }

+ 7 - 13
blade-core-oss/src/main/java/org/springblade/core/oss/config/AliossConfiguration.java

@@ -22,11 +22,10 @@ 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.BladeOssRule;
 import org.springblade.core.oss.rule.OssRule;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
-import org.springframework.boot.autoconfigure.AutoConfigureAfter;
 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;
@@ -37,20 +36,15 @@ import org.springframework.context.annotation.Bean;
  *
  * @author Chill
  */
-@AutoConfiguration
 @AllArgsConstructor
-@AutoConfigureAfter(QiniuConfiguration.class)
+@AutoConfiguration(after = OssConfiguration.class)
 @EnableConfigurationProperties(OssProperties.class)
+@ConditionalOnClass({OSSClient.class})
 @ConditionalOnProperty(value = "oss.name", havingValue = "alioss")
 public class AliossConfiguration {
 
-	private OssProperties ossProperties;
-
-	@Bean
-	@ConditionalOnMissingBean(OssRule.class)
-	public OssRule ossRule() {
-		return new BladeOssRule();
-	}
+	private final OssProperties ossProperties;
+	private final OssRule ossRule;
 
 	@Bean
 	@ConditionalOnMissingBean(OSSClient.class)
@@ -74,9 +68,9 @@ public class AliossConfiguration {
 	}
 
 	@Bean
+	@ConditionalOnBean({OSSClient.class})
 	@ConditionalOnMissingBean(AliossTemplate.class)
-	@ConditionalOnBean({OSSClient.class, OssRule.class})
-	public AliossTemplate aliossTemplate(OSSClient ossClient, OssRule ossRule) {
+	public AliossTemplate aliossTemplate(OSSClient ossClient) {
 		return new AliossTemplate(ossClient, ossProperties, ossRule);
 	}
 

+ 65 - 0
blade-core-oss/src/main/java/org/springblade/core/oss/config/MinioConfiguration.java

@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ * <p>
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.gnu.org/licenses/lgpl.html
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.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);
+	}
+
+}

+ 43 - 0
blade-core-oss/src/main/java/org/springblade/core/oss/config/OssConfiguration.java

@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ * <p>
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.gnu.org/licenses/lgpl.html
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.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 {
+
+	@Bean
+	@ConditionalOnMissingBean(OssRule.class)
+	public OssRule ossRule() {
+		return new BladeOssRule();
+	}
+
+}

+ 13 - 16
blade-core-oss/src/main/java/org/springblade/core/oss/config/QiniuConfiguration.java

@@ -15,47 +15,45 @@
  */
 package org.springblade.core.oss.config;
 
-import com.qiniu.common.Zone;
 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.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;
 
 /**
- * Oss配置类
+ * Qiniu配置类
  *
  * @author Chill
  */
-@AutoConfiguration
 @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 OssProperties ossProperties;
+	private final OssProperties ossProperties;
+	private final OssRule ossRule;
 
 	@Bean
-	@ConditionalOnMissingBean(OssRule.class)
-	public OssRule ossRule() {
-		return new BladeOssRule();
-	}
-
-	@Bean
-	public com.qiniu.storage.Configuration qiniuConfiguration() {
-		return new com.qiniu.storage.Configuration(Zone.autoZone());
+	@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());
 	}
@@ -73,11 +71,10 @@ public class QiniuConfiguration {
 	}
 
 	@Bean
+	@ConditionalOnBean({Auth.class, UploadManager.class, BucketManager.class})
 	@ConditionalOnMissingBean(QiniuTemplate.class)
-	@ConditionalOnBean({Auth.class, UploadManager.class, BucketManager.class, OssRule.class})
-	public QiniuTemplate qiniuTemplate(Auth auth, UploadManager uploadManager, BucketManager bucketManager, OssRule ossRule) {
+	public QiniuTemplate qiniuTemplate(Auth auth, UploadManager uploadManager, BucketManager bucketManager) {
 		return new QiniuTemplate(auth, uploadManager, bucketManager, ossProperties, ossRule);
 	}
 
-
 }

+ 54 - 0
blade-core-oss/src/main/java/org/springblade/core/oss/enums/PolicyType.java

@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ * <p>
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.gnu.org/licenses/lgpl.html
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.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;
+
+}

+ 4 - 0
blade-core-oss/src/main/java/org/springblade/core/oss/model/BladeFile.java

@@ -28,6 +28,10 @@ public class BladeFile {
 	 * 文件地址
 	 */
 	private String link;
+	/**
+	 * 域名地址
+	 */
+	private String domain;
 	/**
 	 * 文件名
 	 */

+ 1 - 1
blade-core-oss/src/main/java/org/springblade/core/oss/props/OssProperties.java

@@ -61,7 +61,7 @@ public class OssProperties {
 	/**
 	 * 默认的存储桶名称
 	 */
-	private String bucketName = "bladex";
+	private String bucketName = "blade";
 
 	/**
 	 * 自定义属性

+ 1 - 1
blade-core-report/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>blade-tool</artifactId>
         <groupId>org.springblade</groupId>
-        <version>3.4.1</version>
+        <version>3.5.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
blade-core-secure/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>blade-tool</artifactId>
         <groupId>org.springblade</groupId>
-        <version>3.4.1</version>
+        <version>3.5.0</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>

+ 2 - 1
blade-core-secure/src/main/java/org/springblade/core/secure/config/SecureConfiguration.java

@@ -21,6 +21,7 @@ import org.springblade.core.secure.aspect.AuthAspect;
 import org.springblade.core.secure.interceptor.ClientInterceptor;
 import org.springblade.core.secure.interceptor.SecureInterceptor;
 import org.springblade.core.secure.props.BladeSecureProperties;
+import org.springblade.core.secure.props.BladeTokenProperties;
 import org.springblade.core.secure.provider.ClientDetailsServiceImpl;
 import org.springblade.core.secure.provider.IClientDetailsService;
 import org.springblade.core.secure.registry.SecureRegistry;
@@ -41,7 +42,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 @Order
 @AutoConfiguration
 @AllArgsConstructor
-@EnableConfigurationProperties({BladeSecureProperties.class})
+@EnableConfigurationProperties({BladeSecureProperties.class, BladeTokenProperties.class})
 public class SecureConfiguration implements WebMvcConfigurer {
 
 	private final SecureRegistry secureRegistry;

+ 50 - 0
blade-core-secure/src/main/java/org/springblade/core/secure/props/BladeTokenProperties.java

@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ * <p>
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.gnu.org/licenses/lgpl.html
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.secure.props;
+
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.launch.constant.TokenConstant;
+import org.springblade.core.tool.utils.StringPool;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * secure放行额外配置
+ *
+ * @author Chill
+ */
+@Slf4j
+@Data
+@ConfigurationProperties("blade.token")
+public class BladeTokenProperties {
+
+	/**
+	 * token签名
+	 */
+	private String signKey = StringPool.EMPTY;
+
+	/**
+	 * 获取签名规则
+	 */
+	public String getSignKey() {
+		if (this.signKey.length() < TokenConstant.SIGN_KEY_LENGTH) {
+			log.warn("Token已启用默认签名,请前往blade.token.sign-key设置32位的key");
+			return TokenConstant.SIGN_KEY;
+		}
+		return this.signKey;
+	}
+
+}

+ 25 - 0
blade-core-secure/src/main/java/org/springblade/core/secure/utils/AuthUtil.java

@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ * <p>
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.gnu.org/licenses/lgpl.html
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.secure.utils;
+
+/**
+ * Secure工具类
+ *
+ * @author Chill
+ */
+public class AuthUtil extends SecureUtil{
+
+}

+ 41 - 7
blade-core-secure/src/main/java/org/springblade/core/secure/utils/SecureUtil.java

@@ -25,6 +25,7 @@ import org.springblade.core.secure.BladeUser;
 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.props.BladeTokenProperties;
 import org.springblade.core.secure.provider.IClientDetails;
 import org.springblade.core.secure.provider.IClientDetailsService;
 import org.springblade.core.tool.constant.RoleConstant;
@@ -54,12 +55,45 @@ public class SecureUtil {
 	private final static String TENANT_ID = TokenConstant.TENANT_ID;
 	private final static String CLIENT_ID = TokenConstant.CLIENT_ID;
 	private final static Integer AUTH_LENGTH = TokenConstant.AUTH_LENGTH;
-	private static final String BASE64_SECURITY = Base64.getEncoder().encodeToString(TokenConstant.SIGN_KEY.getBytes(Charsets.UTF_8));
+	private static IClientDetailsService CLIENT_DETAILS_SERVICE;
+	private static BladeTokenProperties TOKEN_PROPERTIES;
+	private static String BASE64_SECURITY;
 
-	private static final IClientDetailsService clientDetailsService;
 
-	static {
-		clientDetailsService = SpringUtil.getBean(IClientDetailsService.class);
+	/**
+	 * 获取客户端服务类
+	 *
+	 * @return clientDetailsService
+	 */
+	private static IClientDetailsService getClientDetailsService() {
+		if (CLIENT_DETAILS_SERVICE == null) {
+			CLIENT_DETAILS_SERVICE = SpringUtil.getBean(IClientDetailsService.class);
+		}
+		return CLIENT_DETAILS_SERVICE;
+	}
+
+	/**
+	 * 获取配置类
+	 *
+	 * @return jwtProperties
+	 */
+	private static BladeTokenProperties getTokenProperties() {
+		if (TOKEN_PROPERTIES == null) {
+			TOKEN_PROPERTIES = SpringUtil.getBean(BladeTokenProperties.class);
+		}
+		return TOKEN_PROPERTIES;
+	}
+
+	/**
+	 * 获取Token签名
+	 *
+	 * @return String
+	 */
+	private static String getBase64Security() {
+		if (BASE64_SECURITY == null) {
+			BASE64_SECURITY = Base64.getEncoder().encodeToString(getTokenProperties().getSignKey().getBytes(Charsets.UTF_8));
+		}
+		return BASE64_SECURITY;
 	}
 
 	/**
@@ -301,7 +335,7 @@ public class SecureUtil {
 	public static Claims parseJWT(String jsonWebToken) {
 		try {
 			return Jwts.parserBuilder()
-				.setSigningKey(Base64.getDecoder().decode(BASE64_SECURITY)).build()
+				.setSigningKey(Base64.getDecoder().decode(getBase64Security())).build()
 				.parseClaimsJws(jsonWebToken).getBody();
 		} catch (Exception ex) {
 			return null;
@@ -338,7 +372,7 @@ public class SecureUtil {
 		Date now = new Date(nowMillis);
 
 		//生成签名密钥
-		byte[] apiKeySecretBytes = Base64.getDecoder().decode(BASE64_SECURITY);
+		byte[] apiKeySecretBytes = Base64.getDecoder().decode(getBase64Security());
 		Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
 
 		//添加构成JWT的类
@@ -434,7 +468,7 @@ public class SecureUtil {
 	 * @return clientDetails
 	 */
 	private static IClientDetails clientDetails(String clientId) {
-		return clientDetailsService.loadClientByClientId(clientId);
+		return getClientDetailsService().loadClientByClientId(clientId);
 	}
 
 	/**

+ 1 - 2
blade-core-social/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>blade-tool</artifactId>
         <groupId>org.springblade</groupId>
-        <version>3.4.1</version>
+        <version>3.5.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -32,5 +32,4 @@
             <artifactId>httpclient</artifactId>
         </dependency>
     </dependencies>
-
 </project>

+ 1 - 1
blade-core-swagger/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>blade-tool</artifactId>
         <groupId>org.springblade</groupId>
-        <version>3.4.1</version>
+        <version>3.5.0</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
blade-core-swagger/src/main/java/org/springblade/core/swagger/SwaggerProperties.java

@@ -55,7 +55,7 @@ public class SwaggerProperties {
 	/**
 	 * 版本
 	 **/
-	private String version = "3.4.1";
+	private String version = "3.5.0";
 	/**
 	 * 许可证
 	 **/

+ 1 - 2
blade-core-test/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>org.springblade</groupId>
         <artifactId>blade-tool</artifactId>
-        <version>3.4.1</version>
+        <version>3.5.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -27,5 +27,4 @@
             <artifactId>spring-boot-starter-test</artifactId>
         </dependency>
     </dependencies>
-
 </project>

+ 1 - 1
blade-core-tool/pom.xml

@@ -6,7 +6,7 @@
     <parent>
         <groupId>org.springblade</groupId>
         <artifactId>blade-tool</artifactId>
-        <version>3.4.1</version>
+        <version>3.5.0</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>

+ 3 - 0
blade-core-tool/src/main/java/org/springblade/core/tool/config/JacksonConfiguration.java

@@ -21,12 +21,14 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationFeature;
 import lombok.AllArgsConstructor;
+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.AutoConfigureBefore;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Primary;
 import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
@@ -45,6 +47,7 @@ import java.util.TimeZone;
 @AllArgsConstructor
 @ConditionalOnClass(ObjectMapper.class)
 @AutoConfigureBefore(JacksonAutoConfiguration.class)
+@EnableConfigurationProperties(BladeJacksonProperties.class)
 public class JacksonConfiguration {
 
 	@Primary

+ 3 - 1
blade-core-tool/src/main/java/org/springblade/core/tool/config/MessageConfiguration.java

@@ -18,6 +18,7 @@ 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.Charsets;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -40,6 +41,7 @@ import java.util.List;
 public class MessageConfiguration implements WebMvcConfigurer {
 
 	private final ObjectMapper objectMapper;
+	private final BladeJacksonProperties properties;
 
 	/**
 	 * 使用 JACKSON 作为JSON MessageConverter
@@ -51,7 +53,7 @@ public class MessageConfiguration implements WebMvcConfigurer {
 		converters.add(new ByteArrayHttpMessageConverter());
 		converters.add(new ResourceHttpMessageConverter());
 		converters.add(new ResourceRegionHttpMessageConverter());
-		converters.add(new MappingApiJackson2HttpMessageConverter(objectMapper));
+		converters.add(new MappingApiJackson2HttpMessageConverter(objectMapper, properties));
 	}
 
 }

+ 3 - 3
blade-core-tool/src/main/java/org/springblade/core/tool/jackson/AbstractReadWriteJackson2HttpMessageConverter.java

@@ -36,8 +36,8 @@ import org.springframework.util.TypeUtils;
 
 import java.io.IOException;
 import java.lang.reflect.Type;
-import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -64,9 +64,9 @@ public abstract class AbstractReadWriteJackson2HttpMessageConverter extends Abst
 		initSsePrettyPrinter();
 	}
 
-	public AbstractReadWriteJackson2HttpMessageConverter(ObjectMapper readObjectMapper, ObjectMapper writeObjectMapper, MediaType... supportedMediaTypes) {
+	public AbstractReadWriteJackson2HttpMessageConverter(ObjectMapper readObjectMapper, ObjectMapper writeObjectMapper, List<MediaType> supportedMediaTypes) {
 		this(readObjectMapper, writeObjectMapper);
-		setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
+		setSupportedMediaTypes(supportedMediaTypes);
 	}
 
 	private void initSsePrettyPrinter() {

+ 37 - 0
blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeJacksonProperties.java

@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
+ * <p>
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.gnu.org/licenses/lgpl.html
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.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 {
+
+	/**
+	 * 支持 MediaType text/plain,用于和 blade-api-crypto 一起使用
+	 */
+	private Boolean supportTextPlain = Boolean.FALSE;
+
+}

+ 123 - 4
blade-core-tool/src/main/java/org/springblade/core/tool/jackson/JsonUtil.java

@@ -24,10 +24,8 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationFeature;
 import lombok.extern.slf4j.Slf4j;
-import org.springblade.core.tool.utils.DateUtil;
-import org.springblade.core.tool.utils.Exceptions;
-import org.springblade.core.tool.utils.StringPool;
-import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.core.tool.utils.*;
+import org.springframework.lang.Nullable;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -268,6 +266,127 @@ public class JsonUtil {
 		}
 	}
 
+
+	/**
+	 * 将json 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);
+		}
+	}
+
+	/**
+	 * 将json反序列化成对象
+	 *
+	 * @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);
+		}
+	}
+
+	/**
+	 * 将json反序列化成对象
+	 *
+	 * @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);
+		}
+	}
+
+	/**
+	 * 将json反序列化成对象
+	 *
+	 * @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);
+		}
+	}
+
+	/**
+	 * 将json反序列化成对象
+	 *
+	 * @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);
+		}
+	}
+
+	/**
+	 * 将json反序列化成对象
+	 *
+	 * @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);
+		}
+	}
+
 	/**
 	 * 将json字符串转成 JsonNode
 	 *

+ 16 - 10
blade-core-tool/src/main/java/org/springblade/core/tool/jackson/MappingApiJackson2HttpMessageConverter.java

@@ -22,6 +22,8 @@ import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
 import org.springframework.lang.Nullable;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * 针对 api 服务对 android 和 ios 和 web 处理的 分读写的 jackson 处理
@@ -38,22 +40,26 @@ public class MappingApiJackson2HttpMessageConverter extends AbstractReadWriteJac
 	@Nullable
 	private String jsonPrefix;
 
-	/**
-	 * Construct a new {@link MappingApiJackson2HttpMessageConverter} using default configuration
-	 * provided by {@link Jackson2ObjectMapperBuilder}.
-	 */
-	public MappingApiJackson2HttpMessageConverter() {
-		this(Jackson2ObjectMapperBuilder.json().build());
-	}
-
 	/**
 	 * Construct a new {@link MappingApiJackson2HttpMessageConverter} with a custom {@link ObjectMapper}.
 	 * You can use {@link Jackson2ObjectMapperBuilder} to build it easily.
+	 *
 	 * @param objectMapper ObjectMapper
 	 * @see Jackson2ObjectMapperBuilder#json()
 	 */
-	public MappingApiJackson2HttpMessageConverter(ObjectMapper objectMapper) {
-		super(objectMapper, initWriteObjectMapper(objectMapper), MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
+	public MappingApiJackson2HttpMessageConverter(ObjectMapper objectMapper, BladeJacksonProperties properties) {
+		super(objectMapper, initWriteObjectMapper(objectMapper), 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) {

+ 9 - 0
blade-core-tool/src/main/java/org/springblade/core/tool/support/Kv.java

@@ -43,6 +43,15 @@ public class Kv extends LinkedCaseInsensitiveMap<Object> {
 		return new Kv();
 	}
 
+	/**
+	 * 创建Kv
+	 *
+	 * @return Kv
+	 */
+	public static Kv create() {
+		return new Kv();
+	}
+
 	public static <K, V> HashMap<K, V> newMap() {
 		return new HashMap<>(16);
 	}

+ 215 - 34
blade-core-tool/src/main/java/org/springblade/core/tool/utils/AesUtil.java

@@ -15,73 +15,257 @@
  */
 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;
 
 /**
- * 完全兼容微信所使用的AES加密方式。
+ * 完全兼容微信所使用的AES加密工具类
  * aes的key必须是256byte长(比如32个字符),可以使用AesKit.genAesKey()来生成一组key
  *
  * @author L.cm
  */
 public class AesUtil {
 
-	private 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, aesTextKey.getBytes(Charsets.UTF_8));
+		return encrypt(content, Objects.requireNonNull(aesTextKey).getBytes(DEFAULT_CHARSET));
 	}
 
-	public static byte[] encrypt(String content, String aesTextKey) {
-		return encrypt(content.getBytes(Charsets.UTF_8), aesTextKey.getBytes(Charsets.UTF_8));
+	/**
+	 * hex加密
+	 *
+	 * @param content    文本内容
+	 * @param aesTextKey 文本密钥
+	 * @return {String}
+	 */
+	public static String encryptToHex(String content, String aesTextKey) {
+		return HexUtil.encodeToString(encrypt(content, aesTextKey));
 	}
 
-	public static byte[] encrypt(String content, java.nio.charset.Charset charset, String aesTextKey) {
-		return encrypt(content.getBytes(charset), aesTextKey.getBytes(Charsets.UTF_8));
+	/**
+	 * hex加密
+	 *
+	 * @param content    内容
+	 * @param aesTextKey 文本密钥
+	 * @return {String}
+	 */
+	public static String encryptToHex(byte[] content, String aesTextKey) {
+		return HexUtil.encodeToString(encrypt(content, aesTextKey));
 	}
 
-	public static byte[] decrypt(byte[] content, String aesTextKey) {
-		return decrypt(content, aesTextKey.getBytes(Charsets.UTF_8));
+	/**
+	 * Base64加密
+	 *
+	 * @param content    文本内容
+	 * @param aesTextKey 文本密钥
+	 * @return {String}
+	 */
+	public static String encryptToBase64(String content, String aesTextKey) {
+		return Base64Util.encodeToString(encrypt(content, aesTextKey));
 	}
 
-	public static String decryptToStr(byte[] content, String aesTextKey) {
-		return new String(decrypt(content, aesTextKey.getBytes(Charsets.UTF_8)), Charsets.UTF_8);
+	/**
+	 * Base64加密
+	 *
+	 * @param content    内容
+	 * @param aesTextKey 文本密钥
+	 * @return {String}
+	 */
+	public static String encryptToBase64(byte[] content, String aesTextKey) {
+		return Base64Util.encodeToString(encrypt(content, aesTextKey));
 	}
 
-	public static String decryptToStr(byte[] content, String aesTextKey, java.nio.charset.Charset charset) {
-		return new String(decrypt(content, aesTextKey.getBytes(Charsets.UTF_8)), charset);
+	/**
+	 * hex解密
+	 *
+	 * @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);
 	}
 
-	public static byte[] encrypt(byte[] content, byte[] aesKey) {
-		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(aesKey, 0, 16);
-			cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
-			return cipher.doFinal(Pkcs7Encoder.encode(content));
-		} catch (Exception e) {
-			throw Exceptions.unchecked(e);
+	/**
+	 * hex解密
+	 *
+	 * @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解密
+	 *
+	 * @param content    内容
+	 * @param aesTextKey 文本密钥
+	 * @return byte[]
+	 */
+	public static byte[] decryptFormHex(byte[] content, String aesTextKey) {
+		return decrypt(HexUtil.decode(content), aesTextKey);
+	}
+
+	/**
+	 * Base64解密
+	 *
+	 * @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解密
+	 *
+	 * @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解密
+	 *
+	 * @param content    内容
+	 * @param aesTextKey 文本密钥
+	 * @return byte[]
+	 */
+	public static byte[] decryptFormBase64(byte[] content, String aesTextKey) {
+		return decrypt(Base64Util.decode(content), aesTextKey);
+	}
+
+	/**
+	 * 解密
+	 *
+	 * @param content    内容
+	 * @param aesTextKey 文本密钥
+	 * @return {String}
+	 */
+	public static String decryptToString(byte[] content, String aesTextKey) {
+		return new String(decrypt(content, aesTextKey), DEFAULT_CHARSET);
+	}
+
+	/**
+	 * 解密
+	 *
+	 * @param content    内容
+	 * @param aesTextKey 文本密钥
+	 * @return byte[]
+	 */
+	public static byte[] decrypt(byte[] content, String aesTextKey) {
+		return decrypt(content, Objects.requireNonNull(aesTextKey).getBytes(DEFAULT_CHARSET));
+	}
+
+	/**
+	 * 解密
+	 *
+	 * @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(Cipher.DECRYPT_MODE, keySpec, iv);
-			return Pkcs7Encoder.decode(cipher.doFinal(encrypted));
+			cipher.init(mode, keySpec, iv);
+			return cipher.doFinal(encrypted);
 		} catch (Exception e) {
 			throw Exceptions.unchecked(e);
 		}
@@ -90,16 +274,13 @@ public class AesUtil {
 	/**
 	 * 提供基于PKCS7算法的加解密接口.
 	 */
-	static class Pkcs7Encoder {
-		static int BLOCK_SIZE = 32;
+	private static class Pkcs7Encoder {
+		private static final int BLOCK_SIZE = 32;
 
-		static byte[] encode(byte[] src) {
+		private static byte[] encode(byte[] src) {
 			int count = src.length;
 			// 计算需要填充的位数
 			int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
-			if (amountToPad == 0) {
-				amountToPad = BLOCK_SIZE;
-			}
 			// 获得补位所用的字符
 			byte pad = (byte) (amountToPad & 0xFF);
 			byte[] pads = new byte[amountToPad];
@@ -113,8 +294,8 @@ public class AesUtil {
 			return dest;
 		}
 
-		static byte[] decode(byte[] decrypted) {
-			int pad = (int) decrypted[decrypted.length - 1];
+		private static byte[] decode(byte[] decrypted) {
+			int pad = decrypted[decrypted.length - 1];
 			if (pad < 1 || pad > BLOCK_SIZE) {
 				pad = 0;
 			}

+ 21 - 1
blade-core-tool/src/main/java/org/springblade/core/tool/utils/ClassUtil.java

@@ -28,7 +28,7 @@ import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 
 /**
- * 类工具
+ * 类操作工具
  *
  * @author L.cm
  */
@@ -106,4 +106,24 @@ public class ClassUtil extends org.springframework.util.ClassUtils {
 		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,可能包含组合注解,故采用spring的工具类
+		Class<?> targetClass = method.getDeclaringClass();
+		return AnnotatedElementUtils.isAnnotated(targetClass, annotationType);
+	}
+
 }

+ 207 - 0
blade-core-tool/src/main/java/org/springblade/core/tool/utils/DesUtil.java

@@ -0,0 +1,207 @@
+/**
+ * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
+ * <p>
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.gnu.org/licenses/lgpl.html
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 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解密
+	 *
+	 * @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解密
+	 *
+	 * @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解密
+	 *
+	 * @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解密
+	 *
+	 * @param data   内容
+	 * @param desKey 密钥
+	 * @return byte array
+	 */
+	public static byte[] decrypt(byte[] data, byte[] desKey) {
+		return des(data, desKey, Cipher.DECRYPT_MODE);
+	}
+
+	/**
+	 * DES解密
+	 *
+	 * @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加密/解密公共方法
+	 *
+	 * @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);
+		}
+	}
+
+}

+ 163 - 0
blade-core-tool/src/main/java/org/springblade/core/tool/utils/HexUtil.java

@@ -0,0 +1,163 @@
+/**
+ * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
+ * <p>
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.gnu.org/licenses/lgpl.html
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.nio.charset.Charset;
+
+/**
+ * hex 工具,编解码全用 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;
+	}
+
+}

+ 38 - 0
blade-core-tool/src/main/java/org/springblade/core/tool/utils/Holder.java

@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
+ * <p>
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.gnu.org/licenses/lgpl.html
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 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();
+}

+ 1 - 1
blade-core-transaction/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>blade-tool</artifactId>
         <groupId>org.springblade</groupId>
-        <version>3.4.1</version>
+        <version>3.5.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 3 - 2
pom.xml

@@ -5,7 +5,7 @@
 
     <groupId>org.springblade</groupId>
     <artifactId>blade-tool</artifactId>
-    <version>3.4.1</version>
+    <version>3.5.0</version>
     <packaging>pom</packaging>
     <name>blade-tool</name>
     <description>
@@ -36,7 +36,7 @@
     </scm>
 
     <properties>
-        <blade.tool.version>3.4.1</blade.tool.version>
+        <blade.tool.version>3.5.0</blade.tool.version>
 
         <java.version>1.8</java.version>
         <maven.plugin.version>3.8.1</maven.plugin.version>
@@ -80,6 +80,7 @@
         <module>blade-core-transaction</module>
         <module>blade-core-report</module>
         <module>blade-core-datascope</module>
+        <module>blade-core-crypto</module>
     </modules>
 
     <dependencyManagement>