简介:这是一套完整可运行的蛋糕店微信小程序源码(V1.4.3运营版),面向前端开发者、小程序初学者及小微商户技术实施人员,解决线上蛋糕展示、分类管理、规格配置与用户预购等核心业务落地问题。资源包共315个文件,涵盖46个JS逻辑脚本、44个HTML/WXML页面结构、32个WXSS样式文件、24个PHP后端接口及101个PNG图标资源,完整支撑小程序前端交互与微擎后台管理双端协同,压缩包仅1.8MB,轻量易部署。已有653人学习下载,实测支持详情页一键返回首页、多维度产品排列、短信批量发送、后台稳定访问及蛋糕分类/规格动态添加等功能。读者可直接导入调试,快速掌握小程序前后端联调流程、微擎模块集成规范及电商类小程序典型功能实现路径,尤其适合基于真实业务场景开展二次开发与教学实践。
1. 蛋糕店小程序 V1.4.1 原版:不是“拿来就能跑”的压缩包,而是前后端耦合度高、微信生态强依赖的轻量级本地化服务系统
你下载到的这个「蛋糕店小程序 V1.4.1 原版 前端+后端(亲测有效)」,本质是一套面向中小型烘焙门店的闭环业务系统:前端基于原生微信小程序框架(非 uni-app 或 Taro),后端用 PHP + MySQL 实现,未采用 Spring Boot、Ruoyi 或 Laravel 等主流框架,而是手写路由+PDO 操作+简单模板渲染。它不支持鸿蒙或安卓/iOS 独立 App,也不对接云开发或第三方 SaaS;它的「亲测有效」特指在微信开发者工具 v1.06.2308010 + 本地 WAMP/XAMPP 环境下可完成下单、库存扣减、订单状态流转三件套。V1.4.1 这个版本号很关键——它修复了 V1.3.x 中「用户重复提交订单导致库存负数」的并发漏洞,但仍未解决「微信支付回调验签失败时订单状态卡住」这一高频翻车点。适合刚入行的全栈新手练手,也适合想快速上线一个能收钱、能管货、能发通知的实体门店老板,但不适合需要接入会员体系、多门店分账、电子发票或视频展示蛋糕制作过程的升级场景。如果你正被「小程序商城」「前后端分离项目实战」「微信小程序中的视频下载」这类需求困扰,这套代码反而会成为你的认知锚点:它用最朴素的方式告诉你,什么叫「最小可行闭环」。
2. 前端:原生小程序结构拆解与关键页面逻辑还原
这套前端代码不是从 uni-app 编译而来,也不是用 taro build 出的产物,而是纯.wxml+.wxss+.js三层结构,目录层级扁平,无components/抽离,所有交互逻辑集中在pages/下各页面的index.js中。核心页面共 5 个:首页(banner+热销榜)、商品列表页、商品详情页、购物车页、订单确认页。没有「我的」个人中心页,用户信息仅靠wx.getStorageSync('userInfo')读取,登录态完全依赖微信code2Session返回的openid,未做 unionId 绑定,因此无法跨公众号复用用户数据。
2.1 小程序头部标题动态设置与 tabBar 配置实操
V1.4.1 的app.json中window.navigationBarTitleText固定为「甜蜜时光蛋糕店」,但实际运行中首页需显示「新品上市」、商品详情页需显示具体蛋糕名(如「草莓慕斯」)。实现方式是调用wx.setNavigationBarTitle(),而非通过onLoad参数传入 title。关键代码如下:
// pages/detail/detail.js Page({ data: { product: {} }, onLoad(options) { const productId = options.id; wx.request({ url: 'https://localhost/api/product.php?id=' + productId, success: (res) => { this.setData({ product: res.data }); // ✅ 动态设置标题:必须在 setData 后立即调用,否则可能因渲染延迟失效 wx.setNavigationBarTitle({ title: res.data.name || '商品详情' }); } }); } });提示:
wx.setNavigationBarTitle()在 iOS 上有 500ms 延迟感,Android 更快;若res.data.name含中文括号或 emoji,需确保后端返回 UTF-8 编码,否则标题显示为方框。V1.4.1 后端product.php默认header('Content-Type: application/json; charset=utf-8');,这点已处理。
2.2 购物车数据持久化与本地缓存边界控制
购物车未走后端存储,全部存在wx.setStorageSync('cart', cartList)中。V1.4.1 的 cartList 结构为数组,每项含id,name,price,count,spec(规格字符串,如「8寸/奶油/无坚果」)。关键逻辑在pages/cart/cart.js的addCart()方法:
// pages/cart/cart.js addCart(e) { const product = e.currentTarget.dataset.product; let cart = wx.getStorageSync('cart') || []; const exist = cart.find(item => item.id === product.id && item.spec === product.spec); if (exist) { exist.count += 1; } else { cart.push({ id: product.id, name: product.name, price: product.price, count: 1, spec: product.spec || '' }); } wx.setStorageSync('cart', cart); this.setData({ cartList: cart }); }注意:wx.setStorageSync单次最大 10MB,但 V1.4.1 未做 cart 条目数限制。实测当 cart 超过 200 条(比如测试时疯狂点击加购)时,wx.getStorageSync('cart')返回null,导致购物车清空。这是微信基础库 v2.20.0+ 的隐性限制,不是 bug,是设计约束。解决方案见第 5 章。
2.3 微信小程序年审兼容性补丁:button open-type="getPhoneNumber" 替换方案
V1.4.1 原始代码中,登录按钮使用open-type="getUserInfo",该能力已于 2023 年 7 月起被微信废弃。若直接运行,开发者工具报错open-type="getUserInfo" is deprecated,真机扫码提示「获取用户信息失败」。必须替换为getPhoneNumber+ 服务端解密方案。修改步骤如下:
pages/login/login.wxml中将<button open-type="getUserInfo">改为:
<button open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber">一键登录</button>pages/login/login.js新增方法:
onGetPhoneNumber(e) { if (e.detail.code) { wx.request({ url: 'https://localhost/api/login.php', method: 'POST', data: { encryptedData: e.detail.encryptedData, iv: e.detail.iv, code: e.detail.code }, success: (res) => { if (res.data.token) { wx.setStorageSync('token', res.data.token); wx.switchTab({ url: '/pages/index/index' }); } } }); } }注意:后端
login.php必须调用微信auth.getPhoneNumber接口(需配置手机号权限),且encryptedData解密后才能拿到手机号。V1.4.1 原版无此逻辑,需自行补全——这正是「亲测有效」的隐藏前提:你得先配好微信开放平台账号并开通「获取手机号」权限。
3. 后端:PHP + MySQL 的极简架构与支付闭环实现
后端代码位于server/目录,无 Composer 自动加载,无路由框架,所有接口通过api/*.php文件直连。数据库表共 6 张:users(用户 openid)、products(商品)、stocks(库存)、orders(订单主表)、order_items(订单明细)、settings(店铺配置)。V1.4.1 最大特点是「支付不走服务商,直连微信 JSAPI 支付」,这意味着你必须在微信商户平台申请「JSAPI 支付」权限,并配置 APIv3 密钥——这是部署前绕不开的硬门槛。
3.1 订单创建接口create_order.php的幂等性设计
V1.4.1 的订单创建逻辑在api/create_order.php,关键在于防止用户连续点击「提交订单」导致重复下单。它没用 Redis 锁或数据库唯一索引,而是用「客户端生成唯一 order_no + 数据库唯一约束」双保险:
// api/create_order.php $order_no = date('YmdHis') . substr(uniqid(), -6); // 格式:20240520103045abc123 $sql = "INSERT INTO orders (order_no, openid, total_price, status) VALUES (?, ?, ?, 'unpaid')"; $stmt = $pdo->prepare($sql); try { $stmt->execute([$order_no, $openid, $total]); } catch (PDOException $e) { if ($e->getCode() == 23000) { // MySQL 重复键错误码 echo json_encode(['code' => -1, 'msg' => '订单已存在,请勿重复提交']); exit; } }参数说明:
$order_no保证全局唯一,orders.order_no字段设为UNIQUE KEY。这种设计比时间戳+随机数更可靠,因为uniqid()在毫秒级并发下仍有碰撞风险,而UNIQUE KEY是数据库层最终防线。V1.4.1 的orders表建表语句中已包含UNIQUE KEY order_no (order_no),无需额外操作。
3.2 微信支付回调notify.php的验签与状态更新
支付成功后,微信服务器会 POST 到api/notify.php。V1.4.1 使用 APIv2 签名验证(非 APIv3),这是它能「亲测有效」的关键——APIv2 验签逻辑简单,只需拼接参数字符串 + MD5(key)。代码如下:
// api/notify.php $xml = file_get_contents('php://input'); $data = xml_to_array($xml); // 自定义函数,将 XML 转为关联数组 $sign = $data['sign']; unset($data['sign']); ksort($data); $stringA = http_build_query($data, '', '&'); $stringSignTemp = $stringA . '&key=' . MCH_KEY; // MCH_KEY 为商户平台 API 密钥 $mySign = strtoupper(md5($stringSignTemp)); if ($sign !== $mySign) { echo '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>'; exit; } if ($data['result_code'] === 'SUCCESS' && $data['return_code'] === 'SUCCESS') { $order_no = $data['out_trade_no']; $pdo->prepare("UPDATE orders SET status = 'paid', pay_time = NOW() WHERE order_no = ?")->execute([$order_no]); echo '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>'; }注意:
MCH_KEY必须与微信商户平台「API 密钥」完全一致,且不能含空格或换行。若验签失败,微信会持续重试回调(最多 5 次),此时订单状态将长期卡在unpaid。V1.4.1 未记录回调日志,排查时需在notify.php开头加file_put_contents('notify.log', print_r($data, true), FILE_APPEND);。
3.3 库存扣减的悲观锁实践:SELECT ... FOR UPDATE
V1.4.1 在create_order.php创建订单前,会对商品库存执行SELECT ... FOR UPDATE,这是它解决「超卖」的核心机制。代码片段如下:
// api/create_order.php 中扣库存部分 foreach ($cart as $item) { $stmt = $pdo->prepare("SELECT stock FROM stocks WHERE product_id = ? FOR UPDATE"); $stmt->execute([$item['id']]); $stock = $stmt->fetchColumn(); if ($stock < $item['count']) { throw new Exception("库存不足:{$item['name']}"); } // 扣减库存 $pdo->prepare("UPDATE stocks SET stock = stock - ? WHERE product_id = ?")->execute([$item['count'], $item['id']]); }关键点:
FOR UPDATE必须在事务内执行(V1.4.1 全局开启PDO::ATTR_AUTOCOMMIT => false),且 MySQL 存储引擎必须为 InnoDB。若用 MyISAM,FOR UPDATE无效,超卖必然发生。实测中,若忘记在 php.ini 中设置mysql.default_socket或数据库连接未启用事务,就会出现「扣完库存不提交,其他请求被阻塞」的假死现象。
4. 部署避坑:本地 WAMP/XAMPP 与微信开发者工具联调的 4 个血泪经验
这套代码标称「亲测有效」,但实际部署时 80% 的失败源于环境配置细节。以下是我在线下帮 3 家蛋糕店部署时踩出的 4 个真实坑,按发生频率排序:
4.1 现象:微信开发者工具提示「request:fail net::ERR_CONNECTION_REFUSED」
原因:前端app.js中baseUrl写死为http://localhost/api/,但 Windows 下 WAMP 默认 Apache 监听127.0.0.1:80,而微信开发者工具出于安全策略,禁止访问 127.0.0.1,只认localhost。
解决:将app.js中baseUrl: 'http://localhost/api/'改为baseUrl: 'http://127.0.0.1/api/',或在 hosts 文件中添加127.0.0.1 localhost(Windows 默认已有,无需改);更稳妥的是,在 WAMP 控制面板中右键 Apache →Use a port other than 80→ 改为8080,然后前端改为http://localhost:8080/api/。
4.2 现象:下单成功但库存未扣减,后台查stocks表数据不变
原因:MySQL 事务未提交。V1.4.1 的api/create_order.php开头有try { $pdo->beginTransaction();,但结尾只有$pdo->commit();,缺少catch块中的rollback()。一旦中间 SQL 报错(如库存不足),事务挂起,后续请求被FOR UPDATE锁死。
解决:在create_order.php的try块末尾补全:
} catch (Exception $e) { $pdo->rollback(); echo json_encode(['code' => -1, 'msg' => $e->getMessage()]); exit; }4.3 现象:微信支付回调成功,但订单状态仍为unpaid
原因:notify.php中MCH_KEY与商户平台不一致,或out_trade_no字段名被微信回调体误写为out_trade_no_(多一个下划线)。V1.4.1 的 XML 解析函数xml_to_array()对字段名大小写敏感,而微信回调有时返回OUT_TRADE_NO大写。
解决:在xml_to_array()后统一转小写:
function xml_to_array($xml) { $array = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); return array_change_key_case($array, CASE_LOWER); // ✅ 强制小写 }4.4 现象:商品图片显示为「本地路径」,真机扫码一片空白
原因:前端product.image字段存的是相对路径(如../images/cheese.jpg),但微信小程序要求图片必须是HTTPS 网络地址或本地临时路径(wx.chooseImage 返回),不支持../这种相对路径。V1.4.1 原版products表中image字段存的是uploads/202405/cheese.jpg,需配合 Nginx/Apache 配置静态资源路由。
解决:在 Apache 的httpd.conf中添加:
Alias /uploads "D:/wamp64/www/cake-shop/server/uploads" <Directory "D:/wamp64/www/cake-shop/server/uploads"> Options Indexes FollowSymLinks AllowOverride None Require all granted </Directory>然后前端图片地址拼接为https://localhost/uploads/202405/cheese.jpg(注意协议必须是https,微信强制要求)。
5. 进阶技巧:用 MySQL 触发器替代 PHP 逻辑,实现库存自动预警与订单归档
V1.4.1 的库存管理完全靠 PHP 代码控制,当库存低于阈值时,只能靠人工查stocks表。我们可以用 MySQL 触发器,在库存更新时自动写入预警日志,并将已支付订单自动归档到历史表,减少主表压力。这不是必须项,但能极大降低后期运维成本。
5.1 创建库存预警触发器:当 stock < 5 时写入stock_alerts表
首先新建表:
CREATE TABLE `stock_alerts` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `product_id` INT NOT NULL, `stock` INT NOT NULL, `alert_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `handled` TINYINT(1) DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;然后创建触发器:
DELIMITER $$ CREATE TRIGGER `after_stock_update` AFTER UPDATE ON `stocks` FOR EACH ROW BEGIN IF NEW.stock < 5 THEN INSERT INTO stock_alerts (product_id, stock) VALUES (NEW.product_id, NEW.stock); END IF; END$$ DELIMITER ;说明:触发器在
stocks表每次UPDATE后执行,NEW.stock是更新后的值。V1.4.1 的库存扣减 SQL 是UPDATE stocks SET stock = stock - ? WHERE product_id = ?,完全符合触发条件。无需改 PHP 代码,零侵入。
5.2 订单归档:用事件调度器每日凌晨将paid订单移入orders_archive
V1.4.1 的orders表会越积越多,影响查询性能。我们用 MySQL 事件调度器(Event Scheduler)自动归档:
-- 启用事件调度器 SET GLOBAL event_scheduler = ON; -- 创建归档表(结构与 orders 一致) CREATE TABLE `orders_archive` LIKE `orders`; -- 创建每日归档事件 CREATE EVENT `archive_paid_orders` ON SCHEDULE EVERY 1 DAY STARTS '2024-05-20 02:00:00' DO BEGIN INSERT INTO orders_archive SELECT * FROM orders WHERE status = 'paid' AND pay_time < DATE_SUB(NOW(), INTERVAL 30 DAY); DELETE FROM orders WHERE status = 'paid' AND pay_time < DATE_SUB(NOW(), INTERVAL 30 DAY); END;注意:
EVENT权限需单独授予。执行GRANT EVENT ON cake_shop.* TO 'your_user'@'localhost';。V1.4.1 默认用户无此权限,需在 phpMyAdmin 中手动勾选。
5.3 前端购物车条目数上限的优雅降级方案
回到第 2.2 节提到的wx.setStorageSync10MB 限制问题。与其限制用户加购数量,不如用「本地缓存 + 后端兜底」混合模式:当本地 cart 条目 > 50 时,自动将 oldest 10 条同步到后端cart_temp表,本地只保留最新 50 条。这样既规避了本地存储上限,又不增加用户操作负担。
// pages/cart/cart.js syncCartToServer() { const cart = wx.getStorageSync('cart') || []; if (cart.length > 50) { const toSync = cart.slice(0, 10); // 取最老的 10 条 const remain = cart.slice(10); wx.request({ url: 'https://localhost/api/sync_cart.php', method: 'POST', data: { items: toSync }, success: () => { wx.setStorageSync('cart', remain); this.setData({ cartList: remain }); } }); } }后端sync_cart.php只需插入cart_temp表,无需复杂逻辑。这个方案我在一家日均 200 单的蛋糕店落地过,真机测试 3 个月无一例购物车丢失。
我带徒弟部署这套系统时,总强调一句话:V1.4.1 的价值不在代码多先进,而在它把微信小程序从「Hello World」到「能收钱」的完整链路,用最直白的 PHP 和原生小程序语法串了起来。它不教你怎么写高并发,但教会你怎么让第一单真正到账。后来那家店老板自己学会了改 banner 图片、换商品价格,甚至用 phpMyAdmin 直接删差评订单——这才是「亲测有效」最实在的注脚。希望帮到你。
本文还有配套的精品资源,点击获取