maotouying_cc 1 mēnesi atpakaļ
vecāks
revīzija
d2fa19df79
1 mainītis faili ar 149 papildinājumiem un 0 dzēšanām
  1. 149 0
      scripts/upload-s3.mjs

+ 149 - 0
scripts/upload-s3.mjs

@@ -0,0 +1,149 @@
+#!/usr/bin/env node
+/**
+ * S3 文件上传脚本
+ * 用法:node upload-s3.mjs <文件路径> [S3目录前缀]
+ *
+ * 示例:
+ *   node upload-s3.mjs ./test.png              → https://ibitcex.s3.ap-southeast-1.amazonaws.com/test.png
+ *   node upload-s3.mjs ./logo.png images       → https://ibitcex.s3.ap-southeast-1.amazonaws.com/images/logo.png
+ */
+
+import { readFileSync, existsSync } from 'fs';
+import { basename, resolve } from 'path';
+import { fileURLToPath } from 'url';
+import { createHash, createHmac } from 'crypto';
+import { request } from 'https';
+
+// ========== S3 配置 ==========
+const CONFIG = {
+  accessKeyId: 'AKIARHQKVVUBD3M6E3XV',
+  secretAccessKey: '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',
+  bucket: 'ibitcex',
+  region: 'ap-southeast-1',
+};
+
+// ========== AWS Signature V4 签名 ==========
+const SERVICE = 's3';
+const ALGORITHM = 'AWS4-HMAC-SHA256';
+
+function hmac(key, data) {
+  return createHmac('sha256', key).update(data).digest();
+}
+
+function sha256(data) {
+  return createHash('sha256').update(data).digest('hex');
+}
+
+function getSigningKey(dateStamp) {
+  const kDate = hmac('AWS4' + CONFIG.secretAccessKey, dateStamp);
+  const kRegion = hmac(kDate, CONFIG.region);
+  const kService = hmac(kRegion, SERVICE);
+  return hmac(kService, 'aws4_request');
+}
+
+function getSignature(key, stringToSign) {
+  return hmac(key, stringToSign).toString('hex');
+}
+
+// ========== 上传 ==========
+function uploadFile(filePath, s3Key) {
+  const fileContent = readFileSync(filePath);
+  const host = `${CONFIG.bucket}.s3.${CONFIG.region}.amazonaws.com`;
+
+  const amzDate = new Date().toISOString().replace(/[:-]|\.\d{3}/g, '');
+  const dateStamp = amzDate.substring(0, 8);
+
+  const payloadHash = sha256(fileContent);
+
+  const headers = {
+    'Host': host,
+    'Content-Length': String(fileContent.length),
+    'Content-Type': 'application/octet-stream',
+    'x-amz-content-sha256': payloadHash,
+    'x-amz-date': amzDate,
+  };
+
+  const signedHeaders = Object.keys(headers)
+    .sort()
+    .map(k => k.toLowerCase())
+    .join(';');
+
+  // Canonical Request
+  const canonicalRequest = [
+    'PUT',
+    '/' + encodeURIComponent(s3Key).replace(/%2F/g, '/'),
+    '',
+    ...Object.keys(headers).sort().map(k => `${k.toLowerCase()}:${headers[k].trim()}`),
+    '',
+    signedHeaders,
+    payloadHash,
+  ].join('\n');
+
+  // String to Sign
+  const stringToSign = [
+    ALGORITHM,
+    amzDate,
+    `${dateStamp}/${CONFIG.region}/${SERVICE}/aws4_request`,
+    sha256(canonicalRequest),
+  ].join('\n');
+
+  const signingKey = getSigningKey(dateStamp);
+  const signature = getSignature(signingKey, stringToSign);
+
+  const authorization = `${ALGORITHM} Credential=${CONFIG.accessKeyId}/${dateStamp}/${CONFIG.region}/${SERVICE}/aws4_request,SignedHeaders=${signedHeaders},Signature=${signature}`;
+
+  return new Promise((resolvePromise, reject) => {
+    const req = request(
+      {
+        hostname: host,
+        path: '/' + s3Key,
+        method: 'PUT',
+        headers: { ...headers, Authorization: authorization },
+      },
+      (res) => {
+        let body = '';
+        res.on('data', (chunk) => (body += chunk));
+        res.on('end', () => {
+          if (res.statusCode === 200) {
+            resolvePromise(`https://${host}/${s3Key}`);
+          } else {
+            reject(new Error(`上传失败 HTTP ${res.statusCode}: ${body}`));
+          }
+        });
+      }
+    );
+    req.on('error', reject);
+    req.write(fileContent);
+    req.end();
+  });
+}
+
+// ========== 主函数 ==========
+(async () => {
+  const args = process.argv.slice(2);
+  if (args.length < 1) {
+    console.error('用法: node upload-s3.mjs <文件路径> [S3目录前缀]');
+    process.exit(1);
+  }
+
+  const filePath = resolve(args[0]);
+  if (!existsSync(filePath)) {
+    console.error(`文件不存在: ${filePath}`);
+    process.exit(1);
+  }
+
+  const prefix = args[1] ? args[1].replace(/\/$/g, '') + '/' : '';
+  const s3Key = prefix + basename(filePath);
+
+  console.log(`上传: ${filePath} → s3://${CONFIG.bucket}/${s3Key}`);
+  console.log(`大小: ${(readFileSync(filePath).length / 1024).toFixed(1)} KB`);
+
+  try {
+    const url = await uploadFile(filePath, s3Key);
+    console.log(`\n✓ 上传成功`);
+    console.log(`URL: ${url}`);
+  } catch (e) {
+    console.error(`\n✗ ${e.message}`);
+    process.exit(1);
+  }
+})();