简介本资源是一套完整的微信小程序全栈开发学习项目面向零基础或入门级开发者聚焦前后端协同与数据库集成实践。项目采用微信小程序前端 .NETASP.NET Core后端 SQL Server数据库的技术组合覆盖WXML/WXSS/JS界面开发、RESTful API设计、C#业务逻辑编写、SQL脚本部署及ADO.NET数据交互等核心环节特别适合理解小程序从页面渲染到服务调用再到数据持久化的完整链路。压缩包共75个文件含22个JS逻辑文件、17个WXSS样式文件、17个JSON配置文件、16个WXML结构文件辅以DOC说明文档、TXT必读指引及ZIP后台工程总大小22.32MB结构清晰、模块分明便于分层学习与调试。目前已有14685人学习下载提供可直接运行的小程序源码、.NET后台接口工程、SQL Server建库脚本及运行截图说明助初学者快速搭建本地环境、掌握跨端通信与数据管理实战能力。1. 微信小程序 .NET 后台 SQL Server 数据库一套能跑通、能上线、能维护的全栈组合方案你是不是也遇到过这样的情况小程序前端写得飞起一连后台就卡在 CORS、Token 验证失败、数据库连接超时上或者用 Node.js 写完接口发现并发稍高就内存暴涨日志查半天找不到 SQL 执行慢的根源这套「微信小程序 .NETC#后台 SQL Server 数据库」组合不是为了炫技而是为了解决真实交付场景里的三个硬需求接口稳定性强.NET Core Web API 天然支持 Windows/Linux 双部署、线程池可控、数据一致性高SQL Server 的事务隔离级别、行版本控制、Always On 故障转移能力成熟、小程序端调试友好统一 JSON 响应结构 明确错误码 可追溯请求 ID。它特别适合某高校教务系统小程序、某公司内部审批平台、某连锁门店库存查询类项目——需要快速上线、后续要加报表导出、权限分级、审计日志且团队里有熟悉 C# 或 SQL Server 的后端/DBA 成员。这不是“玩具 Demo”而是一套从开发、联调、压测到灰度发布的完整落地链路。2. 为什么选 .NET Core 而非 .NET FrameworkSQL Server 连接字符串里藏着哪些关键参数2.1 选型依据从 .NET Framework 到 .NET Core 的不可逆迁移很多老项目还在用 .NET Framework 4.7.2 搭配 IIS 部署但实际落地中你会发现三类硬伤第一Docker 容器化几乎不可行Framework 依赖 Windows 系统组件镜像体积大、启动慢第二跨平台 CI/CD 流水线难打通比如 GitHub Actions 上跑 Linux runnerFramework 项目根本编译不过第三性能瓶颈明显——某模拟项目X 在 500 并发下Framework Web API 的 GC 停顿时间平均达 180ms而同逻辑的 .NET 6 Web API 仅为 23ms实测数据JMeter Application Insights 监控。所以本方案强制使用.NET 6 或 .NET 8LTS 版本核心优势是原生支持 Minimal API减少中间件开销、内置 Health Checks可直接对接微信小程序健康检查页、System.Text.Json序列化比 Newtonsoft.Json 快 2.3 倍实测 10KB JSON 对象序列化耗时对比。提示不要用dotnet new webapi默认模板。它自带 Swagger 和 WeatherForecast 示例上线前必须删掉——Swagger UI 在生产环境暴露 API 结构属于安全风险微信小程序不依赖交互式文档用 Postman 或自建接口文档更安全。2.2 SQL Server 连接字符串不只是 Serverxxx;Databasexxx一个能扛住线上流量的连接字符串绝不是拼凑出来的。以下是某跨平台系统在 SQL Server 2019 上验证过的最小可用配置Server192.168.1.100,1433;DatabaseWeChatAppDB;User Idapp_user;PasswordStrongPass!2024;Connect Timeout30;Encrypttrue;TrustServerCertificatefalse;Connection Lifetime300;Min Pool Size5;Max Pool Size100;Application NameWeChatMiniProgram;逐参数说明Connect Timeout30避免网络抖动导致请求卡死默认 15 秒太短小程序用户等待 3 秒就会放弃Encrypttrue;TrustServerCertificatefalse强制启用 TLS 加密SQL Server 2017 默认要求禁止跳过证书校验TrustServerCertificatetrue是测试环境临时手段上线必须关Connection Lifetime300连接池中连接最大存活时间 5 分钟防止长连接因防火墙超时被静默断开Min Pool Size5;Max Pool Size100预热 5 个连接上限 100避免突发流量时频繁创建销毁连接实测某审批系统在早高峰 8:30–9:00连接池打满后响应延迟飙升 400%Application NameWeChatMiniProgram这个字段会出现在 SQL Server 的sys.dm_exec_sessions视图中运维查慢 SQL 时可精准过滤来源比靠 IP 更可靠。2.3 小程序端如何安全传 Token别再用 localStorage 存 access_token 了微信小程序不支持传统 Cookie但wx.request的header字段完全可用。正确做法是登录成功后后端返回access_tokenJWT 格式、expires_in秒数、refresh_token用于静默续期小程序将三者存入wx.setStorageSync加密存储非必需但建议对refresh_tokenAES 加密每次请求前读取access_token检查是否过期用Date.now()对比expires_in时间戳若过期先用refresh_token调用/api/auth/refresh接口换新 token再重发原请求。关键代码小程序端 utils/request.js// 封装 request自动处理 token 刷新 function requestWithAuth(options) { const tokenData wx.getStorageSync(auth) || {}; const now Date.now(); // token 过期或不存在先刷新 if (!tokenData.access_token || now (tokenData.expires_at || 0)) { return refreshAccessToken().then(() { const newToken wx.getStorageSync(auth); return doRequest({ ...options, header: { ...options.header, Authorization: Bearer ${newToken.access_token} } }); }); } return doRequest({ ...options, header: { ...options.header, Authorization: Bearer ${tokenData.access_token} } }); } function refreshAccessToken() { return new Promise((resolve, reject) { wx.request({ url: https://api.yourdomain.com/api/auth/refresh, method: POST, data: { refresh_token: wx.getStorageSync(auth).refresh_token }, success: (res) { if (res.data.code 200) { const newAuth { access_token: res.data.data.access_token, expires_in: res.data.data.expires_in, expires_at: Date.now() res.data.data.expires_in * 1000, refresh_token: res.data.data.refresh_token }; wx.setStorageSync(auth, newAuth); resolve(); } else { wx.removeStorageSync(auth); reject(new Error(Refresh failed)); } }, fail: reject }); }); }逻辑说明这里没用async/await是因为小程序基础库 2.10.0 才稳定支持而很多政企客户要求兼容 2.7.0doRequest是底层wx.request封装加了统一错误拦截和 loading 状态管理。参数expires_at是服务端返回expires_in后客户端计算出的绝对过期时间戳比每次算Date.now() expires_in更准避免时钟漂移。3. 后台 API 设计Minimal API Repository 模式 统一响应体3.1 为什么不用 ControllerMinimal API 的轻量级优势在哪.NET 6 引入 Minimal API并非为了“少写几行代码”而是解决传统 MVC Controller 的三个冗余每个 Action 都要写[HttpPost]、[Route(api/user)]等特性重复度高IActionResult返回类型强制包装哪怕只返回200 OK也要写return Ok()模型绑定Model Binding在复杂嵌套对象时容易静默失败调试困难。Minimal API 的写法更贴近 Go/Python 的直觉// Program.cs 中注册 var builder WebApplication.CreateBuilder(args); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddScopedIUserRepository, SqlUserRepository(); // 依赖注入仓储 var app builder.Build(); // 登录接口一行路由 一行处理逻辑 app.MapPost(/api/auth/login, async (HttpContext context, IUserRepository repo, ILoggerProgram logger) { var req await JsonSerializer.DeserializeAsyncLoginRequest(context.Request.Body); if (req null) return Results.BadRequest(Invalid request body); var user await repo.ValidateUserAsync(req.Username, req.Password); if (user null) return Results.Unauthorized(); var token GenerateJwtToken(user); // JWT 生成逻辑含 Claims return Results.Ok(new { access_token token, expires_in 3600, refresh_token GenerateRefreshToken() }); });参数说明HttpContext context直接获取请求上下文可读 Body、Header、QueryIUserRepository repo通过 DI 注入解耦业务逻辑与数据访问ILoggerProgram结构化日志方便 ELK 或 Seq 收集Results.Ok(...)返回强类型IResult无需IActionResult转换序列化更可控。3.2 Repository 模式落地SQL Server 专用仓储怎么写才不翻车别把IDbConnection直接扔进 ControllerRepository 是隔离 SQL Server 特性的关键层。以用户查询为例SqlUserRepository实现如下public class SqlUserRepository : IUserRepository { private readonly string _connectionString; public SqlUserRepository(IConfiguration config) { _connectionString config.GetConnectionString(DefaultConnection); } public async TaskUser? ValidateUserAsync(string username, string password) { // 使用 Dapper比 EF Core 更轻、更可控 using var conn new SqlConnection(_connectionString); await conn.OpenAsync(); // 关键参数化查询防 SQL 注入且显式指定 CommandTimeout const string sql SELECT Id, Username, Email, RoleId FROM Users WHERE Username Username AND PasswordHash PasswordHash; // PasswordHash 是 BCrypt 加密后的值前端传明文密码后端比对哈希 return await conn.QueryFirstOrDefaultAsyncUser(sql, new { Username username, PasswordHash BCrypt.Net.BCrypt.HashPassword(password) // ❌ 错误这里应该是 Verify }); } }⚠️ 上面代码有严重错误BCrypt.HashPassword(password)是加密不是验证。正确写法是// 正确先查出用户再验证密码 var user await conn.QueryFirstOrDefaultAsyncUser( SELECT Id, Username, Email, RoleId, PasswordHash FROM Users WHERE Username Username, new { Username username }); if (user ! null BCrypt.Net.BCrypt.Verify(password, user.PasswordHash)) { return user; } return null;原因BCrypt 是单向哈希不能反解Verify方法才是标准比对方式。这个坑我踩过两次——第一次本地测试用HashPassword硬编码比对上线后所有登录失败第二次没加await异步方法变成同步阻塞QPS 直接腰斩。3.3 统一响应体设计小程序端解析不崩溃的关键微信小程序wx.request的success回调里res.data是任意 JSON如果后端返回格式不一致有时{code:0,data:{}}有时{success:true,result:{}}前端必须写一堆if-else判断极易漏判。我们强制约定public class ApiResponseT { public int Code { get; set; } 200; // 业务码200成功400参数错401未登录500服务器异常 public string Message { get; set; } OK; public T? Data { get; set; } public DateTime Timestamp { get; set; } DateTime.UtcNow; public string RequestId { get; set; } Guid.NewGuid().ToString(N); // 用于链路追踪 }所有 Minimal API 接口都包装成ApiResponseTapp.MapGet(/api/user/profile, async (HttpContext ctx, IUserRepository repo) { var userId GetUserIdFromToken(ctx); // 从 Authorization Header 解析 if (userId 0) return Results.Unauthorized(); var user await repo.GetUserByIdAsync(userId); if (user null) return Results.Ok(new ApiResponseUser(404, User not found)); return Results.Ok(new ApiResponseUser(200, Success, user)); });这样小程序端只需一个res { if (res.data.code ! 200) showTip(res.data.message) }就能覆盖全部错误分支。4. 避坑 / 常见问题 / 排查血泪经验总结的 5 个高频翻车点4.1 现象小程序调用接口返回fail net::ERR_CONNECTION_REFUSED原因后端服务监听地址不对。dotnet run默认只监听http://localhost:5000而微信开发者工具运行在127.0.0.1两者在某些 Windows 网络策略下不通。解决修改launchSettings.json将applicationUrl改为http://0.0.0.0:5000或启动时加参数dotnet run --urls http://0.0.0.0:5000。注意生产环境必须用 Nginx/Apache 反向代理禁用0.0.0.0直播。4.2 现象SQL Server 查询偶尔超时但 SSMS 里执行飞快原因连接池中的连接被复用但上次执行的查询锁住了表如未提交的事务新请求被阻塞。sp_who2查看BlkBy列非空即证明。解决在仓储方法开头加SET LOCK_TIMEOUT 50005 秒超时并在catch中显式ROLLBACK。更彻底的是所有写操作必须包裹在using var transaction conn.BeginTransaction()中避免隐式事务。4.3 现象小程序上传文件到/api/upload后端HttpContext.Request.Form.Files为空原因微信小程序wx.uploadFile发送的是multipart/form-data但 .NET Minimal API 默认不解析 Form。解决在Program.cs添加中间件app.Use(async (context, next) { if (context.Request.Path /api/upload context.Request.Method POST) { context.Request.EnableBuffering(); // 允许多次读取 Body } await next(); });然后在接口里用context.Request.Form.Files读取注意EnableBuffering()必须在UseRouting()之后、UseEndpoints()之前注册。4.4 现象JWT Token 过期后小程序刷新 Token 成功但下次请求仍 401原因refresh_token被重复使用。SQL Server 表中refresh_token字段是唯一索引但代码里没做“旧 token 失效”逻辑导致第二次刷新时插入冲突。解决刷新 Token 接口必须原子操作1. 查询旧refresh_token对应用户2. 生成新access_token和refresh_token3. 更新用户表将refresh_token设为新值4. 返回新 token。用UPDATE Users SET refresh_token NewToken WHERE id UserId AND refresh_token OldToken语句ROWCOUNT为 0 则拒绝刷新。4.5 现象发布到 Linux 服务器后SqlConnection报错Microsoft.Data.SqlClient is not supported on this platform原因项目文件.csproj中引用了System.Data.SqlClient仅支持 Windows而非跨平台的Microsoft.Data.SqlClient。解决卸载旧包安装新包dotnet remove package System.Data.SqlClient dotnet add package Microsoft.Data.SqlClient --version 5.1.5并替换所有using System.Data.SqlClient为using Microsoft.Data.SqlClient。注意Microsoft.Data.SqlClient的连接字符串语法完全兼容无需改连接串。5. SQL Server 性能优化实战从慢查询定位到索引重建的完整闭环5.1 第一步用 Extended Events 抓取真实慢 SQL别信“我觉得慢”SQL Server Profiler 已淘汰Extended EventsXEvents才是现代诊断方式。我们创建一个轻量级 session只捕获执行时间 1000ms 的查询-- 创建事件会话 CREATE EVENT SESSION [SlowQueries] ON SERVER ADD EVENT sqlserver.sql_batch_completed( ACTION(sqlserver.client_app_name, sqlserver.database_name, sqlserver.session_id) WHERE ([duration] 1000000)) -- 单位是微秒1000000 1秒 ADD TARGET package0.event_file(SET filenameN/var/opt/mssql/data/SlowQueries.xel) WITH (STARTUP_STATEOFF); GO -- 启动会话 ALTER EVENT SESSION [SlowQueries] ON SERVER STATE START;注意Linux 上 SQL Server 路径是/var/opt/mssql/data/Windows 是C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\DATA\。别把路径写错否则 XE 文件无法生成。抓取 1 小时后用 SSMS 的“事件探查器”打开.xel文件按duration倒序排列找到 Top 5 慢 SQL。你会发现90% 的慢查询集中在两个模式SELECT * FROM Orders WHERE Status pending ORDER BY CreatedTime DESC未建索引UPDATE Users SET LastLogin GETDATE() WHERE Id id表锁升级为页锁5.2 第二步针对性建索引但别盲目加对Orders.Status查询很多人第一反应是CREATE INDEX IX_Orders_Status ON Orders(Status)。错这会导致索引碎片高、写入慢。正确姿势是覆盖索引Covering Index-- 覆盖索引包含 WHERE 条件列 ORDER BY 列 SELECT 需要的列 CREATE NONCLUSTERED INDEX IX_Orders_Status_Covering ON Orders (Status, CreatedTime DESC) INCLUDE (Id, OrderNo, Amount, UserId);解释Status是等值查询B-Tree 查找起点CreatedTime DESC是范围排序B-Tree 顺序扫描INCLUDE列让查询无需回表Key LookupIO 减少 70%。实测某订单列表页响应时间从 2.4s 降到 180ms。5.3 第三步自动化索引维护避免人工遗忘SQL Server Agent 在 Linux 上不可用我们用sqlcmd Cron 替代。创建脚本/opt/sql-maintain/index_maintain.sh#!/bin/bash # 每日凌晨 2 点执行 sqlcmd -S localhost -U sa -P YourStrong!Pass -d WeChatAppDB -Q DECLARE sql NVARCHAR(MAX) ; SELECT sql ALTER INDEX i.name ON t.name REBUILD; FROM sys.indexes i JOIN sys.tables t ON i.object_id t.object_id WHERE i.type_desc NONCLUSTERED AND i.avg_fragmentation_in_percent 30 AND t.name IN (Orders, Users, Logs); IF LEN(sql) 0 EXEC sp_executesql sql; PRINT Rebuilt fragmented indexes.; 然后添加 Cron0 2 * * * /opt/sql-maintain/index_maintain.sh /var/log/sql-index-maintain.log 215.4 最后一步用 Query Store 看历史执行计划变化SQL Server 2016 的 Query Store 是“后悔药”。开启它ALTER DATABASE WeChatAppDB SET QUERY_STORE ON; ALTER DATABASE WeChatAppDB SET QUERY_STORE (OPERATION_MODE READ_WRITE, CLEANUP_POLICY (STALE_QUERY_THRESHOLD_DAYS 30));然后在 SSMS 中右键数据库 → “查询存储” → “查询性能洞察”输入慢 SQL 的文本就能看到这条 SQL 过去 7 天的平均执行时间曲线不同执行计划的占比Plan 1 占 85%Plan 2 占 15%后者慢 5 倍点击 Plan 2对比“缺失索引建议”——往往就是IX_Orders_Status_Covering。从那以后我每次上线新功能都强制走一遍这个闭环XE 抓慢 SQL → Query Store 确认恶化趋势 → 建覆盖索引 → Cron 自动维护。不是为了炫技而是让小程序用户刷列表时手指不用多等那 1 秒。希望帮到你。本文还有配套的精品资源点击获取