简介:这是一套面向高校学生与小程序开发初学者的毕业设计级项目源码,基于微信小程序、NetCore 与 layui 构建多店铺网上购物商城系统,涵盖小程序端、管理后台、插件管理与 WebApi 四大模块,适合需要完整电商实战案例或毕设选题的开发者参考。压缩包共 1915 个文件,约 10.34MB,以 798 个 C# 后端代码、180 个 JavaScript 脚本、148 个 cshtml 视图、42 个 wxml 与 42 个 wxss 小程序页面文件为主,另含数据库脚本、插件源码、配置与静态资源,结构完整。已有 3592 人学习下载。资源提供可在线预览的演示站点与后台账号,附带 urshopdb.sql 数据库文件、dataSettings.json 数据库连接配置及 installedPlugins.json 插件配置说明,并包含项目安装使用文档,便于读者快速还原运行环境、理解多店铺商城的分层架构与插件化设计,也可作为二次开发与功能扩展的起点。
1. 从一份能跑起来的购物小程序源码说起
很多同学拿到「基于微信小程序的在线网上购物商城系统」这个题目时,第一反应是去搜现成源码,结果下载下来一堆压缩包,解压后目录结构看不懂,数据库导入报错,微信开发者工具一打开就是满屏红字。这个标题背后其实包含四样东西:一套小程序前端、一套后端服务、一份数据库脚本、一份安装说明。它解决的核心问题是让一个没有完整项目经验的人,能在本地把「浏览商品、加购物车、下单、查看订单」这条主链路跑通。适合的人群很明确:正在做毕业设计的学生、想练手全栈开发的新手、需要快速搭一个商城 Demo 的开发者。我见过太多人卡在「源码有了但跑不起来」这一步,所以这篇笔记不讲空泛的架构,只讲怎么让这套东西在你机器上活过来,以及哪些参数改错一个就前功尽弃。
2. 商城小程序的技术选型与目录结构拆解
2.1 为什么这类项目普遍采用「小程序原生 + Node 后端 + MySQL」
拿到源码先别急着运行,先看它用了什么技术栈。常见的做法是前端用微信小程序原生语法(WXML + WXSS + JS),后端用 Node.js 的 Express 或 Koa 框架,数据库用 MySQL。这个组合不是随便选的:小程序原生语法对新手最友好,不需要额外学框架;Node 后端和前端同属 JavaScript 生态,调试时不用来回切换语言;MySQL 则是关系型数据库里资料最多、报错最容易搜到答案的。
如果你拿到的源码后端是 Java SpringBoot 或者 PHP,逻辑是一样的,只是启动命令和依赖安装方式不同。判断方法很简单:看根目录有没有package.json,有就是 Node 系;看有没有pom.xml,有就是 Java 系。我一般会先花五分钟把目录结构画出来,再动手装环境,这样后面报错时能快速定位是前端问题还是后端问题。
一个典型的目录结构长这样:
mall-miniprogram/ ├── miniprogram/ # 小程序前端 │ ├── pages/ # 页面:首页、分类、购物车、订单、我的 │ ├── components/ # 公共组件:商品卡片、数量选择器 │ ├── utils/ # 工具函数:请求封装、格式化 │ └── app.js # 全局逻辑,含登录态管理 ├── server/ # 后端服务 │ ├── routes/ # 路由:商品、订单、用户 │ ├── controllers/ # 业务逻辑 │ ├── models/ # 数据库操作 │ └── app.js # 服务入口 ├── sql/ │ └── mall.sql # 数据库建表与初始数据 └── README.md # 安装说明看到这个结构,你心里要清楚:miniprogram目录用微信开发者工具打开,server目录用命令行启动,sql目录里的文件导入数据库。三者缺一不可,只启动其中两个必然报错。
2.2 数据库表设计的三个关键字段别改错
导入数据库之前,先打开mall.sql看一眼表结构。商城类项目核心表通常有:用户表、商品表、分类表、购物车表、订单表、订单详情表。其中三个字段最容易出问题,改错了后面全链路都跑不通。
第一个是商品表的price字段。常见做法是用DECIMAL(10,2)而不是FLOAT,因为浮点数算钱会出现0.1 + 0.2 = 0.30000000000000004这种玄学问题。如果你拿到的源码用了FLOAT,建议手动改成DECIMAL,不然后台下单金额和前端显示对不上。
第二个是订单表的status字段。一般用TINYINT表示状态:0 待付款、1 已付款、2 已发货、3 已完成、4 已取消。这个映射关系必须和前端代码里的判断逻辑一致,否则会出现「付了款但订单显示待付款」的翻车现场。
第三个是用户表的openid字段。小程序登录靠微信返回的 openid 做唯一标识,这个字段必须建唯一索引,长度建议VARCHAR(64)。有些源码这里用了VARCHAR(28),实际 openid 长度可能超出,导致插入失败。
-- 商品表关键字段示例 CREATE TABLE `goods` ( `id` INT NOT NULL AUTO_INCREMENT, `name` VARCHAR(100) NOT NULL COMMENT '商品名称', `price` DECIMAL(10,2) NOT NULL DEFAULT '0.00' COMMENT '价格,禁用FLOAT', `stock` INT NOT NULL DEFAULT '0' COMMENT '库存', `category_id` INT NOT NULL COMMENT '分类ID', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 用户表openid字段 CREATE TABLE `user` ( `id` INT NOT NULL AUTO_INCREMENT, `openid` VARCHAR(64) NOT NULL COMMENT '微信openid', `nickname` VARCHAR(50) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `idx_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;字符集统一用utf8mb4,不要用utf8,因为商品名称里可能有 emoji 或特殊字符,utf8存不进去会报Incorrect string value错误。这个坑我踩过不止一次,导入数据时才发现,回头改表结构很麻烦。
2.3 后端接口的请求封装与跨域处理
小程序前端不能直接连数据库,所有数据都通过后端接口获取。打开miniprogram/utils/下的请求封装文件,通常会看到一个request函数,里面配置了后端的基础地址。这个地址写错,整个小程序就是白屏。
// miniprogram/utils/request.js const BASE_URL = 'http://localhost:3000/api'; // 本地开发地址 function request(options) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'content-type': 'application/json', 'Authorization': wx.getStorageSync('token') || '' // 登录后存的token }, success: (res) => { if (res.statusCode === 200) { resolve(res.data); } else { reject(res.data); } }, fail: reject }); }); } module.exports = { request };这里有两个参数要重点检查。一是BASE_URL,本地开发时后端跑在localhost:3000,但微信开发者工具默认会校验合法域名,你需要在开发者工具右上角「详情」→「本地设置」里勾选「不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书」。不勾这个,请求全部失败,控制台报「不在以下 request 合法域名列表中」。
二是Authorization头。如果后端用了 JWT 做鉴权,登录接口返回的 token 要存到wx.setStorageSync,后续请求带上。有些源码这里写的是token字段名,后端读的却是Authorization,两边对不上就是 401。排查方法:打开浏览器或开发者工具的 Network 面板,看请求头里到底带了什么,再看后端中间件读的是什么。
后端跨域问题也常见。Node 后端如果没配 CORS,小程序请求会被拦截。最简单的做法是在app.js里加一行:
// server/app.js const cors = require('cors'); app.use(cors()); // 开发阶段允许所有跨域,上线前要收紧上线时不能这么写,要指定具体域名,但本地调试阶段先让它跑起来最重要。
3. 本地跑通全链路的完整操作步骤
3.1 环境准备:Node、MySQL、微信开发者工具的版本匹配
动手之前先把三个环境装好。Node.js 建议用 16.x 或 18.x 的 LTS 版本,不要用最新的实验版,有些依赖包在最新版上编译会报node-gyp错误。装完后命令行执行node -v和npm -v确认版本。
MySQL 建议 5.7 或 8.0。8.0 的默认认证插件是caching_sha2_password,而有些 Node 的 MySQL 驱动不支持,会报Client does not support authentication protocol。解决办法是在 MySQL 里执行:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;微信开发者工具去官方下载稳定版即可,安装后用微信扫码登录。注意:开发者工具需要登录才能预览,但不需要小程序账号也能用测试号开发。在「项目」→「新建项目」时选择「测试号」,这样不用注册企业主体也能跑起来。
3.2 导入数据库并修改后端连接配置
打开命令行,进入sql目录,执行导入:
mysql -u root -p mall < mall.sql输入密码后如果没有报错,说明导入成功。可以进 MySQL 确认一下:
USE mall; SHOW TABLES; SELECT COUNT(*) FROM goods;如果goods表有数据,说明初始数据也进去了。接下来改后端数据库配置。打开server目录下的配置文件,通常叫config.js或.env:
// server/config.js module.exports = { db: { host: 'localhost', user: 'root', password: '你的MySQL密码', // 改成自己的 database: 'mall', port: 3306 }, jwtSecret: 'your_secret_key', // 鉴权密钥,随便填但要和登录接口一致 port: 3000 };这里password填错是最常见的启动失败原因,报错信息是Access denied for user 'root'@'localhost'。另外database名字要和导入的库名一致,有人导入时改了库名,后端还连默认的mall,自然连不上。
3.3 启动后端服务并验证接口可用
配置改完后,在server目录下安装依赖并启动:
cd server npm install npm run devnpm install如果卡住,先执行npm config set registry https://registry.npmmirror.com换国内镜像。启动成功后控制台会打印Server running on port 3000之类的信息。
验证接口是否正常,用浏览器或 Postman 访问:
curl http://localhost:3000/api/goods/list如果返回 JSON 格式的商品列表,说明后端和数据库都通了。如果返回Cannot GET /api/goods/list,说明路由没注册;如果返回 500,看后端控制台的错误堆栈,通常是数据库字段名对不上。
3.4 小程序端配置与真机预览
打开微信开发者工具,导入miniprogram目录。工具会自动读取app.json里的页面配置。如果首页白屏,先看控制台报错。常见问题有两个:一是BASE_URL指向的后端没启动,二是app.json里pages数组的第一个页面路径写错。
在开发者工具里点击「编译」,模拟器应该能看到首页商品列表。点击商品进详情,加购物车,去结算,走一遍下单流程。如果某个环节卡住,打开「调试器」→「Network」看请求返回什么。
真机预览需要点击工具栏「预览」,用手机微信扫码。注意:真机上localhost是不通的,因为手机和电脑不在同一个网络环境。解决办法是把BASE_URL改成电脑的局域网 IP,比如http://192.168.1.100:3000/api,并确保手机和电脑连同一个 WiFi。同时后端要监听0.0.0.0而不是127.0.0.1:
app.listen(3000, '0.0.0.0', () => { console.log('Server running on port 3000'); });4. 部署上线前必须处理的配置与安全项
4.1 从测试号切换到正式小程序账号的改动清单
测试号只能本地开发,上线必须用正式小程序账号。切换时需要改三处:第一,app.js里的wx.login换取的 code 要发给自己的后端,后端再用 code + appid + secret 去微信服务器换 openid。appid 和 secret 在微信公众平台「开发管理」里获取。
第二,后端换 openid 的接口要配置正确的 appid 和 secret:
// server/controllers/user.js const axios = require('axios'); async function login(code) { const res = await axios.get('https://api.weixin.qq.com/sns/jscode2session', { params: { appid: '你的appid', secret: '你的secret', js_code: code, grant_type: 'authorization_code' } }); return res.data.openid; }第三,微信公众平台里配置服务器域名。把后端接口域名加到「request 合法域名」列表,必须是 HTTPS。本地开发用的 HTTP 地址上线后全部失效。
4.2 支付功能的接入边界与模拟方案
毕业设计类项目通常不接真实支付,因为微信支付需要企业主体和商户号,个人开发者申请不下来。常见做法是用「模拟支付」:点击支付按钮后直接调后端接口把订单状态改成已付款,前端跳转到「支付成功」页。
如果一定要接真实支付,需要商户号、API 密钥、证书文件,流程复杂且审核周期长。我的建议是毕业设计阶段用模拟支付把订单状态流转跑通即可,答辩时说明「支付模块预留了微信支付接口,因资质原因采用模拟方案」,老师一般都能理解。
模拟支付的后端接口:
// server/controllers/order.js async function mockPay(req, res) { const { orderId } = req.body; // 直接把订单状态改为已付款 await db.query('UPDATE orders SET status = 1 WHERE id = ?', [orderId]); res.json({ code: 0, msg: '支付成功' }); }这个接口没有任何鉴权校验,上线前必须删掉或加权限控制,否则别人可以随意改订单状态。
4.3 上线前的三个必查项
第一,检查所有接口的鉴权。用户相关接口必须校验 token,订单接口必须校验订单归属,不能只靠前端传的 userId 判断。我见过有人直接SELECT * FROM orders WHERE user_id = ?,userId 从请求参数拿,改一下参数就能看别人的订单。
第二,检查数据库密码和密钥有没有硬编码在代码里。上线前把敏感配置抽到环境变量,.env文件加入.gitignore,不要提交到代码仓库。
第三,检查小程序包体积。微信小程序主包不能超过 2MB,总包不能超过 20MB。图片资源建议放 CDN 或后端静态目录,不要全打包进小程序。在开发者工具「详情」→「基本信息」里能看到包体积。
5. 避坑与常见问题排查
5.1 数据库导入报错「Unknown character set」
现象:执行mysql -u root -p mall < mall.sql时提示Unknown character set: 'utf8mb4'。原因是 MySQL 版本太低,5.5 之前不支持 utf8mb4。解决:升级到 5.7 以上,或者把 sql 文件里的utf8mb4全部替换成utf8,但这样商品名里的特殊字符会丢。
5.2 小程序请求报「不在合法域名列表」
现象:开发者工具控制台报request:fail url not in domain list。原因:没勾选「不校验合法域名」。解决:开发者工具右上角「详情」→「本地设置」→ 勾选「不校验合法域名、web-view、TLS 版本以及 HTTPS 证书」。真机预览时这个选项不生效,必须用 HTTPS 域名或局域网 IP。
5.3 后端启动报「Port 3000 is already in use」
现象:npm run dev提示端口被占用。原因:上次启动的进程没关干净,或者别的程序占了 3000 端口。解决:Windows 执行netstat -ano | findstr :3000找到 PID,再taskkill /PID xxx /F;Mac 执行lsof -i :3000然后kill -9 xxx。或者直接改后端端口为 3001,同时改前端BASE_URL。
5.4 登录后购物车数据丢失
现象:用户登录后加购,退出再登录,购物车空了。原因:购物车数据存在前端wx.setStorageSync里,没同步到后端。解决:加购时调后端接口写入cart表,登录时从后端拉取购物车数据。检查cart表的user_id字段是否正确关联。
5.5 订单金额和商品总价对不上
现象:购物车显示总价 99.9,下单后订单金额变成 99.89 或 100。原因:前端用 JavaScript 浮点数计算,后端用 DECIMAL 存储,两边精度不一致。解决:前端计算时用(price * 100).toFixed(0)转成整数分再算,最后除以 100;或者所有金额计算都放后端做,前端只负责展示。
6. 让这套源码真正变成你的东西
跑通只是第一步,答辩时老师一定会问「你做了什么改动」。如果只是原样跑起来,很难拿高分。我的习惯是拿到源码后做三件事:第一,把核心链路的代码逐行读一遍,在关键位置加注释,比如登录态怎么维持、订单状态怎么流转、库存怎么扣减。第二,改一个看得见的功能,比如把首页商品列表改成瀑布流布局,或者加一个「商品搜索」页面。第三,修一个源码里的小 bug,比如购物车数量为 0 时没自动删除,或者订单列表没按时间倒序排列。
这里给一个加搜索功能的思路。后端加一个接口:
// server/routes/goods.js router.get('/search', async (req, res) => { const { keyword } = req.query; const sql = 'SELECT * FROM goods WHERE name LIKE ?'; const results = await db.query(sql, [`%${keyword}%`]); res.json({ code: 0, data: results }); });前端在首页加一个搜索框,输入关键词后调这个接口,把返回结果渲染到列表。这个改动涉及前端页面、请求封装、后端路由、SQL 查询,完整走一遍,你对整个项目的理解会深很多。
验证方法也很简单:打开开发者工具的「Storage」面板,看 token 有没有存进去;打开「Network」面板,看每个请求的参数和返回;打开后端控制台,看 SQL 执行日志。三个面板对着看,任何问题都能定位到具体哪一层。
最后说一个我自己的教训:第一次做这类项目时,我花了两天时间调一个「下单后库存没扣」的问题,最后发现是后端扣库存的 SQL 写在了订单创建之前,订单创建失败时库存已经扣了,但没有事务回滚。后来我养成了一个习惯:凡是涉及多表写入的操作,先想清楚要不要加事务,START TRANSACTION和COMMIT写上去再测。这个习惯帮我省了很多后悔药。希望帮到你。
本文还有配套的精品资源,点击获取