关于使用 Connect-Busboy 实现文件上传 优化说明
这篇博文完全上关于上一篇的优化 先看上一篇 node.js 在 Express4.0 框架使用 Connect-Busboy 实现文件上传 因为从上次博客改用 connect-busboy 来上传文件后,发现了明显的一个bug
bug 说明
文件显示上传 100% ,然后预览的时候,偶尔会发现图片只能显示一部分 这种情况在 png 格式 图片尤其严重 昨天重新 review 代码,发现一个bug ,当然和 connect-busboy 一点关系都没有,而是涉及到流的处理过程.
这里把上一篇 blog 里贴出的上传代码在放上来分析一下
function upload(req, res, next) {
req.busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
var tmp_path=path.join(os.tmpDir(), path.basename(filename));
file.pipe(fs.createWriteStream(tmp_path));
file.on('end',function(){
var uuid = tool.generateUUID();
commfile.savePathFile(uuid, mimetype, tmp_path, true, function(err, fileBase64) {
if (err) {
res.json({
success:false,
url:''
});
}else{
res.json({
success: true,
url: '/file/' + uuid
});
}
});
});
});
req.pipe(req.busboy);
}
针对代码,我们细致理一下 req.busboy.on('file' ,function(......).....)
1 .req.busboy.on 方法用来监听 form 提交的参数的事件,其中 on(‘file’ ) 是监听 form 提交二进制文件流事件. 上面的监听事件的回调函数传递过来几个参数,我们重点看下 file 参数 file 是二进制文件流句柄,它是一个标准的 stream 2 .接着我取到服务器临时文件目录 ,和 文件名,组合成一个文件路径
类似这样的路径: /Users/zhangzhi/data/temp/aaa.png
var tmp_path=path.join(os.tmpDir(), path.basename(filename));
3 .接着我把 file 流写入上面定义的文件路径
file.pipe(fs.createWriteStream(tmp_path));
- 这里出现了一个流程冗余问题
上面用到了 pipe 希望读一下我的另外一篇文章了解 pipe 原理 node.js 在io实现异步非阻塞
- 然后我读取刚才的临时文件到字节数组,转化为 base64 字符串,存储在了 LevelDB数据库
file.on('end' ....)
- bug 就出现在这里
我们回头分析一下上面出现的流程冗余问题及bug
流程冗余
假如我的图片是直接保存在服务器的某个文件夹下面,那么上面的 1,2,3 流程是没有问题,(当然不需要临时文件目录,直接写入到服务器指定的保存图片目录即可)
但是我目前的博客图片全部在 levelDB 数据库里,所以没有必要先在临时目录生成图片,然后读取图片字节数组,再转化为base64串写入数据库
中间的弯路就是保存临时文件再读取我们已经拿到了 file 上传文件流句柄,所以完全可以直接塞到二进制数组中,临时保存,毕竟不是上面巨大文件,对内存容忍度为0 ,如下代码
var dataArr = [];
file.on("data", function (chunk){
dataArr.push(chunk);
});
上面我们监听 file 的 data 事件,通过源源不断的字节流传输到服务器,我们就不管三七二十一先塞进 dataArr 二进制数组中 那么接着我们要做什么,我们直接可以把上面的 dataArr 数据转化成一个 base64 字符串存入数据库,具体的代码实现在下面,我们该分析下上面的bug了
bug出现
我们上面说到了第 4 步有一个隐形的bug ,在上传文件的时候偶尔出现,尤其是 png 图片时,100% 暴露,叔能忍,婶婶也忍不了啊… 为什么第 4 步这里有bug ? 还是脱离不了整个流程,简单回顾下.
上传的文件流 file 写入到临时文件目录中 ,假设这个过程叫 A 我们读取 临时文件 文件转化为 base64 字符串, 这个过程叫 B 我们应该什么时候去读取 临时文件,应该在 A 过程全部结束, 而 A 过程结束是指 file 上传文件流全部传完 并且 文件流全部写入到了临时文件 但是我们上面 监听的 file.on('end’,function(){…} 只是监听上传文件流结束的事件,具体这个文件流是否完全写入到临时文件,根本不知道,但是这时我们已经做了一个错误的决定,就是开始读取 临时文件 转化为base64 字符串 所以: 当文件流仅仅是通过 http 传完时,但是并没有写完到临时文件,这是我们立即读取图片,一定是不完整的图片
如果这里一定要这样去做,改如果修改一下代码呢:
function upload(req, res, next) {
req.busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
var tmp_path=path.join(os.tmpDir(), path.basename(filename));
var passedLength = 0;
var writeStream = fs.createWriteStream(tmp_path);
file.on('data',function(trunk){
passedLength += chunk.length;
if (writeStream.write(chunk) === false) {
file.pause();
}
});
file.on('end',function(){
//当没有数据读取,关闭写入数据流
writeStream.end();
});
writeStream.on('drain', function() {
//速度写入完毕后
//这里我们才去执行把图片文件转换成 base64 字符串写入数据库操作
var uuid = tool.generateUUID();
commfile.savePathFile(uuid, mimetype, tmp_path, true, function(err, fileBase64) {
if (err) {
res.json({
success:false,
url:''
});
}else{
res.json({
success: true,
url: '/file/' + uuid
});
}
});
});
req.pipe(req.busboy);
}
上面的代码是没有经过测试的,直接敲上去,只是为了说明按照原来流程的正确处理方式
流程问题理清了,bug 也找到了.下面做如下修复
先上代码
req.busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
var dataArr = [], len = 0,strBase64;
file.on("data", function (chunk){
dataArr.push(chunk);
len += chunk.length;
});
file.on("end", function () {
strBase64 = Buffer.concat( dataArr, len ).toString('base64');
var uuid=tool.generateUUID();
commfile.saveFile(uuid,mimetype,strBase64,function(err){
if (err) {
res.json({
success:false,
url:''
});
}else{
res.json({
success: true,
url: '/file/' + uuid
});
}
})
});
});
req.pipe(req.busboy);
简单说明一下上面的代码 file.on(“data” 事件实时把字节流写入数组中 file.on(“end” 当上传文件字节流结束后, 把字节数组转化为 base64 字符串 strBase64 = Buffer.concat( dataArr, len ).toString(‘base64’); 最后我们把 base64 字符串写入数据库 这样上传图片再也不会出现写入数据库的图片不完整的bug 了.
关于使用 Connect-Busboy 实现文件上传 优化说明的更多相关文章
- springMVC3学习(十二)--文件上传优化CommonsMultipartResolver
基于上一篇文件上传发现效率很慢,我们应该对它进行优化 使用springMVC对文件上传的解析器 来处理文件上传的时候需要在spring的applicationContext里面加上springMVC ...
- springMVC文件上传优化
1. 新建web project 2. 填 jar,注意新加入两个上传文件的jar, commons-io, commons-fileupload 3. 改写web.xml <?xml vers ...
- Java web文件上传下载
[版权申明:本文系作者原创,转载请注明出处] 文章出处:http://blog.csdn.net/sdksdk0/article/details/52048666 作者:朱培 ID:sdksdk0 邮 ...
- Java FtpClient 实现文件上传服务
一.Ubuntu 安装 Vsftpd 服务 1.安装 sudo apt-get install vsftpd 2.添加用户(uftp) sudo useradd -d /home/uftp -s /b ...
- JAVA中使用FTPClient实现文件上传下载实例代码
一.上传文件 原理就不介绍了,大家直接看代码吧 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ...
- 【C#公共帮助类】FTPClientHelper帮助类,实现文件上传,目录操作,下载等动作
关于本文档的说明 本文档使用Socket通信方式来实现ftp文件的上传下载等命令的执行 欢迎传播分享,必须保持原作者的信息,但禁止将该文档直接用于商业盈利. 本人自从几年前走上编程之路,一直致力于收集 ...
- 【FTP】FTP文件上传下载-支持断点续传
Jar包:apache的commons-net包: 支持断点续传 支持进度监控(有时出不来,搞不清原因) 相关知识点 编码格式: UTF-8等; 文件类型: 包括[BINARY_FILE_TYPE(常 ...
- 【SFTP】使用Jsch实现Sftp文件上传-支持断点续传和进程监控
JSch是Java Secure Channel的缩写.JSch是一个SSH2的纯Java实现.它允许你连接到一个SSH服务器,并且可以使用端口转发,X11转发,文件传输等,当然你也可以集成它的功能到 ...
- 【转】JSch - Java实现的SFTP(文件上传详解篇)
JSch是Java Secure Channel的缩写.JSch是一个SSH2的纯Java实现.它允许你连接到一个SSH服务器,并且可以使用端口转发,X11转发,文件传输等,当然你也可以集成它的功能到 ...
随机推荐
- 《FPGA全程进阶---实战演练》第二十一章 电源常用类型:LDO和 DCDC
高速电路中的电源设计 高速电路中的电源设计大概分为两种,一种是集总式架构,一种是分布式架构.集总式架构就是由一个电源输入,然后生成多种所需要的电压.如图1所示.这种架构会增加多个DC/DC模块,这样成 ...
- 关于NopCommerce3.6版用户登录详解
一.登录方式 Nop登录方式有两种(且只能选择一种方式登录):一种是用用户名登录,另一种是用户注册邮箱登录,这个在后台可配置: 第一种:用户名登录 后台配置路径在商城设置à设置管理à客户设置:使用用户 ...
- 【LeetCode】5. Longest Palindromic Substring 最大回文子串
题目: Given a string S, find the longest palindromic substring in S. You may assume that the maximum l ...
- PMP-产品范围与项目范围区别
如果你对项目管理.系统架构有兴趣,请加微信订阅号"softjg",加入这个PM.架构师的大家庭 1.产品范围--某项产品.服务或成果所具有的特性和功能. 2.项目范围--为交付具有 ...
- Eclipse User Library Add/Edit/Remove
1:前文 近来开发项目采用的技术是AngularJS+Bootstrap+Spring Boot+SpringMvc+JPA 的方式,创建的项目是 Java Project,对应的JAR文件需要添加到 ...
- No.002 Add Two Numbers
Add Two Numbers Total Accepted: 160702 Total Submissions: 664770 Difficulty: Medium You are given tw ...
- HTML你应该知道的三大基本元素
顶级.块级.内联,html元素的三大分类 如果将这些元素细分, 又可以分别归为顶级(top-level)元素,块级(block-level)元素和内联(inline)元素. 1. Top-level ...
- WP8——页面跳转方法
1.页面传值: this.NavigationService.Navigate(new Uri("/SecondPage.xaml?CustomerId=1234&Product ...
- select 嵌套查询
1. SELECT语句的子查询 语法: SELECT ... FROM (subquery) AS name ... 先创建一个表: CREATE TABLE t1 (s1 INT, s2 C ...
- 使chrome支持跨域访问
在做后台开发的时候,使用了iframe框架.后台主页面如下: <div style="width:185px; overflow: hidden;" id="wes ...