跳到主要内容

REST API 多语言实现

REST API 作为一种架构风格,不绑定特定编程语言。不同的语言和框架都提供了丰富的工具来实现 REST API。本章将介绍如何在 Python、Go 和 Java 中实现符合 REST 规范的 API,重点展示各语言的特点和最佳实践。

设计原则回顾

在实现 REST API 时,无论使用哪种语言,都应遵循以下核心原则:

资源导向:API 围绕资源设计,每个资源有唯一的 URI 标识。URL 中使用名词而非动词,操作语义通过 HTTP 方法表达。

统一接口:使用标准的 HTTP 方法(GET、POST、PUT、PATCH、DELETE)对资源进行操作,保持接口的一致性。

无状态:每个请求应包含所有必要信息,服务器不应存储客户端会话状态。

状态码规范:正确使用 HTTP 状态码表达请求结果,如 200 表示成功、201 表示创建成功、400 表示客户端错误、500 表示服务器错误。

错误处理:提供统一、清晰的错误响应格式,帮助客户端定位问题。

Python 实现

Python 拥有丰富的 Web 框架生态,其中 FastAPI 和 Flask 是实现 REST API 的主流选择。FastAPI 以其高性能、自动文档生成和类型提示支持而著称,Flask 则以轻量灵活著称。

FastAPI 实现

FastAPI 是现代 Python Web 框架的代表,基于 Starlette 和 Pydantic 构建,提供了自动 API 文档、请求验证、异步支持等特性。

from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, EmailStr
from typing import Optional
from datetime import datetime

app = FastAPI(
title="User API",
description="用户管理 REST API",
version="1.0.0"
)

# 数据模型
class UserCreate(BaseModel):
"""创建用户的请求数据"""
name: str
email: EmailStr
age: Optional[int] = None

class UserUpdate(BaseModel):
"""更新用户的请求数据,所有字段可选"""
name: Optional[str] = None
email: Optional[EmailStr] = None
age: Optional[int] = None

class User(BaseModel):
"""用户响应数据"""
id: int
name: str
email: str
age: Optional[int] = None
created_at: datetime
updated_at: datetime

# 模拟数据库
users_db = {}
user_id_counter = 1

# GET /users - 获取用户列表
@app.get("/users", response_model=list[User])
async def list_users(
skip: int = 0,
limit: int = 10
):
"""获取用户列表,支持分页

- skip: 跳过的记录数
- limit: 返回的最大记录数
"""
users = list(users_db.values())[skip:skip + limit]
return users

# GET /users/{user_id} - 获取单个用户
@app.get(
"/users/{user_id}",
response_model=User,
responses={
404: {"description": "用户不存在"}
}
)
async def get_user(user_id: int):
"""获取指定 ID 的用户"""
if user_id not in users_db:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail={
"code": "USER_NOT_FOUND",
"message": f"用户 {user_id} 不存在"
}
)
return users_db[user_id]

# POST /users - 创建用户
@app.post(
"/users",
response_model=User,
status_code=status.HTTP_201_CREATED
)
async def create_user(user: UserCreate):
"""创建新用户

- 返回 201 Created 状态码
- Location 头指向新创建的资源
"""
global user_id_counter

now = datetime.now()
new_user = User(
id=user_id_counter,
name=user.name,
email=user.email,
age=user.age,
created_at=now,
updated_at=now
)

users_db[user_id_counter] = new_user
user_id_counter += 1

return new_user

# PUT /users/{user_id} - 完整更新用户
@app.put("/users/{user_id}", response_model=User)
async def update_user(user_id: int, user: UserCreate):
"""完整更新用户,需要提供所有字段

- 幂等操作:多次执行结果相同
- 如果用户不存在,返回 404
"""
if user_id not in users_db:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail={"code": "USER_NOT_FOUND", "message": "用户不存在"}
)

existing = users_db[user_id]
now = datetime.now()

updated = User(
id=user_id,
name=user.name,
email=user.email,
age=user.age,
created_at=existing.created_at,
updated_at=now
)

users_db[user_id] = updated
return updated

# PATCH /users/{user_id} - 部分更新用户
@app.patch("/users/{user_id}", response_model=User)
async def partial_update_user(user_id: int, user: UserUpdate):
"""部分更新用户,只更新提供的字段

- 非幂等操作
- 只更新请求中包含的字段
"""
if user_id not in users_db:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail={"code": "USER_NOT_FOUND", "message": "用户不存在"}
)

existing = users_db[user_id]
update_data = user.model_dump(exclude_unset=True)

updated = existing.model_copy(update={
**update_data,
"updated_at": datetime.now()
})

users_db[user_id] = updated
return updated

# DELETE /users/{user_id} - 删除用户
@app.delete(
"/users/{user_id}",
status_code=status.HTTP_204_NO_CONTENT
)
async def delete_user(user_id: int):
"""删除用户

- 成功返回 204 No Content
- 无响应体
"""
if user_id not in users_db:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail={"code": "USER_NOT_FOUND", "message": "用户不存在"}
)

del users_db[user_id]
return None

FastAPI 的关键优势:

  • 自动文档:访问 /docs 可获得 Swagger UI 文档,/redoc 可获得 ReDoc 文档
  • 类型验证:基于 Pydantic 自动验证请求和响应数据
  • 异步支持:原生支持 async/await,适合高并发场景
  • 依赖注入:通过 Depends 实现灵活的依赖管理

Flask 实现

Flask 是一个轻量级框架,提供了更大的灵活性,适合小型项目或需要高度定制的场景。

from flask import Flask, request, jsonify, url_for
from werkzeug.exceptions import NotFound, BadRequest
from datetime import datetime
import json

app = Flask(__name__)

# 模拟数据库
users_db = {}
user_id_counter = 1

# 错误处理
@app.errorhandler(NotFound)
def handle_not_found(e):
return jsonify({
"error": {
"code": "NOT_FOUND",
"message": str(e.description)
}
}), 404

@app.errorhandler(BadRequest)
def handle_bad_request(e):
return jsonify({
"error": {
"code": "BAD_REQUEST",
"message": str(e.description)
}
}), 400

# GET /users
@app.route("/users", methods=["GET"])
def list_users():
skip = request.args.get("skip", 0, type=int)
limit = request.args.get("limit", 10, type=int)

users = list(users_db.values())
result = users[skip:skip + limit]

return jsonify({
"data": result,
"pagination": {
"skip": skip,
"limit": limit,
"total": len(users)
}
})

# GET /users/<int:user_id>
@app.route("/users/<int:user_id>", methods=["GET"])
def get_user(user_id):
if user_id not in users_db:
raise NotFound("用户不存在")

return jsonify(users_db[user_id])

# POST /users
@app.route("/users", methods=["POST"])
def create_user():
global user_id_counter

data = request.get_json()
if not data:
raise BadRequest("请求体不能为空")

# 验证必填字段
required = ["name", "email"]
for field in required:
if field not in data:
raise BadRequest(f"缺少必填字段: {field}")

now = datetime.now().isoformat()
user = {
"id": user_id_counter,
"name": data["name"],
"email": data["email"],
"age": data.get("age"),
"created_at": now,
"updated_at": now
}

users_db[user_id_counter] = user
user_id_counter += 1

response = jsonify(user)
response.status_code = 201
response.headers["Location"] = url_for("get_user", user_id=user["id"])
return response

# PUT /users/<int:user_id>
@app.route("/users/<int:user_id>", methods=["PUT"])
def update_user(user_id):
if user_id not in users_db:
raise NotFound("用户不存在")

data = request.get_json()
if not data:
raise BadRequest("请求体不能为空")

now = datetime.now().isoformat()
existing = users_db[user_id]

users_db[user_id] = {
"id": user_id,
"name": data.get("name", existing["name"]),
"email": data.get("email", existing["email"]),
"age": data.get("age", existing["age"]),
"created_at": existing["created_at"],
"updated_at": now
}

return jsonify(users_db[user_id])

# DELETE /users/<int:user_id>
@app.route("/users/<int:user_id>", methods=["DELETE"])
def delete_user(user_id):
if user_id not in users_db:
raise NotFound("用户不存在")

del users_db[user_id]
return "", 204

if __name__ == "__main__":
app.run(debug=True)

Go 实现

Go 语言以简洁高效著称,其标准库 net/http 已经足够实现 REST API。此外,Gin、Echo 等框架提供了更丰富的功能。

标准库实现

使用 Go 标准库实现 REST API,需要手动处理路由、JSON 序列化等,但能够深入理解 HTTP 协议的工作原理。

package main

import (
"encoding/json"
"log"
"net/http"
"strconv"
"sync"
"time"
)

// User 表示用户资源
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Age *int `json:"age,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

// UserCreate 用于创建用户的请求数据
type UserCreate struct {
Name string `json:"name"`
Email string `json:"email"`
Age *int `json:"age,omitempty"`
}

// UserUpdate 用于更新用户的请求数据
type UserUpdate struct {
Name *string `json:"name,omitempty"`
Email *string `json:"email,omitempty"`
Age *int `json:"age,omitempty"`
}

// ErrorResponse 统一错误响应格式
type ErrorResponse struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}

// 模拟数据库
type UserStore struct {
sync.RWMutex
users map[int]*User
nextID int
}

var store = &UserStore{
users: make(map[int]*User),
nextID: 1,
}

// writeJSON 辅助函数:写入 JSON 响应
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}

// writeError 辅助函数:写入错误响应
func writeError(w http.ResponseWriter, status int, code, message string) {
var errResp ErrorResponse
errResp.Error.Code = code
errResp.Error.Message = message
writeJSON(w, status, errResp)
}

// listUsers 处理 GET /users
func listUsers(w http.ResponseWriter, r *http.Request) {
skip, _ := strconv.Atoi(r.URL.Query().Get("skip"))
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
if limit == 0 {
limit = 10
}

store.RLock()
defer store.RUnlock()

// 转换为切片
users := make([]*User, 0, len(store.users))
for _, u := range store.users {
users = append(users, u)
}

// 分页
end := skip + limit
if end > len(users) {
end = len(users)
}
if skip > len(users) {
skip = len(users)
}

writeJSON(w, http.StatusOK, users[skip:end])
}

// getUser 处理 GET /users/{id}
func getUser(w http.ResponseWriter, r *http.Request) {
// 提取路径参数 (简单实现)
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
writeError(w, http.StatusBadRequest, "INVALID_ID", "无效的用户 ID")
return
}

store.RLock()
user, exists := store.users[id]
store.RUnlock()

if !exists {
writeError(w, http.StatusNotFound, "USER_NOT_FOUND", "用户不存在")
return
}

writeJSON(w, http.StatusOK, user)
}

// createUser 处理 POST /users
func createUser(w http.ResponseWriter, r *http.Request) {
var input UserCreate
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
writeError(w, http.StatusBadRequest, "INVALID_JSON", "无效的 JSON 格式")
return
}

// 验证必填字段
if input.Name == "" || input.Email == "" {
writeError(w, http.StatusBadRequest, "MISSING_FIELD", "name 和 email 为必填字段")
return
}

store.Lock()
defer store.Unlock()

now := time.Now()
user := &User{
ID: store.nextID,
Name: input.Name,
Email: input.Email,
Age: input.Age,
CreatedAt: now,
UpdatedAt: now,
}

store.users[store.nextID] = user
store.nextID++

w.Header().Set("Location", "/users/"+strconv.Itoa(user.ID))
writeJSON(w, http.StatusCreated, user)
}

// updateUser 处理 PUT /users/{id}
func updateUser(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
writeError(w, http.StatusBadRequest, "INVALID_ID", "无效的用户 ID")
return
}

var input UserCreate
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
writeError(w, http.StatusBadRequest, "INVALID_JSON", "无效的 JSON 格式")
return
}

store.Lock()
defer store.Unlock()

existing, exists := store.users[id]
if !exists {
writeError(w, http.StatusNotFound, "USER_NOT_FOUND", "用户不存在")
return
}

now := time.Now()
existing.Name = input.Name
existing.Email = input.Email
existing.Age = input.Age
existing.UpdatedAt = now

writeJSON(w, http.StatusOK, existing)
}

// patchUser 处理 PATCH /users/{id}
func patchUser(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
writeError(w, http.StatusBadRequest, "INVALID_ID", "无效的用户 ID")
return
}

var input UserUpdate
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
writeError(w, http.StatusBadRequest, "INVALID_JSON", "无效的 JSON 格式")
return
}

store.Lock()
defer store.Unlock()

existing, exists := store.users[id]
if !exists {
writeError(w, http.StatusNotFound, "USER_NOT_FOUND", "用户不存在")
return
}

now := time.Now()
if input.Name != nil {
existing.Name = *input.Name
}
if input.Email != nil {
existing.Email = *input.Email
}
if input.Age != nil {
existing.Age = input.Age
}
existing.UpdatedAt = now

writeJSON(w, http.StatusOK, existing)
}

// deleteUser 处理 DELETE /users/{id}
func deleteUser(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
writeError(w, http.StatusBadRequest, "INVALID_ID", "无效的用户 ID")
return
}

store.Lock()
defer store.Unlock()

if _, exists := store.users[id]; !exists {
writeError(w, http.StatusNotFound, "USER_NOT_FOUND", "用户不存在")
return
}

delete(store.users, id)
w.WriteHeader(http.StatusNoContent)
}

func main() {
// 路由配置
http.HandleFunc("GET /users", listUsers)
http.HandleFunc("GET /users/{id}", getUser)
http.HandleFunc("POST /users", createUser)
http.HandleFunc("PUT /users/{id}", updateUser)
http.HandleFunc("PATCH /users/{id}", patchUser)
http.HandleFunc("DELETE /users/{id}", deleteUser)

log.Println("服务器启动在 :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}

Go 标准库实现的特点:

  • 简洁:无需第三方依赖,代码清晰易懂
  • 高性能:Go 的 HTTP 服务器性能优异
  • 并发安全:使用 sync.RWMutex 保护共享数据
  • 路径参数:Go 1.22+ 支持路径参数语法 GET /users/{id}

Gin 框架实现

Gin 是 Go 生态中最流行的 Web 框架,提供了路由分组、中间件、请求绑定等便捷功能。

package main

import (
"net/http"
"strconv"
"sync"
"time"

"github.com/gin-gonic/gin"
)

type User struct {
ID int `json:"id"`
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
Age *int `json:"age,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

type UserStore struct {
sync.RWMutex
users map[int]*User
nextID int
}

var store = &UserStore{
users: make(map[int]*User),
nextID: 1,
}

func main() {
r := gin.Default()

// 用户路由组
users := r.Group("/users")
{
users.GET("", listUsers)
users.GET("/:id", getUser)
users.POST("", createUser)
users.PUT("/:id", updateUser)
users.PATCH("/:id", patchUser)
users.DELETE("/:id", deleteUser)
}

r.Run(":8080")
}

func listUsers(c *gin.Context) {
skip, _ := strconv.Atoi(c.DefaultQuery("skip", "0"))
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))

store.RLock()
users := make([]*User, 0, len(store.users))
for _, u := range store.users {
users = append(users, u)
}
store.RUnlock()

end := skip + limit
if end > len(users) {
end = len(users)
}

c.JSON(http.StatusOK, users[skip:end])
}

func getUser(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": gin.H{
"code": "INVALID_ID",
"message": "无效的用户 ID",
},
})
return
}

store.RLock()
user, exists := store.users[id]
store.RUnlock()

if !exists {
c.JSON(http.StatusNotFound, gin.H{
"error": gin.H{
"code": "USER_NOT_FOUND",
"message": "用户不存在",
},
})
return
}

c.JSON(http.StatusOK, user)
}

func createUser(c *gin.Context) {
var input struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
Age *int `json:"age"`
}

if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": gin.H{
"code": "VALIDATION_ERROR",
"message": err.Error(),
},
})
return
}

store.Lock()
now := time.Now()
user := &User{
ID: store.nextID,
Name: input.Name,
Email: input.Email,
Age: input.Age,
CreatedAt: now,
UpdatedAt: now,
}
store.users[store.nextID] = user
store.nextID++
store.Unlock()

c.Header("Location", "/users/"+strconv.Itoa(user.ID))
c.JSON(http.StatusCreated, user)
}

func updateUser(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))

var input User
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{
"code": "VALIDATION_ERROR", "message": err.Error(),
}})
return
}

store.Lock()
defer store.Unlock()

existing, exists := store.users[id]
if !exists {
c.JSON(http.StatusNotFound, gin.H{"error": gin.H{
"code": "USER_NOT_FOUND", "message": "用户不存在",
}})
return
}

now := time.Now()
existing.Name = input.Name
existing.Email = input.Email
existing.Age = input.Age
existing.UpdatedAt = now

c.JSON(http.StatusOK, existing)
}

func patchUser(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))

var input map[string]interface{}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{
"code": "INVALID_JSON", "message": "无效的 JSON",
}})
return
}

store.Lock()
defer store.Unlock()

existing, exists := store.users[id]
if !exists {
c.JSON(http.StatusNotFound, gin.H{"error": gin.H{
"code": "USER_NOT_FOUND", "message": "用户不存在",
}})
return
}

if name, ok := input["name"].(string); ok {
existing.Name = name
}
if email, ok := input["email"].(string); ok {
existing.Email = email
}
if age, ok := input["age"].(float64); ok {
ageInt := int(age)
existing.Age = &ageInt
}
existing.UpdatedAt = time.Now()

c.JSON(http.StatusOK, existing)
}

func deleteUser(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))

store.Lock()
defer store.Unlock()

if _, exists := store.users[id]; !exists {
c.JSON(http.StatusNotFound, gin.H{"error": gin.H{
"code": "USER_NOT_FOUND", "message": "用户不存在",
}})
return
}

delete(store.users, id)
c.Status(http.StatusNoContent)
}

Java 实现

Java 企业级开发中,Spring Boot 是实现 REST API 的首选框架。它提供了强大的依赖注入、自动配置和丰富的生态系统。

Spring Boot 实现

// User.java - 用户实体
package com.example.demo.entity;

import jakarta.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String name;

@Column(nullable = false, unique = true)
private String email;

private Integer age;

@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;

@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;

// 构造函数、getter、setter 省略
}

// UserCreateRequest.java - 创建请求 DTO
package com.example.demo.dto;

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

public class UserCreateRequest {
@NotBlank(message = "姓名不能为空")
private String name;

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

private Integer age;

// getter、setter 省略
}

// UserUpdateRequest.java - 更新请求 DTO
package com.example.demo.dto;

public class UserUpdateRequest {
private String name;
private String email;
private Integer age;

// getter、setter 省略
}

// UserRepository.java - 数据访问层
package com.example.demo.repository;

import com.example.demo.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

// UserService.java - 服务层
package com.example.demo.service;

import com.example.demo.dto.UserCreateRequest;
import com.example.demo.dto.UserUpdateRequest;
import com.example.demo.entity.User;
import com.example.demo.exception.UserNotFoundException;
import com.example.demo.repository.UserRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

@Service
@Transactional
public class UserService {
private final UserRepository userRepository;

public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}

// 获取用户列表
public Page<User> listUsers(int page, int size) {
return userRepository.findAll(PageRequest.of(page, size));
}

// 获取单个用户
public User getUser(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}

// 创建用户
public User createUser(UserCreateRequest request) {
User user = new User();
user.setName(request.getName());
user.setEmail(request.getEmail());
user.setAge(request.getAge());

LocalDateTime now = LocalDateTime.now();
user.setCreatedAt(now);
user.setUpdatedAt(now);

return userRepository.save(user);
}

// 完整更新用户
public User updateUser(Long id, UserCreateRequest request) {
User user = getUser(id);

user.setName(request.getName());
user.setEmail(request.getEmail());
user.setAge(request.getAge());
user.setUpdatedAt(LocalDateTime.now());

return userRepository.save(user);
}

// 部分更新用户
public User patchUser(Long id, UserUpdateRequest request) {
User user = getUser(id);

if (request.getName() != null) {
user.setName(request.getName());
}
if (request.getEmail() != null) {
user.setEmail(request.getEmail());
}
if (request.getAge() != null) {
user.setAge(request.getAge());
}
user.setUpdatedAt(LocalDateTime.now());

return userRepository.save(user);
}

// 删除用户
public void deleteUser(Long id) {
if (!userRepository.existsById(id)) {
throw new UserNotFoundException(id);
}
userRepository.deleteById(id);
}
}

// UserNotFoundException.java - 自定义异常
package com.example.demo.exception;

public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(Long id) {
super("用户 " + id + " 不存在");
}
}

// GlobalExceptionHandler.java - 全局异常处理
package com.example.demo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class GlobalExceptionHandler {

// 处理用户不存在异常
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<Map<String, Object>> handleUserNotFound(UserNotFoundException ex) {
Map<String, Object> body = new HashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("status", HttpStatus.NOT_FOUND.value());

Map<String, String> error = new HashMap<>();
error.put("code", "USER_NOT_FOUND");
error.put("message", ex.getMessage());
body.put("error", error);

return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body);
}

// 处理验证失败异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleValidationErrors(MethodArgumentNotValidException ex) {
Map<String, Object> body = new HashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("status", HttpStatus.BAD_REQUEST.value());

Map<String, Object> error = new HashMap<>();
error.put("code", "VALIDATION_ERROR");
error.put("message", "请求参数验证失败");

// 收集字段错误
Map<String, String> fields = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(err -> {
fields.put(err.getField(), err.getDefaultMessage());
});
error.put("fields", fields);
body.put("error", error);

return ResponseEntity.badRequest().body(body);
}
}

// UserController.java - 控制器层
package com.example.demo.controller;

import com.example.demo.dto.UserCreateRequest;
import com.example.demo.dto.UserUpdateRequest;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.net.URI;

@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;

public UserController(UserService userService) {
this.userService = userService;
}

// GET /users - 获取用户列表
@GetMapping
public Page<User> listUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
return userService.listUsers(page, size);
}

// GET /users/{id} - 获取单个用户
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUser(id);
}

// POST /users - 创建用户
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody UserCreateRequest request) {
User user = userService.createUser(request);
return ResponseEntity
.created(URI.create("/users/" + user.getId()))
.body(user);
}

// PUT /users/{id} - 完整更新用户
@PutMapping("/{id}")
public User updateUser(
@PathVariable Long id,
@Valid @RequestBody UserCreateRequest request
) {
return userService.updateUser(id, request);
}

// PATCH /users/{id} - 部分更新用户
@PatchMapping("/{id}")
public User patchUser(
@PathVariable Long id,
@RequestBody UserUpdateRequest request
) {
return userService.patchUser(id, request);
}

// DELETE /users/{id} - 删除用户
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
}
}

Spring Boot 实现的关键特点:

  • 分层架构:Controller → Service → Repository,职责清晰
  • 依赖注入:通过构造函数注入,便于测试
  • 自动验证:使用 @Valid 和 Bean Validation 注解
  • 统一异常处理@RestControllerAdvice 捕获全局异常
  • 分页支持:Spring Data 提供开箱即用的分页功能

各语言实现对比

特性Python (FastAPI)Go (标准库/Gin)Java (Spring Boot)
开发效率
运行性能
类型安全中(可选)
异步支持原生原生需要 WebFlux
生态系统丰富中等非常丰富
学习曲线平缓平缓陡峭
企业级特性中等需要扩展完整

最佳实践总结

无论使用哪种语言和框架,实现 REST API 时都应遵循以下原则:

一致的命名:URL 使用小写、复数名词、连字符分隔,如 /user-profiles

正确的状态码:创建返回 201,删除返回 204,未找到返回 404,验证失败返回 400。

统一的错误格式:提供错误码、消息和详细信息,便于客户端处理。

版本控制:在 URL 中包含版本号,如 /api/v1/users,便于后续升级。

分页支持:列表接口应支持分页,避免返回过多数据。

输入验证:在服务端验证所有输入,不要依赖客户端验证。

日志记录:记录关键操作和错误,便于排查问题。

API 文档:使用 OpenAPI/Swagger 等工具生成文档,保持文档与代码同步。