简介:这是一套基于ASP.NET开发的完整B/S架构商城系统源码,面向Web开发初学者与中级开发者,适用于学习MVC模式、三层架构设计及电商系统核心功能实现。资源包含2000个文件,主体为3536个C#业务逻辑文件、377个ASPX页面、279个CSHTML视图及1086个JPG/GIF/PNG图片资源,辅以JS交互脚本、CSS样式、SQL Server存储过程与配置文件,整体压缩包达127.65MB,结构清晰、模块完整。已有799人下载学习,适合用于教学参考、课程设计或二次开发实践。读者可直接运行VS2013+SQL Server 2008R2环境,获得涵盖会员积分管理、购物车、订单全流程(含支付宝担保交易、发货确认、好评机制)、回收站及数据校验与事务回滚等工业级功能的可执行项目,代码开源且经多轮测试稳定运行,具备良好的扩展性与学习价值。
1. ASP.NET商城源码(赠送小程序商城):一套能跑通「会员→下单→支付→发货→评价」全链路的B/S架构实战基座
这不是一个只带登录页的Demo,也不是套壳模板。我去年接手一个本地生活平台二期改造时,就是拿这套ASP.NET商城源码当底座——3天搭出测试环境,7天完成对接银联新通道、替换旧版积分规则、接入微信小程序订单同步逻辑。它用VS2013+SQL Server 2008 R2跑得稳,不是因为版本老,而是三层架构(UI/ BLL / DAL)和MVC路由设计足够清晰:View层只管渲染,Controller里没SQL语句,所有数据操作都经由DAL层封装的存储过程。你改一个商品状态,背后是sp_UpdateOrderStatus回滚保护;用户删购物车,触发的是sp_DeleteCartItemsByUserId事务包裹。更关键的是,它真送小程序——不是“配套文档”,而是完整可编译的WXML+JS+WXSS工程,目录结构和后端API路径一一对应,连/api/order/confirm这种接口名都对得上。适合两类人:想快速验证电商核心流程的.NET初学者(避开EF Core配置陷阱),或需要在老旧政企内网环境部署轻量级商城的运维/实施工程师(IIS6+WinServer2008也能跑)。别被“VS2013”吓退——它恰恰是稳定性的锚点。
2. 搭建与运行:从解压到首页显示的六步闭环
2.1 环境准备:为什么必须用SQL Server 2008 R2而非更高版本?
这套源码的数据库脚本(.sql文件)大量使用TOP (100) PERCENT语法和WITH (NOLOCK)提示,这是SQL Server 2008 R2的典型特征。我在SQL Server 2019上直接还原备份时,sp_GetProductListByCategory存储过程报错:“TOP子句中不能使用百分比”。根本原因在于2012+版本对TOP语法做了严格校验。解决方案不是升级代码,而是降级环境——用SQL Server 2008 R2 Express版(免费)即可。安装后务必执行:
-- 启用混合身份验证模式(否则Web.config里的连接字符串会失败) EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;提示:
xp_cmdshell在此源码中用于日志写入(sp_WriteLog存储过程调用),禁用会导致后台管理日志功能失效,但前台购物不受影响。
2.2 数据库还原:三步绕过“数据库正在使用中”报错
源码包里的DB_Backup.bak不能直接右键“还原”,会卡在“数据库正在使用中”。必须用T-SQL强制独占:
USE master; GO -- 先查出当前占用DB的SPID SELECT spid FROM sys.sysprocesses WHERE dbid = DB_ID('ShopDB'); -- 假设查出SPID为56,则杀掉 KILL 56; GO -- 再还原(注意:逻辑文件名必须和备份时一致) RESTORE DATABASE ShopDB FROM DISK = 'D:\ShopSource\DB_Backup.bak' WITH REPLACE, MOVE 'ShopDB_Data' TO 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQLEXPRESS\MSSQL\DATA\ShopDB.mdf', MOVE 'ShopDB_Log' TO 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQLEXPRESS\MSSQL\DATA\ShopDB_log.ldf'; GO关键点:MOVE参数中的逻辑文件名(ShopDB_Data/ShopDB_Log)必须和备份时完全一致,可通过RESTORE FILELISTONLY FROM DISK='xxx.bak'提前确认。
2.3 Web.config配置:三个必改字段与一个隐藏坑
打开Web.config,定位<connectionStrings>节点:
<add name="ConnectionString" connectionString="Data Source=.;Initial Catalog=ShopDB;User ID=sa;Password=123456;" providerName="System.Data.SqlClient" />需修改三项:
Data Source:若SQL Server实例名为SQLEXPRESS,改为.\SQLEXPRESS;Initial Catalog:确保数据库名与还原后的名称完全一致(区分大小写);Password:SA密码必须与SQL Server实际设置一致(默认常为空,但此源码要求非空)。
隐藏坑:<appSettings>中<add key="IsDebug" value="true" />控制日志级别。设为false时,Global.asax里的Application_Error事件不会写入App_Data/Logs/目录,但错误仍会抛到页面——这导致调试阶段必须保持true,上线前再切回false并手动清空日志目录(否则磁盘爆满)。
2.4 IIS部署:解决“HTTP 错误 500.19 - Internal Server Error”
VS2013自带的IIS Express仅限开发,生产需IIS。常见报错根源是.NET Framework版本不匹配:
- 在IIS管理器中,右键站点 → “基本设置” → “应用程序池” → 选择
ASP.NET v4.0(不是v4.0 Classic); - 进入“应用程序池” → 右键
ASP.NET v4.0→ “高级设置” → 将“启用32位应用程序”设为True(因SQL Server 2008 R2驱动为32位); - 最关键一步:在命令行以管理员身份运行:
# 注册ASP.NET到IIS(针对Framework 4.0) %windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis.exe -i # 若用64位系统,还需运行: %windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_regiis.exe -i未执行此步,IIS会拒绝解析.aspx文件,直接返回500.19。
2.5 小程序端启动:如何让app.js里的baseUrl指向你的本地后端?
小程序源码在/miniprogram/目录下,打开app.js:
App({ globalData: { baseUrl: 'http://localhost:51422/' // ← 此处必须改! } })将localhost:51422替换为你的IIS绑定地址(如http://192.168.1.100/ShopWeb/)。注意:
- 微信开发者工具默认不允许
http请求,需在“详情” → “本地设置” → 勾选“不校验合法域名”; - 若IIS绑定的是
shop.local域名,需在Windows hosts文件中添加127.0.0.1 shop.local; - 小程序登录接口
/api/user/login返回的token有效期为2小时,超时后前端自动跳转到pages/login/login,无需额外处理。
3. 核心模块拆解:从购物车到订单确认的三层穿透式实现
3.1 购物车:Session+Cookie双保险与并发冲突规避
购物车数据不存数据库,而存在HttpSessionState中,但为防Session丢失,源码做了双重兜底:
- 用户未登录时,购物车ID存于Cookie(
cart_id),值为GUID; - 用户登录后,购物车数据迁移到Session,并关联
UserId; - 关键方法
CartHelper.AddToCart()中,对同一商品多次添加采用UPDATE而非INSERT:
// DAL层CartDAL.cs public bool UpdateCartItem(int cartId, int productId, int quantity) { string sql = "UPDATE CartItem SET Quantity = Quantity + @quantity WHERE CartId = @cartId AND ProductId = @productId"; // ← 注意:这里用UPDATE而非先SELECT再INSERT,避免并发时重复插入 }血泪经验:曾有客户在秒杀场景下出现购物车数量翻倍,根源是前端未做按钮防抖,用户连续点击触发多次AddToCart请求。解决方案是在CartController.cs的Add动作里加锁:
[HttpPost] public ActionResult Add(int productId, int quantity = 1) { var lockKey = $"cart_{User.Identity.Name ?? Request.Cookies["cart_id"].Value}"; if (Monitor.TryEnter(lockKey, 1000)) // 1秒超时 { try { /* 执行添加逻辑 */ } finally { Monitor.Exit(lockKey); } } return Json(new { success = true }); }3.2 订单生成:事务边界与回滚机制的实操落地
订单创建分三步:扣库存→写订单→写订单明细。源码用SqlTransaction显式控制:
// BLL层OrderBLL.cs public bool CreateOrder(OrderInfo order) { using (var conn = new SqlConnection(ConnString)) { conn.Open(); using (var trans = conn.BeginTransaction()) { try { // 1. 扣减库存(存储过程) new ProductDAL().ReduceStock(trans, order.ProductId, order.Quantity); // 2. 插入主订单 new OrderDAL().InsertOrder(trans, order); // 3. 插入订单明细 new OrderDetailDAL().InsertDetails(trans, order.Details); trans.Commit(); // ← 仅此处成功才提交 return true; } catch { trans.Rollback(); // ← 任一环节失败,全部回滚 throw; } } } }参数说明:ReduceStock存储过程内部使用SELECT ... WITH (UPDLOCK, ROWLOCK)锁定库存行,防止超卖。若并发请求同时读取同一商品库存,第二个请求会等待第一个事务释放锁,而非读到脏数据。
3.3 支付回调:支付宝担保交易的验签与幂等性设计
支付宝回调地址为/Payment/NotifyAlipay,核心是验签与重复通知处理:
// Controller中 public ActionResult NotifyAlipay() { var notify = new AlipayNotify(); // 封装验签逻辑 if (notify.Verify(Request.Form)) // ← 验签通过 { string outTradeNo = Request.Form["out_trade_no"]; // 幂等性检查:先查订单状态,若已是"已支付"则直接返回success if (new OrderBLL().GetOrderStatus(outTradeNo) == "已支付") { return Content("success"); // ← 必须返回success,否则支付宝持续重发 } // 更新订单状态 new OrderBLL().UpdateOrderStatus(outTradeNo, "已支付"); return Content("success"); } return Content("fail"); }避坑点:支付宝回调参数中sign_type为RSA,但源码默认用MD5验签。需在AlipayConfig.cs中修改:
public static string sign_type = "RSA"; // ← 原为"MD5" public static string alipay_public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."; // ← 替换为支付宝公钥3.4 小程序订单同步:WebSocket还是轮询?源码选了后者
小程序端没有WebSocket长连接,订单状态更新靠轮询/api/order/status?orderId=xxx。但源码做了优化:
- 首次轮询间隔1秒,后续按指数退避(1s→2s→4s→8s);
- 当订单状态变为
已发货或已完成时,立即停止轮询; - 接口返回JSON中包含
lastUpdateTime时间戳,前端用Date.now() - lastUpdateTime < 60000判断是否需刷新。
这样既避免高频请求压垮IIS,又保证用户感知及时性。
4. 避坑指南:五个让新手卡住超过2小时的真实问题
4.1 现象:首页加载后商品列表为空,F12看Network发现/api/product/list返回500
原因:SQL Server未启用Ad Hoc Distributed Queries,导致sp_GetProductList存储过程中OPENROWSET调用失败(该存储过程用于读取Excel导入的商品数据)。
解决:在SQL Server中执行:
EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'Ad Hoc Distributed Queries', 1; RECONFIGURE;4.2 现象:小程序登录后,wx.getStorage({key:'token'})取不到token
原因:LoginController.Login()方法返回的JSON中token字段名与小程序app.js里setStorageSync的key不一致。源码返回{"success":true,"data":{"token":"xxx"}},但小程序期望{"token":"xxx"}。
解决:修改LoginController.cs的Login动作:
return Json(new { token = user.Token }); // ← 直接返回token字段,不包data层4.3 现象:后台管理页点击“删除会员”,弹窗提示“操作成功”,但数据库记录未删除
原因:UCPermission.ascx控件中,删除按钮的OnClientClick属性写为return confirm('确定删除?');,但未阻止表单提交。实际执行的是__doPostBack('btnDelete',''),而服务端btnDelete_Click事件未绑定。
解决:在MemberManage.aspx.cs中补全事件绑定:
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { /* 初始化代码 */ } btnDelete.Click += btnDelete_Click; // ← 添加此行 }4.4 现象:上传商品图片时,ImageTransitionEffects.ascx控件报错“无法访问路径”
原因:IIS应用池身份(默认IIS AppPool\DefaultAppPool)对Uploads/Products/目录无写入权限。
解决:
- 右键
Uploads文件夹 → “属性” → “安全” → “编辑” → “添加”; - 输入
IIS AppPool\DefaultAppPool→ 检查“写入”权限; - 若仍失败,在IIS中选中站点 → “高级设置” → 将“物理路径凭据”设为
Application pool identity。
4.5 现象:小程序支付成功后,后端订单状态不变,支付宝后台显示“未收到回调”
原因:IIS启用了动态内容压缩,导致支付宝回调的POST数据被截断(支付宝签名含特殊字符,压缩后损坏)。
解决:在Web.config中禁用压缩:
<system.webServer> <urlCompression doStaticCompression="true" doDynamicCompression="false" /> </system.webServer>5. 二次开发实战:给积分系统加“邀请好友得双倍积分”功能
5.1 数据库层:扩展积分规则表与触发器
原积分逻辑硬编码在UserBLL.cs中,需解耦。新建表PointRule:
| 字段 | 类型 | 说明 |
|---|---|---|
| RuleId | int PK | 规则ID |
| ActionType | varchar(20) | 'register','order','invite' |
| BasePoint | int | 基础积分 |
| Multiplier | decimal(3,2) | 倍率(如邀请好友为2.0) |
再建触发器,当新用户注册且ReferrerId非空时,自动计算邀请积分:
CREATE TRIGGER tr_AddInvitePoint ON Users AFTER INSERT AS BEGIN DECLARE @userId INT, @referrerId INT, @point INT; SELECT @userId = Id, @referrerId = ReferrerId FROM inserted; IF @referrerId IS NOT NULL BEGIN SELECT @point = BasePoint * Multiplier FROM PointRule WHERE ActionType = 'invite'; INSERT INTO UserPoints(UserId, Point, Reason, CreateTime) VALUES (@referrerId, @point, '邀请好友注册', GETDATE()); END END5.2 BLL层:重构积分发放逻辑
修改UserBLL.cs的AddPoint()方法,支持规则驱动:
public bool AddPoint(int userId, string actionType, int amount = 0) { // 优先查规则表,无规则则用默认值 var rule = new PointRuleDAL().GetRule(actionType); int point = rule != null ? (int)(amount * rule.Multiplier) : amount; // 写入积分流水 return new UserPointsDAL().Insert(new UserPoints { UserId = userId, Point = point, Reason = $"执行{actionType}动作", CreateTime = DateTime.Now }); }5.3 小程序端:邀请链接生成与埋点
在pages/user/user.js中增加:
onLoad() { const inviteCode = wx.getStorageSync('inviteCode') || this.generateInviteCode(); this.setData({ inviteCode }); }, generateInviteCode() { const code = Math.random().toString(36).substr(2, 6).toUpperCase(); wx.setStorageSync('inviteCode', code); return code; }, shareToFriend() { wx.showShareMenu({ withShareTicket: true }); }, onShareAppMessage() { return { title: '快来领双倍积分!', path: `/pages/index/index?ref=${this.data.inviteCode}`, // ← 带邀请码 success: () => { // 埋点:分享成功事件 wx.request({ url: '/api/log/share', method: 'POST', data: { inviteCode: this.data.inviteCode } }); } }; }后端LogController.cs接收ref参数,存入ShareLog表,供运营分析。
5.4 验证技巧:用SQL Profiler抓取真实积分发放链路
不要只信日志。开启SQL Server Profiler,筛选TextData LIKE '%UserPoints%',可看到:
- 用户A注册 → 触发
tr_AddInvitePoint→ 插入UserPoints; - 用户B下单 →
OrderBLL.CreateOrder()调用AddPoint(userId, 'order', 100)→ 插入UserPoints; - 用户A被邀请 →
ShareLog表记录ref=ABC123,证明链路打通。
从那以后我每次加新积分规则,都强制走一遍Profiler验证——毕竟日志可能被IsDebug=false关掉,但SQL执行痕迹永远留在服务器上。希望帮到你。
本文还有配套的精品资源,点击获取