乐于分享
好东西不私藏

AI 软件开发实战教程(十四):让出行信息按规则结束

AI 软件开发实战教程(十四):让出行信息按规则结束

系列第 14 篇:实现编辑、截止、关闭、取消、满员和过期,并处理“状态失效但已经披露的联系方式不能撤回”这一真实边界。

发布、候选和交换完成后,信息不会永远有效。用户会改时间、停止找人、取消行程,也可能因站内确认或站外安排而满员。后台任务还可能晚几秒运行。

K6 的目标不是增加几个状态按钮,而是让页面、服务、查询和定时扫描对每个状态说同一种语言。

1. 编辑权限取决于是否已经交换

首次交换前,发布者可以修改影响匹配的时间和路线。服务增加信息修订版本,让旧候选失效后按新内容重算。

这一步发现 K3 的唯一约束缺少修订版本。如果唯一键只有“车主信息、乘客信息、规则版本”,编辑后即使旧候选失效,数据库仍不允许产生新候选。最终唯一事实改为:

车主信息 + 乘客信息
+ 车主修订版本 + 乘客修订版本
+ 匹配规则版本

旧解释不被改写,新候选能准确指向修改后的两份信息。

发生联系方式交换后,日期、时间窗口和路线被锁定。因为对方已经依据旧内容获得联系方式,静默修改会破坏双方对同一件事的理解。此时仍允许修改尚未到达的匹配截止和合法座位数;路线真的变化,应取消后重新发布。

2. 满员必须记录来源

系统因双方确认达到座位数而满员,可以在配对取消或增加座位后自动恢复。车主在站外已经安排满员,则不能被站内取消动作重新打开。

因此状态之外还保存 system 或 manual 来源。手动满员重新开放时,车主必须明确填写当前可用座位,而且截止和出发时间都还没到。

关闭、取消和过期是终态,不能把旧信息直接恢复为有效。继续寻找必须新建信息,避免已经分享出去的链接在不同出行之间反复复用。

3. 不等待后台任务才能保证正确

大厅查询本来就会排除截止和出发时间已到的信息。K6 再增加幂等扫描,把数据库状态和事件补齐:

  • 到匹配截止产生一次结果事件;
  • 到出发时间把有效或满员信息落为过期;
  • 等待双方反馈的配对到较早出发时间落为确认超时;
  • 已确认配对不会被超时扫描覆盖。

扫描运行两次,事件键和状态都保持唯一。即使 Worker 晚运行,实时查询和操作仍按服务器时刻拒绝过期行为;后台任务负责补全持久化事实,不是权限正确性的唯一依赖。

4. 已披露的微信号不能被页面撤回

关闭或取消会让候选失效,但双方在此前已经合法看到微信号。软件无法让对方忘记,也不应该让页面假装历史交换从未发生。

实现中把候选查询拆成两类:

有效候选:允许新的联系方式交换
参与者历史候选:允许双方查看已经发生的交换结果

浏览器旅程中,车主在“我的信息”关闭发布后,乘客刷新交换结果仍能看到此前的加密快照。新的候选和交换则已经停止。

5. 一次测试失败纠正了错误前提

浏览器测试最初期待双方确认后信息显示“座位已满”。失败页面清楚显示车主发布了两个座位,只确认了一名乘客,正确状态本来就应是“正在寻找”。

测试随后修正前提,而不是把实现改成确认一人就满员。这类失败很有价值:TDD 不代表测试永远正确,失败时仍要回到产品事实判断哪一边错了。

6. 本节点结果

  • 99 个非浏览器测试通过,1 个 PostgreSQL 专属测试明确跳过;
  • 分支覆盖率 88.55%;
  • 静态、类型、迁移和 Django 检查通过;
  • 4 条 Chromium 旅程通过;
  • 编辑修订、状态来源、截止、过期、超时和历史披露都有自动证据;
  • 外部提醒、PostgreSQL 并发、WebKit 和微信真机仍未宣称通过;
  • 结果只形成本地提交,不推送。

下一篇进入 K7:业务事件已经存在后,怎样通过数据库待办可靠调用喵提醒,区分明确失败和结果未知,并避免短时间内连续轰炸用户。

7. 关键代码与操作

下面的简化测试把生命周期扫描执行两次,确认终态和业务事件都不会重复:

deftest_lifecycle_sweep_is_idempotent(expired_trip, pending_pairing):
    sweep_trip_lifecycle(now=NOW)
    sweep_trip_lifecycle(now=NOW)

    expired_trip.refresh_from_db()
    pending_pairing.refresh_from_db()
assert expired_trip.status == TripPost.Status.EXPIRED
assert pending_pairing.status == Pairing.Status.TIMED_OUT
assert BusinessEvent.objects.filter(event_type="trip.matching_closed").count() == 1
assert BusinessEvent.objects.filter(event_type="pairing.timed_out").count() == 1

验证命令:make bugfix TEST=tests/trips/test_lifecycle.py::test_sweep_is_idempotent_for_deadline_expiry_and_pairing_timeout

后台任务可以晚到或重跑,但同一个截止事实不能因此产生两次。

8. 本篇验证摘要

  • 联系方式交换前可以修订时间和路线,交换后会锁定影响双方理解的字段;
  • 关闭、取消和过期是终态,继续寻找必须创建新信息;
  • 实时查询按服务器时间排除失效信息,不依赖后台扫描准时运行;
  • 幂等扫描负责补齐截止、过期和确认超时事件,不重复改变已完成状态;
  • 信息结束后禁止新的交换,但参与者仍能查看此前合法披露的联系方式。

9. 附录:相关工具与仓库

9.1 gstack

仓库:garrytan/gstack 地址:https://github.com/garrytan/gstack

9.2 dev-harness

仓库:Dev-Wiki/dev-harness 地址:https://github.com/Dev-Wiki/dev-harness

9.3 UI UX Pro Max Skill

仓库:nextlevelbuilder/ui-ux-pro-max-skill 地址:https://github.com/nextlevelbuilder/ui-ux-pro-max-skill