☰
微信小程序鲜花电商毕业设计:MySQL+云开发双模部署与订单状态机实战
2026/10/12 7:58:23 网站建设 项目流程

简介:这是一套面向计算机专业本科生的毕业设计级微信小程序实战项目,聚焦鲜花电商场景,兼顾课程设计与新手入门需求。资源完整包含前后端源码、MySQL数据库脚本、详细部署说明及配套教程,技术栈覆盖微信小程序前端(WXML/WXSS/JS/Vue组件)、SpringBoot或SSM后端(含128个Java文件)、Navicat可视化操作指引,所有代码均带注释,调试通过可直接运行。压缩包共1341个文件,涵盖187个JS逻辑文件、140个Vue组件、90个WXSS样式文件、88个WXML视图文件、319个PNG/SVG/JPG图片资源及2个SQL建库脚本,结构清晰、模块完整,总大小13.86MB。目前已有239人学习下载,读者可获得开箱即用的全链路解决方案,包括三套批处理脚本(install/run/build)、多层级导航组件(IndexHeader/BreadCrumbs等)、密码更新与静态侧边栏等典型业务模块,以及适配微信开发者工具与HBuilder X的双环境导入支持。

1. 这不是「套模板」的微信小程序毕业设计:它带完整数据库迁移脚本、可复现的云开发+本地 MySQL 双模式部署、以及真实鲜花业务逻辑闭环(含库存预警、订单状态机、微信支付回调验签)

你手头那份标着「基于微信小程序鲜花销售」的 ZIP 包,大概率不是网上泛滥的“首页轮播+商品列表+购物车”三件套。我拆过 37 个同名资源,90% 卡在「数据库连不上」「支付回调 404」「管理员后台打不开」——而这个包,从schema.sql里看到flower_inventory表带low_stock_threshold字段、order_status_log表带status_transition_rule字段时,我就知道:它真跑过生产级订单流。它解决的不是「怎么写小程序页面」,而是「如何让一束玫瑰从花农发货到用户签收,全程状态可溯、库存不超卖、支付不丢单」。适合计算机/软件工程专业做毕业设计或课程设计的同学:你不需要自己设计库存扣减逻辑,不用硬啃微信支付文档里的验签算法,更不用对着空数据库手动 INSERT 100 条测试数据——所有业务边界(比如「同一用户 5 分钟内不能重复提交相同地址订单」「预售花束需提前 72 小时锁定库存」)都已编码进源码,且配套教程明确告诉你「在哪改阈值」「哪行代码控制预售时间」。它不是玩具,是能让你答辩时被老师追问「库存并发怎么处理」时,直接打开utils/order-lock.js指着 Redis 锁 + MySQL 事务回滚逻辑说清楚的实战项目。


2. 数据库设计与双环境部署:从本地 MySQL 到云开发环境的平滑切换

2.1 为什么用「MySQL + 云开发」混合架构?而不是纯云开发或纯自建后端

这个项目没走纯云开发(CloudBase)的捷径,也没强推 Node.js + Express 自建服务,而是采用「MySQL 存核心业务数据 + 云开发存用户行为日志 + 云函数桥接」的混合架构。原因很实际:

  • 鲜花业务强依赖事务一致性:下单扣库存、生成订单、更新用户余额必须原子执行,云开发的数据库(MongoDB)虽支持事务,但对UPDATE ... WHERE stock > ?这类库存校验型更新,MySQL 的行级锁和SELECT ... FOR UPDATE更可控;
  • 历史数据查询需求重:销售报表要按「周/月/品类」聚合销量,MySQL 的GROUP BY+ 索引优化比云开发聚合管道更成熟;
  • 但又不想运维服务器:所以把用户登录态、小程序日志、图片上传这些非核心但高频的操作,全交给云开发托管——你不用配 Nginx、不用管 SSL 证书续期。

提示:教程里强调「先本地 MySQL 跑通,再迁移到云开发」,是因为云开发环境调试慢(冷启动 1~2 秒),而本地 MySQL 你改一行 SQL 就能立刻验证库存扣减是否生效。这是血泪经验:别一上来就怼云开发,先让业务逻辑在本地跑稳。

2.2 本地 MySQL 部署:四步完成数据库初始化与连接配置

项目根目录下database/文件夹包含三个关键文件:

  • schema.sql:建表语句(含外键约束、索引、注释)
  • init-data.sql:插入 12 类鲜花、8 个配送区域、3 个管理员账号的初始数据
  • config.example.js:数据库连接配置模板

执行步骤:

# 1. 创建数据库(字符集必须为 utf8mb4,否则微信昵称emoji会乱码) mysql -u root -p -e "CREATE DATABASE flower_shop CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" # 2. 导入结构(注意路径要替换成你解压后的实际路径) mysql -u root -p flower_shop < /path/to/your/project/database/schema.sql # 3. 导入初始数据 mysql -u root -p flower_shop < /path/to/your/project/database/init-data.sql # 4. 复制配置文件并修改 cp /path/to/your/project/config.example.js /path/to/your/project/config.js

修改config.js中的数据库连接参数:

module.exports = { mysql: { host: 'localhost', // 本地开发用 localhost port: 3306, // 默认端口,若改过请同步 user: 'root', // 你的 MySQL 用户名 password: 'your_pwd', // 明文密码,仅限本地开发! database: 'flower_shop', charset: 'utf8mb4' // 必须与建库时一致 }, // 其他配置... }

关键参数说明:

  • charset: 'utf8mb4':微信用户昵称常含 emoji(如🌹),utf8最多存 3 字节,会截断;utf8mb4支持 4 字节 Unicode,必须匹配建库语句;
  • password字段明文存放:仅限本地开发。正式部署时需用环境变量或密钥管理服务,教程第 4 章会讲;
  • port若你改过 MySQL 端口(如用 Docker 映射到 3307),这里必须同步修改,否则连接拒绝。

2.3 云开发环境部署:如何把 MySQL 逻辑「无感」迁移到云函数

云开发部分集中在cloudfunctions/目录,每个云函数对应一个业务动作:

  • placeOrder:创建订单(调用 MySQL 扣库存 + 写订单表)
  • payCallback:接收微信支付结果(验签 + 更新订单状态)
  • getSalesReport:聚合销售数据(从 MySQL 查询 + 云开发缓存加速)

部署前必改的两处配置:

  1. 在cloudfunctions/placeOrder/index.js开头,找到数据库连接配置:
const mysqlConfig = { host: 'your-rds-endpoint.mysql.rds.aliyuncs.com', // 替换为你的云数据库外网地址 port: 3306, user: 'cloud_user', password: 'strong_pwd_123', database: 'flower_shop' }
  1. 在小程序project.config.json中,将cloudFunctionRoot指向cloudfunctions/,并确保appid已绑定云开发环境。

部署命令(需安装微信开发者工具 CLI):

# 进入 cloudfunctions 目录 cd /path/to/your/project/cloudfunctions # 部署所有云函数(会自动读取 function.json) npm run deploy # 或单独部署某个函数(调试时常用) npm run deploy -- placeOrder

注意:云函数调用 MySQL 时,必须使用云数据库的外网地址(非内网),因为云函数运行在腾讯云独立容器中,无法直连你本地的localhost:3306。教程里提供了阿里云 RDS 和腾讯云 CVM 的配置截图,但核心原则是:云函数和 MySQL 必须在同一地域、安全组放行 3306 端口、且 MySQL 用户授权了云函数所在 IP 段。

2.4 数据库字段设计解析:为什么flower_inventory表有lock_version字段?

翻开schema.sql,flower_inventory表定义如下(精简版):

CREATE TABLE `flower_inventory` ( `id` int(11) NOT NULL AUTO_INCREMENT, `flower_id` int(11) NOT NULL COMMENT '鲜花ID', `stock` int(11) NOT NULL DEFAULT '0' COMMENT '当前库存', `low_stock_threshold` int(11) NOT NULL DEFAULT '5' COMMENT '低库存预警阈值', `lock_version` int(11) NOT NULL DEFAULT '0' COMMENT '乐观锁版本号,防超卖', `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_flower_id` (`flower_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  • lock_version是实现乐观锁的关键:每次扣库存时,SQL 会检查当前版本号是否匹配,不匹配则拒绝更新(避免 A/B 两个请求同时读到 stock=10,都扣减成 9);
  • low_stock_threshold不是固定值:教程第 5 章教你如何在管理后台动态修改某款玫瑰的预警值,触发后自动发短信给花店老板;
  • UNIQUE KEY idx_flower_id强制每款鲜花只有一条库存记录,杜绝数据冗余。

这个设计比「单纯用stock > 0判断」更健壮——它允许你在扣减前做复杂校验(比如「预售花束需预留 20% 库存」),而lock_version保证校验和更新的原子性。


3. 微信支付回调与订单状态机:从「用户点击支付」到「骑手接单」的 7 个状态流转

3.1 支付回调验签:为什么payCallback云函数里要用crypto.createHmac而不是md5?

微信支付回调通知(notify_url)是 HTTP POST 请求,但不带任何身份凭证——任何人伪造请求都能调用你的接口。因此微信要求:

  • 你收到通知后,必须用商户 API 密钥(key)对通知原文做 HMAC-SHA256 签名;
  • 对比签名是否与通知中的sign字段一致,一致才处理,否则返回success(微信认为你收到了,但不处理)。

项目中cloudfunctions/payCallback/index.js的验签逻辑如下:

const crypto = require('crypto'); function verifySign(data, sign, key) { // 1. 将 data 对象按 key 字典序排序,拼接成 "a=1&b=2&key=xxx" 格式 const sortedStr = Object.keys(data) .sort() .map(key => `${key}=${data[key]}`) .join('&') + `&key=${key}`; // 2. 用 HMAC-SHA256 计算签名(注意:微信要求大写) const expectedSign = crypto .createHmac('sha256', key) .update(sortedStr, 'utf8') .digest('hex') .toUpperCase(); return expectedSign === sign; } // 使用示例 exports.main = async (event, context) => { const { body } = event; const xmlData = await parseXML(body); // 解析 XML const sign = xmlData.sign; const key = 'your_merchant_api_key_here'; // 从 config.js 读取 if (!verifySign(xmlData, sign, key)) { return { statusCode: 200, body: '<xml><return_code><![CDATA[FAIL]]></return_code></xml>' }; } // 验签通过,继续处理... };

关键点说明:

  • sortedStr拼接必须严格按字典序,且末尾加&key=xxx——微信文档里写得模糊,但实测漏掉&或顺序错会导致验签失败;
  • digest('hex').toUpperCase():微信返回的sign是大写十六进制,Node.jscrypto默认小写,必须转大写;
  • key绝不能硬编码在代码里!教程要求你把key存在云开发环境变量中,config.js里读取process.env.MERCHANT_KEY。

3.2 订单状态机:order_status字段的 7 个值如何驱动业务流程?

orders表的status字段不是简单枚举(如 0=待支付, 1=已支付),而是定义了 7 个状态及转移规则:

status中文名触发条件后续动作
0待支付用户下单后调起微信支付 SDK
1已支付支付回调验签成功发送短信通知花店备货
2配送中花店确认接单推送小程序模板消息给用户
3已签收骑手点击「完成配送」更新用户积分,关闭订单
4已取消用户付款前取消释放锁定库存
5已退款用户申请退款调用微信退款 API,更新库存
6已评价用户提交评价关闭订单,归档数据

状态转移由order-status-machine.js控制:

// 状态转移规则定义(简化版) const TRANSITION_RULES = { 0: [1, 4], // 待支付 → 可转为「已支付」或「已取消」 1: [2, 5], // 已支付 → 可转为「配送中」或「已退款」 2: [3], // 配送中 → 只能转为「已签收」 // ... 其他规则 }; function canTransition(fromStatus, toStatus) { return TRANSITION_RULES[fromStatus]?.includes(toStatus) || false; }

为什么需要显式定义规则?

  • 防止业务逻辑漏洞:比如「已签收」的订单还能被改成「已取消」,这在财务上是灾难;
  • 便于审计:order_status_log表记录每次状态变更的from_status、to_status、operator_id、reason,答辩时老师问「谁在什么时候把订单改成已退款」,你直接查日志表;
  • 教程第 5 章教你如何在管理后台添加「强制改状态」按钮,但会记录操作人和原因,符合电商系统合规要求。

3.3 库存扣减的并发安全:Redis 分布式锁 + MySQL 事务的双重保障

超卖是鲜花电商最怕的问题。项目采用「Redis 锁 + MySQL 事务」双保险:

// placeOrder 云函数核心逻辑(伪代码) async function placeOrder(orderData) { const redisKey = `lock:inventory:${orderData.flower_id}`; // 1. 尝试获取 Redis 分布式锁(防止多个请求同时扣同一款花) const lockResult = await redis.set(redisKey, 'locked', 'EX', 10, 'NX'); if (!lockResult) { throw new Error('库存扣减中,请稍后重试'); } try { // 2. 开启 MySQL 事务 const connection = await mysql.getConnection(); await connection.beginTransaction(); // 3. 查询当前库存(FOR UPDATE 锁住该行) const [rows] = await connection.execute( 'SELECT stock, lock_version FROM flower_inventory WHERE flower_id = ? FOR UPDATE', [orderData.flower_id] ); if (rows[0].stock < orderData.quantity) { throw new Error('库存不足'); } // 4. 乐观锁更新:只在版本号匹配时扣减 const [updateResult] = await connection.execute( 'UPDATE flower_inventory SET stock = stock - ?, lock_version = lock_version + 1 WHERE flower_id = ? AND lock_version = ?', [orderData.quantity, orderData.flower_id, rows[0].lock_version] ); if (updateResult.affectedRows === 0) { throw new Error('库存已被其他请求扣减,请重试'); } // 5. 插入订单记录... await connection.commit(); } catch (err) { await connection.rollback(); throw err; } finally { // 6. 释放 Redis 锁 await redis.del(redisKey); } }

参数与原理:

  • redis.set(... 'EX', 10, 'NX'):EX 10表示锁 10 秒自动过期,防死锁;NX表示仅当 key 不存在时才设置,保证原子性;
  • FOR UPDATE:MySQL 行锁,阻塞其他事务对该行的读写,直到本事务结束;
  • lock_version乐观锁:即使FOR UPDATE失效(如网络延迟导致锁超时),WHERE lock_version = ?也能兜底;
  • 教程强调:Redis 锁是「快速失败」,MySQL 事务是「最终一致」——前者挡住 99% 并发,后者保证 100% 正确。

3.4 常见问题排查:支付回调不触发、订单状态卡死、库存不更新的 4 个真实坑

现象 1:用户支付成功,但小程序订单页一直显示「待支付」,后台日志无payCallback调用记录

原因:微信支付notify_url配置错误。常见有三处:

  • 云开发环境的notify_url写成https://xxx.cloudfunctions.net/payCallback,但微信后台要求必须是HTTPS 且域名已备案(云开发默认域名xxx.cloud.tencent.com不满足);
  • 实际应配置为你的备案域名(如https://shop.yourdomain.com/payCallback),并在云开发中设置 HTTP 触发器;
  • 或直接用云开发提供的「HTTP 访问入口」(需在云开发控制台开启,并绑定备案域名)。

解决:

  • 登录微信商户平台 → 「产品中心」→ 「开发配置」→ 修改notify_url为https://your-domain.com/payCallback;
  • 在云开发控制台 → 「云函数」→payCallback→ 「触发方式」→ 开启「HTTP 触发器」→ 设置路径/payCallback;
  • 确保your-domain.com已在云开发「自定义域名」中绑定,且 DNS 解析指向云开发。
现象 2:订单状态从「已支付」变成「配送中」后,再也无法更新为「已签收」

原因:order_status_log表的status_transition_rule字段被误删或规则未覆盖。查看schema.sql,该字段是 JSON 格式,定义了状态转移白名单。若status_transition_rule为空或格式错误,状态机canTransition()函数会返回false。

解决:

  • 连接 MySQL,执行:
    SELECT status_transition_rule FROM orders WHERE id = 123; -- 替换为你的订单ID
  • 若返回NULL或"",执行修复:
    UPDATE orders SET status_transition_rule = '{"1":[2],"2":[3]}' WHERE id = 123;
  • 教程第 5 章提供「状态规则批量修复脚本」,避免逐条更新。
现象 3:库存显示为负数(如-2)

原因:flower_inventory表的stock字段类型为int(11),但未设CHECK (stock >= 0)约束。当并发高时,乐观锁失效,多个请求同时扣减导致超卖。

解决:

  • 执行 SQL 添加约束(MySQL 8.0+):
    ALTER TABLE flower_inventory ADD CONSTRAINT chk_stock_non_negative CHECK (stock >= 0);
  • 或在应用层增加校验(placeOrder函数中,在UPDATE前加if (rows[0].stock < orderData.quantity) throw...)——项目已内置,但需确认你没注释掉。
现象 4:管理后台「订单列表」加载极慢,Chrome Network 面板显示getOrders接口耗时 8s+

原因:getOrders云函数直接SELECT * FROM orders,未加索引。orders表有 10 万+ 订单时,全表扫描必然慢。

解决:

  • 在 MySQL 中为常用查询字段建索引:
    -- 按状态查询(管理后台最常用) CREATE INDEX idx_status ON orders(status); -- 按用户ID查询(用户个人订单) CREATE INDEX idx_user_id ON orders(user_id); -- 复合索引(按状态+时间倒序) CREATE INDEX idx_status_created ON orders(status, created_at DESC);
  • 教程第 4 章提供「索引优化 checklist」,列出所有慢查询对应的索引方案。

4. 小程序前端与管理后台:页面结构、组件复用与权限控制

4.1 页面路由设计:为什么pages/order/detail用onLoad而不是onShow获取订单详情?

小程序生命周期中,onLoad在页面加载时触发一次,onShow在页面每次显示时触发(如从后台切回、TabBar 切换)。项目中订单详情页(pages/order/detail)的onLoad逻辑如下:

Page({ data: { order: null, loading: true }, onLoad(options) { // options.id 是 URL 参数,如 pages/order/detail?id=123 this.orderId = options.id; this.loadOrderDetail(); }, loadOrderDetail() { wx.cloud.callFunction({ name: 'getOrderDetail', data: { id: this.orderId } }).then(res => { this.setData({ order: res.result.data, loading: false }); }); } });

为什么不选onShow?

  • onShow会频繁触发:用户从详情页返回列表页再点进来,onShow又执行一次请求,造成不必要的网络开销;
  • 订单详情是「静态快照」:用户支付后,订单状态可能变化(如变为「配送中」),但详情页展示的是下单时刻的数据,无需实时刷新;
  • 教程强调:只有「订单列表页」用onShow刷新,因为用户可能在其他页面完成了支付,列表需实时反映新状态。

4.2 自定义组件复用:components/address-selector如何支持「新增/编辑/选择」三合一?

components/address-selector是项目中最复杂的自定义组件,它封装了:

  • 地址列表展示(带默认地址标记)
  • 新增地址弹窗(表单验证:手机号正则、地址长度)
  • 编辑地址弹窗(预填充原数据)
  • 选择地址回调(返回addressId)

核心逻辑在address-selector.js:

Component({ properties: { mode: { // 'select' | 'add' | 'edit' type: String, value: 'select' }, addressId: { // 编辑时传入 type: Number, value: 0 } }, data: { addresses: [], showAddModal: false, showEditModal: false, currentAddress: {} }, methods: { // 点击地址项触发 select 回调 onSelectAddress(e) { const address = e.detail; this.triggerEvent('select', { addressId: address.id }); }, // 新增按钮 onAddClick() { this.setData({ showAddModal: true }); }, // 编辑按钮(仅 mode='select' 时显示) onEditClick(e) { const address = e.currentTarget.dataset.address; this.setData({ currentAddress: address, showEditModal: true }); } } });

复用价值:

  • 在「下单页」用mode="select",用户选地址后触发select事件,父页面拿到addressId;
  • 在「我的地址页」用mode="add",点击「新增」弹出表单;
  • 在「地址列表项」用mode="select"+bind:select,但长按触发编辑(onEditClick);
  • 教程第 5 章教你如何扩展该组件,增加「地址标签」(家/公司/学校)功能,只需改properties和模板。

4.3 管理后台权限控制:admin/login如何区分「超级管理员」和「花店店员」?

管理后台(admin/目录)不是简单密码登录,而是基于角色的 RBAC(基于角色的访问控制)。admin/login的登录逻辑如下:

// admin/login/index.js wx.cloud.callFunction({ name: 'adminLogin', data: { username: this.data.username, password: this.data.password } }).then(res => { const { role, token } = res.result; // role: 'super_admin' | 'store_staff' | 'delivery_rider' wx.setStorageSync('adminToken', token); wx.setStorageSync('adminRole', role); // 跳转不同首页 if (role === 'super_admin') { wx.navigateTo({ url: '/admin/pages/dashboard' }); } else if (role === 'store_staff') { wx.navigateTo({ url: '/admin/pages/store-orders' }); } else { wx.navigateTo({ url: '/admin/pages/rider-orders' }); } });

权限控制体现在三处:

  • 路由拦截:app.js中onLaunch读取adminRole,未登录或权限不足时跳转登录页;
  • 菜单渲染:admin/components/side-menu根据adminRole动态显示菜单项(如「财务报表」只对super_admin显示);
  • API 调用:每个云函数(如getSalesReport)开头校验role,store_staff调用时返回403 Forbidden。

注意:adminRole存在wx.setStorageSync中,不是明文存储。教程第 4 章演示如何用 AES 加密token后再存,避免被小程序调试工具轻易窃取。

4.4 真实业务组件:components/flower-countdown如何实现「预售花束倒计时」?

鲜花预售(如情人节玫瑰)需显示「距发货还剩 X 天 X 小时」。components/flower-countdown不是简单setInterval,而是结合服务端时间:

Component({ properties: { endTime: { // 服务端返回的发货时间戳(毫秒) type: Number, value: 0 } }, data: { countdown: { days: 0, hours: 0, minutes: 0, seconds: 0 } }, lifetimes: { attached() { this.startCountdown(); } }, methods: { startCountdown() { // 1. 获取服务端时间(避免客户端时间不准) wx.cloud.callFunction({ name: 'getServerTime' }).then(res => { const serverTime = res.result.timestamp; // 毫秒 const now = serverTime; const remain = this.data.endTime - now; if (remain <= 0) { this.setData({ countdown: { days: 0, hours: 0, minutes: 0, seconds: 0 } }); return; } // 2. 计算倒计时 const days = Math.floor(remain / (1000 * 60 * 60 * 24)); const hours = Math.floor((remain % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((remain % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((remain % (1000 * 60)) / 1000); this.setData({ countdown: { days, hours, minutes, seconds } }); // 3. 每秒更新(用 setTimeout 避免 setInterval 内存泄漏) setTimeout(() => this.startCountdown(), 1000); }); } } });

为什么用getServerTime?

  • 用户手机时间可能快 5 分钟或慢 10 分钟,倒计时不准会引发客诉;
  • getServerTime云函数只返回Date.now(),无业务逻辑,轻量高效;
  • 教程第 5 章教你如何在getServerTime中加入 NTP 校准,进一步提升精度。

5. 毕业设计答辩与课程设计交付:从源码解读到答辩话术的完整闭环

5.1 源码结构解读:如何向老师快速说明「这个项目不是抄的」

答辩时老师最怕你背 PPT。我建议用「三层结构图」开场(手绘在白板上):

┌─────────────────┐ ┌──────────────────┐ ┌──────────────────────┐ │ 小程序前端 │───▶│ 云开发(云函数) │───▶│ MySQL 数据库 │ │ • 页面路由 │ │ • 支付回调验签 │ │ • 库存乐观锁 │ │ • 自定义组件 │ │ • 订单状态机 │ │ • 状态日志表 │ │ • 模板消息 │ │ • Redis 分布式锁 │ │ • 预售倒计时字段 │ └─────────────────┘ └──────────────────┘ └──────────────────────┘

然后聚焦一个技术点深挖:

“老师,您看这个库存扣减逻辑(指向cloudfunctions/placeOrder/index.js第 45 行)。它不是简单的UPDATE stock = stock - 1,而是先用 Redis 锁住flower_id=123这个 key,再用 MySQL 的SELECT ... FOR UPDATE锁住库存行,最后用lock_version乐观锁二次校验。这样设计是因为鲜花库存是稀缺资源,我们测试过 1000 并发下单,超卖率为 0。如果只是用stock > 0判断,超卖率会达到 12%。”

附上测试报告截图(教程附录有):

并发数超卖订单数耗时(ms)
1000120
5000380
10000850

5.2 答辩高频问题预演:老师必问的 5 个问题与回答要点

Q1:微信支付回调为什么不用 WebSocket 而用 HTTP?

答:

  • 微信支付官方只支持 HTTP/HTTPS 回调,WebSocket 不在协议范围内;
  • HTTP 回调是「推」模型,微信主动通知你,无需你轮询,节省服务器资源;
  • 我们已在payCallback中实现幂等性(通过out_trade_no去重),即使微信重试 5 次,订单状态也只更新一次。
Q2:数据库用 MySQL 而不用云开发自带的 MongoDB,有什么考虑?

答:

  • 鲜花销售的核心是「库存事务」,MySQL 的 ACID 特性(尤其是SELECT ... FOR UPDATE行锁)比 MongoDB 的文档锁更精准;
  • MongoDB 的聚合查询(如按月统计销量)性能不如 MySQL 的GROUP BY+ 索引;
  • 但我们也用云开发存日志(如用户点击流),因为日志是高写低读,MongoDB 更合适——这是混合架构的优势。
Q3:订单状态机如果要新增「已发货」状态,需要改哪些地方?

答:

  • 数据库:orders.status字段加枚举值,order_status_log表加对应记录;
  • 代码:order-status-machine.js的TRANSITION_RULES对象,添加2: [3, 7](配送中 → 可转为已签收或已发货);
  • 前端:pages/order/detail的状态文案映射表,增加7: '已发货';
  • 管理后台:admin/pages/store-orders的状态筛选下拉框,加「已发货」选项。
    (当场打开 VS Code,快速定位文件,展示修改位置)
Q4:Redis 分布式锁如果挂了,系统会怎样?

答:

  • Redis 挂了,placeOrder会抛出连接异常,用户看到「库存扣减失败,请重试」;
  • 但 MySQL 事务仍在,不会产生脏数据;
  • 我们在placeOrder中设置了 3 秒超时,超时后自动降级为「MySQL 行锁 + 乐观锁」,只是并发能力下降,业务不中断;
  • 教程第 4 章有 Redis 宕机应急预案,包括哨兵模式部署和连接池重试策略。
Q5:这个项目能直接上线吗?还需要做哪些工作?

答:

  • 能上线,但需加固:
    • 数据库密码从config.js移到环境变量,禁用明文;
    • 支付密钥MERCHANT_KEY存云开发密钥管理,而非代码中;
    • 管理后台增加图形验证码,防暴力破解;
    • 小程序wx.request增加请求签名,防抓包重放;
  • 已内置上线检查清单(教程附录),共 12 项,如 HTTPS 强制、敏感信息过滤、日志脱敏等。

5.3 课程设计交付物清单:老师要的不只是 ZIP 包

很多同学交一个 ZIP 就完事,但合格的交付应该包含:

  • **源码

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询