/** * 部署后 OSS 配置校验: 探查 bucket 实际 region, 跟 env 里配的 region + endpoint 对比, * 不一致就退出非 0, 阻止 deploy 走完。 * * 用法: * node deploy-package/scripts/check-oss-config.cjs [bucket] * OSS_BUCKET_NAME=xxx node deploy-package/scripts/check-oss-config.cjs * * 退出码: * 0 = 一致 * 1 = bucket 不可访问 / AK 无权限 * 2 = region 不一致 * 3 = endpoint 不匹配 */ const fs = require('fs'); const path = require('path'); const envTxt = fs.readFileSync(path.join(__dirname, '..', '..', 'server', '.env.production'), 'utf8'); const env = {}; for (const line of envTxt.split(/\r?\n/)) { const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/); if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, ''); } // 允许环境变量覆盖 (线上 .env 跟 .env.production 不同时用线上为准) const AK = process.env.OSS_ACCESS_KEY_ID || env.OSS_ACCESS_KEY_ID; const SK = process.env.OSS_ACCESS_KEY_SECRET || env.OSS_ACCESS_KEY_SECRET; const ENV_BUCKET = process.env.OSS_BUCKET_NAME || env.OSS_BUCKET_NAME; const ENV_REGION = process.env.OSS_REGION || env.OSS_REGION; const ENV_ENDPOINT = process.env.OSS_INTERNAL_ENDPOINT || process.env.OSS_ENDPOINT || env.OSS_INTERNAL_ENDPOINT || env.OSS_ENDPOINT; const BUCKET = process.argv[2] || ENV_BUCKET; if (!BUCKET) { console.error('❌ 缺 OSS_BUCKET_NAME'); process.exit(1); } if (!AK || !SK) { console.error('❌ 缺 AK/SK'); process.exit(1); } const OSS = require(path.join(__dirname, '..', '..', 'server', 'node_modules', 'ali-oss')); // 列举常见 region endpoint (按 aliyun 文档) const REGION_ENDPOINTS = { 'oss-cn-hangzhou': 'oss-cn-hangzhou.aliyuncs.com', 'oss-cn-shanghai': 'oss-cn-shanghai.aliyuncs.com', 'oss-cn-beijing': 'oss-cn-beijing.aliyuncs.com', 'oss-cn-shenzhen': 'oss-cn-shenzhen.aliyuncs.com', 'oss-cn-qingdao': 'oss-cn-qingdao.aliyuncs.com', 'oss-cn-zhangjiakou': 'oss-cn-zhangjiakou.aliyuncs.com', 'oss-cn-huabei-3': 'oss-cn-huabei-3.aliyuncs.com', 'oss-cn-huabei-5': 'oss-cn-huabei-5.aliyuncs.com', 'oss-cn-hangzhou-finance': 'oss-cn-hangzhou-finance.aliyuncs.com', 'oss-cn-shanghai-finance-1': 'oss-cn-shanghai-finance-1.aliyuncs.com', 'oss-cn-chengdu': 'oss-cn-chengdu.aliyuncs.com', 'oss-cn-hongkong': 'oss-cn-hongkong.aliyuncs.com', 'oss-cn-fuzhou': 'oss-cn-fuzhou.aliyuncs.com', 'oss-cn-wulanchabu': 'oss-cn-wulanchabu.aliyuncs.com', 'oss-cn-shenyang': 'oss-cn-shenyang.aliyuncs.com', 'oss-cn-nanjing': 'oss-cn-nanjing.aliyuncs.com', 'oss-cn-heyuan': 'oss-cn-heyuan.aliyuncs.com', }; (async () => { console.log(`\n=== OSS 配置校验 ===`); console.log(`期望 bucket: ${BUCKET}`); console.log(`env region: ${ENV_REGION}`); console.log(`env endpoint: ${ENV_ENDPOINT}`); // 探查所有 region, 找 bucket 实际位置 // 用 ExtranetEndpoint 提取 region(这是 OSS 返回的真值,不靠 hostId 猜) let actualRegion = null, actualEndpoint = null, actualInternal = null; for (const [r, ep] of Object.entries(REGION_ENDPOINTS)) { try { const c = new OSS({ accessKeyId: AK, accessKeySecret: SK, bucket: BUCKET, endpoint: ep, secure: true }); const info = await c.getBucketInfo(BUCKET); const exEp = info.bucket?.ExtranetEndpoint || ep; const m = exEp.match(/oss-([a-z0-9-]+)\.aliyuncs\.com/); if (m) { actualRegion = m[1]; actualEndpoint = exEp; actualInternal = info.bucket?.IntranetEndpoint || exEp.replace(/aliyuncs\.com$/, 'aliyuncs.com').replace(/oss-([a-z0-9-]+)\./, 'oss-$1-internal.'); console.log(`\n✅ bucket 真实 region: ${actualRegion}`); console.log(` ExtranetEndpoint: ${info.bucket?.ExtranetEndpoint}`); console.log(` IntranetEndpoint: ${info.bucket?.IntranetEndpoint}`); console.log(` StorageClass: ${info.bucket?.StorageClass}`); console.log(` CreationDate: ${info.bucket?.CreationDate}`); break; } } catch (e) { // continue } } if (!actualRegion) { console.log('\n❌ 所有 region 都试过了, 无法访问 bucket. 检查 AK/SK 权限.'); process.exit(1); } // 比较 region let pass = true; if (ENV_REGION !== actualRegion) { console.log(`\n❌ region 不一致! env=${ENV_REGION} 实际=${actualRegion}`); pass = false; } else { console.log(`\n✅ region 一致 (${actualRegion})`); } // 比较 endpoint (允许 internal/external 差异) const envEpHost = (ENV_ENDPOINT || '').replace(/^-internal\./, '').replace(/^https?:\/\//, ''); const actualEpHost = (actualEndpoint || '').replace(/^https?:\/\//, ''); if (envEpHost && actualEpHost && !envEpHost.includes(actualEpHost.split('.')[0])) { console.log(`❌ endpoint 不匹配! env=${ENV_ENDPOINT} 实际=${actualEndpoint}`); pass = false; } else { console.log(`✅ endpoint 匹配 (${actualEpHost})`); } if (!pass) { console.log(`\n⚠️ 建议:`); console.log(` 把 .env.production 里:`); console.log(` OSS_REGION=${actualRegion}`); console.log(` OSS_ENDPOINT=${actualEndpoint}`); console.log(` OSS_INTERNAL_ENDPOINT=${actualInternal}`); process.exit(2); } // 最后跑 list 确认能正常操作 try { const c = new OSS({ accessKeyId: AK, accessKeySecret: SK, bucket: BUCKET, endpoint: actualEndpoint, secure: true }); const r = await c.list({ 'max-keys': 1, delimiter: '/' }); console.log(`\n✅ list 正常 (顶层 prefix: ${(r.prefixes || []).slice(0, 5).join(', ')})`); process.exit(0); } catch (e) { console.log(`\n❌ list 失败: ${e.code || e.message}`); process.exit(3); } })().catch(e => { console.error('异常:', e); process.exit(1); });