From bef4ffcf6f2e3f5dfcfef1a56fc198f6d1c480a5 Mon Sep 17 00:00:00 2001 From: KIMI Date: Sun, 9 Aug 2026 01:28:01 +0800 Subject: [PATCH] =?UTF-8?q?account-server=20=E6=96=B0=E5=A2=9E=20/api/rtc?= =?UTF-8?q?=5Ftoken=EF=BC=9A=E7=94=A8=20IM=20token=20=E6=8D=A2=20LiveKit?= =?UTF-8?q?=20=E8=BF=9B=E6=88=BF=20token=EF=BC=88=E6=89=8B=E6=9C=BA?= =?UTF-8?q?=E7=AB=AF=E8=AF=AD=E9=9F=B3=E9=80=9A=E8=AF=9D=E7=94=A8=EF=BC=8C?= =?UTF-8?q?B-58=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 ++++ account-service/server.js | 48 +++++++++++++++++++++++++++++++++++++++ docker-compose.yaml | 3 +++ 3 files changed, 56 insertions(+) diff --git a/.gitignore b/.gitignore index 35dba22..6aebd05 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,8 @@ components/ .selftest/ data/ + +# Flutter 手机端构建产物 +mobile/.dart_tool/ +mobile/build/ +mobile/.flutter-plugins* diff --git a/account-service/server.js b/account-service/server.js index 6cb61e9..2574d1f 100644 --- a/account-service/server.js +++ b/account-service/server.js @@ -28,6 +28,9 @@ const DATA_FILE = process.env.DATA_FILE || path.join(__dirname, 'data', 'employe const OPENIM_API_URL = (process.env.OPENIM_API_URL || 'http://127.0.0.1:10002').replace(/\/+$/, ''); const OPENIM_SECRET = process.env.OPENIM_SECRET || 'openIM123'; const ADMIN_TOKEN = process.env.ADMIN_TOKEN || 'admin123'; +// LiveKit 语音通话(与 livekit 容器同一对 key/secret,见 .env 的 LIVEKIT_API_KEY / LIVEKIT_API_SECRET) +const LIVEKIT_API_KEY = process.env.LIVEKIT_API_KEY || ''; +const LIVEKIT_API_SECRET = process.env.LIVEKIT_API_SECRET || ''; // OpenIM 平台号:1 iOS, 2 Android, 3 Windows, 4 OSX, 5 Web... 停用时逐个踢下线 const ALL_PLATFORM_IDS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; @@ -139,6 +142,36 @@ async function forceLogoutAll(staffNo) { } catch { /* 忽略 */ } } +// ---------------- LiveKit 通话 token ---------------- + +/** 用 OpenIM parse_token 校验客户端带来的 IM token,返回对应的 userID(无效/过期返回 null) */ +async function parseImToken(token) { + try { + const admin = await getAdminToken(); + const r = await imApi('/auth/parse_token', { token }, admin); + if (r.errCode !== 0 || !r.data || !r.data.userID) return null; + return String(r.data.userID); + } catch { + return null; + } +} + +/** 签 LiveKit 访问 token(HS256 JWT,零依赖手写;claims 格式与 livekit-server 约定一致) */ +function signLivekitToken(identity, room) { + const now = Math.floor(Date.now() / 1000); + const b64 = (o) => Buffer.from(JSON.stringify(o)).toString('base64url'); + const data = b64({ alg: 'HS256', typ: 'JWT' }) + '.' + b64({ + iss: LIVEKIT_API_KEY, + sub: identity, + iat: now, + nbf: now - 10, + exp: now + 2 * 3600, // 2 小时,一场通话足够 + video: { roomJoin: true, room }, + }); + const sig = crypto.createHmac('sha256', LIVEKIT_API_SECRET).update(data).digest('base64url'); + return data + '.' + sig; +} + // ---------------- HTTP 服务 ---------------- function send(res, status, obj) { @@ -201,6 +234,21 @@ const routes = { ok(res, { userID: staffNo, nickname: emp.name, imToken: t.token, expireTimeSeconds: t.expireTimeSeconds }); }, + // 语音通话:用登录拿到的 IM token 换 LiveKit 进房 token + // 请求头 Authorization: Bearer ,body {room, identity} + 'POST /api/rtc_token': async (req, res, body) => { + if (!LIVEKIT_API_KEY || !LIVEKIT_API_SECRET) return fail(res, '语音通话服务未配置,请联系管理员', 500); + const m = /^Bearer\s+(.+)$/.exec(String(req.headers.authorization || '')); + if (!m) return fail(res, '未登录或登录已过期', 401); + const { room, identity } = body; + if (!room || typeof room !== 'string' || room.length > 128) return fail(res, '房间号不合法'); + if (!identity || typeof identity !== 'string') return fail(res, '缺少用户标识'); + const userID = await parseImToken(m[1]); + if (!userID) return fail(res, '登录已过期,请重新登录', 401); + if (userID !== identity) return fail(res, '用户标识与登录凭证不一致', 403); + ok(res, { token: signLivekitToken(identity, room) }); + }, + 'GET /api/admin/employees': async (req, res) => { const list = Object.keys(employees).sort().map((k) => publicView(employees[k], k)); ok(res, { total: list.length, employees: list }); diff --git a/docker-compose.yaml b/docker-compose.yaml index 71830d6..fdcc605 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -399,6 +399,9 @@ services: - OPENIM_API_URL=http://openim-server:10002 - OPENIM_SECRET=${OPENIM_SECRET} - ADMIN_TOKEN=${ACCOUNT_ADMIN_TOKEN:-admin123} + # 语音通话进房 token 签发(/api/rtc_token),与 livekit 容器用同一对密钥 + - LIVEKIT_API_KEY=${LIVEKIT_API_KEY} + - LIVEKIT_API_SECRET=${LIVEKIT_API_SECRET} ports: - "${ACCOUNT_PORT:-10010}:10010" volumes: