ASP.NET商城源码实战:B/S架构、MVC三层与支付宝支付全解析
简介一份基于ASP.NET MVC与三层架构的完整商城系统源码面向.NET开发者和电商项目二次开发人员也适合高校学生作为毕业设计或课程项目参考。系统采用B/S模式实现了会员等级积分、订单管理、购物车、支付宝/网银支付、确认收货好评、回收站等主流商城功能页面运用MVC模板灵活展示并通过数据合法性校验、回滚机制保障数据安全。开发环境为VS2013SQL Server2008R2使用存储过程直接操作数据有效提升网站性能。资源包共2000个文件以C#源码、MVC页面(.cshtml)、ASP.NET页面(.aspx)及前端脚本/样式(.js/.css)为主还包含大量静态图片和数据库相关文件压缩包大小127.65MB目录结构清晰便于定位与二次开发。包内另赠小程序商城源码覆盖移动端购物场景。已有799人学习/下载这套资源既能直接运行体验完整流程也可在其基础上做功能扩展或架构研究。1. ASP.NET商城源码实战一套能跑通前后台的B/S架构老项目做电商后台开发的人手里要是没几套能直接翻源码的商城项目遇到“客户要个带会员积分和支付宝支付的商城”这种需求时多半得从零开始搭框架。这套ASP.NET商城源码属于典型的B/S模式项目开发环境是VS2013加SQL Server 2008R2默认采用MVC加三层架构页面模板可切换后台内置了会员等级、订单管理、购物车、回收站、评价和支付宝担保交易这些主流电商功能。更实在的是它随包附赠一份微信小程序的源码工程2048-小程序.zip虽然上一轮交付的小程序工程是独立的不能像网页那样直接在浏览器里跑但正好补上了移动端展示这块短板。适合正在做.NET电商项目、需要参考存储过程和权限控件写法的开发人员也适合拿来当毕业设计和二次开发底子。这套系统最值得看的不是页面长什么样而是它的数据层设计大量业务操作通过视图和存储过程完成在性能和安全上有明确取舍支付宝接口的对接方式也保留了老项目的典型写法。接下来我会把它的架构拆开讲清楚再从订单、会员、支付和模板这些实际模块入手带着你把代码跑起来。2. 架构与数据库MVC三层在VS2013和SQL2008R2下的落地方式2.1 B/S架构与MVC三层的真实分工这套商城系统的整体结构是浏览器发起请求IIS承载站点后台通过ADO.NET连到SQL Server 2008R2数据库。项目里能看到Global.asax负责应用程序生命周期管理比如Application_Start里注册路由和全局过滤器页面文件如.ascx控件承担了前台展示和交互逻辑。我拆过不少ASP.NET商城这套代码在分层上属于典型的“用户界面层—业务逻辑层—数据访问层”三层结构同时引入了MVC的页面模板机制。实际代码里的做法是用户界面层.aspx页面加.ascx用户控件像UCDroplistPermission.ascx这种就是专门的权限下拉控件RegionDropList.ascx和AjaxRegion.ascx负责地区联动。业务逻辑层处理订单状态流转、会员积分计算、购物车价格重算。数据访问层封装SQL操作重点调用存储过程例如分页查询商品列表、更新库存和积分。// Global.asax 核心初始化逻辑 protected void Application_Start(object sender, EventArgs e) { // 注册MVC路由默认路由指向Home/Index RouteConfig.RegisterRoutes(RouteTable.Routes); // 初始化SqlServer依赖用于缓存失效通知 SqlDependency.Start(ConfigurationManager.ConnectionStrings[ConnStr].ConnectionString); } protected void Application_Error(object sender, EventArgs e) { // 捕获未处理异常写日志并回滚事务避免数据脏写 Exception ex Server.GetLastError(); if (ex ! null) { LogHelper.WriteLog(GlobalError, ex); // 调用存储过程回滚当前未提交事务 DbHelper.ExecuteSql(IF TRANCOUNT 0 ROLLBACK TRAN); } }这段代码里SqlDependency.Start是为了在商品数据变更时自动清缓存减少数据库压力。Application_Error属于兜底逻辑保证系统运行中遇到不可恢复错误时数据库不会留下半截数据。注意SqlDependency需要数据库开启Service Broker如果SQL Server安装时没启用这段代码会直接报错。实际调试中很多人忽略这一点。2.2 数据库结构与存储过程设计系统数据库文件对应SQL Server 2008R2版本里面包含视图和大量存储过程。这些存储过程不只做单表操作而是把多表联查、事务控制、分页逻辑全部封在数据库端。-- 订单分页查询存储过程简化版 CREATE PROCEDURE [dbo].[Proc_OrderPageList] PageIndex INT, PageSize INT, UserId INT NULL, TotalCount INT OUTPUT AS BEGIN SET NOCOUNT ON; -- 统计总数供分页控件使用 SELECT TotalCount COUNT(1) FROM Orders WHERE UserId IS NULL OR UserId UserId; -- 利用ROW_NUMBER做高效分页避免一次性加载全表数据 SELECT * FROM ( SELECT ROW_NUMBER() OVER (ORDER BY OrderId DESC) AS RowId, * FROM Orders WHERE UserId IS NULL OR UserId UserId ) AS t WHERE RowId BETWEEN (PageIndex - 1) * PageSize 1 AND PageIndex * PageSize; END这个存储过程是商城后台订单列表的核心查询逻辑代码里通过UserId参数实现“查询全部订单”和“查询指定用户订单”的切换。分页这里用了ROW_NUMBER()而不是OFFSET FETCH因为后者只在SQL Server 2012以上版本才支持2008R2只能用这种写法。在VS2013里调试这个项目时建议直接把存储过程逐个过一遍重点关注Proc_OrderCreate、Proc_UserRecharge这类涉及资金和积分的操作。它们内部用BEGIN TRAN配合ERROR判断事务成败这是老项目的经典写法逻辑比ORM生成的SQL要直观得多。2.3 数据合法性校验与回滚机制摘要里提到的数据合法性校验和回滚机制是这套系统设计上比较严谨的地方。在页面层通过ASP.NET验证控件和后台C#代码双重检查数据类型、日期格式、字符长度并对特殊字符做转码防止SQL注入。// 用户控件中的输入校验示例 public static bool ValidateInput(string input, string fieldName, int maxLength, out string errorMsg) { errorMsg string.Empty; if (string.IsNullOrEmpty(input)) { errorMsg fieldName 不能为空; return false; } if (input.Length maxLength) { errorMsg fieldName 长度不能超过 maxLength 个字符; return false; } // 过滤危险字符如单引号、尖括号等防注入和XSS string dangerousChars ;%\(); foreach (char c in dangerousChars) { if (input.IndexOf(c) 0) { errorMsg fieldName 包含非法字符; return false; } } return true; }数据访问层在调用存储过程时还会对参数化查询做二次保障配合SQL Server事务回滚机制让系统在出现异常时能自动回滚保证库存、订单、支付流水这几个表的数据始终保持一致。这种做法在生产环境里很实用很多基于EF的商城项目反而会在并发下单时出现超卖或状态不一致的问题。这套老代码的这种设计放到今天看也没过时。3. 核心业务模块实战会员、订单、购物车与评价的完整链路3.1 会员等级与积分管理系统内置了会员等级积分管理用户在商城消费后自动获得积分不同积分对应不同等级和折扣。这块逻辑主要在MemberLevel相关存储过程和后台页面中体现。-- 用户下单后更新积分与等级关键存储过程 CREATE PROCEDURE [dbo].[Proc_UserPointsUpdate] UserId INT, OrderAmount DECIMAL(18,2), Points INT OUTPUT AS BEGIN BEGIN TRAN; -- 积分按订单金额1:1累积上不封顶 UPDATE Users SET Points Points OrderAmount, TotalAmount TotalAmount OrderAmount WHERE UserId UserId; -- 根据累计消费金额自动升级等级 UPDATE Users SET LevelId ( SELECT TOP 1 Id FROM MemberLevel WHERE MinAmount TotalAmount ORDER BY MinAmount DESC ) WHERE UserId UserId; SELECT Points Points FROM Users WHERE UserId UserId; IF ERROR 0 BEGIN ROLLBACK TRAN; RETURN -1; END COMMIT TRAN; END这套积分逻辑直接埋在存储过程里好处是订单提交和积分更新在同一事务中完成不会出现订单生成了但积分没加的情况。对应的PCC.ascx和Region.ascx主要是地区级联选择控件用户注册时选择省市区数据来源也是数据库中的地区表通过AjaxRegion.ascx的异步回发实现省市区的三级联动不需要页面刷新。3.2 购物车与订单状态机购物车在商城系统里算核心中的核心。这套系统的购物车数据保存在数据库表中每次添加、修改商品都会同步更新数据库这比用Session存购物车的方案更稳用户换设备登录后购物车数据还在。// 购物车添加商品逻辑BLL层 public static bool AddToCart(int userId, int productId, int quantity) { // 先查购物车是否已存在该商品 DataTable dt DbHelper.ExecuteDataTable( SELECT * FROM Cart WHERE UserIduid AND ProductIdpid, new SqlParameter(uid, userId), new SqlParameter(pid, productId)); if (dt.Rows.Count 0) { // 已存在则累加数量并检查库存上限 int newQty (int)dt.Rows[0][Quantity] quantity; if (newQty 999) newQty 999; DbHelper.ExecuteSql( UPDATE Cart SET Quantityqty WHERE UserIduid AND ProductIdpid, new SqlParameter(qty, newQty), new SqlParameter(uid, userId), new SqlParameter(pid, productId)); } else { // 不存在则新增购物车记录 DbHelper.ExecuteSql( INSERT INTO Cart(UserId, ProductId, Quantity, AddTime) VALUES(uid,pid,qty,GETDATE()), new SqlParameter(uid, userId), new SqlParameter(pid, productId), new SqlParameter(qty, quantity)); } // 调用存储过程重算购物车总金额 DbHelper.ExecuteSql(EXEC Proc_CartTotal UserIduid, new SqlParameter(uid, userId)); return true; }这段代码展示了购物车模块最常见的“有则改、无则加”处理逻辑。这里的Proc_CartTotal存储过程会在每次添加后重算总金额和总数量返回给页面上的购物车角标。订单状态流转分为“待付款 → 已付款 → 待发货 → 已发货 → 已完成 → 已取消”几个阶段对应数据库里的OrderStatus字段。涉及退货时系统支持“确认收货好评”后生成评价记录同时返还积分到用户账户。3.3 回收站与软删除机制这里说的回收站不是Windows那个回收站而是商城的商品和订单的软删除机制。删除商品时不会真从数据库里移除记录而是把IsDeleted字段置为1这样后台还能通过回收站恢复误删的商品或订单。-- 软删除商品保留数据可恢复 UPDATE Products SET IsDeleted 1, DeleteTime GETDATE() WHERE ProductId ProductId; -- 后台回收站恢复商品 UPDATE Products SET IsDeleted 0, DeleteTime NULL WHERE ProductId ProductId;回收站的设计逻辑是把业务数据的生命周期拉长给运营留了后悔药。这类设计在商城后台很常见特别是订单和商品这类数量大、误操作概率高的表硬删除的后果通常很严重。3.4 确认收货与评价闭环确认收货和好评功能是把交易闭环的最后一步补上。订单发货后用户点击确认收货系统会更新订单状态并跳转到评价页面。// 确认收货并触发评价入口 public static bool ConfirmReceive(int orderId, int userId) { // 校验订单是否属于当前用户且状态为已发货 int rows DbHelper.ExecuteSql( UPDATE Orders SET OrderStatus 5, ReceiveTime GETDATE() WHERE OrderIdoid AND UserIduid AND OrderStatus 4, new SqlParameter(oid, orderId), new SqlParameter(uid, userId)); if (rows 0) { // 订单确认收货后触发积分发放 DataTable dt DbHelper.ExecuteDataTable( SELECT OrderAmount FROM Orders WHERE OrderIdoid, new SqlParameter(oid, orderId)); decimal amount Convert.ToDecimal(dt.Rows[0][OrderAmount]); DbHelper.ExecuteSql( EXEC Proc_UserPointsUpdate UserIduid, OrderAmountamount, new SqlParameter(uid, userId), new SqlParameter(amount, amount)); return true; } return false; // 订单状态不对或不属于该用户则返回失败 }注意这里的OrderStatus参数是个关键点。代码里用4表示已发货、5表示已完成这套状态值是数据库里的硬编码二次开发时如果自己新增状态一定不能跟原有的数字冲突否则会出现订单状态错乱的“玄学”问题。4. 支付宝接入与付款流程担保交易、即时到账和网银的配置参数4.1 三种支付方式共存系统支持支付宝担保交易含即时到账和网银付款三种模式。担保交易是老版本支付宝的经典模式资金先由支付宝托管买家确认收货后卖家才能拿到钱适合B2C商城。即时到账则适用于虚拟商品或信任度高的交易场景资金直接进卖家账户。三种支付方式的接入系统里是通过ShowPaymentMode.ascx这个控件在前台切换显示的。后台会根据商品类型和商家配置自动决定展示哪种支付按钮前端代码结构如下// 支付方式选择控件核心逻辑 protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { // 从数据库读取商家配置的支付方式列表 DataTable dt DbHelper.ExecuteDataTable( SELECT PayCode, PayName FROM PaymentConfig WHERE IsEnabled1); foreach (DataRow row in dt.Rows) { // 根据PayCode动态创建支付按钮 string payCode row[PayCode].ToString(); if (payCode alipay_escrow) { // 担保交易按钮 litPayment.Text input typeradio namepaytype valuealipay_escrow /支付宝担保交易; } else if (payCode alipay_direct) { // 即时到账按钮 litPayment.Text input typeradio namepaytype valuealipay_direct /支付宝即时到账; } } } }4.2 支付宝接口参数配置清单支付宝接口对接需要设置商户号、密钥、回调地址等参数。这套代码中这些配置统一存在Web.config或数据库的PaymentConfig表里。实际对接时你需要准备以下参数参数名配置位置说明PartnerIDWeb.config支付宝签约商户号以2088开头的16位数字SellerEmailWeb.config商家支付宝账号PrivateKeyWeb.configRSA私钥老版本用MD5签名新版本用RSA2NotifyUrl支付请求地址支付宝服务器异步通知回调地址必须公网可访问ReturnUrl支付请求地址支付完成后同步跳转地址PayType支付控件alipay_escrow / alipay_direct 二选一// 构造支付宝支付请求旧版接口示例 public static string BuildAlipayRequest(string orderNo, decimal amount, string payType) { // 从Web.config读取配置 string partnerId ConfigurationManager.AppSettings[alipay_partner]; string sellerEmail ConfigurationManager.AppSettings[alipay_seller]; string privateKey ConfigurationManager.AppSettings[alipay_key]; string notifyUrl ConfigurationManager.AppSettings[alipay_notify_url]; string returnUrl ConfigurationManager.AppSettings[alipay_return_url]; // 拼接参数并生成MD5签名 SortedDictionarystring, string parameters new SortedDictionarystring, string(); parameters.Add(service, payType alipay_escrow ? create_partner_trade_by_buyer : create_direct_pay_by_user); parameters.Add(partner, partnerId); parameters.Add(seller_email, sellerEmail); parameters.Add(out_trade_no, orderNo); parameters.Add(subject, 商城订单 orderNo); parameters.Add(total_fee, amount.ToString(F2)); parameters.Add(notify_url, notifyUrl); parameters.Add(return_url, returnUrl); // 按key排序后拼接成待签名字符串 StringBuilder sb new StringBuilder(); foreach (var kv in parameters) { sb.Append(kv.Key).Append().Append(kv.Value).Append(); } string signContent sb.ToString().TrimEnd() privateKey; string sign Md5Encrypt(signContent); // 构建自动提交表单 return BuildAutoSubmitForm(parameters, sign); }注意这套代码用的是老版本MD5签名方式现在的支付宝开放平台已经主推RSA2密钥正式上线前建议升级到新接口。如果你是学习研究用途直接跑通流程就行如果要上生产环境需要替换签名算法和接口地址。4.3 异步通知与订单状态同步支付宝异步通知NotifyUrl是整个支付链路里最容易翻车的一环。服务器收到支付宝的通知后需要先验签再判断订单金额是否一致最后更新订单状态。这套系统的做法是// 支付宝异步通知处理 public void HandleAlipayNotify() { // 1. 获取支付宝POST过来的参数 string tradeStatus Request.Form[trade_status]; string outTradeNo Request.Form[out_trade_no]; string totalFee Request.Form[total_fee]; string tradeNo Request.Form[trade_no]; // 2. 验签使用支付宝公钥验证签名 bool isSignValid AlipayCore.VerifySign(Request.Form); if (!isSignValid) { Response.Write(fail); return; } // 3. 验签通过后查订单比对金额 DataTable dt DbHelper.ExecuteDataTable( SELECT * FROM Orders WHERE OrderNono, new SqlParameter(no, outTradeNo)); if (dt.Rows.Count 0) { Response.Write(fail); return; } decimal orderAmount Convert.ToDecimal(dt.Rows[0][OrderAmount]); if (Math.Abs(orderAmount - Convert.ToDecimal(totalFee)) 0.01m) { // 金额不一致疑似篡改记录日志并返回失败 LogHelper.WriteLog(AlipayNotify, 金额不一致: 订单 outTradeNo); Response.Write(fail); return; } // 4. 判断交易状态更新订单和支付流水 if (tradeStatus TRADE_SUCCESS || tradeStatus TRADE_FINISHED) { DbHelper.ExecuteSql( UPDATE Orders SET OrderStatus1, PayTimeGETDATE(), TradeNotradeNo WHERE OrderNoorderNo AND OrderStatus0, new SqlParameter(tradeNo, tradeNo), new SqlParameter(orderNo, outTradeNo)); Response.Write(success); } }这里的关键是Response.Write(success)必须输出纯文本success不能有任何多余的HTML或换行否则支付宝会一直重发通知造成重复处理。我在一次调试中踩过这个坑页面因为是MVC模板带了页头页脚输出了一堆HTML支付宝那边就疯狂重试了12次。5. 避坑指南运行环境兼容性、权限控件与中文乱码的五个排查点5.1 现象Visual Studio 2013无法打开项目原因项目文件.csproj可能是旧版本格式或者是用更高版本VS保存过导致VS2013加载时提示“不兼容”。解决用记事本打开.csproj文件检查Project ToolsVersion节点。如果是12.0VS2013可以正常打开如果是14.0或更高需要改成12.0并删除项目中的packages.config里高版本依赖项。同时确认目标框架是.NET Framework 4.5因为VS2013默认支持的最高版本就是4.5.1。5.2 现象附加数据库时报“数据库文件版本高于当前实例”原因SQL Server 2008R2的数据库文件.mdf可以通过低版本创建但高版本创建的数据库文件低版本实例无法直接附加。解决如果只有高版本数据库的备份文件.bak在SQL Server 2012及以上版本还原后用“生成脚本”功能导出全部表结构和数据再在2008R2上执行脚本重建。更省事的方式是直接用VS2013自带的数据库项目SQL Server Database Project来管理结构。这个代码包自带的数据库文件一般可以直接附加但如果附加失败按上面步骤处理。5.3 现象页面加载报“未能加载文件或程序集 AjaxControlToolkit”原因项目的Bin目录缺少AjaxControlToolkit.dll或者版本与项目引用的版本不一致。解决在NuGet包管理器中重新安装对应版本的AjaxControlToolkit。具体操作是右键项目 → 管理NuGet程序包 → 搜索“AjaxControlToolkit” → 选择与项目匹配的版本安装。安装完成后确认Web.config中的controls节点引用了正确命名空间。5.4 现象中文数据显示为乱码原因数据库排序规则与页面编码不一致或者连接字符串中未指定字符集。解决在Web.config的连接字符串中加上Character Setutf8如果使用MySQL或确保数据库排序规则为Chinese_PRC_CI_ASSQL Server。同时确认页面meta charsetutf-8设置正确并且Response.Charset和Response.ContentEncoding已设置为UTF-8。connectionStrings add nameConnStr connectionStringserver.;databaseShop;uidsa;pwd123456;Character Setutf8; providerNameSystem.Data.SqlClient / /connectionStrings5.5 现象后台权限控件不起作用所有用户都能访问管理页面原因UCDroplistPermission.ascx和UCPermission.ascx控件的权限判断逻辑依赖Session中的用户角色值如果Session名与代码中读取的键名不一致权限控制会失效。解决全局搜索Session[Admin]或Session[UserRole]等键名确认与登录页面写入的Session键完全一致。另外检查这些控件在.aspx页面中的注册方式和位置确保它们在页面加载的生命周期早期就完成权限绑定。// 权限控件加载逻辑 protected void Page_Load(object sender, EventArgs e) { // 检查当前登录用户是否具有访问权限 if (Session[Admin] null) { Response.Redirect(/Admin/Login.aspx?returnUrl Request.Url.PathAndQuery); return; } // 从数据库读取用户权限列表并绑定到下拉控件 int adminId Convert.ToInt32(Session[AdminId]); ddlPermission.DataSource DbHelper.ExecuteDataTable( SELECT PermId, PermName FROM Permissions WHERE AdminIdaid, new SqlParameter(aid, adminId)); ddlPermission.DataBind(); }这里补一句老项目的Session依赖很重如果部署时使用多台服务器做负载均衡需要把Session模式改为StateServer或SQLServer否则会出现用户登录后操作被跳回登录页的情况。这套代码没有内置Redis Session共享生产环境部署时要特别注意。6. 小程序商城端调试技巧从解压到在微信开发者工具里跑通6.1 小程序工程的结构与页面渲染逻辑随包赠送的2048-小程序.zip是一个微信小程序工程解压后能看到典型的微信小程序目录结构app.js全局逻辑、app.json全局配置、pages目录下是各个页面。这个工程本身是一个2048小游戏但它的项目配置和请求封装模式可以直接套用到商城小程序的开发里。// 小程序 app.js 全局初始化逻辑 App({ globalData: { // 商城API接口地址替换成你自己部署的后端域名 apiBaseUrl: http://localhost:8080, // 登录后存储的用户令牌 token: , userInfo: null }, onLaunch: function () { // 尝试从本地缓存恢复登录状态 const token wx.getStorageSync(token); if (token) { this.globalData.token token; } }, // 统一的网络请求封装自动携带token request: function (url, method, data, successCallback, failCallback) { wx.request({ url: this.globalData.apiBaseUrl url, method: method || GET, data: data || {}, header: { Content-Type: application/json, Authorization: Bearer this.globalData.token }, success: function(res) { // 业务状态码为0表示成功非0表示业务错误 if (res.data.code 0) { successCallback(res.data.data); } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }); if (failCallback) failCallback(res.data); } }, fail: function(error) { console.error(Request Error:, error); wx.showToast({ title: 网络异常请稍后重试, icon: none }); if (failCallback) failCallback(error); } }); } });这段代码里封装了request方法统一处理请求头、业务状态码和错误提示。商城小程序里的商品列表、登录、下单这些页面直接调用this.request就行不需要每个页面再写一遍wx.request。6.2 用微信开发者工具导入和调试微信开发者工具是官方调试器安装后直接导入解压后的工程目录选择“小程序项目”填写AppID。没有AppID的话可以点“测试号”创建临时AppID功能上基本不受限制。# 解压赠送的小程序工程到工作目录 cd D:\projects unzip 2048-小程序.zip -d my-mall-miniapp # 用微信开发者工具打开my-mall-miniapp目录 # 在工具右上角选择 详情 - 本地设置 - 勾选不校验合法域名调试时最重要的一步勾选“不校验合法域名”。开发阶段后端API如果用的是http://localhost或者局域网IP不勾这个选项wx.request会被拦截报错。6.3 把2048小程序改造为商城页面的切入点把2048小程序工程改造成商城小程序核心工作集中在三个方向替换pages/index/index页面的UI把数字格子布局改为商品卡片列表。在app.js的request方法中把apiBaseUrl改成实际部署的接口地址并确保后端接口返回{code:0,data:...}格式。新增pages/cart/cart和pages/order/order页面用这个封装的request方法调用商城接口。// 改造后的商品列表页面核心逻辑 Page({ data: { products: [], loading: false, pageIndex: 1, pageSize: 10 }, onLoad: function () { this.loadProducts(); }, // 调用app.js封装的request方法加载商品列表 loadProducts: function () { if (this.data.loading) return; this.setData({ loading: true }); const app getApp(); app.request( /api/Product/GetPageList, GET, { pageIndex: this.data.pageIndex, pageSize: this.data.pageSize }, (data) { this.setData({ products: data.list, loading: false }); }, () { this.setData({ loading: false }); } ); }, // 下拉刷新 onPullDownRefresh: function () { this.setData({ pageIndex: 1 }); this.loadProducts(); wx.stopPullDownRefresh(); } });改造的难点不在代码量而在后端接口的返回格式统一。商城后端如果返回的是DataTable序列化后的{“list”:[...]}格式前端需要适配如果返回的是老式ASP.NET的JSON序列化格式字段名大小写也要处理好。6.4 本地联调的常用手段小程序不能直接访问localhost因为开发者工具运行在你电脑上localhost指向你电脑但真机预览时localhost指向的是手机自身。所以本地联调时一般通过以下方式把apiBaseUrl改成电脑的局域网IP如http://192.168.1.100:8080手机和电脑连同一WiFi就能访问。使用微信开发者工具自带的“真机调试”功能扫码后手机和电脑建立通道请求会转发到开发者工具开发者工具再访问局域网IP。抓包查看请求参数时老项目里习惯在代码里直接Response.Write返回JSON字符串没有状态码包裹开发小程序端时需要注意这种裸数据格式。从那次把2048小游戏改成商城小程序之后我再拿到类似的项目第一件事就是先把app.js里的请求封装和后端接口文档对照一遍确认参数格式统一再动手改页面。这套商城源码加小程序工程配合起来前后端都能练到作为学习素材确实值。希望这次的拆解对你上手这套源码有帮助。本文还有配套的精品资源点击获取