uniapp 微信小程序实现大文件分片上传

  • 需求:用uniapp开发的微信小程序实现大文件上传
  • 方案:使用文件切割工具分片上传文件
  • 实现:既然需求和方案已经明确了,那么,动手淦吧~
  • 总结:说说大文件上传的难点
  • 断点续传:简要的说明一下
  • 完整代码:↓

需求:用uniapp开发的微信小程序实现大文件上传

公司目前有个需求,就是老师上课的录像需要通过手机端小程序上传到服务器,而手机拍摄的视频一般会很大,虽然微信会自动压缩视频,但是难免的,视频依然会很大~~
微信自带的文件上传工具,虽然能上传大文件,但是。。。难免可能会出现网络波动等问题,导致文件上传失败,而且服务端也做了限制,单个文件不能超过20M,那么~问题来了,录播课程一节课一般都在200-300m左右,如何上传呢??

方案:使用文件切割工具分片上传文件

此时就需要用到大文件切片上传工具啦。我实现的思路很简单:

  1. 文件上传之前的握手:先读取文件信息,例如文件名称、文件大小、文件MD5(用于检验上传完成后的文件完整性,以及作为当前上传的任务key)、文件分片大小、文件总片数等~;
  2. 文件切割:按指定大小将文件切割成独立的文件片,例如2m每片。
  3. 文件合并:将无数个文件片合并成一个完整的文件,然后根据握手时的MD5值校验文件的完整性。
  4. 文件保存:将上传后的文件信息保存到数据库,然后返回文件的保存信息,比如文件路径、文件大小、文件MD5等~
  5. 上传成功:将上传信息返回给前端。

实现:既然需求和方案已经明确了,那么,动手淦吧~

  1. 第一步,先实现视频文件的选中,然后读取文件的信息:
    选择视频文件我是使用的chooseVideo,关于chooseVideo具体的用法,可以参考uniapp的官方文档:点这里传送:chooseVideo
    chooseVideo() {uni.chooseVideo({success: res => {const uploadFile = new BigUpload({url: `这是一个文件上传的路径`,filePath: res.tempFilePath,type: 'video/mp4',byteLength: res.size,size: 2097152,fileName: 'weixin_video.mp4',drowSpeed: (p) => {this.percent = p},callback: (state) => {if (state) {this.percent = 100this.uploadStatus = '上传完成'this.videoMd5 = state.md5}}})uploadFile.startUpload()}})}

2.文件选择成功后,读取文件基础信息,组装握手信息:
在chooseVideo选中文件后,tempFilePath就是文件的临时路径,res.size就是文件的大小总长度,剩余的参数就需要我们自行配置,例如type、size(分片大小)、fileName(文件名称,由于这个chooseVideo不能读取文件名,所以这里就自定义一个)等,配置如下:

    url: `这是一个文件上传的路径`,filePath: res.tempFilePath,type: 'video/mp4',byteLength: res.size,size: 2097152,fileName: 'weixin_video.mp4'

然后获取组装信息:

    startUpload() {this.chunkSize = this.Setting.sizeif (!this.Setting.filePath) {return}this.pt_md5 = ''this.chunks = Math.ceil(this.Setting.byteLength / this.chunkSize)this.currentChunk = 0}

上传握手信息:

 handshake(cbk, e) {let formData = {}let md5 = this.getDataMd5(e)this.pt_md5 = md5formData.pt_md5 = this.pt_md5formData.chunks = this.chunksformData.size = this.Setting.byteLengthformData.type = 'handshake'formData.md5 = md5formData.fileName = this.Setting.fileNameformData.contentType = this.Setting.typepostConsole({url: this.Setting.url,data: formData}).then(res => {if (res === 'success') {cbk(true)} else if (typeof res !== 'number') {this.Setting.callback(res)} else {this.currentChunk = resif (this.currentChunk < this.chunks) {this.loadNext()} else {this.currentChunk--this.loadNext()}}}).catch(err => {console.error(err)cbk(false)})}

3.文件切割上传(最核心的来了):
a.先计算当前上传块的起始位置,以及计算上传进度:

    loadNext() {const p = this.currentChunk * 100 / this.chunksthis.drowSpeed(parseInt(p));let start = this.currentChunk * this.chunkSizelet length = start + this.chunkSize >= this.Setting.byteLength ? this.Setting.byteLength - start : this.chunkSizeif (this.gowith) {this.fileSlice(start, length, file => {this.uploadFileBinary(file)})}}

b.切片:

    fileSlice(start, length, cbk) {uni.getFileSystemManager().readFile({filePath: this.Setting.filePath,encoding: 'binary',position: start,length: length,success: res => {cbk(res.data)},fail: err => {console.error(err)this.callback(false)}})}

c.上传,上传的逻辑是先根据切出来的文件块创建一个临时文件,然后上传这个临时文件,上传成功后就删除这个临时文件${wx.env.USER_DATA_PATH} 这里是用户数据目录,在uniapp中也必须这么写,不然无法识别路径:

 uploadFileBinary(data) {//获取文件系统句柄const fs = uni.getFileSystemManager()//计算数据md5const md5 = this.getDataMd5(data)//创建临时文件const tempPath = `${wx.env.USER_DATA_PATH}/up_temp/${md5}.temp`//授权创建fs.access({path: `${wx.env.USER_DATA_PATH}/up_temp`,fail(res) {fs.mkdirSync(`${wx.env.USER_DATA_PATH}/up_temp`, false)}})//写入文件系统fs.writeFile({filePath: tempPath,encoding: 'binary',data: data,success: res => {let formData = {}formData.currentChunk = this.currentChunk + 1formData.pt_md5 = this.pt_md5formData.type = 'file'formData.md5 = md5//上传文件片uni.uploadFile({url: this.Setting.url,filePath: tempPath,name: 'file',formData: formData,success: res2 => {fs.unlinkSync(tempPath)if (res2.statusCode === 200) {const data = JSON.parse(res2.data)if (data.code === '0') {this.currentChunk++//判断是否所有篇都上传了if (this.currentChunk < this.chunks) {//继续上传下一片this.loadNext()} else {this.callback(data.data)}return true} }//上传错误this.callback(false)},fail: err => {console.log(err)this.callback(false)}})},fail: err => {console.log(err)this.callback(false)}})}

4.文件合并:文件合并的操作主要在后端实现,实现逻辑也很简单,就是按照顺序将所有的文件块拼接起来就可以了。
5.上传成功:回显文件上传信息,比如路径、MD5等信息;

          const uploadFile = new BigUpload({url: `一个路径`,filePath: res.tempFilePath,type: 'video/mp4',byteLength: res.size,size: 2097152,fileName: 'weixin_video.mp4',drowSpeed: (p) => {this.percent = p},callback: (state) => {if (state) {this.percent = 100this.uploadStatus = '上传完成'this.videoMd5 = state.md5}}})

当callback失败时,返回false,当上传成功时,返回文件的信息。drowSpeed为绘制上传进度百分比。

总结:说说大文件上传的难点

大文件切片上传,最复杂的莫过于切片和上传这一块,之前研究uniapp文档时,上面写得很不详细,然后跑去微信官方文档上去查,微信文档上描述的比较清楚,我把地址贴出来戳这里FileSystemManager,有兴趣的可以看看.

断点续传:简要的说明一下

后端以md5值为key,将进度存入redis,所以就算上传到一半有一个片失败了,那么下次重新上传时,会根据MD5值查询上次的上传进度,然后续传。当然也支持其他客户端上传,比如在上机上上传了10%,那么剩下的90%可以在电脑上继续上传,暂时不支持多客户端并行上传同一个文件。

完整代码:↓

upload.js

import SparkMD5 from 'spark-md5'export const postConsole = (options) => {let header = {...options.header}return new Promise((resolve, reject) => {uni.request({url: options.url + '/console',method: options.method || 'POST',data: options.data || {},dataType: 'json',header,success: (res) => {if (res.data) {if (res.data.code === '0') {resolve(res.data.data)} else {reject(res.data.msg)}}},fail: (err) => {reject(err)}})})
}
export default class BigUpload {constructor(Setting) {this.Setting = Setting}startUpload() {this.chunkSize = this.Setting.sizeif (!this.Setting.filePath) {return}this.pt_md5 = ''this.chunks = Math.ceil(this.Setting.byteLength / this.chunkSize)this.currentChunk = 0this.gowith = truethis.fileSlice(0, this.Setting.byteLength, file => {this.handshake(flag => {if (flag) {this.loadNext()} else {this.Setting.callback(false)}}, file)})}handshake(cbk, e) {let formData = {}let md5 = this.getDataMd5(e)this.pt_md5 = md5formData.pt_md5 = this.pt_md5formData.chunks = this.chunksformData.size = this.Setting.byteLengthformData.type = 'handshake'formData.md5 = md5formData.fileName = this.Setting.fileNameformData.contentType = this.Setting.typepostConsole({url: this.Setting.url,data: formData}).then(res => {if (res === 'success') {cbk(true)} else if (typeof res !== 'number') {this.Setting.callback(res)} else {this.currentChunk = resif (this.currentChunk < this.chunks) {this.loadNext()} else {this.currentChunk--this.loadNext()}}}).catch(err => {console.error(err)cbk(false)})}loadNext() {const p = this.currentChunk * 100 / this.chunksthis.drowSpeed(parseInt(p));let start = this.currentChunk * this.chunkSizelet length = start + this.chunkSize >= this.Setting.byteLength ? this.Setting.byteLength - start : this.chunkSizeif (this.gowith) {this.fileSlice(start, length, file => {this.uploadFileBinary(file)})}}uploadFileBinary(data) {const fs = uni.getFileSystemManager()const md5 = this.getDataMd5(data)const tempPath = `${wx.env.USER_DATA_PATH}/up_temp/${md5}.temp`fs.access({path: `${wx.env.USER_DATA_PATH}/up_temp`,fail(res) {fs.mkdirSync(`${wx.env.USER_DATA_PATH}/up_temp`, false)}})fs.writeFile({filePath: tempPath,encoding: 'binary',data: data,success: res => {let formData = {}formData.currentChunk = this.currentChunk + 1formData.pt_md5 = this.pt_md5formData.type = 'file'formData.md5 = md5uni.uploadFile({url: this.Setting.url,filePath: tempPath,name: 'file',formData: formData,success: res2 => {fs.unlinkSync(tempPath)if (res2.statusCode === 200) {const data = JSON.parse(res2.data)if (data.code === '0') {this.currentChunk++if (this.currentChunk < this.chunks) {this.loadNext()} else {this.callback(data.data)}} else {this.callback(false)}} else {this.callback(false)}},fail: err => {console.log(err)this.callback(false)}})},fail: err => {console.log(err)this.callback(false)}})}drowSpeed(p) {if (this.Setting.drowSpeed != null && typeof (this.Setting.drowSpeed) === 'function') {this.Setting.drowSpeed(p)}}getDataMd5(data) {if (data) {let trunkSpark = new SparkMD5()trunkSpark.appendBinary(data)let md5 = trunkSpark.end()return md5}}isPlay(cbk) {if (this.gowith) {this.gowith = falseif (typeof (cbk) === 'function') cbk(false)} else {this.gowith = truethis.loadNext()if (typeof (cbk) === 'function') cbk(true)}}fileSlice(start, length, cbk) {uni.getFileSystemManager().readFile({filePath: this.Setting.filePath,encoding: 'binary',position: start,length: length,success: res => {cbk(res.data)},fail: err => {console.error(err)this.callback(false)}})}callback(res) {if (typeof (this.Setting.callback) === 'function') {this.Setting.callback(res)}}
}

uniapp 微信小程序 分片 断点续传 大文件上传相关推荐

  1. 微信小程序环境下将文件上传到 OSS

    步骤 1: 配置 Bucket 跨域 客户端进行表单直传到 OSS 时,会从浏览器向 OSS 发送带有 Origin 的请求消息.OSS 对带有 Origin 头的请求消息会进行跨域规则(CORS)的 ...

  2. 微信小程序聊天室+websocket+文件上传(发送图片)

    最近哥们在写微信小程序,其中有个需求是搭建一个聊天室,可多人聊天,可私聊,可发送图片.但是由于一直没有这方面相关的了解,于是慢慢的去看,去做,前期真的很困难,路子不好走,慢慢的再搭建. 先看看效果吧 ...

  3. 小程序录音上传服务器,微信小程序录音实现功能并上传(使用node解析接收)

    微信小程序录音实现功能并上传(使用node解析接收) 发布时间:2020-09-04 11:59:06 来源:脚本之家 阅读:97 作者:weixin_43188227 背景 我在开发小程序的时候,有 ...

  4. 微信小程序云开发如何实现上传视频 以及 图片

    微信小程序云开发如何实现上传视频 以及 图片 最基础的数据库增删改查,上传到云存储即可实现,附源码 wxml文件 <button bindtap="upload">上传 ...

  5. taro开发微信小程序-添加开发者预览,上传测试版本(十四)

    taro开发微信小程序,上传测试版本,如果需要访问网络需要打开调试模式,如果配置了https协议的服务,提示对应的服务器证书无效,那么必须正确配置ssl证书,可以在阿里云或者腾讯云申请. 添加开发者预 ...

  6. 微信小程序录音功能实现,并上传录音文件,使用node解析接收

    背景 我在开发小程序的时候,有需求要实现录音功能,并能上传给服务器.小程序录音功能我是使用的微信的wx.getRecorderManager()实现的,通过该方法创建实例,实例录音得到的文件是本地临时 ...

  7. 微信小程序图片(单图多图上传显示)

    微信小程序上传图片组件自定义 最近在做微信小程序开发的时候,遇到了一个问题,就是图片上传于显示问题,微信自带的感觉用起来还是不方便,于是就萌生了自定义图片上传于显示组件 废话不多说直接上代码 首先创建 ...

  8. 小程序上传视频的php接口处理,微信小程序[第十二篇] -- 上传视频

    通过上一篇的学习,我们可以成功将宝宝的照片传到指定相册了,但是可爱的宝宝岂能只有照片,小视频必须同步跟上,莫问题!咱这篇就来一个视频上传的实现. 俺家小核桃镇贴. 服务端 其实对于yii2程序而言,如 ...

  9. 小程序对七牛云文件上传删除批量删除生成token封装无需服务器一个小程序搞定

    微信小程序获取token接入七牛云上传删除批量删除图片封装亲测可用 小程序获取七牛云uptoken删除文件封装 在研究官方文档后自己用小程序生成uptoken上传凭证封装,其他资料都说要服务器我又没钱 ...

  10. Udesk对接微信小程序实现商品浏览轨迹上传

    作者:张振琦 Udesk提供了小程序专用的行为轨迹SDK,可以用来收集客户的商品浏览轨迹,并在客服对话窗口中访问轨迹处可以查看. 在开发之前,需要先完成两个操作: 确认Udesk客服系统内绑定的小程序 ...

最新文章

  1. Java EE---使用Spring框架创建Market小项目
  2. lunix系统安装及分区补充安装包
  3. 图像处理中的dpi(Dots Per Inch)是什么单位?(图像每英寸长度内的像素点数)
  4. ViewPager实现页面切换
  5. VC++ MFC获取对话框上控件的位置
  6. Java 8状态更新
  7. Linux显示中文乱码解决方法
  8. 计算机考试400,400作文:电脑考试
  9. ML《决策树(三)CART》
  10. flex module 弹出窗问题
  11. PETERSON互斥算法解析
  12. js vue 创建一个div_Vue.js 创建一个 CNODE 社区(1)
  13. 瑞星:愚人节Conficker蠕虫未在我国爆发
  14. mysql front 链接_使用mysql_Front链接mysql,出现警告access denied for user ''@'localhost'
  15. 数学连乘和累加运算符号_数学运算符号
  16. java isbn_JAVA ISBN计算问题。。简单JAVA编程
  17. Winform2、(C#) 设置编译后.exe执行文件的图标
  18. n++和++n的区别
  19. 11 JavaScript删除链表的节点 牛客网JZ18
  20. WIFI下无法登录百度网盘

热门文章

  1. C语言中%d,%o,%f,%e,%x的意义
  2. 中国古语中的十大智慧
  3. nvidia-installer
  4. Git 常用记录(删除commit操作/挑拣/删除仓库)
  5. HOG+ADABOOST方式训练头肩检测模型
  6. 极客大学产品经理训练营:数据分析与用户数据 第17课总结
  7. 基本内置类型 声明与定义 static与entern const auto register volatile
  8. 在Python中安装meta模块
  9. 柯林斯第八版高阶字典前缀
  10. 动态为Spring Boot项目中所有自定义的Controller添加过滤器的两种方法