首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >外卖跑腿系统采购验收:范围变更后如何阻止旧版本通过

外卖跑腿系统采购验收:范围变更后如何阻止旧版本通过

原创
作者头像
微订技术团队
修改于 2026-11-14 16:56:28
修改于 2026-11-14 16:56:28
130
举报

外卖跑腿系统采购中的技术验收,需要让每项通过结果对应同一需求范围版本。范围从外卖扩展到跑腿后,旧结果只能作历史;缺失、失败或未知项不能当作新范围通过。下面给出单进程顺序模型和损坏输入反例,说明范围修订、结果记录及确认的输入输出。

采购问题为什么要落到范围版本

采购时先把外卖、帮买、帮送、终端及分站范围列清。演示结果绑定同一范围版本,新增业务以后重核受影响部分,才能解释最终买到什么。范围通过只是项目验收的一项条件,不能代替签约、付款、许可或正式上线。

状态与字段约束

字段或动作

示例约束

owner

从服务端认证上下文取得的项目验收责任人

revision

范围变更时递增,旧请求返回conflict

scope

非空需求集合,本例为takeout与errands

evidence

当前范围每项记录pass、fail或unknown

accepted

仅所有需求通过才为真;改范围或写结果时撤销

完整顺序模型

依赖只有Python标准库,保存为acceptance_model_v2.py并运行python acceptance_model_v2.py。示例为单进程顺序模型,未提供HTTP认证、持久化或并发控制。生产实现需在实际存储事务内核版本,不能把本例直接当作并发安全服务。

代码语言:python
复制
from dataclasses import dataclass, field

@dataclass
class Project:
    owner: str
    revision: int = 1
    scope: frozenset = frozenset({'takeout'})
    evidence: dict = field(default_factory=dict)
    accepted: bool = False

    def validate(self):
        if not isinstance(self.owner, str) or not self.owner:
            raise ValueError('invalid_owner')
        if type(self.revision) is not int or self.revision < 1:
            raise ValueError('invalid_revision')
        if not isinstance(self.scope, frozenset) or not self.scope or not all(
                isinstance(x, str) and x for x in self.scope):
            raise ValueError('invalid_scope')
        if not isinstance(self.evidence, dict) or any(
                k not in self.scope or v not in {'pass','fail','unknown'}
                for k, v in self.evidence.items()):
            raise ValueError('invalid_evidence')
        if type(self.accepted) is not bool or (self.accepted and any(
                self.evidence.get(k) != 'pass' for k in self.scope)):
            raise ValueError('invalid_acceptance')

    def change(self, actor, expected, scope):
        self.validate()
        if type(expected) is not int or expected < 1:
            return 'invalid_expected'
        if actor != self.owner:
            return 'forbidden'
        if expected != self.revision:
            return 'conflict'
        if not scope or not all(isinstance(x, str) and x for x in scope):
            return 'invalid_scope'
        self.scope = frozenset(scope)
        self.revision += 1
        self.evidence = {}
        self.accepted = False
        return 'changed'

    def record(self, actor, expected, item, result):
        self.validate()
        if type(expected) is not int or expected < 1:
            return 'invalid_expected'
        if actor != self.owner:
            return 'forbidden'
        if expected != self.revision:
            return 'conflict'
        if item not in self.scope or result not in {'pass', 'fail', 'unknown'}:
            return 'invalid_result'
        self.evidence[item] = result
        self.accepted = False
        return 'recorded'

    def accept(self, actor, expected):
        self.validate()
        if type(expected) is not int or expected < 1:
            return 'invalid_expected'
        if actor != self.owner:
            return 'forbidden'
        if expected != self.revision:
            return 'conflict'
        if any(self.evidence.get(k) != 'pass' for k in self.scope):
            return 'incomplete'
        self.accepted = True
        return 'accepted'

if __name__ == '__main__':
    p = Project('buyer-A')
    assert p.accept('buyer-A', 1) == 'incomplete'
    assert p.record('buyer-A', 1, 'takeout', 'pass') == 'recorded'
    assert p.accept('buyer-A', 1) == 'accepted'
    assert p.change('other', 1, {'takeout', 'errands'}) == 'forbidden'
    assert p.revision == 1 and p.accepted
    assert p.change('buyer-A', 1, {'takeout', 'errands'}) == 'changed'
    assert not p.accepted and p.evidence == {}
    assert p.accept('buyer-A', 1) == 'conflict'
    assert p.record('buyer-A', 2, 'takeout', 'pass') == 'recorded'
    assert p.record('buyer-A', 2, 'errands', 'unknown') == 'recorded'
    assert p.accept('buyer-A', 2) == 'incomplete'
    assert p.record('buyer-A', 2, 'errands', 'pass') == 'recorded'
    assert p.accept('buyer-A', 2) == 'accepted'
    import copy
    for patch in [dict(owner=''), dict(revision=True),
                  dict(evidence={'takeout':'pass'}),
                  dict(evidence={'other':'pass'})]:
        damaged = copy.deepcopy(p)
        for key,value in patch.items():
            setattr(damaged,key,value)
        before = copy.deepcopy(damaged)
        try:
            damaged.accept('buyer-A',2)
        except ValueError:
            pass
        else:
            raise AssertionError('damaged object accepted')
        assert damaged == before
    assert p.accept('buyer-A', True) == 'invalid_expected'
    print('18 assertions passed; damaged objects unchanged; accepted=True')

正常输入与范围变更反例

初始范围版本1只有takeout,缺结果时accept返回incomplete。记录takeout通过后返回accepted。随后以责任人身份增加errands,范围变2、结果清空、accepted撤销。旧版本1的验收返回conflict,新版本两个需求均通过后才重新accepted。

越权变更返回forbidden,原版本及通过状态不变;unknown结果使验收仍不完整。record只接受本范围需求和限定结果值。接口层还需严格验证字段类型及内容长度,actor从认证上下文取,不能信任客户端自报身份。

本地验证与业务意义

本次18项断言运行通过,输出revision=2、accepted=True。记录包含本地Python环境、代码SHA和实际输出。它只证明这些构造顺序场景,不是微订产品、云部署、并发或客户测试。

采购双方可按同样思路核演示版本与需求。变更终端、分站、支付或帮买规则后,应重新确认哪些用例需要重做,并保留旧结果。接口超时先查询原项目状态,不能靠另建项目掩盖验收不明。

生产接入前还需补齐

持久化时将范围修订、结果写入及验收放入可解释的并发控制机制,记录责任人、配置、版本与操作历史。通知和部署动作有自己的幂等与权限约束,accepted字段不能直接触发真实付款。

最终采购仍按官方主体、渠道授权、收款、交付、许可及维护清单核对。这个版本模型只解决旧验收误用的问题,不能证明渠道可信、功能完整或性能优于其他产品。

损坏对象与布尔版本的前置检查

修订在每个动作前核对非空责任人、正整数版本、非空需求集合、结果字段和通过状态的一致关系。构造空责任人、布尔版本、通过却缺结果,以及范围外结果四类对象,均拒绝且原对象不变。预期版本为布尔值也明确返回invalid_expected,避免Python中True与1相等导致误匹配。

这仍是调用边界的顺序模型:对象属性可被进程内代码直接修改,未提供持久化安全保证。真实服务须从可信存储和认证上下文读取对象,在事务中保护版本及结果,并独立校验接口输入,不能直接信任客户端传入的责任人或通过标识。

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

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

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

目录
  • 采购问题为什么要落到范围版本
  • 状态与字段约束
  • 完整顺序模型
  • 正常输入与范围变更反例
  • 本地验证与业务意义
  • 生产接入前还需补齐
  • 损坏对象与布尔版本的前置检查
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档