468 lines
15 KiB
JavaScript
468 lines
15 KiB
JavaScript
import OSS from 'ali-oss';
|
||
import { OakExternalException, } from 'oak-domain/lib/types/Exception';
|
||
function getEndpoint(RegionID) {
|
||
return `oss-${RegionID}.aliyuncs.com`;
|
||
}
|
||
const ALiYun_ENDPOINT_LIST = {
|
||
'cn-hangzhou': {
|
||
ul: getEndpoint('cn-hangzhou'),
|
||
},
|
||
'cn-shanghai': {
|
||
ul: getEndpoint('cn-shanghai'),
|
||
},
|
||
'cn-nanjing': {
|
||
ul: getEndpoint('cn-nanjing'),
|
||
},
|
||
'cn-fuzhou': {
|
||
ul: getEndpoint('cn-fuzhou'),
|
||
},
|
||
'cn-wuhan': {
|
||
ul: getEndpoint('cn-wuhan'),
|
||
},
|
||
'cn-qingdao': {
|
||
ul: getEndpoint('cn-qingdao'),
|
||
},
|
||
'cn-beijing': {
|
||
ul: getEndpoint('cn-beijing'),
|
||
},
|
||
'cn-zhangjiakou': {
|
||
ul: getEndpoint('cn-zhangjiakou'),
|
||
},
|
||
'cn-huhehaote': {
|
||
ul: getEndpoint('cn-huhehaote'),
|
||
},
|
||
'cn-wulanchabu': {
|
||
ul: getEndpoint('cn-wulanchabu'),
|
||
},
|
||
'cn-shenzhen': {
|
||
ul: getEndpoint('cn-shenzhen'),
|
||
},
|
||
'cn-heyuan': {
|
||
ul: getEndpoint('cn-heyuan'),
|
||
},
|
||
'cn-guangzhou': {
|
||
ul: getEndpoint('cn-guangzhou'),
|
||
},
|
||
'cn-chengdu': {
|
||
ul: getEndpoint('cn-chengdu'),
|
||
},
|
||
'cn-hongkong': {
|
||
ul: getEndpoint('cn-hongkong'),
|
||
},
|
||
'us-west-1': {
|
||
ul: getEndpoint('us-west-1'),
|
||
},
|
||
'us-east-1': {
|
||
ul: getEndpoint('us-east-1'),
|
||
},
|
||
'ap-northeast-1': {
|
||
ul: getEndpoint('ap-northeast-1'),
|
||
},
|
||
'ap-northeast-2': {
|
||
ul: getEndpoint('ap-northeast-2'),
|
||
},
|
||
'ap-southeast-1': {
|
||
ul: getEndpoint('ap-southeast-1'),
|
||
},
|
||
'ap-southeast-2': {
|
||
ul: getEndpoint('ap-southeast-2'),
|
||
},
|
||
'ap-southeast-3': {
|
||
ul: getEndpoint('ap-southeast-3'),
|
||
},
|
||
'ap-southeast-5': {
|
||
ul: getEndpoint('ap-southeast-5'),
|
||
},
|
||
'ap-southeast-6': {
|
||
ul: getEndpoint('ap-southeast-6'),
|
||
},
|
||
'ap-southeast-7': {
|
||
ul: getEndpoint('ap-southeast-7'),
|
||
},
|
||
'ap-south-1': {
|
||
ul: getEndpoint('ap-south-1'),
|
||
},
|
||
'eu-central-1': {
|
||
ul: getEndpoint('eu-central-1'),
|
||
},
|
||
'eu-west-1': {
|
||
ul: getEndpoint('eu-west-1'),
|
||
},
|
||
'me-east-1': {
|
||
ul: getEndpoint('me-east-1'),
|
||
},
|
||
'rg-china-mainland': {
|
||
ul: getEndpoint('rg-china-mainland'),
|
||
},
|
||
};
|
||
export class ALiYunInstance {
|
||
accessKey;
|
||
secretKey;
|
||
constructor(accessKey, secretKey) {
|
||
this.accessKey = accessKey;
|
||
this.secretKey = secretKey;
|
||
}
|
||
getUploadInfo(bucket, zone, key) {
|
||
try {
|
||
const signInfo = this.getSignInfo(bucket);
|
||
return {
|
||
key,
|
||
accessKey: this.accessKey,
|
||
policy: signInfo.policy,
|
||
signature: signInfo.signature,
|
||
uploadHost: `https://${bucket}.${ALiYun_ENDPOINT_LIST[zone].ul}`,
|
||
bucket,
|
||
};
|
||
}
|
||
catch (err) {
|
||
throw err;
|
||
}
|
||
}
|
||
//https://help.aliyun.com/zh/oss/user-guide/form-upload?spm=a2c4g.11186623.0.0.22147f6b1UaGxF#6ee9b6b0be6on
|
||
getSignInfo(bucket) {
|
||
const client = new OSS({
|
||
accessKeyId: this.accessKey,
|
||
accessKeySecret: this.secretKey,
|
||
bucket: bucket,
|
||
});
|
||
const now = new Date();
|
||
now.setDate(now.getDate() + 1);
|
||
const policy = {
|
||
expiration: now.toISOString(),
|
||
conditions: [
|
||
// 设置上传文件的大小限制。
|
||
['content-length-range', 0, 5368709120],
|
||
// 限制可上传的Bucket。
|
||
{ bucket: bucket },
|
||
],
|
||
};
|
||
const data = client.calculatePostSignature(policy);
|
||
return {
|
||
policy: data.policy,
|
||
signature: data.Signature,
|
||
ossAccessKeyId: data.OSSAccessKeyId,
|
||
};
|
||
}
|
||
async removeFile(srcBucket, zone, srcKey) {
|
||
const client = new OSS({
|
||
// oss-cn-hangzhou填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
|
||
region: `oss-${zone}`,
|
||
accessKeyId: this.accessKey,
|
||
accessKeySecret: this.secretKey,
|
||
// 填写Bucket名称。
|
||
bucket: srcBucket,
|
||
});
|
||
try {
|
||
// 填写Object完整路径。Object完整路径中不能包含Bucket名称。
|
||
const result = await client.delete(srcKey);
|
||
return result;
|
||
}
|
||
catch (error) {
|
||
throw error;
|
||
}
|
||
}
|
||
async isExistObject(srcBucket, zone, srcKey) {
|
||
const client = new OSS({
|
||
// yourregion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
|
||
region: `oss-${zone}`,
|
||
accessKeyId: this.accessKey,
|
||
accessKeySecret: this.secretKey,
|
||
bucket: srcBucket,
|
||
});
|
||
let result;
|
||
try {
|
||
result = await client.head(srcKey);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
if (error.code === 'NoSuchKey') {
|
||
return false;
|
||
}
|
||
throw error;
|
||
}
|
||
}
|
||
/**
|
||
* 初始化分片上传
|
||
* https://help.aliyun.com/zh/oss/developer-reference/initiate-a-multipart-upload-task
|
||
*/
|
||
async initiateMultipartUpload(bucket, zone, key, options) {
|
||
const client = new OSS({
|
||
region: `oss-${zone}`,
|
||
accessKeyId: options?.accessKeyId || this.accessKey,
|
||
accessKeySecret: options?.accessKeySecret || this.secretKey,
|
||
bucket: bucket,
|
||
stsToken: options?.stsToken,
|
||
});
|
||
try {
|
||
const result = await client.initMultipartUpload(key, options);
|
||
return {
|
||
uploadId: result.uploadId,
|
||
bucket: result.bucket,
|
||
name: result.name,
|
||
};
|
||
}
|
||
catch (error) {
|
||
throw new OakExternalException('aliyun', error.code, error.message, {
|
||
status: error.status,
|
||
});
|
||
}
|
||
}
|
||
/**
|
||
* 为分片上传生成预签名 URL
|
||
* @param bucket 桶
|
||
* @param key 对象键
|
||
* @param uploadId 上传 ID
|
||
* @param from 起始分片号
|
||
* @param to 结束分片号
|
||
* @param options 配置项
|
||
* @returns 分片预签名 URL 列表
|
||
*/
|
||
async presignMulti(bucket, zone, key, uploadId, from, to, options) {
|
||
const client = new OSS({
|
||
region: `oss-${zone}`,
|
||
accessKeyId: options?.accessKeyId || this.accessKey,
|
||
accessKeySecret: options?.accessKeySecret || this.secretKey,
|
||
bucket: bucket,
|
||
stsToken: options?.stsToken,
|
||
});
|
||
try {
|
||
const presignedUrls = [];
|
||
const expires = options?.expires || 3600;
|
||
for (let i = from; i <= to; i++) {
|
||
const uploadUrl = client.signatureUrl(key, {
|
||
method: 'PUT',
|
||
expires: expires,
|
||
subResource: {
|
||
uploadId: uploadId,
|
||
partNumber: String(i),
|
||
},
|
||
"Content-Type": "application/octet-stream",
|
||
});
|
||
presignedUrls.push({
|
||
partNumber: i,
|
||
uploadUrl: uploadUrl,
|
||
});
|
||
}
|
||
return presignedUrls;
|
||
}
|
||
catch (error) {
|
||
throw new OakExternalException('aliyun', error.code, error.message, {
|
||
status: error.status,
|
||
});
|
||
}
|
||
}
|
||
/**
|
||
* 准备分片上传(初始化并生成所有分片的预签名URL)
|
||
* 用于前端直传场景,返回 uploadId 和每个分片的上传 URL
|
||
*/
|
||
async prepareMultipartUpload(bucket, zone, key, partCount, options) {
|
||
const client = new OSS({
|
||
region: `oss-${zone}`,
|
||
accessKeyId: options?.accessKeyId || this.accessKey,
|
||
accessKeySecret: options?.accessKeySecret || this.secretKey,
|
||
bucket: bucket,
|
||
stsToken: options?.stsToken,
|
||
});
|
||
try {
|
||
// 1. 初始化分片上传
|
||
const initResult = await client.initMultipartUpload(key, {
|
||
headers: options?.headers,
|
||
timeout: options?.timeout,
|
||
});
|
||
// 2. 为每个分片生成预签名 URL
|
||
const parts = [];
|
||
const expires = options?.expires || 3600;
|
||
for (let i = 1; i <= partCount; i++) {
|
||
const uploadUrl = client.signatureUrl(key, {
|
||
method: 'PUT',
|
||
expires: expires,
|
||
subResource: {
|
||
partNumber: String(i),
|
||
uploadId: initResult.uploadId,
|
||
},
|
||
"Content-Type": "application/octet-stream",
|
||
});
|
||
parts.push({
|
||
partNumber: i,
|
||
uploadUrl: uploadUrl,
|
||
});
|
||
}
|
||
return {
|
||
uploadId: initResult.uploadId,
|
||
parts: parts,
|
||
};
|
||
}
|
||
catch (error) {
|
||
throw new OakExternalException('aliyun', error.code, error.message, {
|
||
status: error.status,
|
||
});
|
||
}
|
||
}
|
||
/**
|
||
* 上传分片
|
||
* https://help.aliyun.com/zh/oss/developer-reference/upload-parts
|
||
*/
|
||
async uploadPart(bucket, zone, key, uploadId, partNumber, file, start, end, options) {
|
||
const client = new OSS({
|
||
region: `oss-${zone}`,
|
||
accessKeyId: options?.accessKeyId || this.accessKey,
|
||
accessKeySecret: options?.accessKeySecret || this.secretKey,
|
||
bucket: bucket,
|
||
stsToken: options?.stsToken,
|
||
});
|
||
try {
|
||
const result = await client.uploadPart(key, uploadId, partNumber, file, start ?? 0, end ?? 0, options);
|
||
return {
|
||
etag: result.etag,
|
||
name: result.name,
|
||
};
|
||
}
|
||
catch (error) {
|
||
throw new OakExternalException('aliyun', error.code, error.message, {
|
||
status: error.status,
|
||
});
|
||
}
|
||
}
|
||
/**
|
||
* 完成分片上传
|
||
* https://help.aliyun.com/zh/oss/developer-reference/complete-a-multipart-upload-task
|
||
*/
|
||
async completeMultipartUpload(bucket, zone, key, uploadId, parts, options) {
|
||
const client = new OSS({
|
||
region: `oss-${zone}`,
|
||
accessKeyId: this.accessKey,
|
||
accessKeySecret: this.secretKey,
|
||
bucket: bucket,
|
||
stsToken: options?.stsToken,
|
||
});
|
||
try {
|
||
const result = await client.completeMultipartUpload(key, uploadId, parts, options);
|
||
return {
|
||
bucket: result.bucket,
|
||
name: result.name,
|
||
etag: result.etag,
|
||
};
|
||
}
|
||
catch (error) {
|
||
throw new OakExternalException('aliyun', error.code, error.message, {
|
||
status: error.status,
|
||
});
|
||
}
|
||
}
|
||
/**
|
||
* 取消分片上传
|
||
* https://help.aliyun.com/zh/oss/developer-reference/abort-a-multipart-upload-task
|
||
*/
|
||
async abortMultipartUpload(bucket, zone, key, uploadId, options) {
|
||
const client = new OSS({
|
||
region: `oss-${zone}`,
|
||
accessKeyId: this.accessKey,
|
||
accessKeySecret: this.secretKey,
|
||
bucket: bucket,
|
||
stsToken: options?.stsToken,
|
||
});
|
||
try {
|
||
await client.abortMultipartUpload(key, uploadId, options);
|
||
}
|
||
catch (error) {
|
||
throw new OakExternalException('aliyun', error.code, error.message, {
|
||
status: error.status,
|
||
});
|
||
}
|
||
}
|
||
/**
|
||
* 列举已上传的分片
|
||
* https://help.aliyun.com/zh/oss/developer-reference/list-uploaded-parts
|
||
*/
|
||
async listParts(bucket, zone, key, uploadId, options) {
|
||
const client = new OSS({
|
||
region: `oss-${zone}`,
|
||
accessKeyId: this.accessKey,
|
||
accessKeySecret: this.secretKey,
|
||
bucket: bucket,
|
||
stsToken: options?.stsToken,
|
||
});
|
||
try {
|
||
const result = await client.listParts(key, uploadId, {
|
||
'max-parts': options?.['max-parts']?.toString(),
|
||
'part-number-marker': options?.['part-number-marker']?.toString(),
|
||
});
|
||
return {
|
||
parts: result.parts.map((part) => ({
|
||
partNumber: part.PartNumber,
|
||
lastModified: part.LastModified,
|
||
etag: part.ETag,
|
||
size: part.Size,
|
||
})),
|
||
nextPartNumberMarker: result.nextPartNumberMarker,
|
||
isTruncated: result.isTruncated,
|
||
};
|
||
}
|
||
catch (error) {
|
||
throw new OakExternalException('aliyun', error.code, error.message, {
|
||
status: error.status,
|
||
});
|
||
}
|
||
}
|
||
/**
|
||
* 列举所有执行中的分片上传任务
|
||
* https://help.aliyun.com/zh/oss/developer-reference/list-initiated-multipart-upload-tasks
|
||
*/
|
||
async listMultipartUploads(bucket, zone, options) {
|
||
const client = new OSS({
|
||
region: `oss-${zone}`,
|
||
accessKeyId: this.accessKey,
|
||
accessKeySecret: this.secretKey,
|
||
bucket: bucket,
|
||
stsToken: options?.stsToken,
|
||
});
|
||
try {
|
||
const result = await client.listUploads(options || {});
|
||
return {
|
||
uploads: result.uploads.map((upload) => ({
|
||
name: upload.name,
|
||
uploadId: upload.uploadId,
|
||
initiated: upload.initiated,
|
||
})),
|
||
nextKeyMarker: result.nextKeyMarker,
|
||
nextUploadIdMarker: result.nextUploadIdMarker,
|
||
isTruncated: result.isTruncated,
|
||
};
|
||
}
|
||
catch (error) {
|
||
throw new OakExternalException('aliyun', error.code, error.message, {
|
||
status: error.status,
|
||
});
|
||
}
|
||
}
|
||
/**
|
||
* 获取签名URL
|
||
* https://help.aliyun.com/zh/oss/developer-reference/authorize-access-4
|
||
*/
|
||
async getSignedUrl(bucket, zone, key, options) {
|
||
const client = new OSS({
|
||
region: `oss-${zone}`,
|
||
accessKeyId: this.accessKey,
|
||
accessKeySecret: this.secretKey,
|
||
bucket: bucket,
|
||
});
|
||
try {
|
||
const url = client.signatureUrl(key, options);
|
||
return url;
|
||
}
|
||
catch (error) {
|
||
throw new OakExternalException('aliyun', error.code, error.message);
|
||
}
|
||
}
|
||
/**
|
||
* 获取预签名对象URL(统一接口)
|
||
*/
|
||
async presignObjectUrl(method, bucket, zone, key, options) {
|
||
const url = await this.getSignedUrl(bucket, zone, key, {
|
||
...options,
|
||
method,
|
||
});
|
||
return { url };
|
||
}
|
||
}
|