《優(yōu)化接口設(shè)計的思路》系列:第一篇—接口參數(shù)的一些彎彎繞繞

前言

大家好!我是sum墨,一個一線的底層碼農(nóng),平時喜歡研究和思考一些技術(shù)相關(guān)的問題并整理成文,限于本人水平,如果文章和代碼有表述不當之處,還請不吝賜教。

作為一名從業(yè)已達六年的老碼農(nóng),我的工作主要是開發(fā)后端Java業(yè)務(wù)系統(tǒng),包括各種管理后臺和小程序等。在這些項目中,我設(shè)計過單/多租戶體系系統(tǒng),對接過許多開放平臺,也搞過消息中心這類較為復(fù)雜的應(yīng)用,但幸運的是,我至今還沒有遇到過線上系統(tǒng)由于代碼崩潰導(dǎo)致資損的情況。這其中的原因有三點:一是業(yè)務(wù)系統(tǒng)本身并不復(fù)雜;二是我一直遵循某大廠代碼規(guī)約,在開發(fā)過程中盡可能按規(guī)約編寫代碼;三是經(jīng)過多年的開發(fā)經(jīng)驗積累,我成為了一名熟練工,掌握了一些實用的技巧。

接口參數(shù)是導(dǎo)致很多BUG產(chǎn)生的始作俑者,原因在于接口參數(shù)有3多:接口參數(shù)的取值地方多,如查詢參數(shù)(Query Parameters)、路徑參數(shù)(Path Parameters)、請求體(Request Body)等;數(shù)據(jù)類型多,如數(shù)字、字符、日期、文件等;判斷情況多,如空值判斷、格式判斷、大小判斷等;

本文參考項目源碼地址:summo-springboot-interface-demo

由于文章經(jīng)常被抄襲,開源的代碼甚至被當成收費項,所以源碼里面不是全部代碼,有需要的同學(xué)可以留個郵箱,我給你單獨發(fā)!

一、接口參數(shù)的取值

1. 放在查詢參數(shù)和請求體里

a、方法參數(shù)

示例代碼如下:

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

調(diào)用請求:http://localhost:8080/testParams1?param1=111&param2=222
返回如下:

image.png

沒啥坑。

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 + '\'' +
            '}';
    }
}

調(diào)用請求:http://localhost:8080/testParams2?param1=111&param2=222
返回如下:

image.png

這種有一個坑,Spring默認使用無參構(gòu)造函數(shù)來實例化對象,所以ParamsReq不能是接口、抽象類等特殊類。

POST請求

示例代碼如下:

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

ParamsReq類代碼同上

  • 調(diào)用方式1:參數(shù)放在鏈接上
image.png

和GET請求類似,沒啥坑。

  • 調(diào)用方式2:放在Form表單中,content-type為application/x-www-form-urlencoded


    image.png

沒啥坑。

  • 調(diào)用方式3:放在body參數(shù)中,content-type為application/json
image.png

這里有坑了,當content-type為application/json時,接口參數(shù)取值為空。這時就需要在參數(shù)前加上一個注解:@RequestBody,原因是通過@RequestBody注解,Spring Boot可以自動地將請求體中的JSON數(shù)據(jù)轉(zhuǎn)換為Java對象,從而方便地進行數(shù)據(jù)的處理和轉(zhuǎn)換。如果不加@RequestBody注解,Spring Boot默認會將請求體中的JSON數(shù)據(jù)作為普通的表單數(shù)據(jù)來處理,而不會自動轉(zhuǎn)換為Java對象。:

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

2. 放在路徑參數(shù)上

示例代碼如下:

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

參數(shù)使用{}框起來,然后使用@PathVariable即可獲取到值,坑不多。

3. 放在請求頭和Cookie

這兩種情況里面的參數(shù)主要是標識類的參數(shù)如userToken,一般都是不變的,業(yè)務(wù)中很少使用到。

二、接口參數(shù)的類型

1. 數(shù)字、字符串

沒啥坑。

2. 日期

示例代碼如下:

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

這里有個問題,這樣的接口前端怎么傳這個date值,字符串?時間戳?我已經(jīng)替大家試過了,都不行,接口直接報400。

正確的做法是在日期參數(shù)前加上@DateTimeFormat注解,改成這樣就行了:public ResponseEntity<String> testParams5(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date date)。
傳參的話傳字符串:2023-09-14 00:00:00 即可

image.png

3. 列表

示例代碼如下:

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

這串代碼不用測試,它本身就是錯誤的,前面說過Spring默認使用無參構(gòu)造函數(shù)來實例化對象,但是List是一個接口,沒有無參構(gòu)造函數(shù)。
為了解決這個問題,可以使用Spring的@RequestParam注解來指定參數(shù)名,并將多個參數(shù)值綁定到一個List對象中。

修改代碼如下:public String testParams6(@RequestParam("paramList") List<Integer> paramList) 即可
然后,通過使用逗號分隔的參數(shù)值來訪問接口,如:http://localhost:8080/testParams6?paramList=1,2,3
這樣就可以成功傳遞參數(shù)列表并訪問接口了。

image.png

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);
        }
    }
}

上傳接口后端其實還好,主要是前端需要處理的內(nèi)容多一些,由于MultipartFile類也是一個接口,所以這里也需要加上@RequestParam注解。

image.png

三、接口參數(shù)的判斷

前面提到的@RequestBody、@RequestParam注解都是SpringBoot自帶的,它們主要的功能是將請求參數(shù)轉(zhuǎn)換為我們接口定義的變量或者Java對象,而校驗參數(shù)值是否合法通常有下面幾種做法:

  • 自己寫校驗邏輯,一般是配合使用Assert進行參數(shù)校驗
  • 使用javax.validation包的校驗注解,如@NotNull@NotBlank

這里主要講一下javax.validation如何使用!

1. pom.xml引入

<!-- 接口參數(shù)校驗 -->
<dependency>
  <groupId>javax.validation</groupId>
  <artifactId>validation-api</artifactId>
  <version>2.0.1.Final</version>
</dependency>

2. 注解分類

a. 空值檢查

注解 說明 使用頻率
@NotNull 不能為null,常用于數(shù)字、日期 常用
@NotBlank 不能為null也不能為空,常用于字符串 常用
@NotEmpty 集合不能為空,常用于List、Map、Set 常用

b. 數(shù)值檢查

注解 說明 使用頻率
@Max 被注釋的元素必須小于等于指定的值 常用
@Min 被注釋的元素必須大于等于指定的值 常用
@Positive 被注釋的元素必須是正數(shù) 不常用
@Negative 被注釋的元素必須是負數(shù) 不常用

c. Boolean 檢查

注解 說明 使用頻率
@AssertFalse 被注釋的元素必須是false 常用
@AssertTrue 被注釋的元素必須是true 常用

d. 日期檢查

注解 說明 使用頻率
@Future 被注釋的元素必須是將來的日期 不常用
@Past 被注釋的元素必須是過去的日期 不常用

e. 日期檢查

注解 說明 使用頻率
@Email 被注釋的元素必須是電子郵箱地址 常用
@Pattern 被注釋的元素必須是符合正則表達式,我經(jīng)常使用這個判斷手機號是否合法 常用

3. 使用方法

下面是一個經(jīng)典的案例

@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;
}

這些東西看看就行了,用的時候翻一下文檔就行,記也記不住。

四、一些可以直接獲取到的參數(shù)

  • HttpServletRequest:用于獲取HTTP請求的相關(guān)信息,包括請求頭、請求參數(shù)、請求方法等。
  • HttpServletResponse:用于控制HTTP響應(yīng),包括設(shè)置響應(yīng)狀態(tài)碼、設(shè)置響應(yīng)頭、發(fā)送響應(yīng)內(nèi)容等。
  • HttpSession:用于獲取當前會話的信息,可以用來存儲和獲取會話級別的數(shù)據(jù)。
  • Principal:用于獲取當前用戶的身份信息,通常用于認證和授權(quán)。
  • Model/ModelMap:用于在請求處理方法中傳遞數(shù)據(jù)給前端視圖。
  • BindingResult:用于獲取請求參數(shù)綁定和驗證的結(jié)果,包含了校驗的錯誤信息。
  • Locale:用于獲取當前請求的語言環(huán)境,可以用來進行國際化處理。
  • MultipartFile(或者List):用于處理上傳的文件,包括文件的名稱、大小、內(nèi)容等。
  • RedirectAttributes:用于在重定向時傳遞數(shù)據(jù)給目標頁面。
  • ServletRequest/ServletResponse:HttpServletRequest/HttpServletResponse的父類,可以使用其提供的通用方法。
  • @ModelAttribute注解:用于獲取請求參數(shù),并將其綁定到一個對象上。

這些對象可以直接在接口參數(shù)上使用,通過框架自動注入的方式獲取其實例。在使用時,需要保證框架已經(jīng)正確配置和啟用了對應(yīng)的注解和攔截器。用的最多的就是HttpServletRequest和HttpServletResponse了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容