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 实战里的工程结构也值得一并参考。下一节我们正式进入选型,算一算云开发和自建后端在小团队场景下到底差在哪。

延伸阅读