网页聊天室之图片上传

图片上传包括:选择指定图片发送直接复制黏贴屏幕截图发送

1. 发送选中的图片

Node.js的Formidable模块使用总结
  1. 创建Formidable.IncomingForm对象
    
var form = new formidable.IncomingForm();
  1. `form.encoding = 'utf-8'` 设置表单域的编码
    
  2. form.uploadDir = "/my/dir"; 设置上传文件存放的文件夹,默认为系统的临时文件夹,可以使用fs.rename()来改变上传文件的存放位置和文件名
  3. `form.keepExtensions = false;` 设置该属性为true可以使得上传的文件保持原来的文件的扩展名。
    
  4. form.type 只读,根据请求的类型,取值'multipart' or 'urlencoded'
  5. `form.maxFieldsSize = 2 * 1024 * 1024;` 限制所有存储表单字段域的大小(除去file字段),如果超出,则会触发error事件,默认为2M
    
  6. form.maxFields = 1000 设置可以转换多少查询字符串,默认为1000
  7. `form.hash = false; `设置上传文件的检验码,可以有两个取值'sha1' or 'md5'.
    
  8. `form.multiples = false;` 开启该功能,当调用form.parse()方法时,回调函数的files参数将会是一个file数组,数组每一个成员是一个File对象,此功能需要 html5中multiple特性支持。
    
  9. form.bytesReceived 返回服务器已经接收到当前表单数据多少字节
  10. form.bytesExpected 返回将要接收到当前表单所有数据的大小
  11. form.parse(request, [callback]) 该方法会转换请求中所包含的表单数据,callback会包含所有字段域和文件信息,如:
    form.parse(req, function(err, fields, files) {
      // ...   
    });
  1. 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);
    }
}
  1. 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())来格式化输出文件的信息
  1. form.on('progress', function(bytesReceived, bytesExpected) {}); 当有数据块被处理之后会触发该事件,对于创建进度条非常有用。
  2. form.on('field', function(name, value) {});每当一个字段/值对已经收到时会触发该事件
  3. form.on('fileBegin', function(name, file) {}); 在post流中检测到任意一个新的文件便会触发该事件
  4. form.on('file', function(name, file) {}); 每当有一对字段/文件已经接收到,便会触发该事件
  5. form.on('error', function(err) {});当上传流中出现错误便会触发该事件,当出现错误时,若想要继续触发request的data事件,则必须手动调用request.resume()方法
  6. 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. 直接复制黏贴屏幕截图发送

实现效果为:屏幕截图, 直接黏贴到输入框中, 点击发送按钮上传

发送截图.gif
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编码的图片数据内容通过AjaxPOST到服务器
利用随机生成的字符串作为文件名

//屏幕截图
$('#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}));

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 本文包括:1、文件上传概述2、利用 Commons-fileupload 组件实现文件上传3、核心API——Dis...
    廖少少阅读 12,510评论 5 91
  • 本文为转载,原文:Laravel项目中使用markdown编辑器及图片粘贴上传七牛云 Markdown Markd...
    ChainZhang阅读 2,020评论 7 8
  • 一、幸福的人生活的方式都一样 跟我在一起上班的明明,是我特别羡慕的人。她长的漂亮,爸爸妈妈恩爱无比,...
    Nayatoo阅读 368评论 0 0
  • 满塘春水弄风清,扑面泠,池蛙鸣。 一朵花成,滴露暗香萦。 飞鸟每逐飞絮落,时有意,点浮萍。 柳丝钓起钓丝情,细竹倾...
    云鹫山人阅读 1,092评论 0 3