
简介这是一套基于C#/.NET Core构建的原生微信小程序商城项目面向具备C#基础、希望快速搭建商用级多店铺电商平台的中高级开发者。项目覆盖商品管理、订单处理、会员三级分销、物流配送、优惠券、积分、促销及插件管理等核心业务模块后端采用NetCore架构前端使用原生微信小程序前后端分离便于二次开发和功能扩展。压缩包为ZIP格式共1962个文件大小约10.33MB。文件以789个C#源文件为主体承担后端业务逻辑218个JS文件与WXML/WXSS/CSS文件构成小程序前端页面另含SQL数据库脚本、JSON/XML配置、解决方案与工程文件等结构层次清晰便于按模块针对性学习。当前已有1278人学习。从内容预览可见包含订单、用户、商品等核心业务控制器以及插件管理、导出管理等扩展模块完整度较高。读者可深入钻研商城核心业务的实现思路获得一套可用于项目参考和功能扩展的商用级源码体系。1. 基于C#的小程序商城原生微信小程序 .NET Core到底能省多少事在微信小程序后端选型这件事上我见过太多团队从 Node.js 起步写到一半发现要重新实现权限、事务和定时任务又回头翻 C# 的老底。这份源码恰好戳中这个痛点原生微信小程序做前端.NET Core做 Web APIC# 团队手里沉淀的那套分层和基建能力直接平移过来不需要跨语言维护两套技术栈。项目本身是一个完整的小程序商城涵盖商品浏览、购物车、下单、支付回调、订单管理和用户登录闭环适合有一两年 C# 基础、想快速搭一套可上线商城的中小团队也适合拿来做毕业设计或内部脚手架参考。接下来我从源码结构、启动复现、登录支付闭环三个维度拆开讲最后补上我实际跑项目时踩过的一串坑。2. 源码结构拆解.NET Core 分层架构与小程序的目录组织拿到压缩包后别急着双击运行先把目录铺开看一遍。这套源码的价值不在于单个文件写得多漂亮而在于前后端的组织方式能直接照抄进下一个项目。我拆过的商城源码不下十套凡是能顺利跑起来的后端基本都是Controller → Service → Repository三层小程序端则严格遵守页面、工具、组件的目录切分。这套源码也是这个套路。2.1 后端分层Controller 只做参数翻译业务规则全在 Service 里.NET Core后端的入口是Startup.cs或Program.cs里面注册了 DbContext、JWT 认证和依赖注入容器。先看一个典型的商品查询 Controller[Route(api/[controller])] [ApiController] public class ProductController : ControllerBase { private readonly IProductService _productService; public ProductController(IProductService productService) { _productService productService; } [HttpGet(list)] public async TaskActionResultApiResult GetList(int pageIndex 1, int pageSize 20) { var data await _productService.GetPagedListAsync(pageIndex, pageSize); return Ok(new ApiResult { Code 0, Data data }); } }逻辑说明Controller层只做两件事一是从路由和 QueryString 接收参数二是把 Service 层返回的数据包装成统一响应格式。注意这里没有直接new一个 Service 实例而是通过构造函数注入IProductService这是 .NET Core 依赖注入的标准写法目的是让上层不关心下层对象的生命周期。参数说明pageIndex和pageSize是两个分页参数默认值分别是 1 和 20。ApiResult是全局统一响应模型Code 为 0 表示成功非 0 表示业务异常小程序端拿到后会根据 Code 判断是弹错误提示还是正常渲染。整套响应格式保持稳定是前后端联调省时间的关键。真正复杂的逻辑在 Service 层里比如下单时要同时扣库存、生成订单快照、记录流水这几个操作必须落在同一个事务里public async TaskOrderResult CreateOrderAsync(CreateOrderDto dto) { using var transaction _dbContext.Database.BeginTransaction(); try { var product await _dbContext.Products.FindAsync(dto.ProductId); if (product.Stock dto.Quantity) { return new OrderResult { Success false, Message 库存不足 }; } product.Stock - dto.Quantity; var order new Order { OrderNo GenerateOrderNo(), UserId dto.UserId, TotalAmount product.Price * dto.Quantity, Status OrderStatus.Unpaid }; _dbContext.Orders.Add(order); await _dbContext.SaveChangesAsync(); await transaction.CommitAsync(); return new OrderResult { Success true, OrderId order.Id }; } catch { await transaction.RollbackAsync(); throw; } }逻辑说明先开启数据库事务再检查库存、扣减库存、创建订单最后统一提交。一旦中间任何一步抛异常RollbackAsync会把库存和订单数据全部回滚避免出现扣了库存没生成订单这种脏数据。using关键字保证事务对象在方法结束时一定被释放。参数说明CreateOrderDto是前端传过来的下单参数体包含商品 ID、购买数量和用户标识。OrderStatus.Unpaid是订单状态枚举里的初始值后续支付回调成功后会流转为Paid。GenerateOrderNo是一个私有方法我一般用时间戳 用户ID后四位 随机数拼单号避免并发重复。2.2 小程序端目录pages 按业务切utils 封请求components 收通用件小程序端没有采用uni-app之类的跨端框架而是用原生WXML WXSS JS写的。原生写的优点在于不用等框架适配微信基础库的更新微信发新能力时这边能第一时间用上比如后面要讲的手机号快速验证组件原生代码里接入就比跨端框架顺手得多。pages目录下按业务模块分文件夹比如pages/index放首页、pages/cart放购物车、pages/order放订单列表每个文件夹下是标准的四件套——.js、.json、.wxml、.wxss。utils目录里封了请求库和工具函数components目录放通用 UI 组件比如商品卡片和数量选择器。utils/request.js是整个小程序端最值得看的文件所有后端接口都从这里走const BASE_URL https://your-api.example.com; function request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: ${BASE_URL}${path}, method: method, data: data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success(res) { if (res.data.code 401) { wx.navigateTo({ url: /pages/login/login }); reject(new Error(登录已过期)); return; } if (res.data.code ! 0) { wx.showToast({ title: res.data.msg, icon: none }); reject(new Error(res.data.msg)); return; } resolve(res.data.data); }, fail(err) { reject(err); } }); }); } module.exports { request };逻辑说明所有请求自动携带本地缓存的token作为Authorization请求头后端 JWT 认证中间件会校验这个值。收到响应后先检查业务码0直接向下传递数据非0弹 Toast 并拒绝 Promise。这里把401单拎出来处理是因为 token 过期是小程序商城最高频的接口错误。参数说明BASE_URL是后端接口的基地址本地联调时改成http://127.0.0.1:5000上线前必须换成 HTTPS 域名微信要求所有 request 请求必须走合法域名。path传的是相对路径比如/api/product/list这样后端接口只要保持前缀一致切换环境时只需要改BASE_URL一个常量。2.3 数据流串讲一次商品列表请求在两端各经历什么从用户下拉刷新到看到商品列表完整的链路是小程序pages/index/index.js里触发onPullDownRefresh调用request(/api/product/list, GET, { pageIndex: 1 })后端ProductController接收参数后交给ProductService查数据库返回 JSON 列表小程序拿到数据后setData更新页面。理解这条链路后后面不管是排查超时还是定位数据对不上都能顺着这条线逐段检查。3. 启动复现从改配置到主链路联调一小时内跑起来拆源码最怕的就是第一步跑不起来后面全白搭。这套商城的启动流程不算复杂但要按顺序做先起后端再导小程序最后联调。我按照实际操作顺序一步步写。3.1 先启动后端dotnet restore 与 appsettings.json 里的两处必改项后端是标准的 .NET Core Web API 项目先确认本机装了 .NET SDK然后进到项目目录执行cd src/Shop.Api dotnet restore dotnet run逻辑说明dotnet restore会根据.csproj文件还原所有 NuGet 依赖包网络正常的情况下几分钟能完成。dotnet run会编译并启动 Web API默认监听http://localhost:5000。restore成功后先别急着跑打开appsettings.json重点看两个地方{ ConnectionStrings: { Default: Serverlocalhost;DatabaseShopDb;User Idsa;Passwordyour_password;TrustServerCertificateTrue; }, Jwt: { Issuer: Shop.Api, Audience: Shop.MiniProgram, SecretKey: replace_with_a_long_random_string_at_least_32_chars } }逻辑说明Default是 SQL Server 的连接字符串SecretKey是 JWT 签名密钥。这两处是启动后最容易报错的地方数据库连不上会直接白屏密钥太短会在生成 token 时抛异常。参数说明User Idsa;Passwordyour_password要替换成本地 SQL Server 的实际账号密码。如果用的是LocalDB或者MySQL连接字符串写法不同需要同时检查Shop.Api.csproj里引用的数据库驱动包是否匹配。SecretKey长度至少 32 个字符我一般用 GUID 拼接一段随机字符串避免上线后被猜解。数据库结构不需要手动建表项目里通常自带Database目录或 SQL 脚本。如果是用 Entity Framework Core启动时会自动执行迁移如果是纯 SQL 脚本需要手动在 SQL Server Management Studio 里执行一遍初始化脚本。3.2 再导小程序开发者工具里的 AppID 与本地调试关闭校验后端起来后打开微信开发者工具选择“导入项目”定位到源码里的MiniProgram目录。导入时有两点需要处理一是AppID有自己注册的小程序就用真实 AppID没有的话可以用测试号只是部分能力会被限制二是本地调试阶段需要勾选“不校验合法域名”选项否则请求会被拦截。开发者工具里还需要改utils/request.js中的BASE_URL。在开发者工具中直接请求本机接口会遇到一个常见的坑电脑上跑微信开发者工具手机真机预览时访问不到localhost因为真机访问的是127.0.0.1自己本机不是电脑。这时要把BASE_URL改成电脑的局域网 IP比如http://192.168.1.101:5000并且后端要监听0.0.0.0而不是localhostdotnet run --urls http://0.0.0.0:5000逻辑说明--urls参数让 Web API 监听所有网络接口这样同一局域网内的手机才能访问到电脑上的后端服务。开发者工具中打开“真机调试”后手机会提供一个二维码用微信扫码进入预览模式。参数说明192.168.1.101是举例实际用ipconfig查看本机 IPv4 地址。电脑防火墙如果开了需要放行 5000 端口的入站规则否则真机上请求超时这是新手最容易忽略的一步。3.3 联调主链路从登录态到下单抓包看哪一步断了启动完成后按照核心路径走一遍小程序首页加载商品列表 → 点击商品进入详情 → 加入购物车 → 提交订单 → 模拟支付。这个流程能走通说明前后端的数据流、鉴权、参数传递都正常。如果哪一步断了优先打开开发者工具的Network面板看接口返回。常见问题集中在两类一是401查token是否过期检查请求头里Authorization有没有生成二是500看后端控制台有没有抛异常堆栈基本都是数据库连接或字段为空导致的。后端日志里会输出请求路径、状态码和执行时长.NET Core默认控制台日志就能满足排查需求。我习惯在ProductService这种核心方法里加一行LogInformation记录入参和返回避免靠猜定位问题。4. 登录与支付闭环code2Session、手机号验证组件与下单状态机商城类小程序的生命线就是登录和支付这两块源码里的实现方式决定其是否值得复用。这套源码的登录走的是微信wx.login换取 openid 的标准流程支付则是统一下单加回调验签的完整闭环。4.1 登录换取 openid后端 code2Session 接口与自定义登录态小程序端调wx.login拿到临时code把code传给后端后端拿code请求微信接口换取openid和session_key。源码后端接口的核心逻辑如下[HttpPost(login)] public async TaskActionResultApiResult Login([FromBody] LoginRequest request) { var url $https://api.weixin.qq.com/sns/jscode2session?appid{_appId}secret{_appSecret}js_code{request.Code}grant_typeauthorization_code; var httpClient _httpClientFactory.CreateClient(); var response await httpClient.GetAsync(url); var result await response.Content.ReadAsStringAsync(); var session JsonSerializer.DeserializeWeChatSession(result); if (session.OpenId null) { return Ok(new ApiResult { Code 400, Msg session.ErrMsg }); } var user await _userService.GetOrCreateUserAsync(session.OpenId); var token _jwtService.GenerateToken(user.Id, user.OpenId); return Ok(new ApiResult { Code 0, Data new { Token token } }); }逻辑说明后端收到code后拼出jscode2session的请求地址用HttpClient请求微信服务器。微信返回openid和session_key其中openid是用户在该小程序下的唯一标识session_key用于解密手机号等敏感信息后端不能把它暴露给前端。拿到openid后在数据库里查或建用户记录最后签发自己的 JWT token 返回给小程序。参数说明appid和secret是微信公众平台后台拿到的必须配置在后端环境变量或配置文件里不能写进小程序代码。code是一次性的5 分钟有效且只能用一次缓存或重放都会导致登录失败。有个细节处理得不好会埋下隐患用户登录后session_key通常需要在数据库中存一份以便后面解密手机号时用。这套源码如果没存需要你在UserService.GetOrCreateUserAsync时把session_key一并写入用户表否则手机号验证功能会解密失败。4.2 手机号快速验证组件的使用要点小程序端获取用户手机号目前推荐用的是button的open-typegetPhoneNumber配合手机号快速验证组件老旧的getPhoneNumber直接调 API 的方式已经不适用。前端代码结构大体如下button open-typegetPhoneNumber bindgetphonenumberhandleGetPhoneNumber 获取手机号 /buttonhandleGetPhoneNumber(event) { const { code } event.detail; if (!code) { wx.showToast({ title: 未授权, icon: none }); return; } request(/api/user/phone, POST, { code }) .then(res { wx.showToast({ title: 绑定成功, icon: success }); }); }逻辑说明点击按钮后微信弹窗询问用户是否允许获取手机号。允许的话event.detail里会返回一个动态code把这个code发给后端后端再用code换取真实的手机号。这个设计比直接把手机号传给前端安全手机号只在服务端解密。注意前端拿到的只是一个code不是手机号明文。后端拿到code后需要再次调用微信接口phonenumber.getPhoneNumber用access_token换取手机号需要的参数是之前登录时保存的session_key。这里的access_token是接口调用凭证和用户登录的token是两回事需要后端维护一个定时刷新的appid secret → access_token缓存。4.3 微信支付闭环统一下单、回调验签、订单状态流转支付的核心是后端先调微信下单接口拿到prepay_id后返回给小程序小程序再调wx.requestPayment唤起支付。小程序端代码wx.requestPayment({ timeStamp: res.timeStamp, nonceStr: res.nonceStr, package: res.package, signType: RSA2, paySign: res.paySign, success() { wx.showToast({ title: 支付成功, icon: success }); }, fail() { wx.showToast({ title: 支付失败, icon: none }); } });逻辑说明wx.requestPayment的所有参数都来自后端预下单接口。package是prepay_idxxx这种固定格式paySign是把timeStamp、nonceStr、package等信息按微信规定的规则拼接后做 RSA 签名得到的字符串。注意微信支付现在的签名算法用RSA2不是老版本的MD5源码里如果写的是MD5需要升级加解密库。后端在预下单前必须先查订单状态只有状态为Unpaid的订单才能发起支付已经支付或已关闭的订单要直接拒绝。支付成功后微信会异步调用回调接口通知结果[HttpPost(pay/callback)] public async TaskIActionResult PayCallback() { var body await new StreamReader(Request.Body).ReadToEndAsync(); var success _wechatPayService.VerifyCallback(body, Request.Headers[Wechatpay-Signature]); if (!success) { return BadRequest(验签失败); } var orderNo _wechatPayService.ParseOrderNoFromCallback(body); await _orderService.MarkOrderPaidAsync(orderNo); return Ok(new { code SUCCESS, message 成功 }); }逻辑说明微信支付成功后会把订单结果 POST 到回调地址后端第一件事是验签用微信平台证书验证请求头的签名防止伪造通知。验签通过后才更新订单状态为Paid然后返回微信规定的SUCCESS响应。如果没有正确返回微信会按策略重复通知 24 小时。一个需要特别注意的地方回调地址必须是公网 HTTPS 地址本地开发时需要借助内网穿透工具才能收到回调。数据一致性上MarkOrderPaidAsync里要把订单状态、支付流水写入同一个数据库事务中避免订单状态更新了流水却丢了。5. 避坑指南上线前必须盯紧的五个细节这套源码整体质量不错但真跑起来仍有几个地方容易翻车。我把自己实际跑过的坑从现象到原因写下来每条都对应一次真实的排查过程。5.1 现象真机预览白屏开发者工具一切正常开发者工具里页面正常加载一扫码真机预览就白屏控制台报request:fail。检查后发现是request的 URL 还是http://localhost:5000真机拿localhost去连指向的是手机自己自然连接不上。原因微信开发者工具的“不校验合法域名”只作用于工具模拟器真机预览时请求依然受合法域名规则限制同时localhost这个地址对真机没有意义。解决把BASE_URL从localhost改成电脑的局域网 IP并让后端监听0.0.0.0。上线时换成备案过的 HTTPS 域名并在微信公众平台配置 request 合法域名。5.2 现象支付回调总是不触发本地联调时订单一直停在未支付状态后端看不到微信的支付通知请求。查了微信商户平台和公众平台回调地址填的是http://192.168.1.101:5000/api/pay/callback。原因微信支付回调只允许 HTTPS 公网地址本地局域网地址根本收不到通知即便内网穿透成公网地址回调验签时也容易因为回调请求头缺参数而验签失败。解决用内网穿透工具把本机的 5000 端口映射成一个 HTTPS 公网地址在微信商户平台的“支付回调链接”里填这个映射地址。联调通过后再改成正式的线上回调地址。5.3 现象iPhone 顶部的商品搜索框被刘海屏遮住小程序在 iPhone X 以上机型真机预览时首页顶部搜索框与系统状态栏重叠页面整体被向上顶出一块。Android 机器正常iOS 显示异常。原因小程序默认页面顶部从屏幕最顶端开始布局而 iPhone 有刘海屏安全区需要手动适配状态栏高度。解决在页面的.js里获取系统信息用wx.getWindowInfo()或wx.getSystemInfoSync()拿到statusBarHeight动态给顶部容器设置padding-topconst info wx.getWindowInfo(); Page({ data: { statusBarHeight: info.statusBarHeight } });view classsearch-bar stylepadding-top: {{statusBarHeight}}px;逻辑说明statusBarHeight是状态栏的实际高度不同机型值不同动态绑定到样式的padding-top后页面内容自然避开了状态栏区域。这套源码里如果首页没有做这个适配自己动手补上否则 iPhone 用户的体验会比较糟糕。5.4 现象数据库连接池打满接口频繁超时压测时发现并发稍微上来接口响应时间从几十毫秒飙升到几秒后来直接报数据库连接数耗尽。原因DbContext没有及时释放或者 Service 层里手动new了DbContext而不是通过依赖注入获取。每个连接占用时间过长连接池被耗尽。解决确保DbContext的生命周期由依赖注入容器管理默认注册方式AddDbContext是作用域内的一个请求一个实例请求结束自动释放。检查代码里有没有直接new数据库上下文把它改成构造函数注入private readonly ShopDbContext _dbContext; public OrderService(ShopDbContext dbContext) { _dbContext dbContext; }逻辑说明ShopDbContext通过构造函数注入它的生命周期由 .NET Core 的 DI 容器统一管理。请求进来创建请求结束释放连接归还数据库连接池从而避免连接泄露。5.5 现象商品图片裂图后台管理上传的图片小程序端显示不出来后台可以正常上传图片但小程序商城里图片全部裂开开发者工具里提示图片请求失败。原因后台图片上传后存储的是相对路径或本机绝对路径小程序端无法访问服务器本地磁盘文件而且腾讯系小程序对图片域名也有安全限制。解决产品图片应使用 CDN 或 OSS 存储在小程序端把图片路径拼接成合法的 HTTPS URL。如果只是本地演示把图片放到小程序的static目录下作为静态资源引用不做网络加载。6. 后端加固三板斧内存缓存、接口限流与订单幂等项目跑通之后真正决定它能扛多大流量的是后端的几个非功能性设计。这套源码的基础够用但距离上生产环境还有一段距离多花半小时补上这三个加固点会更稳妥。6.1 热点数据放 MemoryCache把重复数据库查询降下来商品分类和首页轮播图这类数据基本不常变却每次打开首页都要查库。加一层内存缓存效果最为明显public async TaskListCategory GetCategoriesAsync() { var cacheKey categories:all; var cached await _cache.GetAsyncListCategory(cacheKey); if (cached ! null) { return cached; } var categories await _dbContext.Categories.OrderBy(c c.Sort).ToListAsync(); await _cache.SetAsync(cacheKey, categories, TimeSpan.FromMinutes(30)); return categories; }逻辑说明先查缓存缓存命中直接返回不碰数据库。缓存时间设为 30 分钟数据变更时可以先清缓存保证一致性。这是 C# 团队做商城最省成本的读多写少优化方案。6.2 限流中间件提前挡掉刷接口的请求商城类接口最容易被脚本刷特别是登录、发送验证码和商品详情。加一个简单的 IP 限流中间件单 IP 每分钟超过 60 次请求直接拒绝成本低但效果显著。6.3 订单幂等同一笔下单请求不产生第二张订单前端网络抖动时用户点击“提交订单”可能发送两次请求如果没有幂等处理数据库里会出现两张相同订单。常见的做法是前端生成唯一请求号后端在Order表里对请求号加唯一约束重复请求直接返回已存在的订单。补上这三个点之后这套商城的后端才算具备基本的上线能力。我每次把这套源码给同事做新项目的架子时都会让他们强制走一遍完整的支付流程再交付自己也会检查DbContext注册和订单唯一约束这两个容易翻车的地方。这套资源对想用 C# 技术栈快速落地小程序商城的团队来说骨架是现成的接上业务规则就能跑希望帮到你。本文还有配套的精品资源点击获取