"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const assert_1 = require("oak-domain/lib/utils/assert"); const s3_1 = tslib_1.__importDefault(require("./s3")); const oak_external_sdk_1 = require("oak-external-sdk"); const Exception_1 = require("oak-domain/lib/types/Exception"); class S3Backend extends s3_1.default { getConfigAndInstance(application, bucket) { const { config, account, endpoint, defaultBucket } = this.getConfig(application); const realBucket = bucket || defaultBucket; (0, assert_1.assert)(realBucket, '没有指定上传桶,且配置中也没有默认上传桶'); // 在配置中找名称匹配的桶 const { buckets } = config; let bucketConfig = bucket ? buckets.find((ele) => ele.name === realBucket) : buckets[0]; const instance = oak_external_sdk_1.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; } (0, assert_1.assert)(bucket2); const b = buckets.find((ele) => ele.name === bucket2); (0, assert_1.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 Exception_1.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); (0, assert_1.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); (0, assert_1.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: 3 * 24 * 60 * 60, // 3 days }); 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); (0, assert_1.assert)(result.isTruncated === false, `分片数量超过101,无法列出所有分片信息,不应当出现这个情况,触发器中已经限制了最大分片数量为100`); return { parts: result.parts.map((part) => ({ partNumber: part.partNumber, etag: part.eTag, size: part.size, lastModified: part.lastModified, })) }; } } exports.default = S3Backend;