- 修复 pendingQuestions 缺失时无法提交回答的问题 - 添加 fallbackTaskId 参数支持直接发送到后端 - apiClient 自动获取 JWT Token - 取消按钮改为中止对话而非发送消息
102 lines
2.6 KiB
TypeScript
102 lines
2.6 KiB
TypeScript
/**
|
||
* JWT 工具函数
|
||
*/
|
||
|
||
/**
|
||
* JWT Payload 接口
|
||
*/
|
||
export interface JwtPayload {
|
||
sub?: string; // subject (通常是 userId)
|
||
userId?: number; // 用户ID (驼峰命名)
|
||
user_id?: number; // 用户ID (下划线命名)
|
||
exp?: number; // 过期时间
|
||
iat?: number; // 签发时间
|
||
[key: string]: unknown;
|
||
}
|
||
|
||
/**
|
||
* 解析 JWT token 的 payload
|
||
* @param token JWT token
|
||
* @returns 解析后的 payload,解析失败返回 null
|
||
*/
|
||
export function parseJwtPayload(token: string): JwtPayload | null {
|
||
try {
|
||
const parts = token.split('.');
|
||
if (parts.length !== 3) {
|
||
console.warn('[JWT] token 格式不正确,期望3部分,实际:', parts.length);
|
||
return null;
|
||
}
|
||
|
||
// payload 是第二部分,base64url 编码
|
||
const payload = parts[1];
|
||
|
||
// base64url 转 base64
|
||
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
|
||
|
||
// 解码
|
||
const jsonStr = Buffer.from(base64, 'base64').toString('utf-8');
|
||
const parsed = JSON.parse(jsonStr);
|
||
|
||
console.log('[JWT] 解析成功, payload 字段:', Object.keys(parsed));
|
||
console.log('[JWT] payload 内容:', JSON.stringify(parsed));
|
||
return parsed;
|
||
} catch (error) {
|
||
console.error('[JWT] 解析失败:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 从 JWT token 中获取用户ID
|
||
* @param token JWT token
|
||
* @returns 用户ID字符串,获取失败返回 null
|
||
*/
|
||
export function getUserIdFromToken(token: string): string | null {
|
||
const payload = parseJwtPayload(token);
|
||
if (!payload) {
|
||
return null;
|
||
}
|
||
|
||
// 支持多种字段名:user_id, userId, sub
|
||
if (payload.user_id !== undefined) {
|
||
return String(payload.user_id);
|
||
}
|
||
if (payload.userId !== undefined) {
|
||
return String(payload.userId);
|
||
}
|
||
if (payload.sub !== undefined) {
|
||
return String(payload.sub);
|
||
}
|
||
|
||
console.warn('[JWT] payload 中没有 user_id, userId 或 sub 字段');
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 检测 JWT token 是否已过期
|
||
* @param token JWT token
|
||
* @param bufferSeconds 提前多少秒判定为过期(默认60秒)
|
||
* @returns true 表示已过期,false 表示未过期,null 表示无法判断
|
||
*/
|
||
export function isTokenExpired(token: string, bufferSeconds: number = 60): boolean | null {
|
||
const payload = parseJwtPayload(token);
|
||
if (!payload) {
|
||
return null;
|
||
}
|
||
|
||
if (payload.exp === undefined) {
|
||
console.warn('[JWT] payload 中没有 exp 字段,无法判断过期');
|
||
return null;
|
||
}
|
||
|
||
const now = Math.floor(Date.now() / 1000);
|
||
const expTime = payload.exp - bufferSeconds;
|
||
const isExpired = now >= expTime;
|
||
|
||
if (isExpired) {
|
||
console.warn('[JWT] token 已过期,exp:', payload.exp, '当前:', now);
|
||
}
|
||
|
||
return isExpired;
|
||
}
|