feat(chat): W41 未连接时先探一次健康端点,通就回落 REST 发出去,不通才进队列
主人第二次定案:「未连接状态你就不能拿 httpAPI 尝试吗?」→ 明确为「可以走 health 端点 验证连通性」。W40 的「未连接就进队列」保留为最后一级,队列、气泡小标与那套柔和告知原样复用。 ## 为什么先探测再决定,而不是直接排队 网关只有一条:POST /api/v1/chat 与 /ws 是同一个端口上的两条车道。握手被挡(代理不转发 Upgrade、中间设备掐长连接)时 REST 往往是通的,直接排队等于把「WS 这条车道暂时不通」当成 「后端不可用」,让用户白等一次重连——而那一刻这条消息本来就能发出去。 ## 三级阶梯(判据是纯函数 sendRouteFor,见 ui/chat/SendFallback.kt) 1. WS 就绪 → 照旧走 WS。成功路径的字节与状态机一个字没变:sendThroughLadder 的第一件事 仍然是 sendChat,前面不加任何判断、不挂任何起点。 2. 未就绪 → 探一次 GET /api/v1/health(2.5 秒整通上限,只看 2xx;失败 / 超时 / 抛异常一律 按不通处理,fail-closed)。通 → 用既有的 POST /api/v1/chat 把这条发出去(短文本也走 REST);不通 → 进队列。 3. 回落 REST 失败:网络类与超时 → 进队列;服务端给过裁决的(明确拒绝、401)与配置类错误 (证书 / 明文被禁 / 地址格式 / 解析不出来)仍旧回滚加文案,不装成「还没连上」。 4. 补发走同一个函数(fromQueue=true):阶梯只有一份实现,两条路不会分叉成两种行为。 Offline 那一档直接进队列、连探测都不做:它的语义是握手 401 且续期也失败(会话已知是死的), 而健康端点不鉴权(Cyrene_v2/internal/services/gateway/routes.go:51)必然回 200 —— 探了只会 发出一次注定 401 的 REST,把 W40 那句「已排队不会丢」换成一次回滚加一句误导人的鉴权错误。 ## 为什么重发是安全的 回落与补发用的都是那条乐观气泡的 localId,也就是上行幂等键 clientMsgId,一个字节都不新铸。 服务端按 (session_id, client_msg_id) 唯一索引仲裁:重复到达只回 chat_duplicate / duplicate:true,不再落库、不再起回合(契约 v0.56 §3.13)。于是「超时了但服务端其实已经 收下」这种最难判的情况不需要本端猜。 ## 回复怎么回来 那一刻没有 WS 连接,内容帧没人收,靠重连时的正向增量补回来(gapCursor → catchUp → loadAfter → absorb)。本 VM 从没连上过时 gapCursor 是 null、重连那一步什么都不会拉,所以 回落发出去且服务端已受理之后补记一次游标(armGapCursorForFallback,只在它还是 null 时补)。 ## 其他 - 探测结果不缓存,每条消息最多探一次(只有 WS 没把帧交出去才问);WS 就绪那条路上一次探测 都不发。 - 补发从同步循环改成单飞协程:阶梯里要探测、要发 REST,会挂起,「两个触发点都在主线程上 所以不可能交错」这条旧论证不成立 ⇒ 改用 drainingQueue 单飞闸加串行出队,出队仍在有结论 之后。 - 离线时的上界:探测不通就直接进队列、一次 REST 都不会发,用户最多等探测那一段(约 2.5 秒); 回落那一次 REST 另有 10 秒上限(stream:true 的回执本该毫秒级回来)。 - 新增测试 SendFallbackTest(17 例):优先级、fail-closed、Offline 不探测、回落失败分档、 探测超时落在 2 到 3 秒、有图或有附件的消息压根不进这层阶梯。 - devtools/fake-backend 补上 GET /api/v1/health,否则本机跑假后端时这条阶梯会退化成直接排队。
This commit is contained in:
1 parent
7b981dd88a
commit
9f77fb469c
11 files changed
+954
-58
No files matched your search
@@ -1042,6 +1042,13 @@ class Handler(BaseHTTPRequestHandler):
|
||||
self._parse_json_body()
|
||||
self._ok({"revoked": True, "revokedCount": 1})
|
||||
return
|
||||
if path == "/api/v1/health" and method == "GET":
|
||||
# 存活探针:**不鉴权**、不做任何重活(对齐真后端
|
||||
# `internal/services/gateway/rest.go` 的 handleHealth,路由挂在公开那一组)。
|
||||
# 客户端 W41 起在「WS 未就绪」时先探它一次,通就回落 REST 发那条消息——
|
||||
# 假后端没有这个端点的话,探测会吃到 404、一律判「不通」,W41 那条阶梯在本机就退化成 W40。
|
||||
self._ok({"status": "ok"})
|
||||
return
|
||||
if path == "/api/v1/meta/enums" and method == "GET":
|
||||
self._ok(ENUMS_PAYLOAD)
|
||||
return
|
||||
|
||||
Reference in new issue
Block a user