定制小程序开发费用使用多个 @RequestBody 接收参数传递给 Controller

定制小程序开发费用常规情况下, 因为 request 请求的 body 定制小程序开发费用只能读取一次,我们使用 @RequestBody 定制小程序开发费用只能解析一次,定制小程序开发费用如果在方法参数中增加第二个 @RequestBody 注解的话,stream 定制小程序开发费用流已经关闭,无法读取,返回 400 错误

定制小程序开发费用我们想要这么做:在一个 Controller 提供的接口中,使用多个 @RequestBody 注解接收参数

@RestController@RequestMapping("/demo")public class DemoController {    @RequestMapping(value = "/test", method = RequestMethod.POST)    public String test(@RequestBody Param1 param1, @RequestBody param2 param2) {}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

并使用这样的 JSON 进行请求:

{    "param1":{},    "param2":{}}
  • 1
  • 2
  • 3
  • 4

我们有多个解决办法

方法一:

没有什么是多包一层解决不了的,如果有,那么久再来一层~~~

Controller:

@RestController@RequestMapping("/demo")public class DemoController {    @RequestMapping(value = "/test", method = RequestMethod.POST)    public String test(@RequestBody Param param) {}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

然后请求用这个 JSON

{    "param":{        "param1":{},        "param2":{}    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

嗯……是这样没错了,有问题吗?没有问题。。。。没有什么是套娃解决不了的

方法二:

上面的方法显然没有问题,只要我封装的够多,我就能解决所有问题,当然我们不推荐这么干

虽然 @RequestBody 必须映射到单个对象,但是对象可以是一个 map,所以我们有了第二种方法

@RestController@RequestMapping("/demo")public class DemoController {    @RequestMapping(value = "/test", method = RequestMethod.POST)    public String test(@RequestBody Map<String, String> json) {}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

或者使用 Jackson 的 ObjectNode

@RestController@RequestMapping("/demo")public class DemoController {    @RequestMapping(value = "/test", method = RequestMethod.POST)    public String test(@RequestBody ObjectNode json) {}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这样我们可以很容易的传递一个 JSON 串过去了,反正它会给我们解析成一个个键值对的对吧~~~

{    "param":{        "param1":{},        "param2":{}    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这样做有错吗?当然没错!虽然使用 map 让前端来请求后端接口很方便,既不用定义类,想塞什么数据就放什么,后端统一 map 接收,但我个人还是不太赞成的!!!原因就仁者见仁智者见智了

  • 使用 map 总给我一种面向 JSON 编程的感觉,而不是面向对象
  • 如果使用 map 进行传参的话,不看代码根本不知道里面放了什么,别人也能随便塞一些东西进去
  • 判空?@NotNull 是什么?map 传参还想判空?老老实实手动一个个来吧
  • 每次拿到数据都要自己转一下,还有可能发生类型转换异常
  • 方法调用之间使用 map 的话,不太方便我们维护,而且 JSON 没有携带我们的类型信息,反序列化可能会出现问题
  • 维护不便,参数验证,接口文档等等都是问题呀、、、

方法三:

让我们来试试第三种方法,自定义注解,并将它注册到 Spring MVC,像是这样:

@RestController@RequestMapping("/demo")public class DemoController {    @RequestMapping(value = "/test", method = RequestMethod.POST)    public String test(@MultiRequestBody Param1 param1, @MultiRequestBody Param2 param2) {}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

以下代码使用的 Jackson 进行参数解析,可自行替换为 fastJson

首先自定义一个注解 @MultiRequestBody 当然,取什么名字随意

import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * @author sqd */@Target(ElementType.PARAMETER)@Retention(RetentionPolicy.RUNTIME)public @interface MultiRequestBody {    /**     * 解析时用到的 JSON 中的 key     */    String value() default "";    /**     * 是否必传的参数     */    boolean required() default true;    /**     * 当 value 的值或者参数名不匹配时,是否允许解析最外层属性得到该对象     */    boolean parseAllFields() default true;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

实现 HandlerMethodArgumentResolver,用来解析使用了我们定义了注解的参数

import com.fasterxml.jackson.core.JsonParser;import com.fasterxml.jackson.databind.JsonNode;import com.fasterxml.jackson.databind.ObjectMapper;import com.sqd.annotation.MultiRequestBody;import com.sqd.util.IOUtils;import com.sqd.util.StringUtils;import org.springframework.core.MethodParameter;import org.springframework.util.Assert;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 javax.servlet.http.HttpServletRequest;import java.io.BufferedReader;import java.io.IOException;import java.lang.reflect.Field;import java.util.*;/** * MultiRequestBody 解析器 * 1、支持通过注解的 value 指定 JSON 的 key 来解析对象 * 2、支持通过注解无 value,直接根据参数名来解析对象 * 3、支持基本类型的注入 * 4、支持通过注解无 value 且参数名不匹配 JSON 串的 key 时,根据属性解析对象 * * @author sqd */public class MultiRequestBodyResolver implements HandlerMethodArgumentResolver {    private static final Set<Class> classSet = new HashSet<>(16);    private static ObjectMapper objectMapper = new ObjectMapper();    private static final String JSON_REQUEST_BODY = "JSON_REQUEST_BODY";    static {        classSet.add(Integer.class);        classSet.add(Long.class);        classSet.add(Short.class);        classSet.add(Float.class);        classSet.add(Double.class);        classSet.add(Boolean.class);        classSet.add(Byte.class);        classSet.add(Character.class);    }    /**     * 支持的方法参数类型     *     * @see MultiRequestBody     */    @Override    public boolean supportsParameter(MethodParameter parameter) {        return parameter.hasParameterAnnotation(MultiRequestBody.class);    }    /**     * 参数解析     */    @Override    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {        Object result;        Object value;        // 获取请求体        String requestBody = getRequestBody(webRequest);        // 允许使用不带引号的字段        objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);        // 解析 JSON 串        JsonNode rootNode = objectMapper.readTree(requestBody);        // JSON 串为空抛出异常        Assert.notNull(rootNode, String.format("param %s parsing failed", requestBody));        // 获取注解        MultiRequestBody multiRequestBody = parameter.getParameterAnnotation(MultiRequestBody.class);        Assert.notNull(multiRequestBody, String.format("param %s parsing failed", requestBody));        String key = multiRequestBody.value();        // 根据注解 value 解析 JSON 串,如果没有根据参数的名字解析 JSON        if (StringUtils.isNotBlank(key)) {            value = rootNode.get(key);            // 如果为参数必填但未根据 key 成功得到对应 value 抛出异常            Assert.isTrue(multiRequestBody.required() && Objects.nonNull(value), String.format("required param %s is not present", key));        } else {            key = parameter.getParameterName();            value = rootNode.get(key);        }        // 获取参数的类型        Class<?> paramType = parameter.getParameterType();        // 成功从 JSON 解析到对应 key 的 value        if (Objects.nonNull(value)) {            return objectMapper.readValue(value.toString(), paramType);        }        // 未从 JSON 解析到对应 key(可能是注解的 value 或者是参数名字) 的值,要么没传值,要么传的名字不对        // 如果参数为基本数据类型,且为必传参数抛出异常        Assert.isTrue(!(isBasicDataTypes(paramType) && multiRequestBody.required()), String.format("required param %s is not present", key));        // 参数非基本数据类型,如果不允许解析外层属性,且为必传参数报错抛出异常        Assert.isTrue(!(!multiRequestBody.parseAllFields() && multiRequestBody.required()), String.format("required param %s is not present", key));        try {            // 既然找不到对应参数,而且非基本类型,我们可以解析外层属性,将整个 JSON 作为参数进行解析。解析失败会抛出异常            result = objectMapper.readValue(requestBody, paramType);        } catch (Exception e) {            throw new RuntimeException(e);        }        // 必填参数的话,看解析出来的参数是否对应,非必填直接返回吧        if (multiRequestBody.required()) {            Field[] declaredFields = paramType.getDeclaredFields();            for (Field field : declaredFields) {                field.setAccessible(true);                Assert.notNull(field.get(result), String.format("required param %s is not present", key));            }        }        return result;    }    /**     * 获取请求 JSON 字符串     */    private String getRequestBody(NativeWebRequest webRequest) {        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);        String jsonBody = (String) webRequest.getAttribute(JSON_REQUEST_BODY, NativeWebRequest.SCOPE_REQUEST);        if (StringUtils.isEmpty(jsonBody)) {            try (BufferedReader reader = servletRequest.getReader()) {                jsonBody = IOUtils.toString(reader);                webRequest.setAttribute(JSON_REQUEST_BODY, jsonBody, NativeWebRequest.SCOPE_REQUEST);            } catch (IOException e) {                throw new RuntimeException(e);            }        }        return jsonBody;    }    /**     * 判断是否为基本数据类型包装类     */    private boolean isBasicDataTypes(Class clazz) {        return classSet.contains(clazz);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143

Web MVC 配置,实现 WebMvcConfigurer 接口,将我们的参数解析器添加,然后交给 Spring 管理

import com.sqd.resolver.MultiRequestBodyResolver;import org.springframework.format.FormatterRegistry;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;import org.springframework.validation.MessageCodesResolver;import org.springframework.validation.Validator;import org.springframework.web.method.support.HandlerMethodArgumentResolver;import org.springframework.web.method.support.HandlerMethodReturnValueHandler;import org.springframework.web.servlet.HandlerExceptionResolver;import org.springframework.web.servlet.config.annotation.*;import java.nio.charset.Charset;import java.util.ArrayList;import java.util.List;public class WebMvcConfig implements WebMvcConfigurer {    /**     * 参数解析器     * @see MultiRequestBodyResolver     */    @Override    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {        resolvers.add(new MultiRequestBodyResolver());    }    @Override    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {        MappingJackson2HttpMessageConverter jacksonConver = new MappingJackson2HttpMessageConverter();        ArrayList<MediaType> mediaTypes = new ArrayList<>();        mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);        jacksonConver.setSupportedMediaTypes(mediaTypes);        jacksonConver.setDefaultCharset(Charset.forName("UTF-8"));        converters.add(jacksonConver);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

让配置被 spring 管理

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;import org.springframework.context.annotation.Bean;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@ConditionalOnClass(WebMvcConfigurer.class)public class SpringMvcConfigurerAutoConfig {    @Bean    @ConditionalOnMissingClass    public WebMvcConfig webMvcConfig(){        return new WebMvcConfig();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在 resource 下创建文件夹 META-INF > spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\    com.sqd.config.SpringMvcConfigurerAutoConfig
  • 1
  • 2

最后附上 gitgub 代码地址:

文章参考:

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发