图片上传包括:选择指定图片发送 和 直接复制黏贴屏幕截图发送
1. 发送选中的图片
Node.js的Formidable模块使用总结
创建Formidable.IncomingForm对象
var form = new formidable.IncomingForm();
`form.encoding = 'utf-8'` 设置表单域的编码
-
form.uploadDir = "/my/dir";
设置上传文件存放的文件夹,默认为系统的临时文件夹,可以使用fs.rename()来改变上传文件的存放位置和文件名 `form.keepExtensions = false;` 设置该属性为true可以使得上传的文件保持原来的文件的扩展名。
-
form.typ
e 只读,根据请求的类型,取值'multipart' or 'urlencoded' `form.maxFieldsSize = 2 * 1024 * 1024;` 限制所有存储表单字段域的大小(除去file字段),如果超出,则会触发error事件,默认为2M
-
form.maxFields = 1000
设置可以转换多少查询字符串,默认为1000 `form.hash = false; `设置上传文件的检验码,可以有两个取值'sha1' or 'md5'.
`form.multiples = false;` 开启该功能,当调用form.parse()方法时,回调函数的files参数将会是一个file数组,数组每一个成员是一个File对象,此功能需要 html5中multiple特性支持。
-
form.bytesReceived
返回服务器已经接收到当前表单数据多少字节 -
form.bytesExpected
返回将要接收到当前表单所有数据的大小 -
form.parse(request, [callback])
该方法会转换请求中所包含的表单数据,callback会包含所有字段域和文件信息,如:
form.parse(req, function(err, fields, files) {
// ...
});
-
form.onPart(part);
你可以重载处理multipart流的方法,这样做的话会禁止field和file事件的发生,你将不得不自己处理这些事情,如:
form.onPart = function(part) {
part.addListener('data', function() {
// ...
});
}
如果你只想让formdable处理一部分事情,你可以这样做:
form.onPart = function(part) {
if (!part.filename) {
// 让formidable处理所有非文件部分
form.handlePart(part);
}
}
-
formidable.File
对象
-
file.size = 0
上传文件的大小,如果文件正在上传,表示已上传部分的大小
*file.path = null
上传文件的路径。如果不想让formidable产生一个临时文件夹,可以在fileBegain事件中修改路径 -
file.name = null
上传文件的名字 -
file.type = null
上传文件的mime类型 -
file.lastModifiedDate = null
时间对象,上传文件最近一次被修改的时间 -
file.hash = null
返回文件的hash值 - 可以使用
JSON.stringify(file.toJSON())
来格式化输出文件的信息
-
form.on('progress', function(bytesReceived, bytesExpected) {});
当有数据块被处理之后会触发该事件,对于创建进度条非常有用。 -
form.on('field', function(name, value) {});
每当一个字段/值对已经收到时会触发该事件 -
form.on('fileBegin', function(name, file) {});
在post流中检测到任意一个新的文件便会触发该事件 -
form.on('file', function(name, file) {});
每当有一对字段/文件已经接收到,便会触发该事件 -
form.on('error', function(err) {});
当上传流中出现错误便会触发该事件,当出现错误时,若想要继续触发request的data事件,则必须手动调用request.resume()方法 -
form.on('aborted', function() {});
当用户中止请求时会触发该事件,socket中的timeout和close事件也会触发该事件,当该事件触发之后,error事件也会触发
21)form.on('end', function() {});
当所有的请求已经接收到,并且所有的文件都已上传到服务器中,该事件会触发。此时可以发送请求到客户端。
实际应用
HTML
<input type="file" id="uploadFile" multiple="multiple" accept="image/*" />
<a class="btn btn-primary disabled" id="UploadBtn"><i class="fa fa-upload fa-fw"></i> 上传图片 </a>
JS中使用ajax
//发送图片
function doUpload() {
var file = $("#uploadFile")[0].files[0];
var form = new FormData();
form.append("file", file);
$.ajax({
url: "/p/uploadImg",
type: "POST",
data: form,
async: true,
processData: false,
contentType: false,
success: function(result) {
startReq = false;
if (result.code == 0) {
var html = $.format(TO_MSG_IMG, result.data);
$("#m"+fid).append(html);
var msg = {
from: uid,
to: fid,
content: result.data
};
socket.emit('chat message', msg);
toBottom();
}
}
});
}
router
//上传图片
router.post('/uploadImg', function(req, res, next) {
console.log("开始上传");
// var io = global.io;
var form = new formidable.IncomingForm();
var path = "";
var fields = [];
form.encoding = 'utf-8'; //上传文件编码格式
form.uploadDir = "public/uploadFile"; //上传文件保存路径(必须在public下新建)
form.keepExtensions = true; //保持上传文件后缀
form.maxFieldsSize = 30000 * 1024 * 1024; //上传文件格式最大值
var uploadprogress = 0;
console.log("start:upload----"+uploadprogress);
form.parse(req);
form.on('field', function(field, value) {
console.log(field + ":" + value); //上传的参数数据
})
.on('file', function(field, file) {
path = '\\' + file.path; //上传的文件数据
})
.on('progress', function(bytesReceived, bytesExpected) {
uploadprogress = (bytesReceived / bytesExpected * 100).toFixed(0); //计算进度
console.log("upload----"+ uploadprogress);
// io.sockets.in('sessionId').emit('uploadProgress', uploadprogress);
})
.on('end', function() {
//上传完发送成功的json数据
console.log('-> upload done\n');
entries.code = 0;
entries.data = path;
res.writeHead(200, {
'content-type': 'text/json'
});
res.end(JSON.stringify(entries));
})
.on("err",function(err){
var callback="<script>alert('"+err+"');</script>";
res.end(callback);//这段文本发回前端就会被同名的函数执行
}).on("abort",function(){
var callback="<script>alert('"+ttt+"');</script>";
res.end(callback);
});
});
2. 直接复制黏贴屏幕截图发送
实现效果为:屏幕截图, 直接黏贴到输入框中, 点击发送按钮上传
HTML
<a class="btn btn-primary disabled" id="UploadBtn-screen"><i class="fa fa-upload fa-fw"></i> 发送截图 </a>
<a class="btn btn-primary disabled" id="cancelSend"><i class="fa fa-upload fa-fw"></i> 取消发送图片 </a>
<div class="screen-shot" id="imgPreview"></div>
将网上搜到的封装JS插件添加实际需求
//屏幕截图
(function ($) {
$.fn.screenshotPaste=function(options){
var me = this;
if(typeof options =='string'){
var method = $.fn.screenshotPaste.methods[options];
if (method) {
return method();
} else {
return;
}
}
var defaults = {
imgContainer: '', //预览图片的容器,
uploadBtn: '', //上传按钮,
cancelBtn: '', //取消按钮,
imgHeight: 200 //预览图片的默认高度
};
options = $.extend(defaults,options);
var imgReader = function( item ){
var file = item.getAsFile();
var reader = new FileReader();
reader.readAsDataURL( file );
reader.onload = function( e ){
var img = new Image();
img.src = e.target.result;
$(img).css({ height: options.imgHeight });
$(document).find(options.imgContainer)
.html('')
.show()
.append(img);
$(document).find(options.uploadBtn).removeClass('disabled');
$(document).find(options.cancelBtn).removeClass('disabled');
};
};
//事件注册
$(me).on('paste',function(e){
var clipboardData = e.originalEvent.clipboardData;
var items, item, types;
if( clipboardData ){
items = clipboardData.items;
if( !items ){
return;
}
item = items[0];
types = clipboardData.types || [];
for(var i=0 ; i < types.length; i++ ){
if( types[i] === 'Files' ){
item = items[i];
break;
}
}
if( item && item.kind === 'file' && item.type.match(/^image\//i) ){
imgReader( item );
}
}
});
$.fn.screenshotPaste.methods = {
getImgData: function () {
var src = $(document).find(options.imgContainer).find('img').attr('src');
if(src==undefined){
src='';
}
return src;
}
};
};
})(jQuery);
该插件目前只有一个方法 getImgData,调用示例如下:
var imgData = $('#imgPreview').screenshotPaste('getImgData');
该方法返回的是img的src属性里面的内容,即base64编码的图片数据内容.
JS
将得到的base64编码的图片数据内容通过
Ajax
POST到服务器
利用随机生成的字符串作为文件名
//屏幕截图
$('#msg').screenshotPaste({
imgContainer: '#imgPreview', //预览图片的容器
uploadBtn: '#UploadBtn-screen', //上传按钮
cancelBtn: '#cancelSend' //取消按钮
});
// randomWord 产生任意长度随机字母数字组合
// randomFlag 是否任意长度
// min 任意长度最小位[固定位数]
// max 任意长度最大位
function randomWord(randomFlag, min, max){
var str = "",
range = min,
arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
// 随机产生
if(randomFlag){
range = Math.round(Math.random() * (max-min)) + min;
}
for(var i=0; i<range; i++){
pos = Math.round(Math.random() * (arr.length-1));
str += arr[pos];
}
return str;
}
//发送截图
function doUploadScreen() {
var imgData = $('#imgPreview').screenshotPaste('getImgData');
var fileName = randomWord(false, 32); //随机字符串组成的图片名
$.ajax({
type: "POST",
url: "/p/upload",
contentType: "application/json",
dataType: "json",
data: JSON.stringify({
'img': imgData,
'fileName': fileName
}),
success: function(result) {
$('#imgPreview').hide();
$('#UploadBtn-screen').addClass('disabled');
$('#cancelSend').addClass('disabled');
if (result.code == 0) {
var html = $.format(TO_MSG_IMG, result.data);
$("#m"+fid).append(html);
var msg = {
from: uid,
to: fid,
content: result.data
};
socket.emit('chat message', msg);
toBottom();
}
}
})
}
router
Nodejs接收图片base64格式保存为文件
base64的形式为
“data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0。。。。”;
当接收到上边的内容后,需要将data:image/png;base64,这段内容过滤掉,过滤成:“iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0。。。”;
然后进行保存。
利用fs.writeFile
固定文件名前缀并将dataBuffer
写入
fs.writeFile("./public/uploadFile/upload_" + fileName +".jpg", dataBuffer, function(err) {}
//将文件名命名成这样为了使用同意正则表达式
str = str.replace(/[\\]public[\\]uploadFile[\\]upload_[\w]+[\.]jpg/g,"<img class='img-msg' src=" + str + " />");
完整代码:
//上传截图
router.post('/upload', function(req, res, next){
//接收前台POST过来的base64
var imgData = req.body.img;
//过滤data:URL
var base64Data = imgData.replace(/^data:image\/\w+;base64,/, "");
var dataBuffer = new Buffer(base64Data, 'base64');
var fileName = req.body.fileName;
//console.log(dataBuffer);
fs.writeFile("./public/uploadFile/upload_" + fileName +".jpg", dataBuffer, function(err) {
if(err){
res.send(err);
}else{
var path = "\\public\\uploadFile\\upload_" + fileName +".jpg";
entries.code = 0;
entries.data = path;
res.end(JSON.stringify(entries));
}
});
});
这里有个问题:当截取的图片过大时,无法 post 即 POST 413,所以:
express 如何解决 413 请求实体过长?
app.use(express.json({limit: '50mb'}));
app.use(express.urlencoded({limit: '50mb'}));
但在 Express 4里,必须要求有body-parser
模块和使用其json()
和urlencoded()
方法,像这样:
var bodyParser = require('body-parser');
app.use(bodyParser.json({limit: '50mb'}));
app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));