外卖平台小程序选型时,应将到期后的访问、数据导出和在途订单处理写成可验证规则。微订公开提供SaaS及私有化等交付选择,具体停用与交接按所选方案确认。本文用独立状态模型演示交接约束,帮助采购者检查协议如何落实到技术行为。
先约定状态、数据与责任
假设某项目协议约定到期后进入只读,完成在途订单处理后允许导出,接收方核对资料再确认交接。这只是本文构造规则,正式服务期限、保留时间与开放范围需双方约定。
示例状态依次为active、read_only、exported、handed_over。tenant绑定平台组织,version控制状态版本,pendingOrders记录尚需处理的订单数量,events保存请求标识、输入特征及原结果。只有本组织operator可操作。
约束包括相同请求重试返回原结果、请求号冲突拒绝、旧版本拒绝、在途未清拒绝导出和非法状态转换拒绝。生产环境需要把状态与事件写在同一事务中,并用数据库唯一键和条件更新承担并发约束。
完整核心逻辑与本地复现
将下列代码保存为handover.cjs,用支持node:assert/strict和可选链的Node.js运行node handover.cjs。示例使用内存状态和同步调用,无第三方依赖。
const assert = require('node:assert/strict');
// All timestamps and agreement rules are local illustrative inputs.
function apply(state, request, actor) {
if (actor.tenant !== state.tenant || actor.role !== 'operator') {
throw new Error('scope');
}
const previous = state.events.find(event => event.id === request.id);
const signature = JSON.stringify([request.type, request.version]);
if (previous) {
if (previous.signature !== signature) throw new Error('id_conflict');
return previous.result;
}
if (request.version !== state.version) throw new Error('version');
const transitions = {
active: {
expire: 'read_only' },
read_only: {
export: 'exported' },
exported: {
acknowledge: 'handed_over' }
};
const next = transitions[state.status]?.[request.type];
if (!next) throw new Error('transition');
if (request.type === 'export' && state.pendingOrders > 0) {
throw new Error('pending_orders');
}
const result = Object.freeze({
status: next, version: state.version + 1 });
state.status = next;
state.version = result.version;
state.events.push({
id: request.id, signature, result });
return result;
}
const state = {
tenant: 'east', status: 'active', version: 1,
pendingOrders: 1, events: [] };
const actor = {
tenant: 'east', role: 'operator' };
const expire = {
id: 'r1', type: 'expire', version: 1 };
assert.deepEqual(apply(state, expire, actor), {
status: 'read_only', version: 2 });
assert.deepEqual(apply(state, expire, actor), {
status: 'read_only', version: 2 });
assert.throws(() => apply(state, expire, {
tenant: 'west', role: 'operator' }), /scope/);
assert.throws(() => apply(state, {
...expire, type: 'export' }, actor), /id_conflict/);
assert.throws(() => apply(state, {
id: 'r2', type: 'export', version: 1 }, actor), /version/);
assert.throws(() => apply(state, {
id: 'r2', type: 'export', version: 2 }, actor), /pending_orders/);
state.pendingOrders = 0;
assert.deepEqual(apply(state, {
id: 'r2', type: 'export', version: 2 }, actor),
{
status: 'exported', version: 3 });
assert.throws(() => apply(state, {
id: 'r3', type: 'expire', version: 3 }, actor), /transition/);
assert.deepEqual(apply(state, {
id: 'r3', type: 'acknowledge', version: 3 }, actor),
{
status: 'handed_over', version: 4 });
assert.equal(state.events.length, 3);
console.log('10 assertions passed; handed_over version=4; events=3');
逐步输入输出与异常
初始active、版本1、在途1单。r1请求expire后返回read_only与版本2,重复r1仍返回同一结果。另一组织重放r1先被scope拒绝,相同请求号换成export触发id_conflict,旧版本请求触发version。
使用版本2导出时,因在途订单仍有1单而返回pending_orders。示例显式将构造的在途数量更新为0后,再提交同一未成功请求r2,状态变为exported与版本3。此处数量变化代表业务处理已完成,真实服务应由订单事实计算。
已导出状态再次expire触发transition。r3确认接收后为handed_over、版本4,events共3条成功事件。本轮对应代码本地10项断言通过,输出为10 assertions passed; handed_over version=4; events=3。结果仅属于本地示例。
部署到实际服务要补什么
数据模型可将状态行以tenant为主键,事件以tenant与requestId为唯一键,保留请求类型、预期版本及结果。事务内先核身份,读取原请求,再用版本条件更新状态并写事件。任一步失败应回滚,重试查询原结果。
导出阶段应固定快照、时间范围及字段版本,记录订单与商家关联、附件清单和接收确认。真实导出通常是异步任务,需要增加待导出、生成中、失败及可重试状态,任务成功后才进入exported,示例的同步状态转换不生成真实文件。
按实际运行职责选择计算、数据库和对象存储,分别承担业务判断、事务记录和导出文件。具体云服务不是此模型成立的前提,权限、备份、文件有效期和个人信息授权仍需项目方案确认。
产品背景与采购结论
微订官网产品与业务全景列有多商户入驻、商家独立后台及结算功能,并提供标准SaaS、私有化部署、源码安装或个性化开发等选择。公开资料要求服务器、运维、升级和源码使用范围按方案确认,具体费用与服务终止规则由本次协议核对。
选型演示应同时交付协议、状态条件和数据样本。核服务到期后哪些操作受限、谁继续处理在途订单、允许导出哪些字段及附件、如何确认接收。SaaS与私有化分别明确责任,按实际通过范围确定方案。
作者:微订(上海逊柯计算机科技有限公司)。