微信小程序商城源码三端部署与下单链路联调

发布时间:2026/10/27 4:00:47
微信小程序商城源码三端部署与下单链路联调 简介面向高校计算机相关专业学生与初学微信小程序开发的开发者这份基于微信小程序的商城系统毕业设计包提供了可直接用于毕业设计或课程设计的完整方案涵盖商品展示、分类发现、购物车、下单支付、个人中心与消息等常见商城模块。压缩包共68个文件体积约3.31MB以30张png及若干jpg/jpeg界面素材、9个js逻辑脚本、7个wxss样式文件、7个wxml页面结构和7个json配置为主另附使用说明文档前端小程序代码齐备代码含有注释新手也能对照理解页面组织与交互流程。该资源已有1243人学习下载适合希望快速搭建可运行小程序、完成课设答辩或在此基础上二次开发的学生参考也能帮助读者梳理商城类项目的目录结构与功能拆分思路。1. 一套商城源码能不能用先看它把“下单”这条链路走通没有拿到毕业设计压缩包的第一反应多数人是双击解压、找到app.js、点编译然后对着白屏发呆。判断这类微信小程序商城源码值不值得留着做课程资源标准其实很简单数据库、后端接口、小程序页面这三端能不能在本地串成一条完整的下单链路。源码附带的数据库脚本和教程真正省下的不是敲代码的时间而是你已经知道表该怎么连、购物车到订单该怎么算。适合正在准备毕业设计、想自己改出点差异化功能的人如果你只是想要一个能跑起来交差的壳子这套东西也能用但改起来会累一点。2. 三端拉起的顺序目录、运行时与数据库导入解压完先别点编译。这类毕设商城包通常由前端、后端、SQL 脚本、静态资源、说明文档五部分组成起步阶段最容易犯的错误是只改小程序代码后端没起来页面上所有数据都是空的看不出问题在哪。先把三端顺序定下来数据库 → 后端 → 小程序。2.1 五层目录的结构对照与改动清单压缩包解压后常见做法是按下面这张表先扫一遍把“要改的”和“不动的”分开避免一上来就在前端里找后端配置。目录 / 文件内容技术栈是否要改miniprogram/页面、组件、请求封装WXML / WXSS / JS只改utils里的 baseUrlserver/接口服务、路由、控制器Node Express 或 Spring Boot改数据库账号、端口sql/建表脚本、演示数据MySQL改库名、补字符集static/或upload/商品图、头像静态文件保留别删也别挪docs/或说明文档部署步骤、接口清单Markdown / Word只读对照看字段判断后端是哪一套看server/目录就知道有package.json和app.js的是 Node 系有pom.xml和src/main/java的是 Java 系。这一步决定了后面启动命令怎么写别搞混。项目里如果还带了.zip里套.zip的软件/插件目录通常是微信开发者工具的安装包或者数据库客户端工具本身按你机器上已有版本装就行不必照搬。2.2 三个运行时的版本对齐小程序商城对版本其实不挑但有三个点必须对齐否则报错会以各种奇怪的形式出现微信开发者工具的基础库版本、Node 的版本、MySQL 的版本。# 逐项确认版本把输出记下来后面排错要对照 node -v # 建议 14.x / 16.x老项目用 18 以上容易在 node-sass 上翻车 npm -v mysql --version # 5.7 或 8.0 都行但两版的默认认证插件不同参数说明Node 版本低于 12 时很多 ES2018 语法会报错高于 18 时老项目里锁定的node-sass、bcrypt这类原生模块可能装不上报node-gyp编译失败。遇到这种情况用 nvm 切回 16 再npm install比硬改依赖省事得多。MySQL 8 默认用caching_sha2_password认证而老后端连的是mysql模块的低版本接上去会提示Client does not support authentication protocol。解决办法是在 MySQL 里给账号改成兼容插件-- MySQL 8 下让老版本 mysql 客户端能连上 ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 123456; FLUSH PRIVILEGES;这里的123456换成你自己的密码localhost换成实际连接来源。改完立刻重连验证一次别等到后端启动失败再回头找。微信开发者工具这边导入项目时注意 AppID 一栏没有企业主体就选“测试号”能跑通除支付外的所有功能如果源码里写了wx.requestPayment在测试号下也不会真正唤起支付答辩演示时用“模拟支付成功”的按钮或者后端直接把订单状态改掉这是常见做法。2.3 建库、导表与字符集修正数据库是这套毕业设计里最容易被忽略的部分。很多人直接拿图形化工具点“运行 SQL 文件”导完发现商品名里的中文全是问号这就是字符集没定。-- 建库时就定死字符集别用默认的 latin1 CREATE DATABASE mall DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE mall; -- 导表后逐个确认任何一张表不是 utf8mb4 都要改 SELECT TABLE_NAME, TABLE_COLLATION FROM information_schema.TABLES WHERE TABLE_SCHEMA mall;导数据的命令按你手上的工具选一种# 方式一命令行导入适合大文件不会卡界面 mysql -uroot -p123456 --default-character-setutf8mb4 mall sql/mall.sql # 方式二已经进了 mysql 交互界面 # source /绝对路径/sql/mall.sql;逻辑说明--default-character-setutf8mb4是给客户端指定编码不加的话即使库是 utf8mb4导入过程仍可能按 latin1 解析中文变问号就是这么来的。导完执行一次SELECT name FROM goods LIMIT 5;能看到正常中文才算过关。如果表已经建好了但编码不对用下面这段批量修正别一张张手改-- 生成修正语句执行完再跑一遍确认 SELECT CONCAT(ALTER TABLE , TABLE_NAME, CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;) FROM information_schema.TABLES WHERE TABLE_SCHEMA mall AND TABLE_COLLATION utf8mb4_general_ci;把查询结果复制出来执行即可。注意CONVERT TO会重建表数据量大的时候先备份演示用的几十条数据无所谓。库名mall按你实际建的名字替换源码里的连接配置也要同步改成一样的名字这一步对不上后端启动会直接报ER_BAD_DB_ERROR。3. 数据库与后端七张核心表怎么读接口怎么一条条验后端启动成功不等于接口能用。顺序应该是先看表结构确认字段语义再改配置文件连库最后用 curl 把“登录 → 商品列表 → 加购物车 → 下单”这条链路逐条打一遍。这样出问题时能立刻定位是 SQL 错、路由错还是参数错。3.1 七张表的分工与字段陷阱毕设级商城的表设计大同小异照下面这张表对照你手上的 SQL 文件缺哪张、字段名不一致都要在动手前搞清楚。表名作用关键字段容易踩的地方user用户与登录态id、openid、nickname、statusstatus0时接口是否拦截goods商品主表id、name、price、stock、cover、statusprice单位是分还是元goods_category商品分类id、name、sort、pidpid0表示一级分类cart购物车user_id、goods_id、count有没有user_idgoods_id唯一键orders订单主表order_no、user_id、total_fee、status金额是整数分别存小数orders_item订单明细order_id、goods_id、price、countprice要存下单时快照价address收货地址user_id、name、phone、detail、is_default默认地址只能有一条关键在price和total_fee的单位。有的源码用元存DECIMAL(10,2)有的用分存INT两种都能跑但混用必出问题。打开 SQL 文件看建表语句那一行的类型decimal就是元int就是分记住这个结论后面写页面时直接用。orders_item里的price必须是下单那一刻的价格快照。很多毕设代码图省事查订单详情时拿goods.price现算商品一改价历史订单金额就变了答辩时导师问一句“订单金额为什么和下单时不一样”就答不上来。cart表如果没有user_id goods_id的唯一索引重复点“加入购物车”会插进去两条一样的记录页面上数量翻倍。补索引的写法-- 先清掉历史重复数据再加唯一键 DELETE c1 FROM cart c1 INNER JOIN cart c2 WHERE c1.id c2.id AND c1.user_id c2.user_id AND c1.goods_id c2.goods_id; ALTER TABLE cart ADD UNIQUE KEY uk_user_goods (user_id, goods_id);逻辑说明c1.id c2.id保证每组重复只留 id 最小的那条唯一键建好后后端改成INSERT ... ON DUPLICATE KEY UPDATE count count 1就能实现累加而不是新增。3.2 后端配置项与启动顺序Node 系的后端配置文件通常叫config/db.js或.env改的就是库名、账号、密码、端口四项。// server/config/db.js module.exports { host: 127.0.0.1, port: 3306, user: root, password: 123456, // 换成你自己 MySQL 的密码 database: mall, // 必须和 2.3 里建的库名一致 charset: utf8mb4, connectionLimit: 10, // 连接池上限本地演示 10 足够 timezone: 08:00 // 不写这行订单时间会比实际早 8 小时 }参数说明connectionLimit设太小压测或快速翻页会排队设太大本地 MySQL 默认max_connections151几十个并发就撑满。timezone是最容易被忽略的一项不配的话created_at存进去是 UTC页面上显示的订单时间差 8 小时答辩现场被指出来很难解释。启动顺序固定为三步每步都要看到成功日志再往下走# 1. 进后端目录装依赖国内网络换镜像源 cd server npm install --registryhttps://registry.npmmirror.com # 2. 确认能在命令行连上数据库连不上先别启动服务 mysql -h127.0.0.1 -uroot -p123456 -e select 1 from mall.goods limit 1; # 3. 启动看到监听端口日志才算成功 node app.js # 或者 package.json 里配了脚本 npm run start看到server running at http://127.0.0.1:3000这类输出说明进程起来了。这时别急着开小程序先在浏览器里访问一个最简单的接口比如http://127.0.0.1:3000/api/goods/list?page1size2能返回 JSON 才算后端真正可用。返回 500 就看终端日志里的 SQL 报错返回Cannot GET说明路由前缀不对去app.js里找app.use(/api, ...)这一行确认。3.3 用 curl 把登录到下单串一遍页面还没接上之前用命令行工具先把链路打通比在开发者工具里看报错快得多。BASEhttp://127.0.0.1:3000 # 第一步登录拿 token curl -s -X POST $BASE/api/user/login \ -H Content-Type: application/json \ -d {username:demo,password:123456}返回体里通常有{code:0,data:{token:xxx,userInfo:{...}}}。把 token 抠出来存成变量后面的请求都带上TOKEN上一步返回的 token 字符串 # 第二步商品列表看分页参数是否生效 curl -s $BASE/api/goods/list?page1size5 -H Authorization: Bearer $TOKEN # 第三步加购物车注意 goodsId 要换成列表里真实存在的 id curl -s -X POST $BASE/api/cart/add \ -H Content-Type: application/json \ -H Authorization: Bearer $TOKEN \ -d {goodsId:3,count:2} # 第四步下单items 里只传 goodsId 和数量金额由后端算 curl -s -X POST $BASE/api/order/create \ -H Content-Type: application/json \ -H Authorization: Bearer $TOKEN \ -d {addressId:1,items:[{goodsId:3,count:2}]}逻辑说明登录接口返回的token是后面所有请求的凭证放在Authorization头里前缀Bearer加一个空格少了空格后端解析不出来。下单接口刻意只传商品 id 和数量不传价格这是判断一套源码质量的分水岭——价格必须由后端从goods表查出来再算前端传什么价就用什么价的实现改一行请求体就能一块钱买手机。如果下单返回{code:1001,msg:库存不足}去goods表把对应商品的stock改大或者在order/create的控制器里看有没有扣库存的事务没有事务的话并发下会超卖。演示场景用不上并发但答辩时被问到“库存怎么保证不超卖”能说出“用UPDATE goods SET stock stock - ? WHERE id ? AND stock ?靠影响行数判断”就够用了。4. 小程序端接数据请求封装、列表分页与购物车结算后端稳了之后前端只做三件事把 baseUrl 抽出来、把列表和分页接上、把购物车的钱算对。这三块做扎实剩下的页面基本是复制粘贴改字段。4.1 一次封装解决 baseUrl 与 token 两个变量直接在页面里写wx.request是最常见的写法也是最难维护的写法。改一个地址要搜遍全项目token 忘了带就到处 401。// miniprogram/utils/request.js const BASE_URL { dev: http://127.0.0.1:3000, // 开发者工具需勾选“不校验合法域名” lan: http://192.168.1.20:3000, // 真机调试换成电脑的局域网 IP prod: https://api.example.com // 上线用的备案域名 } let env dev function request(options) { const token wx.getStorageSync(token) return new Promise((resolve, reject) { wx.request({ url: BASE_URL[env] options.url, method: options.method || GET, data: options.data || {}, timeout: 8000, header: { Content-Type: application/json, ...(token ? { Authorization: Bearer token } : {}) }, success(res) { // 401 统一处理清 token 并回登录页避免每个页面都写一遍 if (res.statusCode 401) { wx.removeStorageSync(token) wx.navigateTo({ url: /pages/login/login }) return reject(new Error(unauthorized)) } if (res.statusCode 200 res.statusCode 300 res.data.code 0) { resolve(res.data.data) } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }) reject(res.data) } }, fail(err) { wx.showToast({ title: 网络异常, icon: none }) reject(err) } }) }) } module.exports { request, setEnv: e { env e } }参数说明timeout给 8 秒默认 60 秒太长本地后端没起来时页面会一直转圈看不出是死了还是慢。res.data.code 0是这套源码的约定有的项目用code 200改的时候全局搜一下这个判断。Authorization用展开运算符按需拼没登录时不带这个头登录接口本身也不会被拦。真机调试时把env切到lan手机和电脑必须在同一个 Wi-Fi 下IP 用ipconfigWindows或ifconfigmacOS查别用127.0.0.1手机上那个地址指的是手机自己。4.2 商品列表与详情页的取数与缓存列表页的核心就两个状态当前页和有没有更多。写不清楚就是上拉加载重复请求、下拉刷新不重置页码。// miniprogram/pages/goods/list.js const { request } require(../../utils/request) Page({ data: { list: [], page: 1, size: 10, hasMore: true, loading: false }, onLoad() { this.load(true) }, onReachBottom() { this.load(false) }, async load(reset) { // 双保险正在加载不重复请求没有更多也不请求 if (this.data.loading || (!reset !this.data.hasMore)) return this.setData({ loading: true }) try { const page reset ? 1 : this.data.page const rows await request({ url: /api/goods/list?page${page}size${this.data.size} }) this.setData({ list: reset ? rows.list : this.data.list.concat(rows.list), page: page 1, // 返回条数等于每页条数才认为可能还有下一页 hasMore: rows.list.length this.data.size, loading: false }) } catch (e) { this.setData({ loading: false }) } } })逻辑说明reset参数决定是覆盖还是追加下拉刷新传true上拉加载传false。hasMore用“返回条数是否等于 size”判断比后端返回total再算总页数简单缺点是最后一页刚好满页时会多发一次空请求演示场景可以接受。page在成功后自增失败时不动重试还是同一页不会跳页。商品详情页不需要缓存整个对象把 id 传过去重新查一次更稳避免列表页的数据结构和详情页不一致// miniprogram/pages/goods/detail.js Page({ data: { goods: null }, onLoad(options) { this.loadDetail(options.id) }, async loadDetail(id) { // 详情单独查保证库存、价格是最新的 const goods await request({ url: /api/goods/detail?id${id} }) this.setData({ goods }) } })图片地址这块要特别注意数据库里存的如果是/upload/goods/1.jpg这种相对路径页面里必须拼上后端域名BASE_URL.lan goods.cover才能显示直接src{{goods.cover}}会全部变成灰块。4.3 购物车到下单金额只用整数分算金额是这类项目翻车最多的地方。0.1 0.2 ! 0.3不是段子是前端算钱的日常。// miniprogram/pages/cart/index.js const { request } require(../../utils/request) // 金额一律以「分」为单位做整数运算展示时再除 100 function calcTotal(items) { let total 0 items.forEach(it { if (!it.checked) return total it.price * it.count // 整数 × 整数结果仍是整数 }) return total } function fen2yuan(fen) { return (fen / 100).toFixed(2) } Page({ data: { items: [], totalText: 0.00 }, onShow() { this.loadCart() }, async loadCart() { const items await request({ url: /api/cart/list }) items.forEach(it { it.priceText fen2yuan(it.price) }) this.setData({ items, totalText: fen2yuan(calcTotal(items)) }) }, toggle(e) { const idx e.currentTarget.dataset.idx const key items[${idx}].checked this.setData({ [key]: !this.data.items[idx].checked }, () { this.setData({ totalText: fen2yuan(calcTotal(this.data.items)) }) }) }, async submit() { const checked this.data.items.filter(it it.checked) if (!checked.length) return wx.showToast({ title: 请选择商品, icon: none }) // 只传 id 和数量金额由后端重新计算前端算的只用于展示 await request({ url: /api/order/create, method: POST, data: { addressId: wx.getStorageSync(defaultAddressId), items: checked.map(it ({ goodsId: it.goodsId, count: it.count })) } }) wx.redirectTo({ url: /pages/order/list }) } })逻辑说明calcTotal里没有出现任何parseFloat或toFixed参与累加全是整数加乘这是保证金额准确的唯一办法。fen2yuan只负责展示把1999变成19.99。submit里刻意不传totalFee后端会按orders_item的price * count重新汇总前端算错了也不会真扣错钱。如果你的源码是元存DECIMAL把price先乘 100 转成整数再算展示时除回去// 元转分的辅助函数只在取值时用一次 const toFen yuan Math.round(Number(yuan) * 100)Math.round不能省19.99 * 100在 JS 里可能得到1998.9999999999998直接取整就少一分。5. 跑不起来时按这个顺序查五个高频翻车点下面这五条是我在本地反复折腾出来的基本覆盖了这类小程序商城 80% 的启动问题。按顺序查别跳步。5.1 首页白屏、所有请求同时失败现象开发者工具里页面结构出得来但列表是空的控制台一堆request:fail每条请求都红。原因两个可能。一是后端根本没起来二是域名校验没过。开发者工具默认校验合法域名http://127.0.0.1不在白名单里请求直接被拦。解决先curl确认后端能返回再在开发者工具右上角“详情 → 本地设置”里勾上“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。注意这个开关只在开发版和体验版生效正式发布必须用备案过的 HTTPS 域名毕设演示用开发版就行。真机预览时同样要在这个入口里把手机端的调试打开否则手机上的行为跟模拟器不一致会误以为是代码问题。5.2 商品图全是灰块现象接口返回的cover字段有值页面上图片区域一片空白或者显示灰色占位图。原因数据库里存的是相对路径比如/upload/goods/1.jpg小程序把这个字符串当成了完整 URL 去请求域名解析失败。另一种情况是后端static目录没配图片文件本身访问不到。解决页面里拼接域名src{{baseUrl goods.cover}}baseUrl从utils/request里导出。然后单独在浏览器里访问http://127.0.0.1:3000/upload/goods/1.jpg能看到图就说明静态目录正常看不到就去后端找app.use(express.static(upload))这一行路径写错了。图片文件名带中文或空格也会 404导入数据前统一改成英文数字。5.3 登录后点两下又变回未登录现象登录成功进首页正常翻两页或者从详情返回后再调接口就 401页面跳回登录。原因token 存的位置不对或者后端返回的字段名和前端取的字段名不一致。常见的是后端返回data.token前端写成res.data.data.token多取了一层存进去的是undefinedwx.setStorageSync(token, undefined)不报错但取出来是空字符串。解决登录成功后先把返回体打印出来看清楚 token 在第几层再决定怎么存const res await request({ url: /api/user/login, method: POST, data: form }) // 用具体路径取别用可选链一路兜底取错了要能立刻发现 if (!res.token) throw new Error(登录返回里没有 token) wx.setStorageSync(token, res.token)另外检查request封装里读 token 的 key 和存的 key 是否完全一致token和Token是两个 key。5.4 订单金额差一分钱现象购物车里显示19.99下单后订单详情显示19.98或者20.00。原因前端用浮点数算完传给后端后端又用DECIMAL存中间经历了一次舍入两边各按各的规则取整就差一分。解决统一单位要么全用分整数要么全用元decimal 字符串。这条链路里最稳的做法是前端只做展示计算传给后端的永远只有goodsId和count金额由后端在事务里算。如果后端代码已经写成了接收totalFee把它改成从orders_item汇总改动不大但能根治。5.5 模拟器正常真机一片空白现象开发者工具里怎么点都没问题扫码真机预览页面能打开但没数据。原因BASE_URL还是127.0.0.1手机访问自己的回环地址当然什么都没有或者手机和电脑不在同一网段公司 Wi-Fi 常开 AP 隔离。解决把env切成lanIP 用电脑的真实局域网地址命令行ping一下能不能通。还不行就开手机热点让电脑连两者在同一网段概率最高。真机调试时也可以在开发者工具里点“真机调试”它会自动做一层转发比手改 IP 稳。6. 答辩演示前的加固三道保险和一个自检脚本演示环境和开发环境是两回事。你本地跑通不代表答辩教室的电脑能跑通也不代表投影仪分辨率下页面布局没塌。我一般会给这类项目上三道保险。第一道是数据兜底。提前把演示要用的数据固定下来至少 3 个分类、12 个商品、2 个测试账号、1 条默认地址。写成一个demo.sql出问题的时候一条命令恢复# 演示前 10 分钟数据被点乱了就重跑一遍 mysql -uroot -p123456 --default-character-setutf8mb4 mall sql/demo.sql第二道是链路自检。把第 3 章那几条 curl 固化成脚本一次性跑完任何一步失败立刻能看出来#!/usr/bin/env bash # check.sh —— 答辩前跑一遍全绿再上场 BASEhttp://127.0.0.1:3000 curl -sf $BASE/api/goods/list?page1size2 /dev/null || { echo 商品接口不通; exit 1; } TOKEN$(curl -s -X POST $BASE/api/user/login \ -H Content-Type: application/json \ -d {username:demo,password:123456} \ | sed -n s/.*token:\([^]*\).*/\1/p) [ -n $TOKEN ] || { echo 登录没返回 token; exit 1; } curl -sf -X POST $BASE/api/order/create \ -H Content-Type: application/json \ -H Authorization: Bearer $TOKEN \ -d {addressId:1,items:[{goodsId:3,count:1}]} /dev/null \ || { echo 下单失败; exit 1; } echo 链路正常可以演示第三道是降级预案。教室网络不可控小程序又依赖后端最稳的做法是把整个演示录成一段 3 分钟的屏真出问题时直接放。录之前把浏览器地址栏、终端日志都清干净别把本地路径和密码带进去。演示前 15 分钟的检查清单照着过一遍检查项通过标准不通过怎么办后端进程终端有监听日志重跑node app.js看报错数据库商品表能查出数据重跑demo.sql小程序域名校验已勾选不校验详情 → 本地设置里勾上演示账号能登录且头像正常提前在demo.sql里补图片首页首屏无灰块检查 static 目录和拼接逻辑下单能生成订单号看库存和地址 id 是否存在血泪经验是我在一次演示前把数据库密码改成了更复杂的忘了同步config/db.js前面所有准备都白做。从那以后我每次改任何配置项都强制先跑一遍check.sh再动别的把“改完立刻验”变成肌肉记忆而不是靠记性。这套源码本身不复杂难的是把三端的环境变量、字符集、金额单位这些不起眼的地方一次性对齐对上了就一劳永逸。压缩包里的数据库脚本、部署文档和前端代码是配套的按第 2 章的顺序导一遍、按第 3 章的顺序验一遍剩下的就是改改页面配色和商品文案。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询