diff --git a/blade-core-boot/pom.xml b/blade-core-boot/pom.xml index 061af72..d4a5f37 100644 --- a/blade-core-boot/pom.xml +++ b/blade-core-boot/pom.xml @@ -5,7 +5,7 @@ org.springblade blade-tool - 3.4.1 + 3.5.0 4.0.0 diff --git a/blade-core-boot/src/main/resources/bootstrap.yml b/blade-core-boot/src/main/resources/bootstrap.yml index 57d74a0..12ccbe4 100644 --- a/blade-core-boot/src/main/resources/bootstrap.yml +++ b/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 diff --git a/blade-core-cloud/pom.xml b/blade-core-cloud/pom.xml index 91cc7e2..0139e3e 100644 --- a/blade-core-cloud/pom.xml +++ b/blade-core-cloud/pom.xml @@ -5,7 +5,7 @@ blade-tool org.springblade - 3.4.1 + 3.5.0 4.0.0 diff --git a/blade-core-crypto/pom.xml b/blade-core-crypto/pom.xml new file mode 100644 index 0000000..ef61ae5 --- /dev/null +++ b/blade-core-crypto/pom.xml @@ -0,0 +1,29 @@ + + + + blade-tool + org.springblade + 3.5.0 + + 4.0.0 + + blade-core-crypto + ${project.artifactId} + ${blade.tool.version} + jar + + + + org.springblade + blade-core-tool + ${blade.tool.version} + + + org.springframework.cloud + spring-cloud-context + + + + diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecrypt.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecrypt.java new file mode 100644 index 0000000..6defbd0 --- /dev/null +++ b/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.*; + +/** + *

解密含有{@link org.springframework.web.bind.annotation.RequestBody}注解的参数请求数据,可用于整个控制类或者某个控制器上

+ * + * @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 ""; + +} diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecryptAes.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecryptAes.java new file mode 100644 index 0000000..7dfcbb4 --- /dev/null +++ b/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 ""; + +} diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecryptDes.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/annotation/decrypt/ApiDecryptDes.java new file mode 100644 index 0000000..d2efc7b --- /dev/null +++ b/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 ""; + +} diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncrypt.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncrypt.java new file mode 100644 index 0000000..fb50bd3 --- /dev/null +++ b/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.*; + +/** + *

加密{@link org.springframework.web.bind.annotation.ResponseBody}响应数据,可用于整个控制类或者某个控制器上

+ * + * @author licoy.cn, L.cm + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface ApiEncrypt { + + /** + * 加密类型 + * + * @return 类型 + */ + CryptoType value(); + + /** + * 私钥,用于某些需要单独配置私钥的方法,没有时读取全局配置的私钥 + * + * @return 私钥 + */ + String secretKey() default ""; + +} diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncryptAes.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncryptAes.java new file mode 100644 index 0000000..2b45790 --- /dev/null +++ b/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 ""; + +} diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncryptDes.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/annotation/encrypt/ApiEncryptDes.java new file mode 100644 index 0000000..036de8a --- /dev/null +++ b/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 ""; + +} diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/bean/CryptoInfoBean.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/bean/CryptoInfoBean.java new file mode 100644 index 0000000..e1e6d3f --- /dev/null +++ b/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; + +/** + *

加密注解信息

+ * + * @author licoy.cn, L.cm + */ +@Getter +@RequiredArgsConstructor +public class CryptoInfoBean { + + /** + * 加密类型 + */ + private final CryptoType type; + /** + * 私钥 + */ + private final String secretKey; + +} diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/bean/DecryptHttpInputMessage.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/bean/DecryptHttpInputMessage.java new file mode 100644 index 0000000..8c23fb3 --- /dev/null +++ b/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; + +/** + *

解密信息输入流

+ * + * @author licoy.cn, L.cm + */ +@Getter +@RequiredArgsConstructor +public class DecryptHttpInputMessage implements HttpInputMessage { + private final InputStream body; + private final HttpHeaders headers; +} diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/config/ApiCryptoConfiguration.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/config/ApiCryptoConfiguration.java new file mode 100644 index 0000000..c683f44 --- /dev/null +++ b/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 argumentResolvers) { + argumentResolvers.add(new ApiDecryptParamResolver(apiCryptoProperties)); + } + +} diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/config/ApiCryptoProperties.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/config/ApiCryptoProperties.java new file mode 100644 index 0000000..e548359 --- /dev/null +++ b/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; + +} diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiDecryptParamResolver.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiDecryptParamResolver.java new file mode 100644 index 0000000..a03f80d --- /dev/null +++ b/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). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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()); + } +} diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiDecryptRequestBodyAdvice.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiDecryptRequestBodyAdvice.java new file mode 100644 index 0000000..28075c0 --- /dev/null +++ b/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; + +/** + * 请求数据的加密信息解密处理
+ * 本类只对控制器参数中含有{@link org.springframework.web.bind.annotation.RequestBody} + * 以及package为org.springblade.core.api.signature.annotation.decrypt下的注解有效 + * + * @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> 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> converterType) { + return body; + } + + @NonNull + @Override + public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, @NonNull MethodParameter parameter, + @NonNull Type targetType, @NonNull Class> 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> converterType) { + return body; + } + +} diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiEncryptResponseBodyAdvice.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/core/ApiEncryptResponseBodyAdvice.java new file mode 100644 index 0000000..a491d54 --- /dev/null +++ b/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; + + +/** + * 响应数据的加密处理
+ * 本类只对控制器参数中含有{@link org.springframework.web.bind.annotation.ResponseBody} + * 或者控制类上含有{@link org.springframework.web.bind.annotation.RestController} + * 以及package为org.springblade.core.api.signature.annotation.encrypt下的注解有效 + * + * @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 { + private final ApiCryptoProperties properties; + + @Override + public boolean supports(MethodParameter returnType, @NonNull Class converterType) { + return ClassUtil.isAnnotated(returnType.getMethod(), ApiEncrypt.class); + } + + @Nullable + @Override + public Object beforeBodyWrite(Object body, @NonNull MethodParameter returnType, @NonNull MediaType selectedContentType, + @NonNull Class selectedConverterType, @NonNull ServerHttpRequest request, @NonNull ServerHttpResponse response) { + if (body == null) { + return null; + } + response.getHeaders().setContentType(MediaType.TEXT_PLAIN); + CryptoInfoBean cryptoInfoBean = ApiCryptoUtil.getEncryptInfo(returnType); + if (cryptoInfoBean != null) { + byte[] bodyJsonBytes = JsonUtil.toJsonAsBytes(body); + return ApiCryptoUtil.encryptData(properties, bodyJsonBytes, cryptoInfoBean); + } + throw new EncryptBodyFailException(); + } + +} diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/enums/CryptoType.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/enums/CryptoType.java new file mode 100644 index 0000000..0139c2e --- /dev/null +++ b/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; + +/** + *

加密方式

+ * + * @author licoy.cn, L.cm + */ +public enum CryptoType { + + /** + * des + */ + DES, + + /** + * aes + */ + AES + +} diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/exception/DecryptBodyFailException.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/exception/DecryptBodyFailException.java new file mode 100644 index 0000000..a103dbf --- /dev/null +++ b/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; + +/** + *

解密数据失败异常

+ * + * @author licoy.cn + */ +public class DecryptBodyFailException extends RuntimeException { + + public DecryptBodyFailException(String message) { + super(message); + } +} diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/exception/EncryptBodyFailException.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/exception/EncryptBodyFailException.java new file mode 100644 index 0000000..eb3bf70 --- /dev/null +++ b/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; + +/** + *

加密数据失败异常

+ * + * @author licoy.cn + */ +public class EncryptBodyFailException extends RuntimeException { + + public EncryptBodyFailException() { + super("Encrypted data failed. (加密数据失败)"); + } + + public EncryptBodyFailException(String message) { + super(message); + } +} diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/exception/EncryptMethodNotFoundException.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/exception/EncryptMethodNotFoundException.java new file mode 100644 index 0000000..f148fe8 --- /dev/null +++ b/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; + +/** + *

加密方式未找到或未定义异常

+ * + * @author licoy.cn + */ +public class EncryptMethodNotFoundException extends RuntimeException { + + public EncryptMethodNotFoundException() { + super("Encryption method is not defined. (加密方式未定义)"); + } + +} diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/exception/KeyNotConfiguredException.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/exception/KeyNotConfiguredException.java new file mode 100644 index 0000000..4fc05f5 --- /dev/null +++ b/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; + + +/** + *

未配置KEY运行时异常

+ * + * @author licoy.cn, L.cm + */ +public class KeyNotConfiguredException extends RuntimeException { + + public KeyNotConfiguredException(String message) { + super(message); + } +} diff --git a/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/util/ApiCryptoUtil.java b/blade-core-crypto/src/main/java/org/springblade/core/api/crypto/util/ApiCryptoUtil.java new file mode 100644 index 0000000..bade259 --- /dev/null +++ b/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; + +/** + *

辅助检测工具类

+ * + * @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; + } +} diff --git a/blade-core-datascope/pom.xml b/blade-core-datascope/pom.xml index 8d0e52f..54a4c34 100644 --- a/blade-core-datascope/pom.xml +++ b/blade-core-datascope/pom.xml @@ -5,7 +5,7 @@ blade-tool org.springblade - 3.4.1 + 3.5.0 4.0.0 diff --git a/blade-core-develop/pom.xml b/blade-core-develop/pom.xml index 47c59ab..f7988ea 100644 --- a/blade-core-develop/pom.xml +++ b/blade-core-develop/pom.xml @@ -5,7 +5,7 @@ blade-tool org.springblade - 3.4.1 + 3.5.0 4.0.0 @@ -40,5 +40,4 @@ - diff --git a/blade-core-launch/pom.xml b/blade-core-launch/pom.xml index 527a0fc..b353659 100644 --- a/blade-core-launch/pom.xml +++ b/blade-core-launch/pom.xml @@ -5,7 +5,7 @@ blade-tool org.springblade - 3.4.1 + 3.5.0 4.0.0 diff --git a/blade-core-launch/src/main/java/org/springblade/core/launch/constant/AppConstant.java b/blade-core-launch/src/main/java/org/springblade/core/launch/constant/AppConstant.java index cd09d6c..f255ca6 100644 --- a/blade-core-launch/src/main/java/org/springblade/core/launch/constant/AppConstant.java +++ b/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"; /** * 基础包 diff --git a/blade-core-launch/src/main/java/org/springblade/core/launch/constant/TokenConstant.java b/blade-core-launch/src/main/java/org/springblade/core/launch/constant/TokenConstant.java index 1cb3364..f65910f 100644 --- a/blade-core-launch/src/main/java/org/springblade/core/launch/constant/TokenConstant.java +++ b/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; + } diff --git a/blade-core-loadbalancer/pom.xml b/blade-core-loadbalancer/pom.xml index 49b11cb..62d505b 100644 --- a/blade-core-loadbalancer/pom.xml +++ b/blade-core-loadbalancer/pom.xml @@ -5,7 +5,7 @@ blade-tool org.springblade - 3.4.1 + 3.5.0 4.0.0 diff --git a/blade-core-log/pom.xml b/blade-core-log/pom.xml index fb1bb8a..7c3ea29 100644 --- a/blade-core-log/pom.xml +++ b/blade-core-log/pom.xml @@ -5,7 +5,7 @@ blade-tool org.springblade - 3.4.1 + 3.5.0 4.0.0 @@ -39,5 +39,4 @@ ${mybatis.plus.version} - diff --git a/blade-core-log/src/main/java/org/springblade/core/log/config/BladeErrorMvcAutoConfiguration.java b/blade-core-log/src/main/java/org/springblade/core/log/config/BladeErrorMvcAutoConfiguration.java index 7490d61..fad5f36 100644 --- a/blade-core-log/src/main/java/org/springblade/core/log/config/BladeErrorMvcAutoConfiguration.java +++ b/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 diff --git a/blade-core-log/src/main/java/org/springblade/core/log/config/BladeLogToolAutoConfiguration.java b/blade-core-log/src/main/java/org/springblade/core/log/config/BladeLogToolAutoConfiguration.java index 1627f6b..37cf711 100644 --- a/blade-core-log/src/main/java/org/springblade/core/log/config/BladeLogToolAutoConfiguration.java +++ b/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(); + } + } diff --git a/blade-core-log/src/main/java/org/springblade/core/log/error/BladeErrorAttributes.java b/blade-core-log/src/main/java/org/springblade/core/log/error/BladeErrorAttributes.java index 5e2f7da..a3de7bd 100644 --- a/blade-core-log/src/main/java/org/springblade/core/log/error/BladeErrorAttributes.java +++ b/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 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); } diff --git a/blade-core-log/src/main/java/org/springblade/core/log/error/BladeRestExceptionTranslator.java b/blade-core-log/src/main/java/org/springblade/core/log/error/BladeRestExceptionTranslator.java index 881b881..77294bd 100644 --- a/blade-core-log/src/main/java/org/springblade/core/log/error/BladeRestExceptionTranslator.java +++ b/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())); } diff --git a/blade-core-log/src/main/java/org/springblade/core/log/props/BladeLogProperties.java b/blade-core-log/src/main/java/org/springblade/core/log/props/BladeLogProperties.java new file mode 100644 index 0000000..4fa1583 --- /dev/null +++ b/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). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; +} diff --git a/blade-core-mybatis/pom.xml b/blade-core-mybatis/pom.xml index 8caac07..3f77aa7 100644 --- a/blade-core-mybatis/pom.xml +++ b/blade-core-mybatis/pom.xml @@ -5,7 +5,7 @@ blade-tool org.springblade - 3.4.1 + 3.5.0 4.0.0 diff --git a/blade-core-mybatis/src/main/java/org/springblade/core/mp/intercept/QueryInterceptor.java b/blade-core-mybatis/src/main/java/org/springblade/core/mp/intercept/QueryInterceptor.java index f101a81..db5717f 100644 --- a/blade-core-mybatis/src/main/java/org/springblade/core/mp/intercept/QueryInterceptor.java +++ b/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). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/blade-core-oss/pom.xml b/blade-core-oss/pom.xml index 1020e2f..3772d4b 100644 --- a/blade-core-oss/pom.xml +++ b/blade-core-oss/pom.xml @@ -5,7 +5,7 @@ blade-tool org.springblade - 3.4.1 + 3.5.0 4.0.0 @@ -21,17 +21,23 @@ blade-core-tool ${blade.tool.version} + + + com.aliyun.oss + aliyun-sdk-oss + 3.14.0 + + + + io.minio + minio + 8.3.7 + com.qiniu qiniu-java-sdk - 7.2.18 - - - - com.aliyun.oss - aliyun-sdk-oss - 3.1.0 + 7.9.4 diff --git a/blade-core-oss/src/main/java/org/springblade/core/oss/AliossTemplate.java b/blade-core-oss/src/main/java/org/springblade/core/oss/AliossTemplate.java index 184b8f8..fded6c8 100644 --- a/blade-core-oss/src/main/java/org/springblade/core/oss/AliossTemplate.java +++ b/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 fileNames) { fileNames.forEach(this::removeFile); } + @Override @SneakyThrows public void removeFiles(String bucketName, List fileNames) { fileNames.forEach(fileName -> removeFile(getBucketName(bucketName), fileName)); @@ -227,11 +248,6 @@ public class AliossTemplate { return getUploadToken(bucketName, ossProperties.getArgs().get("expireTime", 3600L)); } - /** - * TODO 上传大小限制、基础路径 - *

- * 获取上传凭证,普通上传 - */ 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()); } diff --git a/blade-core-oss/src/main/java/org/springblade/core/oss/MinioTemplate.java b/blade-core-oss/src/main/java/org/springblade/core/oss/MinioTemplate.java new file mode 100644 index 0000000..c9023cc --- /dev/null +++ b/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). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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 bucketOptional = client.listBuckets().stream().filter(bucket -> bucket.name().equals(getBucketName(bucketName))).findFirst(); + return bucketOptional.orElse(null); + } + + @SneakyThrows + public List 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 fileNames) { + removeFiles(ossProperties.getBucketName(), fileNames); + } + + @Override + @SneakyThrows + public void removeFiles(String bucketName, List fileNames) { + Stream 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()); + } + +} diff --git a/blade-core-oss/src/main/java/org/springblade/core/oss/OssTemplate.java b/blade-core-oss/src/main/java/org/springblade/core/oss/OssTemplate.java new file mode 100644 index 0000000..fbddf7b --- /dev/null +++ b/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). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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 fileNames); + + /** + * 批量删除文件 + * + * @param bucketName 存储桶名称 + * @param fileNames 存储桶对象名称集合 + */ + void removeFiles(String bucketName, List fileNames); + + +} diff --git a/blade-core-oss/src/main/java/org/springblade/core/oss/QiniuTemplate.java b/blade-core-oss/src/main/java/org/springblade/core/oss/QiniuTemplate.java index 9c53efe..57392d9 100644 --- a/blade-core-oss/src/main/java/org/springblade/core/oss/QiniuTemplate.java +++ b/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 fileNames) { fileNames.forEach(this::removeFile); } - + @Override @SneakyThrows public void removeFiles(String bucketName, List 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(); + } + } diff --git a/blade-core-oss/src/main/java/org/springblade/core/oss/config/AliossConfiguration.java b/blade-core-oss/src/main/java/org/springblade/core/oss/config/AliossConfiguration.java index 661d94e..7736fc7 100644 --- a/blade-core-oss/src/main/java/org/springblade/core/oss/config/AliossConfiguration.java +++ b/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); } diff --git a/blade-core-oss/src/main/java/org/springblade/core/oss/config/MinioConfiguration.java b/blade-core-oss/src/main/java/org/springblade/core/oss/config/MinioConfiguration.java new file mode 100644 index 0000000..2ce0757 --- /dev/null +++ b/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). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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); + } + +} diff --git a/blade-core-oss/src/main/java/org/springblade/core/oss/config/OssConfiguration.java b/blade-core-oss/src/main/java/org/springblade/core/oss/config/OssConfiguration.java new file mode 100644 index 0000000..f80edd5 --- /dev/null +++ b/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). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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(); + } + +} diff --git a/blade-core-oss/src/main/java/org/springblade/core/oss/config/QiniuConfiguration.java b/blade-core-oss/src/main/java/org/springblade/core/oss/config/QiniuConfiguration.java index 0d64a9c..44394f9 100644 --- a/blade-core-oss/src/main/java/org/springblade/core/oss/config/QiniuConfiguration.java +++ b/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); } - } diff --git a/blade-core-oss/src/main/java/org/springblade/core/oss/enums/PolicyType.java b/blade-core-oss/src/main/java/org/springblade/core/oss/enums/PolicyType.java new file mode 100644 index 0000000..4d3d44c --- /dev/null +++ b/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). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; + +} diff --git a/blade-core-oss/src/main/java/org/springblade/core/oss/model/BladeFile.java b/blade-core-oss/src/main/java/org/springblade/core/oss/model/BladeFile.java index 3b746c7..77dbfc4 100644 --- a/blade-core-oss/src/main/java/org/springblade/core/oss/model/BladeFile.java +++ b/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; /** * 文件名 */ diff --git a/blade-core-oss/src/main/java/org/springblade/core/oss/props/OssProperties.java b/blade-core-oss/src/main/java/org/springblade/core/oss/props/OssProperties.java index 41a64f8..f02049e 100644 --- a/blade-core-oss/src/main/java/org/springblade/core/oss/props/OssProperties.java +++ b/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"; /** * 自定义属性 diff --git a/blade-core-report/pom.xml b/blade-core-report/pom.xml index 029877b..20f6b64 100644 --- a/blade-core-report/pom.xml +++ b/blade-core-report/pom.xml @@ -5,7 +5,7 @@ blade-tool org.springblade - 3.4.1 + 3.5.0 4.0.0 diff --git a/blade-core-secure/pom.xml b/blade-core-secure/pom.xml index 016a26e..fa278e9 100644 --- a/blade-core-secure/pom.xml +++ b/blade-core-secure/pom.xml @@ -5,7 +5,7 @@ blade-tool org.springblade - 3.4.1 + 3.5.0 4.0.0 diff --git a/blade-core-secure/src/main/java/org/springblade/core/secure/config/SecureConfiguration.java b/blade-core-secure/src/main/java/org/springblade/core/secure/config/SecureConfiguration.java index 2e06e06..be228c8 100644 --- a/blade-core-secure/src/main/java/org/springblade/core/secure/config/SecureConfiguration.java +++ b/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; diff --git a/blade-core-secure/src/main/java/org/springblade/core/secure/props/BladeTokenProperties.java b/blade-core-secure/src/main/java/org/springblade/core/secure/props/BladeTokenProperties.java new file mode 100644 index 0000000..c2104bc --- /dev/null +++ b/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). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; + } + +} diff --git a/blade-core-secure/src/main/java/org/springblade/core/secure/utils/AuthUtil.java b/blade-core-secure/src/main/java/org/springblade/core/secure/utils/AuthUtil.java new file mode 100644 index 0000000..fe7205f --- /dev/null +++ b/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). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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{ + +} diff --git a/blade-core-secure/src/main/java/org/springblade/core/secure/utils/SecureUtil.java b/blade-core-secure/src/main/java/org/springblade/core/secure/utils/SecureUtil.java index e904e60..053bdc2 100644 --- a/blade-core-secure/src/main/java/org/springblade/core/secure/utils/SecureUtil.java +++ b/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); } /** diff --git a/blade-core-social/pom.xml b/blade-core-social/pom.xml index 2072104..bf1637f 100644 --- a/blade-core-social/pom.xml +++ b/blade-core-social/pom.xml @@ -5,7 +5,7 @@ blade-tool org.springblade - 3.4.1 + 3.5.0 4.0.0 @@ -32,5 +32,4 @@ httpclient - diff --git a/blade-core-swagger/pom.xml b/blade-core-swagger/pom.xml index 7f50c3d..814dc05 100644 --- a/blade-core-swagger/pom.xml +++ b/blade-core-swagger/pom.xml @@ -5,7 +5,7 @@ blade-tool org.springblade - 3.4.1 + 3.5.0 4.0.0 diff --git a/blade-core-swagger/src/main/java/org/springblade/core/swagger/SwaggerProperties.java b/blade-core-swagger/src/main/java/org/springblade/core/swagger/SwaggerProperties.java index 30bf3cf..25042fc 100644 --- a/blade-core-swagger/src/main/java/org/springblade/core/swagger/SwaggerProperties.java +++ b/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"; /** * 许可证 **/ diff --git a/blade-core-test/pom.xml b/blade-core-test/pom.xml index 4e06412..792ca47 100644 --- a/blade-core-test/pom.xml +++ b/blade-core-test/pom.xml @@ -5,7 +5,7 @@ org.springblade blade-tool - 3.4.1 + 3.5.0 4.0.0 @@ -27,5 +27,4 @@ spring-boot-starter-test - diff --git a/blade-core-tool/pom.xml b/blade-core-tool/pom.xml index ad76442..f6c4960 100644 --- a/blade-core-tool/pom.xml +++ b/blade-core-tool/pom.xml @@ -6,7 +6,7 @@ org.springblade blade-tool - 3.4.1 + 3.5.0 4.0.0 diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/config/JacksonConfiguration.java b/blade-core-tool/src/main/java/org/springblade/core/tool/config/JacksonConfiguration.java index 50386ae..e439b79 100644 --- a/blade-core-tool/src/main/java/org/springblade/core/tool/config/JacksonConfiguration.java +++ b/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 diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/config/MessageConfiguration.java b/blade-core-tool/src/main/java/org/springblade/core/tool/config/MessageConfiguration.java index 1997103..17e03ef 100644 --- a/blade-core-tool/src/main/java/org/springblade/core/tool/config/MessageConfiguration.java +++ b/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)); } } diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/AbstractReadWriteJackson2HttpMessageConverter.java b/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/AbstractReadWriteJackson2HttpMessageConverter.java index f41a151..47c286a 100644 --- a/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/AbstractReadWriteJackson2HttpMessageConverter.java +++ b/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 supportedMediaTypes) { this(readObjectMapper, writeObjectMapper); - setSupportedMediaTypes(Arrays.asList(supportedMediaTypes)); + setSupportedMediaTypes(supportedMediaTypes); } private void initSsePrettyPrinter() { diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeJacksonProperties.java b/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeJacksonProperties.java new file mode 100644 index 0000000..9b65bda --- /dev/null +++ b/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). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; + +} diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/JsonUtil.java b/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/JsonUtil.java index 8bdac8d..27da19c 100644 --- a/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/JsonUtil.java +++ b/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 泛型标记 + * @return Bean + */ + @Nullable + public static T readValue(@Nullable byte[] content, Class 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 泛型标记 + * @return Bean + */ + @Nullable + public static T readValue(@Nullable String jsonString, Class 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 泛型标记 + * @return Bean + */ + @Nullable + public static T readValue(@Nullable InputStream in, Class 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 泛型标记 + * @return Bean + */ + @Nullable + public static T readValue(@Nullable byte[] content, TypeReference 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 泛型标记 + * @return Bean + */ + @Nullable + public static T readValue(@Nullable String jsonString, TypeReference 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 泛型标记 + * @return Bean + */ + @Nullable + public static T readValue(@Nullable InputStream in, TypeReference typeReference) { + if (in == null) { + return null; + } + try { + return getInstance().readValue(in, typeReference); + } catch (IOException e) { + throw Exceptions.unchecked(e); + } + } + /** * 将json字符串转成 JsonNode * diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/MappingApiJackson2HttpMessageConverter.java b/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/MappingApiJackson2HttpMessageConverter.java index 9eb0e81..3c19cc9 100644 --- a/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/MappingApiJackson2HttpMessageConverter.java +++ b/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 initMediaType(BladeJacksonProperties properties) { + List 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) { diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/support/Kv.java b/blade-core-tool/src/main/java/org/springblade/core/tool/support/Kv.java index cbd6a53..1417960 100644 --- a/blade-core-tool/src/main/java/org/springblade/core/tool/support/Kv.java +++ b/blade-core-tool/src/main/java/org/springblade/core/tool/support/Kv.java @@ -43,6 +43,15 @@ public class Kv extends LinkedCaseInsensitiveMap { return new Kv(); } + /** + * 创建Kv + * + * @return Kv + */ + public static Kv create() { + return new Kv(); + } + public static HashMap newMap() { return new HashMap<>(16); } diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/utils/AesUtil.java b/blade-core-tool/src/main/java/org/springblade/core/tool/utils/AesUtil.java index cf8116f..82d1e55 100644 --- a/blade-core-tool/src/main/java/org/springblade/core/tool/utils/AesUtil.java +++ b/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); } - public static byte[] encrypt(byte[] content, String aesTextKey) { - return encrypt(content, aesTextKey.getBytes(Charsets.UTF_8)); - } - + /** + * 加密 + * + * @param content 文本内容 + * @param aesTextKey 文本密钥 + * @return byte[] + */ public static byte[] encrypt(String content, String aesTextKey) { - return encrypt(content.getBytes(Charsets.UTF_8), aesTextKey.getBytes(Charsets.UTF_8)); + return encrypt(content.getBytes(DEFAULT_CHARSET), aesTextKey); } - public static byte[] encrypt(String content, java.nio.charset.Charset charset, String aesTextKey) { - return encrypt(content.getBytes(charset), aesTextKey.getBytes(Charsets.UTF_8)); + /** + * 加密 + * + * @param content 文本内容 + * @param charset 编码 + * @param aesTextKey 文本密钥 + * @return byte[] + */ + public static byte[] encrypt(String content, Charset charset, String aesTextKey) { + return encrypt(content.getBytes(charset), aesTextKey); } - public static byte[] decrypt(byte[] content, String aesTextKey) { - return decrypt(content, aesTextKey.getBytes(Charsets.UTF_8)); + /** + * 加密 + * + * @param content 内容 + * @param aesTextKey 文本密钥 + * @return byte[] + */ + public static byte[] encrypt(byte[] content, String aesTextKey) { + return encrypt(content, Objects.requireNonNull(aesTextKey).getBytes(DEFAULT_CHARSET)); } - public static String decryptToStr(byte[] content, String aesTextKey) { - return new String(decrypt(content, aesTextKey.getBytes(Charsets.UTF_8)), 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 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} + */ + public static String encryptToHex(byte[] content, String aesTextKey) { + return HexUtil.encodeToString(encrypt(content, aesTextKey)); } - 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); + /** + * Base64加密 + * + * @param content 文本内容 + * @param aesTextKey 文本密钥 + * @return {String} + */ + public static String encryptToBase64(String content, String aesTextKey) { + return Base64Util.encodeToString(encrypt(content, aesTextKey)); + } + + /** + * Base64加密 + * + * @param content 内容 + * @param aesTextKey 文本密钥 + * @return {String} + */ + public static String encryptToBase64(byte[] content, String aesTextKey) { + return Base64Util.encodeToString(encrypt(content, aesTextKey)); + } + + /** + * hex解密 + * + * @param content 文本内容 + * @param aesTextKey 文本密钥 + * @return {String} + */ + @Nullable + public static String decryptFormHexToString(@Nullable String content, String aesTextKey) { + byte[] hexBytes = decryptFormHex(content, aesTextKey); + if (hexBytes == null) { + return null; } + return new String(hexBytes, DEFAULT_CHARSET); } + /** + * hex解密 + * + * @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; } diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ClassUtil.java b/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ClassUtil.java index c08eea9..1234c0e 100644 --- a/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ClassUtil.java +++ b/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 泛型标记 + * @return {boolean} + */ + public static boolean isAnnotated(Method method, Class annotationType) { + // 先找方法,再找方法上的类 + boolean isMethodAnnotated = AnnotatedElementUtils.isAnnotated(method, annotationType); + if (isMethodAnnotated) { + return true; + } + // 获取类上面的Annotation,可能包含组合注解,故采用spring的工具类 + Class targetClass = method.getDeclaringClass(); + return AnnotatedElementUtils.isAnnotated(targetClass, annotationType); + } + } diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DesUtil.java b/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DesUtil.java new file mode 100644 index 0000000..5e826a0 --- /dev/null +++ b/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). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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); + } + } + +} diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/utils/HexUtil.java b/blade-core-tool/src/main/java/org/springblade/core/tool/utils/HexUtil.java new file mode 100644 index 0000000..c2b3c4a --- /dev/null +++ b/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). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; + } + +} diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Holder.java b/blade-core-tool/src/main/java/org/springblade/core/tool/utils/Holder.java new file mode 100644 index 0000000..fcb8d22 --- /dev/null +++ b/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). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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(); +} diff --git a/blade-core-transaction/pom.xml b/blade-core-transaction/pom.xml index b6e089f..353deab 100644 --- a/blade-core-transaction/pom.xml +++ b/blade-core-transaction/pom.xml @@ -5,7 +5,7 @@ blade-tool org.springblade - 3.4.1 + 3.5.0 4.0.0 diff --git a/pom.xml b/pom.xml index 53a0339..8634417 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springblade blade-tool - 3.4.1 + 3.5.0 pom blade-tool @@ -36,7 +36,7 @@ - 3.4.1 + 3.5.0 1.8 3.8.1 @@ -80,6 +80,7 @@ blade-core-transaction blade-core-report blade-core-datascope + blade-core-crypto