uploadFile.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. import '../../common/aws-sdk-2.235.1.min.js'
  2. import { v4 as uuidv4 } from 'uuid'
  3. // 上传单个文件
  4. const uploadOneFile = (file, progressFn) => {
  5. return new Promise((resolve) => {
  6. let credentials = {
  7. accessKeyId: 'AKIATLPEDU37QV5CHLMH',
  8. secretAccessKey: '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',
  9. } //秘钥形式的登录上传
  10. window.AWS.config.update(credentials)
  11. window.AWS.config.region = 'cn-northwest-1' //设置区域
  12. let bucket = new window.AWS.S3({ params: { Bucket: 'ccrb' } }) //选择桶
  13. if (file) {
  14. var params = {
  15. Key:
  16. file.name.split('.')[0] +
  17. new Date().getTime() +
  18. '.' +
  19. file.name.split('.')[file.name.split('.').length - 1],
  20. ContentType: file.type,
  21. Body: file,
  22. 'Access-Control-Allow-Credentials': '*',
  23. ACL: 'public-read',
  24. } //key可以设置为桶的相抵路径,Body为文件, ACL最好要设置
  25. var options = {
  26. partSize: 2048 * 1024 * 1024,
  27. queueSize: 2,
  28. leavePartsOnError: true,
  29. }
  30. bucket
  31. .upload(params, options)
  32. .on('httpUploadProgress', function (evt) {
  33. //这里可以写进度条
  34. if (progressFn) {
  35. let _progress = {
  36. loaded:evt.loaded,
  37. total:evt.total,
  38. percent:(evt.loaded/evt.total*100).toFixed(2),
  39. }
  40. progressFn(_progress)
  41. }
  42. })
  43. .send(function (err, data) {
  44. if (err) {
  45. resolve('')
  46. } else {
  47. let fileObj = {
  48. name: file.name,
  49. url: data.Location,
  50. size: formatFileSize(file.size), // 格式化文件大小
  51. uid: uuidv4(),
  52. type: file.type,
  53. fileType:getFileType(file),
  54. }
  55. console.log('uploadFile', fileObj)
  56. resolve(fileObj)
  57. }
  58. })
  59. }
  60. })
  61. }
  62. // 获取文件数据
  63. const getFile = (url) => {
  64. return new Promise((resolve) => {
  65. var credentials = {
  66. accessKeyId: "AKIATLPEDU37QV5CHLMH",
  67. secretAccessKey: "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",
  68. }; //秘钥形式的登录上传
  69. window.AWS.config.update(credentials);
  70. window.AWS.config.region = "cn-northwest-1"; //设置区域
  71. let url2 = url;
  72. let _url2 = "";
  73. if (
  74. url2.indexOf("https://view.officeapps.live.com/op/view.aspx?src=") != -1
  75. ) {
  76. _url2 = url2.split(
  77. "https://view.officeapps.live.com/op/view.aspx?src="
  78. )[1];
  79. } else {
  80. _url2 = url2;
  81. }
  82. var s3 = new window.AWS.S3({ params: { Bucket: "ccrb" } });
  83. let name = decodeURIComponent(_url2.split("https://ccrb.s3.cn-northwest-1.amazonaws.com.cn/")[1])
  84. var params = {
  85. Bucket: "ccrb",
  86. Key: name,
  87. };
  88. s3.getObject(params, function (err, data) {
  89. if (err) {
  90. console.log(err, err.stack)
  91. resolve({ data: 1 });
  92. } else {
  93. resolve({ data: data.Body });
  94. console.log(data);
  95. } // sxuccessful response
  96. });
  97. });
  98. };
  99. const getTxtFileContent = (url) => {
  100. return new Promise((resolve) => {
  101. var credentials = {
  102. accessKeyId: "AKIATLPEDU37QV5CHLMH",
  103. secretAccessKey: "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"
  104. }; //秘钥形式的登录上传
  105. window.AWS.config.update(credentials);
  106. window.AWS.config.region = "cn-northwest-1"; //设置区域
  107. let url2 = url;
  108. let _url2 = "";
  109. if (
  110. url2.indexOf("https://view.officeapps.live.com/op/view.aspx?src=") != -1
  111. ) {
  112. _url2 = url2.split(
  113. "https://view.officeapps.live.com/op/view.aspx?src="
  114. )[1];
  115. } else {
  116. _url2 = url2;
  117. }
  118. var s3 = new window.AWS.S3({ params: { Bucket: "ccrb" } });
  119. let name = decodeURIComponent(
  120. _url2.split("https://ccrb.s3.cn-northwest-1.amazonaws.com.cn/")[1]
  121. );
  122. var params = {
  123. Bucket: "ccrb",
  124. Key: name
  125. };
  126. s3.getObject(params, function (err, data) {
  127. if (err) {
  128. console.log(err, err.stack);
  129. resolve({ data: 1 });
  130. } else {
  131. const fileContent = data.Body.toString("utf-8");
  132. resolve({ data: fileContent });
  133. }
  134. });
  135. });
  136. }
  137. const formatFileSize = (size) => {
  138. if (size < 1024) {
  139. return size + 'B'
  140. } else if (size < 1024 * 1024) {
  141. return (size / 1024).toFixed(2) + 'KB'
  142. } else if (size < 1024 * 1024 * 1024) {
  143. return (size / (1024 * 1024)).toFixed(2) + 'MB'
  144. } else if (size < 1024 * 1024 * 1024 * 1024) {
  145. return (size / (1024 * 1024 * 1024)).toFixed(2) + 'GB'
  146. } else {
  147. return (size / (1024 * 1024 * 1024 * 1024)).toFixed(2) + 'TB'
  148. }
  149. }
  150. // 常用文件类型清单
  151. const fileTypes = [
  152. { 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'] },
  153. { 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'] },
  154. { value: 'audio', label: '音频', exts: ['mp3', 'wav', 'aac', 'ogg', 'flac', 'm4a'], mime: ['audio/mpeg', 'audio/x-wav', 'audio/aac', 'audio/ogg', 'audio/flac', 'audio/mp4'] },
  155. { 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'] },
  156. { 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'] },
  157. { value: 'other', label: '其它', exts: [], mime: [] }
  158. ]
  159. // 根据传入的文件类型(文件名/后缀或mime)获取value
  160. const getFileType = (file) => {
  161. if (!file) return 'other';
  162. let ext = '';
  163. let mime = '';
  164. // 允许传入 string 或 File/Blob 对象
  165. if (typeof file === 'string') {
  166. // 从文件名里提取后缀
  167. const match = file.match(/\.([^.]+)$/);
  168. ext = match ? match[1].toLowerCase() : '';
  169. } else if (typeof file === 'object' && file) {
  170. // File 或 Blob 情况,取type为mime,name取后缀
  171. if (file.type) mime = file.type;
  172. if (file.name) {
  173. const match = file.name.match(/\.([^.]+)$/);
  174. ext = match ? match[1].toLowerCase() : '';
  175. }
  176. }
  177. // 先按mime类型查找
  178. if (mime) {
  179. for (let type of fileTypes) {
  180. if (type.mime.includes(mime)) {
  181. return type.value;
  182. }
  183. }
  184. }
  185. // 再按扩展名查找
  186. if (ext) {
  187. for (let type of fileTypes) {
  188. if (type.exts.includes(ext)) {
  189. return type.value;
  190. }
  191. }
  192. }
  193. // 未命中则返回other
  194. return 'other';
  195. }
  196. // 分片上传
  197. /**
  198. * AWS S3 分片上传工具,支持进度回调及停止上传功能
  199. * 使用方式:
  200. * const uploader = new AwsMultipartUploader(options);
  201. * uploader.upload({ file, keyName, folderName, onProgress, onSuccess, onError });
  202. * uploader.stop(); // 随时可停止
  203. */
  204. class AwsMultipartUploader {
  205. constructor({
  206. bucketname = "ccrb",
  207. accessKeyId = "AKIATLPEDU37QV5CHLMH",
  208. secretAccessKey = "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",
  209. region = "cn-northwest-1",
  210. partsize = 10 * 1024 * 1024 // 10MB
  211. } = {}) {
  212. this.bucketname = bucketname;
  213. this.accessKeyId = accessKeyId;
  214. this.secretAccessKey = secretAccessKey;
  215. this.region = region;
  216. this.partsize = partsize;
  217. this.bucket = null;
  218. this.flag = true;
  219. this.uploadid = "";
  220. this.lastFilestate = null; // 缓存上次的filestate, 避免重复回调
  221. }
  222. async init() {
  223. const credentials = {
  224. accessKeyId: this.accessKeyId,
  225. secretAccessKey: this.secretAccessKey,
  226. region: this.region
  227. };
  228. window.AWS.config.update(credentials);
  229. this.bucket = new window.AWS.S3({ params: { Bucket: this.bucketname } });
  230. }
  231. async initMultipartUpload(key, file) {
  232. const params = {
  233. Bucket: this.bucketname,
  234. Key: key,
  235. ContentType: file.type,
  236. ACL: "public-read"
  237. };
  238. const data = await this.bucket.createMultipartUpload(params).promise();
  239. return data.UploadId;
  240. }
  241. async uploadPart(file, keyname, uploadid, pn, start, end) {
  242. if (!this.flag) return;
  243. const params = {
  244. Bucket: this.bucketname,
  245. Key: keyname,
  246. PartNumber: pn,
  247. UploadId: uploadid,
  248. Body: file.slice(start, end)
  249. };
  250. const result = await this.bucket.uploadPart(params).promise();
  251. return { ETag: result.ETag, PartNumber: pn };
  252. }
  253. async completeMultipartUpload(parts, keyname, uploadid) {
  254. if (!this.flag) return;
  255. const params = {
  256. Bucket: this.bucketname,
  257. Key: keyname,
  258. MultipartUpload: { Parts: parts },
  259. UploadId: uploadid
  260. };
  261. return await this.bucket.completeMultipartUpload(params).promise();
  262. }
  263. async abortMultipartUpload(key, uploadid) {
  264. const params = {
  265. Bucket: this.bucketname,
  266. Key: key,
  267. UploadId: uploadid
  268. };
  269. await this.bucket.abortMultipartUpload(params).promise();
  270. }
  271. async getCheckpoint(key) {
  272. let partsinfo;
  273. let uploadid = "";
  274. try {
  275. const result = await this.bucket
  276. .listMultipartUploads({ Bucket: this.bucketname, Prefix: key })
  277. .promise();
  278. if (result.Uploads && result.Uploads.length) {
  279. uploadid = result.Uploads[result.Uploads.length - 1].UploadId;
  280. partsinfo = await this.bucket
  281. .listParts({
  282. Bucket: this.bucketname,
  283. Key: key,
  284. UploadId: uploadid
  285. })
  286. .promise();
  287. }
  288. } catch (err) {
  289. console.log(err);
  290. }
  291. return { uploadid, partsinfo };
  292. }
  293. // 控制停止
  294. stop() {
  295. this.flag = false;
  296. }
  297. emitProgress(filestate, onProgress) {
  298. // 只回调变化
  299. if (typeof onProgress === "function") {
  300. if (
  301. !this.lastFilestate ||
  302. this.lastFilestate.percent !== filestate.percent ||
  303. this.lastFilestate.status !== filestate.status
  304. ) {
  305. onProgress(Object.assign({}, filestate));
  306. }
  307. }
  308. this.lastFilestate = Object.assign({}, filestate);
  309. }
  310. // 分段上传
  311. async uploadParts({ file, uploadid, parts = [], key, onProgress }) {
  312. let filestate = { status: "processing", percent: 0 };
  313. let partarr = [];
  314. const completeparts = parts.map((_) => {
  315. partarr.push(_.PartNumber);
  316. return { PartNumber: _.PartNumber, ETag: _.ETag };
  317. });
  318. let len = Math.ceil(file.size / this.partsize);
  319. if (partarr.length) {
  320. filestate.percent = parseInt((completeparts.length * 100) / len);
  321. this.emitProgress(filestate, onProgress);
  322. }
  323. for (let i = 0; i < len; i++) {
  324. if (!this.flag) break;
  325. let start = i * this.partsize;
  326. let end = (i + 1) * this.partsize;
  327. if (!partarr.includes(i + 1)) {
  328. const uploadpart = await this.uploadPart(
  329. file,
  330. key,
  331. uploadid,
  332. i + 1,
  333. start,
  334. end
  335. );
  336. if (uploadpart && uploadpart.ETag != null) {
  337. completeparts.push(uploadpart);
  338. partarr.push(uploadpart.PartNumber);
  339. filestate.percent = parseInt((completeparts.length * 100) / len);
  340. filestate.status = "processing";
  341. this.emitProgress(filestate, onProgress);
  342. } else {
  343. filestate.status = "fail";
  344. this.emitProgress(filestate, onProgress);
  345. this.stop();
  346. throw new Error("分片上传失败");
  347. }
  348. }
  349. }
  350. if (this.flag) {
  351. const data = await this.completeMultipartUpload(
  352. completeparts,
  353. key,
  354. uploadid
  355. );
  356. filestate.status = "success";
  357. filestate.percent = 100;
  358. this.emitProgress(filestate, onProgress);
  359. return data; // 返回complete后的aws数据
  360. } else {
  361. filestate.status = "stop";
  362. this.emitProgress(filestate, onProgress);
  363. throw new Error("上传已中断");
  364. }
  365. }
  366. // 主入口(异步,支持await)
  367. /**
  368. *
  369. * @param {*} param0
  370. * file: 要上传的File对象
  371. * keyName: 指定上传key(可选)
  372. * folderName: 文件夹(可选)
  373. * onProgress: function({status, percent})
  374. */
  375. async upload({
  376. file,
  377. keyName,
  378. folderName,
  379. onProgress
  380. }) {
  381. if (!file) {
  382. throw new Error("请上传文件");
  383. }
  384. this.flag = true;
  385. this.lastFilestate = null;
  386. await this.init();
  387. let key = "";
  388. if (keyName) {
  389. key = keyName;
  390. } else if (folderName) {
  391. key = `${folderName}/${file.name}`;
  392. } else {
  393. const ext = file.name.split(".").length > 1 ?
  394. file.name.substring(file.name.lastIndexOf(".") + 1) : "";
  395. key =
  396. `${file.name.split(".")[0]}${Date.now()}${ext ? "." + ext : ""}`;
  397. }
  398. let filestate = { percent: 0, status: "start" };
  399. this.emitProgress(filestate, onProgress);
  400. const params = {
  401. Bucket: this.bucketname,
  402. Key: key
  403. };
  404. // 用Promise包装, 使upload支持await
  405. return new Promise(async (resolve, reject) => {
  406. try {
  407. // 检查是否已上传
  408. this.bucket.headObject(params, async (err, data) => {
  409. if (err) {
  410. // 检查断点
  411. let { uploadid, partsinfo } = await this.getCheckpoint(key);
  412. let curUploadId = uploadid;
  413. let awsParts = [];
  414. if (uploadid && partsinfo) {
  415. awsParts = partsinfo.Parts || [];
  416. }
  417. try {
  418. let completeResult;
  419. if (curUploadId) {
  420. completeResult = await this.uploadParts({
  421. file,
  422. uploadid: curUploadId,
  423. parts: awsParts,
  424. key,
  425. onProgress
  426. });
  427. } else {
  428. const newUploadId = await this.initMultipartUpload(key, file);
  429. completeResult = await this.uploadParts({
  430. file,
  431. uploadid: newUploadId,
  432. parts: [],
  433. key,
  434. onProgress
  435. });
  436. }
  437. let url = `https://${this.bucketname}.s3.${this.region}.amazonaws.com.cn/${key}`;
  438. const resObj = {
  439. name: file.name,
  440. url: url,
  441. size: formatFileSize(file.size),
  442. uid: uuidv4(),
  443. type: file.type,
  444. Key: key,
  445. Location: url,
  446. ETag: completeResult.ETag,
  447. Bucket: this.bucketname
  448. };
  449. console.log(resObj)
  450. resolve(resObj);
  451. } catch (err) {
  452. console.log('分片上传失败', err)
  453. reject('');
  454. }
  455. } else if (data) {
  456. // 已经100%
  457. let url = `https://${this.bucketname}.s3.${this.region}.amazonaws.com.cn/${key}`;
  458. const resObj = {
  459. name: file.name,
  460. url: url,
  461. size: formatFileSize(file.size),
  462. uid: uuidv4(),
  463. type: file.type,
  464. Key: key,
  465. Location: url,
  466. ETag: data.ETag,
  467. Bucket: this.bucketname
  468. };
  469. filestate.percent = 100;
  470. filestate.status = "success";
  471. this.emitProgress(filestate, onProgress);
  472. resolve(resObj);
  473. }
  474. });
  475. } catch (err) {
  476. console.log('分片上传失败', err)
  477. reject('');
  478. }
  479. });
  480. }
  481. }
  482. export {
  483. uploadOneFile,
  484. getFileType,
  485. getFile,
  486. getTxtFileContent,
  487. AwsMultipartUploader,
  488. formatFileSize
  489. }