oak-external-sdk/lib/service/s3/S3.js

383 lines
13 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.S3Instance = void 0;
const client_s3_1 = require("@aws-sdk/client-s3");
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
const types_1 = require("oak-domain/lib/types");
class S3Instance {
accessKey;
secretKey;
client;
defaultEndpoint;
defaultRegion;
constructor(accessKey, secretKey, endpoint, region = 'us-east-1') {
this.accessKey = accessKey;
this.secretKey = secretKey;
this.defaultEndpoint = endpoint;
this.defaultRegion = region;
// 创建默认客户端
this.client = this.createClient(endpoint, region);
}
createClient(endpoint, region = this.defaultRegion) {
const config = {
region,
credentials: {
accessKeyId: this.accessKey,
secretAccessKey: this.secretKey,
},
};
// 如果指定了 endpoint (Minio 场景)
if (endpoint) {
let url = endpoint;
if (!/^https?:\/\//.test(url)) {
url = `http://${url}`; // 自动补协议
}
if (!url.endsWith('/')) {
url += '/'; // 自动补尾斜杠
}
config.endpoint = url;
config.tls = url.startsWith('https');
config.forcePathStyle = true; // Minio 通常需要路径风格
}
return new client_s3_1.S3Client(config);
}
/**
* 获取上传信息(预签名 URL)
*/
async getUploadInfo(bucket, key, endpoint, pathStyle = false) {
try {
const client = endpoint
? this.createClient(endpoint, this.defaultRegion)
: this.client;
const command = new client_s3_1.PutObjectCommand({
Bucket: bucket,
Key: key,
});
// 生成预签名 URL有效期 1 小时
const uploadUrl = await (0, s3_request_presigner_1.getSignedUrl)(client, command, {
expiresIn: 3600,
});
return {
key,
uploadUrl,
bucket,
accessKey: this.accessKey,
};
}
catch (err) {
throw new types_1.OakExternalException('s3', err.code, err.message, err, 'oak-external-sdk', {});
}
}
/**
* 删除文件
*/
async removeFile(bucket, key, endpoint, pathStyle = false) {
try {
const client = endpoint
? this.createClient(endpoint, this.defaultRegion)
: this.client;
const command = new client_s3_1.DeleteObjectCommand({
Bucket: bucket,
Key: key,
});
await client.send(command);
}
catch (err) {
throw new types_1.OakExternalException('s3', err.code, err.message, err, 'oak-external-sdk', {});
}
}
/**
* 检查对象是否存在
*/
async isExistObject(bucket, key, endpoint, pathStyle = false) {
try {
const client = endpoint
? this.createClient(endpoint, this.defaultRegion)
: this.client;
const command = new client_s3_1.HeadObjectCommand({
Bucket: bucket,
Key: key,
});
await client.send(command);
return true;
}
catch (err) {
if (err.name === 'NotFound' || err.$metadata?.httpStatusCode === 404) {
return false;
}
throw new types_1.OakExternalException('s3', err.code, err.message, err, 'oak-external-sdk', {});
}
}
/**
* 获取文件访问 URL
*/
getFileUrl(bucket, key, endpoint, pathStyle = false) {
if (endpoint) {
// Minio 或自定义 endpoint
if (pathStyle) {
return `${endpoint}/${bucket}/${key}`;
}
return `${endpoint.replace(/https?:\/\//, `$&${bucket}.`)}/${key}`;
}
// AWS S3 默认
return `https://${bucket}.s3.${this.defaultRegion}.amazonaws.com/${key}`;
}
/**
* 创建分片上传
*/
async createMultipartUpload(bucket, key, endpoint, contentType) {
try {
const client = endpoint
? this.createClient(endpoint, this.defaultRegion)
: this.client;
const command = new client_s3_1.CreateMultipartUploadCommand({
Bucket: bucket,
Key: key,
ContentType: contentType,
});
const response = await client.send(command);
return {
uploadId: response.UploadId,
bucket: response.Bucket,
key: response.Key,
};
}
catch (err) {
throw new types_1.OakExternalException('s3', err.code, err.message, err, 'oak-external-sdk', {});
}
}
/**
* 为分片上传生成预签名 URL
* @param bucket 桶
* @param key 对象键
* @param uploadId 上传 ID
* @param from 起始分片号
* @param to 结束分片号
* @param options 配置项
* @returns 分片预签名 URL 列表
*/
async presignMulti(bucket, key, uploadId, from, to, options) {
const client = options?.endpoint
? this.createClient(options.endpoint, this.defaultRegion)
: this.client;
// 2. 为每个分片生成预签名 URL
const parts = [];
const expiresIn = options?.expiresIn || 3600;
for (let i = from; i <= to; i++) {
const uploadCommand = new client_s3_1.UploadPartCommand({
Bucket: bucket,
Key: key,
UploadId: uploadId,
PartNumber: i,
});
const uploadUrl = await (0, s3_request_presigner_1.getSignedUrl)(client, uploadCommand, {
expiresIn: expiresIn,
});
parts.push({
partNumber: i,
uploadUrl: uploadUrl,
});
}
return parts;
}
/**
* 准备分片上传创建分片上传并生成所有分片的预签名URL
* 用于前端直传场景,返回 uploadId 和每个分片的上传 URL
*/
async prepareMultipartUpload(bucket, key, partCount, options) {
try {
const client = options?.endpoint
? this.createClient(options.endpoint, this.defaultRegion)
: this.client;
// 1. 创建分片上传
const createCommand = new client_s3_1.CreateMultipartUploadCommand({
Bucket: bucket,
Key: key,
ContentType: options?.contentType,
});
const createResponse = await client.send(createCommand);
const uploadId = createResponse.UploadId;
// 2. 为每个分片生成预签名 URL
const parts = [];
const expiresIn = options?.expiresIn || 3600;
for (let i = 1; i <= partCount; i++) {
const uploadCommand = new client_s3_1.UploadPartCommand({
Bucket: bucket,
Key: key,
UploadId: uploadId,
PartNumber: i,
});
const uploadUrl = await (0, s3_request_presigner_1.getSignedUrl)(client, uploadCommand, {
expiresIn: expiresIn,
});
parts.push({
partNumber: i,
uploadUrl: uploadUrl,
});
}
return {
uploadId: uploadId,
parts: parts,
};
}
catch (err) {
throw new types_1.OakExternalException('s3', err.code, err.message, err, 'oak-external-sdk', {});
}
}
/**
* 上传分片
*/
async uploadPart(bucket, key, uploadId, partNumber, body, endpoint) {
try {
const client = endpoint
? this.createClient(endpoint, this.defaultRegion)
: this.client;
const command = new client_s3_1.UploadPartCommand({
Bucket: bucket,
Key: key,
UploadId: uploadId,
PartNumber: partNumber,
Body: body,
});
const response = await client.send(command);
return {
partNumber,
eTag: response.ETag,
};
}
catch (err) {
throw new types_1.OakExternalException('s3', err.code, err.message, err, 'oak-external-sdk', {});
}
}
/**
* 完成分片上传
*/
async completeMultipartUpload(bucket, key, uploadId, parts, endpoint) {
try {
const client = endpoint
? this.createClient(endpoint, this.defaultRegion)
: this.client;
const command = new client_s3_1.CompleteMultipartUploadCommand({
Bucket: bucket,
Key: key,
UploadId: uploadId,
MultipartUpload: {
Parts: parts.map(part => ({
PartNumber: part.partNumber,
ETag: part.eTag,
})),
},
});
const response = await client.send(command);
return {
location: response.Location,
bucket: response.Bucket,
key: response.Key,
eTag: response.ETag,
};
}
catch (err) {
throw new types_1.OakExternalException('s3', err.code, err.message, err, 'oak-external-sdk', {});
}
}
/**
* 中止分片上传
*/
async abortMultipartUpload(bucket, key, uploadId, endpoint) {
try {
const client = endpoint
? this.createClient(endpoint, this.defaultRegion)
: this.client;
const command = new client_s3_1.AbortMultipartUploadCommand({
Bucket: bucket,
Key: key,
UploadId: uploadId,
});
await client.send(command);
}
catch (err) {
throw new types_1.OakExternalException('s3', err.code, err.message, err, 'oak-external-sdk', {});
}
}
/**
* 列出已上传的分片
*/
async listParts(bucket, key, uploadId, endpoint, maxParts) {
try {
const client = endpoint
? this.createClient(endpoint, this.defaultRegion)
: this.client;
const command = new client_s3_1.ListPartsCommand({
Bucket: bucket,
Key: key,
UploadId: uploadId,
MaxParts: maxParts,
});
const response = await client.send(command);
return {
parts: response.Parts?.map(part => ({
partNumber: part.PartNumber,
eTag: part.ETag,
size: part.Size,
lastModified: part.LastModified,
})) || [],
isTruncated: response.IsTruncated,
nextPartNumberMarker: response.NextPartNumberMarker,
};
}
catch (err) {
throw new types_1.OakExternalException('s3', err.code, err.message, err, 'oak-external-sdk', {});
}
}
/**
* 获取预签名 URL通用方法
*/
async getSignedUrl(bucket, key, operation = 'get', expiresIn = 3600, endpoint) {
try {
const client = endpoint
? this.createClient(endpoint, this.defaultRegion)
: this.client;
const command = operation === 'put'
? new client_s3_1.PutObjectCommand({ Bucket: bucket, Key: key })
: new client_s3_1.GetObjectCommand({ Bucket: bucket, Key: key });
const url = await (0, s3_request_presigner_1.getSignedUrl)(client, command, { expiresIn });
return url;
}
catch (err) {
throw new types_1.OakExternalException('s3', err.code, err.message, err, 'oak-external-sdk', {});
}
}
/**
* 获取预签名对象URL统一接口
*/
async presignObjectUrl(method, bucket, zone = '', key, options) {
try {
const client = options?.endpoint
? this.createClient(options.endpoint, this.defaultRegion)
: this.client;
let req;
switch (method) {
case 'GET':
req = new client_s3_1.GetObjectCommand({ Bucket: bucket, Key: key });
break;
case 'PUT':
req = new client_s3_1.PutObjectCommand({ Bucket: bucket, Key: key });
break;
case 'DELETE':
req = new client_s3_1.DeleteObjectCommand({ Bucket: bucket, Key: key });
break;
case 'POST':
throw new Error('S3 不支持 POST 方法的预签名 URL');
}
const url = await (0, s3_request_presigner_1.getSignedUrl)(client, req, {
expiresIn: options?.expires || 3600,
});
return { url };
}
catch (error) {
throw new types_1.OakExternalException('s3', error.code, error.message, error, 'oak-external-sdk', {});
}
}
}
exports.S3Instance = S3Instance;