fix(integrations): 「接收中」跨进程一致,不再跟着 gunicorn 进程抖动
CI / backend (push) Canceled after 0s

上一版用 `manager.polling()` 判"有没有人在收消息",那是进程内状态:请求落到持有者
回答 true、落到备用进程回答 false,页面上就会每两秒闪一次"已停止"。

改成查数据库里的 advisory lock(`pg_locks`,objsubid=2 的两段式锁),谁抢到租约所有
进程都看得见。`lease_held()` 自己开一条短会话查,不复用请求会话——接口里常在
`await session.commit()` 之后才拼返回值,那时原事务已经关了。
This commit is contained in:
Steven
2026-11-05 23:39:33 +08:00
parent 2170a0664d
commit f6e7616756
3 changed files with 43 additions and 8 deletions
+8 -8
View File
@@ -33,7 +33,7 @@ from ..errors import AppError, ErrorCode
from ..models_integrations import BotChannel, BotMessage
from ..schemas import ok
from ..services import wechat_ilink
from ..services.wechat_bot import manager
from ..services.wechat_bot import lease_held, manager
from ..stores import get_store
admin_router = APIRouter(prefix="/workspaces", tags=["外部接入"])
@@ -59,15 +59,15 @@ def render_qr_svg(payload: str) -> str:
return f"data:image/svg+xml;base64,{encoded}"
def _channel_out(channel: BotChannel) -> dict[str, Any]:
async def _channel_out(channel: BotChannel) -> dict[str, Any]:
return {
"id": str(channel.id),
"kind": channel.kind,
"name": channel.name,
"status": channel.status,
"connected": bool(channel.bot_token_enc) and channel.status == "active",
# 只有抢到租约、真在拉消息的进程才算"接收中";备用进程的任务在等键,不算。
"polling": manager.polling(channel.id),
# 数据库级租约:谁在真收消息所有进程都认,页面不会跟着进程抖动。
"polling": await lease_held(channel.id),
"ilink_user_id": channel.ilink_user_id,
"last_seen_at": channel.last_seen_at,
"last_error": channel.last_error,
@@ -91,7 +91,7 @@ async def list_integrations(ws_id: uuid.UUID, ctx: AdminDep, session: SessionDep
.where(BotChannel.workspace_id == ws_id)
.order_by(BotChannel.created_at.desc())
)
return ok({"items": [_channel_out(c) for c in rows.scalars().all()]})
return ok({"items": [await _channel_out(c) for c in rows.scalars().all()]})
@admin_router.post("/{ws_id}/integrations/wechat/qr")
@@ -168,7 +168,7 @@ async def poll_wechat_login(ws_id: uuid.UUID, qrcode_id: str, ctx: AdminDep, ses
await get_store().delete(_qr_key(ws_id))
await manager.start(channel.id)
return ok({"status": "confirmed", "channel": _channel_out(channel)})
return ok({"status": "confirmed", "channel": await _channel_out(channel)})
@admin_router.patch("/{ws_id}/integrations/{channel_id}")
@@ -193,7 +193,7 @@ async def update_integration(
await manager.start(channel.id)
else:
await manager.stop(channel.id)
return ok({"channel": _channel_out(channel)})
return ok({"channel": await _channel_out(channel)})
@admin_router.delete("/{ws_id}/integrations/{channel_id}")
@@ -211,7 +211,7 @@ async def disconnect_integration(
channel.status = "pending"
channel.last_error = None
await session.commit()
return ok({"channel": _channel_out(channel)})
return ok({"channel": await _channel_out(channel)})
@admin_router.get("/{ws_id}/integrations/{channel_id}/messages")