CRMEB标准版社交电商演示 CRMEB 标准版
CRMEB Pro高性能私域管理电商系统演示 CRMEB Pro版
CRMEB多店连锁加盟电商管理系统 CRMEB 多店版
多商户 PHP版
多商户 Java版
CRMEB Java开源商城系统 CRMEB Java版
CRMEB 开源外贸版电商系统 CRMEB 外贸版
CRMEB知识付费系统 知识付费
陀螺匠
产品 演示网址 账号 密码

CRMEB客服

CRMEB咨询热线 咨询热线

400-8888-794

CRMEB微信扫码咨询

微信扫码咨询

微信扫码咨询

招商代理 招商代理 CRMEB开源商城下载 开源下载
返回顶部 返回顶部
CRMEB客服
CRMEB论坛
开发商城系统时,如何对大文件分片上传?
技术分享
商城系统
电商平台
2022-10-12
12952

在开发的商城系统的过程中,大家多少会遇到上传视频功能的需求,往往我们采用的都是对视频大小进行限制等方法,来防止上传请求超时,导致上传失败。这时候可能将视频分片上传可以对你的项目有一个小小的体验优化。下面就跟CRMEB来一起了解下!

本片文章前端是vue,后台基于PHP进行的分片上传,需要的小伙伴可以借鉴。

分片上传

1、什么是分片上传

分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为Part)来进行分别上传,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件。

2、分片上传的场景

1大文件上传

2网络环境环境不好,存在需要重传风险的场景

3实现流程步骤

a、方案一,常规步骤、本文实现的步骤

将需要上传的文件按照一定的分割规则,分割成相同大小的数据块;

初始化一个分片上传任务,返回本次分片上传唯一标识;

按照一定的策略(串行或并行)发送各个分片数据块;

发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件。

 

b、方案二

前端(客户端)需要根据固定大小对文件进行分片,请求后端(服务端)时要带上分片序号和大小

服务端创建conf文件用来记录分块位置,conf文件长度为总分片数,每上传一个分块即向conf文件中写入一个127,那么没上传的位置就是默认的0,已上传的就是Byte.MAX_VALUE 127(这步是实现断点续传和秒传的核心步骤)

服务器按照请求数据中给的分片序号和每片分块大小(分片大小是固定且一样的)算出开始位置,与读取到的文件片段数据,写入文件。

前端代码

template

<Upload

  :show-upload-list="false"

  :action="fileUrl2"

  :before-upload="videoSaveToUrl"  // 分片上传处理方法

  :data="uploadData"               // 上传时需要携带的参数 例如token之类的

  :headers="header"                // 请求头

  :multiple="true"

>

// 上传按钮样式</Upload>

移入方法

import { uploadByPieces } from "@/utils/upload"; //引入uploadByPieces方法

methods

// 分片上传

videoSaveToUrl(file) {

  uploadByPieces({

    file: file, // 获取到的视频文件

    pieceSize: 3, // 分片大小  这里是3M一片

    success: (data) => {

      this.formValidate.video_link = data.file_path;

      this.progress = 100;    // 上传成功 进度条为100%

    },

    error: (e) => {

      this.$Message.error(e.msg);  //报错信息

    },

    uploading: (chunk, allChunk) => {

      this.videoIng = true;   // 上传时进度条展示 根据需要添加

      let st = Math.floor((chunk / allChunk) * 100);  这里是用上传的第几片除以总片数进行百分比计算

      this.progress = st;

    },

  });

  return false;

},

utils/upload

import md5 from 'js-md5' //引入MD5加密

import { upload } from '@/api/upload.js'  // 这里指前端调用接口的api方法

export const uploadByPieces = ({ file, pieceSize = 2, success, error, uploading }) => {

    // 如果文件传入为空直接 return 返回

    if (!file) return

    let fileMD5 = ''// 总文件列表

    const chunkSize = pieceSize * 1024 * 1024 // 5MB一片

    const chunkCount = Math.ceil(file.size / chunkSize) // 总片数

    console.log(chunkSize, chunkCount)

    // 获取md5

    const readFileMD5 = () => {

        // 读取视频文件的md5

        console.log("获取文件的MD5值")

        let fileRederInstance = new FileReader()

        console.log('file', file)

        fileRederInstance.readAsBinaryString(file)

        fileRederInstance.addEventListener('load', e => {

            let fileBolb = e.target.result

            fileMD5 = md5(fileBolb)

            console.log('fileMD5', fileMD5)

            console.log("文件未被上传,将分片上传")

            readChunkMD5()

        })

    }

    const getChunkInfo = (file, currentChunk, chunkSize) => {

        let start = currentChunk * chunkSize

        let end = Math.min(file.size, start + chunkSize)

        let chunk = file.slice(start, end)

        return { start, end, chunk }

    }

    // 针对每个文件进行chunk处理

    const readChunkMD5 = async () => {

        // 针对单个文件进行chunk上传

        for (var i = 0; i < chunkCount; i++) {

            const { chunk } = getChunkInfo(file, i, chunkSize)

            console.log("总片数" + chunkCount)

            console.log("分片后的数据---测试:" + i)

            await uploadChunk({ chunk, currentChunk: i, chunkCount })

        }

    }

    const uploadChunk = (chunkInfo) => {

        // progressFun()

        return new Promise((resolver, reject) => {

            let config = {

                headers: {

                    'Content-Type': 'multipart/form-data'

                }

            }

            // 创建formData对象,下面是结合不同项目给后端传入的对象。

            let fetchForm = new FormData()

            fetchForm.append('chunkNumber', chunkInfo.currentChunk + 1)  // 第几片

            fetchForm.append('chunkSize', chunkSize)  // 分片大小的限制  例如限制 5M

            fetchForm.append('currentChunkSize', chunkInfo.chunk.size)  // 每一片的大小

            fetchForm.append('file', chunkInfo.chunk)   //每一片的文件

            fetchForm.append('filename', file.name)  // 文件名

            fetchForm.append('totalChunks', chunkInfo.chunkCount) //总片数

            fetchForm.append('md5', fileMD5)

            upload(fetchForm, config).then(res => {

                console.log("分片上传返回信息:", res)

                if (res.data.code == 1) {

                    // // 结合不同项目 将成功的信息返回出去

                    // 下面如果在项目中没有用到可以不用打开注释

                    uploading(chunkInfo.currentChunk + 1, chunkInfo.chunkCount)

                    resolver(true)

                } else if (res.data.code == 2) {

                    if (chunkInfo.currentChunk < chunkInfo.chunkCount - 1) {

                        console.log("分片上传成功")

                    } else {

                        // 当总数大于等于分片个数的时候

                        if ((chunkInfo.currentChunk + 1) == chunkInfo.chunkCount) {

                            console.log("文件开始------合并成功")

                            success(res.data)

                        }

                    }

                }

            }).catch((e) => {

                error && error(e)

            })

        })

    }

    readFileMD5() // 开始执行代码

}

后端代码

控制器

/**

     * 视频分片上传

     * @return mixed

     */

    public function videoUpload()

    {

        $data = $this->request->postMore([

            ['chunkNumber', 0],//第几分片

            ['currentChunkSize', 0],//分片大小

            ['chunkSize', 0],//总大小

            ['totalChunks', 0],//分片总数

            ['file', 'file'],//文件

            ['md5', ''],//MD5

            ['filename', ''],//文件名称

        ]);

        $res = $this->service->videoUpload($data, $_FILES['file']);

        return app('json')->success($res);

    }

方法

/**

     * 视频分片上传

     * @param $data

     * @param $file

     * @return mixed

     */

    public function videoUpload($data, $file)

    {

        $public_dir = app()->getRootPath() . 'public';

        $dir = '/uploads/attach/' . date('Y') . DIRECTORY_SEPARATOR . date('m') . DIRECTORY_SEPARATOR . date('d');

        $all_dir = $public_dir . $dir;

        if (!is_dir($all_dir)) mkdir($all_dir, 0777, true);

        $filename = $all_dir . '/' . $data['filename'] . '__' . $data['chunkNumber'];

        move_uploaded_file($file['tmp_name'], $filename);

        $res['code'] = 0;

        $res['msg'] = 'error';

        $res['file_path'] = '';

        if ($data['chunkNumber'] == $data['totalChunks']) {

            $blob = '';

            for ($i = 1; $i <= $data['totalChunks']; $i++) {

                $blob .= file_get_contents($all_dir . '/' . $data['filename'] . '__' . $i);

            }

            file_put_contents($all_dir . '/' . $data['filename'], $blob);

            for ($i = 1; $i <= $data['totalChunks']; $i++) {

                @unlink($all_dir . '/' . $data['filename'] . '__' . $i);

            }

            if (file_exists($all_dir . '/' . $data['filename'])) {

                $res['code'] = 2;

                $res['msg'] = 'success';

                $res['file_path'] = $dir . '/' . $data['filename'];

            }

        } else {

            if (file_exists($all_dir . '/' . $data['filename'] . '__' . $data['chunkNumber'])) {

                $res['code'] = 1;

                $res['msg'] = 'waiting';

                $res['file_path'] = '';

            }

        }

        return $res;

    }

 

实现分片上传的过程,需要前端和后端配合,比如前后端的上传块号的文件大小,前后端必须得要一致,否则上传就会有问题。其次文件相关操作正常都是要搭建一个文件服务器的,比如使用fastdfs、hdfs等。

本示例代码在电脑配置为4核内存8G情况下,上传24G大小的文件,上传时间需要30多分钟,主要时间耗费在前端的md5值计算,后端写入的速度还是比较快。

如果项目组觉得自建文件服务器太花费时间,且项目的需求仅仅只是上传下载,那么推荐使用阿里的oss服务器,其介绍可以查看官网:

https://help.aliyun.com/product/31815.html

阿里的oss它本质是一个对象存储服务器,而非文件服务器,因此如果有涉及到大量删除或者修改文件的需求,oss可能就不是一个好的选择。

以上就是视频分片上传的前后台的所有代码,其中有需求小伙伴可以自行加入视频上传验证,断点续传等操作。

您如果还想了解更多的商城系统功能开发知识,可以关注CRMEB官网-行业新闻。有更多实用的技术知识为您分享!


微信登录/注册

切换手机号登录

{{ bind_phone ? '绑定手机' : '手机登录'}}

{{codeText}}
切换微信登录/注册
暂不绑定
添加官方客服微信
CRMEB公众号二维码

联系客服 领取源码+接口文档🎁