|
|
@@ -0,0 +1,519 @@
|
|
|
+import '../../common/aws-sdk-2.235.1.min.js'
|
|
|
+import { v4 as uuidv4 } from 'uuid'
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+// 上传单个文件
|
|
|
+const uploadOneFile = (file, progressFn) => {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ let credentials = {
|
|
|
+ accessKeyId: 'AKIATLPEDU37QV5CHLMH',
|
|
|
+ secretAccessKey: 'Q2SQw37HfolS7yeaR1Ndpy9Jl4E2YZKUuuy2muZR',
|
|
|
+ } //秘钥形式的登录上传
|
|
|
+ window.AWS.config.update(credentials)
|
|
|
+ window.AWS.config.region = 'cn-northwest-1' //设置区域
|
|
|
+
|
|
|
+ let bucket = new window.AWS.S3({ params: { Bucket: 'ccrb' } }) //选择桶
|
|
|
+ if (file) {
|
|
|
+ var params = {
|
|
|
+ Key:
|
|
|
+ file.name.split('.')[0] +
|
|
|
+ new Date().getTime() +
|
|
|
+ '.' +
|
|
|
+ file.name.split('.')[file.name.split('.').length - 1],
|
|
|
+ ContentType: file.type,
|
|
|
+ Body: file,
|
|
|
+ 'Access-Control-Allow-Credentials': '*',
|
|
|
+ ACL: 'public-read',
|
|
|
+ } //key可以设置为桶的相抵路径,Body为文件, ACL最好要设置
|
|
|
+ var options = {
|
|
|
+ partSize: 2048 * 1024 * 1024,
|
|
|
+ queueSize: 2,
|
|
|
+ leavePartsOnError: true,
|
|
|
+ }
|
|
|
+ bucket
|
|
|
+ .upload(params, options)
|
|
|
+ .on('httpUploadProgress', function (evt) {
|
|
|
+ //这里可以写进度条
|
|
|
+ if (progressFn) {
|
|
|
+ let _progress = {
|
|
|
+ loaded:evt.loaded,
|
|
|
+ total:evt.total,
|
|
|
+ percent:(evt.loaded/evt.total*100).toFixed(2),
|
|
|
+ }
|
|
|
+ progressFn(_progress)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .send(function (err, data) {
|
|
|
+ if (err) {
|
|
|
+ resolve('')
|
|
|
+ } else {
|
|
|
+ let fileObj = {
|
|
|
+ name: file.name,
|
|
|
+ url: data.Location,
|
|
|
+ size: formatFileSize(file.size), // 格式化文件大小
|
|
|
+ uid: uuidv4(),
|
|
|
+ type: file.type,
|
|
|
+ fileType:getFileType(file),
|
|
|
+ }
|
|
|
+ console.log('uploadFile', fileObj)
|
|
|
+ resolve(fileObj)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 获取文件数据
|
|
|
+const getFile = (url) => {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ var credentials = {
|
|
|
+ accessKeyId: "AKIATLPEDU37QV5CHLMH",
|
|
|
+ secretAccessKey: "Q2SQw37HfolS7yeaR1Ndpy9Jl4E2YZKUuuy2muZR",
|
|
|
+ }; //秘钥形式的登录上传
|
|
|
+ window.AWS.config.update(credentials);
|
|
|
+ window.AWS.config.region = "cn-northwest-1"; //设置区域
|
|
|
+ let url2 = url;
|
|
|
+ let _url2 = "";
|
|
|
+ if (
|
|
|
+ url2.indexOf("https://view.officeapps.live.com/op/view.aspx?src=") != -1
|
|
|
+ ) {
|
|
|
+ _url2 = url2.split(
|
|
|
+ "https://view.officeapps.live.com/op/view.aspx?src="
|
|
|
+ )[1];
|
|
|
+ } else {
|
|
|
+ _url2 = url2;
|
|
|
+ }
|
|
|
+ var s3 = new window.AWS.S3({ params: { Bucket: "ccrb" } });
|
|
|
+ let name = decodeURIComponent(_url2.split("https://ccrb.s3.cn-northwest-1.amazonaws.com.cn/")[1])
|
|
|
+ var params = {
|
|
|
+ Bucket: "ccrb",
|
|
|
+ Key: name,
|
|
|
+ };
|
|
|
+ s3.getObject(params, function (err, data) {
|
|
|
+ if (err) {
|
|
|
+ console.log(err, err.stack)
|
|
|
+ resolve({ data: 1 });
|
|
|
+ } else {
|
|
|
+ resolve({ data: data.Body });
|
|
|
+ console.log(data);
|
|
|
+ } // sxuccessful response
|
|
|
+
|
|
|
+ });
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const getTxtFileContent = (url) => {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ var credentials = {
|
|
|
+ accessKeyId: "AKIATLPEDU37QV5CHLMH",
|
|
|
+ secretAccessKey: "Q2SQw37HfolS7yeaR1Ndpy9Jl4E2YZKUuuy2muZR"
|
|
|
+ }; //秘钥形式的登录上传
|
|
|
+ window.AWS.config.update(credentials);
|
|
|
+ window.AWS.config.region = "cn-northwest-1"; //设置区域
|
|
|
+ let url2 = url;
|
|
|
+ let _url2 = "";
|
|
|
+ if (
|
|
|
+ url2.indexOf("https://view.officeapps.live.com/op/view.aspx?src=") != -1
|
|
|
+ ) {
|
|
|
+ _url2 = url2.split(
|
|
|
+ "https://view.officeapps.live.com/op/view.aspx?src="
|
|
|
+ )[1];
|
|
|
+ } else {
|
|
|
+ _url2 = url2;
|
|
|
+ }
|
|
|
+ var s3 = new window.AWS.S3({ params: { Bucket: "ccrb" } });
|
|
|
+ let name = decodeURIComponent(
|
|
|
+ _url2.split("https://ccrb.s3.cn-northwest-1.amazonaws.com.cn/")[1]
|
|
|
+ );
|
|
|
+ var params = {
|
|
|
+ Bucket: "ccrb",
|
|
|
+ Key: name
|
|
|
+ };
|
|
|
+ s3.getObject(params, function (err, data) {
|
|
|
+ if (err) {
|
|
|
+ console.log(err, err.stack);
|
|
|
+ resolve({ data: 1 });
|
|
|
+ } else {
|
|
|
+ const fileContent = data.Body.toString("utf-8");
|
|
|
+ resolve({ data: fileContent });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+const formatFileSize = (size) => {
|
|
|
+ if (size < 1024) {
|
|
|
+ return size + 'B'
|
|
|
+ } else if (size < 1024 * 1024) {
|
|
|
+ return (size / 1024).toFixed(2) + 'KB'
|
|
|
+ } else if (size < 1024 * 1024 * 1024) {
|
|
|
+ return (size / (1024 * 1024)).toFixed(2) + 'MB'
|
|
|
+ } else if (size < 1024 * 1024 * 1024 * 1024) {
|
|
|
+ return (size / (1024 * 1024 * 1024)).toFixed(2) + 'GB'
|
|
|
+ } else {
|
|
|
+ return (size / (1024 * 1024 * 1024 * 1024)).toFixed(2) + 'TB'
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 常用文件类型清单
|
|
|
+const fileTypes = [
|
|
|
+ { value: 'image', label: '图片', exts: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'], mime: ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp', 'image/svg+xml'] },
|
|
|
+ { value: 'video', label: '视频', exts: ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm'], mime: ['video/mp4', 'video/x-msvideo', 'video/quicktime', 'video/x-ms-wmv', 'video/x-flv', 'video/x-matroska', 'video/webm'] },
|
|
|
+ { value: 'audio', label: '音频', exts: ['mp3', 'wav', 'aac', 'ogg', 'flac', 'm4a'], mime: ['audio/mpeg', 'audio/x-wav', 'audio/aac', 'audio/ogg', 'audio/flac', 'audio/mp4'] },
|
|
|
+ { value: 'doc', label: '文档', exts: ['doc', 'docx', 'pdf', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'md', 'csv'], mime: ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/pdf', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'text/plain', 'text/markdown', 'text/csv'] },
|
|
|
+ { value: 'zip', label: '压缩包', exts: ['zip', 'rar', '7z', 'tar', 'gz'], mime: ['application/zip', 'application/x-rar-compressed', 'application/x-7z-compressed', 'application/x-tar', 'application/gzip'] },
|
|
|
+ { value: 'other', label: '其它', exts: [], mime: [] }
|
|
|
+]
|
|
|
+
|
|
|
+// 根据传入的文件类型(文件名/后缀或mime)获取value
|
|
|
+const getFileType = (file) => {
|
|
|
+ if (!file) return 'other';
|
|
|
+ let ext = '';
|
|
|
+ let mime = '';
|
|
|
+
|
|
|
+ // 允许传入 string 或 File/Blob 对象
|
|
|
+ if (typeof file === 'string') {
|
|
|
+ // 从文件名里提取后缀
|
|
|
+ const match = file.match(/\.([^.]+)$/);
|
|
|
+ ext = match ? match[1].toLowerCase() : '';
|
|
|
+ } else if (typeof file === 'object' && file) {
|
|
|
+ // File 或 Blob 情况,取type为mime,name取后缀
|
|
|
+ if (file.type) mime = file.type;
|
|
|
+ if (file.name) {
|
|
|
+ const match = file.name.match(/\.([^.]+)$/);
|
|
|
+ ext = match ? match[1].toLowerCase() : '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 先按mime类型查找
|
|
|
+ if (mime) {
|
|
|
+ for (let type of fileTypes) {
|
|
|
+ if (type.mime.includes(mime)) {
|
|
|
+ return type.value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 再按扩展名查找
|
|
|
+ if (ext) {
|
|
|
+ for (let type of fileTypes) {
|
|
|
+ if (type.exts.includes(ext)) {
|
|
|
+ return type.value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 未命中则返回other
|
|
|
+ return 'other';
|
|
|
+}
|
|
|
+
|
|
|
+// 分片上传
|
|
|
+/**
|
|
|
+ * AWS S3 分片上传工具,支持进度回调及停止上传功能
|
|
|
+ * 使用方式:
|
|
|
+ * const uploader = new AwsMultipartUploader(options);
|
|
|
+ * uploader.upload({ file, keyName, folderName, onProgress, onSuccess, onError });
|
|
|
+ * uploader.stop(); // 随时可停止
|
|
|
+ */
|
|
|
+class AwsMultipartUploader {
|
|
|
+ constructor({
|
|
|
+ bucketname = "ccrb",
|
|
|
+ accessKeyId = "AKIATLPEDU37QV5CHLMH",
|
|
|
+ secretAccessKey = "Q2SQw37HfolS7yeaR1Ndpy9Jl4E2YZKUuuy2muZR",
|
|
|
+ region = "cn-northwest-1",
|
|
|
+ partsize = 10 * 1024 * 1024 // 10MB
|
|
|
+ } = {}) {
|
|
|
+ this.bucketname = bucketname;
|
|
|
+ this.accessKeyId = accessKeyId;
|
|
|
+ this.secretAccessKey = secretAccessKey;
|
|
|
+ this.region = region;
|
|
|
+ this.partsize = partsize;
|
|
|
+ this.bucket = null;
|
|
|
+ this.flag = true;
|
|
|
+ this.uploadid = "";
|
|
|
+ this.lastFilestate = null; // 缓存上次的filestate, 避免重复回调
|
|
|
+ }
|
|
|
+
|
|
|
+ async init() {
|
|
|
+ const credentials = {
|
|
|
+ accessKeyId: this.accessKeyId,
|
|
|
+ secretAccessKey: this.secretAccessKey,
|
|
|
+ region: this.region
|
|
|
+ };
|
|
|
+ window.AWS.config.update(credentials);
|
|
|
+ this.bucket = new window.AWS.S3({ params: { Bucket: this.bucketname } });
|
|
|
+ }
|
|
|
+
|
|
|
+ async initMultipartUpload(key, file) {
|
|
|
+ const params = {
|
|
|
+ Bucket: this.bucketname,
|
|
|
+ Key: key,
|
|
|
+ ContentType: file.type,
|
|
|
+ ACL: "public-read"
|
|
|
+ };
|
|
|
+ const data = await this.bucket.createMultipartUpload(params).promise();
|
|
|
+ return data.UploadId;
|
|
|
+ }
|
|
|
+
|
|
|
+ async uploadPart(file, keyname, uploadid, pn, start, end) {
|
|
|
+ if (!this.flag) return;
|
|
|
+ const params = {
|
|
|
+ Bucket: this.bucketname,
|
|
|
+ Key: keyname,
|
|
|
+ PartNumber: pn,
|
|
|
+ UploadId: uploadid,
|
|
|
+ Body: file.slice(start, end)
|
|
|
+ };
|
|
|
+ const result = await this.bucket.uploadPart(params).promise();
|
|
|
+ return { ETag: result.ETag, PartNumber: pn };
|
|
|
+ }
|
|
|
+
|
|
|
+ async completeMultipartUpload(parts, keyname, uploadid) {
|
|
|
+ if (!this.flag) return;
|
|
|
+ const params = {
|
|
|
+ Bucket: this.bucketname,
|
|
|
+ Key: keyname,
|
|
|
+ MultipartUpload: { Parts: parts },
|
|
|
+ UploadId: uploadid
|
|
|
+ };
|
|
|
+ return await this.bucket.completeMultipartUpload(params).promise();
|
|
|
+ }
|
|
|
+
|
|
|
+ async abortMultipartUpload(key, uploadid) {
|
|
|
+ const params = {
|
|
|
+ Bucket: this.bucketname,
|
|
|
+ Key: key,
|
|
|
+ UploadId: uploadid
|
|
|
+ };
|
|
|
+ await this.bucket.abortMultipartUpload(params).promise();
|
|
|
+ }
|
|
|
+
|
|
|
+ async getCheckpoint(key) {
|
|
|
+ let partsinfo;
|
|
|
+ let uploadid = "";
|
|
|
+ try {
|
|
|
+ const result = await this.bucket
|
|
|
+ .listMultipartUploads({ Bucket: this.bucketname, Prefix: key })
|
|
|
+ .promise();
|
|
|
+ if (result.Uploads && result.Uploads.length) {
|
|
|
+ uploadid = result.Uploads[result.Uploads.length - 1].UploadId;
|
|
|
+ partsinfo = await this.bucket
|
|
|
+ .listParts({
|
|
|
+ Bucket: this.bucketname,
|
|
|
+ Key: key,
|
|
|
+ UploadId: uploadid
|
|
|
+ })
|
|
|
+ .promise();
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.log(err);
|
|
|
+ }
|
|
|
+ return { uploadid, partsinfo };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 控制停止
|
|
|
+ stop() {
|
|
|
+ this.flag = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ emitProgress(filestate, onProgress) {
|
|
|
+ // 只回调变化
|
|
|
+ if (typeof onProgress === "function") {
|
|
|
+ if (
|
|
|
+ !this.lastFilestate ||
|
|
|
+ this.lastFilestate.percent !== filestate.percent ||
|
|
|
+ this.lastFilestate.status !== filestate.status
|
|
|
+ ) {
|
|
|
+ onProgress(Object.assign({}, filestate));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.lastFilestate = Object.assign({}, filestate);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 分段上传
|
|
|
+ async uploadParts({ file, uploadid, parts = [], key, onProgress }) {
|
|
|
+ let filestate = { status: "processing", percent: 0 };
|
|
|
+ let partarr = [];
|
|
|
+ const completeparts = parts.map((_) => {
|
|
|
+ partarr.push(_.PartNumber);
|
|
|
+ return { PartNumber: _.PartNumber, ETag: _.ETag };
|
|
|
+ });
|
|
|
+ let len = Math.ceil(file.size / this.partsize);
|
|
|
+
|
|
|
+ if (partarr.length) {
|
|
|
+ filestate.percent = parseInt((completeparts.length * 100) / len);
|
|
|
+ this.emitProgress(filestate, onProgress);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (let i = 0; i < len; i++) {
|
|
|
+ if (!this.flag) break;
|
|
|
+ let start = i * this.partsize;
|
|
|
+ let end = (i + 1) * this.partsize;
|
|
|
+ if (!partarr.includes(i + 1)) {
|
|
|
+ const uploadpart = await this.uploadPart(
|
|
|
+ file,
|
|
|
+ key,
|
|
|
+ uploadid,
|
|
|
+ i + 1,
|
|
|
+ start,
|
|
|
+ end
|
|
|
+ );
|
|
|
+ if (uploadpart && uploadpart.ETag != null) {
|
|
|
+ completeparts.push(uploadpart);
|
|
|
+ partarr.push(uploadpart.PartNumber);
|
|
|
+ filestate.percent = parseInt((completeparts.length * 100) / len);
|
|
|
+ filestate.status = "processing";
|
|
|
+ this.emitProgress(filestate, onProgress);
|
|
|
+ } else {
|
|
|
+ filestate.status = "fail";
|
|
|
+ this.emitProgress(filestate, onProgress);
|
|
|
+ this.stop();
|
|
|
+ throw new Error("分片上传失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (this.flag) {
|
|
|
+ const data = await this.completeMultipartUpload(
|
|
|
+ completeparts,
|
|
|
+ key,
|
|
|
+ uploadid
|
|
|
+ );
|
|
|
+ filestate.status = "success";
|
|
|
+ filestate.percent = 100;
|
|
|
+ this.emitProgress(filestate, onProgress);
|
|
|
+ return data; // 返回complete后的aws数据
|
|
|
+ } else {
|
|
|
+ filestate.status = "stop";
|
|
|
+ this.emitProgress(filestate, onProgress);
|
|
|
+ throw new Error("上传已中断");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 主入口(异步,支持await)
|
|
|
+ /**
|
|
|
+ *
|
|
|
+ * @param {*} param0
|
|
|
+ * file: 要上传的File对象
|
|
|
+ * keyName: 指定上传key(可选)
|
|
|
+ * folderName: 文件夹(可选)
|
|
|
+ * onProgress: function({status, percent})
|
|
|
+ */
|
|
|
+ async upload({
|
|
|
+ file,
|
|
|
+ keyName,
|
|
|
+ folderName,
|
|
|
+ onProgress
|
|
|
+ }) {
|
|
|
+ if (!file) {
|
|
|
+ throw new Error("请上传文件");
|
|
|
+ }
|
|
|
+ this.flag = true;
|
|
|
+ this.lastFilestate = null;
|
|
|
+ await this.init();
|
|
|
+ let key = "";
|
|
|
+ if (keyName) {
|
|
|
+ key = keyName;
|
|
|
+ } else if (folderName) {
|
|
|
+ key = `${folderName}/${file.name}`;
|
|
|
+ } else {
|
|
|
+ const ext = file.name.split(".").length > 1 ?
|
|
|
+ file.name.substring(file.name.lastIndexOf(".") + 1) : "";
|
|
|
+ key =
|
|
|
+ `${file.name.split(".")[0]}${Date.now()}${ext ? "." + ext : ""}`;
|
|
|
+ }
|
|
|
+ let filestate = { percent: 0, status: "start" };
|
|
|
+ this.emitProgress(filestate, onProgress);
|
|
|
+
|
|
|
+ const params = {
|
|
|
+ Bucket: this.bucketname,
|
|
|
+ Key: key
|
|
|
+ };
|
|
|
+
|
|
|
+ // 用Promise包装, 使upload支持await
|
|
|
+ return new Promise(async (resolve, reject) => {
|
|
|
+ try {
|
|
|
+ // 检查是否已上传
|
|
|
+ this.bucket.headObject(params, async (err, data) => {
|
|
|
+ if (err) {
|
|
|
+ // 检查断点
|
|
|
+ let { uploadid, partsinfo } = await this.getCheckpoint(key);
|
|
|
+ let curUploadId = uploadid;
|
|
|
+ let awsParts = [];
|
|
|
+ if (uploadid && partsinfo) {
|
|
|
+ awsParts = partsinfo.Parts || [];
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ let completeResult;
|
|
|
+ if (curUploadId) {
|
|
|
+ completeResult = await this.uploadParts({
|
|
|
+ file,
|
|
|
+ uploadid: curUploadId,
|
|
|
+ parts: awsParts,
|
|
|
+ key,
|
|
|
+ onProgress
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ const newUploadId = await this.initMultipartUpload(key, file);
|
|
|
+ completeResult = await this.uploadParts({
|
|
|
+ file,
|
|
|
+ uploadid: newUploadId,
|
|
|
+ parts: [],
|
|
|
+ key,
|
|
|
+ onProgress
|
|
|
+ });
|
|
|
+ }
|
|
|
+ let url = `https://${this.bucketname}.s3.${this.region}.amazonaws.com.cn/${key}`;
|
|
|
+ const resObj = {
|
|
|
+ name: file.name,
|
|
|
+ url: url,
|
|
|
+ size: formatFileSize(file.size),
|
|
|
+ uid: uuidv4(),
|
|
|
+ type: file.type,
|
|
|
+ Key: key,
|
|
|
+ Location: url,
|
|
|
+ ETag: completeResult.ETag,
|
|
|
+ Bucket: this.bucketname
|
|
|
+ };
|
|
|
+ console.log(resObj)
|
|
|
+ resolve(resObj);
|
|
|
+ } catch (err) {
|
|
|
+
|
|
|
+ console.log('分片上传失败', err)
|
|
|
+ reject('');
|
|
|
+ }
|
|
|
+ } else if (data) {
|
|
|
+ // 已经100%
|
|
|
+ let url = `https://${this.bucketname}.s3.${this.region}.amazonaws.com.cn/${key}`;
|
|
|
+ const resObj = {
|
|
|
+ name: file.name,
|
|
|
+ url: url,
|
|
|
+ size: formatFileSize(file.size),
|
|
|
+ uid: uuidv4(),
|
|
|
+ type: file.type,
|
|
|
+ Key: key,
|
|
|
+ Location: url,
|
|
|
+ ETag: data.ETag,
|
|
|
+ Bucket: this.bucketname
|
|
|
+ };
|
|
|
+ filestate.percent = 100;
|
|
|
+ filestate.status = "success";
|
|
|
+ this.emitProgress(filestate, onProgress);
|
|
|
+ resolve(resObj);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } catch (err) {
|
|
|
+ console.log('分片上传失败', err)
|
|
|
+ reject('');
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export {
|
|
|
+ uploadOneFile,
|
|
|
+ getFileType,
|
|
|
+ getFile,
|
|
|
+ getTxtFileContent,
|
|
|
+ AwsMultipartUploader,
|
|
|
+ formatFileSize
|
|
|
+}
|