154 lines
4.2 KiB
TypeScript
154 lines
4.2 KiB
TypeScript
/**
|
|
* Token管理服务
|
|
* 负责处理OAuth访问令牌的刷新和管理
|
|
* 如果需要添加新的Token相关功能:
|
|
* 1. 优先考虑在 `TokenManager` 中添加
|
|
* 2. 如果需要新的网络请求,在 `OAuthClient` 中添加
|
|
*/
|
|
import { OAuthClient } from "./oauth-client";
|
|
import { OAUTH_CONFIG } from "~/config/api-config";
|
|
|
|
interface TokenInfo {
|
|
accessToken: string;
|
|
refreshToken: string;
|
|
tokenIssuedAt: number;
|
|
tokenExpiresIn: number;
|
|
}
|
|
|
|
interface RefreshResult {
|
|
success: boolean;
|
|
newTokenInfo?: TokenInfo;
|
|
error?: string;
|
|
}
|
|
|
|
/**
|
|
* Token管理服务
|
|
* 负责处理OAuth访问令牌的刷新和管理
|
|
*/
|
|
export class TokenManager {
|
|
private oauthClient: OAuthClient;
|
|
|
|
constructor() {
|
|
this.oauthClient = new OAuthClient(OAUTH_CONFIG);
|
|
}
|
|
|
|
/**
|
|
* 检查token是否过期
|
|
*/
|
|
isTokenExpired(tokenInfo: TokenInfo): boolean {
|
|
const now = Date.now();
|
|
const expiresAt = tokenInfo.tokenIssuedAt + (tokenInfo.tokenExpiresIn * 1000);
|
|
return now >= expiresAt;
|
|
}
|
|
|
|
/**
|
|
* 检查token是否需要刷新(提前5分钟)
|
|
*/
|
|
shouldRefreshToken(tokenInfo: TokenInfo, refreshThresholdMinutes: number = 5): boolean {
|
|
const now = Date.now();
|
|
const expiresAt = tokenInfo.tokenIssuedAt + (tokenInfo.tokenExpiresIn * 1000);
|
|
const refreshThreshold = refreshThresholdMinutes * 60 * 1000;
|
|
return now >= (expiresAt - refreshThreshold);
|
|
}
|
|
|
|
/**
|
|
* 获取token剩余有效时间(秒)
|
|
*/
|
|
getTokenRemainingTime(tokenInfo: TokenInfo): number {
|
|
const now = Date.now();
|
|
const expiresAt = tokenInfo.tokenIssuedAt + (tokenInfo.tokenExpiresIn * 1000);
|
|
return Math.floor((expiresAt - now) / 1000);
|
|
}
|
|
|
|
/**
|
|
* 刷新访问令牌
|
|
*/
|
|
async refreshToken(refreshToken: string): Promise<RefreshResult> {
|
|
try {
|
|
console.log("开始刷新访问令牌...");
|
|
|
|
const newTokenResponse = await this.oauthClient.refreshAccessToken(refreshToken);
|
|
|
|
if (!newTokenResponse) {
|
|
console.error("刷新令牌失败:服务器返回空响应");
|
|
return {
|
|
success: false,
|
|
error: "服务器返回空响应"
|
|
};
|
|
}
|
|
|
|
const newTokenInfo: TokenInfo = {
|
|
accessToken: newTokenResponse.access_token,
|
|
refreshToken: newTokenResponse.refresh_token,
|
|
tokenIssuedAt: Date.now(),
|
|
tokenExpiresIn: newTokenResponse.expires_in
|
|
};
|
|
|
|
console.log(`令牌刷新成功,新令牌有效期: ${newTokenResponse.expires_in}秒`);
|
|
|
|
return {
|
|
success: true,
|
|
newTokenInfo
|
|
};
|
|
|
|
} catch (error) {
|
|
console.error("刷新令牌时发生错误:", error);
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : "未知错误"
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 检查并自动刷新token(如果需要)
|
|
*/
|
|
async checkAndRefreshToken(tokenInfo: TokenInfo): Promise<RefreshResult> {
|
|
// 如果token已过期,必须刷新
|
|
if (this.isTokenExpired(tokenInfo)) {
|
|
console.log("Token已过期,尝试刷新...");
|
|
return this.refreshToken(tokenInfo.refreshToken);
|
|
}
|
|
|
|
// 如果token即将过期,主动刷新
|
|
if (this.shouldRefreshToken(tokenInfo)) {
|
|
const remainingTime = this.getTokenRemainingTime(tokenInfo);
|
|
console.log(`Token将在${remainingTime}秒后过期,主动刷新...`);
|
|
return this.refreshToken(tokenInfo.refreshToken);
|
|
}
|
|
|
|
// Token仍然有效,无需刷新
|
|
return {
|
|
success: true,
|
|
newTokenInfo: tokenInfo
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 格式化token到期时间
|
|
*/
|
|
formatTokenExpiry(tokenInfo: TokenInfo): string {
|
|
const expiresAt = new Date(tokenInfo.tokenIssuedAt + (tokenInfo.tokenExpiresIn * 1000));
|
|
return expiresAt.toLocaleString('zh-CN');
|
|
}
|
|
|
|
/**
|
|
* 获取token状态信息(用于调试)
|
|
*/
|
|
getTokenStatus(tokenInfo: TokenInfo): {
|
|
isExpired: boolean;
|
|
shouldRefresh: boolean;
|
|
remainingTime: number;
|
|
expiryTime: string;
|
|
} {
|
|
return {
|
|
isExpired: this.isTokenExpired(tokenInfo),
|
|
shouldRefresh: this.shouldRefreshToken(tokenInfo),
|
|
remainingTime: this.getTokenRemainingTime(tokenInfo),
|
|
expiryTime: this.formatTokenExpiry(tokenInfo)
|
|
};
|
|
}
|
|
}
|
|
|
|
// 导出单例实例
|
|
export const tokenManager = new TokenManager();
|