SpringBoot3.x入门到精通系列:2.4 RESTful API设计

SpringBoot 3.x RESTful API设计

🎯 RESTful API设计原则

REST (Representational State Transfer) 是一种架构风格,用于设计网络应用程序的API。

核心原则

  1. 资源导向 - 一切皆资源,每个资源都有唯一的URI
  2. 统一接口 - 使用标准的HTTP方法
  3. 无状态 - 每个请求都包含处理该请求所需的所有信息
  4. 可缓存 - 响应应该明确标识是否可缓存
  5. 分层系统 - 客户端无需知道是否直接连接到最终服务器

🌐 HTTP方法与CRUD操作

HTTP方法 CRUD操作 描述 示例
GET Read 获取资源 GET /api/users
POST Create 创建资源 POST /api/users
PUT Update 完整更新资源 PUT /api/users/1
PATCH Update 部分更新资源 PATCH /api/users/1
DELETE Delete 删除资源 DELETE /api/users/1

📋 URL设计规范

1. 资源命名规范

java 复制代码
// ✅ 好的设计
GET    /api/users              // 获取用户列表
GET    /api/users/123          // 获取特定用户
POST   /api/users              // 创建用户
PUT    /api/users/123          // 更新用户
DELETE /api/users/123          // 删除用户

// 嵌套资源
GET    /api/users/123/orders   // 获取用户的订单
POST   /api/users/123/orders   // 为用户创建订单

// ❌ 不好的设计
GET    /api/getUsers            // 动词形式
POST   /api/createUser          // 动词形式
GET    /api/user_list           // 下划线

2. 实际API设计示例

java 复制代码
@RestController
@RequestMapping("/api/v1")
@CrossOrigin(origins = "*")
public class UserApiController {
    
    private final UserService userService;
    
    public UserApiController(UserService userService) {
        this.userService = userService;
    }
    
    /**
     * 获取用户列表
     * GET /api/v1/users?page=0&size=10&sort=name,asc
     */
    @GetMapping("/users")
    public ResponseEntity<PagedResponse<UserDTO>> getUsers(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(defaultValue = "id") String sort,
            @RequestParam(required = false) String search) {
        
        PagedResponse<UserDTO> users = userService.findAll(page, size, sort, search);
        return ResponseEntity.ok(users);
    }
    
    /**
     * 根据ID获取用户
     * GET /api/v1/users/{id}
     */
    @GetMapping("/users/{id}")
    public ResponseEntity<UserDTO> getUserById(@PathVariable Long id) {
        UserDTO user = userService.findById(id);
        return ResponseEntity.ok(user);
    }
    
    /**
     * 创建用户
     * POST /api/v1/users
     */
    @PostMapping("/users")
    public ResponseEntity<UserDTO> createUser(
            @RequestBody @Valid CreateUserRequest request,
            HttpServletRequest httpRequest) {
        
        UserDTO user = userService.create(request);
        
        // 返回201状态码和Location头
        URI location = ServletUriComponentsBuilder
                .fromCurrentRequest()
                .path("/{id}")
                .buildAndExpand(user.getId())
                .toUri();
                
        return ResponseEntity.created(location).body(user);
    }
    
    /**
     * 完整更新用户
     * PUT /api/v1/users/{id}
     */
    @PutMapping("/users/{id}")
    public ResponseEntity<UserDTO> updateUser(
            @PathVariable Long id,
            @RequestBody @Valid UpdateUserRequest request) {
        
        UserDTO user = userService.update(id, request);
        return ResponseEntity.ok(user);
    }
    
    /**
     * 部分更新用户
     * PATCH /api/v1/users/{id}
     */
    @PatchMapping("/users/{id}")
    public ResponseEntity<UserDTO> patchUser(
            @PathVariable Long id,
            @RequestBody Map<String, Object> updates) {
        
        UserDTO user = userService.patch(id, updates);
        return ResponseEntity.ok(user);
    }
    
    /**
     * 删除用户
     * DELETE /api/v1/users/{id}
     */
    @DeleteMapping("/users/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.delete(id);
        return ResponseEntity.noContent().build();
    }
    
    /**
     * 批量删除用户
     * DELETE /api/v1/users?ids=1,2,3
     */
    @DeleteMapping("/users")
    public ResponseEntity<BatchDeleteResponse> batchDeleteUsers(
            @RequestParam List<Long> ids) {
        
        BatchDeleteResponse response = userService.batchDelete(ids);
        return ResponseEntity.ok(response);
    }
}

📊 统一响应格式

1. 成功响应格式

java 复制代码
public class ApiResponse<T> {
    private boolean success;
    private String message;
    private T data;
    private long timestamp;
    
    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(true, "操作成功", data, System.currentTimeMillis());
    }
    
    public static <T> ApiResponse<T> success(String message, T data) {
        return new ApiResponse<>(true, message, data, System.currentTimeMillis());
    }
    
    // 构造函数、getter和setter
    public ApiResponse(boolean success, String message, T data, long timestamp) {
        this.success = success;
        this.message = message;
        this.data = data;
        this.timestamp = timestamp;
    }
    
    // getter和setter方法
    public boolean isSuccess() { return success; }
    public void setSuccess(boolean success) { this.success = success; }
    
    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }
    
    public T getData() { return data; }
    public void setData(T data) { this.data = data; }
    
    public long getTimestamp() { return timestamp; }
    public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
}

2. 分页响应格式

java 复制代码
public class PagedResponse<T> {
    private List<T> content;
    private int page;
    private int size;
    private long totalElements;
    private int totalPages;
    private boolean first;
    private boolean last;
    
    public PagedResponse(List<T> content, int page, int size, long totalElements) {
        this.content = content;
        this.page = page;
        this.size = size;
        this.totalElements = totalElements;
        this.totalPages = (int) Math.ceil((double) totalElements / size);
        this.first = page == 0;
        this.last = page >= totalPages - 1;
    }
    
    // getter和setter方法
    public List<T> getContent() { return content; }
    public void setContent(List<T> content) { this.content = content; }
    
    public int getPage() { return page; }
    public void setPage(int page) { this.page = page; }
    
    public int getSize() { return size; }
    public void setSize(int size) { this.size = size; }
    
    public long getTotalElements() { return totalElements; }
    public void setTotalElements(long totalElements) { this.totalElements = totalElements; }
    
    public int getTotalPages() { return totalPages; }
    public void setTotalPages(int totalPages) { this.totalPages = totalPages; }
    
    public boolean isFirst() { return first; }
    public void setFirst(boolean first) { this.first = first; }
    
    public boolean isLast() { return last; }
    public void setLast(boolean last) { this.last = last; }
}

3. 错误响应格式

java 复制代码
public class ErrorResponse {
    private boolean success = false;
    private String error;
    private String message;
    private int status;
    private String path;
    private long timestamp;
    private List<FieldError> fieldErrors;
    
    public ErrorResponse(String error, String message, int status, String path) {
        this.error = error;
        this.message = message;
        this.status = status;
        this.path = path;
        this.timestamp = System.currentTimeMillis();
    }
    
    public static class FieldError {
        private String field;
        private Object rejectedValue;
        private String message;
        
        public FieldError(String field, Object rejectedValue, String message) {
            this.field = field;
            this.rejectedValue = rejectedValue;
            this.message = message;
        }
        
        // getter和setter方法
        public String getField() { return field; }
        public void setField(String field) { this.field = field; }
        
        public Object getRejectedValue() { return rejectedValue; }
        public void setRejectedValue(Object rejectedValue) { this.rejectedValue = rejectedValue; }
        
        public String getMessage() { return message; }
        public void setMessage(String message) { this.message = message; }
    }
    
    // getter和setter方法
    public boolean isSuccess() { return success; }
    public void setSuccess(boolean success) { this.success = success; }
    
    public String getError() { return error; }
    public void setError(String error) { this.error = error; }
    
    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }
    
    public int getStatus() { return status; }
    public void setStatus(int status) { this.status = status; }
    
    public String getPath() { return path; }
    public void setPath(String path) { this.path = path; }
    
    public long getTimestamp() { return timestamp; }
    public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
    
    public List<FieldError> getFieldErrors() { return fieldErrors; }
    public void setFieldErrors(List<FieldError> fieldErrors) { this.fieldErrors = fieldErrors; }
}

🔧 HTTP状态码使用

1. 常用状态码

状态码 含义 使用场景
200 OK 成功获取资源
201 Created 成功创建资源
204 No Content 成功删除资源
400 Bad Request 请求参数错误
401 Unauthorized 未认证
403 Forbidden 无权限
404 Not Found 资源不存在
409 Conflict 资源冲突
422 Unprocessable Entity 验证失败
500 Internal Server Error 服务器错误

2. 状态码使用示例

java 复制代码
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
    
    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        try {
            UserDTO user = userService.findById(id);
            return ResponseEntity.ok(user);  // 200
        } catch (UserNotFoundException e) {
            return ResponseEntity.notFound().build();  // 404
        }
    }
    
    @PostMapping
    public ResponseEntity<UserDTO> createUser(@RequestBody @Valid CreateUserRequest request) {
        try {
            UserDTO user = userService.create(request);
            URI location = ServletUriComponentsBuilder
                    .fromCurrentRequest()
                    .path("/{id}")
                    .buildAndExpand(user.getId())
                    .toUri();
            return ResponseEntity.created(location).body(user);  // 201
        } catch (EmailAlreadyExistsException e) {
            return ResponseEntity.status(HttpStatus.CONFLICT).build();  // 409
        }
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        try {
            userService.delete(id);
            return ResponseEntity.noContent().build();  // 204
        } catch (UserNotFoundException e) {
            return ResponseEntity.notFound().build();  // 404
        }
    }
}

🔍 API版本控制

1. URL路径版本控制

java 复制代码
@RestController
@RequestMapping("/api/v1/users")
public class UserV1Controller {
    // v1版本的用户API
}

@RestController
@RequestMapping("/api/v2/users")
public class UserV2Controller {
    // v2版本的用户API
}

2. 请求头版本控制

java 复制代码
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @GetMapping(headers = "API-Version=1")
    public ResponseEntity<UserV1DTO> getUserV1(@PathVariable Long id) {
        // v1版本逻辑
    }
    
    @GetMapping(headers = "API-Version=2")
    public ResponseEntity<UserV2DTO> getUserV2(@PathVariable Long id) {
        // v2版本逻辑
    }
}

📝 API文档

1. 使用OpenAPI 3.0 (Swagger)

xml 复制代码
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.2.0</version>
</dependency>

2. API文档注解

java 复制代码
@RestController
@RequestMapping("/api/v1/users")
@Tag(name = "用户管理", description = "用户相关的API接口")
public class UserController {
    
    @Operation(summary = "获取用户列表", description = "分页获取用户列表")
    @ApiResponses(value = {
        @ApiResponse(responseCode = "200", description = "成功获取用户列表"),
        @ApiResponse(responseCode = "400", description = "请求参数错误")
    })
    @GetMapping
    public ResponseEntity<PagedResponse<UserDTO>> getUsers(
            @Parameter(description = "页码,从0开始") @RequestParam(defaultValue = "0") int page,
            @Parameter(description = "每页大小") @RequestParam(defaultValue = "10") int size) {
        
        PagedResponse<UserDTO> users = userService.findAll(page, size);
        return ResponseEntity.ok(users);
    }
    
    @Operation(summary = "创建用户", description = "创建新的用户")
    @PostMapping
    public ResponseEntity<UserDTO> createUser(
            @io.swagger.v3.oas.annotations.parameters.RequestBody(
                description = "用户创建请求", 
                required = true
            )
            @RequestBody @Valid CreateUserRequest request) {
        
        UserDTO user = userService.create(request);
        return ResponseEntity.status(201).body(user);
    }
}

🔗 下一篇

在下一篇文章中,我们将学习SpringBoot的数据访问与JPA,了解如何进行数据库操作。


本文关键词: RESTful API, HTTP方法, 状态码, API设计, 版本控制, OpenAPI