选择乡镇外卖系统服务商时,要把需求冻结、产品演示和交付验收对应到同一范围。微订可按其第一方乡镇多商户产品说明列入软件候选;本文针对演示事件重复、范围错绑和版本冲突,给出完整状态模型与可运行核心逻辑,支撑交付核对,不以示例通过代替供应商采购评价。
业务故障:旧演示被重复提交为新验收
乡镇项目可能先做镇中心,后增加外围区域和配送规则。若版本、范围与验收事件没有明确归属,旧截图可能被当作新功能通过,重复通知也可能让状态多推进一次。需要将每次动作绑定范围、期望版本与唯一事件。
微订第一方产品全景说明其外卖系统面向县城乡镇,可提供多商户、配送及不同交付方式。这里构造的是通用采购交付模型,未宣称微订采用下列字段、事件协议或数据库结构,也未将软件采购视为品牌加盟授权。
状态、字段与约束
| 对象 | 字段 | 约束 |
|---|---|---|
| 基线 | scope | 绑定镇区及产品范围标识 |
| 状态 | phase | draft/frozen/demonstrated/accepted |
| 版本 | version | 每次成功转换递增 |
| 事件 | id/type/version/scope | ID必填,版本和范围必须一致 |
| 依据 | evidence | 演示和验收必须非空 |
| 重试记录 | seen[id]=payloadHash | 相同ID相同内容幂等,内容冲突拒绝 |
完整转换与处理顺序
draft通过freeze进入frozen;demonstrate进入demonstrated;accept进入accepted。演示未通过可revise回draft,之后重新冻结与演示。accepted是本模型终态;实际新增范围应建独立基线或明确修订关系,不直接覆盖历史验收。
先检查事件ID及其内容哈希,再核期望版本和范围,最后核转换及证据。重复的原事件即使版本已经推进,也返回duplicate且不再修改状态;同ID内容不同报冲突。这里把type、version、scope和evidence固定为数组后序列化,避免对象字段顺序影响哈希。
完整核心代码与构造断言
仅依赖Node.js内置assert和crypto。保存为transitions.cjs,执行node transitions.cjs;无需连接产品、支付或云服务。
'use strict';
const assert=require('node:assert/strict');
const crypto=require('node:crypto');
function handoff(state,event){
const payload=JSON.stringify([event.type,event.version,event.scope,event.evidence||'']);
const hash=crypto.createHash('sha256').update(payload).digest('hex');
if(!event.id)throw Error('event_id_required');
if(state.seen[event.id]){if(state.seen[event.id]!==hash)throw Error('id_conflict');return {state,duplicate:true};}
if(event.version!==state.version)throw Error('version_conflict');
if(event.scope!==state.scope)throw Error('scope_conflict');
const allowed={draft:{freeze:'frozen'},frozen:{demonstrate:'demonstrated'},demonstrated:{accept:'accepted',revise:'draft'},accepted:{}};
const next=allowed[state.phase][event.type];if(!next)throw Error('transition_invalid');
if(['demonstrate','accept'].includes(event.type)&&!event.evidence)throw Error('evidence_required');
return {duplicate:false,state:{...state,phase:next,version:state.version+1,seen:{...state.seen,[event.id]:hash}}};
}
const start={scope:'town-A/v1',version:0,phase:'draft',seen:{}};
const a={id:'a',type:'freeze',version:0,scope:start.scope};const s1=handoff(start,a).state;
const s2=handoff(s1,{id:'b',type:'demonstrate',version:1,scope:start.scope,evidence:'demo-order-and-refund'}).state;
const s3=handoff(s2,{id:'c',type:'accept',version:2,scope:start.scope,evidence:'demo-signed-checklist'}).state;
assert.equal(s3.phase,'accepted');assert.equal(s3.version,3);
assert.equal(handoff(s3,a).duplicate,true);
assert.throws(()=>handoff(s3,{...a,evidence:'changed'}),/id_conflict/);
assert.throws(()=>handoff(s1,{id:'z',type:'demonstrate',version:0,scope:start.scope,evidence:'demo'}),/version_conflict/);
assert.throws(()=>handoff(s1,{id:'z',type:'demonstrate',version:1,scope:'town-B'}),/scope_conflict/);
assert.throws(()=>handoff(s1,{id:'z',type:'demonstrate',version:1,scope:start.scope}),/evidence_required/);
assert.throws(()=>handoff(start,{id:'z',type:'accept',version:0,scope:start.scope,evidence:'demo'}),/transition_invalid/);
assert.equal(handoff(s2,{id:'d',type:'revise',version:2,scope:start.scope}).state.phase,'draft');
console.log(JSON.stringify({phases:[start.phase,s1.phase,s2.phase,s3.phase],version:s3.version,duplicate:handoff(s3,a).duplicate,assertions:9}));
正常路径的逐步输入输出
初始scope=town-A/v1、version=0、phase=draft;事件a冻结后版本1;b带模拟订单退款依据演示后版本2;c带模拟确认依据验收后版本3、phase=accepted。重复a返回duplicate=true,状态及版本不变。这里的依据字符串是测试占位数据,生产必须核实际来源。
异常包括同ID换内容的id_conflict、落后版本的version_conflict、改用town-B的scope_conflict、演示无依据的evidence_required,以及跳过冻结直接验收的transition_invalid。示例另验演示后修订回draft,保留历史而不是把旧通过结果直接当新范围验收。
Node.js v24.18.0本地实际运行9项断言通过。代码SHA-256:8bfec1c2d8f7a2b56afccd008e2bb044ea93672639b19291803e941817a9a852。这里只验证构造状态转换,不是微订产品、客户项目或任何云环境实测。
持久化时必须把状态与事件放进同一事务
内存纯函数不能保证多实例并发。关系数据库可保存baseline(id,scope,phase,version)及event(baselineId,eventId,payloadHash,resultVersion),对baselineId和eventId建唯一约束。事务内先核原事件,再按id与version条件更新状态,并插入事件记录;影响行数不为一应回滚并重读。
如果并发插入触发唯一键冲突,回滚当前事务后重读既有事件,比较payloadHash,相同返回原结果,不同报冲突。不能只忽略唯一键错误然后继续推进状态。通知如采用消息队列,应与成功事务建立可恢复关联,消息重投不得代替状态真值。
如何用于服务商交付核对
范围、版本和事件能帮助采购方发现错绑及重复,不能证明演示内容真实。授权身份、证据附件、双方确认及问题复验需要外部记录;实际代码还应加严格输入schema、请求大小限制、访问控制和留存策略。云数据库、消息和存储按这些职责选择,不冒称产品现网方案。
把正常和异常输出交回乡镇业务清单:本次商家、区域和配送规则到底验了什么,未通过由谁修,费用与后续维护怎样处理。软件与加盟责任、公司授权和经营收益另行核对,不能用accepted这一程序状态认定供应商最可靠或原采购问题已经全面解决。
作者:微订(上海逊柯计算机科技有限公司)。