我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

C# .NET Core 微信小程序商城实战:支付库存订单闭环

C# .NET Core 微信小程序商城实战:支付库存订单闭环 简介这是一套基于C#与.NET Core构建的商用级微信小程序多店铺商城系统面向中高级开发者及企业级项目学习者解决电商类小程序从开发到部署的全栈实践需求。资源包含1962个文件以789个C#后端逻辑文件如OrderController.cs、UserController.cs、218个JS前端交互脚本、188个PNG图标资源、147个CSHTML管理后台视图及63个JSON配置文件为主辅以WXML/WXSS小程序页面结构与样式整体压缩包仅10.33MB轻量但功能完备。已有1278人学习下载体现了其在实战教学与快速落地中的高参考价值。读者可直接获取含三级分销、多店铺运营、物流对接、优惠券与积分体系、插件化扩展能力的完整解决方案代码结构清晰、模块职责分明特别适合用于二次开发、架构学习或毕业设计原型搭建。1. 这不是“又一个小程序模板”C# .NET Core 构建的微信小程序商城真能跑通服务端闭环你搜“小程序商城源码”90% 是 Vue/UniApp 前端配 Node.js 或 PHP 后端剩下 10% 里Java 和 Python 占大头。但如果你手头是 Windows 生产环境、团队主攻 C#、已有 SQL Server 或 Oracle 数据库、甚至要对接金蝶/用友 ERP 接口——那这份基于 C# 的小程序商城原生微信小程序 .NET Core就不是“可选项”而是“少踩三年坑的刚需”。它不是把 Vue 页面套个 .NET Core API 层而是从微信登录态校验、支付回调验签、商品 SKU 库存原子扣减、订单状态机流转到后台管理系统的角色权限 RBAC 控制全部用 C# 实现且所有接口都按微信小程序官方文档要求做签名、加密、重放校验。我去年在一家医疗器械经销商落地时直接复用这套结构把原有 WinForm 上位机团队无缝拉进小程序开发3 天就跑通扫码下单 → 支付成功 → 打印发货单全流程。适合.NET 开发者想快速交付小程序商城、企业内部系统需与现有 C# 生态深度集成、对支付/库存/订单一致性有强事务要求的场景。2. 为什么选 .NET Core 而不是 ASP.NET Framework技术栈拆解与模块职责2.1 核心架构图三层分离 微信特有链路整个项目采用清晰的分层架构Presentation 层微信小程序端原生 WXML WXSS JavaScript无任何框架依赖所有网络请求走wx.request严格遵循微信小程序 HTTPS 强制要求app.js中统一注入token和openid避免每个页面重复获取。API 层.NET Core Web API基于 .NET Core 6.0非 .NET 5 或 7原因见下文避坑章节使用Microsoft.AspNetCore.Mvc.Core统一路由所有 Controller 继承自WeChatBaseController该基类自动完成Authorization头解析并校验 JWTToken 由小程序code2Session换取后生成微信支付回调验签SHA256withRSA 商户证书敏感操作日志记录如库存扣减、退款申请Business Data LayerC# 业务逻辑 EF Core核心业务封装在Services文件夹如IOrderService、IInventoryService数据访问层使用 EF Core 6.0不使用 Database-First全部 Code-First Migration 管理实体类中大量使用[Column(xxx)]显式映射字段规避微信字段名如open_id与 C# 命名规范冲突。提示项目未使用 DDD 分层但通过Domain文件夹隔离了领域模型如OrderAggregateRoot避免贫血模型。这是为后续接入 Saga 分布式事务预留的扩展点。2.2 关键技术选型理由不是跟风是填坑技术组件选用版本为什么不用其他方案血泪经验.NET Core 版本6.0 LTS不选 7.0/8.0微信支付 SDKWxPaySDK官方仅适配至 .NET Core 6.0升级后RSACryptoServiceProvider被弃用导致验签失败曾试过手动移植验签逻辑耗时 2 天仍存在签名长度不一致问题最终回滚数据库 ORMEF Core 6.0不选 Dapper库存扣减需强事务 并发控制EF Core 的ExecuteUpdateAsyncRowVersion更易实现乐观并发Dapper 手写 SQL 易漏WHERE Version oldVersion线上出现超卖JWT Token 签发Microsoft.IdentityModel.Tokens6.22.0不选System.IdentityModel.Tokens.Jwt旧版新版支持SecurityTokenDescriptor.SigningCredentials显式指定密钥长度兼容微信aes-128-cbc加密要求旧版默认 RSA-2048微信解密失败返回invalid aes key微信登录态管理自研WeChatSessionManager不用 IdentityServer4微信code2Session返回的是openidsession_key非标准 OAuth2 流程IS4 配置复杂且增加单点故障点IS4 在高并发下 session_key 解密延迟达 800ms导致小程序白屏2.3 项目目录结构一眼看懂谁管什么/src ├── WeChatMiniProgram.API/ # 主 API 项目.NET Core Web API │ ├── Controllers/ │ │ ├── AuthController.cs # 登录、code2Session、token 刷新 │ │ ├── ProductController.cs # 商品列表、详情、SKU 查询 │ │ ├── OrderController.cs # 创建订单、支付回调、订单查询 │ │ └── AdminController.cs # 后台管理接口需 JWT 角色校验 │ ├── Services/ │ │ ├── IWeChatAuthService.cs # 微信登录态校验服务 │ │ ├── IOrderService.cs # 订单状态机服务含支付成功后库存扣减 │ │ └── IInventoryService.cs # 库存服务支持秒杀场景的 RedisDB 双写 │ ├── Models/ │ │ ├── WeChatLoginResult.cs # code2Session 返回结构体 │ │ └── OrderAggregateRoot.cs # 订单聚合根含 Address、Items、PaymentInfo │ └── Filters/ │ └── WeChatSignatureFilter.cs # 全局过滤器校验微信回调签名 ├── WeChatMiniProgram.Client/ # 微信小程序源码已编译为 miniprogram/ 目录 │ ├── pages/ │ │ ├── index/index.wxml # 首页含商品瀑布流 下拉刷新 │ │ ├── product/detail.wxml # 商品详情含规格选择器 加入购物车 │ │ └── order/confirm.wxml # 订单确认页调用微信 JS-SDK 发起支付 │ └── utils/ │ └── request.js # 封装 wx.request自动添加 Authorization header └── WeChatMiniProgram.Data/ # EF Core DbContext Migration ├── ApplicationDbContext.cs └── Migrations/ # 包含 20230515182233_Init.cs 等 7 个迁移脚本注意WeChatMiniProgram.Client是完整的小程序源码不是 npm 包或构建产物可直接导入微信开发者工具运行WeChatMiniProgram.API需自行发布为 IIS 应用或 Docker 容器。3. 从零部署三步跑通本地调试环境含微信配置3.1 后端.NET Core 项目启动与数据库初始化确保已安装 .NET SDK 6.0dotnet --version输出6.0.400或更高。进入WeChatMiniProgram.API目录# 1. 还原 NuGet 包关键必须用 .NET 6 SDK dotnet restore # 2. 执行数据库迁移首次运行会创建表结构 dotnet ef database update --project ../WeChatMiniProgram.Data/WeChatMiniProgram.Data.csproj # 3. 启动 API监听 https://localhost:5001 dotnet run逻辑说明dotnet ef database update会读取WeChatMiniProgram.Data项目的ApplicationDbContext执行所有未应用的 Migration 脚本。若报错The specified framework version 6.0 could not be found请确认全局安装的是 .NET 6 SDK非仅 Runtime。参数说明--project参数指定 DbContext 所在项目路径避免因解决方案多项目导致上下文识别错误若需指定连接字符串修改appsettings.Development.json中的ConnectionStrings: { DefaultConnection: Server.;DatabaseWeChatMall;Trusted_Connectiontrue; }支持 SQL Server、PostgreSQL需更换 EF Core Provider。3.2 前端微信小程序开发者工具配置要点将WeChatMiniProgram.Client目录拖入微信开发者工具必须使用稳定版 v1.05.2305161不支持 Nightly 版AppID 配置在project.config.json中修改appid: wx1234567890abcdef为你自己的小程序 AppID域名配置在app.js第 12 行const API_BASE_URL https://localhost:5001/api;——开发阶段允许 localhost但必须在「开发管理 开发者工具」中勾选「不校验合法域名」HTTPS 代理因 API 使用 HTTPS需在开发者工具设置中开启「启用 HTTPS 代理」否则wx.request报request:fail ssl hand shake error登录调试首次打开index页面会自动调用wx.login()获取code然后 POST 到/api/auth/login返回access_token存入wx.setStorageSync。注意小程序端所有接口请求头自动携带Authorization: Bearer token由utils/request.js统一封装无需每个页面手动设置。3.3 微信侧关键配置三个地方必须改否则支付/登录全挂微信公众平台 → 开发管理 → 开发设置服务器域名添加https://your-domain.com生产环境或留空开发环境request合法域名添加你的 API 域名如https://api.yourmall.comJS接口安全域名必须填写小程序前端所在域名如https://mp.weixin.qq.com不行要填你备案的域名。微信支付商户平台 → 账户中心 → API安全设置 APIv3 密钥32位随机字符串上传 API 证书apiclient_cert.p12注意此证书需从商户平台下载后用密码解压再转为.pem格式供 .NET Core 使用在appsettings.json中配置WeChatPay: { MchId: 1900000109, AppId: wx8888888888888888, ApiV3Key: your-32-char-api-v3-key-here, CertPath: ./cert/apiclient_cert.pem, CertPassword: your-p12-password }小程序管理后台 → 功能推广 → 微信支付必须提交「小程序支付」类目如“电商-综合电商”否则wx.requestPayment调用直接失败错误码10002审核通过前可用测试号调通流程但无法真实支付。4. 避坑微信小程序 .NET Core 商城最常翻车的 5 个点4.1 现象小程序调用/api/auth/login返回 401但code确认有效原因WeChatSessionManager中GetSessionKeyAsync方法调用微信sns/jscode2session接口时未正确 urlencodeappid和secret导致微信返回{errcode:40013,errmsg:invalid appid}但项目日志未捕获该响应直接抛出 401。解决检查WeChatSessionManager.cs第 47 行确保HttpClient.PostAsync的 URL 构造使用Uri.EscapeDataString()var url $https://api.weixin.qq.com/sns/jscode2session? $appid{Uri.EscapeDataString(_options.AppId)} $secret{Uri.EscapeDataString(_options.AppSecret)} $js_code{code}grant_typeauthorization_code;4.2 现象支付成功后订单状态仍是 “待支付”库存未扣减原因微信支付回调地址/api/order/paynotify被配置为 HTTP非 HTTPS微信服务器拒绝推送或回调接口未加[HttpPost]特性导致路由匹配失败返回 404。解决确保Startup.cs中app.UseHttpsRedirection()已启用PayNotifyController.cs必须有[Route(api/[controller])]和[HttpPost(paynotify)]在appsettings.json中配置WeChatPay:NotifyUrl: https://your-domain.com/api/order/paynotify不能带 query string。4.3 现象商品列表页下拉刷新后新数据覆盖旧数据而非追加原因小程序端onPullDownRefresh中调用getProducts时未重置this.data.products []导致setData({ products: [...old, ...new] })逻辑缺失。解决修改pages/index/index.js的onPullDownRefreshonPullDownRefresh() { this.setData({ products: [] }); // 关键清空旧数据 this.getProducts().then(() { wx.stopPullDownRefresh(); }); }4.4 现象后台管理系统登录后部分菜单不显示AdminMenuController返回空数组原因AdminController中GetMenus方法使用User.IsInRole(Admin)判断但 JWT Token 中未包含roleclaimEF Core 查询时Where(x x.Role Admin)永远为 false。解决在AuthController.Login方法中生成 Token 时显式添加 rolevar claims new ListClaim { new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(ClaimTypes.Role, Admin) // 必须加这行 };4.5 现象Docker 部署后dotnet ef database update报错Unable to connect to any of the specified MySQL hosts原因Dockerfile中ENTRYPOINT [dotnet, WeChatMiniProgram.API.dll]启动时EF Core Migration 尚未执行且容器内 DNS 解析不到宿主机数据库。解决方案一推荐在Dockerfile中添加RUN dotnet ef database update --project ../WeChatMiniProgram.Data/方案二使用docker-compose.yml定义depends_onhealthcheck确保数据库就绪后再启动 API方案三将 Migration 脚本导出为 SQL在容器启动脚本中mysql -h db -u root -p$PASS migration.sql。5. 进阶技巧如何让库存扣减真正“不超卖”Redis EF Core 双校验实战微信小程序商城最怕的不是界面卡顿而是“100 件库存1000 人同时下单结果卖出 120 件”。这份源码的IInventoryService.DecreaseStockAsync方法实现了工业级库存控制不是简单UPDATE SET stock stock - 1。5.1 双校验流程为什么只靠数据库锁不够高并发下单纯依赖数据库行锁如SELECT ... FOR UPDATE会导致锁等待时间长请求堆积若事务未及时提交锁持有时间不可控Redis 缓存穿透风险缓存失效瞬间大量请求打穿 DB。因此本项目采用“Redis 预减 DB 终审”两段式校验Redis 预减用DECRBY sku:1001:stock 1原子操作若返回值 ≥ 0进入下一步否则直接返回“库存不足”DB 终审查 DB 当前库存若stock 1则执行UPDATE ... SET stock stock - 1 WHERE id 1001 AND stock 1利用AND stock 1作为乐观锁条件Redis 回补若 DB 更新失败影响行数为 0则INCRBY sku:1001:stock 1回退预减量。5.2 核心代码InventoryService.cs关键片段public async Taskbool DecreaseStockAsync(int skuId, int quantity 1) { var redisKey $sku:{skuId}:stock; // Step 1: Redis 预减原子操作 var remaining await _redisDatabase.DecrementAsync(redisKey, quantity); if (remaining 0) { // 预减失败立即回退 await _redisDatabase.IncrementAsync(redisKey, quantity); return false; } // Step 2: DB 终审乐观锁 using var transaction await _context.Database.BeginTransactionAsync(); try { var sku await _context.Skus .FirstOrDefaultAsync(x x.Id skuId x.Stock quantity); if (sku null) { // DB 库存不足回退 Redis await _redisDatabase.IncrementAsync(redisKey, quantity); await transaction.RollbackAsync(); return false; } sku.Stock - quantity; _context.Skus.Update(sku); await _context.SaveChangesAsync(); await transaction.CommitAsync(); return true; } catch { await transaction.RollbackAsync(); throw; } }参数说明quantity默认为 1支持批量扣减如套餐商品_redisDatabase使用StackExchange.Redis连接字符串从appsettings.json读取_context是 EF Core DbContext。5.3 Redis 缓存策略不是所有数据都该缓存数据类型是否缓存TTL秒更新时机原因SKU 库存✅ 强制缓存3005分钟每次扣减/补货后SET sku:1001:stock value高频读低频写容忍短暂不一致商品详情✅ 缓存36001小时后台编辑商品后DEL product:1001减轻 DB 压力详情页加载快用户订单列表❌ 不缓存——每个用户数据隔离缓存 key 爆炸且需实时性微信 session_key✅ 缓存72002小时code2Session成功后SET session:xxx key微信官方有效期为 2 小时精准匹配5.4 压测验证用 wrk 模拟 1000 并发下单在 Linux 服务器上安装wrk执行以下命令压测库存接口# 模拟 1000 并发持续 30 秒POST 到扣减接口 wrk -t12 -c1000 -d30s -H Authorization: Bearer your-jwt-token \ -s post-stock.lua http://localhost:5001/api/inventory/decreasestock # post-stock.lua 内容 wrk.method POST wrk.body {skuId:1001,quantity:1} wrk.headers[Content-Type] application/json预期结果成功率 100%无超卖99% 请求延迟 200msRedisINFO显示instantaneous_ops_per_sec峰值 5000。从那以后我每次上线新商城都会在预发环境跑一遍这个wrk脚本哪怕只是改了个按钮颜色——因为库存逻辑一旦出错就是真金白银的损失。现在我的习惯是改完任何和InventoryService相关的代码第一件事就是删掉本地 Redis 所有sku:*key再跑三轮压测。希望帮到你。本文还有配套的精品资源点击获取
返回列表