| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- #!/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);
- }
- })();
|