oak-general-business/es/utils/cos/s3.backend.js

147 lines
6.7 KiB
JavaScript
Raw 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.

import { assert } from 'oak-domain/lib/utils/assert';
import S3 from './s3';
import { S3SDK } from 'oak-external-sdk';
import { OakExternalException } from 'oak-domain/lib/types/Exception';
export default class S3Backend extends S3 {
getConfigAndInstance(application, bucket) {
const { config, account, endpoint, defaultBucket } = this.getConfig(application);
const realBucket = bucket || defaultBucket;
assert(realBucket, '没有指定上传桶,且配置中也没有默认上传桶');
// 在配置中找名称匹配的桶
const { buckets } = config;
let bucketConfig = bucket
? buckets.find((ele) => ele.name === realBucket)
: buckets[0];
const instance = S3SDK.getInstance(account.accessKey, account.secretKey, endpoint, bucketConfig?.zone || 'us-east-1');
return {
config,
instance,
endpoint,
};
}
async composeFileUrlBackend(options) {
const { application, extraFile, context, style } = options;
const { config: s3CosConfig, endpoint } = this.getConfig(application);
if (s3CosConfig) {
let bucket = s3CosConfig.buckets.find((ele) => ele.name === extraFile.bucket);
if (bucket) {
const { domain, protocol, pathStyle } = bucket;
let protocol2 = protocol;
if (protocol instanceof Array) {
const index = protocol.includes('https:')
? protocol.findIndex((ele) => ele === 'https:')
: 0;
protocol2 = protocol[index];
}
const key = this.formKey(extraFile);
// 如果使用 pathStyle (Minio 常用)
if (pathStyle && endpoint) {
return `${protocol2}//${domain}/${bucket.name}/${key}${style ? style : ''}`;
}
// 否则使用虚拟主机风格 (AWS S3 默认)
return `${protocol2}//${domain}/${key}${style ? style : ''}`;
}
}
return '';
}
async formUploadMeta(application, extraFile) {
const { bucket } = extraFile;
const key = this.formKey(extraFile);
const { instance, config: s3CosConfig, endpoint } = this.getConfigAndInstance(application, bucket);
const { buckets } = s3CosConfig;
let bucket2 = bucket;
if (!bucket2) {
const { defaultBucket } = s3CosConfig;
bucket2 = defaultBucket;
}
assert(bucket2);
const b = buckets.find((ele) => ele.name === bucket2);
assert(b, `${bucket2}不是一个有效的桶配置`);
try {
const uploadInfo = await instance.getUploadInfo(bucket2, key, endpoint, b.pathStyle);
Object.assign(extraFile, {
bucket: bucket2,
uploadMeta: uploadInfo,
});
}
catch (err) {
throw new OakExternalException(`生成S3上传信息失败: ${err.message}`);
}
}
async checkWhetherSuccess(application, extraFile) {
const { bucket } = extraFile;
const key = this.formKey(extraFile);
const { instance, config: s3CosConfig, endpoint } = this.getConfigAndInstance(application, bucket);
const b = s3CosConfig.buckets.find((ele) => ele.name === extraFile.bucket);
assert(b, `extraFile中的bucket名称在S3配置中找不到「${extraFile.bucket}`);
try {
const result = await instance.isExistObject(extraFile.bucket, key, endpoint, b.pathStyle);
return result;
}
catch (err) {
throw err;
}
}
async removeFile(application, extraFile) {
const { bucket } = extraFile;
const key = this.formKey(extraFile);
const { instance, config: s3CosConfig, endpoint } = this.getConfigAndInstance(application, bucket);
const b = s3CosConfig.buckets.find((ele) => ele.name === extraFile.bucket);
assert(b, `extraFile中的bucket名称在S3配置中找不到「${extraFile.bucket}`);
try {
await instance.removeFile(extraFile.bucket, key, endpoint, b.pathStyle);
}
catch (err) {
throw err;
}
}
async composeChunkUploadInfo(application, extraFile, context) {
const key = this.formKey(extraFile);
const { instance, config: s3Config } = this.getConfigAndInstance(application, extraFile.bucket);
const preInit = await instance.prepareMultipartUpload(extraFile.bucket, key, extraFile.chunkInfo?.partCount, {
endpoint: s3Config.endpoint,
expiresIn: 30000, // 上传链接过期时间,单位秒
});
return {
uploadId: preInit.uploadId,
chunkSize: extraFile.chunkInfo?.chunkSize,
partCount: preInit.parts.length,
parts: preInit.parts.map((part) => ({
partNumber: part.partNumber,
uploadUrl: part.uploadUrl,
formData: {}, // S3不需要额外的formData
})),
};
}
/**
* 完成分片上传后的合并操作
*/
async mergeChunkedUpload(application, extraFile, context) {
const key = this.formKey(extraFile);
const { instance, config: s3CosConfig } = this.getConfigAndInstance(application, extraFile.bucket);
await instance.completeMultipartUpload(extraFile.bucket, key, extraFile.chunkInfo.uploadId, extraFile.chunkInfo.parts.map((part) => ({
partNumber: part.partNumber,
eTag: part.etag,
})), s3CosConfig.endpoint);
}
async abortMultipartUpload(application, extraFile, context) {
const key = this.formKey(extraFile);
const { instance, config: s3CosConfig } = this.getConfigAndInstance(application, extraFile.bucket);
await instance.abortMultipartUpload(extraFile.bucket, key, extraFile.chunkInfo.uploadId, s3CosConfig.endpoint);
}
async listMultipartUploads(application, extraFile, context) {
const key = this.formKey(extraFile);
const { instance, config: s3CosConfig } = this.getConfigAndInstance(application, extraFile.bucket);
const result = await instance.listParts(extraFile.bucket, key, extraFile.chunkInfo.uploadId, s3CosConfig.endpoint, 101);
assert(result.isTruncated === false, `分片数量超过101无法列出所有分片信息不应当出现这个情况触发器中已经限制了最大分片数量为100`);
return {
parts: result.parts.map((part) => ({
partNumber: part.partNumber,
etag: part.eTag,
size: part.size,
lastModified: part.lastModified,
}))
};
}
}