- 新增试用用户首次登录欢迎弹窗,展示使用教程 - 新增试用期过期检测服务和过期提醒弹窗 - 从 JWT token 中提取 ispluginTrial 标识判断用户类型 - 试用用户跳过邀请码验证流程 - 在消息发送前检查试用期是否过期 - 新增 ExpiredPanel 和 WelcomePanel 面板组件 - 新增 expiredModal 和 welcomeModal 视图组件 - 优化用户登录流程,根据用户类型显示不同引导
127 lines
3.3 KiB
TypeScript
127 lines
3.3 KiB
TypeScript
/**
|
||
* JWT 工具函数
|
||
*/
|
||
|
||
/**
|
||
* JWT Payload 接口
|
||
*/
|
||
export interface JwtPayload {
|
||
sub?: string; // subject (通常是 userId)
|
||
userId?: number; // 用户ID (驼峰命名)
|
||
user_id?: number; // 用户ID (下划线命名)
|
||
exp?: number; // 过期时间
|
||
iat?: number; // 签发时间
|
||
ispluginTrial?: boolean; // 是否是插件试用用户
|
||
[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;
|
||
}
|
||
|
||
/**
|
||
* 从 JWT token 中获取 ispluginTrial 标识
|
||
* @param token JWT token
|
||
* @returns true=插件试用用户,false=正式用户,null=无法判断
|
||
*/
|
||
export function getIsPluginTrialFromToken(token: string): boolean | null {
|
||
const payload = parseJwtPayload(token);
|
||
if (!payload) {
|
||
return null;
|
||
}
|
||
|
||
// 检查 ispluginTrial 字段
|
||
if (payload.ispluginTrial !== undefined) {
|
||
console.log("[JWT] 从 token 中获取到 ispluginTrial:", payload.ispluginTrial);
|
||
return payload.ispluginTrial === true;
|
||
}
|
||
|
||
console.log("[JWT] token 中没有 ispluginTrial 字段,判定为正式用户");
|
||
return false;
|
||
}
|