fix: CDN 刷新改用原生 HTTPS,移除 SDK 依赖

This commit is contained in:
2026-01-09 11:57:51 +08:00
parent 135ef67cf1
commit ddca64ccd2

View File

@@ -13,16 +13,8 @@
const COS = require('cos-nodejs-sdk-v5');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
// CDN SDK (可选,如果安装了的话)
let CdnClient = null;
try {
const tencentcloud = require('tencentcloud-sdk-nodejs-cdn');
CdnClient = tencentcloud.cdn.v20180606.Client;
} catch (e) {
// CDN SDK 未安装,稍后会提示用户
}
const https = require('https');
const crypto = require('crypto');
// ============================================================================
// 配置加载
@@ -293,9 +285,89 @@ async function uploadBuildDir() {
}
// ============================================================================
// CDN 刷新
// CDN 刷新 (使用原生 HTTPS + TC3 签名,无需额外 SDK)
// ============================================================================
/**
* 生成腾讯云 TC3-HMAC-SHA256 签名
*/
function generateTC3Signature(secretId, secretKey, service, payload) {
const timestamp = Math.floor(Date.now() / 1000);
const date = new Date(timestamp * 1000).toISOString().slice(0, 10);
// 1. 拼接规范请求串
const httpRequestMethod = 'POST';
const canonicalUri = '/';
const canonicalQueryString = '';
const hashedPayload = crypto.createHash('sha256').update(payload).digest('hex');
const canonicalHeaders = `content-type:application/json; charset=utf-8\nhost:cdn.tencentcloudapi.com\nx-tc-action:purgeUrlsCache\n`;
const signedHeaders = 'content-type;host;x-tc-action';
const canonicalRequest = `${httpRequestMethod}\n${canonicalUri}\n${canonicalQueryString}\n${canonicalHeaders}\n${signedHeaders}\n${hashedPayload}`;
// 2. 拼接待签名字符串
const algorithm = 'TC3-HMAC-SHA256';
const credentialScope = `${date}/${service}/tc3_request`;
const hashedCanonicalRequest = crypto.createHash('sha256').update(canonicalRequest).digest('hex');
const stringToSign = `${algorithm}\n${timestamp}\n${credentialScope}\n${hashedCanonicalRequest}`;
// 3. 计算签名
const secretDate = crypto.createHmac('sha256', `TC3${secretKey}`).update(date).digest();
const secretService = crypto.createHmac('sha256', secretDate).update(service).digest();
const secretSigning = crypto.createHmac('sha256', secretService).update('tc3_request').digest();
const signature = crypto.createHmac('sha256', secretSigning).update(stringToSign).digest('hex');
// 4. 拼接 Authorization
const authorization = `${algorithm} Credential=${secretId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
return { authorization, timestamp };
}
/**
* 调用腾讯云 CDN API
*/
function callCdnApi(secretId, secretKey, action, params) {
return new Promise((resolve, reject) => {
const payload = JSON.stringify(params);
const { authorization, timestamp } = generateTC3Signature(secretId, secretKey, 'cdn', payload);
const options = {
hostname: 'cdn.tencentcloudapi.com',
port: 443,
path: '/',
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Host': 'cdn.tencentcloudapi.com',
'X-TC-Action': action,
'X-TC-Version': '2018-06-06',
'X-TC-Timestamp': timestamp.toString(),
'Authorization': authorization,
},
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const result = JSON.parse(data);
if (result.Response && result.Response.Error) {
reject(new Error(`${result.Response.Error.Code}: ${result.Response.Error.Message}`));
} else {
resolve(result.Response);
}
} catch (e) {
reject(new Error(`解析响应失败: ${data}`));
}
});
});
req.on('error', reject);
req.write(payload);
req.end();
});
}
/**
* 刷新 CDN 缓存
* 自动刷新 index.html 等关键文件,确保用户获取最新版本
@@ -307,34 +379,10 @@ async function refreshCDN() {
return { success: false, reason: 'no_domain' };
}
// 检查是否安装了 CDN SDK
if (!CdnClient) {
console.log('\n\x1b[33m[提示]\x1b[0m CDN SDK 未安装,跳过自动刷新');
console.log(' 安装命令: npm install tencentcloud-sdk-nodejs-cdn --save-dev');
console.log(' 或手动刷新: https://console.cloud.tencent.com/cdn/refresh');
return { success: false, reason: 'no_sdk' };
}
console.log('\n\x1b[36m[CDN]\x1b[0m 刷新 CDN 缓存...');
console.log(` 域名: ${config.CDN_DOMAIN}`);
try {
// 初始化 CDN 客户端
const cdnClient = new CdnClient({
credential: {
secretId: config.COS_SECRET_ID,
secretKey: config.COS_SECRET_KEY,
},
region: '', // CDN 为全局服务region 留空
profile: {
signMethod: 'TC3-HMAC-SHA256',
httpProfile: {
reqMethod: 'POST',
reqTimeout: 30,
},
},
});
// 需要刷新的关键文件(不带 hash 的文件)
const domain = config.CDN_DOMAIN.replace(/^https?:\/\//, '');
const urlsToRefresh = [
@@ -348,9 +396,12 @@ async function refreshCDN() {
urlsToRefresh.forEach(url => console.log(` - ${url}`));
// 调用 PurgeUrlsCache 接口
const result = await cdnClient.PurgeUrlsCache({
Urls: urlsToRefresh,
});
const result = await callCdnApi(
config.COS_SECRET_ID,
config.COS_SECRET_KEY,
'PurgeUrlsCache',
{ Urls: urlsToRefresh }
);
console.log(`\n\x1b[32m[✓]\x1b[0m CDN 刷新任务已提交`);
console.log(` 任务ID: ${result.TaskId}`);
@@ -361,10 +412,9 @@ async function refreshCDN() {
} catch (err) {
console.error(`\n\x1b[33m[警告]\x1b[0m CDN 刷新失败: ${err.message}`);
// 提供常见错误的解决方案
if (err.code === 'AuthFailure.SecretIdNotFound' || err.code === 'AuthFailure.SignatureFailure') {
if (err.message.includes('AuthFailure')) {
console.log(' 请检查 API 密钥是否正确');
} else if (err.code === 'UnauthorizedOperation') {
} else if (err.message.includes('UnauthorizedOperation')) {
console.log(' 请确保 API 密钥有 CDN 刷新权限');
console.log(' 权限策略: QcloudCDNFullAccess 或自定义 cdn:PurgeUrlsCache');
}