/** * RUSTFS 对象存储配置 * 直接使用 HTTP API 避免 AWS SDK 兼容性问题 */ const https = require('https'); const http = require('http'); const crypto = require('crypto'); const config = { endpoint: process.env.RUSTFS_ENDPOINT || 'http://43.156.91.115:9001', bucket: process.env.RUSTFS_BUCKET || 'setting', accessKey: process.env.RUSTFS_ACCESS_KEY || 'rustfsadmin', secretKey: process.env.RUSTFS_SECRET_KEY || 'rustfsadmin' }; // 生成 AWS Signature V4 function createSignature(method, path, headers, query = '') { const date = new Date().toISOString().replace(/[:\-]|\.\d{3}/g, ''); const dateShort = date.substring(0, 8); const payloadHash = crypto.createHash('sha256').update('').digest('hex'); const canonicalHeaders = Object.entries(headers) .map(([k, v]) => `${k.toLowerCase()}:${v}`) .join('\n'); const signedHeaders = Object.keys(headers).map(k => k.toLowerCase()).join(';'); const canonicalRequest = [ method, path, query, canonicalHeaders, '', signedHeaders, payloadHash ].join('\n'); const credentialScope = `${dateShort}/us-east-1/s3/aws4_request`; const stringToSign = [ 'AWS4-HMAC-SHA256', date, credentialScope, crypto.createHash('sha256').update(canonicalRequest).digest('hex') ].join('\n'); const kDate = crypto.createHmac('sha256', `AWS4${config.secretKey}`).update(dateShort).digest(); const kRegion = crypto.createHmac('sha256', kDate).update('us-east-1').digest(); const kService = crypto.createHmac('sha256', kRegion).update('s3').digest(); const kSigning = crypto.createHmac('sha256', kService).update('aws4_request').digest(); const signature = crypto.createHmac('sha256', kSigning).update(stringToSign).digest('hex'); return { authorization: `AWS4-HMAC-SHA256 Credential=${config.accessKey}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`, 'x-amz-date': date, 'x-amz-content-sha256': payloadHash }; } function makeRequest(method, path, body = null, contentType = 'application/octet-stream') { return new Promise((resolve, reject) => { const url = new URL(config.endpoint); const isHttps = url.protocol === 'https:'; const httpModule = isHttps ? https : http; const query = ''; const headers = { 'host': url.host, 'content-type': contentType, 'x-amz-date': new Date().toISOString().replace(/[:\-]|\.\d{3}/g, '').substring(0, 8) + 'T000000Z' }; if (body) { headers['content-length'] = Buffer.byteLength(body); } // 简化认证:使用 Access Key 作为 Authorization header headers['Authorization'] = `AWS ${config.accessKey}:${crypto.createHash('sha256').update(config.secretKey).digest('hex')}`; const options = { hostname: url.hostname, port: url.port || (isHttps ? 443 : 9001), path: `/${config.bucket}${path}`, method: method, headers: headers }; const req = httpModule.request(options, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { if (res.statusCode >= 200 && res.statusCode < 300) { resolve(data); } else { reject(new Error(`HTTP ${res.statusCode}: ${data}`)); } }); }); req.on('error', reject); if (body) { req.write(body); } req.end(); }); } /** * 上传文件到 RUSTFS */ async function uploadFile(fileData, key, contentType = 'image/jpeg') { // 解析 endpoint const endpoint = config.endpoint; const url = new URL(endpoint); const isHttps = url.protocol === 'https:'; const httpModule = isHttps ? https : http; const date = new Date().toISOString().replace(/[:\-]|\.\d{3}/g, '').substring(0, 8); const datetime = date + 'T000000Z'; // 创建签名字符串 const credential = `${config.accessKey}/${date}/us-east-1/s3/aws4_request`; const signedHeaders = 'content-type;host;x-amz-content-sha256;x-amz-date'; const contentHash = crypto.createHash('sha256').update(fileData).digest('hex'); const canonicalHeaders = [ `content-type:${contentType}`, `host:${url.host}`, `x-amz-content-sha256:${contentHash}`, `x-amz-date:${datetime}` ].join('\n'); const canonicalRequest = [ 'PUT', `/${config.bucket}/${key}`, '', canonicalHeaders, '', signedHeaders, contentHash ].join('\n'); const stringToSign = [ 'AWS4-HMAC-SHA256', datetime, `${date}/us-east-1/s3/aws4_request`, crypto.createHash('sha256').update(canonicalRequest).digest('hex') ].join('\n'); // 计算签名 const k1 = crypto.createHmac('sha256', 'AWS4' + config.secretKey).update(date).digest(); const k2 = crypto.createHmac('sha256', k1).update('us-east-1').digest(); const k3 = crypto.createHmac('sha256', k2).update('s3').digest(); const k4 = crypto.createHmac('sha256', k3).update('aws4_request').digest(); const signature = crypto.createHmac('sha256', k4).update(stringToSign).digest('hex'); const authorization = `AWS4-HMAC-SHA256 Credential=${credential}, SignedHeaders=${signedHeaders}, Signature=${signature}`; return new Promise((resolve, reject) => { const options = { hostname: url.hostname, port: url.port || (isHttps ? 443 : 9001), path: `/${config.bucket}/${key}`, method: 'PUT', headers: { 'Content-Type': contentType, 'Host': url.host, 'X-Amz-Content-Sha256': contentHash, 'X-Amz-Date': datetime, 'Authorization': authorization, 'Content-Length': Buffer.byteLength(fileData) } }; const req = httpModule.request(options, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { if (res.statusCode >= 200 && res.statusCode < 300) { resolve(`${endpoint}/${config.bucket}/${key}`); } else { reject(new Error(`Upload failed: HTTP ${res.statusCode} - ${data}`)); } }); }); req.on('error', reject); req.write(fileData); req.end(); }); } /** * 删除 RUSTFS 中的文件 */ async function deleteFile(key) { const endpoint = config.endpoint; const url = new URL(endpoint); const isHttps = url.protocol === 'https:'; const httpModule = isHttps ? https : http; const datetime = new Date().toISOString().replace(/[:\-]|\.\d{3}/g, '').substring(0, 8) + 'T000000Z'; return new Promise((resolve, reject) => { const options = { hostname: url.hostname, port: url.port || (isHttps ? 443 : 9001), path: `/${config.bucket}/${key}`, method: 'DELETE', headers: { 'Host': url.host, 'X-Amz-Date': datetime, 'Authorization': `AWS ${config.accessKey}:${crypto.createHash('sha256').update(config.secretKey).digest('hex')}` } }; const req = httpModule.request(options, (res) => { if (res.statusCode >= 200 && res.statusCode < 300) { resolve(); } else { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => reject(new Error(`Delete failed: HTTP ${res.statusCode}`))); } }); req.on('error', reject); req.end(); }); } module.exports = { uploadFile, deleteFile, config };