SpringBoot不同返回值包装类解决方案

问题

在系统开发时,我们需要对接不同的第三方系统,我们可能需要给他们提供接口,但是他们要求的返回值包装类和我们系统中现有的包装类不一致。甚至还有各种各样的加密解决需求,我以前的解决方案是将加密解密,包装这些都写到代码中,抽取一个公共类即可,但是总觉得这种方式不够优雅,而且还要专门catch异常,然后将异常也封装成第三方系统需要的格式。

例如我们现有系统的包装类:

{
    "timestamp": "2023-01-31 14:04:07",
    "status": 500,
    "error": "xx",
    "exception": "xx",
    "trace": "xx",
    "message": "Token失效,请重新登录",
    "path": "/xx"
}

而客户要求的包装类:

{
    "Ret": 500,
    "Msg": "xx",
    "Data": null,
    "Sig": "43D9C14B87BA728B9F33AA17962259FC"
}

客户要求Data参数要AES加密,同时还要附带Hmac签名的一个Sig。同时,客服传给我的值也会按照特定格式加密,需要进行解密。

解决方案

解决接口的加密解密,以及返回值包装。

对于返回给客户的数据需要加码,我们先编写两个注解

需要加密

/**
 * 定义注解
 * 功能:请求体/响应体解密/加密,请求体参数/响应需要进行解密/加密
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ThirdBodyDecrypt {
    
}

无需加密

/**
 * 定义注解
 * 功能:排除请求体/响应体解密/加密,请求体参数/响应不需要进行解密/加密
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExcludeThridBodyDecrypt {
    
}

返回值包装类定义

@Data
@NoArgsConstructor
public class ThirdResultVO {

    /**
     * 返回编码
     */
    @JsonProperty("Ret")
    private Integer ret;

    /**
     * 有错误表示具体错误信息,无错误返回成功信息
     */
    @JsonProperty("Msg")
    private String msg;

    /**
     * 具体返回参数见各接口参数说明,所有数据采用UTF-8编码,JSON格式
     */
    @JsonProperty("Data")
    private String data;

    /**
     * 采用HMAC-MD5算法,采用MD5作为散列函数,通过SigSecret(签名密钥)对整个消息主体各参数的值拼接后进行加密
     * 出参拼接顺序为:Ret(返回值)、Msg(返回信息)、Data(参数内容)
     * 然后采用MD5信息摘要的方式形成新密文,参数签名必须大写
     */
    @JsonProperty("Sig")
    private String sig;

    @JsonIgnore
    public String getParamString() {
        return this.ret + this.msg + this.data;
    }

    public static ThirdResultVO OK(String data) {
        ThirdResultVO teldResultVO = new ThirdResultVO();
        teldResultVO.setData(data);
        teldResultVO.setRet(0);
        teldResultVO.setMsg("success");
        return teldResultVO;
    }

    public static ThirdResultVO error(int ret, String msg) {
        ThirdResultVO r = new ThirdResultVO();
        r.setRet(ret);
        r.setMsg(msg);
        return r;
    }

    public static ThirdResultVO error(int ret, String msg, String data) {
        ThirdResultVO r = new ThirdResultVO();
        r.setRet(ret);
        r.setMsg(msg);
        r.setData(data);
        return r;
    }

}

拦截请求值并解密

@Slf4j
@ControllerAdvice
@RequiredArgsConstructor
public class ThirdRequestBodyHandler implements RequestBodyAdvice {

    private final ObjectMapper objectMapper;

    @Override
    public boolean supports(@NotNull MethodParameter methodParameter, @NotNull Type targetType,
                            @NotNull Class<? extends HttpMessageConverter<?>> converterType) {
        return SupportCheckUtil.getInstance().checkSupport(methodParameter);
    }

    @NotNull
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, @NotNull MethodParameter parameter, @NotNull Type targetType,
                                           @NotNull Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        log.info("beforeBodyRead");
        if (inputMessage.getBody().available() <= 0) {
            return inputMessage;
        }

        byte[] requestDataByte = new byte[inputMessage.getBody().available()];
        int read = inputMessage.getBody().read(requestDataByte);
        log.debug("read: {}", read);

        byte[] requestDataByteNew = null;
        // 这里换成自己项目的统一入参即可
        String data = objectMapper.readValue(requestDataByte, String.class);
        try {
            // 解密
            requestDataByteNew = AesEncryptUtil.getInstance().decrypt(data);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 使用解密后的数据,构造新的读取流
        assert requestDataByteNew != null;
        InputStream rawInputStream = new ByteArrayInputStream(requestDataByteNew);
        return new HttpInputMessage() {
            @NotNull
            @Override
            public HttpHeaders getHeaders() {
                return inputMessage.getHeaders();
            }

            @NotNull
            @Override
            public InputStream getBody() {
                return rawInputStream;
            }
        };

    }

    @NotNull
    @Override
    public Object afterBodyRead(@NotNull Object body, @NotNull HttpInputMessage inputMessage,
                                @NotNull MethodParameter parameter, @NotNull Type targetType,
                                @NotNull Class<? extends HttpMessageConverter<?>> converterType) {
        log.info("afterBodyRead");
        log.info("请求值原文:{}", body);
        return body;
    }

    @Override
    public Object handleEmptyBody(Object body, @NotNull HttpInputMessage inputMessage,
                                  @NotNull MethodParameter parameter, @NotNull Type targetType,
                                  @NotNull Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

}

拦截返回值并加密

@Slf4j
@RestControllerAdvice
@RequiredArgsConstructor
public class ThirdResponseBodyHandler implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(@NotNull MethodParameter returnType, @NotNull Class converterType) {
        //排除解密注解
        return SupportCheckUtil.getInstance().checkSupport(returnType);
    }

    @Override
    public Object beforeBodyWrite(Object body, @NotNull MethodParameter returnType, @NotNull MediaType selectedContentType,
                                  @NotNull Class selectedConverterType, @NotNull ServerHttpRequest request, @NotNull ServerHttpResponse response) {
        // 直接将Body转换成包装类,然后直接加密,加签即可
        ThirdResultVO resultVO;
        if (body instanceof ThirdResultVO) {
            resultVO = ((ThirdResultVO) body);
            if (StringUtils.isNotBlank(resultVO.getSig()))
                return body;
        } else {
            String content = JSONUtil.toJsonStr(body);
            log.info("返回值原文:{}", content);
            String result = null;
            try {
                if (StringUtils.isNotBlank(content)) {
                    result = AesEncryptUtil.getInstance().encrypt(content);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            resultVO = ThirdResultVO.OK(result);
        }
        String sig = HmacUtil.getInstance().getHmacMD5(resultVO.getParamString());
        resultVO.setSig(sig);
        log.info("返回值包装:{}", resultVO);
        return resultVO;
    }

}

使用 将@TeldBodyDecrypt加到Controller上即可实现加密解决

@Slf4j
@RestController
@ThirdBodyDecrypt
@Api(tags = "第三方API")
@RequiredArgsConstructor
@RequestMapping("/third")
public class TeldApi1 extends TeldErrorHandler<TeldResultVO> {
    
    @PostMapping("/query_token")
    public String queryToken() {
        return "我是没加密的参数";
    }
    
}

解决异常抛出的问题

我想到的是将原有异常拦截的上层,抽象出一个异常拦截的泛型类,定义一个抽象方法,每个继承他的方法都需要去实现转换返回值的抽象方法。

@Slf4j
public abstract class AllErrorHandler<T> {


    /**
     * 处理自定义异常
     */
    @ExceptionHandler(ResultException.class)
    public T handleRRException(ResultException e) {
        return ERROR(e.getMessage());
    }


    @ExceptionHandler(NoHandlerFoundException.class)
    public T handlerNoFoundException(Exception e) {
        log.error(e.getMessage(), e);
        return ERROR(404, "路径不存在,请检查路径是否正确");
    }

    // ....... 自行补充自己项目的异常一颗

    /**
     * 实现异常拦截后转换的核心方法,需要每个继承的类自定义去实现返回值
     *
     * @param code 错误码
     * @param msg  错误消息
     * @param data 返回的实体信息
     * @return 转换后的错误类
     */
    protected abstract T ERROR(int code, String msg, Object data);

    protected T ERROR(int code, String msg) {
        return ERROR(code, msg, null);
    }

    protected T ERROR(String msg) {
        return ERROR(500, msg, null);
    }

}

然后在统一异常拦截那里继承

@Slf4j
@RestControllerAdvice
@RequiredArgsConstructor
public class BaseExceptionHandler extends AllErrorHandler<Result<?>> {

    @Override
    protected Result<?> ERROR(int code, String msg, Object data) {
        return Result.error(code, msg, data);
    }

}

这样我们原有项目就还是返回原来的包装类

然后在要改变返回包装类的方法处继承抽象异常类,并重写返回值方法即可。

@Slf4j
@RestController
@ThridBodyDecrypt
@Api(tags = "第三方API1")
@RequiredArgsConstructor
@RequestMapping("/third")
public class TeldApi1 extends AllErrorHandler<ThridResultVo> {
    
    @PostMapping("/query_token1")
    public String queryToken() {
        return "我是没加密的参数";
    }

    @Override
    protected ThridResultVo ERROR(int code, String msg, Object data) {
        ThridResultVo thridResultVo;
        if (data != null) {
            try {
                String dataString = objectMapper.writeValueAsString(data);
                String encrypt = AesEncryptUtil.getInstance().encrypt(dataString);
                thridResultVo = ThridResultVo.error(500, msg, encrypt);
            } catch (Exception e) {
                log.info("异常拦截错误:{}", e.getMessage());
                thridResultVo = ThridResultVo.error(500, msg);
            }
        } else {
            thridResultVo = ThridResultVo.error(500, msg);
        }
        String sig = HmacUtil.getInstance().getHmacMD5( thridResultVo.getParamString());
        thridResultVo.setSig(sig);
        return thridResultVo;
    }

}

这样特定接口抛出的异常,就会是不同的包装类包装的返回值了!!

优雅!!真他娘的优雅!!哈哈哈哈!!

你可能感兴趣的:(Java,spring,boot,java,json)