網(wǎng)頁(yè)聊天室之圖片上傳

圖片上傳包括:選擇指定圖片發(fā)送直接復(fù)制黏貼屏幕截圖發(fā)送

1. 發(fā)送選中的圖片

Node.js的Formidable模塊使用總結(jié)
  1. 創(chuàng)建Formidable.IncomingForm對(duì)象
    
var form = new formidable.IncomingForm();
  1. `form.encoding = 'utf-8'` 設(shè)置表單域的編碼
    
  2. form.uploadDir = "/my/dir"; 設(shè)置上傳文件存放的文件夾,默認(rèn)為系統(tǒng)的臨時(shí)文件夾,可以使用fs.rename()來改變上傳文件的存放位置和文件名
  3. `form.keepExtensions = false;` 設(shè)置該屬性為true可以使得上傳的文件保持原來的文件的擴(kuò)展名。
    
  4. form.type 只讀,根據(jù)請(qǐng)求的類型,取值'multipart' or 'urlencoded'
  5. `form.maxFieldsSize = 2 * 1024 * 1024;` 限制所有存儲(chǔ)表單字段域的大小(除去file字段),如果超出,則會(huì)觸發(fā)error事件,默認(rèn)為2M
    
  6. form.maxFields = 1000 設(shè)置可以轉(zhuǎn)換多少查詢字符串,默認(rèn)為1000
  7. `form.hash = false; `設(shè)置上傳文件的檢驗(yàn)碼,可以有兩個(gè)取值'sha1' or 'md5'.
    
  8. `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特性支持。
    
  9. form.bytesReceived 返回服務(wù)器已經(jīng)接收到當(dāng)前表單數(shù)據(jù)多少字節(jié)
  10. form.bytesExpected 返回將要接收到當(dāng)前表單所有數(shù)據(jù)的大小
  11. form.parse(request, [callback]) 該方法會(huì)轉(zhuǎn)換請(qǐng)求中所包含的表單數(shù)據(jù),callback會(huì)包含所有字段域和文件信息,如:
    form.parse(req, function(err, fields, files) {
      // ...   
    });
  1. 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);
    }
}
  1. 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())來格式化輸出文件的信息
  1. form.on('progress', function(bytesReceived, bytesExpected) {}); 當(dāng)有數(shù)據(jù)塊被處理之后會(huì)觸發(fā)該事件,對(duì)于創(chuàng)建進(jìn)度條非常有用。
  2. form.on('field', function(name, value) {});每當(dāng)一個(gè)字段/值對(duì)已經(jīng)收到時(shí)會(huì)觸發(fā)該事件
  3. form.on('fileBegin', function(name, file) {}); 在post流中檢測(cè)到任意一個(gè)新的文件便會(huì)觸發(fā)該事件
  4. form.on('file', function(name, file) {}); 每當(dāng)有一對(duì)字段/文件已經(jīng)接收到,便會(huì)觸發(fā)該事件
  5. 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()方法
  6. 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)容通過AjaxPOST到服務(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}));

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,716評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,706評(píng)論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,036評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,203評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,725評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,451評(píng)論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,677評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,857評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評(píng)論 1 295
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,407評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,643評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,836評(píng)論 18 139
  • 本文包括:1、文件上傳概述2、利用 Commons-fileupload 組件實(shí)現(xiàn)文件上傳3、核心API——Dis...
    廖少少閱讀 12,573評(píng)論 5 91
  • 本文為轉(zhuǎn)載,原文:Laravel項(xiàng)目中使用markdown編輯器及圖片粘貼上傳七牛云 Markdown Markd...
    ChainZhang閱讀 2,054評(píng)論 7 8
  • 一、幸福的人生活的方式都一樣 跟我在一起上班的明明,是我特別羨慕的人。她長(zhǎng)的漂亮,爸爸媽媽恩愛無比,...
    Nayatoo閱讀 385評(píng)論 0 0
  • 滿塘春水弄風(fēng)清,撲面泠,池蛙鳴。 一朵花成,滴露暗香縈。 飛鳥每逐飛絮落,時(shí)有意,點(diǎn)浮萍。 柳絲釣起釣絲情,細(xì)竹傾...
    云鷲山人閱讀 1,186評(píng)論 0 3