
简介面向智慧美容行业打造的微信小程序完整源码及项目说明适合高校计算机相关专业学生、企业开发者和初期项目团队使用覆盖线上客户管理、门店管理、商品管理、项目管理以及在线预约、在线下单、特色项目展示等连锁美容机构核心场景。压缩包共1148个文件大小4.13MB以Java、XML、JS、WXML、WXSS等前后端源码文件为主Java与xml/yml/properties构成后端服务与配置体系wxml/wxss/js实现小程序前端页面与交互json承担数据交换另含sql数据库脚本及bat/sh启动脚本便于本地快速搭建运行环境。全部代码均经过测试运行成功功能正常适合小白实战练习也可直接作为大作业、课程设计、毕业设计或项目立项演示的参照。已有292人学习浏览是学习小程序全栈开发与美容行业业务建模的实用样例。1. 智慧美容小程序源码包里到底有什么先别急着解压它解决的是连锁经营的三个老大难全国连锁美容机构有三个绕不开的既成事实总部管不住分散在各地的门店数据客户在 A 店办的卡到 B 店不认商品库存和项目价格在每家店各写各的账。这套智慧美容小程序完整源码就是把这三个老大难做成一套最小可用的线上闭环小程序端管客户下单、预约、查余额后台管门店上下线、商品库存、项目排班。它适合两类人一类是想在自有连锁门店里快速跑起线上客户管理的运营负责人另一类是接外包单、想找一个现成连锁 SaaS 骨架改改就交付的开发者。别急着解压先看后面几张表怎么设计的方向对了代码才读得动。2. 四张主表撑起连锁数据模型客户归属、门店隔离与卡项解耦的库表设计2.1 解压后先看目录结构小程序端、服务端、SQL 脚本三件套拿到 zip 之后第一件事不是双击运行而是解压后看目录。一份能对外交付的智慧美容小程序源码包通常按三层组织小程序前端代码常见目录名是 miniapp/ 或 pages/、后端接口工程server/ 或 api/、数据库初始化脚本sql/ 或 database/。有些包还会带一份 README 或说明文档里面写部署步骤和默认账号这份说明在连锁机构交接运维时特别重要千万不要丢。mkdir -p /opt/beauty cd /opt/beauty unzip smart-beauty-miniapp.zip find . -maxdepth 2 -type d | sort逻辑说明unzip 解压到 /opt/beauty 后用 find 查看两级目录。如果解压过程中报“zip 伪加密”或 CRC 校验失败说明压缩包被第三方工具改写过头不要强行继续换一个可信渠道重新获取。目录里能找到 app.json 和 pages/说明前端是原生微信小程序如果看到 manifest.json 加 uni-app 的目录结构说明用的是 uni-app 跨端方案同一套代码后续还能编译到支付宝小程序和 H5连锁机构想同时覆盖微信和抖音端时跨端方案更划算。参数说明maxdepth 2 表示只列出两层目录正常情况下能看到 miniapp、server、sql 三个顶层目录以及每个目录下的第一层子目录。如果第一层就出现一堆散乱文件没有目录分层那大概率是从某台服务器上直接打包的项目不是一份整理好的交付源码后面搭环境会多花不少时间。2.2 客户表与会员卡表把“人”和“资产”分开记账连锁美容场景里最常翻车的做法是想用一张表同时装下客户身份和卡余额。客户可能持有多张卡一张卡也可能被多家门店共用硬合一张表会导致后续消费流水无法追溯。正确做法是拆成 customer 客户表和 member_card 会员卡表两张表身份归身份资产归资产。CREATE TABLE customer ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, store_id INT UNSIGNED NOT NULL COMMENT 归属门店, mobile VARCHAR(20) NOT NULL COMMENT 手机号脱敏展示, nickname VARCHAR(64) DEFAULT COMMENT 客户昵称, source TINYINT NOT NULL DEFAULT 1 COMMENT 来源1到店 2小程序注册 3转介绍, level TINYINT NOT NULL DEFAULT 0 COMMENT 客户等级0普通 1银卡 2金卡, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_store_mobile (store_id, mobile) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE member_card ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, customer_id INT UNSIGNED NOT NULL COMMENT 持卡人, card_no VARCHAR(32) NOT NULL COMMENT 卡号前端展示用, balance DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT 储值余额, total_recharge DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT 累计充值, total_consume DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT 累计耗卡, open_store_id INT UNSIGNED NOT NULL COMMENT 开卡门店, status TINYINT NOT NULL DEFAULT 1 COMMENT 1正常 2冻结 3注销, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_customer (customer_id), KEY idx_card_no (card_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明customer 表的 store_id 表示这个客户归哪家门店管member_card 表的 open_store_id 记录这张卡在哪里开两个字段语义不同客户可以随居住地转店卡的“法定归属”在连锁对账时不轻易变。member_card 用 DECIMAL(10,2) 存金额禁止用 FLOAT 或 DOUBLE美容储值卡涉及真金白银浮点误差会在月结对账时变成几毛钱的差额到时候挨骂的还是技术。参数说明DECIMAL(10,2) 整数部分最多 8 位、小数 2 位单张卡百万余额足够。source 和 level 用 TINYINT 保存后续扩展等级不需要重建表。status2 冻结状态是连锁场景必备比如退卡纠纷或挂失时不用删数据直接冻结账还留在系统里。2.3 门店表与商品库存表store_id 作为安全分隔线而不是装饰连锁结构的核心是“总部能看所有门店门店只能看自己”。这个隔离在表设计阶段就要做否则后面写任何接口都会遇到越权问题。门店表存基础信息商品表存公共信息商品库存表按门店拆行三张表各管一段。CREATE TABLE store ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, store_name VARCHAR(128) NOT NULL, province VARCHAR(32) DEFAULT COMMENT 省, city VARCHAR(32) DEFAULT , address VARCHAR(255) DEFAULT , status TINYINT NOT NULL DEFAULT 1 COMMENT 1营业 2停业, open_time VARCHAR(32) DEFAULT 09:00-21:00 COMMENT 营业时段, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE product ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, product_name VARCHAR(128) NOT NULL, category_id INT UNSIGNED NOT NULL COMMENT 商品分类, sale_price DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT 售价, cost_price DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT 成本价, main_image VARCHAR(255) DEFAULT COMMENT 小程序展示图, status TINYINT NOT NULL DEFAULT 1 COMMENT 1上架 2下架, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE product_stock ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, product_id INT UNSIGNED NOT NULL, store_id INT UNSIGNED NOT NULL COMMENT 门店维度库存, stock_qty INT NOT NULL DEFAULT 0, locked_qty INT NOT NULL DEFAULT 0 COMMENT 预约锁定库存, PRIMARY KEY (id), UNIQUE KEY uk_product_store (product_id, store_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明product 表放商品的公共信息产品名称和价格在总部统一维护product_stock 表按门店拆分库存这样才支持“A 店库存不足时从 B 店调拨”。locked_qty 给线上小程序商城的预约单锁库存用先锁后付否则会出现客户下单成功、到店却没货的客诉。store 表里的 status 字段配合小程序端门店列表停业门店不在小程序展示。参数说明product_stock 的联合唯一键 uk_product_store 是关键约束它保证“一个门店对一个商品只存在一行库存记录”没有它同一商品在同一门店会出现两条库存扣减时到底扣哪条就成玄学了。locked_qty 单独列出来统计可售库存时直接算 stock_qty - locked_qty不用反写原库存字段少一次 UPDATE 就少一次并发风险。2.4 项目表与预约主表服务时长和技师绑定要留在数据库美容服务项目和商品有本质区别项目有服务时长、有操作技师还要和会员卡绑定优惠价。项目表的设计决定后续预约排班能不能展开。见过不少交付源码把项目时长写死在页面上换一家门店就乱套正确的做法是留在数据库里。CREATE TABLE project ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, project_name VARCHAR(128) NOT NULL, duration_minutes INT NOT NULL DEFAULT 60 COMMENT 服务时长, price DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT 门市价, card_price DECIMAL(10,2) DEFAULT NULL COMMENT 卡项折后价, store_id INT UNSIGNED NOT NULL COMMENT 适用门店0为全部门店, status TINYINT NOT NULL DEFAULT 1, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE appointment ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, customer_id INT UNSIGNED NOT NULL, project_id INT UNSIGNED NOT NULL, store_id INT UNSIGNED NOT NULL, staff_id INT UNSIGNED NOT NULL COMMENT 服务技师, appoint_date DATE NOT NULL, time_slot VARCHAR(16) NOT NULL COMMENT 时间段 10:00-11:00, status TINYINT NOT NULL DEFAULT 1 COMMENT 1待确认 2已确认 3已完成 4已取消, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_store_date (store_id, appoint_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明project 表加 card_price 字段是为了支持“用会员卡结算时的优惠价”小程序端展示的价格会与门市价不同这个字段就是价格差异的依据。appointment 表把技师 staff_id 直接挂在预约单上避免“到店随便派单”的管理模式连锁机构对技师有绩效结算需求预约单就是技师的业绩凭证后面工资核算直接从预约表统计就行。参数说明project 表的 store_id 设为 0 表示全部门店通用项目这个约定在代码里统一判断比用 NULL 更直观。appoint_date 加普通索引 idx_store_date支撑按门店查某天预约列表的常用查询连锁总部排班看板就靠这个索引不用建联合索引当前查询粒度足够。3. 客户管理与储值耗卡跨店消费时余额怎么扣、卡项怎么认3.1 开卡与充值两段式写库余额和流水必须同事务客户管理最核心的操作是充值和耗卡。新手最容易犯的操作是只 UPDATE 余额不写流水结果月底对不上账客户说“我充过 5000”时拿不出任何凭据。正确做法是余额变更和流水记录放在同一个数据库事务里要么都成功要么都回滚。transaction.atomic def recharge(customer_id, card_id, amount, operator_store_id, pay_channel): # 锁定会员卡行防止并发充值 card MemberCard.objects.select_for_update().get( idcard_id, customer_idcustomer_id ) card.balance amount card.total_recharge amount card.save(update_fields[balance, total_recharge]) RechargeLog.objects.create( card_idcard.id, amountamount, operator_store_idoperator_store_id, pay_channelpay_channel, # 1微信支付 2现金 3银联 before_balancecard.balance - amount, after_balancecard.balance )逻辑说明select_for_update() 把这张会员卡的行锁住两个收银台同时给同一张卡充值后一个事务会等前一个提交后才执行余额不会丢。RechargeLog 记录变更前的余额和变更后的余额这条流水是对账和审计的依据连锁总部接到客户投诉时最先查的就是它。before_balance 用 card.balance - amount 算出来因为余额已经加过了要在加之前的值做减法还原。参数说明pay_channel 建议按“线上支付/门店收银”区分小程序端充值走微信支付门店端充值走现金或 POS月底对账时按渠道分组统计差异一目了然。update_fields 只更新 balance 和 total_recharge 两个字段避免整行刷新意外覆盖其他字段这也是写生产代码的习惯少动就是少错。3.2 跨店消费的耗卡逻辑优先查卡项再查门店客户在 A 店办的卡去 B 店做项目这是连锁场景下最常见也最容易被低估的需求。源码里处理跨店耗卡的核心判断是这张卡是否绑定了当前项目而不是这家店认不认这张卡。只要卡项适用门店范围包含这个项目B 店就可以耗卡。// 小程序端耗卡提交示例wx.request 简化 wx.request({ url: ${apiBase}/card/consume, method: POST, data: { cardId: that.data.cardId, projectId: that.data.projectId, storeId: that.data.storeId, staffId: that.data.staffId, useBalance: true }, success(res) { if (res.data.code 0) { wx.showToast({ title: 耗卡成功, icon: success }) // 刷新余额与卡项列表 that.loadCardDetail() } else { wx.showModal({ title: 无法耗卡, content: res.data.msg, showCancel: false }) } } })逻辑说明耗卡请求提交后后端要做三件事校验卡状态正常判断 projectId 是否在该卡绑定的卡项列表里检查余额是否足够。把 storeId 和 staffId 也传上来是为了后端做门店和技师归属校验比如某个技师不属于这家门店就不能接这家店的单。前端在 success 回调里重新调用 loadCardDetail保证耗卡后余额和卡项列表及时刷新这个细节直接影响客户体验很多演示源码漏了这一步耗卡后界面数字不动客户以为没成功。参数说明apiBase 是小程序后端接口域名需要在微信公众平台配置 request 合法域名才生效。useBalance 字段表示本次是否使用储值余额有些项目支持“部分余额 部分现金”混合结算先用布尔值占位后续扩展成金额字段也不影响接口结构。3.3 储值余额不足的拦截策略别把负数写进库里拦截要在后端做而不是只看前端输入。前端可以判断余额不足并弹窗提示但接口不拦截的话用工具直接调接口就能把余额扣成负数这种黑匣子问题在连锁场景里一旦出现总部和门店之间就是一场扯皮。def consume(card_id, amount, project_id, store_id): card MemberCard.objects.select_for_update().get(idcard_id) card_items CardItem.objects.filter(card_idcard.id, project_idproject_id, status1) if not card_items.exists(): raise BizError(当前卡项不包含该服务项目) if card.balance card_items.first().gift_balance amount: raise BizError(储值余额不足请先充值) card.balance - amount card.total_consume amount card.save(update_fields[balance, total_consume]) # 写耗卡流水后续步骤略逻辑说明这里有个容易被忽略的点gift_balance 是充值赠送金额很多源码在耗卡时只用 balance 扣款赠送金永远花不出去客户体验很差。正确做法是余额与赠送金合计判断可用额度扣款顺序按业务规则配置常见的是先用赠送金后用本金或按比例抵扣。raise BizError 后由全局异常处理器统一返回可读提示前端拿到 code 非 0 就弹 modal不会把异常堆栈暴露给客户。参数说明CardItem 是卡项关联表记录这张卡绑定了哪些服务项目、剩余次数或剩余金额。连锁机构卖“次卡”和“储值卡”是两套逻辑次卡按次扣、储值卡按金额扣源码里用 status1 过滤已作废的卡项防止客户拿失效的项目继续预约耗卡。4. 门店、商品与项目三个管理模块一套后台框架下的权限与联动4.1 门店管理接口门店上下线、列表展示与角色权限门店管理后台最常用的是上下线门店。总部运营把某家店的状态改为停业小程序端门店列表要立刻不展示预约和商城入口也要关闭。这个联动的关键不是改一个字段而是让小程序端所有数据读取都走 status1 的过滤条件。# 更新门店状态后小程序端拉取门店列表 curl -X PUT https://api.example.com/api/v1/store/{id}/status \ -H Authorization: Bearer {admin_token} \ -d {status: 2} # 小程序端只拉营业中门店 curl https://api.example.com/api/v1/store/list?status1lat31.23lng121.47逻辑说明门店列表接口增加了 status1 过滤停业门店不进小程序。lat/lng 参数用于按距离排序时后端先查出所有营业门店再按经纬度算距离返回最近的三家。后端如果是 Node.js 常见用 haversine 函数计算如果是 Java 服务则可以用 Redis GEO两种方案在数据量小于一万家门店时性能差别不大选源码里已有的实现即可。注意门店接口返回字段不要带 province、city 之外的运营敏感数据比如门店月流水和成本数据这些只走管理后台接口。参数说明Authorization 用的是 Bearer Token管理后台与小程序端共用一套用户体系时需要在 Token 的 payload 里声明角色字段admin 角色才能调用状态变更接口门店店长只能看自己门店数据。这里的 id 是路径参数更新的是 store 表的 status 字段接口层要做幂等处理重复提交相同的状态值应返回成功但不重复记录操作日志。4.2 商品管理的库存联动下单锁定、支付扣减、超时回补商品管理不只是后端增删改查真正的坑在库存联动。线上小程序商城下单占库存支付成功扣库存超时未支付释放库存这个闭环做不好就会出现超卖两个人同时下单最后一件商品两单都显示支付成功。-- 扣减库存的原子操作防止并发超卖 UPDATE product_stock SET stock_qty stock_qty - 1, locked_qty locked_qty - 1 WHERE product_id ? AND store_id ? AND locked_qty 1;逻辑说明这条 UPDATE 语句带 AND locked_qty 1 条件每次扣减前先确认锁定库存大于等于 1否则影响行数为 0后端判断 RowsAffected0 就返回“库存不足”。用 SQL 原子操作而不是先 SELECT 再 UPDATE是为了避免并发场景下的超卖两个用户同时下单同一件商品只有一条 UPDATE 能成功另一条影响行数为 0 直接失败。参数说明locked_qty 是下单时从 stock_qty 转入的“锁定库存”先占住名额支付完成后走到这条 SQL 把锁定量扣掉。如果订单超时取消反向操作stock_qty 1、locked_qty - 1把库存释放回去。门店调拨使用单独调拨单表生成一条 A 店出库、B 店入库的两条库存流水而不是直接改商品表的库存总数这样才能追溯“货到底去哪了”。4.3 项目管理的预约排班查技师冲突和营业时段项目管理接口里最容易出问题的是预约时间冲突。技师一天接多少单、某个时间段是否已被占源码里一般用最稳妥的简单方案查询预约表里同一技师同一日期同一时段有没有重叠记录而不是引入复杂的排班算法。算法越复杂边界情况越多技师请假、临时换班都容易出漏子。SELECT COUNT(*) AS conflict_count FROM appointment WHERE staff_id ? AND appoint_date ? AND status IN (1, 2) AND time_slot ?;逻辑说明预约时把技师、日期、时间段组合起来查重。status IN (1,2) 只统计待确认和已确认的预约已取消和已完成的单子不影响新预约否则客户取消后又约同一个时段会被误判冲突。时间段精确匹配到 time_slot 字符串比如 10:00-11:00前端用选择器让客户选固定时间段不允许自由填写否则查重条件就形同虚设。参数说明如果连锁门店要做更细的分钟级排班比如同一技师 09:30-10:30 和 10:00-11:00 必须判定为冲突就得把 time_slot 拆成 start_time 和 end_time 两个字段用重叠区间判断start ? AND end ?。这个改动涉及预约表单、列表渲染和冲突查询三处代码源码里如果原始设计就是字符串区间改造前先评估改动范围别匆忙动手。5. 把源码跑起来的避坑清单环境、支付、微信审核与数据一致性的五个教训5.1 解压报错或文件缺失先校验 zip 完整性再谈启动现象解压时报“CRC 校验失败”或提示 zip 伪加密强行解压后发现小程序目录缺 app.json后端目录缺配置文件。 原因压缩包在传输过程中被截断或第三方网盘转存时改写了压缩包头zip 伪加密是常见的误操作或恶意处理结果直接解压出来的文件大概率是不完整的。 解决用命令行工具先校验压缩包完整性确认没问题再解压。缺 app.json 的目录肯定不是完整小程序先换源别浪费时间在残缺代码上补文件。unzip -t smart-beauty-miniapp.zip参数说明unzip -t 只测试不释放输出 OK 才继续正式解压。如果源文件后缀是 .zip 但实际是 RAR 或 7zunzip 会直接报错用 file smart-beauty-miniapp.zip 先识别真实格式再选择对应的解压工具能少走一段弯路。5.2 小程序 request 请求全部失败合法域名没有配好现象微信开发者工具里调接口全部正常手机预览时每个请求都是 fail控制台显示“不在以下 request 合法域名列表中”。 原因微信小程序生产环境要求 request 的 URL 必须是 HTTPS且域名已经在微信公众平台配置过。本地开发用的 IP 加端口在真机上完全不可用这是小程序平台的安全限制不是代码问题。 解决在微信公众平台“开发管理-开发设置-服务器域名”里把后端接口域名加进 request 合法域名域名要求已备案且支持 HTTPS。开发阶段可以在开发者工具“详情-本地设置”勾选“不校验合法域名”临时调试上线前必须关掉这个开关。5.3 微信支付拉起失败商户号与小程序主体不是同一家现象点击充值弹收银台时报“当前商户号未开通”或下单接口返回“mch_id 与 appid 不匹配”。 原因微信支付商户号需要和小程序账号完成绑定连锁机构常用总公司营业执照注册商户号小程序挂在子公司主体下两边主体不一致支付授权无法通过。 解决在微信支付商户平台的产品中心开通“小程序支付”把小程序 appid 关联到商户号下并确认关联的 appid 主体与商户号主体一致。如果总公司与子公司确实不同主体走服务商模式让总公司商户号做服务商、子公司做特约商户这个配置通常占支付联调一半的工时提前和财务确认执照信息。5.4 小程序审核被驳回美容行业涉及特殊资质类目现象提交微信审核时提示“资质文件不完整”驳回原因是医疗美容相关要求提供《医疗机构执业许可证》。 原因美甲、美发、生活美容类目和医疗美容类目是两套审核标准。源码功能里如果出现“医美”“注射”“激光”等字样审核系统会自动命中医疗类目普通美容机构没有相应资质就过不了审。 解决把小程序的简介、商品标题和项目名称里的医美敏感词全部改成生活美容话术比如“光子嫩肤”改为“日常皮肤护理”。如果机构确实有医美资质则在微信公众平台类目配置里选择对应医疗类目并上传许可证两者只能取其一不要混着写。5.5 数据库时间差 8 小时与金额精度错位现象后台看到的预约时间总比小程序端早 8 个小时月底对账时发现余额总和差几分钱怎么都对不上。 原因数据库连接串没配 serverTimezoneAsia/ShanghaiMySQL 默认读取系统时区云服务器如果设置为 UTCDATETIME 字段读写就会整体偏移。金额错位则是字段用了 FLOAT 类型累计导致的浮点误差在小数点后第二位悄悄放大。 解决数据库连接串显式加 serverTimezoneAsia/ShanghaiJava 服务再加启动参数 -Duser.timezoneGMT8。余额字段按前面建表方案用 DECIMAL但代码里如果存在除以 100 或乘 0.95 的折扣逻辑也要统一用 Decimal 类型运算不要混用 float 做中间量。修完这两处月结对账基本不会再出幺蛾子。5.6 上线前过一遍这四步检查把以上问题整理成一份最小检查清单每接手一套源码包都按这个顺序走先 unzip -t 验包再检查小程序合法域名是否可访问然后测一遍微信支付沙箱或小额真实支付最后提交审核前在开发者工具里全局搜索敏感词。这套流程走下来能把八成以上的交付问题挡在上线之前。6. 源码跑通后的进阶从单店演示到区域连锁的四个改造点6.1 把写死的 store_id 换成登录态注入演示版源码通常在页面里写死一个 store_id所有数据查询按这个固定门店走。接入真实连锁业务时这个常量必须改成从登录态里取员工在小程序端登录后后端根据 Token 解析出所属门店查询全部带上这个字段。改造时注意统一走一个请求拦截器在 service 层注入不要在每一个 controller 里手动加否则漏一处就是越权数据泄露。6.2 给扣款和扣库存补上并发锁单店演示时并发量低select_for_update 和 UPDATE 条件扣减看着可有可无。连锁机构开业活动一做几百个客户同时充值不加锁就会出现余额扣错、库存超卖。我接手过一套现成源码就是把储值扣款改成带版本号的乐观锁加一个 version 字段UPDATE 时带上 WHERE version ?, 影响行数为 0 就重试三次简单有效。6.3 给门店列表和项目缓存加过期时间小程序商城首页每次加载都查一次门店表和项目表网络慢时白屏好几秒。常见做法是用 Redis 缓存门店列表和项目列表过期时间设 5 分钟门店状态变更时主动删除对应缓存。注意缓存 key 一定要带 store_id 维度比如 store:list:{status}别把所有门店混在一个 key 里否则停业门店的缓存刷新会延迟。6.4 小程序动态设置标题和顶部导航栏适配最后一个改造点看起来小但直接影响客户留存进入不同的门店页面或项目详情页时用 wx.setNavigationBarTitle 动态设置头部标题而不是所有页面都叫“智慧美容”。另外微信小程序的顶部导航栏高度在带刘海屏和灵动岛的手机上差异明显建议用 wx.getMenuButtonBoundingClientRect 拿到胶囊按钮位置再动态计算导航栏高度兼容性比写死 64px 靠谱得多。我习惯把这段适配代码封装成独立工具函数所有页面统一调用省得每个页面都踩一遍错位坑。这套源码给我的最大教训是演示能跑通只是起点真正上线要过的坎全在边界处理上。希望这份落地经验能帮你少走几趟弯路一次把小程序从开发机送到客户手里。本文还有配套的精品资源点击获取