《优化接口设计的思路》——接口参数的一些弯弯绕绕
本文参考项目源码地址:summo-springboot-interface-demo
前言大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。
作为一名从业已达六年的老码农,我的工作主要是开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。
接口参数是导致很多BUG产生的始作俑者,原因在于接口参数有3多:接口参数的取值地方多,如查询参数(Query Parameters)、路径参数(Path Parameters)、请求体(Request Body)等;数据类型多,如数字、字符、日期、文件等;判断情况多,如空值判断、格式判断、大小判断等;
一、接口参数的取值1. 放在查询参数和请求体里 a、方法参数示例代码如下:
@GetMapping("/testParams1")
publicResponseEntityStringtestParams1(Stringparam1,Integerparam2){
returnResponseEntity.ok(MessageFormat.format("param1:[{0}];param2:[{1}]",param1,param2));
}
调用请求:http://localhost:8080/testParams1?param1=111¶m2=222返回如下:
没啥坑。
b、请求对象GET请求示例代码如下
@GetMapping("/testParams2")
publicResponseEntityStringtestParams2(ParamsReqparamsReq){
returnResponseEntity.ok(MessageFormat.format("param1:[{0}];param2:[{1}]",paramsReq.getParam1(),paramsReq.getParam2()));
}
ParamsReq.java
publicclassParamsReq{
privateStringparam1;
privateStringparam2;
publicParamsReq(){
}
publicParamsReq(Stringparam1,Stringparam2){
this.param1=param1;
this.param2=param2;
}
publicStringgetParam1(){
returnparam1;
}
publicvoidsetParam1(Stringparam1){
this.param1=param1;
}
publicStringgetParam2(){
returnparam2;
}
publicvoidsetParam2(Stringparam2){
this.param2=param2;
}
@Override
publicStringtoString(){
return"ParamsReq{"+
"param1='"+param1+'\''+
",param2='"+param2+'\''+
'}';
}
}
调用请求:http://localhost:8080/testParams2?param1=111¶m2=222返回如下:
这种有一个坑,Spring默认使用无参构造函数来实例化对象,所以ParamsReq不能是接口、抽象类等特殊类。
POST请求示例代码如下:
@PostMapping("/testParams3")
publicResponseEntityStringtestParams3(ParamsReqparamsReq){
returnResponseEntity.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 ResponseEntityString testParams3(@RequestBody ParamsReq paramsReq)
2. 放在路径参数上 示例代码如下:
@GetMapping("/testParams4/{pathParam}")
publicResponseEntityStringtestParams4(@PathVariable("pathParam")StringpathParam){
returnResponseEntity.ok(MessageFormat.format("pathParam:[{0}];",pathParam));
}
参数使用{和}框起来,然后使用@PathVariable即可获取到值,坑不多。
3. 放在请求头和Cookie 这两种情况里面的参数主要是标识类的参数如userToken,一般都是不变的,业务中很少使用到。
二、接口参数的类型1. 数字、字符串 没啥坑。
2. 日期 示例代码如下:
@GetMapping("/testParams5")
publicResponseEntityStringtestParams5(Datedate){
returnResponseEntity.ok(MessageFormat.format("pathParam:[{0}];",date));
}
这里有个问题,这样的接口前端怎么传这个date值,字符串?时间戳?我已经替大家试过了,都不行,接口直接报400。
正确的做法是在日期参数前加上@DateTimeFormat注解,改成这样就行了:public ResponseEntityString testParams5(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date date)。传参的话传字符串:2023-09-14 00:00:00 即可
3. 列表 示例代码如下:
@GetMapping("/testParams6")
publicResponseEntityStringtestParams6(ListIntegerparamList){
returnResponseEntity.ok(MessageFormat.format("paramList:[{0}];",paramList));
}
这串代码不用测试,它本身就是错误的,前面说过Spring默认使用无参构造函数来实例化对象,但是List是一个接口,没有无参构造函数。为了解决这个问题,可以使用Spring的@RequestParam注解来指定参数名,并将多个参数值绑定到一个List对象中。
修改代码如下:public String testParams6(@RequestParam("paramList") ListInteger paramList) 即可
然后,通过使用逗号分隔的参数值来访问接口,如:http://localhost:8080/testParams6?paramList=1,2,3这样就可以成功传递参数列表并访问接口了。
4. 文件 先写一个简单上传界面
upload.html
!DOCTYPEhtml
html
head
titleFileUploadDemo/title
/head
body
h1FileUploadDemo/h1
formaction="http://localhost:8080/upload"method="post"enctype="multipart/form-data"
inputtype="file"name="file"/
br/br/
inputtype="submit"value="Upload"/
/form
/body
/html
后端上传代码
FileUploadController.java
importorg.springframework.http.HttpStatus;
importorg.springframework.http.ResponseEntity;
importorg.springframework.stereotype.Controller;
importorg.springframework.web.bind.annotation.PostMapping;
importorg.springframework.web.bind.annotation.RequestParam;
importorg.springframework.web.multipart.MultipartFile;
@Controller
publicclassFileUploadController{
@PostMapping("/upload")
publicResponseEntityStringuploadFile(@RequestParam("file")MultipartFilefile){
//Checkiffileisempty
if(file.isEmpty()){
returnnewResponseEntity("Fileisempty",HttpStatus.BAD_REQUEST);
}
//Savethefile
try{
byte[]bytes=file.getBytes();
//Logictosavethefiletoadesiredlocation
returnnewResponseEntity("Fileuploadedsuccessfully",HttpStatus.OK);
}catch(Exceptione){
returnnewResponseEntity("Failedtouploadfile",HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
上传接口后端其实还好,主要是前端需要处理的内容多一些,由于MultipartFile类也是一个接口,所以这里也需要加上@RequestParam注解。
三、接口参数的判断前面提到的@RequestBody、@RequestParam注解都是SpringBoot自带的,它们主要的功能是将请求参数转换为我们接口定义的变量或者Java对象,而校验参数值是否合法通常有下面几种做法:
自己写校验逻辑,一般是配合使用Assert进行参数校验使用javax.validation包的校验注解,如@NotNull、@NotBlank这里主要讲一下javax.validation如何使用!
1. pom.xml引入 !--接口参数校验--
dependency
groupIdjavax.validation/groupId
artifactIdvalidation-api/artifactId
version2.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
publicclassStudentReq{
@NotBlank(message="主键不能为空")
privateString
@NotBlank(message="名字不能为空")
@Size(min=2,max=4,message="名字字符长度必须为2~4个")
privateStringname;
@Pattern(regexp="^1[3456789]\\d{9}$",message="手机号格式错误")
privateStringphone;
@Email(message="邮箱格式错误")
privateStringemail;
@Past(message="生日必须早于当前时间")
privateDatebirth;
@Min(value=0,message="年龄必须为0~100")
@Max(value=100,message="年龄必须为0~100")
privateInteger
@PositiveOrZero
privateDoublescore;
}
这些东西看看就行了,用的时候翻一下文档就行,记也记不住。
四、一些可以直接获取到的参数HttpServletRequest:用于获取HTTP请求的相关信息,包括请求头、请求参数、请求方法等。HttpServletResponse:用于控制HTTP响应,包括设置响应状态码、设置响应头、发送响应内容等。HttpSession:用于获取当前会话的信息,可以用来存储和获取会话级别的数据。Principal:用于获取当前用户的身份信息,通常用于认证和授权。Model/ModelMap:用于在请求处理方法中传递数据给前端视图。BindingResult:用于获取请求参数绑定和验证的结果,包含了校验的错误信息。Locale:用于获取当前请求的语言环境,可以用来进行国际化处理。MultipartFile(或者List):用于处理上传的文件,包括文件的名称、大小、内容等。RedirectAttributes:用于在重定向时传递数据给目标页面。ServletRequest/ServletResponse:HttpServletRequest/HttpServletResponse的父类,可以使用其提供的通用方法。@ModelAttribute注解:用于获取请求参数,并将其绑定到一个对象上。这些对象可以直接在接口参数上使用,通过框架自动注入的方式获取其实例。在使用时,需要保证框架已经正确配置和启用了对应的注解和拦截器。用的最多的就是HttpServletRequest和HttpServletResponse了。
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线