Files
pqs-9100_client/frontend/src/utils/jwtUtil.ts
2025-08-06 13:33:09 +08:00

234 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useUserStore } from "@/stores/modules/user";
// JWT Token解析后的载荷接口
export interface JwtPayload {
userId?: string;
loginName?: string;
exp?: number;
iat?: number;
[key: string]: any;
}
// Token信息摘要接口
export interface TokenInfo {
userId: string | null;
loginName: string | null;
expiration: string | null;
isExpired: boolean;
remainingTime: string;
}
/**
* JWT工具类
* 提供JWT token的解析、验证等功能
*/
export class JwtUtil {
/**
* Base64URL解码
* @param str Base64URL编码的字符串
*/
private static base64UrlDecode(str: string): string {
try {
// Base64URL转Base64
let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
// 补齐padding
while (base64.length % 4) {
base64 += '=';
}
// Base64解码
const decoded = atob(base64);
// 处理UTF-8编码
return decodeURIComponent(
decoded
.split('')
.map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join('')
);
} catch (error) {
throw new Error('Base64URL解码失败: ' + error);
}
}
/**
* 解析JWT Token获取载荷信息
* @param token JWT token字符串如果不传则从store中获取
*/
static parseToken(token?: string): JwtPayload | null {
try {
let targetToken = token;
// 如果没有传入token从store中获取
if (!targetToken) {
const userStore = useUserStore();
targetToken = userStore.accessToken;
}
if (!targetToken) {
console.warn('Token不存在');
return null;
}
// JWT token由三部分组成用.分割header.payload.signature
const parts = targetToken.split('.');
if (parts.length !== 3) {
console.error('无效的JWT token格式');
return null;
}
// 解码payload部分第二部分
const payload = parts[1];
const decodedPayload = this.base64UrlDecode(payload);
const tokenData: JwtPayload = JSON.parse(decodedPayload);
return tokenData;
} catch (error) {
console.error('解析JWT Token失败:', error);
return null;
}
}
/**
* 获取指定字段的值
* @param field 字段名
* @param token JWT token字符串可选
*/
static getField<T = any>(field: string, token?: string): T | null {
const tokenData = this.parseToken(token);
return tokenData?.[field] || null;
}
/**
* 获取用户ID
* @param token JWT token字符串可选
*/
static getUserId(token?: string): string | null {
return this.getField<string>('userId', token);
}
/**
* 获取登录名
* @param token JWT token字符串可选
*/
static getLoginName(token?: string): string | null {
return this.getField<string>('loginName', token);
}
/**
* 获取Token过期时间戳
* @param token JWT token字符串可选
*/
static getExpiration(token?: string): number | null {
return this.getField<number>('exp', token);
}
/**
* 获取Token签发时间戳
* @param token JWT token字符串可选
*/
static getIssuedAt(token?: string): number | null {
return this.getField<number>('iat', token);
}
/**
* 检查Token是否过期
* @param token JWT token字符串可选
*/
static isExpired(token?: string): boolean {
const exp = this.getExpiration(token);
if (!exp) return true;
// JWT中的exp是秒级时间戳需要转换为毫秒
const expTime = exp * 1000;
return Date.now() >= expTime;
}
/**
* 获取Token剩余有效时间毫秒
* @param token JWT token字符串可选
*/
static getRemainingTime(token?: string): number {
const exp = this.getExpiration(token);
if (!exp) return 0;
const expTime = exp * 1000;
const remaining = expTime - Date.now();
return Math.max(0, remaining);
}
/**
* 格式化剩余时间为可读字符串
* @param ms 毫秒数
*/
static formatRemainingTime(ms: number): string {
if (ms <= 0) return '已过期';
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) return `${days}${hours % 24}小时`;
if (hours > 0) return `${hours}小时 ${minutes % 60}分钟`;
if (minutes > 0) return `${minutes}分钟 ${seconds % 60}`;
return `${seconds}`;
}
/**
* 获取完整的Token信息摘要
* @param token JWT token字符串可选
*/
static getTokenInfo(token?: string): TokenInfo {
const exp = this.getExpiration(token);
const remainingMs = this.getRemainingTime(token);
return {
userId: this.getUserId(token),
loginName: this.getLoginName(token),
expiration: exp ? new Date(exp * 1000).toLocaleString() : null,
isExpired: this.isExpired(token),
remainingTime: this.formatRemainingTime(remainingMs)
};
}
/**
* 验证Token是否有效格式正确且未过期
* @param token JWT token字符串可选
*/
static isValid(token?: string): boolean {
const tokenData = this.parseToken(token);
return tokenData !== null && !this.isExpired(token);
}
/**
* 获取Token的头部信息
* @param token JWT token字符串可选
*/
static getHeader(token?: string): any | null {
try {
let targetToken = token;
if (!targetToken) {
const userStore = useUserStore();
targetToken = userStore.accessToken;
}
if (!targetToken) return null;
const parts = targetToken.split('.');
if (parts.length !== 3) return null;
const header = parts[0];
const decodedHeader = this.base64UrlDecode(header);
return JSON.parse(decodedHeader);
} catch (error) {
console.error('解析JWT Header失败:', error);
return null;
}
}
}
// 导出单例方法,方便直接调用
export const jwtUtil = JwtUtil;