3、Spring Boot与Web开发:RESTful API设计、@RestController、@RequestMapping、参数接收与校验

好,咱们今天聊聊Web开发中最核心的一块——RESTful API。说实话,我见过太多项目,API设计得乱七八糟,前端和后端互相甩锅。其实只要掌握几个关键点,这事儿就能捋顺。

3.1 什么是RESTful?别把它想得太玄乎

RESTful说白了就是一种API的设计风格。它不是协议,也不是标准,就是一套约定。核心思想就一句话:把后端操作映射成对资源的增删改查

举个例子。你要操作用户数据,传统做法可能是这样:

  • /getUser?id=1 —— 查用户
  • /createUser —— 创建用户
  • /deleteUser?id=1 —— 删用户

RESTful风格会怎么设计?

  • GET /users/1 —— 查用户
  • POST /users —— 创建用户
  • DELETE /users/1 —— 删用户

看出来没?资源是名词(users),操作是HTTP方法(GET/POST/PUT/DELETE)。我个人习惯把URL看作「名词路径」,把HTTP方法看作「动词」。这样设计出来的API,看一眼就知道在干什么。

核心原则:

  • 使用名词复数表示资源集合:/users/orders
  • 使用HTTP方法表示操作:GET查、POST增、PUT全量改、PATCH部分改、DELETE删
  • 资源间的关系用路径层级表示:/users/1/orders
  • 查询参数用于过滤、排序、分页:/users?page=1&size=20&sort=name

我的经验:别把动词塞进URL里。我见过有人写 /getAllUsers/deleteUserById,这其实违背了RESTful的初衷。用HTTP方法区分操作,URL只描述资源。

3.2 @RestController —— 你的API入口

在Spring Boot里,写RESTful API最常用的就是@RestController。它其实是@Controller + @ResponseBody的组合。什么意思呢?就是每个方法的返回值,都会直接序列化成JSON(或XML)写入HTTP响应体。

我记得刚用Spring Boot那会儿,有人问我:为什么不用@Controller?我说可以啊,但你每个方法都得加@ResponseBody,多麻烦。用@RestController,省心。

@RestController
@RequestMapping("/api/users")
public class UserController {

    // 这里所有方法都默认返回JSON
    // 不需要在每个方法上加 @ResponseBody
}

注意:如果你某个方法需要返回视图(比如JSP页面),那就不能用@RestController了,得用@Controller。我曾经在一个纯前后端分离的项目里,不小心把返回视图的方法也写进了@RestController,结果前端拿到了一堆JSON字符串……嗯,排查了半天。

3.3 @RequestMapping 及其变体

@RequestMapping是Spring MVC中最基础的映射注解。它可以加在类上,也可以加在方法上。但说实话,我平时用得更多的是它的变体:

注解 等价于 用途
@GetMapping @RequestMapping(method = GET) 查询资源
@PostMapping @RequestMapping(method = POST) 创建资源
@PutMapping @RequestMapping(method = PUT) 全量更新资源
@DeleteMapping @RequestMapping(method = DELETE) 删除资源
@PatchMapping @RequestMapping(method = PATCH) 部分更新资源

你想想看,用@GetMapping是不是比@RequestMapping(method = RequestMethod.GET)简洁多了?代码可读性也更好。

@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping
    public List<User> getAllUsers() {
        // 查询所有用户
    }

    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        // 根据ID查询用户
    }

    @PostMapping
    public User createUser(@RequestBody User user) {
        // 创建用户
    }

    @PutMapping("/{id}")
    public User updateUser(@PathVariable Long id, @RequestBody User user) {
        // 全量更新用户
    }

    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        // 删除用户
    }
}

我的习惯:类上统一用@RequestMapping定义基础路径,方法上用具体的Mapping注解。这样整个Controller的URL结构一目了然。

3.4 参数接收 —— 五花八门的方式

写API,免不了要接收前端传过来的参数。Spring Boot提供了好几种方式,我挑最常用的几个说说。

3.4.1 @PathVariable —— 路径参数

用于接收URL路径中的变量。比如/users/123中的123

@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
    // id = 123
}

3.4.2 @RequestParam —— 查询参数

用于接收URL问号后面的参数。比如/users?page=1&size=20

@GetMapping
public List<User> getUsers(@RequestParam(defaultValue = "1") int page,
                           @RequestParam(defaultValue = "20") int size) {
    // page = 1, size = 20
}

3.4.3 @RequestBody —— 请求体参数

用于接收JSON格式的请求体。POST、PUT、PATCH请求常用。

@PostMapping
public User createUser(@RequestBody @Valid User user) {
    // user 对象由JSON自动反序列化
}

3.4.4 @RequestHeader —— 请求头参数

用于接收HTTP头中的信息,比如Token、语言偏好等。

@GetMapping("/me")
public User getCurrentUser(@RequestHeader("Authorization") String token) {
    // 从token中解析用户信息
}

避坑指南:我曾经遇到过一个bug,前端传的参数名是user_name,后端用@RequestParam("userName")接收,结果一直拿不到值。后来发现是参数名没对上。记住:@RequestParam默认要求参数名完全匹配。如果前端传的是下划线风格,后端可以用@RequestParam("user_name"),或者配置Spring Boot的spring.jackson.property-naming-strategy来统一转换。

3.5 参数校验 —— 别让脏数据进系统

说实话,我最怕的就是不做参数校验的项目。前端传个空字符串、负数、超长文本,后端直接往数据库里塞……后果可想而知。

Spring Boot提供了@Valid + Bean Validation(JSR-380)来做参数校验。你只需要在实体类的字段上加注解,然后在Controller方法参数上加@Valid就行。

public class User {

    @NotBlank(message = "用户名不能为空")
    @Size(min = 2, max = 20, message = "用户名长度2-20个字符")
    private String name;

    @NotNull(message = "年龄不能为空")
    @Min(value = 0, message = "年龄不能小于0")
    @Max(value = 150, message = "年龄不能大于150")
    private Integer age;

    @Email(message = "邮箱格式不正确")
    private String email;

    // getter/setter 省略
}

Controller里这样用:

@PostMapping
public User createUser(@RequestBody @Valid User user) {
    // 如果校验失败,Spring会自动返回400 Bad Request
    // 并附带详细的错误信息
    return userService.save(user);
}

注意:默认的校验失败返回信息是英文的,而且格式不太友好。我建议你全局处理一下校验异常,返回统一格式的中文错误信息。后面我会专门讲全局异常处理,这里先留个悬念。

常用校验注解一览

注解 作用
@NotBlank 字符串不能为null,且去掉前后空格后长度大于0
@NotEmpty 集合、数组、字符串不能为null,且长度大于0
@NotNull 任何类型都不能为null
@Size 限制字符串、集合、数组的长度范围
@Min / @Max 限制数字的最小/最大值
@Email 校验邮箱格式
@Pattern 正则表达式校验

我的建议:别把所有校验都堆在实体类上。对于特定场景的校验(比如创建用户时密码不能为空,但更新用户时密码可以为空),可以用分组校验(@Validated + groups)。或者干脆在Service层做二次校验。我一般会在Controller层做基础格式校验,在Service层做业务逻辑校验,两层把关,基本不会出问题。

3.6 一个完整的例子

说了这么多,咱们来个完整的例子串一下。

@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        // 路径参数:/api/users/123
        return userService.findById(id);
    }

    @PostMapping
    public User createUser(@RequestBody @Valid User user) {
        // 请求体:{"name":"张三","age":25,"email":"zhangsan@example.com"}
        return userService.save(user);
    }

    @GetMapping
    public List<User> searchUsers(@RequestParam(required = false) String name,
                                   @RequestParam(defaultValue = "1") int page) {
        // 查询参数:/api/users?name=张&page=1
        return userService.search(name, page);
    }
}

你看,一个完整的RESTful API就出来了。路径清晰,方法明确,参数校验也到位。前端拿到这个API文档,基本不用问后端怎么调。

嗯,这一章的内容就到这里。下一章咱们聊聊全局异常处理和统一响应格式——这可是让API更「专业」的关键一步。