VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • 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的密钥、有效期等从配置文件读取,方便不同环境切换;
基础知识拓展

  1. RESTful核心原则
    原则 说明
    资源定位 用URL唯一标识资源,比如/api/users/1表示ID为1的用户;
    HTTP方法语义化 用GET/POST/PUT/DELETE表示对资源的操作,而非用URL包含动作(如/api/AddUser);
    无状态 每个请求包含所有必要信息,服务器不保存客户端状态,方便扩展和缓存;
    统一接口 所有API使用相同的响应格式、错误处理、状态码,降低学习成本;
    分层系统 服务器可以分层(负载均衡、缓存层、业务层),客户端无需关心;
    按需返回 支持字段过滤(fields=id,userName),减少响应数据量;
  2. HTTP状态码最佳实践
状态码 类别 常用场景
200 成功 查询资源、更新资源成功;
201 成功创建 创建资源成功,返回资源URL;
204 成功无内容 删除资源、更新资源成功,无需返回内容;
400 客户端错误 参数错误、业务验证失败(比如用户名已存在);
401 未授权 未提供JWT令牌、令牌无效;
403 禁止访问 已授权但无权限(比如普通用户尝试删除管理员);
404 资源不存在 用户ID不存在、API路径错误;
429 请求过多 接口限流,请求频率超过限制;
500 服务器错误 未处理的异常、数据库连接失败;
  1. DTO设计原则
    输入DTO(Request):只包含用户需要输入的字段,添加验证注解;
    输出DTO(Response):只返回前端需要的字段,隐藏敏感信息;
    通用DTO:分页结果、错误响应等可以做成通用DTO,避免重复代码;
    驼峰命名:返回JSON用驼峰命名(比如userName),符合前端习惯;
  2. API版本控制方式
方式 示例URL 优点 缺点
URL版本 /api/v1/users、/api/v2/users 清晰直观,易于调试 URL变化大,前端需修改请求
查询参数版本 /api/users?version=1 无需修改URL,兼容旧版本 容易忽略版本参数
Header版本 X-API-Version: 1 干净的URL,不污染资源路径 调试时需手动添加Header
  1. 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


相关教程