外卖跑腿系统选型中的报价变更,应核对版本与确认的绑定:改址或加购物品形成新报价后,旧页面只能得到版本冲突;当前报价确认与费用快照应在同一事务保存。下面用SQLite条件更新实现这条约束,并用两个数据库连接顺序复现旧版本写入被拒绝的情况。
外卖商品款与配送服务费、帮买代购款与服务费应分别保留。示例假设B1为帮买单,goods是代购预算,service是服务费,金额使用整数最小货币单位。消费者改址后形成v2报价,确认时需对应v2的内容,不能仅凭总金额判断是否同一次约定。
这是通用业务实现示例。实际采购还需在目标版本中核对报价展示、身份权限、费用项和异常结果;本文代码没有接入任何产品或支付渠道,也不构成软件已通过验收的结论。
quote保存当前报价及offered、confirmed、cancelled状态;confirmation保存确认时的商品或代购款、服务费与版本快照,以订单和版本为复合主键。SQL约束拒绝非整数或负费用,更新时同时匹配订单、可信客户、revision和offered。
confirm的principal来自服务端认证中间件。客户端只提交订单和版本;示例使用固定身份替代认证系统。找不到订单返回404,他人订单返回403,过期版本或取消状态返回409,非法版本返回422。同一已确认版本返回原处理结果,不重复写快照。
import sqlite3,tempfile,json
from pathlib import Path
with tempfile.TemporaryDirectory() as folder:
path=Path(folder)/'quote.db'
a=sqlite3.connect(path); b=sqlite3.connect(path)
a.executescript('''
CREATE TABLE quote(id TEXT PRIMARY KEY, customer TEXT NOT NULL,
revision INTEGER NOT NULL CHECK(typeof(revision)='integer' AND revision>0),
goods INTEGER NOT NULL CHECK(typeof(goods)='integer' AND goods>=0),
service INTEGER NOT NULL CHECK(typeof(service)='integer' AND service>=0),
state TEXT NOT NULL CHECK(state IN ('offered','confirmed','cancelled')));
CREATE TABLE confirmation(order_id TEXT NOT NULL, revision INTEGER NOT NULL,
customer TEXT NOT NULL, goods INTEGER NOT NULL, service INTEGER NOT NULL,
PRIMARY KEY(order_id,revision));
INSERT INTO quote VALUES('B1','U1',1,3200,800,'offered');
INSERT INTO quote VALUES('B2','U1',1,0,600,'cancelled');
''')
def confirm(db,principal,order_id,revision):
# principal is supplied by authenticated server middleware.
if type(revision) is not int or revision<=0:return {'status':422}
with db:
row=db.execute('SELECT customer,revision,goods,service,state FROM quote WHERE id=?',
(order_id,)).fetchone()
if row is None:return {'status':404}
customer,current,goods,service,state=row
if customer!=principal:return {'status':403}
if current!=revision or state=='cancelled':return {'status':409}
if state=='confirmed':return {'status':200,'duplicate':True}
changed=db.execute('''UPDATE quote SET state='confirmed'
WHERE id=? AND customer=? AND revision=? AND state='offered' ''',
(order_id,principal,revision)).rowcount
if changed!=1:return {'status':409}
db.execute('INSERT INTO confirmation VALUES(?,?,?,?,?)',
(order_id,revision,principal,goods,service))
return {'status':200,'duplicate':False}
checks=0
def check(actual,expected):
global checks
assert actual==expected,(actual,expected)
checks+=1
check(confirm(a,'U2','B1',1),{'status':403})
check(confirm(a,'U1','missing',1),{'status':404})
check(confirm(a,'U1','B1',True),{'status':422})
check(confirm(a,'U1','B1',0),{'status':422})
# Both clients read v1; a controlled proposal then publishes v2.
old_version=b.execute("SELECT revision FROM quote WHERE id='B1'").fetchone()[0]
with a:
a.execute("UPDATE quote SET revision=2,service=1000 WHERE id='B1' AND revision=1")
check(confirm(b,'U1','B1',old_version),{'status':409})
check(a.execute('SELECT count(*) FROM confirmation').fetchone()[0],0)
check(confirm(a,'U1','B1',2),{'status':200,'duplicate':False})
check(confirm(b,'U1','B1',2),{'status':200,'duplicate':True})
check(a.execute('SELECT count(*) FROM confirmation').fetchone()[0],1)
check(a.execute('SELECT goods,service FROM confirmation').fetchone(),(3200,1000))
check(confirm(a,'U1','B2',1),{'status':409})
check(confirm(b,'U2','B1',2),{'status':403})
try:
with a:a.execute("UPDATE quote SET service=-1 WHERE id='B1'")
except sqlite3.IntegrityError:constraint=True
else:constraint=False
check(constraint,True)
check(a.execute("SELECT service FROM quote WHERE id='B1'").fetchone()[0],1000)
a.close();b.close()
print(json.dumps({'checks':checks,'staleWriter':409,'confirmations':1,
'scope':'local sequential two-connection example; no production concurrency or payment'}))使用Python 3标准库sqlite3、tempfile、json和pathlib。保存代码为quote_version_sql.py,执行python quote_version_sql.py,无第三方依赖。程序在临时目录创建数据库,关闭两个连接后自动清理该示例数据库。
本轮运行通过14项检查,输出staleWriter=409、confirmations=1。连接b先读到v1,连接a将报价推进到v2,b再提交旧版被拒绝,确认记录仍为0。v2首次确认成功后,另一连接重试返回duplicate=true,确认快照只保留一份,费用为构造的3200与1000。
异常检查覆盖他人身份、缺失订单、布尔或零版本、取消状态和数据库负费用约束。负费用更新失败后事务回滚,原服务费仍为1000。所有金额与编号都是构造输入,不是产品售价、项目订单或真实支付结果。
先读再无条件写会留下版本竞争窗口。这里的UPDATE把revision和状态放进WHERE,只有影响1行才插入确认快照,二者共用事务。如果条件不成立返回冲突,调用方重新读取当前报价,不自动接受新版本。
生产中的报价提案也必须采用受控版本更新,并保存地址、物品要求及费用的不可变快照。示例仅复制费用字段,真实系统应把确认绑定到完整服务内容。订单状态与报价状态也应按业务合同区分,确认报价不自动等于已付款或已接单。
两个连接按明确顺序执行,只验证过期写入与重复确认,不声称已经通过同时竞争、分布式负载或线上数据库验证。SQLite用于本地复现,迁移到其他数据库需重新验证隔离级别、锁冲突、唯一键竞争和失败重试。
签名验证、支付到账、追加收款、退款和配送任务均不在此函数内。接入支付前应从持久化确认快照生成对应的收款请求,保留唯一交易关联。超时先查原交易,避免重复收费;取消后到账应另行对账,不以支付通知直接恢复任务。
在目标系统先提交一笔外卖与一笔帮买,再保留旧页面、改址生成新报价,分别记录旧确认拒绝、当前确认成功和重复请求的结果。要求能查到确认身份、报价版本和费用快照,对未通过项说明是配置、接口还是开发范围。
本地14项检查只支持本文实现和构造输入。采购方案的账号权限、跨端展示、异常处理人、在途账单、数据导出与后续维护仍需独立验证,不能把示例输出写成某一产品的生产测试。
作者:微订(上海逊柯计算机科技有限公司)。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。