C# .NET 6 WebAPI 对接微信小程序:3类接口设计与JWT鉴权实战
当开发者需要为微信小程序构建稳定可靠的后端服务时,C# .NET 6 WebAPI凭借其高性能和丰富的生态成为理想选择。本文将深入探讨三种典型接口的设计实现,并构建完整的JWT鉴权体系,为缺乏小程序联调经验的.NET开发者提供可直接复用的工程化解决方案。
1. 环境准备与基础配置
在开始接口开发前,需要完成基础环境的搭建。首先确保已安装.NET 6 SDK和Visual Studio 2022(或Rider等IDE)。创建新项目时选择"ASP.NET Core Web API"模板,并勾选启用OpenAPI支持:
dotnet new webapi -n WeChatMiniAppApi --framework net6.0微信小程序要求所有网络请求必须使用HTTPS协议,因此在Program.cs中需强制启用HTTPS重定向:
app.UseHttpsRedirection();小程序后端接口的典型配置参数如下表所示:
| 配置项 | 示例值 | 说明 |
|---|---|---|
| BaseUrl | https://api.yourdomain.com | 已备案的HTTPS域名 |
| RequestTimeout | 30000 | 微信请求超时(毫秒) |
| MaxRequestBodySize | 20971520 | 文件上传最大支持20MB |
| CORS Policy | WeChatPolicy | 跨域策略名称 |
配置微信小程序合法域名时,需在微信公众平台将API域名加入request合法域名列表。同时配置开发环境的appsettings.Development.json:
{ "WeChatSettings": { "AppId": "wx你的小程序appid", "AppSecret": "你的小程序appsecret" } }2. JWT鉴权体系实现
微信小程序推荐使用自定义登录态维护用户身份,JWT(JSON Web Token)因其无状态特性成为首选方案。首先安装必要的NuGet包:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer dotnet add package System.IdentityModel.Tokens.Jwt在Program.cs中配置JWT认证服务:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidateAudience = true, ValidAudience = builder.Configuration["Jwt:Audience"], ValidateLifetime = true, IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"])), ValidateIssuerSigningKey = true, ClockSkew = TimeSpan.Zero // 严格校验过期时间 }; });创建JwtHelper工具类处理token生成:
public static class JwtHelper { public static string GenerateToken(string openId, IConfiguration config) { var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, openId), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) }; var key = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(config["Jwt:SecretKey"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: config["Jwt:Issuer"], audience: config["Jwt:Audience"], claims: claims, expires: DateTime.Now.AddDays(7), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); } }注意:实际生产环境中应考虑使用分布式缓存存储token黑名单,实现更灵活的身份控制
3. 用户登录接口设计
微信小程序登录流程涉及三方交互:小程序端、开发者服务器和微信接口服务。典型登录接口实现如下:
[ApiController] [Route("api/[controller]")] public class AuthController : ControllerBase { private readonly IHttpClientFactory _httpClient; private readonly IConfiguration _config; public AuthController(IHttpClientFactory httpClient, IConfiguration config) { _httpClient = httpClient; _config = config; } [HttpPost("login")] public async Task<IActionResult> Login([FromBody] LoginRequest request) { // 1. 校验code有效性 var client = _httpClient.CreateClient(); var url = $"https://api.weixin.qq.com/sns/jscode2session?" + $"appid={_config["WeChatSettings:AppId"]}" + $"&secret={_config["WeChatSettings:AppSecret"]}" + $"&js_code={request.Code}" + "&grant_type=authorization_code"; var response = await client.GetFromJsonAsync<WeChatSessionResponse>(url); if (string.IsNullOrEmpty(response.OpenId)) return BadRequest("微信登录失败"); // 2. 创建或更新用户记录 var user = await _userRepository.FindOrCreateAsync(response.OpenId); // 3. 生成JWT var token = JwtHelper.GenerateToken(response.OpenId, _config); return Ok(new LoginResponse { Token = token, UserInfo = new UserDto(user) }); } }登录流程中涉及的关键DTO定义:
public record LoginRequest(string Code); public record WeChatSessionResponse(string OpenId, string SessionKey, string UnionId); public record LoginResponse(string Token, UserDto UserInfo);4. 数据获取接口实现
数据接口需要同时考虑安全性和性能优化。以下是带分页的商品列表接口示例:
[Authorize] [HttpGet("products")] public async Task<IActionResult> GetProducts([FromQuery] ProductQuery query) { // 1. 参数校验 if (query.PageSize > 100) return BadRequest("单页数量不能超过100"); // 2. 从JWT中获取用户身份 var openId = User.FindFirstValue(JwtRegisteredClaimNames.Sub); // 3. 构建查询条件 var filter = new ProductFilter { CategoryId = query.CategoryId, MinPrice = query.MinPrice, MaxPrice = query.MaxPrice, Keywords = query.Keywords }; // 4. 分页查询 var (products, total) = await _productService.GetPagedListAsync( filter, query.PageIndex, query.PageSize); // 5. 返回标准化响应 return Ok(new PagedResult<ProductDto>( products.Select(p => new ProductDto(p)), query.PageIndex, query.PageSize, total)); }为提高接口安全性,建议实现以下防护措施:
- 参数签名验证:防止参数篡改
- 请求频率限制:防止暴力请求
- SQL注入防护:使用参数化查询
- 敏感数据脱敏:如手机号中间四位替换为*
5. 文件上传接口设计
微信小程序常见的文件上传场景包括用户头像、反馈图片等。以下是支持多文件上传的接口实现:
[Authorize] [HttpPost("upload")] [RequestSizeLimit(20 * 1024 * 1024)] // 20MB限制 public async Task<IActionResult> UploadFiles(List<IFormFile> files) { var openId = User.FindFirstValue(JwtRegisteredClaimNames.Sub); var result = new List<UploadResult>(); foreach (var file in files) { // 1. 文件类型校验 var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); if (!_allowedExtensions.Contains(ext)) { result.Add(new UploadResult { FileName = file.FileName, Success = false, Message = "不支持的文件类型" }); continue; } // 2. 生成唯一文件名 var newFileName = $"{Guid.NewGuid()}{ext}"; var savePath = Path.Combine(_env.WebRootPath, "uploads", newFileName); // 3. 保存文件 await using var stream = new FileStream(savePath, FileMode.Create); await file.CopyToAsync(stream); // 4. 记录上传结果 result.Add(new UploadResult { FileName = file.FileName, FileUrl = $"/uploads/{newFileName}", FileSize = file.Length, Success = true }); } return Ok(result); }文件上传接口的关键配置参数:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| MaxRequestBodySize | 20971520 | Kestrel最大请求体 |
| MultipartBodyLengthLimit | 20971520 | 多部分表单限制 |
| FileSizeLimit | 5242880 | 单个文件最大5MB |
| AllowedExtensions | .jpg,.png,.gif | 允许的扩展名 |
6. 小程序端请求封装
为统一处理鉴权和错误,建议在小程序端封装wx.request:
const request = (url, method, data) => { return new Promise((resolve, reject) => { const token = wx.getStorageSync('token') wx.request({ url: `https://your-api-domain.com${url}`, method: method, data: data, header: { 'Content-Type': 'application/json', 'Authorization': token ? `Bearer ${token}` : '' }, success: (res) => { if (res.statusCode === 401) { // token过期处理 wx.navigateTo({ url: '/pages/login/login' }) return } if (res.data.code !== 0) { wx.showToast({ title: res.data.message, icon: 'none' }) reject(res.data) return } resolve(res.data.data) }, fail: (err) => { wx.showToast({ title: '网络错误', icon: 'none' }) reject(err) } }) }) } // 使用示例 const fetchProducts = async (params) => { return request('/api/products', 'GET', params) }7. 异常处理与日志记录
完善的异常处理机制能显著提升接口可靠性。在.NET 6中配置全局异常处理:
// Program.cs app.UseExceptionHandler(appError => { appError.Run(async context => { context.Response.ContentType = "application/json"; var contextFeature = context.Features.Get<IExceptionHandlerFeature>(); if (contextFeature != null) { // 记录异常日志 _logger.LogError(contextFeature.Error, "全局异常捕获"); // 构造标准化错误响应 await context.Response.WriteAsync(new ErrorDetails { StatusCode = context.Response.StatusCode, Message = contextFeature.Error is BusinessException ? contextFeature.Error.Message : "系统异常" }.ToString()); } }); });建议将日志记录到文件的同时,接入APM工具如Application Insights实现:
- 请求链路追踪
- 性能指标监控
- 异常告警通知
- 依赖调用分析
8. 性能优化实践
针对小程序场景的高并发特点,可采用以下优化策略:
缓存策略示例:
[HttpGet("categories")] [ResponseCache(Duration = 3600)] // 客户端缓存1小时 public async Task<IActionResult> GetCategories() { var cacheKey = "product_categories"; if (!_cache.TryGetValue(cacheKey, out List<Category> categories)) { categories = await _categoryService.GetAllAsync(); _cache.Set(cacheKey, categories, new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromHours(1))); } return Ok(categories); }数据库查询优化技巧:
- 使用
AsNoTracking()避免不必要的变更跟踪 - 通过
Select只查询必要字段 - 合理配置索引,特别是高频查询条件
- 对大数据集使用分页查询
异步编程规范:
- Controller方法全部使用async/await
- 避免
.Result或.Wait()导致的死锁 - 长时间运行任务使用
BackgroundService - IO密集型操作配置合理的超时时间
9. 安全加固措施
除JWT鉴权外,还需实施以下安全防护:
防XSS攻击:
services.AddControllers(options => { options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); });CSRF防护配置:
builder.Services.AddAntiforgery(options => { options.HeaderName = "X-CSRF-TOKEN"; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; });敏感数据保护:
// 配置数据保护API builder.Services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo("/path/to/keys")) .SetApplicationName("WeChatMiniApp") .ProtectKeysWithCertificate(certificate);10. 部署与监控
推荐使用Docker容器化部署,示例Dockerfile:
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /src COPY ["WeChatMiniAppApi.csproj", "."] RUN dotnet restore "WeChatMiniAppApi.csproj" COPY . . RUN dotnet build "WeChatMiniAppApi.csproj" -c Release -o /app/build FROM build AS publish RUN dotnet publish "WeChatMiniAppApi.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "WeChatMiniAppApi.dll"]关键监控指标应包括:
- 接口响应时间P99
- 错误率与异常统计
- JWT签发/验证性能
- 数据库查询效率
- 外部API调用成功率
11. 接口测试策略
采用分层测试策略确保接口质量:
单元测试示例:
[Fact] public void GenerateToken_ShouldReturnValidJwt() { // Arrange var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary<string, string> { ["Jwt:SecretKey"] = "test_key_1234567890", ["Jwt:Issuer"] = "test_issuer", ["Jwt:Audience"] = "test_audience" }) .Build(); // Act var token = JwtHelper.GenerateToken("test_openid", config); // Assert Assert.NotNull(token); Assert.NotEmpty(token); }集成测试要点:
- 测试带鉴权的API访问
- 验证微信code交换流程
- 文件上传大小限制测试
- 并发请求压力测试
- 错误参数边界测试
12. 实际项目经验分享
在电商类小程序项目中,我们遇到了购物车高并发更新的挑战。最终解决方案是:
- 使用Redis分布式锁控制并发
- 采用乐观并发控制处理冲突
- 客户端实现本地缓存减少请求
- 重要操作添加二次确认
另一个教育类小程序中,视频上传接口经过以下优化:
- 分片上传支持
- 断点续传功能
- OSS直传方案
- 上传进度实时反馈
对于需要更高安全要求的金融类小程序,我们额外实现了:
- 接口调用白名单
- 请求参数签名
- 敏感操作短信验证
- 操作日志审计