尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

ASP.NET MVC C# 优惠券领取微信小程序源码:库存并发与防重复领实战

发布时间:2026/9/26 4:54:56

资讯中心
01
ARTICLE

ASP.NET MVC C# 优惠券领取微信小程序源码:库存并发与防重复领实战

ASP.NET MVC C# 优惠券领取微信小程序源码:库存并发与防重复领实战
简介这是一套面向微信小程序开发者与淘宝客业务学习者的完整源码包基于C#.NET MVC与微信小程序前后端分离架构实现调用阿里妈妈淘宝客API进行优惠券自助搜索与领取。后台采用ASP.NET MVC框架已内置内容管理、会员、订单、微信及WAP等模块便于在此基础上二次开发成完整站点。资源共76个文件以js、json、wxss、wxml等小程序页面与逻辑文件为主另含数据库脚本、配置说明及后台压缩包整体约22.8MB前台页面与TBKAdmin后台源码分开放置结构清晰。开发环境为Visual Studio 2010搭配SQLServer2008与.NET 4.0默认管理员账号admin/admin888数据库连接字符串在web.config中修改DataBase文件夹提供建库脚本。目前已有1784人学习下载适合想研究小程序对接淘宝客API、或需要一套可运行后台框架进行二次开发的中级开发者参考。1. 2019 版 ASP.NET MVC 优惠券领取小程序一套老源码为什么还值得翻出来读2019 年前后上线的微信小程序里优惠券领取是最典型的一类业务用户点开小程序、授权登录、看到券列表、点“领取”、后台扣库存、写领取记录、回传结果。这套流程看着简单但真正落到代码里涉及小程序端请求封装、ASP.NET MVC 的控制器路由、C# 侧的库存并发控制、以及数据库事务边界。标题里这套“ASP.NET MVC C# 优惠券领取微信小程序源码源代码2019完整版”本质就是一份把上述链路全部串起来的后端 小程序端参考实现。它适合谁一是手里有老项目要维护、需要对照 MVC 三层架构补业务逻辑的 C# 开发者二是想拿一个真实业务练手、把微信小程序请求打到自建后端的新手三是需要快速搭一套领券 Demo 去验证库存扣减方案的人。不适合指望直接上线生产的人——2019 年的依赖版本、鉴权写法、并发处理都需要按现在的标准重写。下面按“先看懂结构、再跑通链路、最后处理并发和坑”的顺序拆开讲。2. 先看清这套 MVC 源码的分层控制器、服务、数据访问各管什么拿到一份 2019 年的 ASP.NET MVC 源码第一件事不是急着 F5 运行而是把目录结构和分层关系理清楚。这套优惠券项目的典型结构是Controllers放接口入口Services放领券业务逻辑Models放实体和视图模型DAL或Repositories放数据访问小程序端单独一个目录放pages和utils。分层清不清楚直接决定你后面改库存逻辑时会不会牵一发动全身。2.1 MVC 三层架构在这套领券项目里的实际落点MVC 设计模式落到优惠券业务上Controller 只做三件事接参数、调 Service、包返回。真正的“能不能领、库存够不够、是不是重复领”全部应该在 Service 层判断。我见过太多老项目把库存判断写在 Controller 里结果小程序端一改请求参数就绕过了校验。正确的落点是CouponController暴露/api/coupon/list、/api/coupon/receive两个动作CouponServiceGetAvailableCoupons()、ReceiveCoupon(userId, couponId)CouponRepositoryDecreaseStock(couponId)、InsertReceiveRecord(...)这样分的好处是库存扣减和记录写入可以放进同一个事务Controller 不掺和业务规则。C# 里判断字符串、集合操作这些基础能力在这里会频繁用到比如用ListCoupon承载券列表、用string.IsNullOrEmpty校验 openid都是绕不开的基本功。2.2 小程序端请求是怎么打到 MVC 控制器的小程序端不认 MVC 的视图只认 JSON 接口所以这套源码里 Controller 返回的应该是JsonResult而不是ViewResult。小程序用wx.request发 POST后端用[HttpPost]接收。一个最小可跑通的请求封装长这样// utils/request.js —— 小程序端统一请求封装 const BASE_URL https://your-domain.com; // 换成你自己的后端域名 function post(url, data) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: POST, data: data, header: { content-type: application/json }, success: (res) { // 后端约定 code0 为成功其余为业务失败 if (res.data res.data.code 0) { resolve(res.data.data); } else { reject(res.data || { msg: 未知错误 }); } }, fail: reject }); }); } module.exports { post };逻辑说明把wx.request包成 Promise是为了在页面里用async/await写领券流程避免回调地狱。参数说明BASE_URL必须换成已在小程序后台配置过 request 合法域名的地址否则真机调试直接报域名不合法header里的content-type要和后端[HttpPost]的绑定方式对上用application/json时后端参数要加[FromBody]这是新手最常翻车的地方。2.3 后端接收领券请求的控制器写法对应上面的请求MVC 侧的控制器动作应该这样写// Controllers/CouponController.cs public class CouponController : Controller { private readonly CouponService _service new CouponService(); [HttpPost] public JsonResult Receive([FromBody] ReceiveRequest req) { // req 里至少要有 userId 和 couponId if (req null || string.IsNullOrEmpty(req.UserId) || req.CouponId 0) { return Json(new { code 1, msg 参数不合法 }); } var result _service.ReceiveCoupon(req.UserId, req.CouponId); return Json(new { code result.Success ? 0 : 2, msg result.Message }); } } public class ReceiveRequest { public string UserId { get; set; } public int CouponId { get; set; } }逻辑说明[FromBody]是配合application/json的关键漏了它req会是 null表现为“参数永远不合法”。参数说明UserId实际项目里应该由后端从登录态里取而不是前端传这里为了讲清链路先显式传CouponId用 int是因为券 ID 通常自增。返回统一用code字段区分成功和业务失败小程序端才能统一处理。3. 把领券链路跑通库存扣减、防重复领和事务边界链路能跑通只是及格线优惠券业务真正的难点在“高并发下不超发、同一用户不重复领”。2019 年的老源码很多用“先查再扣”的写法单机测试没问题一上量就超发。这一章讲清楚正确的扣减姿势和事务边界也是这套源码最值得你重写的地方。3.1 库存扣减为什么不能“先查再扣”“先查库存够不够再更新库存减一”这个写法在并发下必然超发两个请求同时查到库存为 1都判断够都去减结果库存变成 -1。正确做法是把判断和扣减合并成一条带条件的 SQL让数据库来保证原子性-- 只有库存大于 0 时才扣减返回受影响行数 UPDATE Coupon SET Stock Stock - 1 WHERE Id CouponId AND Stock 0;逻辑说明这条语句的WHERE Stock 0是并发安全的核心数据库执行时会对该行加锁两个请求串行执行第二个请求受影响行数为 0直接判定领取失败。参数说明CouponId是券 ID执行后要检查ExecuteNonQuery的返回值等于 1 才算扣减成功等于 0 说明库存已空。这一步是整套源码里最该改对的地方。3.2 防重复领唯一索引比代码判断更可靠同一用户重复领同一张券靠 Service 层“先查有没有领过”同样有并发漏洞。最稳的做法是在领取记录表上加唯一索引让数据库兜底-- 领取记录表加联合唯一索引防止同一用户重复领同一张券 CREATE UNIQUE INDEX UX_User_Coupon ON CouponReceive (UserId, CouponId);逻辑说明有了唯一索引即使两个请求同时通过了代码层的检查插入时也只有一个能成功另一个抛唯一约束冲突捕获后返回“已领取”。参数说明UserId和CouponId的组合必须唯一如果业务允许同一用户领多张同款券就要改成(UserId, CouponId, BatchNo)这种带批次的组合。C# 侧捕获SqlException判断错误号 2601/2627 即可识别唯一冲突。3.3 扣库存和写记录必须在同一个事务里扣了库存但写记录失败用户没领到券库存却少了写了记录但扣库存失败用户领到了券但库存没减。这两种脏数据都源于没加事务。正确写法// Services/CouponService.cs public ReceiveResult ReceiveCoupon(string userId, int couponId) { using (var conn new SqlConnection(ConnStr)) { conn.Open(); using (var tran conn.BeginTransaction()) { try { // 第一步原子扣减库存 var affected DecreaseStock(conn, tran, couponId); if (affected 0) { tran.Rollback(); return new ReceiveResult { Success false, Message 库存不足 }; } // 第二步写领取记录唯一索引兜底防重复 InsertReceiveRecord(conn, tran, userId, couponId); tran.Commit(); return new ReceiveResult { Success true, Message 领取成功 }; } catch (SqlException ex) when (ex.Number 2601 || ex.Number 2627) { tran.Rollback(); return new ReceiveResult { Success false, Message 您已领取过 }; } } } }逻辑说明扣库存和写记录共用一个SqlTransaction任一步失败整体回滚保证数据一致。参数说明ex.Number 2601/2627是 SQL Server 唯一约束冲突的错误号捕获它就能把“重复领取”转成友好提示而不是 500 错误。注意事务范围要尽量小别把网络请求、日志写库这些慢操作包进去否则会拖长锁持有时间。4. 避坑与排查这套 2019 源码最容易翻车的 5 个地方老源码跑不起来八成不是逻辑错而是环境和配置对不上。下面这 5 条是我实际接手这类项目时踩过的按“现象 → 原因 → 解决”列清楚照着排查能省不少时间。4.1 小程序真机请求报“不在以下 request 合法域名列表中”现象开发者工具里能跑真机预览就报域名不合法。原因小程序对 request 域名有白名单限制工具里可以勾选“不校验合法域名”真机不行。解决在小程序后台把后端域名配进 request 合法域名必须是 HTTPS 且已备案本地调试阶段可以先用工具的不校验选项但上线前必须配好。4.2 后端接口收到参数全是 null现象小程序明明传了userId后端req却是 null 或字段为空。原因content-type和参数绑定方式不匹配用application/json却没加[FromBody]或者用了表单格式却按 JSON 解析。解决JSON 请求统一加[FromBody]表单请求用[FromForm]两边约定死一种格式别混用。4.3 压测时库存变成负数现象单机测试正常一并发就超发。原因用了“先查再扣”的非原子写法。解决改成UPDATE ... WHERE Stock 0的原子扣减并检查受影响行数同时确认数据库隔离级别没有把这条更新降级成快照读。4.4 领取记录出现同一用户多条现象用户反馈领了一次却有多条记录。原因只靠代码层查重并发下两个请求都通过了检查。解决加(UserId, CouponId)联合唯一索引代码层捕获唯一冲突错误号返回友好提示双保险。4.5 事务里报“连接已关闭”或超时现象领券偶发失败日志里是连接相关异常。原因SqlConnection被提前释放或者事务持有时间过长导致锁等待超时。解决确保conn、tran用using正确嵌套事务内不做耗时操作必要时给命令设置合理的CommandTimeout别用默认的无限等待。5. 从能跑到敢用给老源码补上接口防刷和幂等把链路跑通、坑排完这套源码离“敢用”还差一步防刷和幂等。热搜里常出现“mvc 防止接口快速提交”放到领券场景就是防止脚本高频刷券。最省事的做法是给领券接口加一层基于用户维度的频率限制再配合前端按钮防抖。5.1 用内存缓存做单机频率限制单机部署时用MemoryCache按用户维度限流就够了// 在 ReceiveCoupon 入口处加频率限制 private static readonly MemoryCache _cache MemoryCache.Default; public ReceiveResult ReceiveCoupon(string userId, int couponId) { var key receive_ userId; if (_cache.Contains(key)) { return new ReceiveResult { Success false, Message 操作太频繁请稍后再试 }; } // 5 秒内同一用户只允许请求一次 _cache.Set(key, 1, DateTimeOffset.Now.AddSeconds(5)); // ... 后续扣库存、写记录逻辑 }逻辑说明以userId为 key5 秒内重复请求直接拒绝能挡住大部分脚本高频提交。参数说明AddSeconds(5)是限流窗口按业务调整领券这种低频操作 3 到 5 秒足够多机部署时MemoryCache不共享要换成 Redis 之类的集中式缓存否则限流形同虚设。5.2 幂等让重复请求返回同一结果防刷解决的是“频率”幂等解决的是“同一请求重复到达”。领券接口天然适合用(UserId, CouponId)做幂等键第一次请求正常扣减并返回成功后续重复请求因为唯一索引冲突直接返回“已领取”而不是报错。这样即使小程序端因为网络重试发了两次用户看到的也是一致结果。5.3 上线前值得做的三个验证第一用并发工具如 Apache Bench 或自己写多线程脚本对领券接口打 100 并发确认库存不为负、记录不重复。第二模拟小程序端断网重试确认幂等生效。第三把限流窗口调小做压测确认限流真的拦住了高频请求。这三点过了这套 2019 年的源码才算真正能撑住一个小型领券活动。我自己接手这类老项目时有个习惯先不改业务逻辑只把库存扣减和唯一索引这两处补上跑一遍并发验证再动其他代码。因为这两处是超发和重复领的根根没扎稳后面加再多防刷都是白搭。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。