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 版本控制

相关资源​