Spring Boot 一个接口同时支持 form 表单、form-data、json 的优雅写法

x33g5p2x  于2022-07-19 转载在 Spring  
字(4.7k)|赞(0)|评价(0)|浏览(625)

来源:juejin.cn/post/7054441239839506446
网上很多代码都是千篇一律的 cvs,相信我只要你认真看完我写的这篇,你就可以完全掌握这个知识点,这篇文章不适合直接 cvs,一定要先理解。

最近重写个项目遇到个比较棘手的问题,老项目是 PHP 接口,这个接口同时兼容 POST json 和 form 表单,更骚的是连 form-data 也兼容。。。因为写 PHP 请求的对接方代码不严谨。

而在 Java 中,一个接口只支持一种 content-type,json 就用 @RequestBody,form 表单就用 @RequestParam 或不写,form-data 就用 MultipartFile

兼容版本

如果要把在一个接口中同时兼容三种,比较笨的办法就是获取 HttpServletRequest,然后自己再写方法解析。类似如下:

  1. private Map<String, Object> getParams(HttpServletRequest request) {
  2.     String contentType = request.getContentType();
  3.     if (contentType.contains("application/json")) {
  4.         // json 解析...
  5.         return null;
  6.     } else if (contentType.contains("application/x-www-form-urlencoded")) {
  7.         // form 表单解析 ...
  8.         return null;
  9.     } else if (contentType.contains("multipart")) {
  10.         // 文件流解析
  11.         return null;
  12.     } else {
  13.          throw new BizException("不支持的content-type");
  14.     } 
  15. }

但是这样写有弊端

  • 代码很丑,具体到解析代码又臭又长
  • 只能返回固定 map 或者自己重新组装参数类
  • 无法使用 @Valid 校验参数,像我这种几十个参数都要检验的简直是灾难

优雅版本

网上有 form 表单和 json 同时兼容的版本,但是没有兼容 form-data,我在这做一下补充。

1. 自定义注解

  1. @Target(ElementType.PARAMETER)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface GamePHP {
  5. }

2. 自定义注解解析

  1. public class GamePHPMethodProcessor implements HandlerMethodArgumentResolver {
  2.     private GameFormMethodArgumentResolver formResolver;
  3.     private GameJsonMethodArgumentResolver jsonResolver;
  4.     public GamePHPMethodProcessor() {
  5.         List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
  6.         PHPMessageConverter PHPMessageConverter = new PHPMessageConverter();
  7.         messageConverters.add(PHPMessageConverter);
  8.         jsonResolver = new GameJsonMethodArgumentResolver(messageConverters);
  9.         formResolver = new GameFormMethodArgumentResolver();
  10.     }
  11.     @Override
  12.     public boolean supportsParameter(MethodParameter parameter) {
  13.         GamePHP ann = parameter.getParameterAnnotation(GamePHP.class);
  14.         return (ann != null);
  15.     }
  16.     @Override
  17.     public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
  18.         ServletRequest servletRequest = nativeWebRequest.getNativeRequest(ServletRequest.class);
  19.         String contentType = servletRequest.getContentType();
  20.         if (contentType == null) {
  21.             throw new IllegalArgumentException("不支持contentType");
  22.         }
  23.         if (contentType.contains("application/json")) {
  24.             return jsonResolver.resolveArgument(methodParameter, modelAndViewContainer, nativeWebRequest, webDataBinderFactory);
  25.         }
  26.         if (contentType.contains("application/x-www-form-urlencoded")) {
  27.             return formResolver.resolveArgument(methodParameter, modelAndViewContainer, nativeWebRequest, webDataBinderFactory);
  28.         }
  29.         if (contentType.contains("multipart")) {
  30.             return formResolver.resolveArgument(methodParameter, modelAndViewContainer, nativeWebRequest, webDataBinderFactory);
  31.         }
  32.         throw new IllegalArgumentException("不支持contentType");
  33.     }
  34. }

3. 添加到 spring configuration

  1. @Bean
  2.     public MyMvcConfigurer mvcConfigurer() {
  3.         return new MyMvcConfigurer();
  4.     }
  5.     public static class MyMvcConfigurer implements WebMvcConfigurer {
  6.         public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
  7.             resolvers.add(new GamePHPMethodProcessor());
  8.         }
  9.     }

4. form-data 的特殊处理

引入 jar 包

  1. <dependency>
  2.       <groupId>commons-fileupload</groupId>
  3.       <artifactId>commons-fileupload</artifactId>
  4.       <version>1.3.1</version>
  5.     </dependency>
  6.     <dependency>
  7.       <groupId>commons-io</groupId>
  8.       <artifactId>commons-io</artifactId>
  9.       <version>2.4</version>
  10.     </dependency>

新增解析 bean

  1. @Bean(name = "multipartResolver")
  2. public MultipartResolver multipartResolver(){
  3.     CommonsMultipartResolver resolver = new CommonsMultipartResolver();
  4.     resolver.setDefaultEncoding("UTF-8");
  5.     resolver.setResolveLazily(true);//resolveLazily属性启用是为了推迟文件解析,以在在UploadAction中捕获文件大小异常
  6.     resolver.setMaxInMemorySize(40960);
  7.     resolver.setMaxUploadSize(50*1024*1024);//上传文件大小 50M 50*1024*1024
  8.     return resolver;
  9. }

特殊说明,GameJsonMethodArgumentResolver 和 GameFormMethodArgumentResolver 是我们自定义的 json 和 form 解析,如果你没有自定义的,使用 spring 默认的 ServletModelAttributeMethodProcessor 和 RequestResponseBodyMethodProcessor 也可以。

只需将 @RequestParam 注解改为 @GamePHP,接口即可同时兼容三种 content-type

其流程为,spring 启动的时候,MyMvcConfigurer 调用 addArgumentResolvers 方法将 GamePHPMethodProcessor 注入,接到请求时,supportsParameter 方法判断是否使用此 resolver,如果为 true,则进入 resolveArgument 方法执行。

推荐3个原创springboot+Vue项目,有完整视频讲解与文档和源码:

【dailyhub】【实战】带你从0搭建一个Springboot+elasticsearch+canal的完整项目
  • 视频讲解:https://www.bilibili.com/video/BV1Jq4y1w7Bc/
  • 完整开发文档:https://www.zhuawaba.com/post/124
  • 线上演示:https://www.zhuawaba.com/dailyhub
【VueAdmin】手把手教你开发SpringBoot+Jwt+Vue的前后端分离后台管理系统
  • 视频讲解:https://www.bilibili.com/video/BV1af4y1s7Wh/
  • 完整开发文档前端:https://www.zhuawaba.com/post/18
  • 完整开发文档后端:https://www.zhuawaba.com/post/19
  • 线上演示:https://www.markerhub.com/vueadmin/
【VueBlog】基于SpringBoot+Vue开发的前后端分离博客项目完整教学
  • 视频讲解:https://www.bilibili.com/video/BV1PQ4y1P7hZ
  • 完整开发文档:https://www.zhuawaba.com/post/17

关注我,学Java

相关文章