gongxiangp2/lib/download-token.ts

117 lines
2.6 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.

/**
* 下载 Token 生成和验证
* 用于外部接口的文件下载鉴权
*/
import jwt from 'jsonwebtoken';
// Token 有效期:永不过期
// 注意:任务平台会缓存下载链接,所以设置为永不过期
// 安全性依赖于 DOWNLOAD_TOKEN_SECRET 的保密性
const TOKEN_EXPIRY = '100y'; // 100年 ≈ 永不过期
// 下载类型
export type DownloadType =
| 'all'
| 'main'
| 'sku'
| 'detail'
| 'psd'
| 'single'
| 'image-source'
| 'white-background-single'
| 'white-background-zip';
export interface DownloadTokenPayload {
materialId: string;
downloadType: DownloadType;
assetId?: string; // 单个资产下载时使用
skuId?: string; // SKU 级资源下载时使用
}
interface DecodedToken extends DownloadTokenPayload {
iat: number;
exp: number;
}
/**
* 获取 Token 密钥
*/
function getTokenSecret(): string {
const secret = process.env.DOWNLOAD_TOKEN_SECRET;
if (!secret) {
throw new Error('DOWNLOAD_TOKEN_SECRET 环境变量未配置');
}
return secret;
}
/**
* 生成下载 Token
*/
export function generateDownloadToken(payload: DownloadTokenPayload): string {
const secret = getTokenSecret();
return jwt.sign(payload, secret, { expiresIn: TOKEN_EXPIRY });
}
/**
* 验证下载 Token
* @returns 解码后的 payload,验证失败返回 null
*/
export function verifyDownloadToken(token: string): DownloadTokenPayload | null {
try {
const secret = getTokenSecret();
const decoded = jwt.verify(token, secret) as DecodedToken;
return {
materialId: decoded.materialId,
downloadType: decoded.downloadType,
assetId: decoded.assetId,
skuId: decoded.skuId,
};
} catch (error) {
return null;
}
}
/**
* 检查 Token 是否过期
*/
export function isTokenExpired(token: string): boolean {
try {
const secret = getTokenSecret();
jwt.verify(token, secret);
return false;
} catch (error: any) {
if (error.name === 'TokenExpiredError') {
return true;
}
// 其他错误(如无效 token)也返回 true
return true;
}
}
/**
* 获取 Token 过期时间
*/
export function getTokenExpiry(token: string): Date | null {
try {
const decoded = jwt.decode(token) as DecodedToken | null;
if (decoded?.exp) {
return new Date(decoded.exp * 1000);
}
return null;
} catch {
return null;
}
}
/**
* 生成带 Token 的下载 URL
*/
export function generateDownloadUrl(
baseUrl: string,
payload: DownloadTokenPayload
): string {
const token = generateDownloadToken(payload);
return `${baseUrl}/api/external/download?token=${encodeURIComponent(token)}`;
}