首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >多商户外卖系统二开版本评审:配置修订号冲突校验

多商户外卖系统二开版本评审:配置修订号冲突校验

原创
作者头像
微订技术团队
修改于 2026-11-04 17:01:27
修改于 2026-11-04 17:01:27
930
举报

选择多商户外卖系统二次开发版本时,可把区域规则修改转成可复现的验收用例。两个管理员读取同一修订号,后续写入需要原子识别冲突,并在重试时返回原请求结果。本文用SQLite事务和条件更新实现这项检查,说明扩展代码如何接续既有业务规则。

业务场景与数据模型

假设区域平台需要调整商家抽成。目标版本若已有配置,先按配置演示,确需扩展时再核接口及代码修改。这里专门处理配置写入的一致性,订单、退款和结算需要接续原规则,分别安排业务回归。示例区域A、B与费率都是测试输入。

rules以region_key为主键,保存revision和rate_bps。费率使用整数基点,0到10000对应零到全额。requests以request_id为主键,保存原区域、预期修订号、费率和首次成功修订号。请求编号的作用域在本例中为整个服务。

输入的request_id、key为非空字符串,修订号和费率严格接收整数,布尔值和小数被拒绝。修订号限制在SQLite有符号64位整数的安全递增范围内。生产系统应从可信身份确定区域操作权限,并对接入长度和审计要求作具体配置。

事务状态与返回规则

条件

事务行为

输出

新区域且预期修订号0

创建初始记录并条件更新

返回1并记录请求

原修订号匹配

原子更新且登记请求

返回递增版本

基于旧版本写入

回滚整个事务

REVISION_CONFLICT

同请求编号参数一致

读取首次请求结果

返回首次版本

同请求编号参数改变

回滚且保留原记录

REQUEST_CONFLICT

把检查与写入放入同一事务

BEGIN IMMEDIATE在事务开始申请写锁,示例两个连接共享同一文件。首次写入创建区域初始值,再用WHERE revision条件更新。影响行数必须为1,随后保存请求记录,最后统一提交。失败路径rollback会撤回初始记录和配置修改。

已有请求先比较业务参数。原请求在后来配置已经升级的情况下重试,仍返回首次结果。返回修订号1只表示该请求原来成功形成版本1,当前配置可能已经是版本2,客户端需要区分请求回执和当前资源查询。

完整实现及反例

代码语言:python
复制
import json
import sqlite3
import tempfile
from pathlib import Path


def change(connection, request_id, key, expected_revision, rate_bps):
    if not isinstance(request_id, str) or not request_id.strip():
        raise ValueError('INVALID_REQUEST')
    if not isinstance(key, str) or not key.strip():
        raise ValueError('INVALID_KEY')
    if type(expected_revision) is not int or not 0 <= expected_revision < 9223372036854775807:
        raise ValueError('INVALID_REVISION')
    if type(rate_bps) is not int or not 0 <= rate_bps <= 10000:
        raise ValueError('INVALID_RATE')
    connection.execute('BEGIN IMMEDIATE')
    try:
        prior = connection.execute(
            'SELECT region_key, expected_revision, rate_bps, result_revision FROM requests WHERE request_id=?',
            (request_id,),
        ).fetchone()
        if prior:
            if prior[:3] != (key, expected_revision, rate_bps):
                raise ValueError('REQUEST_CONFLICT')
            connection.commit()
            return prior[3]
        connection.execute(
            'INSERT OR IGNORE INTO rules(region_key, revision, rate_bps) VALUES (?, 0, 0)', (key,),
        )
        updated = connection.execute(
            'UPDATE rules SET revision=revision+1, rate_bps=? WHERE region_key=? AND revision=?',
            (rate_bps, key, expected_revision),
        )
        if updated.rowcount != 1:
            raise ValueError('REVISION_CONFLICT')
        result_revision = expected_revision + 1
        connection.execute(
            'INSERT INTO requests VALUES (?, ?, ?, ?, ?)',
            (request_id, key, expected_revision, rate_bps, result_revision),
        )
        connection.commit()
        return result_revision
    except Exception:
        connection.rollback()
        raise


def reject(call, expected):
    try:
        call()
    except ValueError as error:
        assert str(error) == expected
    else:
        raise AssertionError('request unexpectedly accepted')


with tempfile.TemporaryDirectory() as directory:
    database = Path(directory) / 'rules.sqlite'
    first = sqlite3.connect(database, timeout=5)
    second = sqlite3.connect(database, timeout=5)
    try:
        first.executescript('''
            CREATE TABLE rules (
                region_key TEXT PRIMARY KEY NOT NULL,
                revision INTEGER NOT NULL CHECK(revision >= 0),
                rate_bps INTEGER NOT NULL CHECK(rate_bps BETWEEN 0 AND 10000)
            );
            CREATE TABLE requests (
                request_id TEXT PRIMARY KEY NOT NULL,
                region_key TEXT NOT NULL REFERENCES rules(region_key),
                expected_revision INTEGER NOT NULL,
                rate_bps INTEGER NOT NULL,
                result_revision INTEGER NOT NULL
            );
        ''')
        assert change(first, 'R1', 'A', 0, 500) == 1
        reject(lambda: change(second, 'R2', 'A', 0, 900), 'REVISION_CONFLICT')
        assert second.execute('SELECT revision,rate_bps FROM rules WHERE region_key=?', ('A',)).fetchone() == (1, 500)
        assert change(second, 'R3', 'A', 1, 600) == 2
        assert change(first, 'R1', 'A', 0, 500) == 1
        reject(lambda: change(first, 'R1', 'A', 0, 700), 'REQUEST_CONFLICT')
        assert first.execute('SELECT COUNT(*) FROM requests').fetchone()[0] == 2
        reject(lambda: change(first, 'R4', 'A', 2, 10001), 'INVALID_RATE')
        reject(lambda: change(first, 'R4', 'A', 2, 0.5), 'INVALID_RATE')
        reject(lambda: change(first, 'R4', 'A', True, 500), 'INVALID_REVISION')
        reject(lambda: change(first, 'R4', '', 0, 500), 'INVALID_KEY')
        reject(lambda: change(first, '', 'A', 2, 500), 'INVALID_REQUEST')
        reject(lambda: change(second, 'R5', 'B', 1, 300), 'REVISION_CONFLICT')
        assert first.execute('SELECT COUNT(*) FROM rules WHERE region_key=?', ('B',)).fetchone()[0] == 0
        assert change(second, 'R6', 'B', 0, 0) == 1
        assert change(first, 'R7', 'B', 1, 10000) == 2
        output = dict(first.execute('SELECT region_key,revision FROM rules'))
        print(json.dumps({'assertions': 16, 'revisions': output, 'connections': 2, 'staleWrite': 'REVISION_CONFLICT', 'retryResult': 1}))
    finally:
        second.close()
        first.close()

复现步骤与实际输出

只依赖Python标准库sqlite3、tempfile、pathlib和json。保存为revision-example.py,执行python revision-example.py。本次Windows本地Python 3.10.11、SQLite 3.40.1,退出码0,16项断言通过。临时数据库在示例结束后由上下文清理。

{"assertions": 16, "revisions": {"A": 2, "B": 2}, "connections": 2, "staleWrite": "REVISION_CONFLICT", "retryResult": 1}

逐步核对正常与异常

第一个连接用R1把A从版本0改到1、费率500。第二个连接仍带预期0提交R2,得到REVISION_CONFLICT,A保持版本1。第二个连接改带预期1提交R3,A进入版本2。再次执行R1仍返回1,请求表只保留两次成功写入。

同编号R1更换费率触发REQUEST_CONFLICT。超范围或小数费率、布尔修订号、空区域及空请求标识被拒绝。B第一次带预期1写入时,初始记录与更新同在事务,冲突回滚后B不存在。随后预期0可正常创建,0与10000基点边界均有覆盖。

这两个连接按顺序执行,模拟管理者持有陈旧版本的情况。运行结果验证本地SQL条件更新、事务回滚和重试记录,没有进行并发负载、崩溃恢复或真实系统测试。数据库锁超时与连接失败需要由上游识别,结果未确定时沿同一请求编号核对。

如何用于版本采购验收

评审时要求目标版本演示相同场景,保留配置读值、请求参数、状态和最终结果。还要测试旧订单规则、权限、退款和结算是否接续。SQLite示例验证一项实现思路,目标项目的数据库、事务隔离、账号、存储与备份恢复需要相应环境验证。

源码交付和二次开发权限应列明文件、依赖与构建方法,升级后重跑原业务用例。版本选择结合需求满足方式、可维护性和实际交付资料,局部程序结果不能自动证明整套系统适配。

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

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

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

目录
  • 业务场景与数据模型
  • 事务状态与返回规则
  • 把检查与写入放入同一事务
  • 完整实现及反例
  • 复现步骤与实际输出
  • 逐步核对正常与异常
  • 如何用于版本采购验收
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档