3月下旬无锡鼋头渚的樱花开到最盛那几天,马山一处34间房的民宿群,前台连着接到七八个电话,客人说小程序上明明显示满房,怎么隔壁平台还能订到房。老板自己点开自家的直订微信小程序看,大床房、庭院房都是灰的,一间都放不出来。当晚查房,实际空着11间。
樱花季结束后拿PMS流水和小程序订单一笔笔对,18天里有43个间夜是这么白扔的。那段档期房价在420到680元一晚,少收的房费落在1.8万到2.9万之间,被劝去别家、以后大概不会再回来的客人不算在里面。老板一开始认定是前台显示出了毛病,让开发商清了缓存、重新提交了一个版本,第二天照旧满房。
先分清是显示错了,还是房量真的没了
这类现象只有两种可能:库存数据是对的、前端渲染错了;或者小程序自己那份库存数据已经错了。分清只需要三个后台、五分钟。挑一个确认有空房的日期和房型,把三个数字并排摆出来——PMS里的可售房数、渠道管理系统里的可售房数、微信小程序后台库存表里的剩余量。
那天的结果是PMS显示大床房可售5间,渠道管理系统也是5间,小程序库存表是0。数据源没错,错的是小程序这一侧自己记的那份账。再点开那条库存记录的变更历史,末次更新停在3月21日19点42分,而客人取消那笔订单发生在3月23日上午。
到这一步事情已经清楚一半:扣房的链路在正常工作,放房的链路根本没被触发过。
第一层:合同里写的是对接房态,落地只做了一半
一笔民宿订单在生命周期里引起的房量变动,远不止下单减一间这一个动作。客人自行取消、客人改期、提前退房、临时升房型、到点没来的no-show释放、渠道整单撤回、店家手工关房再放开,每一个都要反向或者横向改动可售房量。这七类动作里,这套微信小程序只实现了两个:下单扣减,以及支付超时后15分钟自动释放。
为什么会漏成这样,翻一遍交付物就明白。沙箱联调阶段的测试脚本一共12条,全是选房、锁房、支付、回写这一条正向路径。验收演示同样只演了下单,取消按钮当场点过,但演示环境里那笔订单本来就没往渠道推送过。更直接的证据在渠道后台的回调失败日志里:取消通知的回调地址填的还是联调时用的测试域名,上线切正式环境时没跟着改,18天里63笔取消与改期通知全部5秒超时失败,重试耗尽后被丢弃。
把域名、商户号、密钥、回调地址这类配置硬编码在代码里、靠人记得改,这件事本身就是判断一个团队工程习惯的入口。改成按环境隔离的配置项是一小时的工作量,省下的是这18天。
第二层:回调通了,乱序和重发照样能把房量搞错
把回调地址改对,问题只解决一半。渠道侧的通知机制普遍是失败重发,间隔从5秒逐步拉长到60秒,重试三到五次;网络抖一下,同一笔取消可能连着来三遍。更麻烦的是顺序——下单通知和取消通知走不同队列时,取消先到、下单后到是真实会发生的,这时候按到达顺序处理,房量会被后到的下单通知重新扣掉,客人明明取消了,房还是锁着。
可用的做法是给每条房态消息定唯一身份和先后次序。库存变动表上建一个由渠道订单号、动作类型、渠道侧变更序号三列组成的唯一索引,重复消息插入即冲突、直接丢弃;再拿消息里带的渠道时间戳跟本地这条库存的版本号比一次,比本地更旧的消息一律不执行。这家民宿补完这段逻辑后回放那63笔通知,其中9笔正是重复投递,被正常拦下。
问一家团队做没做过房态,问法可以很具体:同一笔取消通知连发三次,你们靠什么保证房量只加回一间。答"我们会判断一下订单状态"的,多半没有唯一索引,只是在业务代码里读了一次当前状态——两条消息并发进来时,这种判断拦不住。
第三层:缺一条日终校准,错一次就一直错到有人投诉
增量推送这种机制注定会漏。对方系统维护、消息队列积压、自己这边发版重启,都会吞掉几条通知。真正决定损失大小的不是漏不漏,而是漏了以后多久能自己纠正回来。这套系统里没有任何纠正机制,所以3月21日晚上错掉的那一间,一直错到樱花季结束。
兜底做法是每天凌晨拉一次全量:约定03点30分从PMS或渠道管理系统取未来60到90天的全部房型房量,跟微信小程序库存表逐日逐房型比对,不一致的以数据源为准直接改写,同时把差异条数推到值班手机上。差异连续两天不为0,说明增量链路又断了,而不只是补一下数的事。这套校准加告警在云上的开销一年不到500元,而一个间夜是420到680元。
还有一个问法很好用:同步失败的那条消息去哪了。能立刻答出死信队列、失败重试表、几分钟一次补偿扫描的团队,基本做过真实的房态项目;答"失败会打日志"的,等于承认没有人会知道它失败过。
再往下一层:那份验收报告里,逆向用例是0条
事后翻当初签字的验收报告,47条用例,异常路径只有3条,分别是登录失效、支付失败、上传图片超限,与房态相关的逆向用例一条没有。系统不是在上线那天变坏的,它从验收那天起就只被证明过顺着走能通。
所以挑一家能做民宿直订微信小程序的团队,比翻案例截图有效得多的动作是要一份用例表,数里面异常与逆向用例的占比。房态这类强状态系统,逆向用例低于四成,基本可以判断没覆盖到位。更狠一点的验收动作叫取消风暴:在验收环境里30分钟内造50笔操作,取消、改期、提前退房、no-show各占一部分,中途把同步服务停掉2分钟再启动,结束后比对小程序库存与数据源是否逐日逐房型一致,允许的差异是0。这个演练半天能跑完,能提前暴露的正是上面三层问题。
无锡的民宿老板对档期的敏感度比多数地方更高:鼋头渚樱花季、阳山桃花节、太湖国际博览中心的展会周,这几段时间房价是平日的两到三倍,错一个间夜的代价也跟着翻倍。演练就该压在这种价格设置下做,用平日房价试出来的容错度,在旺季并不成立。
这类问题该在哪个环节被拦住
三个节点,越往前越便宜。
需求评审阶段,把房态变更动作清单做成合同附件,上面那七类动作逐行列出来,每行写清谁触发、经哪条链路、允许几秒内在小程序上可见、失败了怎么补。"支持对接PMS"这六个字不构成需求,它既没说取消算不算,也没说延迟多少算合格。
开发中期,要求交三样东西而不是听进度汇报:渠道后台回调成功与失败的原始日志截图、失败消息的死信与重试机制说明、配置项按环境隔离的证据。这三样都拿得出来,配置里留着测试域名这种事就不会带到线上。
验收阶段,把三条写成可量化的验收项,不通过不付尾款:逆向用例占比不低于四成;取消风暴演练结束后库存差异为0;日终全量校准与差异告警已上线,并出具连续七天的比对记录。事后来补这三块的工作量是6到9人日、5000到1.4万元,摊在预研阶段做几乎不额外花钱,差别只在有没有人在签字之前把它写进去。
这家民宿后来在第二栋楼上线新的直订微信小程序时,验收清单里加了一条写得很死的条款:任意渠道的取消通知发出后10分钟内,本地可售房量必须回位,抽查20笔,失败1笔即整项不通过。条款下面附了那63笔失败回调的日志截图,注明为反面样本。




