从多端商品浏览到报价清单:一次跨端零售全栈复盘
本文目录
这篇文章来自对个人全栈项目当前默认分支的静态源码审阅。为保护项目边界,省略仓库名、品牌、业务地址、接口地址和配置值,只保留可以迁移到其他零售产品的工程经验。前端源码显示使用 Taro 4.1.11、React 18 与 Zustand 5;构建脚本声明了微信、支付宝、抖音和 H5 目标。配套 API 使用 TypeScript、Express 4、Sequelize 6 与 PostgreSQL 驱动。版本和目录结构是本次检查时的源码快照,不代表所有平台均已构建、发布或完成真机验收。
先看用户走过的业务链
页面配置把商品/服务浏览、详情、活动、个人中心、登录和清单组织在同一个客户端中;底部导航只放高频一级入口,详情和提交动作由页面路由承接。用户可把不同类型的条目加入本地清单,再选择复制为文本,或将清单保存为一条服务端报价记录。这个流程把“浏览时的临时选择”与“可供后续跟进的报价草稿”分成了两个生命周期,避免把购物车误当成已成交订单。
跨端项目的关键不是让每个平台看起来完全相同,而是让领域规则一致、平台差异有明确归属:
小程序 / H5
-> 页面与交互
-> 商品、服务和清单状态
-> 统一请求模块
-> Express API
-> PostgreSQL 持久化
页面路径、窗口和 tab 配置集中在应用配置中,业务页面各自维护列表/详情流程。多端共享数据模型和请求语义;登录授权、导航栈、剪贴板、存储容量、分享与支付等平台能力,则应放进可替换的适配层并在目标平台单独验证。配置里出现某个构建目标,只能证明项目声明了该目标,不等于它在对应开发者工具或设备上运行正常。
购物车是草稿,不是可信账本
源码中的清单项包含类型、条目 ID、名称、价格、数量等字段;状态由 Zustand 管理,并通过 Taro 同步存储接口写入本地。应用重启后恢复清单,网络暂时不可用时也能继续整理,这对“先挑选、稍后询价”的流程很实用。
但本地持久化只解决体验连续性,不提供数据完整性或权威性。存储内容可能过期、被旧版本写入、损坏或被用户环境修改;读取后应做运行时 schema 校验,剔除非法条目,并为结构变化设置版本迁移。源码的清单合并按条目 ID 判断相同项;如果配件和服务使用各自独立的 ID 空间,身份键应同时包含类型,例如 type:id,避免两个领域恰好使用相同 ID 时被合并。数量也应在状态层和服务端都校验为有限的正整数,并设业务上限,不能只依靠按钮控件。
持久化数据建议采用明确的版本与白名单字段,而不是把整个 UI store 原样序列化:
{
"version": 2,
"items": [
{ "type": "part", "id": "catalog-item-id", "quantity": 2 }
]
}
名称、图片、价格等展示快照可以帮助用户辨认旧清单,但恢复时要标注价格和可售状态可能变化。不要把访问令牌、支付凭据或不必要的个人信息塞进购物车缓存。
账号切换时隔离本地清单
这次源码审阅还发现,购物清单和收藏各自写入一个固定的设备存储键;认证状态与令牌则保存在另外的键中。退出登录会清除令牌并重置认证状态,但没有同步清理或按用户身份分区购物清单和收藏。收藏同步只有在服务端返回非空列表时才覆盖本地收藏,因此一个账号退出后,另一个账号登录时仍可能看到前一个账号留下的旧收藏。这里确认的是客户端状态实现,不是已复现的服务端越权或远程数据泄漏;服务端是否会把这些旧条目关联到新账号,尚未通过接口或真机验证。
对共享设备或允许切换账号的应用,这至少是需要处理的隐私和数据正确性风险。可以按“匿名访客”和稳定账号 ID 分区保存;登录后明确询问是否合并访客清单,切换账号时立即替换内存态;服务端返回空收藏也必须成为有效结果并覆盖旧状态。退出后若保留该账号的本地草稿,应放进该账号自己的分区,不能退回到所有用户共用的默认键。回归测试要覆盖 A 登录并加入收藏、退出、B 登录且服务端收藏为空、B 的本地视图不出现 A 的数据,以及 A 重新登录后只恢复 A 自己的清单。
报价提交要有服务端不变量
复制文本和创建服务端报价是两种不同动作。前者是客户端生成的便捷摘要;后者涉及持久化、重复提交、商品状态和价格口径。浏览器显示的金额可以作为预估,但不能成为最终报价或订单的权威来源。稳妥的请求只提交业务条目标识、类型和数量;服务端重新查询当前有效目录、校验访问范围与数量,再按统一规则计算金额并保存不可变的报价明细快照。
一个通用请求形状可以是:
{
"requestId": "unique-retry-key",
"items": [
{ "type": "part", "id": "catalog-item-id", "quantity": 2 }
]
}
服务端应定义报价的有效期、币种与舍入规则,并在同一事务里保存报价主记录和明细快照。目录价格变化后,旧报价应保持原始计算依据或明确失效,不能静默重算。客户端超时重试时,用幂等键或等价的业务去重机制返回同一结果,避免生成多个报价;按钮禁用只能改善交互,不能替代服务端幂等。库存、折扣、税费若参与最终金额,也必须在服务端按同一业务时点校验。报价不等于支付订单,状态转换和支付回调需要独立设计。
这类边界尤其重要,因为客户端提交的对象字段并不天然可信。应明确允许写入的属性,避免批量赋值覆盖服务端维护的用户、价格、状态或审计字段;再通过对象级授权限制谁能查看和更新报价。OWASP API 安全指南将对象属性级授权列为独立风险类别,不能只靠前端隐藏字段或页面按钮。
可迁移的工程检查清单
- 身份键:同一 ID 是否可能出现在不同商品类型;合并、删除、数量修改是否使用同一复合身份。
- 持久化:畸形 JSON、旧 schema、存储写入失败和清空后重新启动时,页面是否有明确且可恢复的状态。
- 请求契约:客户端只发送必要字段;服务端校验类型、数量上限、条目状态、身份与对象权限。
- 报价一致性:总价只由服务端可信数据计算;保存主从记录使用事务;重复提交和并发目录变更有确定语义。
- 多端兼容:分别构建每个声明的平台,并测试登录、返回导航、复制、弱网恢复、清单迁移与长列表;H5 浏览器通过不代表小程序通过。
- 观测与隐私:记录请求 ID、结果码和耗时;日志不保存完整个人联系信息、访问令牌或敏感清单正文。
本次只读审阅确认了前端构建目标、主要页面域、Zustand/Taro 本地清单实现和报价保存的交互;没有在这些远端仓库执行安装、构建、数据库迁移、自动化测试、设备调试或生产请求。上面的服务端不变量是面向后续加固的设计建议,不是对这些检查已完成的声明。公开文章也不包含私有仓库链接,读者可独立复用方法而不依赖访问原项目。
参考:Taro 全局配置、Taro 同步存储、Zustand 持久化与迁移、OWASP API3:2023 对象属性级授权。
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。