This commit is contained in:
dcr_xuxgc
2026-06-12 17:49:54 +08:00
commit d759a9e740
69 changed files with 14243 additions and 0 deletions

235
server/src/config/rustfs.js Normal file
View File

@@ -0,0 +1,235 @@
/**
* 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
};