-
C#中关于RESTful API——用户管理接口设计
第三部分:Web应用开发
6. RESTful API——用户管理接口设计
实例介绍
之前做电商后台API时,完全是“野路子”:创建用户用POST /api/AddUser,修改用POST /api/EditUser,删除用GET /api/DeleteUser?id=1,前端对接时天天问“这个接口用什么方法?参数放哪里?返回格式是什么?”;而且接口返回的是数据库实体,把PasswordHash、IsDeleted这些敏感字段都暴露了,还经常因为字段名拼写错误(比如user_name vs UserName)改来改去。后来用RESTful规范重构:URL用资源命名(/api/users),HTTP方法对应CRUD(GET查、POST创、PUT改、DELETE删),返回统一格式的JSON,用DTO隔离输入输出,还自动生成Swagger文档,前端对接时直接看文档就行,沟通成本降了90%,接口稳定性也大幅提升。这节就带你从零设计符合REST规范的用户管理API,彻底vb.net教程C#教程python教程SQL教程access 2010教程告别“接口混乱”的痛苦。
需求分析
RESTful API要解决“接口规范、输入输出隔离、错误统一处理、文档自动生成、安全可控”的问题,具体需求如下:
1.核心CRUD接口:用户列表(分页)、用户详情、创建用户、更新用户、删除用户;
2.REST规范:资源命名用复数,HTTP方法语义化(GET/POST/PUT/DELETE),状态码正确使用;
3.输入输出隔离:用DTO(数据传输对象)接收请求参数、返回响应数据,隐藏敏感字段;
4.分页排序过滤:支持分页(page/size)、排序(sort=+id/-registerTime)、过滤(search=用户名);
5.统一错误处理:所有错误返回相同格式的JSON,包含错误码、消息、详情;
6.文档自动生成:集成Swagger,显示接口描述、参数说明、返回示例;
7.安全保护:JWT认证,限制未授权访问;接口限流,防止恶意请求;
8.版本控制:支持API版本迭代,不影响旧版本接口;
9.性能优化:缓存用户列表,压缩响应数据,减少带宽消耗。
代码实现
前置条件:.NET 6+、ASP.NET Core Web API;需掌握JWT基础、Swagger配置;推荐用Swashbuckle.AspNetCore生成文档,Microsoft.AspNetCore.Authentication.JwtBearer做JWT认证。
场景1:基础CRUD接口(REST规范实现)
定义用户管理的核心接口,遵循REST规范,用DTO隔离输入输出。
步骤1:定义DTO(Models/Dtos/UserDtos.cs)
csharp
using System.ComponentModel.DataAnnotations;
namespace ECommerceWeb.Models.Dtos
{
// 响应DTO:返回给前端的用户数据,隐藏敏感字段
public record UserDto(
int Id,
string UserName,
string Email,
DateTime RegisterTime,
bool IsEnabled);
// 创建用户请求DTO:只包含需要用户输入的字段
public record CreateUserRequest(
[Required(ErrorMessage = "用户名不能为空")]
[StringLength(20, MinimumLength = 3, ErrorMessage = "用户名长度3-20字符")]
string UserName,
[Required(ErrorMessage = "邮箱不能为空")]
[EmailAddress(ErrorMessage = "邮箱格式错误")]
string Email,
[Required(ErrorMessage = "密码不能为空")]
[StringLength(16, MinimumLength = 6, ErrorMessage = "密码长度6-16字符")]
string Password);
// 更新用户请求DTO:只允许更新可修改的字段(比如邮箱、状态)
public record UpdateUserRequest(
[EmailAddress(ErrorMessage = "邮箱格式错误")]
string? Email,
bool? IsEnabled);
}
步骤2:Users API控制器(Controllers/UsersApiController.cs)
csharp
using Microsoft.AspNetCore.Mvc;
using ECommerceWeb.Models;
using ECommerceWeb.Models.Dtos;
using ECommerceWeb.Repositories;
namespace ECommerceWeb.Controllers
{
[Route("api/[controller]")] // API路径:/api/users
[ApiController] // 自动模型验证、返回JSON格式
public class UsersController : ControllerBase
{
private readonly IUserRepository _userRepo;
// 构造函数注入数据访问服务
public UsersController(IUserRepository userRepo)
{
_userRepo = userRepo;
}
// 1. 获取用户列表(分页、排序、过滤)
// GET /api/users?page=1&size=10&search=test&sort=-registerTime
[HttpGet]
public async Task<ActionResult<PagedResult<UserDto>>> GetUsers(
[FromQuery] int page = 1,
[FromQuery] int size = 10,
[FromQuery] string? search = null,
[FromQuery] string? sort = null)
{
// 分页参数验证
if (page < 1) page = 1;
if (size < 1 || size > 50) size = 10;
// 构建查询
var query = _userRepo.GetAll();
// 过滤:按用户名/邮箱模糊搜索
if (!string.IsNullOrWhiteSpace(search))
{
query = query.Where(u => u.UserName.Contains(search) || u.Email.Contains(search));
}
// 排序:支持+升序、-降序,比如sort=+id/-registerTime
if (!string.IsNullOrWhiteSpace(sort))
{
var sortField = sort.TrimStart('+', '-');
var isDescending = sort.StartsWith('-');
query = isDescending ? query.OrderByDescending(u => EF.Property<object>(u, sortField))
: query.OrderBy(u => EF.Property<object>(u, sortField));
}
else
{
// 默认按注册时间降序
query = query.OrderByDescending(u => u.RegisterTime);
}
// 分页查询
var totalCount = await query.CountAsync();
var users = await query.Skip((page - 1) * size)
.Take(size)
.Select(u => new UserDto(
u.Id,
u.UserName,
u.Email,
u.RegisterTime,
u.IsEnabled))
.ToListAsync();
// 返回分页结果
return Ok(new PagedResult<UserDto>
{
Page = page,
Size = size,
Total = totalCount,
Data = users
});
}
// 2. 获取用户详情
// GET /api/users/1
[HttpGet("{id:int}")] // 路由参数限制为整数
public async Task<ActionResult<UserDto>> GetUser(int id)
{
var user = await _userRepo.GetByIdAsync(id);
if (user == null)
{
return NotFound(new ApiError(404, "用户不存在")); // 404状态码+错误信息
}
return Ok(new UserDto(
user.Id,
user.UserName,
user.Email,
user.RegisterTime,
user.IsEnabled));
}
// 3. 创建用户
// POST /api/users
[HttpPost]
public async Task<ActionResult<UserDto>> CreateUser(CreateUserRequest request)
{
// 模型验证:[ApiController]自动验证,验证失败返回400
// 业务验证:用户名/邮箱是否已存在
if (await _userRepo.GetAll().AnyAsync(u => u.UserName == request.UserName))
{
return BadRequest(new ApiError(400, "用户名已存在"));
}
if (await _userRepo.GetAll().AnyAsync(u => u.Email == request.Email))
{
return BadRequest(new ApiError(400, "邮箱已被注册"));
}
// 创建用户(密码加密,后续JWT章节实现)
var user = new User
{
UserName = request.UserName,
Email = request.Email,
PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.Password), // BCrypt加密
RegisterTime = DateTime.Now,
IsEnabled = true
};
await _userRepo.AddAsync(user);
// 返回201状态码+创建的用户数据
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, new UserDto(
user.Id,
user.UserName,
user.Email,
user.RegisterTime,
user.IsEnabled));
}
// 4. 更新用户(部分更新)
// PUT /api/users/1
[HttpPut("{id:int}")]
public async Task<IActionResult> UpdateUser(int id, UpdateUserRequest request)
{
var user = await _userRepo.GetByIdAsync(id);
if (user == null)
{
return NotFound(new ApiError(404, "用户不存在"));
}
// 部分更新:只修改非空的字段
if (!string.IsNullOrWhiteSpace(request.Email))
{
// 验证邮箱是否已被其他用户使用
if (await _userRepo.GetAll().AnyAsync(u => u.Email == request.Email && u.Id != id))
{
return BadRequest(new ApiError(400, "邮箱已被注册"));
}
user.Email = request.Email;
}
if (request.IsEnabled.HasValue)
{
user.IsEnabled = request.IsEnabled.Value;
}
await _userRepo.UpdateAsync(user);
return NoContent(); // 204状态码:更新成功,无返回内容
}
// 5. 删除用户
// DELETE /api/users/1
[HttpDelete("{id:int}")]
public async Task<IActionResult> DeleteUser(int id)
{
var user = await _userRepo.GetByIdAsync(id);
if (user == null)
{
return NotFound(new ApiError(404, "用户不存在"));
}
await _userRepo.DeleteAsync(id);
return NoContent(); // 204状态码:删除成功
}
}
// 分页结果通用DTO
public record PagedResult<T>(
int Page,
int Size,
int Total,
List<T> Data);
// 统一错误响应DTO
public record ApiError(
int Code,
string Message,
string? Details = null);
}
场景2:统一错误处理(中间件实现)
捕获全局异常,返回统一格式的错误响应,避免暴露敏感信息。
步骤1:错误中间件(Middleware/ErrorHandlingMiddleware.cs)
csharp
using System.Text.Json;
namespace ECommerceWeb.Middleware
{
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ErrorHandlingMiddleware> _logger;
private readonly IWebHostEnvironment _env;
public ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> logger, IWebHostEnvironment env)
{
_next = next;
_logger = logger;
_env = env;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context); // 调用下一个中间件
}
catch (Exception ex)
{
// 捕获异常,记录日志
_logger.LogError(ex, "未处理异常:{Message}", ex.Message);
// 返回统一错误响应
await HandleExceptionAsync(context, ex);
}
}
private async Task HandleExceptionAsync(HttpContext context, Exception ex)
{
context.Response.ContentType = "application/json";
var response = context.Response;
var error = new ApiError
{
Code = GetStatusCode(ex),
Message = "服务器内部错误,请稍后重试"
};
// 开发环境返回异常详情,生产环境隐藏
if (_env.IsDevelopment())
{
error.Details = ex.ToString();
}
// 序列化错误响应
var json = JsonSerializer.Serialize(error, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
await context.Response.WriteAsync(json);
}
// 根据异常类型返回对应的状态码
private int GetStatusCode(Exception ex)
{
return ex switch
{
ArgumentException => 400, // 参数错误
UnauthorizedAccessException => 401, // 未授权
KeyNotFoundException => 404, // 资源不存在
_ => 500 // 服务器内部错误
};
}
}
}
步骤2:注册中间件(Program.cs)
csharp
var app = builder.Build();
// 注册错误处理中间件(要在其他中间件之前)
app.UseMiddleware<ErrorHandlingMiddleware>();
// ...其他中间件配置
app.UseHttpsRedirection();
app.UseAuthentication(); // JWT认证中间件
app.UseAuthorization();
app.MapControllers();
app.Run();
场景3:Swagger文档自动生成
集成Swagger,自动生成API文档,支持在线调试。
步骤1:安装Swashbuckle NuGet包
bash
dotnet add package Swashbuckle.AspNetCore
步骤2:配置Swagger(Program.cs)
csharp
// 添加Swagger服务
builder.Services.AddSwaggerGen(options =>
{
// 生成API文档的标题和版本
options.SwaggerDoc("v1", new OpenApiInfo { Title = "电商后台API", Version = "v1" });
// 读取XML注释(需在项目属性中启用XML文档文件)
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath, includeControllerXmlComments: true);
// 配置JWT认证支持
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "请输入JWT令牌,格式:Bearer {token}",
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" }
},
Array.Empty<string>()
}
});
});
// 启用Swagger中间件(开发环境)
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "电商后台API v1");
options.RoutePrefix = string.Empty; // 访问根路径直接显示Swagger文档
});
}
步骤3:添加XML注释(项目属性)
右键项目 → 属性 → 生成 → 勾选“XML文档文件”,路径设为bin$(Configuration)$(TargetFramework)$(AssemblyName).xml。
场景4:JWT认证保护接口
配置JWT认证,限制未授权用户访问API。
步骤1:配置JWT服务(Program.cs)
csharp
// 添加JWT认证服务
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true, // 验证发行者
ValidateAudience = true, // 验证受众
ValidateLifetime = true, // 验证有效期
ValidateIssuerSigningKey = true, // 验证签名密钥
ValidIssuer = builder.Configuration["Jwt:Issuer"], // 从配置读取
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"]!))
};
});
// 添加授权服务
builder.Services.AddAuthorization();
步骤2:配置文件(appsettings.json)
json
{
"Jwt": {
"Issuer": "ECommerceWeb",
"Audience": "ECommerceWebClients",
"SecretKey": "your-256-bit-secret-key-here", // 至少16位字符
"ExpiresInMinutes": 60
}
}
步骤3:保护接口(UsersController.cs)
在控制器或动作上添加[Authorize]特性:
csharp
[Authorize] // 整个控制器需要认证
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
// ...接口方法
}
逐行讲解
场景1:基础CRUD接口核心代码
1.[ApiController]特性:
1.自动模型验证:验证失败返回400错误,无需手动判断ModelState.IsValid;
2.自动返回JSON:默认返回JSON格式,无需指定[Produces("application/json")];
3.路由绑定:自动绑定路由参数、查询参数到动作参数;
2.HTTP方法语义化:
1.[HttpGet]:查询资源(用户列表/详情);
2.[HttpPost]:创建资源(创建用户);
3.[HttpPut]:更新资源(全量/部分更新用户);
4.[HttpDelete]:删除资源;
3.DTO隔离:
1.CreateUserRequest:只包含需要用户输入的字段,避免接收敏感参数;
2.UserDto:只返回前端需要的字段,隐藏PasswordHash、IsDeleted等敏感信息;
4.状态码正确使用:
1.200:成功查询/更新;
2.201:成功创建资源,返回CreatedAtAction包含资源URL;
3.204:成功删除/更新,无返回内容;
4.400:参数错误/业务验证失败;
5.404:资源不存在;
6.500:服务器内部错误;
场景2:统一错误处理
1.全局异常捕获:中间件捕获所有未处理的异常,避免返回默认的HTML错误页面;
2.环境区分:开发环境返回异常详情,方便调试;生产环境隐藏详情,避免泄露敏感信息;
3.状态码映射:根据异常类型返回对应的HTTP状态码,比如KeyNotFoundException返回404;
场景3:Swagger文档
1.XML注释:启用后,接口、参数、DTO的注释会显示在Swagger文档中,提升可读性;
2.JWT支持:配置后,Swagger页面可以输入JWT令牌,直接调试需要认证的接口;
3.根路径访问:RoutePrefix = string.Empty,访问https://localhost:5001直接显示Swagger文档;
场景4:JWT认证
1.TokenValidationParameters:验证JWT的发行者、受众、有效期、签名密钥,确保令牌合法;
2.[Authorize]特性:添加到控制器或动作上,未授权用户访问会返回401错误;
3.配置驱动:JWT的密钥、有效期等从配置文件读取,方便不同环境切换;
基础知识拓展
-
RESTful核心原则
原则 说明
资源定位 用URL唯一标识资源,比如/api/users/1表示ID为1的用户;
HTTP方法语义化 用GET/POST/PUT/DELETE表示对资源的操作,而非用URL包含动作(如/api/AddUser);
无状态 每个请求包含所有必要信息,服务器不保存客户端状态,方便扩展和缓存;
统一接口 所有API使用相同的响应格式、错误处理、状态码,降低学习成本;
分层系统 服务器可以分层(负载均衡、缓存层、业务层),客户端无需关心;
按需返回 支持字段过滤(fields=id,userName),减少响应数据量; - HTTP状态码最佳实践
| 状态码 | 类别 | 常用场景 |
|---|---|---|
| 200 | 成功 | 查询资源、更新资源成功; |
| 201 | 成功创建 | 创建资源成功,返回资源URL; |
| 204 | 成功无内容 | 删除资源、更新资源成功,无需返回内容; |
| 400 | 客户端错误 | 参数错误、业务验证失败(比如用户名已存在); |
| 401 | 未授权 | 未提供JWT令牌、令牌无效; |
| 403 | 禁止访问 | 已授权但无权限(比如普通用户尝试删除管理员); |
| 404 | 资源不存在 | 用户ID不存在、API路径错误; |
| 429 | 请求过多 | 接口限流,请求频率超过限制; |
| 500 | 服务器错误 | 未处理的异常、数据库连接失败; |
-
DTO设计原则
输入DTO(Request):只包含用户需要输入的字段,添加验证注解;
输出DTO(Response):只返回前端需要的字段,隐藏敏感信息;
通用DTO:分页结果、错误响应等可以做成通用DTO,避免重复代码;
驼峰命名:返回JSON用驼峰命名(比如userName),符合前端习惯; - API版本控制方式
| 方式 | 示例URL | 优点 | 缺点 |
|---|---|---|---|
| URL版本 | /api/v1/users、/api/v2/users | 清晰直观,易于调试 | URL变化大,前端需修改请求 |
| 查询参数版本 | /api/users?version=1 | 无需修改URL,兼容旧版本 | 容易忽略版本参数 |
| Header版本 | X-API-Version: 1 | 干净的URL,不污染资源路径 | 调试时需手动添加Header |
-
API性能优化
缓存:用[ResponseCache]特性或Redis缓存频繁查询的接口(比如用户列表);
限流:用AspNetCoreRateLimit包实现接口限流,防止恶意请求;
压缩:启用Gzip/Brotli压缩,减少响应数据量;
异步接口:所有数据库操作、HTTP调用用async/await,提升并发能力;
总结
RESTful API的价值在于规范统一、易于维护、对接高效,让前后端开发都有章可循,避免“接口混乱”的问题。关键要点:
1.语义化优先:用HTTP方法表示操作,用URL表示资源,避免在URL中包含动作;
2.DTO隔离:输入输出严格分离,隐藏敏感字段,减少接口耦合;
3.错误统一:所有错误返回相同格式,方便前端统一处理;
4.文档驱动:集成Swagger,自动生成文档,降低沟通成本;
5.安全可控:JWT认证保护接口,限流防止恶意请求;
6.版本迭代:支持API版本,不影响旧版本用户;
比如电商后台中,RESTful API让前端对接效率提升了数倍,后端接口的可维护性大幅增强,迭代新功能时无需担心影响旧版本。掌握RESTful规范,你设计的API会更专业、更易用、更稳定!
来源:https://www.xin3721.com/ArticlecSharp/c49476.html










