springboot中多个请求参数(@RequestBody)加数据校验(BIndingResult)的使用

在日常的开发中遇到了一种问题 - 没办法对Controller注入多个参数,每个Controller的RequestMap方法参数只能注入一个的RequestBody。但是又经常使用多个参数,通常我们的做法是将那几个DTO重新组合成一个新的DTO,最常见的例子就是我们在请求分页数据的时候,每次要嘛将请求参数和分页参数组合成一个新的DTO,要嘛请求参数的DTO继承分页参数的DTO,这样使我们没办法从方法上直观的看到这个请求接口的参数性质。我再网上找到了一种多参数注入的方式,是一个大神写的,他自己写了一个注解(@MultiRequestBody),应该会有人看过这篇文章,我顺便也把链接附上SpringBoot Controller 中使用多个@RequestBody的正确姿势 ,我在这边再次膜拜大神,这篇文章初期给了我很大的帮助,感谢。

人总是不满足的,因为在业务当中要加很多参数的判空处理,所以我就想着有没有什么工具可以自动帮我们做参数的校验,所以就找到了BindingRequest,配合@Valid使用十分好用。但是大神的@MultiRequestBody没办法配合@Valid、BindingRequest使用,所以就很蛋疼。抱着我一定要用上可以自动参数校验的多个RequestBody注入的想法,便开始趟雷了。

趟雷的时候发现报的异常我很熟悉,异常叫什么我忘记了,但是记得内容是request.getInputStream()里面没有值了,所以RequestBody拿不到值(异常引起的原因是request.getInputStream()里面的值只能取一次,顺便说一下request.getReader()里面的值也只能取一次,可能是request.getReader()使用了request.getInputStream())。也很幸运,因为之前遇到过这个异常,感觉会不会是第二个RequestBody从request里面去取值的时候因为request.getInputStream()的值被第一个拿走了,所以第二个RequestBody拿不到值,所以就网上找了一个工具,先将request.getInputStream()拿出来,然后再把它放到一个新的request里面,我们去使用这个新的request,不就解决了这个问题了。

现在就开始把这些骚操作串起来。

1、加入依赖



    javax.validation
    validation-api
    2.0.1.Final

2、加入重置request里面的值的工具

附上大神的文章链接解决在Filter中读取Request中的流后, 然后在Controller中@RequestBody的参数无法注入而导致 400 错误

大神的代码中我们只需要里面将request.getInputStream()重置这部分。

(1)写好request重置的工具

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.IOException;
import java.nio.charset.Charset;

/**  将request中的getinputstream取出来再放到一个新的request中
 * @author Chen KaiYang
 * @date 2019/8/12 9:01
 */
public class ReaderReuseHttpServletRequestWrapper extends HttpServletRequestWrapper {
    //取出来的值就放在这
    private final byte[] body;

    public ReaderReuseHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        this.body = IOUtils.toString(request.getReader()).getBytes(Charset.forName("UTF-8"));
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return super.getReader();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        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 bais.read();
            }
        };
    }
}

(2)使用新的request顶替旧的request

import cn.fancybull.framework.config.ReaderReuseHttpServletRequestWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.stereotype.Component;

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

/**
 * 权限认证过滤器
* 执行接口访问权限控制。需与控制层的AOP进行联动 * 跨越处理 */ @Component @ServletComponentScan @WebFilter(urlPatterns = "/*",filterName = "authorFilter") public class AuthorFilter implements Filter { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = ((HttpServletResponse) servletResponse); String uri = request.getRequestURI().replaceFirst(request.getContextPath(), ""); ServletContext context = request.getSession().getServletContext(); ServletRequest requestWrapper = null; if(request instanceof HttpServletRequest){ requestWrapper = new ReaderReuseHttpServletRequestWrapper(request); } //设置跨域处理 response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin")); response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "0"); response.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,userId,token,Access-Control-Allow-Headers"); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setHeader("XDomainRequestAllowed", "1"); /// 过滤器的控制实现代码 /// 过滤结果可以写入到request中,在控制层的AOP进行处理 //使用新的request顶替旧的request filterChain.doFilter(requestWrapper, response); } @Override public void destroy() { } }

我里面的代码可以自己增删,最重要的代码为

        ServletRequest requestWrapper = null;
        if(request instanceof HttpServletRequest){
            requestWrapper = new ReaderReuseHttpServletRequestWrapper(request);
        }
        //使用新的request顶替旧的request
        filterChain.doFilter(requestWrapper, response);

3、操作演示

public class BaseDTO implements Serializable {
    private Integer code;
    private String msg;

    private T result;
//.....setget自己补全
}

public class PageDTO extends BaseDTO {
    @Min(value = 1,message = "请求页数不能小于1")
    /**
     * 第几页
     */
    private int pageno;
    @Min(value = 0,message = "每页请求不能小于0")
    @Max(value = 100,message = "每页请求不能超过100")
    /**
     * 每页个数
     */
    private int pagesize;
    /**
     * 总数
     */
    private Long total;
//.....setget自己补全
}
public class SysuserDTO {
    /**
     * 身份证号码
     */
    @NotBlank(message = "身份证号码不能为空")
    private String idnumber;
    /**
     * 联系电话
     */
    @NotBlank(message = "手机号码不能为空")
    private String tel;
//。。。。。set get 自己补全
}
public class SysuserDTO2 extends PageDTO {
    /**
     * 身份证号码
     */
    private String idnumber;
    /**
     * 联系电话
     */
    private String tel;
//。。。。。set get 自己补全
}

 (1)旧的操作

@RestController
@RequestMapping("/user")
public class SysuserController {
    //这边services我就不写了
    @AutoWired
    UserServices userServices;


    /**
    * 假设是根据用户的身份证和电话号码来搜索满足条件的用户列表
    */
    @PostMapping("/userList")
    public BaseDTO userList(@RequestBody SysUserDTO2 dto){
        //StringUntil.isEmpty()自己封装的一个判空方法
        if(StringUntil.isEmpty(dto.getIdnumber())){
            //ReturnUtil 自己封装的一个统一返回信息格式
            //ResponseEnum.RESPONSE_CODE_300.getCode()这是返回的状态码  自己定义的
            ReturnUtil.error(ResponseEnum.RESPONSE_CODE_300.getCode(),"身份证号码不能为空");
        }
        if(StringUntil.isEmpty(dto.getTel())){
            return ReturnUtil.error(ResponseEnum.RESPONSE_CODE_300.getCode(),"手机号码不能为空");
        }
        //现在看起来好像不怎么繁琐 但是如果你有十几个字段需要判断呢 岂不是累死而且代码可读性极差
        return userServices.userList(dto);
    }
}

(2)骚操作

单个参数的我就不贴了,只贴多个参数。

@RestController
@RequestMapping("/user")
public class SysuserController {
    //这边services我就不写了
    @AutoWired
    UserServices userServices;


    /**
    * 假设是根据用户的身份证和电话号码来搜索满足条件的用户列表
    */
    @PostMapping("/userList")
    public BaseDTO userList(@Valid @RequestBody SysUserDTO dto,
           BindingResult result1, @Valid @RequestBody PageDTO pageDTO,
                                      BindingResult result2){
        if (result1.hasErrors()||result2.hasErrors()) {
            String strMsg = result1.hasErrors()?result1.getFieldError().getDefaultMessage():
                    result2.getFieldError().getDefaultMessage();
            //ReturnUtil这边我是封装了一个统一的返回信息格式
            //ResponseEnum.RESPONSE_CODE_300.getCode()这是返回的状态码  自己定义的
            ReturnUtil.error(ResponseEnum.RESPONSE_CODE_300.getCode(),strMsg);
        }
        return userServices.userList(dto,pageDTO);
    }
}

(3)@MultiRequestBody 的操作

大神的操作下次有空再贴出来(其实这样说的话基本上没空),很晚了 睡觉。

 

后面的演示代码手打,有什么错误请谅解,然后顺便提醒我一下,Java小萌新致上。

你可能感兴趣的:(Java,springboot)