首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >乡镇外卖先小程序还是App:跨端重试不重复建单的核心实现

乡镇外卖先小程序还是App:跨端重试不重复建单的核心实现

原创
作者头像
微订技术团队
发布于 2026-10-25 09:51:07
发布于 2026-10-25 09:51:07
820
举报

乡镇外卖先做小程序还是App,应先按顾客入口与工作端分工选组合;若两入口共存,需要防同一购买请求跨端重试时生成两张订单。下面用可信身份映射、业务请求号和内容绑定实现这个局部约束,作为增设App前的验收示例。

业务背景与产品边界

微订官网外卖跑腿方案列有公众号、小程序、App等入口,以及商家、骑手、平台多角色端口。这是第一方产品类别说明,可据此核对乡镇项目的端口组合;具体套餐、账号互通、通知方式和设备支持仍要按实际版本演示。

构造用户在小程序提交后未看到响应,随后切App恢复原请求。相同业务身份与请求应得到原订单;主动再购买要用新请求。示例不包含真实登录、支付与部署,不代表微订内部实现。

模型与约束

对象

约束

作用

identities

渠道和外部身份映射到可信业务用户

示例只预置两个已绑定身份

receipt key

业务用户加请求号唯一

跨端重试沿同一购买意图

payload

商家与整数分金额固定

同号改内容拒绝

order

CREATED保存来源入口

新增入口不改变原订单身份

完整核心代码与复现

依赖Node.js内置node:assert/strict,无第三方包。保存为entry.cjs并运行node entry.cjs。Map只用于单进程演示;外部身份到业务身份的认证绑定不在实现范围,不能把请求里自报的身份当作已认证身份。

代码语言:javascript
复制
const assert=require('node:assert/strict');
// Local core example only. Authentication and durable transactions are omitted.
const identities=new Map([['mini:U7','U1'],['app:A9','U1']]);
const receipts=new Map();
function create(channel,external,request,merchant,total){
 const user=identities.get(channel+':'+external);
 if(!user)throw Error('IDENTITY_UNBOUND');
 if([request,merchant].some(x=>typeof x!=='string'||!x||x.includes(':'))||!Number.isSafeInteger(total)||total<0)throw Error('INVALID_INPUT');
 const key=user+':'+request,payload=JSON.stringify([merchant,total]);
 if(receipts.has(key)){
  const old=receipts.get(key);if(old.payload!==payload)throw Error('REQUEST_CONFLICT');
  return {...old.order};
 }
 const order={id:'O'+(receipts.size+1),user,merchant,total,originChannel:channel,state:'CREATED'};
 receipts.set(key,{payload,order});return {...order};
}
const first=create('mini','U7','R1','M1',2000);
assert.deepEqual(create('app','A9','R1','M1',2000),first);
assert.throws(()=>create('app','A9','R1','M1',2100),/REQUEST_CONFLICT/);
assert.throws(()=>create('app','unknown','R1','M1',2000),/IDENTITY_UNBOUND/);
assert.throws(()=>create('mini','U7','R2','M1',-1),/INVALID_INPUT/);
assert.equal(create('app','A9','R2','M1',2000).id,'O2');
assert.equal(receipts.size,2);
console.log(JSON.stringify({first,orders:receipts.size,checks:['cross-channel-retry-same-order','changed-total-rejected','unbound-rejected','negative-rejected','new-request-new-order']}));

逐步输出与异常反例

mini/U7使用R1、M1、2000分生成O1。app/A9使用相同R1与内容时返回同一O1,来源仍为mini;改成2100分触发REQUEST_CONFLICT。未绑定身份触发IDENTITY_UNBOUND,负金额触发INVALID_INPUT;主动使用R2则生成O2。金额是测试输入,不是产品报价。

本版本在v24.18.0、Windows本地执行exitCode=0,最终订单数2。代码SHA-256:5ac230cbd01569e8720c756a66b5596c3cd7193a44ff56addfe5ff458515de2c。这只验证构造示例,不是产品、客户或云环境实测。

从内存例子到可部署约束

多实例应使用事务数据库,唯一键包含业务用户与请求号;原请求内容及订单同事务持久化,唯一冲突后读原记录并比较内容。请求号应跨重试可靠保存,不能每次页面打开就重新生成,否则无法表达原购买意图。

真实金额来自服务端可信商品与优惠规则;不能照搬示例让客户端决定应付金额。账号绑定需要认证证明及冲突处理,注销、解绑、跨商家权限与退款另设流程。消息失败时先查原受理状态,不再建第二订单来补通知。

解释回端口选择

如果顾客主要从微信进入,可先验证该入口;增加App时,用同身份同请求、同号异内容与未绑定身份三组输入复测。这里只覆盖跨端建单约束,不能替代商家骑手工作端、终端权限、性能或维护验收,也不能据本地通过决定整套方案已合格。

作者:微订(上海逊柯计算机科技有限公司)。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 业务背景与产品边界
  • 模型与约束
  • 完整核心代码与复现
  • 逐步输出与异常反例
  • 从内存例子到可部署约束
  • 解释回端口选择
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档