fix: CDN 刷新改用原生 HTTPS,移除 SDK 依赖
This commit is contained in:
@@ -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');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user