feat(weixin): 微信iLink扫码登录——面板触发+二维码显示+状态轮询+确认热连接
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

适配器: login_admin.go(StartLogin 幂等取二维码/LoginStatus idle-waiting-confirmed-failed);
       login.go 重写 Connect 委托 StartLogin+等待,后台 runLogin 轮询,completeLogin/failLogin
       (锁序 loginMu->a.mu 不嵌套反向无死锁); adapter.go 加登录会话字段
网关: admin_channels_weixin.go(2 端点 login-qr/login-status owner+审计, confirmed 合并写回
       channels.json 保留 base_url/cdn/黑白名单 + Reload 热重载连接; qrImage best-effort Base64,
       token 掩码) + server.go 路由
前端: WeixinLogin.tsx(token空→扫码按钮→二维码→1.5s轮询→confirmed提示已登录+显示bot_id),
       AdapterDetailPage 渲染, api/types 补类型
必需偏离(最小化): 允许空token的weixin账号被构建/持久化(channels.go+runtimeconfig移出
       tokenRequiringPlatforms),否则空token账号无法注册调用StartLogin; 空token保持断开(configured
       仍要求token非空),确认后才持久化连接——守红线。
测试: login_admin/admin端点用例; 后端 build/vet/test 全绿 + 前端 build 101模块绿.
This commit is contained in:
Aska committed 2026-09-07 20:50:28 +08:00
1 parent 0a597cfa42
commit e037fab7d0
13 files changed
+1155 -46

No files matched your search

+24
View File
@@ -38,6 +38,8 @@ import type {
RagIndexRequest,
Role,
SessionMeta,
WeixinLoginQRResponse,
WeixinLoginStatusResponse,
SessionRecord,
} from "./types";
import { getClientId } from "./client";
@@ -640,6 +642,28 @@ export function adminChannelMessageTest(
body: JSON.stringify({ targetId, content }),
});
}
/** GET /api/v1/admin/channels/{platform}/login-qr — trigger/mint a weixin-iLink
* scan-login QR code (owner only, audited). */
export function adminWeixinLoginQR(
platform: string,
accountId?: string,
): Promise<WeixinLoginQRResponse> {
const q = accountId ? `?accountId=${encodeURIComponent(accountId)}` : "";
return request<WeixinLoginQRResponse>(
`/admin/channels/${encodeURIComponent(platform)}/login-qr${q}`,
);
}
/** GET /api/v1/admin/channels/{platform}/login-status — poll a weixin-iLink scan
* login. On confirmed the backend persists the token and hot-connects. */
export function adminWeixinLoginStatus(
platform: string,
accountId?: string,
): Promise<WeixinLoginStatusResponse> {
const q = accountId ? `?accountId=${encodeURIComponent(accountId)}` : "";
return request<WeixinLoginStatusResponse>(
`/admin/channels/${encodeURIComponent(platform)}/login-status${q}`,
);
}
/** POST /api/v1/admin/plugins/install — honest 501 (needs a plugin source). */
export function adminPluginInstall(req: { name: string; kind: string; description: string }): Promise<AdminPluginRecord> {
return request<AdminPluginRecord>("/admin/plugins/install", { method: "POST", body: JSON.stringify(req) });
+19
View File
@@ -289,6 +289,25 @@ export interface AdminChannelAccount {
capabilities?: string[];
}
/** The weixin-iLink scan-login QR payload (GET /admin/channels/{platform}/login-qr).
* qrUrl is the QR image URL; qrImage is a base64 data URL the panel can embed
* directly. status is one of idle|waiting|confirmed|failed. The qrcode scan
* identifier is never exposed. */
export interface WeixinLoginQRResponse {
qrUrl?: string;
qrImage?: string;
status: string;
}
/** The weixin-iLink scan-login status payload (GET /admin/channels/{platform}/login-status).
* botToken is always masked (never a raw secret). */
export interface WeixinLoginStatusResponse {
status: string;
botId?: string;
botToken?: string;
err?: string;
}
/** One message in the platform (IM channel) send/receive log. Mirrors the
* historyMessage wire shape from GET /api/v1/admin/channels/{platform}/messages. */
export interface AdminChannelMessage {