check-oss-config.cjs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. /**
  2. * 部署后 OSS 配置校验: 探查 bucket 实际 region, 跟 env 里配的 region + endpoint 对比,
  3. * 不一致就退出非 0, 阻止 deploy 走完。
  4. *
  5. * 用法:
  6. * node deploy-package/scripts/check-oss-config.cjs [bucket]
  7. * OSS_BUCKET_NAME=xxx node deploy-package/scripts/check-oss-config.cjs
  8. *
  9. * 退出码:
  10. * 0 = 一致
  11. * 1 = bucket 不可访问 / AK 无权限
  12. * 2 = region 不一致
  13. * 3 = endpoint 不匹配
  14. */
  15. const fs = require('fs');
  16. const path = require('path');
  17. const envTxt = fs.readFileSync(path.join(__dirname, '..', '..', 'server', '.env.production'), 'utf8');
  18. const env = {};
  19. for (const line of envTxt.split(/\r?\n/)) {
  20. const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/);
  21. if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, '');
  22. }
  23. // 允许环境变量覆盖 (线上 .env 跟 .env.production 不同时用线上为准)
  24. const AK = process.env.OSS_ACCESS_KEY_ID || env.OSS_ACCESS_KEY_ID;
  25. const SK = process.env.OSS_ACCESS_KEY_SECRET || env.OSS_ACCESS_KEY_SECRET;
  26. const ENV_BUCKET = process.env.OSS_BUCKET_NAME || env.OSS_BUCKET_NAME;
  27. const ENV_REGION = process.env.OSS_REGION || env.OSS_REGION;
  28. const ENV_ENDPOINT = process.env.OSS_INTERNAL_ENDPOINT || process.env.OSS_ENDPOINT || env.OSS_INTERNAL_ENDPOINT || env.OSS_ENDPOINT;
  29. const BUCKET = process.argv[2] || ENV_BUCKET;
  30. if (!BUCKET) { console.error('❌ 缺 OSS_BUCKET_NAME'); process.exit(1); }
  31. if (!AK || !SK) { console.error('❌ 缺 AK/SK'); process.exit(1); }
  32. const OSS = require(path.join(__dirname, '..', '..', 'server', 'node_modules', 'ali-oss'));
  33. // 列举常见 region endpoint (按 aliyun 文档)
  34. const REGION_ENDPOINTS = {
  35. 'oss-cn-hangzhou': 'oss-cn-hangzhou.aliyuncs.com',
  36. 'oss-cn-shanghai': 'oss-cn-shanghai.aliyuncs.com',
  37. 'oss-cn-beijing': 'oss-cn-beijing.aliyuncs.com',
  38. 'oss-cn-shenzhen': 'oss-cn-shenzhen.aliyuncs.com',
  39. 'oss-cn-qingdao': 'oss-cn-qingdao.aliyuncs.com',
  40. 'oss-cn-zhangjiakou': 'oss-cn-zhangjiakou.aliyuncs.com',
  41. 'oss-cn-huabei-3': 'oss-cn-huabei-3.aliyuncs.com',
  42. 'oss-cn-huabei-5': 'oss-cn-huabei-5.aliyuncs.com',
  43. 'oss-cn-hangzhou-finance': 'oss-cn-hangzhou-finance.aliyuncs.com',
  44. 'oss-cn-shanghai-finance-1': 'oss-cn-shanghai-finance-1.aliyuncs.com',
  45. 'oss-cn-chengdu': 'oss-cn-chengdu.aliyuncs.com',
  46. 'oss-cn-hongkong': 'oss-cn-hongkong.aliyuncs.com',
  47. 'oss-cn-fuzhou': 'oss-cn-fuzhou.aliyuncs.com',
  48. 'oss-cn-wulanchabu': 'oss-cn-wulanchabu.aliyuncs.com',
  49. 'oss-cn-shenyang': 'oss-cn-shenyang.aliyuncs.com',
  50. 'oss-cn-nanjing': 'oss-cn-nanjing.aliyuncs.com',
  51. 'oss-cn-heyuan': 'oss-cn-heyuan.aliyuncs.com',
  52. };
  53. (async () => {
  54. console.log(`\n=== OSS 配置校验 ===`);
  55. console.log(`期望 bucket: ${BUCKET}`);
  56. console.log(`env region: ${ENV_REGION}`);
  57. console.log(`env endpoint: ${ENV_ENDPOINT}`);
  58. // 探查所有 region, 找 bucket 实际位置
  59. // 用 ExtranetEndpoint 提取 region(这是 OSS 返回的真值,不靠 hostId 猜)
  60. let actualRegion = null, actualEndpoint = null, actualInternal = null;
  61. for (const [r, ep] of Object.entries(REGION_ENDPOINTS)) {
  62. try {
  63. const c = new OSS({ accessKeyId: AK, accessKeySecret: SK, bucket: BUCKET, endpoint: ep, secure: true });
  64. const info = await c.getBucketInfo(BUCKET);
  65. const exEp = info.bucket?.ExtranetEndpoint || ep;
  66. const m = exEp.match(/oss-([a-z0-9-]+)\.aliyuncs\.com/);
  67. if (m) {
  68. actualRegion = m[1];
  69. actualEndpoint = exEp;
  70. actualInternal = info.bucket?.IntranetEndpoint || exEp.replace(/aliyuncs\.com$/, 'aliyuncs.com').replace(/oss-([a-z0-9-]+)\./, 'oss-$1-internal.');
  71. console.log(`\n✅ bucket 真实 region: ${actualRegion}`);
  72. console.log(` ExtranetEndpoint: ${info.bucket?.ExtranetEndpoint}`);
  73. console.log(` IntranetEndpoint: ${info.bucket?.IntranetEndpoint}`);
  74. console.log(` StorageClass: ${info.bucket?.StorageClass}`);
  75. console.log(` CreationDate: ${info.bucket?.CreationDate}`);
  76. break;
  77. }
  78. } catch (e) {
  79. // continue
  80. }
  81. }
  82. if (!actualRegion) {
  83. console.log('\n❌ 所有 region 都试过了, 无法访问 bucket. 检查 AK/SK 权限.');
  84. process.exit(1);
  85. }
  86. // 比较 region
  87. let pass = true;
  88. if (ENV_REGION !== actualRegion) {
  89. console.log(`\n❌ region 不一致! env=${ENV_REGION} 实际=${actualRegion}`);
  90. pass = false;
  91. } else {
  92. console.log(`\n✅ region 一致 (${actualRegion})`);
  93. }
  94. // 比较 endpoint (允许 internal/external 差异)
  95. const envEpHost = (ENV_ENDPOINT || '').replace(/^-internal\./, '').replace(/^https?:\/\//, '');
  96. const actualEpHost = (actualEndpoint || '').replace(/^https?:\/\//, '');
  97. if (envEpHost && actualEpHost && !envEpHost.includes(actualEpHost.split('.')[0])) {
  98. console.log(`❌ endpoint 不匹配! env=${ENV_ENDPOINT} 实际=${actualEndpoint}`);
  99. pass = false;
  100. } else {
  101. console.log(`✅ endpoint 匹配 (${actualEpHost})`);
  102. }
  103. if (!pass) {
  104. console.log(`\n⚠️ 建议:`);
  105. console.log(` 把 .env.production 里:`);
  106. console.log(` OSS_REGION=${actualRegion}`);
  107. console.log(` OSS_ENDPOINT=${actualEndpoint}`);
  108. console.log(` OSS_INTERNAL_ENDPOINT=${actualInternal}`);
  109. process.exit(2);
  110. }
  111. // 最后跑 list 确认能正常操作
  112. try {
  113. const c = new OSS({ accessKeyId: AK, accessKeySecret: SK, bucket: BUCKET, endpoint: actualEndpoint, secure: true });
  114. const r = await c.list({ 'max-keys': 1, delimiter: '/' });
  115. console.log(`\n✅ list 正常 (顶层 prefix: ${(r.prefixes || []).slice(0, 5).join(', ')})`);
  116. process.exit(0);
  117. } catch (e) {
  118. console.log(`\n❌ list 失败: ${e.code || e.message}`);
  119. process.exit(3);
  120. }
  121. })().catch(e => { console.error('异常:', e); process.exit(1); });