Skip to main content

RESTful API 设计

REST 基础

REST 是什么?

REST (Representational State Transfer) 是一种软件架构风格,使用 HTTP 协议进行通信,通过标准的 HTTP 方法操作资源。

HTTP 方法

HTTP 方法说明幂等性使用场景
GET获取资源查询数据
POST创建资源创建新数据
PUT更新资源(完整替换)完整更新
PATCH更新资源(部分更新)部分更新
DELETE删除资源删除数据

URL 设计

URL 设计原则
  1. 使用名词而不是动词
  2. 使用复数形式
  3. 层级结构清晰
  4. 使用小写字母和连字符

良好的 URL 设计

# 获取所有用户
GET /api/users

# 获取单个用户
GET /api/users/123

# 创建用户
POST /api/users

# 更新用户
PUT /api/users/123

# 删除用户
DELETE /api/users/123

# 获取用户的订单
GET /api/users/123/orders

# 获取用户的特定订单
GET /api/users/123/orders/456

# 查询过滤
GET /api/users?status=active&page=1&pageSize=20

# 排序
GET /api/users?sortBy=createdAt&order=desc

ASP.NET Core 实现

基础 Controller

Controllers/UsersController.cs
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
private readonly ILogger<UsersController> _logger;

public UsersController(IUserService userService, ILogger<UsersController> logger)
{
_userService = userService;
_logger = logger;
}

/// <summary>
/// 获取所有用户
/// </summary>
[HttpGet]
[ProducesResponseType(typeof(List<UserDto>), 200)]
public async Task<ActionResult<List<UserDto>>> GetUsers(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20)
{
var users = await _userService.GetUsersAsync(page, pageSize);
return Ok(users);
}

/// <summary>
/// 获取指定用户
/// </summary>
[HttpGet("{id}")]
[ProducesResponseType(typeof(UserDto), 200)]
[ProducesResponseType(404)]
public async Task<ActionResult<UserDto>> GetUser(int id)
{
var user = await _userService.GetUserByIdAsync(id);

if (user == null)
{
return NotFound(new { message = $"用户 {id} 不存在" });
}

return Ok(user);
}

/// <summary>
/// 创建用户
/// </summary>
[HttpPost]
[ProducesResponseType(typeof(UserDto), 201)]
[ProducesResponseType(400)]
public async Task<ActionResult<UserDto>> CreateUser([FromBody] CreateUserRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

var user = await _userService.CreateUserAsync(request);

return CreatedAtAction(
nameof(GetUser),
new { id = user.Id },
user
);
}

/// <summary>
/// 更新用户
/// </summary>
[HttpPut("{id}")]
[ProducesResponseType(typeof(UserDto), 200)]
[ProducesResponseType(404)]
public async Task<ActionResult<UserDto>> UpdateUser(int id, [FromBody] UpdateUserRequest request)
{
var user = await _userService.UpdateUserAsync(id, request);

if (user == null)
{
return NotFound();
}

return Ok(user);
}

/// <summary>
/// 部分更新用户
/// </summary>
[HttpPatch("{id}")]
[ProducesResponseType(typeof(UserDto), 200)]
[ProducesResponseType(404)]
public async Task<ActionResult<UserDto>> PatchUser(int id, [FromBody] JsonPatchDocument<UpdateUserRequest> patchDoc)
{
var user = await _userService.GetUserByIdAsync(id);
if (user == null)
{
return NotFound();
}

var userToPatch = new UpdateUserRequest();
patchDoc.ApplyTo(userToPatch, ModelState);

if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

var updated = await _userService.UpdateUserAsync(id, userToPatch);
return Ok(updated);
}

/// <summary>
/// 删除用户
/// </summary>
[HttpDelete("{id}")]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public async Task<IActionResult> DeleteUser(int id)
{
var success = await _userService.DeleteUserAsync(id);

if (!success)
{
return NotFound();
}

return NoContent();
}
}

HTTP 状态码

2xx 成功

状态码说明使用场景
200 OK请求成功GET, PUT, PATCH 成功
201 Created资源创建成功POST 创建成功
204 No Content成功但无返回内容DELETE 成功

4xx 客户端错误

状态码说明使用场景
400 Bad Request请求参数错误验证失败
401 Unauthorized未认证需要登录
403 Forbidden无权限权限不足
404 Not Found资源不存在资源未找到
409 Conflict资源冲突重复创建
422 Unprocessable Entity实体验证失败业务规则验证失败
429 Too Many Requests请求过多限流

5xx 服务器错误

状态码说明使用场景
500 Internal Server Error服务器错误未处理的异常
503 Service Unavailable服务不可用维护中

统一响应格式

// 统一响应模型
public class ApiResponse<T>
{
public bool Success { get; set; }
public string Message { get; set; }
public T Data { get; set; }
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
}

public class PagedResponse<T> : ApiResponse<T>
{
public int Page { get; set; }
public int PageSize { get; set; }
public int TotalCount { get; set; }
public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
}

// 扩展方法
public static class ControllerBaseExtensions
{
public static ActionResult<ApiResponse<T>> Success<T>(
this ControllerBase controller,
T data,
string message = "操作成功")
{
return controller.Ok(new ApiResponse<T>
{
Success = true,
Message = message,
Data = data
});
}

public static ActionResult<ApiResponse<object>> Error(
this ControllerBase controller,
string message,
int statusCode = 400)
{
var response = new ApiResponse<object>
{
Success = false,
Message = message
};

return controller.StatusCode(statusCode, response);
}
}

// 使用
[HttpGet("{id}")]
public async Task<ActionResult<ApiResponse<UserDto>>> GetUser(int id)
{
var user = await _userService.GetUserByIdAsync(id);

if (user == null)
{
return this.Error("用户不存在", 404);
}

return this.Success(user);
}

数据验证

// 请求模型
public class CreateUserRequest
{
[Required(ErrorMessage = "用户名不能为空")]
[StringLength(50, MinimumLength = 3, ErrorMessage = "用户名长度必须在3-50之间")]
public string Username { get; set; }

[Required(ErrorMessage = "邮箱不能为空")]
[EmailAddress(ErrorMessage = "邮箱格式不正确")]
public string Email { get; set; }

[Required]
[StringLength(100, MinimumLength = 6, ErrorMessage = "密码长度必须在6-100之间")]
[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{6,}$",
ErrorMessage = "密码必须包含大小写字母和数字")]
public string Password { get; set; }

[Range(18, 150, ErrorMessage = "年龄必须在18-150之间")]
public int Age { get; set; }

[Phone(ErrorMessage = "电话格式不正确")]
public string Phone { get; set; }

[Url(ErrorMessage = "网址格式不正确")]
public string Website { get; set; }
}

// Controller 自动验证
[HttpPost]
public async Task<ActionResult> CreateUser([FromBody] CreateUserRequest request)
{
// ModelState 自动验证
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

// 处理请求
var user = await _userService.CreateUserAsync(request);
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}

API 版本控制

Program.cs
// 安装: dotnet add package Microsoft.AspNetCore.Mvc.Versioning

builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
});

// V1 Controller
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class UsersV1Controller : ControllerBase
{
[HttpGet]
public ActionResult<string> Get() => Ok("Version 1.0");
}

// V2 Controller
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class UsersV2Controller : ControllerBase
{
[HttpGet]
public ActionResult<string> Get() => Ok("Version 2.0");
}

// 使用
// GET /api/v1/users
// GET /api/v2/users

Swagger/OpenAPI 集成

Program.cs
// 安装: dotnet add package Swashbuckle.AspNetCore

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "My API",
Version = "v1",
Description = "API 文档",
Contact = new OpenApiContact
{
Name = "Support",
Email = "support@example.com"
}
});

// 添加 XML 注释
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath);

// 添加 JWT 认证
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});

options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}

最佳实践

RESTful API 最佳实践

1. 使用正确的 HTTP 方法

// ✅ 好
[HttpGet]
public async Task<ActionResult> GetUsers() { }

[HttpPost]
public async Task<ActionResult> CreateUser() { }

// ❌ 差
[HttpGet]
public async Task<ActionResult> CreateUser() { } // 应该用 POST

2. 返回适当的状态码

// ✅ 好
[HttpGet("{id}")]
public async Task<ActionResult> GetUser(int id)
{
var user = await _service.GetAsync(id);
if (user == null) return NotFound();
return Ok(user);
}

[HttpPost]
public async Task<ActionResult> CreateUser(CreateUserRequest request)
{
var user = await _service.CreateAsync(request);
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}

[HttpDelete("{id}")]
public async Task<ActionResult> DeleteUser(int id)
{
await _service.DeleteAsync(id);
return NoContent();
}

3. 使用 DTO 而不是直接返回实体

// ❌ 差:直接返回实体
[HttpGet]
public async Task<ActionResult<User>> GetUser(int id)
{
return await _dbContext.Users.FindAsync(id);
}

// ✅ 好:使用 DTO
[HttpGet]
public async Task<ActionResult<UserDto>> GetUser(int id)
{
var user = await _dbContext.Users.FindAsync(id);
return _mapper.Map<UserDto>(user);
}

4. 实现分页

[HttpGet]
public async Task<ActionResult<PagedResponse<UserDto>>> GetUsers(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20)
{
var (users, totalCount) = await _service.GetUsersAsync(page, pageSize);

return new PagedResponse<List<UserDto>>
{
Success = true,
Data = users,
Page = page,
PageSize = pageSize,
TotalCount = totalCount
};
}

5. 使用过滤和排序

[HttpGet]
public async Task<ActionResult> GetUsers(
[FromQuery] string? search = null,
[FromQuery] string? status = null,
[FromQuery] string? sortBy = "createdAt",
[FromQuery] string? order = "desc",
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20)
{
var query = _dbContext.Users.AsQueryable();

// 搜索
if (!string.IsNullOrEmpty(search))
{
query = query.Where(u => u.Username.Contains(search) || u.Email.Contains(search));
}

// 过滤
if (!string.IsNullOrEmpty(status))
{
query = query.Where(u => u.Status == status);
}

// 排序
query = sortBy?.ToLower() switch
{
"username" => order == "asc" ? query.OrderBy(u => u.Username) : query.OrderByDescending(u => u.Username),
"createdat" => order == "asc" ? query.OrderBy(u => u.CreatedAt) : query.OrderByDescending(u => u.CreatedAt),
_ => query.OrderByDescending(u => u.CreatedAt)
};

// 分页
var totalCount = await query.CountAsync();
var users = await query
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();

return Ok(new { users, totalCount, page, pageSize });
}

总结

关键要点
  • 使用标准的 HTTP 方法操作资源
  • URL 使用名词、复数形式、小写字母
  • 返回适当的 HTTP 状态码
  • 使用统一的响应格式
  • 实现数据验证
  • 使用 DTO 而不是直接返回实体
  • 实现分页、过滤、排序
  • 集成 Swagger 文档
  • 考虑 API 版本控制

相关资源