ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ASP.NET MVC C# 微信小程序优惠券领取源码解析与实战

ASP.NET MVC C# 微信小程序优惠券领取源码解析与实战

简介:这是一套面向微信小程序与.NET开发者的淘宝客优惠券领取系统源码,适合想学习小程序对接第三方API、或需要搭建优惠券导购平台的初中级开发者。后台基于ASP.NET MVC框架与C#语言开发,调用阿里妈妈淘宝客接口实现商品搜索与优惠券领取,并已内置内容管理、会员、订单、微信及WAP等模块,便于二次开发成完整站点。压缩包共76个文件,约22.8MB,包含22个js、17个json、17个wxss、16个wxml等,分别对应小程序逻辑、页面配置、样式与结构,另有后台源码压缩包与数据库脚本。开发环境为Visual Studio 2010搭配SQL Server 2008与.NET 4.0,管理员账号密码及数据库连接字符串均有说明,上手门槛较低。目前已有1784人学习下载,读者可借此掌握小程序前后台联调、淘宝客API调用与MVC分层设计思路,快速搭建可运行的优惠券领取项目。

1. 从一份 2019 完整版源码说起:ASP.NET MVC + C# 优惠券领取小程序能跑通什么

手里这份 ASP.NET MVC C# 优惠券领取微信小程序源码,是 2019 年前后比较典型的一套「服务端 + 小程序端」组合。服务端用 ASP.NET MVC(.NET Framework 4.x 时代)搭后台,C# 写业务逻辑,小程序端负责展示优惠券列表、领取按钮、我的券包这几个核心页面。它解决的不是「从零设计一个营销系统」的问题,而是给了一个能直接跑起来的领券闭环:用户授权登录、拉取可领券列表、点击领取、写入领取记录、回显已领状态。适合两类人:一是手上有个 .NET 老项目、想快速加一个小程序入口的后端同学;二是想学 C# 微信小程序源码怎么把 MVC 控制器和小程序请求对接起来的新手。这套代码的边界也很清楚——它是 2019 年的完整版,不是云开发,也不是 .NET Core,别拿现在的架构标准去套它。

2. 拆开这套 .NET 微信小程序源码:目录结构与请求链路

2.1 服务端 MVC 分层与关键文件

拿到源码先别急着 F5,先看目录。ASP.NET MVC 的骨架基本固定:Controllers、Models、Views、App_Start、Content、Scripts。这套领券项目里,真正要盯的是三块:控制器里的接口方法、Models 里的实体、以及Web.config里的数据库连接串。

常见做法是控制器按业务拆,比如CouponController管券的列表和领取,UserController管登录和用户信息。小程序端不请求 View,只请求返回 JSON 的 Action,所以你会看到很多方法直接return Json(...),而不是return View()。这一点是新手最容易懵的地方——MVC 在这里其实只用了 M 和 C,V 那层基本是给后台管理页面用的。

// Controllers/CouponController.cs 典型结构 public class CouponController : Controller { // 小程序拉取可领取的优惠券列表 public ActionResult List() { // 只取未过期、库存大于 0 的券 var list = db.Coupons .Where(c => c.ExpireTime > DateTime.Now && c.Stock > 0) .Select(c => new { c.Id, c.Title, c.Amount, c.Stock }).ToList(); return Json(new { code = 0, data = list }, JsonRequestBehavior.AllowGet); } }

这段代码的逻辑很直白:查库、过滤、投影成匿名对象、序列化成 JSON。参数上要注意JsonRequestBehavior.AllowGet,2019 年的 MVC 默认禁止 GET 返回 JSON,不加这行小程序 GET 请求会直接 500。code = 0是这套源码约定的成功码,小程序端靠它判断,别自己改成 200 又不改前端。

2.2 小程序端请求与登录态

小程序端目录一般是pages、utils、app.js、app.json。领券逻辑集中在pages/coupon这类页面里,核心是wx.request调服务端接口,加上wx.login拿 code 换 openid。

// pages/coupon/coupon.js 领取动作 wx.request({ url: baseUrl + '/Coupon/Receive', // 对应 MVC 的 Receive Action method: 'POST', data: { couponId: this.data.currentId, openid: app.globalData.openid // 登录后缓存的用户标识 }, success: (res) => { if (res.data.code === 0) { wx.showToast({ title: '领取成功' }); } else { wx.showToast({ title: res.data.msg, icon: 'none' }); } } });

这里baseUrl必须是小程序后台配置过的合法域名,本地调试要在开发者工具里勾「不校验合法域名」。openid是登录后存在globalData里的,如果没登录就点领取,服务端拿不到 openid,会返回错误码。参数说明:couponId是券主键,openid是用户唯一标识,服务端靠这两个字段做「一人一券」的去重判断。

2.3 数据库表与领取去重逻辑

这套源码一般带.sql脚本,核心两张表:券表Coupons和领取记录表CouponRecords。去重就靠CouponRecords里Openid + CouponId的唯一约束,或者服务端先查再插。

-- 领取记录表关键字段 CREATE TABLE CouponRecords ( Id INT IDENTITY(1,1) PRIMARY KEY, Openid NVARCHAR(64) NOT NULL, CouponId INT NOT NULL, ReceiveTime DATETIME DEFAULT GETDATE() ); -- 防重复领取:同一用户同一券只能有一条 CREATE UNIQUE INDEX UX_Openid_Coupon ON CouponRecords(Openid, CouponId);

有了唯一索引,即使小程序端连点两次,第二次插入也会被数据库拦下,服务端捕获异常返回「已领取」。这比在 C# 里先Any()再Add()更稳,因为并发下先查后插有窗口期。参数上Openid长度给 64 够用,ReceiveTime默认值省得代码里再赋值。

3. 把源码跑起来:环境、配置与接口联调

3.1 服务端环境与 Web.config 配置

2019 年的 ASP.NET MVC 项目,环境要求是 Windows + IIS 或 IIS Express + SQL Server + Visual Studio 2017/2019。用 VS 打开.sln,先还原 NuGet 包,再改Web.config里的连接串。

<!-- Web.config 连接串,改成自己的库 --> <connectionStrings> <add name="DefaultConnection" connectionString="Data Source=.;Initial Catalog=CouponDb;User ID=sa;Password=你的密码" providerName="System.Data.SqlClient" /> </connectionStrings>

逻辑说明:Data Source=.表示本机默认实例,Initial Catalog是库名,要和.sql脚本建的库一致。参数上如果用的是 LocalDB,连接串写法不同,别照抄。改完连接串,先在 VS 里跑一次,浏览器访问/Coupon/List,能返回 JSON 就说明服务端通了。这一步不通,后面小程序怎么调都是白搭。

3.2 小程序端配置与合法域名

小程序端用微信开发者工具打开,改app.js里的baseUrl指向你的服务端地址。本地调试阶段,在开发者工具「详情 - 本地设置」里勾上「不校验合法域名、web-view、TLS 版本以及 HTTPS 证书」。

// app.js 全局配置 App({ globalData: { openid: '', baseUrl: 'http://localhost:端口号' // 本地调试用,上线必须 HTTPS } });

参数说明:baseUrl本地可以写http://localhost:xxxx,但真机预览和上线必须是 HTTPS 且域名已备案、已在小程序后台配置。这是硬门槛,不是玄学。很多人本地跑通、真机白屏,九成是域名没配或用了 http。

3.3 登录换 openid 的完整链路

小程序登录不是拿微信账号密码,而是wx.login拿临时 code,传给服务端,服务端用 code + AppId + AppSecret 调微信接口换 openid。这套源码里通常有个UserController/Login干这事。

// 服务端用 code 换 openid(示意) public ActionResult Login(string code) { var url = $"https://api.weixin.qq.com/sns/jscode2session" + $"?appid={appId}&secret={appSecret}" + $"&js_code={code}&grant_type=authorization_code"; var result = HttpGet(url); // 返回 openid 和 session_key var openid = ParseOpenid(result); return Json(new { code = 0, openid = openid }); }

逻辑说明:appId和appSecret在小程序后台拿,appSecret绝不能放小程序端,只能放服务端。参数上grant_type固定authorization_code。拿到 openid 后存进globalData,后续所有请求带上它。这一步的坑是 code 只能用一次,且五分钟过期,重复用会报错。

4. 避坑与排查:这套 2019 源码最容易翻车的五个地方

4.1 现象:小程序请求一直失败,报「不在以下 request 合法域名列表中」

原因:开发者工具没勾「不校验合法域名」,或者真机环境下域名没在小程序后台配置。解决:本地调试勾选跳过校验;真机测试必须用已备案 HTTPS 域名,并在小程序后台「开发 - 开发设置 - 服务器域名」里加上 request 合法域名。

4.2 现象:服务端返回 JSON 但小程序解析出来是字符串

原因:MVC 的Json()默认返回text/html内容类型,部分情况下小程序按字符串处理。解决:改用return Json(data, "application/json", JsonRequestBehavior.AllowGet);显式指定内容类型,或者统一封装一个返回 JSON 的基类方法。

4.3 现象:同一用户能重复领同一张券

原因:只在小程序端做了按钮置灰,服务端没做去重,或者去重逻辑是「先查后插」在并发下失效。解决:在CouponRecords表上建Openid + CouponId唯一索引,服务端插入时捕获唯一约束冲突,返回「已领取」。别只信前端。

4.4 现象:库存扣成负数

原因:扣库存用的是「先读再减再写」,并发领取时多个请求读到同一个库存值。解决:用UPDATE Coupons SET Stock = Stock - 1 WHERE Id = @Id AND Stock > 0,靠数据库行锁保证原子性,再判断受影响行数是否为 1。

4.5 现象:换台机器部署后登录换不到 openid

原因:appId和appSecret写死在代码里,换环境没改;或者服务端服务器时间不准导致签名类接口失败。解决:把微信配置抽到Web.config的appSettings里,部署时改配置不改代码;同时校准服务器时间。

5. 在 2019 版基础上做二次开发:三个能直接落地的改造点

这套源码跑通之后,真正有价值的是怎么在它上面加东西。第一个改造点是「券的领取上限」——现在是一人一券,实际业务常需要「每人最多领 N 张」。做法是在CouponRecords上按Openid分组计数,领取前判断Count < N,同时把唯一索引从Openid + CouponId改成普通索引,否则一人多领会被拦。这个改动要同步改服务端判断和小程序端的提示文案,别只改一半。

第二个改造点是「领取时间窗口」。2019 版一般只判断券是否过期,没做「每天几点开抢」。可以在Coupons表加StartTime和EndTime两个字段,List接口的过滤条件从ExpireTime > Now改成StartTime <= Now && EndTime >= Now。参数上注意时区,服务端和数据库都用同一时区,别一个 UTC 一个本地时间,这种问题排查起来就是黑匣子。

第三个改造点是「接口返回结构统一」。原版每个 Action 各写各的Json(new { code, data, msg }),字段名不统一。建议抽一个ApiResult类:

public class ApiResult { public int Code { get; set; } // 0 成功,非 0 失败 public string Msg { get; set; } public object Data { get; set; } public static ApiResult Ok(object data = null) => new ApiResult { Code = 0, Msg = "ok", Data = data }; public static ApiResult Fail(string msg) => new ApiResult { Code = -1, Msg = msg }; }

这样所有 Action 统一return Json(ApiResult.Ok(list)),小程序端只认code一个字段,维护成本直线下降。参数上Code用 0 表示成功是这套源码的约定,改成 200 要全局替换,不划算。

验证改造是否生效,我一般会走一遍「登录 → 拉列表 → 领取 → 再拉列表看状态 → 查库核对记录」这条链路,每一步都看返回的code和数据库实际数据是否一致。光看小程序界面不够,界面会骗人,库不会。从那以后我每次拿到这类 .NET 小程序源码,都先把登录换 openid 和领取去重这两条链路单独跑通再动别的,顺序反了就是给自己找麻烦。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进