RESTful API 设计
REST 基础
REST 是什么?
REST (Representational State Transfer) 是一种软件架构风格,使用 HTTP 协议进行通信,通过标准的 HTTP 方法操作资源。
HTTP 方法
HTTP 方法 | 说明 | 幂等性 | 使用场景 |
---|---|---|---|
GET | 获取资源 | ✅ | 查询数据 |
POST | 创建资源 | ❌ | 创建新数据 |
PUT | 更新资源(完整替换) | ✅ | 完整更新 |
PATCH | 更新资源(部分更新) | ❌ | 部分更新 |
DELETE | 删除资源 | ✅ | 删除数据 |
URL 设计
URL 设计原则
- 使用名词而不是动词
- 使用复数形式
- 层级结构清晰
- 使用小写字母和连字符
良好的 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
# 使用动词
GET /api/getUsers
POST /api/createUser
DELETE /api/deleteUser
# 使用大写和下划线
GET /api/User_List
# 不清晰的层级
GET /api/userorders/123
# 混乱的参数
GET /api/users?getUserByStatus=active
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 版本控制