account-server 新增 /api/rtc_token:用 IM token 换 LiveKit 进房 token(手机端语音通话用,B-58)
This commit is contained in:
@@ -3,3 +3,8 @@
|
||||
components/
|
||||
.selftest/
|
||||
data/
|
||||
|
||||
# Flutter 手机端构建产物
|
||||
mobile/.dart_tool/
|
||||
mobile/build/
|
||||
mobile/.flutter-plugins*
|
||||
|
||||
@@ -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 <imToken>,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 });
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user