uniapp 微信小程序 分片 断点续传 大文件上传
uniapp 微信小程序实现大文件分片上传
- 需求:用uniapp开发的微信小程序实现大文件上传
- 方案:使用文件切割工具分片上传文件
- 实现:既然需求和方案已经明确了,那么,动手淦吧~
- 总结:说说大文件上传的难点
- 断点续传:简要的说明一下
- 完整代码:↓
需求:用uniapp开发的微信小程序实现大文件上传
公司目前有个需求,就是老师上课的录像需要通过手机端小程序上传到服务器,而手机拍摄的视频一般会很大,虽然微信会自动压缩视频,但是难免的,视频依然会很大~~
微信自带的文件上传工具,虽然能上传大文件,但是。。。难免可能会出现网络波动等问题,导致文件上传失败,而且服务端也做了限制,单个文件不能超过20M,那么~问题来了,录播课程一节课一般都在200-300m左右,如何上传呢??
方案:使用文件切割工具分片上传文件
此时就需要用到大文件切片上传工具啦。我实现的思路很简单:
- 文件上传之前的握手:先读取文件信息,例如文件名称、文件大小、文件MD5(用于检验上传完成后的文件完整性,以及作为当前上传的任务key)、文件分片大小、文件总片数等~;
- 文件切割:按指定大小将文件切割成独立的文件片,例如2m每片。
- 文件合并:将无数个文件片合并成一个完整的文件,然后根据握手时的MD5值校验文件的完整性。
- 文件保存:将上传后的文件信息保存到数据库,然后返回文件的保存信息,比如文件路径、文件大小、文件MD5等~
- 上传成功:将上传信息返回给前端。
实现:既然需求和方案已经明确了,那么,动手淦吧~
- 第一步,先实现视频文件的选中,然后读取文件的信息:
选择视频文件我是使用的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 微信小程序 分片 断点续传 大文件上传相关推荐
- 微信小程序环境下将文件上传到 OSS
步骤 1: 配置 Bucket 跨域 客户端进行表单直传到 OSS 时,会从浏览器向 OSS 发送带有 Origin 的请求消息.OSS 对带有 Origin 头的请求消息会进行跨域规则(CORS)的 ...
- 微信小程序聊天室+websocket+文件上传(发送图片)
最近哥们在写微信小程序,其中有个需求是搭建一个聊天室,可多人聊天,可私聊,可发送图片.但是由于一直没有这方面相关的了解,于是慢慢的去看,去做,前期真的很困难,路子不好走,慢慢的再搭建. 先看看效果吧 ...
- 小程序录音上传服务器,微信小程序录音实现功能并上传(使用node解析接收)
微信小程序录音实现功能并上传(使用node解析接收) 发布时间:2020-09-04 11:59:06 来源:脚本之家 阅读:97 作者:weixin_43188227 背景 我在开发小程序的时候,有 ...
- 微信小程序云开发如何实现上传视频 以及 图片
微信小程序云开发如何实现上传视频 以及 图片 最基础的数据库增删改查,上传到云存储即可实现,附源码 wxml文件 <button bindtap="upload">上传 ...
- taro开发微信小程序-添加开发者预览,上传测试版本(十四)
taro开发微信小程序,上传测试版本,如果需要访问网络需要打开调试模式,如果配置了https协议的服务,提示对应的服务器证书无效,那么必须正确配置ssl证书,可以在阿里云或者腾讯云申请. 添加开发者预 ...
- 微信小程序录音功能实现,并上传录音文件,使用node解析接收
背景 我在开发小程序的时候,有需求要实现录音功能,并能上传给服务器.小程序录音功能我是使用的微信的wx.getRecorderManager()实现的,通过该方法创建实例,实例录音得到的文件是本地临时 ...
- 微信小程序图片(单图多图上传显示)
微信小程序上传图片组件自定义 最近在做微信小程序开发的时候,遇到了一个问题,就是图片上传于显示问题,微信自带的感觉用起来还是不方便,于是就萌生了自定义图片上传于显示组件 废话不多说直接上代码 首先创建 ...
- 小程序上传视频的php接口处理,微信小程序[第十二篇] -- 上传视频
通过上一篇的学习,我们可以成功将宝宝的照片传到指定相册了,但是可爱的宝宝岂能只有照片,小视频必须同步跟上,莫问题!咱这篇就来一个视频上传的实现. 俺家小核桃镇贴. 服务端 其实对于yii2程序而言,如 ...
- 小程序对七牛云文件上传删除批量删除生成token封装无需服务器一个小程序搞定
微信小程序获取token接入七牛云上传删除批量删除图片封装亲测可用 小程序获取七牛云uptoken删除文件封装 在研究官方文档后自己用小程序生成uptoken上传凭证封装,其他资料都说要服务器我又没钱 ...
- Udesk对接微信小程序实现商品浏览轨迹上传
作者:张振琦 Udesk提供了小程序专用的行为轨迹SDK,可以用来收集客户的商品浏览轨迹,并在客服对话窗口中访问轨迹处可以查看. 在开发之前,需要先完成两个操作: 确认Udesk客服系统内绑定的小程序 ...
最新文章
- Java EE---使用Spring框架创建Market小项目
- lunix系统安装及分区补充安装包
- 图像处理中的dpi(Dots Per Inch)是什么单位?(图像每英寸长度内的像素点数)
- ViewPager实现页面切换
- VC++ MFC获取对话框上控件的位置
- Java 8状态更新
- Linux显示中文乱码解决方法
- 计算机考试400,400作文:电脑考试
- ML《决策树(三)CART》
- flex module 弹出窗问题
- PETERSON互斥算法解析
- js vue 创建一个div_Vue.js 创建一个 CNODE 社区(1)
- 瑞星:愚人节Conficker蠕虫未在我国爆发
- mysql front 链接_使用mysql_Front链接mysql,出现警告access denied for user ''@'localhost'
- 数学连乘和累加运算符号_数学运算符号
- java isbn_JAVA ISBN计算问题。。简单JAVA编程
- Winform2、(C#) 设置编译后.exe执行文件的图标
- n++和++n的区别
- 11 JavaScript删除链表的节点 牛客网JZ18
- WIFI下无法登录百度网盘
热门文章
- C语言中%d,%o,%f,%e,%x的意义
- 中国古语中的十大智慧
- nvidia-installer
- Git 常用记录(删除commit操作/挑拣/删除仓库)
- HOG+ADABOOST方式训练头肩检测模型
- 极客大学产品经理训练营:数据分析与用户数据 第17课总结
- 基本内置类型 声明与定义 static与entern const auto register volatile
- 在Python中安装meta模块
- 柯林斯第八版高阶字典前缀
- 动态为Spring Boot项目中所有自定义的Controller添加过滤器的两种方法