圖片上傳包括:選擇指定圖片發(fā)送 和 直接復(fù)制黏貼屏幕截圖發(fā)送
1. 發(fā)送選中的圖片
Node.js的Formidable模塊使用總結(jié)
創(chuàng)建Formidable.IncomingForm對(duì)象
var form = new formidable.IncomingForm();
`form.encoding = 'utf-8'` 設(shè)置表單域的編碼
-
form.uploadDir = "/my/dir";
設(shè)置上傳文件存放的文件夾,默認(rèn)為系統(tǒng)的臨時(shí)文件夾,可以使用fs.rename()來改變上傳文件的存放位置和文件名 `form.keepExtensions = false;` 設(shè)置該屬性為true可以使得上傳的文件保持原來的文件的擴(kuò)展名。
-
form.typ
e 只讀,根據(jù)請(qǐng)求的類型,取值'multipart' or 'urlencoded' `form.maxFieldsSize = 2 * 1024 * 1024;` 限制所有存儲(chǔ)表單字段域的大小(除去file字段),如果超出,則會(huì)觸發(fā)error事件,默認(rèn)為2M
-
form.maxFields = 1000
設(shè)置可以轉(zhuǎn)換多少查詢字符串,默認(rèn)為1000 `form.hash = false; `設(shè)置上傳文件的檢驗(yàn)碼,可以有兩個(gè)取值'sha1' or 'md5'.
`form.multiples = false;` 開啟該功能,當(dāng)調(diào)用form.parse()方法時(shí),回調(diào)函數(shù)的files參數(shù)將會(huì)是一個(gè)file數(shù)組,數(shù)組每一個(gè)成員是一個(gè)File對(duì)象,此功能需要 html5中multiple特性支持。
-
form.bytesReceived
返回服務(wù)器已經(jīng)接收到當(dāng)前表單數(shù)據(jù)多少字節(jié) -
form.bytesExpected
返回將要接收到當(dāng)前表單所有數(shù)據(jù)的大小 -
form.parse(request, [callback])
該方法會(huì)轉(zhuǎn)換請(qǐng)求中所包含的表單數(shù)據(jù),callback會(huì)包含所有字段域和文件信息,如:
form.parse(req, function(err, fields, files) {
// ...
});
-
form.onPart(part);
你可以重載處理multipart流的方法,這樣做的話會(huì)禁止field和file事件的發(fā)生,你將不得不自己處理這些事情,如:
form.onPart = function(part) {
part.addListener('data', function() {
// ...
});
}
如果你只想讓formdable處理一部分事情,你可以這樣做:
form.onPart = function(part) {
if (!part.filename) {
// 讓formidable處理所有非文件部分
form.handlePart(part);
}
}
-
formidable.File
對(duì)象
-
file.size = 0
上傳文件的大小,如果文件正在上傳,表示已上傳部分的大小
*file.path = null
上傳文件的路徑。如果不想讓formidable產(chǎn)生一個(gè)臨時(shí)文件夾,可以在fileBegain事件中修改路徑 -
file.name = null
上傳文件的名字 -
file.type = null
上傳文件的mime類型 -
file.lastModifiedDate = null
時(shí)間對(duì)象,上傳文件最近一次被修改的時(shí)間 -
file.hash = null
返回文件的hash值 - 可以使用
JSON.stringify(file.toJSON())
來格式化輸出文件的信息
-
form.on('progress', function(bytesReceived, bytesExpected) {});
當(dāng)有數(shù)據(jù)塊被處理之后會(huì)觸發(fā)該事件,對(duì)于創(chuàng)建進(jìn)度條非常有用。 -
form.on('field', function(name, value) {});
每當(dāng)一個(gè)字段/值對(duì)已經(jīng)收到時(shí)會(huì)觸發(fā)該事件 -
form.on('fileBegin', function(name, file) {});
在post流中檢測(cè)到任意一個(gè)新的文件便會(huì)觸發(fā)該事件 -
form.on('file', function(name, file) {});
每當(dāng)有一對(duì)字段/文件已經(jīng)接收到,便會(huì)觸發(fā)該事件 -
form.on('error', function(err) {});
當(dāng)上傳流中出現(xiàn)錯(cuò)誤便會(huì)觸發(fā)該事件,當(dāng)出現(xiàn)錯(cuò)誤時(shí),若想要繼續(xù)觸發(fā)request的data事件,則必須手動(dòng)調(diào)用request.resume()方法 -
form.on('aborted', function() {});
當(dāng)用戶中止請(qǐng)求時(shí)會(huì)觸發(fā)該事件,socket中的timeout和close事件也會(huì)觸發(fā)該事件,當(dāng)該事件觸發(fā)之后,error事件也會(huì)觸發(fā)
21)form.on('end', function() {});
當(dāng)所有的請(qǐng)求已經(jīng)接收到,并且所有的文件都已上傳到服務(wù)器中,該事件會(huì)觸發(fā)。此時(shí)可以發(fā)送請(qǐng)求到客戶端。
實(shí)際應(yīng)用
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
//發(fā)送圖片
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); //上傳的參數(shù)數(shù)據(jù)
})
.on('file', function(field, file) {
path = '\\' + file.path; //上傳的文件數(shù)據(jù)
})
.on('progress', function(bytesReceived, bytesExpected) {
uploadprogress = (bytesReceived / bytesExpected * 100).toFixed(0); //計(jì)算進(jìn)度
console.log("upload----"+ uploadprogress);
// io.sockets.in('sessionId').emit('uploadProgress', uploadprogress);
})
.on('end', function() {
//上傳完發(fā)送成功的json數(shù)據(jù)
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);//這段文本發(fā)回前端就會(huì)被同名的函數(shù)執(zhí)行
}).on("abort",function(){
var callback="<script>alert('"+ttt+"');</script>";
res.end(callback);
});
});
2. 直接復(fù)制黏貼屏幕截圖發(fā)送
實(shí)現(xiàn)效果為:屏幕截圖, 直接黏貼到輸入框中, 點(diǎn)擊發(fā)送按鈕上傳
發(fā)送截圖.gif
HTML
<a class="btn btn-primary disabled" id="UploadBtn-screen"><i class="fa fa-upload fa-fw"></i> 發(fā)送截圖 </a>
<a class="btn btn-primary disabled" id="cancelSend"><i class="fa fa-upload fa-fw"></i> 取消發(fā)送圖片 </a>
<div class="screen-shot" id="imgPreview"></div>
將網(wǎng)上搜到的封裝JS插件添加實(shí)際需求
//屏幕截圖
(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: '', //預(yù)覽圖片的容器,
uploadBtn: '', //上傳按鈕,
cancelBtn: '', //取消按鈕,
imgHeight: 200 //預(yù)覽圖片的默認(rèn)高度
};
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');
};
};
//事件注冊(cè)
$(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);
該插件目前只有一個(gè)方法 getImgData,調(diào)用示例如下:
var imgData = $('#imgPreview').screenshotPaste('getImgData');
該方法返回的是img的src屬性里面的內(nèi)容,即base64編碼的圖片數(shù)據(jù)內(nèi)容.
JS
將得到的base64編碼的圖片數(shù)據(jù)內(nèi)容通過
Ajax
POST到服務(wù)器
利用隨機(jī)生成的字符串作為文件名
//屏幕截圖
$('#msg').screenshotPaste({
imgContainer: '#imgPreview', //預(yù)覽圖片的容器
uploadBtn: '#UploadBtn-screen', //上傳按鈕
cancelBtn: '#cancelSend' //取消按鈕
});
// randomWord 產(chǎn)生任意長(zhǎng)度隨機(jī)字母數(shù)字組合
// randomFlag 是否任意長(zhǎng)度
// min 任意長(zhǎng)度最小位[固定位數(shù)]
// max 任意長(zhǎng)度最大位
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'];
// 隨機(jī)產(chǎn)生
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;
}
//發(fā)送截圖
function doUploadScreen() {
var imgData = $('#imgPreview').screenshotPaste('getImgData');
var fileName = randomWord(false, 32); //隨機(jī)字符串組成的圖片名
$.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接收?qǐng)D片base64格式保存為文件
base64的形式為
“data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0。。。。”;
當(dāng)接收到上邊的內(nèi)容后,需要將data:image/png;base64,這段內(nèi)容過濾掉,過濾成:“iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0。。。”;
然后進(jìn)行保存。
利用fs.writeFile
固定文件名前綴并將dataBuffer
寫入
fs.writeFile("./public/uploadFile/upload_" + fileName +".jpg", dataBuffer, function(err) {}
//將文件名命名成這樣為了使用同意正則表達(dá)式
str = str.replace(/[\\]public[\\]uploadFile[\\]upload_[\w]+[\.]jpg/g,"<img class='img-msg' src=" + str + " />");
完整代碼:
//上傳截圖
router.post('/upload', function(req, res, next){
//接收前臺(tái)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));
}
});
});
這里有個(gè)問題:當(dāng)截取的圖片過大時(shí),無法 post 即 POST 413,所以:
express 如何解決 413 請(qǐng)求實(shí)體過長(zhǎng)?
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}));