Springboot切面获取方法参数类中的泛型类

这周再搞一个比较奇葩的事,老项目中做的接口验签是放到httpRequestBody中的,这就给第三方对接我们时产生了很大的误区。请求的Content-Type是application/json;charset=UTF-8

接受的controller:

@PostMapping(value = "/test")
public String register(@RequestBody @Valid ComParam<User>){
     return "success";
}

ComParam:

public class ComParam<T> {
    @ApiModelProperty(value = "请求头参数", required = true)
    @NotNull(message = "请求头参数不能为空")
    @Valid
    private Header header;

    @ApiModelProperty(value = "业务参数", required = true)
    @NotNull(message = "业务参数不能为空")
    @Valid
    private T body;
}

public class Header {
    @NotNull(message = "appKey不能为空")
    private String appKey;

    @NotNull(message = "timestamp不能为空")
    private String timestamp;

    @NotNull(message = "签名不能为空")
    private String signature;
}

从上面就可以看出,业务参数是放到一个泛型对象中的,那对应的请求参数就要成为下面的这种:

{
    "body": {
        "idNo": "111111111",
        "mobile": "13000000001",
        "name": "张三"
    },
    "header": {
        "appKey": "testKey",
        "timestamp": "2023-04-12 00:11:22",
        "signature": "eb5bfcb311c320bede5f7cb403fe1687d6aefca1"
    }
}

是不是很奇怪。

更奇怪的是,我们的验签是放到切面中的,根据切面参数是否是实现ComParam来做。

这时就有一个需求:把请求体中的header参数全部放到httpHeader中,又不动controller的代码,这就难搞,需要把HttpBody的json自己转化成ComParam类型的。

请求的httpBody格式是类似这样的

{
        "idNo": "111111111",
        "mobile": "13000000001",
        "name": "张三"
}

那就开搞:

1. 因为验签是放到切面中的,切面中能拿到的参数已经是ComParam,切属性都是null。所以需要重新从request中获取请求内容,但是request中的流是一次性的,读完就没有了,考虑暂存下这个流。需要两个类

import org.springframework.core.annotation.Order;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * @author :
 * @date: 2024/8/23 13:05
 * @Description:过滤器缓存内容
 */
@WebFilter
@Order(1)
public class BodyReaderFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        BodyReaderRequestWrapper requestWrapper = new BodyReaderRequestWrapper(req);
        filterChain.doFilter(requestWrapper, servletResponse);
    }

    @Override
    public void destroy() {

    }
}
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.IOException;
import java.io.InputStreamReader;

/**
 * @author :
 * @date: 2024/8/23 11:45
 * @Description:继承HttpServletRequestWrapper,缓存内容
 */
public class BodyReaderRequestWrapper extends HttpServletRequestWrapper {
    private final String body;

    public BodyReaderRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        StringBuilder sb = new StringBuilder();
        ServletInputStream ins = request.getInputStream();
        BufferedReader isr = null;
        try {
            if (ins != null) {
                isr = new BufferedReader(new InputStreamReader(ins));
                char[] charBuffer = new char[128];
                int readCount = 0;
                while ((readCount = isr.read(charBuffer)) != -1) {
                    sb.append(charBuffer,0,readCount);
                }
            } else {
                sb.append("");
            }
        } catch (IOException e) {
            throw e;
        } finally {
            if (isr != null) {
                isr.close();
            }
        }
        body = sb.toString();
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        ByteArrayInputStream byteArrayIns = new ByteArrayInputStream(body.getBytes());
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() throws IOException {
                return byteArrayIns.read();
            }
        };
    }
}

2.在切面中重新实例化ComParam,然后验证

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.Objects;
import java.util.Set;

@Component
@Aspect
@Slf4j
public class SignatureAspectConfig {

    @Around("execution(* com.xx.controller..*.*(..))")
    public Object around(ProceedingJoinPoint jp) throws Throwable {
        Object[] args = jp.getArgs();
        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        if (args[0] instanceof RequestParam) {
            RequestParam param = (RequestParam) args[0];
        // header为空,从请求头中获取
            if (Objects.isNull(param.getHeader()) && StringUtils.isNotBlank(request.getHeader("appKey"))) {
                param.setHeader(getHeaderByRequest(request));
                MethodSignature methodSignature = (MethodSignature)((MethodInvocationProceedingJoinPoint) jp).getSignature();
                // 这里获取第0个参数,和外围的args[0]对应
                ParameterizedType parameterizedType = (ParameterizedType) methodSignature.getMethod().getParameters()[0].getParameterizedType();
                // 这里只获取了第0个泛型的类型
                String typeName = parameterizedType.getActualTypeArguments()[0].getTypeName();
                // 获取泛型类型
                Class<?> TT = Class.forName(typeName);
                String body = StreamUtils.copyToString(request.getInputStream(), Charset.forName("UTF-8"));
                Object bo = JSONObject.parseObject(body, TT);
                param.setBody(bo);

            }
            Header header = param.getHeader();
            // 手动校验器校验header
            if (Objects.isNull(header)) {
                throw new ParamException("缺少请求header");
            }
            Set<ConstraintViolation<Object>> validateHeader = validator.validate(header);
            String headerErrorMsg = getErrorMsg(validateHeader);
            if (StringUtils.isNotBlank(headerErrorMsg)) {
                throw new ParamException(headerErrorMsg);
            }
            if (Objects.isNull(param.getBody())) {
                throw new ParamException("业务参数不能为空");
            }
            // 手动校验body
            Set<ConstraintViolation<Object>> validateBody = validator.validate(param.getBody());
            String bodyErrorMsg = getErrorMsg(validateBody);
            if (StringUtils.isNotBlank(bodyErrorMsg)) {
                throw new ParamException(bodyErrorMsg);
            }

}

主要点是缓存httpRequest的内容+获取类泛型属性的真实运行类

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值