Skip to content
清晨的一缕阳光
返回

Spring Boot RESTful API 开发

前言

RESTful API 是现代 Web 服务的标准设计方式。Spring Boot 提供了强大的支持,让 RESTful API 开发变得简单高效。本文将介绍 RESTful API 的完整开发流程和最佳实践。

RESTful 设计规范

资源命名

推荐:使用名词复数,小写,连字符分隔

// ✅ 推荐
GET /api/users
GET /api/user-profiles
GET /api/order-items

// ❌ 不推荐
GET /api/getUsers
GET /api/user
GET /api/UserProfiles

HTTP 方法

方法说明幂等性
GET查询资源
POST创建资源
PUT更新资源(全量)
PATCH更新资源(部分)
DELETE删除资源

状态码

状态码说明
200 OK请求成功
201 Created资源创建成功
204 No Content删除成功
400 Bad Request请求参数错误
401 Unauthorized未授权
403 Forbidden禁止访问
404 Not Found资源不存在
500 Internal Server Error服务器错误

基础 Controller 开发

1. 创建实体类

package com.example.demo.entity;

import lombok.Data;
import java.time.LocalDateTime;

@Data
public class User {
    private Long id;
    private String username;
    private String email;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
}

2. 创建 DTO

package com.example.demo.dto;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;

@Data
public class UserDTO {
    
    private Long id;
    
    @NotBlank(message = "用户名不能为空")
    private String username;
    
    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;
}

3. 创建 Controller

package com.example.demo.controller;

import com.example.demo.dto.UserDTO;
import com.example.demo.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
import java.util.List;

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    
    private final UserService userService;
    
    /**
     * 获取用户列表
     */
    @GetMapping
    public ResponseEntity<List<UserDTO>> getUsers() {
        List<UserDTO> users = userService.findAll();
        return ResponseEntity.ok(users);
    }
    
    /**
     * 获取单个用户
     */
    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        UserDTO user = userService.findById(id);
        return ResponseEntity.ok(user);
    }
    
    /**
     * 创建用户
     */
    @PostMapping
    public ResponseEntity<UserDTO> createUser(@Valid @RequestBody UserDTO dto) {
        UserDTO created = userService.create(dto);
        return ResponseEntity
            .status(HttpStatus.CREATED)
            .body(created);
    }
    
    /**
     * 更新用户
     */
    @PutMapping("/{id}")
    public ResponseEntity<UserDTO> updateUser(
        @PathVariable Long id,
        @Valid @RequestBody UserDTO dto
    ) {
        UserDTO updated = userService.update(id, dto);
        return ResponseEntity.ok(updated);
    }
    
    /**
     * 删除用户
     */
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.delete(id);
        return ResponseEntity.noContent().build();
    }
}

统一响应格式

1. 定义响应体

package com.example.demo.common;

import lombok.Data;
import java.time.LocalDateTime;

@Data
public class R<T> {
    
    private int code;
    private String message;
    private T data;
    private LocalDateTime timestamp;
    
    public static <T> R<T> ok(T data) {
        R<T> r = new R<>();
        r.setCode(200);
        r.setMessage("success");
        r.setData(data);
        r.setTimestamp(LocalDateTime.now());
        return r;
    }
    
    public static <T> R<T> ok() {
        return ok(null);
    }
    
    public static <T> R<T> error(String message) {
        R<T> r = new R<>();
        r.setCode(500);
        r.setMessage(message);
        r.setTimestamp(LocalDateTime.now());
        return r;
    }
    
    public static <T> R<T> error(int code, String message) {
        R<T> r = new R<>();
        r.setCode(code);
        r.setMessage(message);
        r.setTimestamp(LocalDateTime.now());
        return r;
    }
}

2. 使用统一响应

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    
    private final UserService userService;
    
    @GetMapping
    public R<List<UserDTO>> getUsers() {
        return R.ok(userService.findAll());
    }
    
    @GetMapping("/{id}")
    public R<UserDTO> getUser(@PathVariable Long id) {
        return R.ok(userService.findById(id));
    }
    
    @PostMapping
    public R<UserDTO> createUser(@Valid @RequestBody UserDTO dto) {
        return R.ok(userService.create(dto));
    }
}

分页查询

1. 分页请求

@Data
public class PageRequest {
    
    @Min(value = 1, message = "页码最小值为 1")
    private int page = 1;
    
    @Min(value = 1, message = "每页大小最小值为 1")
    @Max(value = 100, message = "每页大小最大值为 100")
    private int size = 10;
    
    private String sortField;
    private String sortOrder = "asc";
}

2. 分页响应

@Data
@AllArgsConstructor
public class PageResult<T> {
    
    private List<T> list;
    private long total;
    private int page;
    private int size;
    private int pages;
    
    public static <T> PageResult<T> of(List<T> list, long total, int page, int size) {
        return new PageResult<>(list, total, page, size, (int) Math.ceil((double) total / size));
    }
}

3. 分页接口

@GetMapping
public R<PageResult<UserDTO>> getUsers(
    @ModelAttribute PageRequest pageRequest
) {
    Pageable pageable = PageRequest.of(
        pageRequest.getPage() - 1,
        pageRequest.getSize(),
        Sort.by(Sort.Direction.fromString(pageRequest.getSortOrder()), pageRequest.getSortField())
    );
    Page<UserDTO> page = userService.findAll(pageable);
    return R.ok(PageResult.of(
        page.getContent(),
        page.getTotalElements(),
        pageRequest.getPage(),
        pageRequest.getSize()
    ));
}

版本控制

1. URI 版本

@RestController
@RequestMapping("/api/v1/users")
public class UserControllerV1 {
    // v1 版本接口
}

@RestController
@RequestMapping("/api/v2/users")
public class UserControllerV2 {
    // v2 版本接口
}

2. 请求头版本

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @GetMapping(produces = "application/vnd.demo.v1+json")
    public UserDTO getUserV1(@PathVariable Long id) {
        // v1 版本
    }
    
    @GetMapping(produces = "application/vnd.demo.v2+json")
    public UserDTO getUserV2(@PathVariable Long id) {
        // v2 版本
    }
}

文件上传

@PostMapping("/avatar")
public R<String> uploadAvatar(
    @RequestParam("file") MultipartFile file
) {
    if (file.isEmpty()) {
        return R.error("文件不能为空");
    }
    
    // 验证文件类型
    String contentType = file.getContentType();
    if (!List.of("image/jpeg", "image/png", "image/gif").contains(contentType)) {
        return R.error("只支持 JPG、PNG、GIF 格式图片");
    }
    
    // 验证文件大小(2MB)
    if (file.getSize() > 2 * 1024 * 1024) {
        return R.error("文件大小不能超过 2MB");
    }
    
    // 保存文件
    String filename = UUID.randomUUID() + "_" + file.getOriginalFilename();
    Path path = Paths.get("uploads/" + filename);
    
    try {
        Files.write(path, file.getBytes());
        return R.ok("/uploads/" + filename);
    } catch (IOException e) {
        return R.error("上传失败:" + e.getMessage());
    }
}

最佳实践

1. 使用 ResponseEntity

// ✅ 推荐
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
    return ResponseEntity.ok(userService.findById(id));
}

// ❌ 不推荐
@GetMapping("/{id}")
public UserDTO getUser(@PathVariable Long id) {
    return userService.findById(id);
}

2. 使用路径变量

// ✅ 推荐
@GetMapping("/users/{userId}/orders/{orderId}")
public ResponseEntity<OrderDTO> getOrder(
    @PathVariable Long userId,
    @PathVariable Long orderId
) {
    return ResponseEntity.ok(orderService.findById(orderId));
}

// ❌ 不推荐
@GetMapping("/users/{userId}/orders")
public ResponseEntity<OrderDTO> getOrder(
    @PathVariable Long userId,
    @RequestParam Long orderId
) {
    return ResponseEntity.ok(orderService.findById(orderId));
}

3. 使用查询参数过滤

@GetMapping
public ResponseEntity<List<UserDTO>> getUsers(
    @RequestParam(required = false) String status,
    @RequestParam(required = false) String role,
    @RequestParam(required = false) LocalDateTime startDate,
    @RequestParam(required = false) LocalDateTime endDate
) {
    List<UserDTO> users = userService.findByFilters(status, role, startDate, endDate);
    return ResponseEntity.ok(users);
}

4. 使用 HATEOAS

@GetMapping("/{id}")
public ResponseEntity<EntityModel<UserDTO>> getUser(@PathVariable Long id) {
    UserDTO user = userService.findById(id);
    
    EntityModel<UserDTO> resource = EntityModel.of(user);
    resource.add(linkTo(methodOn(UserController.class).getUser(id)).withSelfRel());
    resource.add(linkTo(methodOn(UserController.class).getUsers()).withRel("users"));
    
    return ResponseEntity.ok(resource);
}

总结

RESTful API 开发要点:

遵循 RESTful 设计规范,能让 API 更加规范、易用、易维护。


分享这篇文章到:

上一篇文章
Seata 核心概念
下一篇文章
Spring Boot 请求与响应处理