赞
踩
- let uploadFile = async (file)=> {
- let onProgress = (progress) => {
- let percent = Math.round((progress.loaded / progress.total) * 100); // 文件上传进度回调
- // do something here ...
- };
- // @required file 文件 ,onProgress 进度回调 ,splitSize 分片大小 默认5MB, splitCount 一次上传分片个数 默认5
- const res = await multipartUpload(file, onProgress, 5, 5);
- },
multipartUpload 方法中,首先会简单检查一下参数,防止因参数导致程序报错。除了 file ( 文件)参数是必填 ,onProgress (上传进度回调),splitSize( 分片大小),splitCount( 一次性发送多少个分片上传请求) ,都是可选参数 。 为什么是简单检查一下参数呢,因为就目前的条件判断还是不能够避免所有出现异常的情况,比如file 参数传个null ,splitCount 传个小数......在这里参数条件判断写得太多会影响代码的可读性,抽离成一个方法出去的话,我个人觉得是可以有,但是没必要。因为按照正常人的正常逻辑来使用的话,目前的判断还是可以避免编码中的参数错误的。
检查参数之后 ,对文件进行分片,获取文件分片信息。 将分片信息发送给服务器,服务返回成功后,上传分片文件,为了避免上传文件过大,会分批次,一次性发送指定个(splitCount 默认为 5) 分片上传请求。在每一个切片上传成功之后,则回调用一次 onProgress 文件上传进度回调函数,模拟出一个文件上传进度。 等所有的分片文件都上传成功之后,请求服务器合并分片文件,服务器合并成功则返回合并成功的文件路径 。
-
- /**
- * @param {*} file 上传文件
- * @param {*} onProgress 切片上传进度
- * @param {*} splitSize 每个切片文件的大小
- * @param {*} splitCount 一次发送多少个切片请求
- * @returns
- */
-
- // 文件切片上传
- export const multipartUpload = async (
- file, // 切片原文件
- onProgress = () => {}, // 切片回调参数
- splitSize = 5, // 切片大小 默认5 MB
- splitCount = 5 // 一次性发送切片请求的个数 默认 5
- ) => {
- // 简单检查一下file 参数
- if (!file || typeof file != "object" || Array.isArray(file))
- throw new Error(" a required parameter 'file' missing .");
- // 简单判断一下 splitSize 必须是大于或等于 1 的 数字
- if (typeof splitSize != "number" || splitSize < 1)
- throw new Error(
- " the type of 'splitSize' must be 'number' and greater than 1 ."
- );
- // 简单判断一下 splitCount 必须是大于或等于 1 的 数字
- if (typeof splitCount !== "number" || splitCount < 1)
- throw new Error(
- " the type of 'splitCount' must be 'number' and greater than 1 ."
- );
- let _file = file.file || file;
- const { size, name, type, lastModified } = _file; // 获取文件大小 和名称
- let identifier = await getIdentifier(_file); // 根据文件md5 和当前时间戳生成的唯一标识符
- const totalChunks = Math.ceil(size / splitSize / 1024 / 1024); // 切片总块数 文件大小/ 切片大小向上取整
- const splitFileList = splitFile(_file, splitSize); // 返回切片数组
- let groupFileList = groupFileListByCount(splitFileList, splitCount); // 将 切片数组根据count 分组
- let splitInfo = { totalChunks, identifier, fileName: name, type }; // 文件切片信息
- // 上传分片文件件 告诉服务器准备上传的切片文件信息
- let beforeUploadFlag = await beforeUpload(splitInfo);
- if (!beforeUploadFlag) return false; // 当服务器没准备好切片上传
- // 切片上传的结果
- const uploadFlag = await uploadGroupFileList(
- groupFileList,
- splitInfo,
- onProgress
- );
- if (!uploadFlag) return false;
- // 请求服务器合并切片文件
- const mergeResult = await mergeFileFlag(identifier);
- if (!mergeResult) return false;
- // 请求服务器合并切片成功后 调一次上传进度回调接口 让上传进度100%
- onProgress({
- total: totalChunks + 1,
- loaded: totalChunks + 1,
- });
- return mergeResult;
- };

- // 根据文件MD5 + timestamp 生成的唯一标识
- export const getIdentifier = (file) => {
- return new Promise((resolve, reject) => {
- let reader = new FileReader();
- let spark = new SparkMD5();
- reader.readAsBinaryString(file);
- reader.onload = (e) => {
- spark.appendBinary(e.target.result);
- const md5 = spark.end();
- resolve(md5 + Date.now());
- };
- });
- };
- export const splitFile = (file, splitSize = 5) => {
- const _5M = 1024 * 1024 * splitSize; // 默认分割成5M
- const { size, type } = file;
- let tempFileList = []; // 盛放切割后的切片
- for (let index = 0; index < size; index += _5M) {
- let start = index; // 切片开始位置
- let end = start + _5M < size ? index + _5M : size; // 切片结束位置
- let item = file.slice(start, end, type); // 切割的文件
- tempFileList.push(item);
- }
- return tempFileList;
- };
- // 将fileList 分组
- export const groupFileListByCount = (fileList, count) => {
- let group = Math.ceil(fileList.length / count); // 将 fileList 分为多少个组
- let resultList = new Array(group); // 根据组数创建一个空的 list
- fileList.map((item, index) => {
- let i = Math.floor(index / count); // index / count 向下取整 为组数编号
- if (Array.isArray(resultList[i])) resultList[i].push(item);
- else resultList[i] = [item];
- });
- return resultList;
- };
- export const beforeUpload = async (splitInfo) => {
-
- try {
- const res = await that.$httpRequest({
- url: "/sys-file/prepareUpload",
- method: "post",
- data: splitInfo,
- isForm: true,
- });
- if (res.code == 0) return true;
- return false;
- } catch (err) {
- return false;
- }
- };
- // 上传分组的切片文件
- export const uploadGroupFileList = async (
- list = [],
- splitInfo = {},
- onProgress // 每一个切片上传成功之后 调一次上传回调 模拟一个进度条
- ) => {
- /**
- * 模拟一个进度条
- * total+1 避免当所有的切片都上传完成后进度100% 。而还没有请求服务器合并切片
- * 或者请求服务器合并分片失败 而上传进度为100% 对用户不友好
- */
- let progress = {
- total: splitInfo.totalChunks + 1,
- loaded: 0,
- };
- for (let i = 0; i < list.length; i++) {
- let itemList = Array.isArray(list[i]) ? list[i] : [];
- let itemHttpUpload = itemList.map((item, index) => {
- return new Promise(async (resolve, reject) => {
- let chunkNumber = i * list[i].length + index + 1; // 当前是所有文件切片中的第几个
- let { totalChunks, identifier, fileName } = splitInfo;
- let params = { totalChunks, chunkNumber, identifier, fileName };
- uploadFile(item, params, "/sys-file/multiUpload")
- .then((res) => {
- // 上传进度回调
- progress.loaded++;
- onProgress(progress);
- resolve(chunkNumber);
- })
- .catch((err) => {
- reject(chunkNumber);
- });
- });
- });
- // Promise.allSettled 等待上一组 upload http请求所有都完成后 返回结果
- const lastGroupUpload = await Promise.allSettled(itemHttpUpload)
- .then((res) => {
- return Promise.resolve(res);
- })
- .catch((err) => {
- return Promise.resolve(err);
- });
- // 将上传失败的分片文件存储起来
- let errorChunkList = lastGroupUpload
- .filter((item) => item.status === "rejected")
- .map((item) => item.reason);
- // 判断上一组的http 中是否有上传失败的分片 存在则不继续上传剩余的分片 返回 false
- if (errorChunkList.length > 0) return false;
- }
-
- return true;
- };

合并成功 则文件上传完成 。
- // 分片全部上传完成之后 请求服务器合并 上传的分片文件
- export const mergeFileFlag = async (identifier) => {
- try {
- const params = { identifier, convert: 0 };
- const res = await that.$httpRequest({
- url: "/sys-file/complete",
- method: "post",
- isForm: true,
- data: params,
- });
- if (res.code == 0 && res.data) {
- return res.data;
- }
- return false;
- } catch (err) {
- return false;
- }
- };

- import Vue from "vue";
- import $ from "jquery";
- import SparkMD5 from "spark-md5"; // 获取文件MD5
- import store from "@/store";
- let that = new Vue(); // that 指向一个新Vue 实例 ,用于调用 Vue prototype 的一些一些方法 例如 deepClone 、httpRequest 等
- const is_Dev = process.env.NODE_ENV == "development" ? true : false;
- let baseUrl = is_Dev
- ? window.globalConfig.DEV_BASE_API
- : window.globalConfig.PRO_BASE_API;
- // 单独封装一个分片上传的请求
- export const uploadFile = (file, config = {}, url = "/sys-file/upload") => {
- if (!file || typeof file != "object" || Array.isArray(file))
- throw new Error(" a required parameter 'file' missing . ");
- let _file = file.file || file;
- let _para = new FormData();
- _para.append("file", _file);
- // 除文件外的其他参数
- if (typeof config == "object" && !Array.isArray(config)) {
- const keyList = Object.keys(config);
- keyList.map((item) => {
- _para.append(item, config[item]);
- });
- }
- return new Promise((resolve, reject) => {
- let options = {
- url: baseUrl + url,
- method: "POST",
- data: _para,
- timeout: 60000,
- contentType: false,
- processData: false,
- headers: {
- Authorization: "Bearer " + store.state.user.access_token,
- },
- };
- $.ajax(options)
- .then((res) => {
- resolve(res);
- })
- .catch((err) => {
- if (err.statusText == "timeout") err.abort();
- reject(err);
- });
- });
- };
- // 根据splitSize 对文件进行切片
- export const splitFile = (file, splitSize = 5) => {
- const _5M = 1024 * 1024 * splitSize; // 默认分割成5M
- const { size, type } = file;
- let tempFileList = []; // 盛放切割后的切片
- for (let index = 0; index < size; index += _5M) {
- let start = index; // 切片开始位置
- let end = start + _5M < size ? index + _5M : size; // 切片结束位置
- let item = file.slice(start, end, type); // 切割的文件
- tempFileList.push(item);
- }
- return tempFileList;
- };
- // 根据文件MD5 + timestamp 生成的唯一标识
- export const getIdentifier = (file) => {
- return new Promise((resolve, reject) => {
- let reader = new FileReader();
- let spark = new SparkMD5();
- reader.readAsBinaryString(file);
- reader.onload = (e) => {
- spark.appendBinary(e.target.result);
- const md5 = spark.end();
- resolve(md5 + Date.now());
- };
- });
- };
- // 分片上传前 告诉服务器 分片文件信息
- export const beforeUpload = async (splitInfo) => {
- try {
- const res = await that.$httpRequest({
- url: "/sys-file/prepareUpload",
- method: "post",
- data: splitInfo,
- isForm: true,
- });
- if (res.code == 0) return true;
- return false;
- } catch (err) {
- return false;
- }
- };
- // 分片全部上传完成之后 请求服务器合并 上传的分片文件
- export const mergeFileFlag = async (identifier) => {
- try {
- const params = { identifier, convert: 0 };
- const res = await that.$httpRequest({
- url: "/sys-file/complete",
- method: "post",
- isForm: true,
- data: params,
- });
- if (res.code == 0 && res.data) {
- return res.data;
- }
- return false;
- } catch (err) {
- return false;
- }
- };
-
- // 将fileList 分组
- export const groupFileListByCount = (fileList, count) => {
- let group = Math.ceil(fileList.length / count); // 将 fileList 分为多少个组
- let resultList = new Array(group); // 根据组数创建一个空的 list
- fileList.map((item, index) => {
- let i = Math.floor(index / count); // index / count 向下取整 为组数编号
- if (Array.isArray(resultList[i])) resultList[i].push(item);
- else resultList[i] = [item];
- });
- return resultList;
- };
- // 上传分组的切片文件
- export const uploadGroupFileList = async (
- list = [],
- splitInfo = {},
- onProgress // 每一个切片上传成功之后 调一次上传回调 模拟一个进度条
- ) => {
- /**
- * 模拟一个进度条
- * total+1 避免当所有的切片都上传完成后进度100% 。而还没有请求服务器合并切片
- * 或者请求服务器合并分片失败 而上传进度为100% 对用户不友好
- */
- let progress = {
- total: splitInfo.totalChunks + 1,
- loaded: 0,
- };
- for (let i = 0; i < list.length; i++) {
- let itemList = Array.isArray(list[i]) ? list[i] : [];
- let itemHttpUpload = itemList.map((item, index) => {
- return new Promise(async (resolve, reject) => {
- let chunkNumber = i * list[i].length + index + 1; // 当前是所有文件切片中的第几个
- let { totalChunks, identifier, fileName } = splitInfo;
- let params = { totalChunks, chunkNumber, identifier, fileName };
- uploadFile(item, params, "/sys-file/multiUpload")
- .then((res) => {
- // 上传进度回调
- progress.loaded++;
- onProgress(progress);
- resolve(chunkNumber);
- })
- .catch((err) => {
- reject(chunkNumber);
- });
- });
- });
- // Promise.allSettled 等待上一组 upload http请求所有都完成后 返回结果
- const lastGroupUpload = await Promise.allSettled(itemHttpUpload)
- .then((res) => {
- return Promise.resolve(res);
- })
- .catch((err) => {
- return Promise.resolve(err);
- });
- // 将上传失败的分片文件存储起来
- let errorChunkList = lastGroupUpload
- .filter((item) => item.status === "rejected")
- .map((item) => item.reason);
- // 判断上一组的http 中是否有上传失败的分片 存在则不继续上传剩余的分片 返回 false
- if (errorChunkList.length > 0) return false;
- }
-
- return true;
- };
-
- /**
- * @param {*} file 上传文件
- * @param {*} onProgress 切片上传进度
- * @param {*} splitSize 每个切片文件的大小
- * @param {*} splitCount 一次发送多少个切片请求
- * @returns
- */
-
- // 文件切片上传
- export const multipartUpload = async (
- file, // 切片原文件
- onProgress = () => {}, // 切片回调参数
- splitSize = 5, // 切片大小 默认5 MB
- splitCount = 5 // 一次性发送切片请求的个数 默认 5
- ) => {
- // 简单检查一下file 参数
- if (!file || typeof file != "object" || Array.isArray(file))
- throw new Error(" a required parameter 'file' missing .");
- // 简单判断一下 splitSize 必须是大于或等于 1 的 数字
- if (typeof splitSize != "number" || splitSize < 1)
- throw new Error(
- " the type of 'splitSize' must be 'number' and greater than 1 ."
- );
- // 简单判断一下 splitCount 必须是大于或等于 1 的 数字
- if (typeof splitCount !== "number" || splitCount < 1)
- throw new Error(
- " the type of 'splitCount' must be 'number' and greater than 1 ."
- );
- let _file = file.file || file;
- const { size, name, type, lastModified } = _file; // 获取文件大小 和名称
- let identifier = await getIdentifier(_file); // 根据文件md5 和当前时间戳生成的唯一标识符
- const totalChunks = Math.ceil(size / splitSize / 1024 / 1024); // 切片总块数 文件大小/ 切片大小向上取整
- const splitFileList = splitFile(_file, splitSize); // 返回切片数组
- let groupFileList = groupFileListByCount(splitFileList, splitCount); // 将 切片数组根据count 分组
- let splitInfo = { totalChunks, identifier, fileName: name, type }; // 文件切片信息
- // 上传分片文件件 告诉服务器准备上传的切片文件信息
- let beforeUploadFlag = await beforeUpload(splitInfo);
- if (!beforeUploadFlag) return false; // 当服务器没准备好切片上传
- // 切片上传的结果
- const uploadFlag = await uploadGroupFileList(
- groupFileList,
- splitInfo,
- onProgress
- );
- if (!uploadFlag) return false;
- // 请求服务器合并切片文件
- const mergeResult = await mergeFileFlag(identifier);
- if (!mergeResult) return false;
- // 请求服务器合并切片成功后 调一次上传进度回调接口 让上传进度100%
- onProgress({
- total: totalChunks + 1,
- loaded: totalChunks + 1,
- });
- return mergeResult;
- };

将分片文件根据splitCount 分组,一次只发送一组请求,等待上一组请求全部完成,再发送下一组请求。1是因为如果文件过大的话,同时发送过多的请求,服务器可能会处理不了,服务异常导致上传文件失败。2是因为同时发送的请求,最后一个请求可能会等待很久才返回,可能会出现请求超时,导致上传文件失败。3.如果一个文件上传完,再上传后一个文件的话,不能够减少上传文件的时间,一次性,传多个请求可以减少上传时间 。 4.可以根据不同服务器的带宽能力,调试一次性上传多少个文件合适。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。