diff --git a/spring-boot-interface-crypto/09-spring-boot-interface-crypto.iml b/spring-boot-interface-crypto/09-spring-boot-interface-crypto.iml new file mode 100644 index 0000000..f409c0e --- /dev/null +++ b/spring-boot-interface-crypto/09-spring-boot-interface-crypto.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-boot-interface-crypto/cry-common/crypto-common.iml b/spring-boot-interface-crypto/cry-common/crypto-common.iml new file mode 100644 index 0000000..1435ba1 --- /dev/null +++ b/spring-boot-interface-crypto/cry-common/crypto-common.iml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-boot-interface-crypto/cry-common/pom.xml b/spring-boot-interface-crypto/cry-common/pom.xml new file mode 100644 index 0000000..c825646 --- /dev/null +++ b/spring-boot-interface-crypto/cry-common/pom.xml @@ -0,0 +1,40 @@ + + + + spring-boot-interface-crypto + xyz.hlh + 1.0-SNAPSHOT + + 4.0.0 + + crypto-common + + + 8 + 8 + + + + + org.springframework.boot + spring-boot-starter-validation + + + org.projectlombok + lombok + + + org.apache.commons + commons-io + 1.3.2 + + + org.springframework.boot + spring-boot-starter-web + true + + + + \ No newline at end of file diff --git a/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/entity/Result.java b/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/entity/Result.java new file mode 100644 index 0000000..cc48aff --- /dev/null +++ b/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/entity/Result.java @@ -0,0 +1,27 @@ +package xyz.hlh.crypto.common.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.springframework.http.HttpStatus; + +/** + * @author wn + * @description 统一的返回结果 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +@Builder +public class Result { + + @Builder.Default + private int status = HttpStatus.OK.value(); + private T data; + @Builder.Default + private String message = "成功"; + +} \ No newline at end of file diff --git a/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/entity/ResultBuilder.java b/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/entity/ResultBuilder.java new file mode 100644 index 0000000..f3a4aab --- /dev/null +++ b/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/entity/ResultBuilder.java @@ -0,0 +1,63 @@ +package xyz.hlh.crypto.common.entity; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +/** + * @author wn + * @description 返回值构造 + */ +public interface ResultBuilder { + + /** + * 成功的构造 + * @param data 数据 + * @return Result + */ + default ResponseEntity> success(Object data) { + return ResponseEntity.ok(Result.builder() + .status(HttpStatus.OK.value()).data(data) + .build()); + } + + /** + * 400的构造 + * @param errorMsg 错误信息 + * @return Result + */ + default ResponseEntity> badRequest(String errorMsg) { + return ResponseEntity.badRequest().body(Result.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .message(errorMsg) + .build()); + } + + /** + * 404的构造 + * @param errorMsg 错误信息 + * @return Result + */ + default ResponseEntity> notFound(String errorMsg) { + return ResponseEntity + .status(HttpStatus.NOT_FOUND.value()) + .body(Result.builder() + .status(HttpStatus.NOT_FOUND.value()) + .message(errorMsg) + .build()); + } + + /** + * 500的构造 + * @param errorMsg 错误信息 + * @return Result + */ + default ResponseEntity> internalServerError(String errorMsg) { + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .body(Result.builder() + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .message(errorMsg) + .build()); + } + +} \ No newline at end of file diff --git a/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/exception/CryptoException.java b/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/exception/CryptoException.java new file mode 100644 index 0000000..1703c18 --- /dev/null +++ b/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/exception/CryptoException.java @@ -0,0 +1,17 @@ +package xyz.hlh.crypto.common.exception; + +/** + * @author wn + * @description: 自定义异常 + */ +public class CryptoException extends CustomizeException { + + public CryptoException(String message, Throwable cause) { + super(message, cause); + } + + public CryptoException(String message) { + super(message); + } + +} diff --git a/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/exception/CustomizeException.java b/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/exception/CustomizeException.java new file mode 100644 index 0000000..86bf32b --- /dev/null +++ b/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/exception/CustomizeException.java @@ -0,0 +1,17 @@ +package xyz.hlh.crypto.common.exception; + +/** + * @author wn + * @description: 自定义异常 + */ +public class CustomizeException extends Exception { + + public CustomizeException(String message, Throwable cause) { + super(message, cause); + } + + public CustomizeException(String message) { + super(message); + } + +} diff --git a/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/exception/GlobExceptionHandler.java b/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/exception/GlobExceptionHandler.java new file mode 100644 index 0000000..6327863 --- /dev/null +++ b/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/exception/GlobExceptionHandler.java @@ -0,0 +1,61 @@ +package xyz.hlh.crypto.common.exception; + +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import xyz.hlh.crypto.common.entity.Result; +import xyz.hlh.crypto.common.entity.ResultBuilder; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author wn + * @description 统一异常处理类 + */ +@RestControllerAdvice // 返回json +public class GlobExceptionHandler implements ResultBuilder { + + @ExceptionHandler(value = BindException.class) + public ResponseEntity> bindExceptionHandler(BindException ex) { + ex.printStackTrace(); + // 获取所有错误信息,拼接 + List fieldErrors = ex.getBindingResult().getFieldErrors(); + String errorMsg = fieldErrors.stream() + .map(fieldError -> fieldError.getField() + ":" + fieldError.getDefaultMessage()) + .collect(Collectors.joining(",")); + // 返回统一处理类 + return badRequest(errorMsg); + } + + @ExceptionHandler(value = ParamException.class) + public ResponseEntity> paramExceptionHandler(ParamException ex) { + ex.printStackTrace(); + // 返回统一处理类 + return badRequest(ex.getMessage()); + } + + @ExceptionHandler(value = CryptoException.class) + public ResponseEntity> cryptoExceptionHandler(CryptoException ex) { + ex.printStackTrace(); + // 返回统一处理类 + return internalServerError(ex.getMessage()); + } + + @ExceptionHandler(value = CustomizeException.class) + public ResponseEntity> customizeExceptionHanlder(CustomizeException ex) { + ex.printStackTrace(); + // 返回统一处理类 + return internalServerError(ex.getMessage()); + } + + @ExceptionHandler(value = Exception.class) + public ResponseEntity> exceptionHanlder(Exception ex) { + ex.printStackTrace(); + // 返回统一处理类 + return internalServerError(ex.getMessage()); + } + +} \ No newline at end of file diff --git a/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/exception/ParamException.java b/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/exception/ParamException.java new file mode 100644 index 0000000..be22789 --- /dev/null +++ b/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/exception/ParamException.java @@ -0,0 +1,57 @@ +package xyz.hlh.crypto.common.exception; + +import lombok.Getter; +import org.springframework.util.CollectionUtils; + +import java.util.List; + +/** + * @author wn + * @description 自定义参数异常 + */ +@Getter +public class ParamException extends CustomizeException { + + private List fieldList; + private List msgList; + + public ParamException(String message) { + super(message); + } + + public ParamException(String message, Throwable cause) { + super(message, cause); + } + + public ParamException(List fieldList, List msgList) throws CustomizeException { + super(generatorMessage(fieldList, msgList)); + this.fieldList = fieldList; + this.msgList = msgList; + } + + public ParamException(List fieldList, List msgList, Exception ex) throws CustomizeException { + super(generatorMessage(fieldList, msgList), ex); + this.fieldList = fieldList; + this.msgList = msgList; + } + + private static String generatorMessage(List fieldList, List msgList) throws CustomizeException { + if (CollectionUtils.isEmpty(fieldList) || CollectionUtils.isEmpty(msgList) || fieldList.size() != msgList.size()) { + return "参数错误"; + } + + StringBuilder message = new StringBuilder(); + for (int i = 0; i < fieldList.size(); i++) { + String field = fieldList.get(i); + String msg = msgList.get(i); + if (i == fieldList.size() - 1) { + message.append(field).append(":").append(msg); + } else { + message.append(field).append(":").append(msg).append(","); + } + } + return message.toString(); + } + + +} diff --git a/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/filter/HttpServletRequestInputStreamFilter.java b/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/filter/HttpServletRequestInputStreamFilter.java new file mode 100644 index 0000000..e6c7399 --- /dev/null +++ b/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/filter/HttpServletRequestInputStreamFilter.java @@ -0,0 +1,35 @@ +package xyz.hlh.crypto.common.filter; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import xyz.hlh.crypto.common.utils.InputStreamHttpServletRequestWrapper; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +/** + * @author wn + * @description: + * 请求流转换为多次读取的请求流 过滤器 + */ +@Component +@Order(Ordered.HIGHEST_PRECEDENCE + 1) // 优先级最高 +public class HttpServletRequestInputStreamFilter implements Filter { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + + // 转换为可以多次获取流的request + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + InputStreamHttpServletRequestWrapper inputStreamHttpServletRequestWrapper = new InputStreamHttpServletRequestWrapper(httpServletRequest); + + // 放行 + chain.doFilter(inputStreamHttpServletRequestWrapper, response); + } +} diff --git a/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/utils/InputStreamHttpServletRequestWrapper.java b/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/utils/InputStreamHttpServletRequestWrapper.java new file mode 100644 index 0000000..d803a98 --- /dev/null +++ b/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/utils/InputStreamHttpServletRequestWrapper.java @@ -0,0 +1,89 @@ +package xyz.hlh.crypto.common.utils; + +import org.apache.commons.io.IOUtils; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * 请求流支持多次获取 + */ +public class InputStreamHttpServletRequestWrapper extends HttpServletRequestWrapper { + + /** + * 用于缓存输入流 + */ + private ByteArrayOutputStream cachedBytes; + + public InputStreamHttpServletRequestWrapper(HttpServletRequest request) { + super(request); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + if (cachedBytes == null) { + // 首次获取流时,将流放入 缓存输入流 中 + cacheInputStream(); + } + + // 从 缓存输入流 中获取流并返回 + return new CachedServletInputStream(cachedBytes.toByteArray()); + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + /** + * 首次获取流时,将流放入 缓存输入流 中 + */ + private void cacheInputStream() throws IOException { + // 缓存输入流以便多次读取。为了方便, 我使用 org.apache.commons IOUtils + cachedBytes = new ByteArrayOutputStream(); + IOUtils.copy(super.getInputStream(), cachedBytes); + } + + /** + * 读取缓存的请求正文的输入流 + *

+ * 用于根据 缓存输入流 创建一个可返回的 + */ + public static class CachedServletInputStream extends ServletInputStream { + + private final ByteArrayInputStream input; + + public CachedServletInputStream(byte[] buf) { + // 从缓存的请求正文创建一个新的输入流 + input = new ByteArrayInputStream(buf); + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener listener) { + + } + + @Override + public int read() throws IOException { + return input.read(); + } + } + +} diff --git a/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/utils/ValidationUtils.java b/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/utils/ValidationUtils.java new file mode 100644 index 0000000..63ada53 --- /dev/null +++ b/spring-boot-interface-crypto/cry-common/src/main/java/xyz/hlh/crypto/common/utils/ValidationUtils.java @@ -0,0 +1,91 @@ +package xyz.hlh.crypto.common.utils; + +import xyz.hlh.crypto.common.exception.CustomizeException; +import xyz.hlh.crypto.common.exception.ParamException; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * @author wn + * @description 验证工具类 + */ +public class ValidationUtils { + + private static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator(); + + /** + * 验证数据 + * @param object 数据 + */ + public static void validate(Object object) throws CustomizeException { + + Set> validate = VALIDATOR.validate(object); + + // 验证结果异常 + throwParamException(validate); + } + + /** + * 验证数据(分组) + * @param object 数据 + * @param groups 所在组 + */ + public static void validate(Object object, Class ... groups) throws CustomizeException { + + Set> validate = VALIDATOR.validate(object, groups); + + // 验证结果异常 + throwParamException(validate); + } + + /** + * 验证数据中的某个字段(分组) + * @param object 数据 + * @param propertyName 字段名称 + */ + public static void validate(Object object, String propertyName) throws CustomizeException { + Set> validate = VALIDATOR.validateProperty(object, propertyName); + + // 验证结果异常 + throwParamException(validate); + + } + + /** + * 验证数据中的某个字段(分组) + * @param object 数据 + * @param propertyName 字段名称 + * @param groups 所在组 + */ + public static void validate(Object object, String propertyName, Class ... groups) throws CustomizeException { + + Set> validate = VALIDATOR.validateProperty(object, propertyName, groups); + + // 验证结果异常 + throwParamException(validate); + + } + + /** + * 验证结果异常 + * @param validate 验证结果 + */ + private static void throwParamException(Set> validate) throws CustomizeException { + if (validate.size() > 0) { + List fieldList = new LinkedList<>(); + List msgList = new LinkedList<>(); + for (ConstraintViolation next : validate) { + fieldList.add(next.getPropertyPath().toString()); + msgList.add(next.getMessage()); + } + + throw new ParamException(fieldList, msgList); + } + } + +} diff --git a/spring-boot-interface-crypto/cry-spring-boot-starter/crypto-spring-boot-starter.iml b/spring-boot-interface-crypto/cry-spring-boot-starter/crypto-spring-boot-starter.iml new file mode 100644 index 0000000..34d174d --- /dev/null +++ b/spring-boot-interface-crypto/cry-spring-boot-starter/crypto-spring-boot-starter.iml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-boot-interface-crypto/cry-spring-boot-starter/pom.xml b/spring-boot-interface-crypto/cry-spring-boot-starter/pom.xml new file mode 100644 index 0000000..3def857 --- /dev/null +++ b/spring-boot-interface-crypto/cry-spring-boot-starter/pom.xml @@ -0,0 +1,45 @@ + + + + spring-boot-interface-crypto + xyz.hlh + 1.0-SNAPSHOT + + 4.0.0 + + cry-spring-boot-starter + + + 8 + 8 + + + + + xyz.hlh + crypto-common + 1.0-SNAPSHOT + + + + cn.hutool + hutool-all + 5.7.20 + + + + org.springframework.boot + spring-boot-starter-web + true + + + + org.apache.commons + commons-lang3 + + + + + \ No newline at end of file diff --git a/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/advice/DecryptRequestBodyAdvice.java b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/advice/DecryptRequestBodyAdvice.java new file mode 100644 index 0000000..36780a1 --- /dev/null +++ b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/advice/DecryptRequestBodyAdvice.java @@ -0,0 +1,146 @@ +package xyz.hlh.crypto.advice; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.MethodParameter; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice; +import xyz.hlh.crypto.annotation.DecryptionAnnotation; +import xyz.hlh.crypto.common.exception.ParamException; +import xyz.hlh.crypto.constant.CryptoConstant; +import xyz.hlh.crypto.entity.RequestBase; +import xyz.hlh.crypto.entity.RequestData; +import xyz.hlh.crypto.util.AESUtil; + +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.lang.reflect.Type; + +/** + * @author wn + * @description: requestBody 自动解密 + */ +@ControllerAdvice +public class DecryptRequestBodyAdvice implements RequestBodyAdvice { + + @Autowired + private ObjectMapper objectMapper; + + /** + * 方法上有DecryptionAnnotation注解的,进入此拦截器 + * @param methodParameter 方法参数对象 + * @param targetType 参数的类型 + * @param converterType 消息转换器 + * @return true,进入,false,跳过 + */ + @Override + public boolean supports(MethodParameter methodParameter, Type targetType, Class> converterType) { + return methodParameter.hasMethodAnnotation(DecryptionAnnotation.class); + } + + @Override + public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) throws IOException { + return inputMessage; + } + + /** + * 转换之后,执行此方法,解密,赋值 + * @param body spring解析完的参数 + * @param inputMessage 输入参数 + * @param parameter 参数对象 + * @param targetType 参数类型 + * @param converterType 消息转换类型 + * @return 真实的参数 + */ + @SneakyThrows + @Override + public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) { + + // 获取request + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes; + if (servletRequestAttributes == null) { + throw new ParamException("request错误"); + } + + HttpServletRequest request = servletRequestAttributes.getRequest(); + + // 获取数据 + ServletInputStream inputStream = request.getInputStream(); + RequestData requestData = objectMapper.readValue(inputStream, RequestData.class); + + if (requestData == null || StringUtils.isBlank(requestData.getText())) { + throw new ParamException("参数错误"); + } + + // 获取加密的数据 + String text = requestData.getText(); + + // 放入解密之前的数据 + request.setAttribute(CryptoConstant.INPUT_ORIGINAL_DATA, text); + + // 解密 + String decryptText = null; + try { + decryptText = AESUtil.decrypt(text); + } catch (Exception e) { + throw new ParamException("解密失败"); + } + + if (StringUtils.isBlank(decryptText)) { + throw new ParamException("解密失败"); + } + + // 放入解密之后的数据 + request.setAttribute(CryptoConstant.INPUT_DECRYPT_DATA, decryptText); + + // 获取结果 + Object result = objectMapper.readValue(decryptText, body.getClass()); + + // 强制所有实体类必须继承RequestBase类,设置时间戳 + if (result instanceof RequestBase) { + // 获取时间戳 + Long currentTimeMillis = ((RequestBase) result).getCurrentTimeMillis(); + // 有效期 60秒 + long effective = 60*1000; + + // 时间差 + long expire = System.currentTimeMillis() - currentTimeMillis; + + // 是否在有效期内 + if (Math.abs(expire) > effective) { + throw new ParamException("时间戳不合法"); + } + + // 返回解密之后的数据 + return result; + } else { + throw new ParamException(String.format("请求参数类型:%s 未继承:%s", result.getClass().getName(), RequestBase.class.getName())); + } + } + + /** + * 如果body为空,转为空对象 + * @param body spring解析完的参数 + * @param inputMessage 输入参数 + * @param parameter 参数对象 + * @param targetType 参数类型 + * @param converterType 消息转换类型 + * @return 真实的参数 + */ + @SneakyThrows + @Override + public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) { + String typeName = targetType.getTypeName(); + Class bodyClass = Class.forName(typeName); + return bodyClass.newInstance(); + } +} diff --git a/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/advice/EncryptResponseBodyAdvice.java b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/advice/EncryptResponseBodyAdvice.java new file mode 100644 index 0000000..39c9533 --- /dev/null +++ b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/advice/EncryptResponseBodyAdvice.java @@ -0,0 +1,96 @@ +package xyz.hlh.crypto.advice; + +import cn.hutool.json.JSONUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; +import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl; +import xyz.hlh.crypto.annotation.EncryptionAnnotation; +import xyz.hlh.crypto.common.entity.Result; +import xyz.hlh.crypto.common.exception.CryptoException; +import xyz.hlh.crypto.entity.RequestBase; +import xyz.hlh.crypto.util.AESUtil; + +import java.lang.reflect.Type; + +/** + * @author wn + * @description: responseBody自动加密 + */ +@ControllerAdvice +public class EncryptResponseBodyAdvice implements ResponseBodyAdvice> { + + @Autowired + private ObjectMapper objectMapper; + + @Override + public boolean supports(MethodParameter returnType, Class> converterType) { + ParameterizedTypeImpl genericParameterType = (ParameterizedTypeImpl)returnType.getGenericParameterType(); + + // 如果直接是Result并且有解密注解,则处理 + if (genericParameterType.getRawType() == Result.class && returnType.hasMethodAnnotation(EncryptionAnnotation.class)) { + return true; + } + + // 如果不是ResponseBody或者是Result,则放行 + if (genericParameterType.getRawType() != ResponseEntity.class) { + return false; + } + + // 如果是ResponseEntity并且有解密注解,则处理 + for (Type type : genericParameterType.getActualTypeArguments()) { + if (((ParameterizedTypeImpl) type).getRawType() == Result.class && returnType.hasMethodAnnotation(EncryptionAnnotation.class)) { + return true; + } + } + + return false; + } + + @SneakyThrows + @Override + public Result beforeBodyWrite(Result body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { + + // 真实数据 + Object data = body.getData(); + + // 如果data为空,直接返回 + if (data == null) { + return body; + } + + // 如果是实体,并且继承了Request,则放入时间戳 + if (data instanceof RequestBase) { + ((RequestBase)data).setCurrentTimeMillis(System.currentTimeMillis()); + } + + String dataText = JSONUtil.toJsonStr(data); + + // 如果data为空,直接返回 + if (StringUtils.isBlank(dataText)) { + return body; + } + + // 如果位数小于16,报错 + if (dataText.length() < 16) { + throw new CryptoException("加密失败,数据小于16位"); + } + + String encryptText = AESUtil.encryptHex(dataText); + + return Result.builder() + .status(body.getStatus()) + .data(encryptText) + .message(body.getMessage()) + .build(); + } +} diff --git a/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/annotation/DecryptionAnnotation.java b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/annotation/DecryptionAnnotation.java new file mode 100644 index 0000000..527a4f7 --- /dev/null +++ b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/annotation/DecryptionAnnotation.java @@ -0,0 +1,17 @@ +package xyz.hlh.crypto.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author wn + * @description: 解密生成类 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DecryptionAnnotation { +} diff --git a/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/annotation/EncryptionAnnotation.java b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/annotation/EncryptionAnnotation.java new file mode 100644 index 0000000..2718a8b --- /dev/null +++ b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/annotation/EncryptionAnnotation.java @@ -0,0 +1,17 @@ +package xyz.hlh.crypto.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author wn + * @description: 加密生成类 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface EncryptionAnnotation { +} diff --git a/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/config/AppConfig.java b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/config/AppConfig.java new file mode 100644 index 0000000..1cc0442 --- /dev/null +++ b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/config/AppConfig.java @@ -0,0 +1,25 @@ +package xyz.hlh.crypto.config; + +import cn.hutool.crypto.symmetric.AES; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.Resource; +import java.nio.charset.StandardCharsets; + +/** + * @author wn + * @description: 自动配置类 + */ +@Configuration +public class AppConfig { + + @Resource + private CryptConfig cryptConfig; + + @Bean + public AES aes() { + return new AES(cryptConfig.getMode(), cryptConfig.getPadding(), cryptConfig.getKey().getBytes(StandardCharsets.UTF_8), cryptConfig.getIv().getBytes(StandardCharsets.UTF_8)); + } + +} diff --git a/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/config/CryptConfig.java b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/config/CryptConfig.java new file mode 100644 index 0000000..e1abaef --- /dev/null +++ b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/config/CryptConfig.java @@ -0,0 +1,31 @@ +package xyz.hlh.crypto.config; + +import cn.hutool.crypto.Mode; +import cn.hutool.crypto.Padding; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +import java.io.Serializable; + +/** + * @author wn + * @description: AES需要的配置参数 + */ +@Configuration +@ConfigurationProperties(prefix = "crypto") +@PropertySource("classpath:crypto.properties") +@Data +@EqualsAndHashCode +@Getter +public class CryptConfig implements Serializable { + + private Mode mode; + private Padding padding; + private String key; + private String iv; + +} diff --git a/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/constant/CryptoConstant.java b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/constant/CryptoConstant.java new file mode 100644 index 0000000..80cd12c --- /dev/null +++ b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/constant/CryptoConstant.java @@ -0,0 +1,14 @@ +package xyz.hlh.crypto.constant; + +/** + * @author wn + * @description: 加密解密用到的常量 + */ +public class CryptoConstant { + + /** 请求解密之前的数据存放key */ + public static final String INPUT_ORIGINAL_DATA = "inputOriginalData"; + /** 请求解密之后的数据存放key */ + public static final String INPUT_DECRYPT_DATA = "inputDescryptData"; + +} diff --git a/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/entity/RequestBase.java b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/entity/RequestBase.java new file mode 100644 index 0000000..a4c1d26 --- /dev/null +++ b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/entity/RequestBase.java @@ -0,0 +1,18 @@ +package xyz.hlh.crypto.entity; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author wn + * @description: + */ +@Getter +@Setter +@EqualsAndHashCode +public class RequestBase { + + private Long currentTimeMillis; + +} diff --git a/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/entity/RequestData.java b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/entity/RequestData.java new file mode 100644 index 0000000..def203a --- /dev/null +++ b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/entity/RequestData.java @@ -0,0 +1,25 @@ +package xyz.hlh.crypto.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author wn + * @description: + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode +public class RequestData implements Serializable { + + // 加密的文本 + private String text; + +} diff --git a/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/util/AESUtil.java b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/util/AESUtil.java new file mode 100644 index 0000000..128bca8 --- /dev/null +++ b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/java/xyz/hlh/crypto/util/AESUtil.java @@ -0,0 +1,58 @@ +package xyz.hlh.crypto.util; + +import cn.hutool.crypto.symmetric.AES; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +/** + * @author wn + * @description: + */ +@Component +public class AESUtil { + + private static AES aes; + + @Resource + private AES iocAes; + @PostConstruct + public void setAes() { + AESUtil.aes = this.iocAes; + } + + public static AES getAes() { + return aes; + } + + public static String encryptHex(byte[] data) { + return aes.encryptHex(data); + } + public static String encryptHex(InputStream data) { + return aes.encryptHex(data); + } + + public static String encryptHex(String data) { + return aes.encryptHex(data, StandardCharsets.UTF_8); + } + + /** + * 解密 + */ + + public static String decrypt(byte[] data) { + return aes.decryptStr(data, StandardCharsets.UTF_8); + } + + public static String decrypt(InputStream data) { + return aes.decryptStr(data); + } + + public static String decrypt(String data) { + return aes.decryptStr(data, StandardCharsets.UTF_8); + } + +} diff --git a/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/resources/crypto.properties b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/resources/crypto.properties new file mode 100644 index 0000000..0abab8d --- /dev/null +++ b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/resources/crypto.properties @@ -0,0 +1,8 @@ +# \u6A21\u5F0F cn.hutool.crypto.Mode +crypto.mode=CTS +# \u8865\u7801\u65B9\u5F0F cn.hutool.crypto.Mode +crypto.padding=PKCS5Padding +# \u79D8\u94A5 +crypto.key=testkey123456789 +# \u76D0 +crypto.iv=testiv1234567890 \ No newline at end of file diff --git a/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/resources/spring/spring.factories b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/resources/spring/spring.factories new file mode 100644 index 0000000..f94b5b0 --- /dev/null +++ b/spring-boot-interface-crypto/cry-spring-boot-starter/src/main/resources/spring/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +xyz.hlh.crypto.config.AppConfig \ No newline at end of file diff --git a/spring-boot-interface-crypto/cry-test/crypto-test.iml b/spring-boot-interface-crypto/cry-test/crypto-test.iml new file mode 100644 index 0000000..0a02ee2 --- /dev/null +++ b/spring-boot-interface-crypto/cry-test/crypto-test.iml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-boot-interface-crypto/cry-test/pom.xml b/spring-boot-interface-crypto/cry-test/pom.xml new file mode 100644 index 0000000..cc7332d --- /dev/null +++ b/spring-boot-interface-crypto/cry-test/pom.xml @@ -0,0 +1,62 @@ + + + + spring-boot-interface-crypto + xyz.hlh + 1.0-SNAPSHOT + + 4.0.0 + + crypto-test + + + 8 + 8 + + + + + + xyz.hlh + crypto-common + 1.0-SNAPSHOT + + + + xyz.hlh + cry-spring-boot-starter + 1.0-SNAPSHOT + + + + org.apache.commons + commons-io + 1.3.2 + + + + junit + junit + test + + + + cn.hutool + hutool-all + 5.7.20 + + + + org.springframework.boot + spring-boot-starter-test + + + org.springframework.boot + spring-boot-starter-web + + + + + \ No newline at end of file diff --git a/spring-boot-interface-crypto/cry-test/src/main/java/xyz/hlh/crypto/CryptoTestApplication.java b/spring-boot-interface-crypto/cry-test/src/main/java/xyz/hlh/crypto/CryptoTestApplication.java new file mode 100644 index 0000000..eb1251b --- /dev/null +++ b/spring-boot-interface-crypto/cry-test/src/main/java/xyz/hlh/crypto/CryptoTestApplication.java @@ -0,0 +1,15 @@ +package xyz.hlh.crypto; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author wn + * @description: 启动类 + */ +@SpringBootApplication +public class CryptoTestApplication { + public static void main(String[] args) { + SpringApplication.run(CryptoTestApplication.class, args); + } +} diff --git a/spring-boot-interface-crypto/cry-test/src/main/java/xyz/hlh/crypto/controller/TestController.java b/spring-boot-interface-crypto/cry-test/src/main/java/xyz/hlh/crypto/controller/TestController.java new file mode 100644 index 0000000..f6daa78 --- /dev/null +++ b/spring-boot-interface-crypto/cry-test/src/main/java/xyz/hlh/crypto/controller/TestController.java @@ -0,0 +1,42 @@ +package xyz.hlh.crypto.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import xyz.hlh.crypto.annotation.DecryptionAnnotation; +import xyz.hlh.crypto.annotation.EncryptionAnnotation; +import xyz.hlh.crypto.common.entity.Result; +import xyz.hlh.crypto.common.entity.ResultBuilder; +import xyz.hlh.crypto.entity.Teacher; + +/** + * springboot一个注解实现接口加解密!新姿势! + */ +@RestController +public class TestController implements ResultBuilder { + + /** + * 返回加密后的数据 + * @param teacher Teacher对象 + * @return 返回加密后的数据 ResponseBody格式 + */ + @PostMapping("/encrypt") + @EncryptionAnnotation + public ResponseEntity> encrypt(@Validated @RequestBody Teacher teacher) { + return success(teacher); + } + + /** + * 返回解密后的数据 + * @param teacher Teacher对象 + * @return 返回解密后的数据 + */ + @PostMapping("/decrypt") + @DecryptionAnnotation + public ResponseEntity> decrypt(@Validated @RequestBody Teacher teacher) { + return success(teacher); + } + +} diff --git a/spring-boot-interface-crypto/cry-test/src/main/java/xyz/hlh/crypto/entity/Teacher.java b/spring-boot-interface-crypto/cry-test/src/main/java/xyz/hlh/crypto/entity/Teacher.java new file mode 100644 index 0000000..92e1efb --- /dev/null +++ b/spring-boot-interface-crypto/cry-test/src/main/java/xyz/hlh/crypto/entity/Teacher.java @@ -0,0 +1,35 @@ +package xyz.hlh.crypto.entity; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Range; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.io.Serializable; +import java.util.Date; + +/** + * @author wn + * @description: Teacher实体类,使用SpringBoot的validation校验 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class Teacher extends RequestBase implements Serializable { + + @NotBlank(message = "姓名不能为空") + private String name; + @NotNull(message = "年龄不能为空") + @Range(min = 0, max = 150, message = "年龄不合法") + private Integer age; + @NotNull(message = "生日不能为空") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date birthday; + +} diff --git a/spring-boot-interface-crypto/cry-test/src/main/resources/application.yml b/spring-boot-interface-crypto/cry-test/src/main/resources/application.yml new file mode 100644 index 0000000..7d2400a --- /dev/null +++ b/spring-boot-interface-crypto/cry-test/src/main/resources/application.yml @@ -0,0 +1,11 @@ +spring: + mvc: + format: + date-time: yyyy-MM-dd HH:mm:ss + date: yyyy-MM-dd + # 日期格式化 + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + + diff --git a/spring-boot-interface-crypto/cry-test/src/test/java/xyz/hlh/crypto/controller/TestControllerTest.java b/spring-boot-interface-crypto/cry-test/src/test/java/xyz/hlh/crypto/controller/TestControllerTest.java new file mode 100644 index 0000000..8846c0f --- /dev/null +++ b/spring-boot-interface-crypto/cry-test/src/test/java/xyz/hlh/crypto/controller/TestControllerTest.java @@ -0,0 +1,29 @@ +package xyz.hlh.crypto.controller; + +import cn.hutool.json.JSONUtil; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; +import xyz.hlh.crypto.common.entity.Result; +import xyz.hlh.crypto.util.AESUtil; + +/** + * @author wn + * @description: + */ +@SpringBootTest +@RunWith(SpringRunner.class) +public class TestControllerTest { + + @Test + public void test1() { + + System.out.println(AESUtil.getAes()); + + String data = JSONUtil.toJsonStr(new Result()); + System.out.println(AESUtil.encryptHex(data)); + System.out.println(AESUtil.decrypt(AESUtil.encryptHex(data))); + + } +} \ No newline at end of file diff --git a/spring-boot-interface-crypto/pom.xml b/spring-boot-interface-crypto/pom.xml new file mode 100644 index 0000000..46d9e5b --- /dev/null +++ b/spring-boot-interface-crypto/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + xyz.hlh + spring-boot-interface-crypto + pom + 1.0-SNAPSHOT + + cry-spring-boot-starter + cry-test + cry-common + + + + org.springframework.boot + spring-boot-starter-parent + 2.6.3 + + + + + 8 + 8 + + + + + \ No newline at end of file