1.1 需求拆解:商品、购物车、下单、支付
场景
开工第一周,我和朋友坐在店里做需求访谈。她的诉求听上去很简单:把店里的货搬到小程序上,顾客自己下单付款,店员在后台能看见订单、改发货状态。店里月流水大约十二万,客单价一百八,核心客源是两百来个老顾客,靠朋友圈和他群复购。但往下聊全是细节:一款手工杯有四个釉色、三个容量,价格还不一样;特价款限购,每人只能买一件;大促那天两小时预计涌进两千单,库存绝不能超卖;顾客付完款不发货要能退款;老客户想知道发货了没有、快递到哪了;店员只有三个,懒得学新后台,订单管理最好还是在企业微信里看。
这些诉求背后其实藏着两类需求:一类是朋友说得出口的功能,一类是她默认存在、出事才会想起的底线——钱别算错、库存别穿、页面别卡、顾客付了钱不能找不到人。前者管产品长什么样,后者管系统能不能活过大促。我把两类分开记,功能进功能清单,底线进非功能指标。
团队只有我一个开发,如果把这些需求原样全接,一个月也做不完。我做的第一件事不是画原型,而是把需求拆成模块、标上优先级,先圈出 MVP——两周年大促前必须上线的部分,其余排到后续版本。这一步看起来慢,实际是后面所有技术选型和排期决策的依据。需求没拆清楚就动手,最常见的后果是做到一半发现某个功能依赖一个根本没规划的动作,然后整条链路返工。比如后来做退款时才发现,如果下单时没把商品快照写进订单,活动一改价退款金额就算不清。
方案
我把全部需求归到五个模块:商品、购物车、交易、用户、运营。每个模块拆成具体功能点,按上线紧迫程度标 P0 到 P2。原则是:P0 做不成大促就没法开张,P1 是体验补强,P2 是有了再说。
| 模块 | 功能点 | 优先级 | 备注 |
|---|---|---|---|
| 商品 | SPU/SKU 建模、列表分页、详情图 | P0 | 200 个 SKU 规模 |
| 商品 | 搜索、分类筛选 | P1 | 靠云数据库索引 |
| 购物车 | 本地缓存 + 云端同步 | P0 | 未登录也能加车 |
| 交易 | 库存原子扣减、微信支付 | P0 | 防超卖是红线 |
| 交易 | 订单状态机、退款流程 | P0 | 状态流转可追溯 |
| 用户 | openid 静默登录、手机号授权 | P1 | 下单强制授权手机号 |
| 运营 | 订阅消息催付与物流通知 | P1 | 付款与发货触发 |
| 运营 | 埋点、监控告警、防刷 | P2 | 上线后再迭代 |
拆完有个关键判断:交易链路(库存扣减、支付、订单状态机)是整个商城的骨架,商品和购物车是肉,用户体验和运营是皮。写代码的顺序按这个来,先保证钱算对、库存不穿,再打磨加载速度和消息推送。这个顺序后来被证实是对的——大促那天最先扛不住的是库存并发,而不是页面好不好看。
还有一组非功能需求,是朋友没提但必须自己兜住的。我也把它们落成一张表,验收时逐条打钩:
| 指标 | 目标值 | 怎么验证 |
|---|---|---|
| 下单峰值 | 每秒 50 笔 | 压测工具打真实链路 |
| 列表首屏 | 1.5 秒内出首图 | 真机性能面板采样 |
| 支付回调 | 不丢单 | 回调延迟到五分钟仍正确 |
| 数据保留 | 订单留三年 | 按月归档冷备 |
| 页面崩溃率 | 低于千分之五 | 监控告警兜底 |
这些指标直接决定了后面要不要加索引、要不要上 CDN、回调要不要做幂等重试,写不写下来差别很大。我还把每个 P0 功能的验收标准压成一句话,比如库存扣减的验收是任意并发下不出现负库存,付款的验收是回调晚到五分钟订单状态依然正确。
清单之外再立一条规矩:需求可以加,但必须换优先级排序,而不是简单往 P0 里塞。每周五我和朋友过一次清单,新增需求必须说清属于 P1 还是 P2,以及它会不会影响大促前的 P0 交付。有了这条规矩,后面想加拼团、想加会员积分的时候,讨论五分钟就能决定排期,而不是开三天会还没结论。
配置/代码
需求拆清楚后,我在小程序里先把页面骨架立起来,方便设计和前端并行介入。app.json 里把 tabBar 和一次定好,避免后期改路由:
{
"pages": [
"pages/goods/list/list",
"pages/goods/detail/detail",
"pages/cart/cart",
"pages/order/checkout/checkout",
"pages/order/list/list"
],
"tabBar": {
"list": [
{ "pagePath": "pages/goods/list/list", "text": "商城" },
{ "pagePath": "pages/cart/cart", "text": "购物车" },
{ "pagePath": "pages/order/list/list", "text": "订单" }
]
}
}
对应的云开发资源也同步规划,避免做到一半改集合名这种麻烦事:
| 云资源 | 名称 | 用途 |
|---|---|---|
| 云数据库集合 | goods |
商品 SPU 信息 |
| 云数据库集合 | skus |
规格、价格、库存 |
| 云数据库集合 | orders |
订单主表 |
| 云数据库集合 | events |
自建埋点数据 |
| 云函数 | createOrder |
下单与原子扣库存 |
| 云函数 | payCallback |
支付回调验签与发货 |
这张清单后来贴在团队墙上,谁要加字段都先看一眼归属,省了不少扯皮。比如最初前端把购物车数据直接写进 orders 集合,建表前对了一眼清单就改回独立的 cart 临时集合,否则订单列表会被未支付的脏数据污染。
每个集合在开工前还顺手把文档结构样例定下来,字段口径提前对齐,之后再也没有为“库存字段叫 stock 还是 remain”这种事争论过:
// skus 集合的一条文档(口径在开发前就定死)
{
_id: "sku_1001",
spuId: "spu_2001",
spec: "白釉 / 300ml",
price: 12800, // 单位:分
stock: 60,
limitPerUser: 1, // 限购口径:按活动款计
activityId: "act_anniv"
}
每个集合的索引和权限规则在技术栈地图那节展开细说;想了解传统 PHP 后端怎么做秒杀级并发扣减的,可以对照秒杀实战手记的思路,核心矛盾都是库存竞争。
踩坑
第一个坑是需求口径。朋友说限购每人一件,我以为是同一 SKU 限一件,她说是同一活动款限一件——三个 SKU 都算同一款。大促快上线才对齐,差点把限购逻辑写错。结论:限购、库存、退款这类涉及钱的规则,必须把口径压缩成一句话写进文档,再让业务方逐条确认,别靠开发自己理解。
第二个坑是低估图片量。两百个 SKU,每个详情五张图,加主图和活动素材将近两千张。开发期我随手丢进云存储根目录,上线前整理 CDN 缓存策略时全乱套了,只能一张张迁移重命名。如果一开始就定好 goods/{spuId}/ 分目录规范,能省一整天。这个教训在后面图床方案那节还会细说。
第三个坑是订单号并发撞号。最初用时间戳拼随机数,压测时两小时两千单没撞,峰值一秒几十单时偶尔撞出重复号,幸亏集合唯一索引兜底报错,否则就出重复订单了。后来改用云数据库计数器生成分段序号,日期 + 分片段 + 自增段 的组合,才算稳。
第四个坑是没给需求变更留缓冲。设计中途要加拼团功能,我硬排进 P0,结果大促前一周天天加班还没测完,最后砍掉才上的线。之后我学乖了:大促前两周冻结需求,新功能一律排到下一版。朋友起初不乐意,看到大促稳稳扛住两千单后才认可这个节奏。
第五个坑是验收标准只存在于我脑子里。开发完成后我口头跟朋友说“测过了”,结果她让店员真去试用,下单流程在安卓低端机点第三次才进得去详情页。这件事让我把验收标准全部落到纸面:什么机型、什么网络、走哪几步、看到什么算通过。后面每个功能上线前都照着过一遍,返工率肉眼可见地降下来了。
小结
需求拆解这步的核心产出不是文档,是优先级和顺序:交易链路优先于展示,P0 先行 P2 后补,云资源在写第一行代码前就规划好,非功能指标写进验收标准。模块、功能点、优先级、云资源四张清单钉死之后,后面所有篇目都是照着这张地图走的。事后看,大促两周的开发量能在十天内完成,主要就是占了清单清楚的便宜——每天开工都知道今天必须闭合哪个 P0 功能,不用临到头才想下一步做什么。如果你正在做类似规模的小程序,云开发 Python 实战里的工程结构也值得一并参考。下一节我们正式进入选型,算一算云开发和自建后端在小团队场景下到底差在哪。
延伸阅读
- 选型对比:云开发与自建后端:本系列下一节,把账算清楚
- 秒杀防超卖实战:并发扣减的另一种实现思路
- 回到本书目录:全书五篇结构一览
