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

This commit is contained in:
smallchill 2022-10-13 00:43:30 +08:00
parent 1700db64c5
commit d178675c53
74 changed files with 2728 additions and 217 deletions

View File

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

View File

@ -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

View File

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

29
blade-core-crypto/pom.xml Normal file
View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>3.5.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blade-core-crypto</artifactId>
<name>${project.artifactId}</name>
<version>${blade.tool.version}</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-tool</artifactId>
<version>${blade.tool.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,32 @@
package org.springblade.core.api.crypto.annotation.decrypt;
import org.springblade.core.api.crypto.enums.CryptoType;
import java.lang.annotation.*;
/**
* <p>解密含有{@link org.springframework.web.bind.annotation.RequestBody}注解的参数请求数据可用于整个控制类或者某个控制器上</p>
*
* @author licoy.cn, L.cm
*/
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ApiDecrypt {
/**
* 解密类型
*
* @return 类型
*/
CryptoType value();
/**
* 私钥用于某些需要单独配置私钥的方法没有时读取全局配置的私钥
*
* @return 私钥
*/
String secretKey() default "";
}

View File

@ -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 "";
}

View File

@ -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 "";
}

View File

@ -0,0 +1,33 @@
package org.springblade.core.api.crypto.annotation.encrypt;
import org.springblade.core.api.crypto.enums.CryptoType;
import java.lang.annotation.*;
/**
* <p>加密{@link org.springframework.web.bind.annotation.ResponseBody}响应数据可用于整个控制类或者某个控制器上</p>
*
* @author licoy.cn, L.cm
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ApiEncrypt {
/**
* 加密类型
*
* @return 类型
*/
CryptoType value();
/**
* 私钥用于某些需要单独配置私钥的方法没有时读取全局配置的私钥
*
* @return 私钥
*/
String secretKey() default "";
}

View File

@ -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 "";
}

View File

@ -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 "";
}

View File

@ -0,0 +1,25 @@
package org.springblade.core.api.crypto.bean;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springblade.core.api.crypto.enums.CryptoType;
/**
* <p>加密注解信息</p>
*
* @author licoy.cn, L.cm
*/
@Getter
@RequiredArgsConstructor
public class CryptoInfoBean {
/**
* 加密类型
*/
private final CryptoType type;
/**
* 私钥
*/
private final String secretKey;
}

View File

@ -0,0 +1,20 @@
package org.springblade.core.api.crypto.bean;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import java.io.InputStream;
/**
* <p>解密信息输入流</p>
*
* @author licoy.cn, L.cm
*/
@Getter
@RequiredArgsConstructor
public class DecryptHttpInputMessage implements HttpInputMessage {
private final InputStream body;
private final HttpHeaders headers;
}

View File

@ -0,0 +1,30 @@
package org.springblade.core.api.crypto.config;
import lombok.RequiredArgsConstructor;
import org.springblade.core.api.crypto.core.ApiDecryptParamResolver;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/**
* api 签名自动配置
*
* @author L.cm
*/
@AutoConfiguration
@RequiredArgsConstructor
@EnableConfigurationProperties(ApiCryptoProperties.class)
@ConditionalOnProperty(value = ApiCryptoProperties.PREFIX + ".enabled", havingValue = "true", matchIfMissing = true)
public class ApiCryptoConfiguration implements WebMvcConfigurer {
private final ApiCryptoProperties apiCryptoProperties;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new ApiDecryptParamResolver(apiCryptoProperties));
}
}

View File

@ -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;
}

View File

@ -0,0 +1,66 @@
/**
* Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.api.crypto.core;
import lombok.RequiredArgsConstructor;
import org.springblade.core.api.crypto.annotation.decrypt.ApiDecrypt;
import org.springblade.core.api.crypto.bean.CryptoInfoBean;
import org.springblade.core.api.crypto.config.ApiCryptoProperties;
import org.springblade.core.api.crypto.util.ApiCryptoUtil;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.Charsets;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import java.lang.reflect.Parameter;
/**
* param 参数 解析
*
* @author L.cm
*/
@RequiredArgsConstructor
public class ApiDecryptParamResolver implements HandlerMethodArgumentResolver {
private final ApiCryptoProperties properties;
@Override
public boolean supportsParameter(MethodParameter parameter) {
return AnnotatedElementUtils.hasAnnotation(parameter.getParameter(), ApiDecrypt.class);
}
@Nullable
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
Parameter parameter = methodParameter.getParameter();
ApiDecrypt apiDecrypt = AnnotatedElementUtils.getMergedAnnotation(parameter, ApiDecrypt.class);
String text = webRequest.getParameter(properties.getParamName());
if (StringUtil.isBlank(text)) {
return null;
}
CryptoInfoBean infoBean = new CryptoInfoBean(apiDecrypt.value(), apiDecrypt.secretKey());
byte[] textBytes = text.getBytes(Charsets.UTF_8);
byte[] decryptData = ApiCryptoUtil.decryptData(properties, textBytes, infoBean);
return JsonUtil.readValue(decryptData, parameter.getType());
}
}

View File

@ -0,0 +1,87 @@
package org.springblade.core.api.crypto.core;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.api.crypto.annotation.decrypt.ApiDecrypt;
import org.springblade.core.api.crypto.bean.CryptoInfoBean;
import org.springblade.core.api.crypto.bean.DecryptHttpInputMessage;
import org.springblade.core.api.crypto.config.ApiCryptoProperties;
import org.springblade.core.api.crypto.exception.DecryptBodyFailException;
import org.springblade.core.api.crypto.util.ApiCryptoUtil;
import org.springblade.core.tool.utils.ClassUtil;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.NonNull;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
/**
* 请求数据的加密信息解密处理<br>
* 本类只对控制器参数中含有<strong>{@link org.springframework.web.bind.annotation.RequestBody}</strong>
* 以及package为<strong><code>org.springblade.core.api.signature.annotation.decrypt</code></strong>下的注解有效
*
* @author licoy.cn, L.cm
* @see RequestBodyAdvice
*/
@Slf4j
@Order(1)
@AutoConfiguration
@ControllerAdvice
@RequiredArgsConstructor
@ConditionalOnProperty(value = ApiCryptoProperties.PREFIX + ".enabled", havingValue = "true", matchIfMissing = true)
public class ApiDecryptRequestBodyAdvice implements RequestBodyAdvice {
private final ApiCryptoProperties properties;
@Override
public boolean supports(MethodParameter methodParameter, @NonNull Type targetType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
return ClassUtil.isAnnotated(methodParameter.getMethod(), ApiDecrypt.class);
}
@Override
public Object handleEmptyBody(Object body, @NonNull HttpInputMessage inputMessage, @NonNull MethodParameter parameter,
@NonNull Type targetType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
@NonNull
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, @NonNull MethodParameter parameter,
@NonNull Type targetType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
// 判断 body 是否为空
InputStream messageBody = inputMessage.getBody();
if (messageBody.available() <= 0) {
return inputMessage;
}
byte[] decryptedBody = null;
CryptoInfoBean cryptoInfoBean = ApiCryptoUtil.getDecryptInfo(parameter);
if (cryptoInfoBean != null) {
// base64 byte array
byte[] bodyByteArray = StreamUtils.copyToByteArray(messageBody);
decryptedBody = ApiCryptoUtil.decryptData(properties, bodyByteArray, cryptoInfoBean);
}
if (decryptedBody == null) {
throw new DecryptBodyFailException("Decryption error, " +
"please check if the selected source data is encrypted correctly." +
" (解密错误,请检查选择的源数据的加密方式是否正确。)");
}
InputStream inputStream = new ByteArrayInputStream(decryptedBody);
return new DecryptHttpInputMessage(inputStream, inputMessage.getHeaders());
}
@NonNull
@Override
public Object afterBodyRead(@NonNull Object body, @NonNull HttpInputMessage inputMessage, @NonNull MethodParameter parameter, @NonNull Type targetType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}

View File

@ -0,0 +1,64 @@
package org.springblade.core.api.crypto.core;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.api.crypto.annotation.encrypt.ApiEncrypt;
import org.springblade.core.api.crypto.bean.CryptoInfoBean;
import org.springblade.core.api.crypto.config.ApiCryptoProperties;
import org.springblade.core.api.crypto.exception.EncryptBodyFailException;
import org.springblade.core.api.crypto.util.ApiCryptoUtil;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.ClassUtil;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* 响应数据的加密处理<br>
* 本类只对控制器参数中含有<strong>{@link org.springframework.web.bind.annotation.ResponseBody}</strong>
* 或者控制类上含有<strong>{@link org.springframework.web.bind.annotation.RestController}</strong>
* 以及package为<strong><code>org.springblade.core.api.signature.annotation.encrypt</code></strong>下的注解有效
*
* @author licoy.cn, L.cm
* @see ResponseBodyAdvice
*/
@Slf4j
@Order(1)
@AutoConfiguration
@ControllerAdvice
@RequiredArgsConstructor
@ConditionalOnProperty(value = ApiCryptoProperties.PREFIX + ".enabled", havingValue = "true", matchIfMissing = true)
public class ApiEncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
private final ApiCryptoProperties properties;
@Override
public boolean supports(MethodParameter returnType, @NonNull Class converterType) {
return ClassUtil.isAnnotated(returnType.getMethod(), ApiEncrypt.class);
}
@Nullable
@Override
public Object beforeBodyWrite(Object body, @NonNull MethodParameter returnType, @NonNull MediaType selectedContentType,
@NonNull Class selectedConverterType, @NonNull ServerHttpRequest request, @NonNull ServerHttpResponse response) {
if (body == null) {
return null;
}
response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
CryptoInfoBean cryptoInfoBean = ApiCryptoUtil.getEncryptInfo(returnType);
if (cryptoInfoBean != null) {
byte[] bodyJsonBytes = JsonUtil.toJsonAsBytes(body);
return ApiCryptoUtil.encryptData(properties, bodyJsonBytes, cryptoInfoBean);
}
throw new EncryptBodyFailException();
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,14 @@
package org.springblade.core.api.crypto.exception;
/**
* <p>加密方式未找到或未定义异常</p>
*
* @author licoy.cn
*/
public class EncryptMethodNotFoundException extends RuntimeException {
public EncryptMethodNotFoundException() {
super("Encryption method is not defined. (加密方式未定义)");
}
}

View File

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

View File

@ -0,0 +1,117 @@
package org.springblade.core.api.crypto.util;
import org.springblade.core.api.crypto.annotation.decrypt.ApiDecrypt;
import org.springblade.core.api.crypto.annotation.encrypt.ApiEncrypt;
import org.springblade.core.api.crypto.bean.CryptoInfoBean;
import org.springblade.core.api.crypto.config.ApiCryptoProperties;
import org.springblade.core.api.crypto.enums.CryptoType;
import org.springblade.core.api.crypto.exception.EncryptBodyFailException;
import org.springblade.core.api.crypto.exception.EncryptMethodNotFoundException;
import org.springblade.core.api.crypto.exception.KeyNotConfiguredException;
import org.springblade.core.tool.utils.AesUtil;
import org.springblade.core.tool.utils.ClassUtil;
import org.springblade.core.tool.utils.DesUtil;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
/**
* <p>辅助检测工具类</p>
*
* @author licoy.cn, L.cm
*/
public class ApiCryptoUtil {
/**
* 获取方法控制器上的加密注解信息
*
* @param methodParameter 控制器方法
* @return 加密注解信息
*/
@Nullable
public static CryptoInfoBean getEncryptInfo(MethodParameter methodParameter) {
ApiEncrypt encryptBody = ClassUtil.getAnnotation(methodParameter.getMethod(), ApiEncrypt.class);
if (encryptBody == null) {
return null;
}
return new CryptoInfoBean(encryptBody.value(), encryptBody.secretKey());
}
/**
* 获取方法控制器上的解密注解信息
*
* @param methodParameter 控制器方法
* @return 加密注解信息
*/
@Nullable
public static CryptoInfoBean getDecryptInfo(MethodParameter methodParameter) {
ApiDecrypt decryptBody = ClassUtil.getAnnotation(methodParameter.getMethod(), ApiDecrypt.class);
if (decryptBody == null) {
return null;
}
return new CryptoInfoBean(decryptBody.value(), decryptBody.secretKey());
}
/**
* 选择加密方式并进行加密
*
* @param jsonData json 数据
* @param infoBean 加密信息
* @return 加密结果
*/
public static String encryptData(ApiCryptoProperties properties, byte[] jsonData, CryptoInfoBean infoBean) {
CryptoType type = infoBean.getType();
if (type == null) {
throw new EncryptMethodNotFoundException();
}
String secretKey = infoBean.getSecretKey();
if (type == CryptoType.DES) {
secretKey = ApiCryptoUtil.checkSecretKey(properties.getDesKey(), secretKey, "DES");
return DesUtil.encryptToBase64(jsonData, secretKey);
}
if (type == CryptoType.AES) {
secretKey = ApiCryptoUtil.checkSecretKey(properties.getAesKey(), secretKey, "AES");
return AesUtil.encryptToBase64(jsonData, secretKey);
}
throw new EncryptBodyFailException();
}
/**
* 选择加密方式并进行解密
*
* @param bodyData byte array
* @param infoBean 加密信息
* @return 解密结果
*/
public static byte[] decryptData(ApiCryptoProperties properties, byte[] bodyData, CryptoInfoBean infoBean) {
CryptoType type = infoBean.getType();
if (type == null) {
throw new EncryptMethodNotFoundException();
}
String secretKey = infoBean.getSecretKey();
if (type == CryptoType.AES) {
secretKey = ApiCryptoUtil.checkSecretKey(properties.getAesKey(), secretKey, "AES");
return AesUtil.decryptFormBase64(bodyData, secretKey);
}
if (type == CryptoType.DES) {
secretKey = ApiCryptoUtil.checkSecretKey(properties.getDesKey(), secretKey, "DES");
return DesUtil.decryptFormBase64(bodyData, secretKey);
}
throw new EncryptMethodNotFoundException();
}
/**
* 检验私钥
*
* @param k1 配置的私钥
* @param k2 注解上的私钥
* @param keyName key名称
* @return 私钥
*/
private static String checkSecretKey(String k1, String k2, String keyName) {
if (StringUtil.isBlank(k1) && StringUtil.isBlank(k2)) {
throw new KeyNotConfiguredException(String.format("%s key is not configured (未配置%s)", keyName, keyName));
}
return StringUtil.isBlank(k2) ? k1 : k2;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
}

View File

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

View File

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

View File

@ -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

View File

@ -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();
}
}

View File

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

View File

@ -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()));
}

View File

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

View File

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

View File

@ -1,18 +1,17 @@
/*
* Copyright (c) 2018-2028, DreamLu All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the dreamlu.net developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: DreamLu 卢春梦 (596392912@qq.com)
/**
* Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.mp.intercept;

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>3.4.1</version>
<version>3.5.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -21,17 +21,23 @@
<artifactId>blade-core-tool</artifactId>
<version>${blade.tool.version}</version>
</dependency>
<!--Aliyun-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.14.0</version>
</dependency>
<!--MinIO-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.3.7</version>
</dependency>
<!--QiNiu-->
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>7.2.18</version>
</dependency>
<!--alioss-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.1.0</version>
<version>7.9.4</version>
</dependency>
</dependencies>

View File

@ -45,11 +45,12 @@ import java.util.Map;
* @author Chill
*/
@AllArgsConstructor
public class AliossTemplate {
private OSSClient ossClient;
private OssProperties ossProperties;
private OssRule ossRule;
public class AliossTemplate implements OssTemplate {
private final OSSClient ossClient;
private final OssProperties ossProperties;
private final OssRule ossRule;
@Override
@SneakyThrows
public void makeBucket(String bucketName) {
if (!bucketExists(bucketName)) {
@ -57,31 +58,37 @@ public class AliossTemplate {
}
}
@Override
@SneakyThrows
public void removeBucket(String bucketName) {
ossClient.deleteBucket(getBucketName(bucketName));
}
@Override
@SneakyThrows
public boolean bucketExists(String bucketName) {
return ossClient.doesBucketExist(getBucketName(bucketName));
}
@Override
@SneakyThrows
public void copyFile(String bucketName, String fileName, String destBucketName) {
ossClient.copyObject(getBucketName(bucketName), fileName, getBucketName(destBucketName), fileName);
}
@Override
@SneakyThrows
public void copyFile(String bucketName, String fileName, String destBucketName, String destFileName) {
ossClient.copyObject(getBucketName(bucketName), fileName, getBucketName(destBucketName), destFileName);
}
@Override
@SneakyThrows
public OssFile statFile(String fileName) {
return statFile(ossProperties.getBucketName(), fileName);
}
@Override
@SneakyThrows
public OssFile statFile(String bucketName, String fileName) {
ObjectMetadata stat = ossClient.getObjectMetadata(getBucketName(bucketName), fileName);
@ -95,46 +102,55 @@ public class AliossTemplate {
return ossFile;
}
@Override
@SneakyThrows
public String filePath(String fileName) {
return getOssHost().concat(StringPool.SLASH).concat(fileName);
}
@Override
@SneakyThrows
public String filePath(String bucketName, String fileName) {
return getOssHost(bucketName).concat(StringPool.SLASH).concat(fileName);
}
@Override
@SneakyThrows
public String fileLink(String fileName) {
return getOssHost().concat(StringPool.SLASH).concat(fileName);
}
@Override
@SneakyThrows
public String fileLink(String bucketName, String fileName) {
return getOssHost(bucketName).concat(StringPool.SLASH).concat(fileName);
}
@Override
@SneakyThrows
public BladeFile putFile(MultipartFile file) {
return putFile(ossProperties.getBucketName(), file.getOriginalFilename(), file);
}
@Override
@SneakyThrows
public BladeFile putFile(String fileName, MultipartFile file) {
return putFile(ossProperties.getBucketName(), fileName, file);
}
@Override
@SneakyThrows
public BladeFile putFile(String bucketName, String fileName, MultipartFile file) {
return putFile(bucketName, fileName, file.getInputStream());
}
@Override
@SneakyThrows
public BladeFile putFile(String fileName, InputStream stream) {
return putFile(ossProperties.getBucketName(), fileName, stream);
}
@Override
@SneakyThrows
public BladeFile putFile(String bucketName, String fileName, InputStream stream) {
return put(bucketName, stream, fileName, false);
@ -160,25 +176,30 @@ public class AliossTemplate {
BladeFile file = new BladeFile();
file.setOriginalName(originalName);
file.setName(key);
file.setDomain(getOssHost(bucketName));
file.setLink(fileLink(bucketName, key));
return file;
}
@Override
@SneakyThrows
public void removeFile(String fileName) {
ossClient.deleteObject(getBucketName(), fileName);
}
@Override
@SneakyThrows
public void removeFile(String bucketName, String fileName) {
ossClient.deleteObject(getBucketName(bucketName), fileName);
}
@Override
@SneakyThrows
public void removeFiles(List<String> fileNames) {
fileNames.forEach(this::removeFile);
}
@Override
@SneakyThrows
public void removeFiles(String bucketName, List<String> fileNames) {
fileNames.forEach(fileName -> removeFile(getBucketName(bucketName), fileName));
@ -227,11 +248,6 @@ public class AliossTemplate {
return getUploadToken(bucketName, ossProperties.getArgs().get("expireTime", 3600L));
}
/**
* TODO 上传大小限制基础路径
* <p>
* 获取上传凭证普通上传
*/
public String getUploadToken(String bucketName, long expireTime) {
String baseDir = "upload";
@ -258,11 +274,22 @@ public class AliossTemplate {
return JsonUtil.toJson(respMap);
}
/**
* 获取域名
*
* @param bucketName 存储桶名称
* @return String
*/
public String getOssHost(String bucketName) {
String prefix = ossProperties.getEndpoint().contains("https://") ? "https://" : "http://";
return prefix + getBucketName(bucketName) + StringPool.DOT + ossProperties.getEndpoint().replaceFirst(prefix, StringPool.EMPTY);
}
/**
* 获取域名
*
* @return String
*/
public String getOssHost() {
return getOssHost(ossProperties.getBucketName());
}

View File

@ -0,0 +1,417 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.oss;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteObject;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springblade.core.oss.enums.PolicyType;
import org.springblade.core.oss.model.BladeFile;
import org.springblade.core.oss.model.OssFile;
import org.springblade.core.oss.props.OssProperties;
import org.springblade.core.oss.rule.OssRule;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringPool;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
/**
* MinIOTemplate
*
* @author Chill
*/
@AllArgsConstructor
public class MinioTemplate implements OssTemplate {
/**
* MinIO客户端
*/
private final MinioClient client;
/**
* 存储桶命名规则
*/
private final OssRule ossRule;
/**
* 配置类
*/
private final OssProperties ossProperties;
@Override
@SneakyThrows
public void makeBucket(String bucketName) {
if (
!client.bucketExists(
BucketExistsArgs.builder().bucket(getBucketName(bucketName)).build()
)
) {
client.makeBucket(
MakeBucketArgs.builder().bucket(getBucketName(bucketName)).build()
);
client.setBucketPolicy(
SetBucketPolicyArgs.builder().bucket(getBucketName(bucketName)).config(getPolicyType(getBucketName(bucketName), PolicyType.READ)).build()
);
}
}
@SneakyThrows
public Bucket getBucket() {
return getBucket(getBucketName());
}
@SneakyThrows
public Bucket getBucket(String bucketName) {
Optional<Bucket> bucketOptional = client.listBuckets().stream().filter(bucket -> bucket.name().equals(getBucketName(bucketName))).findFirst();
return bucketOptional.orElse(null);
}
@SneakyThrows
public List<Bucket> listBuckets() {
return client.listBuckets();
}
@Override
@SneakyThrows
public void removeBucket(String bucketName) {
client.removeBucket(
RemoveBucketArgs.builder().bucket(getBucketName(bucketName)).build()
);
}
@Override
@SneakyThrows
public boolean bucketExists(String bucketName) {
return client.bucketExists(
BucketExistsArgs.builder().bucket(getBucketName(bucketName)).build()
);
}
@Override
@SneakyThrows
public void copyFile(String bucketName, String fileName, String destBucketName) {
copyFile(bucketName, fileName, destBucketName, fileName);
}
@Override
@SneakyThrows
public void copyFile(String bucketName, String fileName, String destBucketName, String destFileName) {
client.copyObject(
CopyObjectArgs.builder()
.source(CopySource.builder().bucket(getBucketName(bucketName)).object(fileName).build())
.bucket(getBucketName(destBucketName))
.object(destFileName)
.build()
);
}
@Override
@SneakyThrows
public OssFile statFile(String fileName) {
return statFile(ossProperties.getBucketName(), fileName);
}
@Override
@SneakyThrows
public OssFile statFile(String bucketName, String fileName) {
StatObjectResponse stat = client.statObject(
StatObjectArgs.builder().bucket(getBucketName(bucketName)).object(fileName).build()
);
OssFile ossFile = new OssFile();
ossFile.setName(Func.isEmpty(stat.object()) ? fileName : stat.object());
ossFile.setLink(fileLink(ossFile.getName()));
ossFile.setHash(String.valueOf(stat.hashCode()));
ossFile.setLength(stat.size());
ossFile.setPutTime(DateUtil.toDate(stat.lastModified().toLocalDateTime()));
ossFile.setContentType(stat.contentType());
return ossFile;
}
@Override
public String filePath(String fileName) {
return getBucketName().concat(StringPool.SLASH).concat(fileName);
}
@Override
public String filePath(String bucketName, String fileName) {
return getBucketName(bucketName).concat(StringPool.SLASH).concat(fileName);
}
@Override
@SneakyThrows
public String fileLink(String fileName) {
return ossProperties.getEndpoint().concat(StringPool.SLASH).concat(getBucketName()).concat(StringPool.SLASH).concat(fileName);
}
@Override
@SneakyThrows
public String fileLink(String bucketName, String fileName) {
return ossProperties.getEndpoint().concat(StringPool.SLASH).concat(getBucketName(bucketName)).concat(StringPool.SLASH).concat(fileName);
}
@Override
@SneakyThrows
public BladeFile putFile(MultipartFile file) {
return putFile(ossProperties.getBucketName(), file.getOriginalFilename(), file);
}
@Override
@SneakyThrows
public BladeFile putFile(String fileName, MultipartFile file) {
return putFile(ossProperties.getBucketName(), fileName, file);
}
@Override
@SneakyThrows
public BladeFile putFile(String bucketName, String fileName, MultipartFile file) {
return putFile(bucketName, file.getOriginalFilename(), file.getInputStream());
}
@Override
@SneakyThrows
public BladeFile putFile(String fileName, InputStream stream) {
return putFile(ossProperties.getBucketName(), fileName, stream);
}
@Override
@SneakyThrows
public BladeFile putFile(String bucketName, String fileName, InputStream stream) {
return putFile(bucketName, fileName, stream, "application/octet-stream");
}
@SneakyThrows
public BladeFile putFile(String bucketName, String fileName, InputStream stream, String contentType) {
makeBucket(bucketName);
String originalName = fileName;
fileName = getFileName(fileName);
client.putObject(
PutObjectArgs.builder()
.bucket(getBucketName(bucketName))
.object(fileName)
.stream(stream, stream.available(), -1)
.contentType(contentType)
.build()
);
BladeFile file = new BladeFile();
file.setOriginalName(originalName);
file.setName(fileName);
file.setDomain(getOssHost(bucketName));
file.setLink(fileLink(bucketName, fileName));
return file;
}
@Override
@SneakyThrows
public void removeFile(String fileName) {
removeFile(ossProperties.getBucketName(), fileName);
}
@Override
@SneakyThrows
public void removeFile(String bucketName, String fileName) {
client.removeObject(
RemoveObjectArgs.builder().bucket(getBucketName(bucketName)).object(fileName).build()
);
}
@Override
@SneakyThrows
public void removeFiles(List<String> fileNames) {
removeFiles(ossProperties.getBucketName(), fileNames);
}
@Override
@SneakyThrows
public void removeFiles(String bucketName, List<String> fileNames) {
Stream<DeleteObject> stream = fileNames.stream().map(DeleteObject::new);
client.removeObjects(RemoveObjectsArgs.builder().bucket(getBucketName(bucketName)).objects(stream::iterator).build());
}
/**
* 根据规则生成存储桶名称规则
*
* @return String
*/
private String getBucketName() {
return getBucketName(ossProperties.getBucketName());
}
/**
* 根据规则生成存储桶名称规则
*
* @param bucketName 存储桶名称
* @return String
*/
private String getBucketName(String bucketName) {
return ossRule.bucketName(bucketName);
}
/**
* 根据规则生成文件名称规则
*
* @param originalFilename 原始文件名
* @return string
*/
private String getFileName(String originalFilename) {
return ossRule.fileName(originalFilename);
}
/**
* 获取文件外链
*
* @param bucketName bucket名称
* @param fileName 文件名称
* @param expires 过期时间
* @return url
*/
@SneakyThrows
public String getPresignedObjectUrl(String bucketName, String fileName, Integer expires) {
return client.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(getBucketName(bucketName))
.object(fileName)
.expiry(expires)
.build()
);
}
/**
* 获取存储桶策略
*
* @param policyType 策略枚举
* @return String
*/
public String getPolicyType(PolicyType policyType) {
return getPolicyType(getBucketName(), policyType);
}
/**
* 获取存储桶策略
*
* @param bucketName 存储桶名称
* @param policyType 策略枚举
* @return String
*/
public static String getPolicyType(String bucketName, PolicyType policyType) {
StringBuilder builder = new StringBuilder();
builder.append("{\n");
builder.append(" \"Statement\": [\n");
builder.append(" {\n");
builder.append(" \"Action\": [\n");
switch (policyType) {
case WRITE:
builder.append(" \"s3:GetBucketLocation\",\n");
builder.append(" \"s3:ListBucketMultipartUploads\"\n");
break;
case READ_WRITE:
builder.append(" \"s3:GetBucketLocation\",\n");
builder.append(" \"s3:ListBucket\",\n");
builder.append(" \"s3:ListBucketMultipartUploads\"\n");
break;
default:
builder.append(" \"s3:GetBucketLocation\"\n");
break;
}
builder.append(" ],\n");
builder.append(" \"Effect\": \"Allow\",\n");
builder.append(" \"Principal\": \"*\",\n");
builder.append(" \"Resource\": \"arn:aws:s3:::");
builder.append(bucketName);
builder.append("\"\n");
builder.append(" },\n");
if (PolicyType.READ.equals(policyType)) {
builder.append(" {\n");
builder.append(" \"Action\": [\n");
builder.append(" \"s3:ListBucket\"\n");
builder.append(" ],\n");
builder.append(" \"Effect\": \"Deny\",\n");
builder.append(" \"Principal\": \"*\",\n");
builder.append(" \"Resource\": \"arn:aws:s3:::");
builder.append(bucketName);
builder.append("\"\n");
builder.append(" },\n");
}
builder.append(" {\n");
builder.append(" \"Action\": ");
switch (policyType) {
case WRITE:
builder.append("[\n");
builder.append(" \"s3:AbortMultipartUpload\",\n");
builder.append(" \"s3:DeleteObject\",\n");
builder.append(" \"s3:ListMultipartUploadParts\",\n");
builder.append(" \"s3:PutObject\"\n");
builder.append(" ],\n");
break;
case READ_WRITE:
builder.append("[\n");
builder.append(" \"s3:AbortMultipartUpload\",\n");
builder.append(" \"s3:DeleteObject\",\n");
builder.append(" \"s3:GetObject\",\n");
builder.append(" \"s3:ListMultipartUploadParts\",\n");
builder.append(" \"s3:PutObject\"\n");
builder.append(" ],\n");
break;
default:
builder.append("\"s3:GetObject\",\n");
break;
}
builder.append(" \"Effect\": \"Allow\",\n");
builder.append(" \"Principal\": \"*\",\n");
builder.append(" \"Resource\": \"arn:aws:s3:::");
builder.append(bucketName);
builder.append("/*\"\n");
builder.append(" }\n");
builder.append(" ],\n");
builder.append(" \"Version\": \"2012-10-17\"\n");
builder.append("}\n");
return builder.toString();
}
/**
* 获取域名
*
* @param bucketName 存储桶名称
* @return String
*/
public String getOssHost(String bucketName) {
return ossProperties.getEndpoint() + StringPool.SLASH + getBucketName(bucketName);
}
/**
* 获取域名
*
* @return String
*/
public String getOssHost() {
return getOssHost(ossProperties.getBucketName());
}
}

View File

@ -0,0 +1,202 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.oss;
import org.springblade.core.oss.model.BladeFile;
import org.springblade.core.oss.model.OssFile;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.List;
/**
* OssTemplate抽象API
*
* @author Chill
*/
public interface OssTemplate {
/**
* 创建 存储桶
*
* @param bucketName 存储桶名称
*/
void makeBucket(String bucketName);
/**
* 删除 存储桶
*
* @param bucketName 存储桶名称
*/
void removeBucket(String bucketName);
/**
* 存储桶是否存在
*
* @param bucketName 存储桶名称
* @return boolean
*/
boolean bucketExists(String bucketName);
/**
* 拷贝文件
*
* @param bucketName 存储桶名称
* @param fileName 存储桶文件名称
* @param destBucketName 目标存储桶名称
*/
void copyFile(String bucketName, String fileName, String destBucketName);
/**
* 拷贝文件
*
* @param bucketName 存储桶名称
* @param fileName 存储桶文件名称
* @param destBucketName 目标存储桶名称
* @param destFileName 目标存储桶文件名称
*/
void copyFile(String bucketName, String fileName, String destBucketName, String destFileName);
/**
* 获取文件信息
*
* @param fileName 存储桶文件名称
* @return InputStream
*/
OssFile statFile(String fileName);
/**
* 获取文件信息
*
* @param bucketName 存储桶名称
* @param fileName 存储桶文件名称
* @return InputStream
*/
OssFile statFile(String bucketName, String fileName);
/**
* 获取文件相对路径
*
* @param fileName 存储桶对象名称
* @return String
*/
String filePath(String fileName);
/**
* 获取文件相对路径
*
* @param bucketName 存储桶名称
* @param fileName 存储桶对象名称
* @return String
*/
String filePath(String bucketName, String fileName);
/**
* 获取文件地址
*
* @param fileName 存储桶对象名称
* @return String
*/
String fileLink(String fileName);
/**
* 获取文件地址
*
* @param bucketName 存储桶名称
* @param fileName 存储桶对象名称
* @return String
*/
String fileLink(String bucketName, String fileName);
/**
* 上传文件
*
* @param file 上传文件类
* @return BladeFile
*/
BladeFile putFile(MultipartFile file);
/**
* 上传文件
*
* @param file 上传文件类
* @param fileName 上传文件名
* @return BladeFile
*/
BladeFile putFile(String fileName, MultipartFile file);
/**
* 上传文件
*
* @param bucketName 存储桶名称
* @param fileName 上传文件名
* @param file 上传文件类
* @return BladeFile
*/
BladeFile putFile(String bucketName, String fileName, MultipartFile file);
/**
* 上传文件
*
* @param fileName 存储桶对象名称
* @param stream 文件流
* @return BladeFile
*/
BladeFile putFile(String fileName, InputStream stream);
/**
* 上传文件
*
* @param bucketName 存储桶名称
* @param fileName 存储桶对象名称
* @param stream 文件流
* @return BladeFile
*/
BladeFile putFile(String bucketName, String fileName, InputStream stream);
/**
* 删除文件
*
* @param fileName 存储桶对象名称
*/
void removeFile(String fileName);
/**
* 删除文件
*
* @param bucketName 存储桶名称
* @param fileName 存储桶对象名称
*/
void removeFile(String bucketName, String fileName);
/**
* 批量删除文件
*
* @param fileNames 存储桶对象名称集合
*/
void removeFiles(List<String> fileNames);
/**
* 批量删除文件
*
* @param bucketName 存储桶名称
* @param fileNames 存储桶对象名称集合
*/
void removeFiles(String bucketName, List<String> fileNames);
}

View File

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

View File

@ -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);
}

View File

@ -0,0 +1,65 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.oss.config;
import io.minio.MinioClient;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springblade.core.oss.MinioTemplate;
import org.springblade.core.oss.props.OssProperties;
import org.springblade.core.oss.rule.OssRule;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
/**
* Minio配置类
*
* @author Chill
*/
@AllArgsConstructor
@AutoConfiguration(after = OssConfiguration.class)
@ConditionalOnClass({MinioClient.class})
@EnableConfigurationProperties(OssProperties.class)
@ConditionalOnProperty(value = "oss.name", havingValue = "minio")
public class MinioConfiguration {
private final OssProperties ossProperties;
private final OssRule ossRule;
@Bean
@SneakyThrows
@ConditionalOnMissingBean(MinioClient.class)
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(ossProperties.getEndpoint())
.credentials(ossProperties.getAccessKey(), ossProperties.getSecretKey())
.build();
}
@Bean
@ConditionalOnBean({MinioClient.class})
@ConditionalOnMissingBean(MinioTemplate.class)
public MinioTemplate minioTemplate(MinioClient minioClient) {
return new MinioTemplate(minioClient, ossRule, ossProperties);
}
}

View File

@ -0,0 +1,43 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.oss.config;
import lombok.AllArgsConstructor;
import org.springblade.core.oss.props.OssProperties;
import org.springblade.core.oss.rule.BladeOssRule;
import org.springblade.core.oss.rule.OssRule;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
/**
* Oss配置类
*
* @author Chill
*/
@AutoConfiguration
@AllArgsConstructor
@EnableConfigurationProperties(OssProperties.class)
public class OssConfiguration {
@Bean
@ConditionalOnMissingBean(OssRule.class)
public OssRule ossRule() {
return new BladeOssRule();
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,54 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.oss.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* minio策略配置
*
* @author SCMOX
*/
@Getter
@AllArgsConstructor
public enum PolicyType {
/**
* 只读
*/
READ("read", "只读"),
/**
* 只写
*/
WRITE("write", "只写"),
/**
* 读写
*/
READ_WRITE("read_write", "读写");
/**
* 类型
*/
private final String type;
/**
* 描述
*/
private final String policy;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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;

View File

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

View File

@ -0,0 +1,25 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.secure.utils;
/**
* Secure工具类
*
* @author Chill
*/
public class AuthUtil extends SecureUtil{
}

View File

@ -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);
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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

View File

@ -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));
}
}

View File

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

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.tool.jackson;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* jackson 配置
*
* @author L.cm
*/
@Getter
@Setter
@ConfigurationProperties("blade.jackson")
public class BladeJacksonProperties {
/**
* 支持 MediaType text/plain用于和 blade-api-crypto 一起使用
*/
private Boolean supportTextPlain = Boolean.FALSE;
}

View File

@ -24,10 +24,8 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.Exceptions;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.*;
import org.springframework.lang.Nullable;
import java.io.IOException;
import java.io.InputStream;
@ -268,6 +266,127 @@ public class JsonUtil {
}
}
/**
* 将json byte 数组反序列化成对象
*
* @param content json bytes
* @param valueType class
* @param <T> T 泛型标记
* @return Bean
*/
@Nullable
public static <T> T readValue(@Nullable byte[] content, Class<T> valueType) {
if (ObjectUtil.isEmpty(content)) {
return null;
}
try {
return getInstance().readValue(content, valueType);
} catch (IOException e) {
throw Exceptions.unchecked(e);
}
}
/**
* 将json反序列化成对象
*
* @param jsonString jsonString
* @param valueType class
* @param <T> T 泛型标记
* @return Bean
*/
@Nullable
public static <T> T readValue(@Nullable String jsonString, Class<T> valueType) {
if (StringUtil.isBlank(jsonString)) {
return null;
}
try {
return getInstance().readValue(jsonString, valueType);
} catch (IOException e) {
throw Exceptions.unchecked(e);
}
}
/**
* 将json反序列化成对象
*
* @param in InputStream
* @param valueType class
* @param <T> T 泛型标记
* @return Bean
*/
@Nullable
public static <T> T readValue(@Nullable InputStream in, Class<T> valueType) {
if (in == null) {
return null;
}
try {
return getInstance().readValue(in, valueType);
} catch (IOException e) {
throw Exceptions.unchecked(e);
}
}
/**
* 将json反序列化成对象
*
* @param content bytes
* @param typeReference 泛型类型
* @param <T> T 泛型标记
* @return Bean
*/
@Nullable
public static <T> T readValue(@Nullable byte[] content, TypeReference<T> typeReference) {
if (ObjectUtil.isEmpty(content)) {
return null;
}
try {
return getInstance().readValue(content, typeReference);
} catch (IOException e) {
throw Exceptions.unchecked(e);
}
}
/**
* 将json反序列化成对象
*
* @param jsonString jsonString
* @param typeReference 泛型类型
* @param <T> T 泛型标记
* @return Bean
*/
@Nullable
public static <T> T readValue(@Nullable String jsonString, TypeReference<T> typeReference) {
if (StringUtil.isBlank(jsonString)) {
return null;
}
try {
return getInstance().readValue(jsonString, typeReference);
} catch (IOException e) {
throw Exceptions.unchecked(e);
}
}
/**
* 将json反序列化成对象
*
* @param in InputStream
* @param typeReference 泛型类型
* @param <T> T 泛型标记
* @return Bean
*/
@Nullable
public static <T> T readValue(@Nullable InputStream in, TypeReference<T> typeReference) {
if (in == null) {
return null;
}
try {
return getInstance().readValue(in, typeReference);
} catch (IOException e) {
throw Exceptions.unchecked(e);
}
}
/**
* 将json字符串转成 JsonNode
*

View File

@ -22,6 +22,8 @@ import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.lang.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 针对 api 服务对 android ios web 处理的 分读写的 jackson 处理
@ -38,22 +40,26 @@ public class MappingApiJackson2HttpMessageConverter extends AbstractReadWriteJac
@Nullable
private String jsonPrefix;
/**
* Construct a new {@link MappingApiJackson2HttpMessageConverter} using default configuration
* provided by {@link Jackson2ObjectMapperBuilder}.
*/
public MappingApiJackson2HttpMessageConverter() {
this(Jackson2ObjectMapperBuilder.json().build());
}
/**
* Construct a new {@link MappingApiJackson2HttpMessageConverter} with a custom {@link ObjectMapper}.
* You can use {@link Jackson2ObjectMapperBuilder} to build it easily.
*
* @param objectMapper ObjectMapper
* @see Jackson2ObjectMapperBuilder#json()
*/
public MappingApiJackson2HttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, initWriteObjectMapper(objectMapper), MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
public MappingApiJackson2HttpMessageConverter(ObjectMapper objectMapper, BladeJacksonProperties properties) {
super(objectMapper, initWriteObjectMapper(objectMapper), initMediaType(properties));
}
private static List<MediaType> initMediaType(BladeJacksonProperties properties) {
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
supportedMediaTypes.add(new MediaType("application", "*+json"));
// 支持 text 文本用于报文签名
if (Boolean.TRUE.equals(properties.getSupportTextPlain())) {
supportedMediaTypes.add(MediaType.TEXT_PLAIN);
}
return supportedMediaTypes;
}
private static ObjectMapper initWriteObjectMapper(ObjectMapper readObjectMapper) {

View File

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

View File

@ -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;
}

View File

@ -28,7 +28,7 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* 工具
* 操作工具
*
* @author L.cm
*/
@ -106,4 +106,24 @@ public class ClassUtil extends org.springframework.util.ClassUtils {
return AnnotatedElementUtils.findMergedAnnotation(beanType, annotationType);
}
/**
* 判断是否有注解 Annotation
*
* @param method Method
* @param annotationType 注解类
* @param <A> 泛型标记
* @return {boolean}
*/
public static <A extends Annotation> boolean isAnnotated(Method method, Class<A> annotationType) {
// 先找方法再找方法上的类
boolean isMethodAnnotated = AnnotatedElementUtils.isAnnotated(method, annotationType);
if (isMethodAnnotated) {
return true;
}
// 获取类上面的Annotation可能包含组合注解故采用spring的工具类
Class<?> targetClass = method.getDeclaringClass();
return AnnotatedElementUtils.isAnnotated(targetClass, annotationType);
}
}

View File

@ -0,0 +1,207 @@
/**
* Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.tool.utils;
import org.springframework.lang.Nullable;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import java.util.Objects;
/**
* DES加解密处理工具
*
* @author L.cm
*/
public class DesUtil {
/**
* 数字签名密钥算法
*/
public static final String DES_ALGORITHM = "DES";
/**
* 生成 des 密钥
*
* @return 密钥
*/
public static String genDesKey() {
return StringUtil.random(16);
}
/**
* DES加密
*
* @param data byte array
* @param password 密钥
* @return des hex
*/
public static String encryptToHex(byte[] data, String password) {
return HexUtil.encodeToString(encrypt(data, password));
}
/**
* DES加密
*
* @param data 字符串内容
* @param password 密钥
* @return des hex
*/
@Nullable
public static String encryptToHex(@Nullable String data, String password) {
if (StringUtil.isBlank(data)) {
return null;
}
byte[] dataBytes = data.getBytes(Charsets.UTF_8);
return encryptToHex(dataBytes, password);
}
/**
* DES解密
*
* @param data 字符串内容
* @param password 密钥
* @return des context
*/
@Nullable
public static String decryptFormHex(@Nullable String data, String password) {
if (StringUtil.isBlank(data)) {
return null;
}
byte[] hexBytes = HexUtil.decode(data);
return new String(decrypt(hexBytes, password), Charsets.UTF_8);
}
/**
* DES加密
*
* @param data byte array
* @param password 密钥
* @return des hex
*/
public static String encryptToBase64(byte[] data, String password) {
return Base64Util.encodeToString(encrypt(data, password));
}
/**
* DES加密
*
* @param data 字符串内容
* @param password 密钥
* @return des hex
*/
@Nullable
public static String encryptToBase64(@Nullable String data, String password) {
if (StringUtil.isBlank(data)) {
return null;
}
byte[] dataBytes = data.getBytes(Charsets.UTF_8);
return encryptToBase64(dataBytes, password);
}
/**
* DES解密
*
* @param data 字符串内容
* @param password 密钥
* @return des context
*/
public static byte[] decryptFormBase64(byte[] data, String password) {
byte[] dataBytes = Base64Util.decode(data);
return decrypt(dataBytes, password);
}
/**
* DES解密
*
* @param data 字符串内容
* @param password 密钥
* @return des context
*/
@Nullable
public static String decryptFormBase64(@Nullable String data, String password) {
if (StringUtil.isBlank(data)) {
return null;
}
byte[] dataBytes = Base64Util.decodeFromString(data);
return new String(decrypt(dataBytes, password), Charsets.UTF_8);
}
/**
* DES加密
*
* @param data 内容
* @param desKey 密钥
* @return byte array
*/
public static byte[] encrypt(byte[] data, byte[] desKey) {
return des(data, desKey, Cipher.ENCRYPT_MODE);
}
/**
* DES加密
*
* @param data 内容
* @param desKey 密钥
* @return byte array
*/
public static byte[] encrypt(byte[] data, String desKey) {
return encrypt(data, Objects.requireNonNull(desKey).getBytes(Charsets.UTF_8));
}
/**
* DES解密
*
* @param data 内容
* @param desKey 密钥
* @return byte array
*/
public static byte[] decrypt(byte[] data, byte[] desKey) {
return des(data, desKey, Cipher.DECRYPT_MODE);
}
/**
* DES解密
*
* @param data 内容
* @param desKey 密钥
* @return byte array
*/
public static byte[] decrypt(byte[] data, String desKey) {
return decrypt(data, Objects.requireNonNull(desKey).getBytes(Charsets.UTF_8));
}
/**
* DES加密/解密公共方法
*
* @param data byte数组
* @param desKey 密钥
* @param mode 加密{@link Cipher#ENCRYPT_MODE}解密{@link Cipher#DECRYPT_MODE}
* @return des
*/
private static byte[] des(byte[] data, byte[] desKey, int mode) {
try {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES_ALGORITHM);
Cipher cipher = Cipher.getInstance(DES_ALGORITHM);
DESKeySpec desKeySpec = new DESKeySpec(desKey);
cipher.init(mode, keyFactory.generateSecret(desKeySpec), Holder.SECURE_RANDOM);
return cipher.doFinal(data);
} catch (Exception e) {
throw Exceptions.unchecked(e);
}
}
}

View File

@ -0,0 +1,163 @@
/**
* Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.tool.utils;
import org.springframework.lang.Nullable;
import java.nio.charset.Charset;
/**
* hex 工具编解码全用 byte
*
* @author L.cm
*/
public class HexUtil {
public static final Charset DEFAULT_CHARSET = Charsets.UTF_8;
private static final byte[] DIGITS_LOWER = new byte[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
private static final byte[] DIGITS_UPPER = new byte[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
/**
* encode Hex
*
* @param data data to hex
* @return hex bytes
*/
public static byte[] encode(byte[] data) {
return encode(data, true);
}
/**
* encode Hex
*
* @param data data to hex
* @param toLowerCase 是否小写
* @return hex bytes
*/
public static byte[] encode(byte[] data, boolean toLowerCase) {
return encode(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
}
/**
* encode Hex
*
* @param data Data to Hex
* @return bytes as a hex string
*/
private static byte[] encode(byte[] data, byte[] digits) {
int len = data.length;
byte[] out = new byte[len << 1];
for (int i = 0, j = 0; i < len; i++) {
out[j++] = digits[(0xF0 & data[i]) >>> 4];
out[j++] = digits[0xF & data[i]];
}
return out;
}
/**
* encode Hex
*
* @param data Data to Hex
* @param toLowerCase 是否小写
* @return bytes as a hex string
*/
public static String encodeToString(byte[] data, boolean toLowerCase) {
return new String(encode(data, toLowerCase), DEFAULT_CHARSET);
}
/**
* encode Hex
*
* @param data Data to Hex
* @return bytes as a hex string
*/
public static String encodeToString(byte[] data) {
return new String(encode(data), DEFAULT_CHARSET);
}
/**
* encode Hex
*
* @param data Data to Hex
* @return bytes as a hex string
*/
@Nullable
public static String encodeToString(@Nullable String data) {
if (StringUtil.isBlank(data)) {
return null;
}
return encodeToString(data.getBytes(DEFAULT_CHARSET));
}
/**
* decode Hex
*
* @param data Hex data
* @return decode hex to bytes
*/
@Nullable
public static byte[] decode(@Nullable String data) {
if (StringUtil.isBlank(data)) {
return null;
}
return decode(data.getBytes(DEFAULT_CHARSET));
}
/**
* encode Hex
*
* @param data Data to Hex
* @return bytes as a hex string
*/
@Nullable
public static String decodeToString(@Nullable String data) {
byte[] decodeBytes = decode(data);
if (decodeBytes == null) {
return null;
}
return new String(decodeBytes, DEFAULT_CHARSET);
}
/**
* decode Hex
*
* @param data Hex data
* @return decode hex to bytes
*/
public static byte[] decode(byte[] data) {
int len = data.length;
if ((len & 0x01) != 0) {
throw new IllegalArgumentException("hexBinary needs to be even-length: " + len);
}
byte[] out = new byte[len >> 1];
for (int i = 0, j = 0; j < len; i++) {
int f = toDigit(data[j], j) << 4;
j++;
f |= toDigit(data[j], j);
j++;
out[i] = (byte) (f & 0xFF);
}
return out;
}
private static int toDigit(byte b, int index) {
int digit = Character.digit(b, 16);
if (digit == -1) {
throw new IllegalArgumentException("Illegal hexadecimal byte " + b + " at index " + index);
}
return digit;
}
}

View File

@ -0,0 +1,38 @@
/**
* Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.tool.utils;
import java.security.SecureRandom;
import java.util.Random;
/**
* 一些常用的单利对象
*
* @author L.cm
*/
public class Holder {
/**
* RANDOM
*/
public final static Random RANDOM = new Random();
/**
* SECURE_RANDOM
*/
public final static SecureRandom SECURE_RANDOM = new SecureRandom();
}

View File

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

View File

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