/** * 下载 Token 生成和验证 * 用于外部接口的文件下载鉴权 */ import jwt from 'jsonwebtoken'; // Token 有效期:永不过期 // 注意:任务平台会缓存下载链接,所以设置为永不过期 // 安全性依赖于 DOWNLOAD_TOKEN_SECRET 的保密性 const TOKEN_EXPIRY = '100y'; // 100年 ≈ 永不过期 // 下载类型 export type DownloadType = | 'all' | 'main' | 'sku' | 'detail' | 'psd' | 'single' | '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)}`; }