UNPKG

object-storage-js

Version:

js直传server、minio、obs、oss、s3系列

587 lines (571 loc) 23.1 kB
'use strict'; // 对象存储类型 var ObjectStorageType; (function (ObjectStorageType) { // 直传服务器 ObjectStorageType["NORMAL"] = "normal"; // oss ObjectStorageType["OSS"] = "oss"; // obs ObjectStorageType["OBS"] = "obs"; // minio ObjectStorageType["MINIO"] = "minio"; // aws s3 ObjectStorageType["AWS"] = "aws"; })(ObjectStorageType || (ObjectStorageType = {})); function initFactor(params) { const { sdkOtherParams = {} } = params; let instance; if (!params.type) { throw new Error('请选择需上传的对象存储类型'); } switch (params.type) { case ObjectStorageType.NORMAL: break; case ObjectStorageType.OBS: if (!params.sdkObject) throw new Error('未传入obs skd库'); if (!params.endPoint) throw new Error('未传入endpoint'); if (!params.bucketName) throw new Error('未传入bucketName'); let obsParams = Object.assign(Object.assign({}, sdkOtherParams), { access_key_id: params.accessKey, secret_access_key: params.secretKey, server: params.endPoint }); // 存在sts if (params.securityToken) obsParams.security_token = params.securityToken; instance = new params.sdkObject(obsParams); break; case ObjectStorageType.OSS: if (!params.sdkObject) throw new Error('未传入oss skd库'); if (!params.endPoint) throw new Error('未传入endpoint'); if (!params.bucketName) throw new Error('未传入bucketName'); let ossParams = Object.assign(Object.assign({}, sdkOtherParams), { accessKeyId: params.accessKey, accessKeySecret: params.secretKey, authorizationV4: true, endpoint: params.endPoint, bucket: params.bucketName }); // 未传入region,从endPoint中取 if (!params.region) { const ossRegionReg = /https?:\/\/([a-zA-Z0-9-]+)\.aliyuncs\.com/; const match = params.endPoint.match(ossRegionReg); if (match && match[1]) { params.region = match[1]; } else { throw new Error('未解析出region'); } } else { ossParams.region = params.region; } // 存在sts if (params.securityToken) ossParams.stsToken = params.securityToken; instance = new params.sdkObject(ossParams); break; // minio aws全部采用aws库 todo sdk采用V2版本 case ObjectStorageType.MINIO: case ObjectStorageType.AWS: if (!params.sdkObject) throw new Error('未传入aws v2 skd库'); let S3Params = Object.assign(Object.assign({}, sdkOtherParams), { accessKeyId: params.accessKey, secretAccessKey: params.secretKey, endpoint: params.endPoint, s3ForcePathStyle: params.type === ObjectStorageType.MINIO ? true : null, signatureVersion: 'v4' }); if (params.securityToken) S3Params.sessionToken = params.securityToken; params.sdkObject.config.update(S3Params); instance = new params.sdkObject.S3({}); break; } return instance; } /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise */ function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function httpRequest(options) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); if (options.xhrInstance && typeof options.xhrInstance === 'object') { options.xhrInstance.instance = xhr; } xhr.open(options.method, options.url, true); xhr.upload.onprogress = (event) => { if (options.onProgress) { options.onProgress({ loaded: event.loaded, total: event.total }); } }; xhr.onabort = () => { reject('request aborted'); }; xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { resolve(xhr.responseText); } else { reject(new Error(`Request failed with status ${xhr.status}`)); } }; xhr.onerror = (err) => { reject(new Error('Network error')); }; if (options.data) { xhr.send(options.data); } else { xhr.send(); } }); } /** * 直传服务器 */ function upload$3(params) { return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { const file = params.file; // 校验参数 if (!file) return reject('未提供上传文件'); const { maxSize = -1, partSize = -1, mergeUrl = '', queueLength = 0, uploadUrl = '', } = params; if (!uploadUrl) return reject('上传地址不能为空'); // 是否采用分片 const isPart = maxSize > 0 && file.size > maxSize; // 普通上传 if (!isPart) { // 函数引用传递 let uploadAbort = { instance: null, }; // 注册中断 let Abort = {}; Abort.cancel = function () { var _a; (_a = uploadAbort.instance) === null || _a === void 0 ? void 0 : _a.abort(); }; params.onCancel && params.onCancel(Abort); // 组装上传参数 let formData = new FormData(); formData.append('file', file); if (params.fileName) formData.append('fileName', params.fileName); httpRequest({ url: uploadUrl, method: 'post', data: formData, onProgress: params.onUploadProgress, xhrInstance: uploadAbort, }) .then((res) => { resolve(res); }) .catch((error) => { reject(error); }); } // 切片上传 if (isPart) { if (partSize <= 0) return reject('分片大小不能小于0'); if (!mergeUrl) return reject('分片合并地址不能为空'); // 切片总数 const chunks = Math.ceil(file.size / partSize); let uploadList = []; let mergeAbort = { instance: null, }; // 注册中断 let Abort = {}; Abort.cancel = function () { var _a; // 中断切片上传 uploadList.forEach((item) => { var _a; (_a = item.instance) === null || _a === void 0 ? void 0 : _a.abort(); }); // 中断合并 (_a = mergeAbort.instance) === null || _a === void 0 ? void 0 : _a.abort(); }; params.onCancel && params.onCancel(Abort); // 按照生成随机uploadId let uploadId = new Date().getTime().toString() + (Math.random() * 100000).toFixed(0); for (let i = 1; i <= chunks; i++) { const start = (i - 1) * partSize; const end = Math.min(start + partSize, file.size) - 1; uploadList.push({ chunk: i, file: file.slice(start, end + 1), uploadId, // 中断 instance: null, }); } // 队列上传 for (let i = 0; i < queueLength; i++) { uploadList[i].uploading = true; loop(uploadList[i]); } // 上传切片 function upload(item) { return new Promise((resolve, reject) => { let data = new FormData(); data.append('file', item.file); data.append('uploadId', item.uploadId); data.append('chunk', item.chunk); data.append('chunks', chunks.toString()); httpRequest({ url: params.uploadUrl, method: 'post', data, xhrInstance: item, }) .then((data) => { resolve(data); }) .catch((err) => { reject(err); }); }); } // 加入队列 function loop(item) { upload(item) .then(() => { item.uploaded = true; let uploadedLen = uploadList.filter((var0) => var0.uploaded).length; // 按照切片完成数量来返回进度 params.onUploadProgress && params.onUploadProgress({ loaded: Math.floor((uploadedLen / chunks) * file.size), total: file.size, }); // 找出未开始上传的文件 let index = uploadList.findIndex((item) => !item.uploading && !item.uploaded); if (index > -1) { uploadList[index].uploading = true; loop(uploadList[index]); } // 全部上传完毕,开始合并 if (uploadList.every((var0) => var0.uploaded)) { httpRequest({ url: `${mergeUrl}?uploadId=${uploadId}&fileName=${params.fileName}`, method: 'get', xhrInstance: mergeAbort, }) .then((res) => { resolve(res); }) .catch((err) => { reject(err); }); } }) .catch((err) => { reject(err); }); } } })); } function upload$2(instance, params, bucketName) { return new Promise((resolve, reject) => { const file = params.file; if (!file) return reject('未提供上传文件'); const { maxSize = -1, partSize = -1, queueLength = 1 } = params; // 是否采用分片 const isPart = maxSize > 0 && file.size > maxSize; // 非切片环境 if (!isPart) { let uploadAbort = { instance: null, }; uploadAbort.cancel = function () { var _a; (_a = uploadAbort.instance) === null || _a === void 0 ? void 0 : _a.abort(); }; params.onCancel && params.onCancel(uploadAbort); let signInfo = instance.createV4SignedUrlSync({ Bucket: bucketName, Key: params.fileName, Expires: 60 * 60 * 24, Method: 'PUT', }); if (!signInfo) return reject('预签名生成失败'); httpRequest({ url: signInfo.SignedUrl, method: 'put', data: file, xhrInstance: uploadAbort, onProgress: params.onUploadProgress, }) .then((res) => { resolve(res); }) .catch((error) => { reject(error); }); } // 切片环境 if (isPart) { instance.uploadFile({ Bucket: bucketName, Key: params.fileName, SourceFile: file, PartSize: isPart ? partSize : file.size, TaskNum: isPart ? queueLength : 1, ProgressCallback(transferredAmount, totalAmount) { params.onUploadProgress && params.onUploadProgress({ loaded: transferredAmount, total: totalAmount, }); }, ResumeCallback: function (resumeHook) { params.onCancel && params.onCancel(resumeHook); }, }, (err, result) => { resolve(result); if (err) { reject(err); } }); } }); } function upload$1(instance, params) { return new Promise((resolve, reject) => { const file = params.file; if (!file) return reject('未提供上传文件'); const { maxSize = -1, partSize = -1, queueLength = 1 } = params; // 是否采用分片 const isPart = maxSize > 0 && file.size > maxSize; // 中断信号 params.onCancel && params.onCancel(instance); instance .multipartUpload(params.fileName, file, { progress: (p) => { params.onUploadProgress && params.onUploadProgress({ loaded: Math.floor(p * file.size), total: file.size, }); }, parallel: isPart ? queueLength : 1, partSize: isPart ? partSize : file.size, }) .then((res) => { resolve(res); }) .catch((err) => { reject(err); }); }); } function upload(instance, params, bucketName) { return new Promise((resolve, reject) => { const file = params.file; if (!file) return reject('未提供上传文件'); const { maxSize = -1, partSize = -1, queueLength = 1 } = params; // 是否采用分片 const isPart = maxSize > 0 && file.size > maxSize; // 非切片 if (!isPart) { let Abort = {}; Abort.cancel = function () { var _a; (_a = Abort.instance) === null || _a === void 0 ? void 0 : _a.abort(); }; params.onCancel && params.onCancel(Abort); instance .getSignedUrlPromise('putObject', { Bucket: bucketName, Key: params.fileName, Expires: 60 * 60 * 24, }) .then((url) => { httpRequest({ url, method: 'put', data: file, xhrInstance: Abort, onProgress: params.onUploadProgress, }) .then((res) => { resolve(res); }) .catch((err) => { reject(err); }); }) .catch((err) => { reject(err); }); } // 切片 if (isPart) { // 获取分片上传id instance .createMultipartUpload({ Bucket: bucketName, Key: params.fileName, }) .promise() .then((result) => { const uploadId = result.UploadId; // 统一 cancel 方法中断切片上传 let Abort = {}; Abort.cancel = function () { // 中断s3上传 instance.abortMultipartUpload({ Bucket: bucketName, Key: params.fileName, UploadId: uploadId, }); }; params.onCancel && params.onCancel(Abort); // 所有切片临时存储 let completeParts = []; // 开始切片 const chunks = Math.ceil(file.size / partSize); let uploadList = []; // 组装切片数据 for (let i = 1; i <= chunks; i++) { const start = (i - 1) * partSize; const end = Math.min(start + partSize, file.size) - 1; uploadList.push({ PartNumber: i, Body: file.slice(start, end + 1), }); } // 上传 function upload(item) { return new Promise((resolve, reject) => { instance .uploadPart({ Bucket: bucketName, Key: params.fileName, UploadId: uploadId, PartNumber: item.PartNumber, Body: item.Body, }) .promise() .then((data) => { completeParts.push({ PartNumber: item.PartNumber, ETag: data.ETag, }); resolve(null); }) .catch((err) => { reject(err); }); }); } // 队列上传 for (let i = 0; i < queueLength; i++) { uploadList[i].uploading = true; loop(uploadList[i]); } // 加入队列 function loop(item) { upload(item) .then(() => { item.uploaded = true; let uploadedLen = uploadList.filter((var0) => var0.uploaded).length; // 按照切片完成数量来返回进度 params.onUploadProgress && params.onUploadProgress({ loaded: Math.floor((uploadedLen / chunks) * file.size), total: file.size, }); // 找出未开始上传的文件 let index = uploadList.findIndex((item) => !item.uploading && !item.uploaded); if (index > -1) { uploadList[index].uploading = true; loop(uploadList[index]); } // 全部上传完毕,开始合并 if (uploadList.every((var0) => var0.uploaded)) { // 按照切片文件排序 ,避免合并后文件错误 const finalParts = completeParts.sort((a, b) => a.PartNumber - b.PartNumber); instance .completeMultipartUpload({ Bucket: bucketName, UploadId: uploadId, MultipartUpload: { Parts: finalParts, }, Key: params.fileName, }) .promise() .then((error) => { resolve(error); }) .catch((err) => { reject(err); }); } }) .catch((err) => { reject(err); }); } }) .catch((err) => { reject(err); }); } }); } // upload 工厂中转 function uploadFactor(params) { let { bucketName, uploadType, instance, params: uploadParams } = params; switch (uploadType) { case ObjectStorageType.NORMAL: return upload$3(uploadParams); case ObjectStorageType.OBS: return upload$2(instance, uploadParams, bucketName); case ObjectStorageType.OSS: return upload$1(instance, uploadParams); case ObjectStorageType.MINIO: case ObjectStorageType.AWS: return upload(instance, uploadParams, bucketName); } } // 初始化上传类 class ObjectStorage { constructor(params) { this.instance = initFactor(params !== null && params !== void 0 ? params : {}); this.bucketName = params.bucketName; this.uploadType = params.type; } upload(params) { return uploadFactor({ uploadType: this.uploadType, instance: this.instance, bucketName: this.bucketName, params, }); } } module.exports = ObjectStorage;