《优化接口设计的思路》系列:第一篇—接口参数的一些弯弯绕绕

前言

大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。

作为一名从业已达六年的老码农,我的工作主要是开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。

接口参数是导致很多BUG产生的始作俑者,原因在于接口参数有3多:接口参数的取值地方多,如查询参数(Query Parameters)、路径参数(Path Parameters)、请求体(Request Body)等;数据类型多,如数字、字符、日期、文件等;判断情况多,如空值判断、格式判断、大小判断等;

本文参考项目源码地址:summo-springboot-interface-demo

由于文章经常被抄袭,开源的代码甚至被当成收费项,所以源码里面不是全部代码,有需要的同学可以留个邮箱,我给你单独发!

一、接口参数的取值

1. 放在查询参数和请求体里

a、方法参数

示例代码如下:

@GetMapping("/testParams1") public ResponseEntity<String> testParams1(String param1, Integer param2) { return ResponseEntity.ok(MessageFormat.format("param1:[{0}];param2:[{1}]", param1, param2)); }

调用请求:http://localhost:8080/testParams1?param1=111&param2=222 返回如下:

没啥坑。

b、请求对象

GET请求

示例代码如下

@GetMapping("/testParams2") public ResponseEntity<String> testParams2(ParamsReq paramsReq) { return ResponseEntity.ok(MessageFormat.format("param1:[{0}];param2:[{1}]", paramsReq.getParam1(), paramsReq.getParam2())); }

ParamsReq.java

public class ParamsReq { private String param1; private String param2; public ParamsReq() { } public ParamsReq(String param1, String param2) { this.param1 = param1; this.param2 = param2; } public String getParam1() { return param1; } public void setParam1(String param1) { this.param1 = param1; } public String getParam2() { return param2; } public void setParam2(String param2) { this.param2 = param2; } @Override public String toString() { return "ParamsReq{" + "param1='" + param1 + '\'' + ", param2='" + param2 + '\'' + '}'; } }

调用请求:http://localhost:8080/testParams2?param1=111&param2=222 返回如下:

这种有一个坑,Spring默认使用无参构造函数来实例化对象,所以ParamsReq不能是接口、抽象类等特殊类。

POST请求

示例代码如下:

@PostMapping("/testParams3") public ResponseEntity<String> testParams3(ParamsReq paramsReq) { return ResponseEntity.ok(MessageFormat.format("param1:[{0}];param2:[{1}]", paramsReq.getParam1(), paramsReq.getParam2())); }

ParamsReq类代码同上

  • 调用方式1:参数放在链接上

和GET请求类似,没啥坑。

  • 调用方式2:放在Form表单中,content-type为application/x-www-form-urlencoded

没啥坑。

  • 调用方式3:放在body参数中,content-type为application/json

这里有坑了,当content-type为application/json时,接口参数取值为空。这时就需要在参数前加上一个注解:@RequestBody,原因是通过@RequestBody注解,Spring Boot可以自动地将请求体中的JSON数据转换为Java对象,从而方便地进行数据的处理和转换。如果不加@RequestBody注解,Spring Boot默认会将请求体中的JSON数据作为普通的表单数据来处理,而不会自动转换为Java对象。:

改成这样就行:public ResponseEntity<String> testParams3(@RequestBody ParamsReq paramsReq)

2. 放在路径参数上

示例代码如下:

@GetMapping("/testParams4/{pathParam}") public ResponseEntity<String> testParams4(@PathVariable("pathParam") String pathParam) { return ResponseEntity.ok(MessageFormat.format("pathParam:[{0}];", pathParam)); }

参数使用{}框起来,然后使用@PathVariable即可获取到值,坑不多。

3. 放在请求头和Cookie

这两种情况里面的参数主要是标识类的参数如userToken,一般都是不变的,业务中很少使用到。

二、接口参数的类型

1. 数字、字符串

没啥坑。

2. 日期

示例代码如下:

@GetMapping("/testParams5") public ResponseEntity<String> testParams5(Date date) { return ResponseEntity.ok(MessageFormat.format("pathParam:[{0}];", date)); }

这里有个问题,这样的接口前端怎么传这个date值,字符串?时间戳?我已经替大家试过了,都不行,接口直接报400。

正确的做法是在日期参数前加上@DateTimeFormat注解,改成这样就行了:public ResponseEntity<String> testParams5(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date date)。 传参的话传字符串:2023-09-14 00:00:00 即可

3. 列表

示例代码如下:

@GetMapping("/testParams6") public ResponseEntity<String> testParams6(List<Integer> paramList) { return ResponseEntity.ok(MessageFormat.format("paramList:[{0}];", paramList)); }

这串代码不用测试,它本身就是错误的,前面说过Spring默认使用无参构造函数来实例化对象,但是List是一个接口,没有无参构造函数。 为了解决这个问题,可以使用Spring的@RequestParam注解来指定参数名,并将多个参数值绑定到一个List对象中。

修改代码如下:public String testParams6(@RequestParam("paramList") List<Integer> paramList) 即可 然后,通过使用逗号分隔的参数值来访问接口,如:http://localhost:8080/testParams6?paramList=1,2,3 这样就可以成功传递参数列表并访问接口了。

4. 文件

先写一个简单上传界面 upload.html

<!DOCTYPE html> <html> <head> <title>File Upload Demo</title> </head> <body> <h1>File Upload Demo</h1> <form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data"> <input type="file" name="file" /> <br/><br/> <input type="submit" value="Upload" /> </form> </body> </html>

后端上传代码 FileUploadController.java

import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; @Controller public class FileUploadController { @PostMapping("/upload") public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) { // Check if file is empty if (file.isEmpty()) { return new ResponseEntity<>("File is empty", HttpStatus.BAD_REQUEST); } // Save the file try { byte[] bytes = file.getBytes(); // Logic to save the file to a desired location return new ResponseEntity<>("File uploaded successfully", HttpStatus.OK); } catch (Exception e) { return new ResponseEntity<>("Failed to upload file", HttpStatus.INTERNAL_SERVER_ERROR); } } }

上传接口后端其实还好,主要是前端需要处理的内容多一些,由于MultipartFile类也是一个接口,所以这里也需要加上@RequestParam注解。

三、接口参数的判断

前面提到的@RequestBody@RequestParam注解都是SpringBoot自带的,它们主要的功能是将请求参数转换为我们接口定义的变量或者Java对象,而校验参数值是否合法通常有下面几种做法:

  • 自己写校验逻辑,一般是配合使用Assert进行参数校验
  • 使用javax.validation包的校验注解,如@NotNull@NotBlank

这里主要讲一下javax.validation如何使用!

1. pom.xml引入

<!-- 接口参数校验 --> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>2.0.1.Final</version> </dependency>

2. 注解分类

a. 空值检查

注解说明使用频率
@NotNull不能为null,常用于数字、日期常用
@NotBlank不能为null也不能为空,常用于字符串常用
@NotEmpty集合不能为空,常用于List、Map、Set常用

b. 数值检查

注解说明使用频率
@Max被注释的元素必须小于等于指定的值常用
@Min被注释的元素必须大于等于指定的值常用
@Positive被注释的元素必须是正数不常用
@Negative被注释的元素必须是负数不常用

c. Boolean 检查

注解说明使用频率
@AssertFalse被注释的元素必须是false常用
@AssertTrue被注释的元素必须是true常用

d. 日期检查

注解说明使用频率
@Future被注释的元素必须是将来的日期不常用
@Past被注释的元素必须是过去的日期不常用

e. 日期检查

注解说明使用频率
@Email被注释的元素必须是电子邮箱地址常用
@Pattern被注释的元素必须是符合正则表达式,我经常使用这个判断手机号是否合法常用

3. 使用方法

下面是一个经典的案例

@Data public class StudentReq { @NotBlank(message = "主键不能为空") private String id; @NotBlank(message = "名字不能为空") @Size(min = 2, max = 4, message = "名字字符长度必须为 2~4个") private String name; @Pattern(regexp = "^1[3456789]\\d{9}$", message = "手机号格式错误") private String phone; @Email(message = "邮箱格式错误") private String email; @Past(message = "生日必须早于当前时间") private Date birth; @Min(value = 0, message = "年龄必须为 0~100") @Max(value = 100, message = "年龄必须为 0~100") private Integer age; @PositiveOrZero private Double score; }

这些东西看看就行了,用的时候翻一下文档就行,记也记不住。

四、一些可以直接获取到的参数

  • HttpServletRequest:用于获取HTTP请求的相关信息,包括请求头、请求参数、请求方法等。
  • HttpServletResponse:用于控制HTTP响应,包括设置响应状态码、设置响应头、发送响应内容等。
  • HttpSession:用于获取当前会话的信息,可以用来存储和获取会话级别的数据。
  • Principal:用于获取当前用户的身份信息,通常用于认证和授权。
  • Model/ModelMap:用于在请求处理方法中传递数据给前端视图。
  • BindingResult:用于获取请求参数绑定和验证的结果,包含了校验的错误信息。
  • Locale:用于获取当前请求的语言环境,可以用来进行国际化处理。
  • MultipartFile(或者List):用于处理上传的文件,包括文件的名称、大小、内容等。
  • RedirectAttributes:用于在重定向时传递数据给目标页面。
  • ServletRequest/ServletResponse:HttpServletRequest/HttpServletResponse的父类,可以使用其提供的通用方法。
  • @ModelAttribute注解:用于获取请求参数,并将其绑定到一个对象上。

这些对象可以直接在接口参数上使用,通过框架自动注入的方式获取其实例。在使用时,需要保证框架已经正确配置和启用了对应的注解和拦截器。用的最多的就是HttpServletRequest和HttpServletResponse了。

点赞
收藏

评论区

加载中...

相关推荐

灵感乍现!造了个与众不同的Dubbo注册中心扩展轮子

hello大家好呀,我是小楼。作为一名基础组件开发,服务好每一位业务开发同学是我们的义务(KPI)。客服群里经常有业务开发同学丢来一段代码、一个报错,而我们,当然要微笑服务,耐心解答。有的问题,凭借多年踩坑经验,一眼就能看出;有的问题,看一眼代码也能知道原因,但有的问题,还真就光凭看是看不出来的,这时,只能下载代码,本地跑跑看了。熟悉我的朋友都知道,我从事d

低代码开发平台 | 低代码的衍生历程、优势及未来趋势

通过简单的拖拉拽操作,而不用编写复杂的代码,实现少写代码或者不写代码,就能快速高效完成业务目标。低代码平台演进1.低代码概念低代码是无需编码(0代码)或通过少量代码就可以快速生成应用程序的开发平台。通过可视化进行应用程序开发的方法,具有不同经验水平的开发人员可以通过图形化的用户界面,使用拖拽组件和模型驱动的逻辑来创建网页和移动应用程序。2.低代码衍生历

Spring Boot快速开发企业级Admin管理后台

Erupt可快速的构建管理页面,零前端代码、零CURD、自动建表,仅需单个类文件简洁的注解配置,即可快速开发企业级Admin管理后台!后台管理系统非常重要,但开发存在一定的痛点,如:开发效率低、界面不美观、交互不理想、工作量重复、存在安全漏洞、后端研发被迫写前端代码等。我是程序汪Erupt提供企业级中后台管理系统的全栈解决方案,提供超多业务组

Sql Server之旅——第一站 那些给我们带来福利的系统视图

本来想这个系列写点什么好呢,后来想想大家作为程序员,用的最多的莫过于数据库了,但是事实上很多像我这样工作在一线的码农,对sql都一知半解,别谈优化和对数据库底层的认识了,我也是这样。。。一:那些系统视图1\.系统视图是干什么呢?从名字上看就知道,系统视图嘛?猜的不错的话,就是存放一些sqlserver系统的一些信息,

PHP+MySql实现图书管理系统

这个图书管理系统是我学完PHP时写的一个练手项目,功能参考了自己学校的图书管理系统。为了锻炼自己的动手能力以及加深对代码的理解,前端和后端均由自己完成,前端使用了一些基本的框架(毕竟我主攻后端开发方向),后端大部分要用到的功能都是自己从底层实现并封装,基本没有用到第三方框架。总体来说还是比较简陋的,在某些地方可能存在缺陷或者漏洞。项目地址

Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除

大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这