前端文件上传方式总结

1.使用FormData进行上传

这是比较主流的方式,也是兼容性最好的方式。

前端代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>转FormData传递数据</title>
</head>
<body>
<input type="file" id="file" multiple>
</body>
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<script>
    let $file = document.getElementById('file');
    $file.addEventListener('change', function (e) {
        let files = e.target.files;
        let formData=new FormData();
        let fields={
            id:'007',
            username:'张三',
        }
        for (const file of files) {
            formData.append('file',file,file.name);//三个参数分别对应key value和文件名
        }
        formData.append('fields',JSON.stringify(fields));//文件上传时携带的参数
        $.ajax({
            type: 'post',
            url: 'http://172.31.14.33:3000/formdata',
            data: formData,
            processData: false,//防止数据被转成字符串
            contentType:false,
            success: function (res) {
                console.log(res);
            },
            error: function (err) {
                console.log(err);
            }
        })
    })
</script>
</html>
后端代码
const http = require('http')
const port = 3000
const fs = require('fs')
const formidable = require('formidable')
http.createServer((req, res) => {
    //跨域处理
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
    res.setHeader('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS');
    //预检请求的有效期,单位秒,在此期间不会再次发出预检请求
    res.setHeader('Access-Control-Max-Age', 36000)
    if (req.method === 'OPTIONS') {
        res.writeHead(200);
        res.end();
    }
    let path = req.url.split('?')[0];
    if (path === '/formdata') {
        let form = formidable.IncomingForm();
        form.encoding = 'utf-8';//设置表单域编码
        form.maxFileSize = 10 * 1024 * 1024;//设置文件的大小限制,默认设置是200M
        form.maxFieldsSize = 2 * 1024 * 1024;//限制所有存储表单字段域的大小(除去file字段,并不是限制文件的大小),如果超出,则会触发error事件,默认为2M
        form.maxFields = 1000;//设置可以转换多少查询字符串,默认为1000
        form.uploadDir = __dirname + '/files';//设置文件零时存放的目录(需要建一个文件夹)
        form.keepExtensions = true;//是否保存文件原有的文件扩展名
        form.hash = false;//设置上传文件的检验码,可以有两个取值'sha1' or 'md5'.
        form.multiples = true;//开启该功能,当调用form.parse()方法时,
        form.on('progress', function (bytesReceived, bytesExpected) {
            console.log((bytesReceived / bytesExpected)*100 + '%');
        });
        form.on('error', err => {
            console.log(err);
        })
        // 多文件时回调函数的files参数的file属性是一个数组,数组每一个成员是一个File对象,此功能需要 html5中multiple特性支持。
        form.parse(req, function (err, fields, files) {
            let info = JSON.parse(fields.fields);
            console.log(info);//{ id: '007', username: '张三' }
            console.log(files);
            if (files.file.length) {
                //多文件
                let count = 0;
                for (const file of files.file) {
                    //更改文件名
                    fs.rename(file.path, './files/' + file.name + '', err => {
                        if (err) {
                            console.log(err);
                            res.writeHead(500);
                            res.end(JSON.stringify({files, fields}));
                        }
                        count++;
                        if (count === files.file.length) {
                            res.writeHead(200);
                            res.end(JSON.stringify({files, fields}));
                        }
                    })
                }
            } else {
                //单文件
                let file = files.file;
                //更改文件名
                fs.rename(file.path, './files/' + file.name + '', err => {
                    if (err) {
                        console.log(err);
                        res.writeHead(500);
                        res.end(JSON.stringify({files, fields}));
                    }
                    res.writeHead(200);
                    res.end(JSON.stringify({files, fields}));
                })
            }
        })
    }
}).listen(port)

2.使用FileReader进行上传

由于某些老旧的浏览器对FileReaderAPI的兼容性问题,不建议在对兼容性要求高的项目里使用。

2.1使用FileReader上传小文件

直接将文件转成base64字符串发给后端,后端再将base64字符串中代表文件数据的字符串转成Buffer,然后再写成文件。

前端代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>转base64上传文件</title>
</head>
<body>
<input type="file" id="file">
</body>
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<script>
    let $file=document.getElementById('file');
    $file.addEventListener('change',function (e) {
        let file=e.target.files[0];
        let filename=file.name;
        console.log(file);
        let fileReader=new FileReader();
        fileReader.onload=function(e){
            let base64=e.target.result;//base64字符串
            $.ajax({
                type:'post',
                url:'http://172.31.14.33:3000/base64File',
                data:JSON.stringify({
                    filename:filename,
                    base64:base64,
                }),
                success:function (res) {
                    console.log(res);
                },
                error:function (err) {
                    console.log(err);
                }
            })
        }
        fileReader.readAsDataURL(file);
    })
</script>
</html>
后端代码
const http = require('http')
const port = 3000
const fs = require('fs')
http.createServer((req, res) => {
    //跨域处理
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
    res.setHeader('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS');
    res.setHeader('Access-Control-Max-Age', 36000)
    if (req.method === 'OPTIONS') {
        res.writeHead(200);
        res.end();
    }
    let path=req.url.split('?')[0];
    if(path==='/base64File'&&req.method==='POST'){
        let str='';
        req.on('data',function (chunk) {
            str+=chunk;
        })
        req.on('end',function () {
            let data=JSON.parse(str);
            let buffer=Buffer.from(data.base64.split(',')[1],'base64');//base64字符串转Buffer
            fs.writeFile('./'+data.filename,buffer,function (err) {
                if(err){
                    console.log(err);
                    res.writeHead(500);
                    res.end('error');
                }else {
                    res.writeHead(200);
                    res.end('success');
                }
            })
        })
    }
}).listen(port)

2.2使用FileReader上传大文件

当文件较大时,base64字符串会长得难以想象,这会带来一些意料之外的问题。这时候可以将大文件转成ArrayBuffer进行传输,后端再将ArrayBuffer转成Buffer写入文件。

2.2.1不携额外参数时

前端代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>转ArrayBuffer上传文件,不带其他参数</title>
</head>
<body>
<input type="file" id="file">
</body>
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<script>
    let $file = document.getElementById('file');
    $file.addEventListener('change', function (e) {
        let file = e.target.files[0];
        let fileReader = new FileReader();
        fileReader.onload = function (e) {
            let arraybuffer = e.target.result;//ArrayBuffer
            $.ajax({
                type: 'post',
                url: 'http://172.31.14.33:3000/arraybuffer_no_options',
                data: arraybuffer,
                headers:{
                    'Content-Type':'application/octet-stream',//也可以不设置
                },
                processData:false,//防止数据被转成字符串
                success: function (res) {
                    console.log(res);
                },
                error: function (err) {
                    console.log(err);
                }
            })
        }
        fileReader.readAsArrayBuffer(file);
    })
</script>
</html>

后端代码

const http = require('http')
const port = 3000
const fs = require('fs')
http.createServer((req, res) => {
    //跨域处理
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
    res.setHeader('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS');
    //预检请求的有效期,单位秒,在此期间不会再次发出预检请求
    res.setHeader('Access-Control-Max-Age', 36000)
    if (req.method === 'OPTIONS') {
        res.writeHead(200);
        res.end();
    }
    let path = req.url.split('?')[0];
    if (path === '/arraybuffer_no_options' && req.method === 'POST') {
        let arr = [];
        req.on('data', function (chunk) {
            arr.push(chunk);
        })
        req.on('end', function () {
            let buffer = Buffer.concat(arr);//转成Buffer
            console.log(buffer);
            fs.writeFile('./test.png', buffer, function (err) {
                if (err) {
                    console.log(err);
                    res.writeHead(500);
                    res.end('error');
                } else {
                    res.writeHead(200);
                    res.end('success');
                }
            })
        })
    }
}).listen(port)
2.2.2需要携带额外参数时

可以在前端将ArrayBuffer转成Array,后端接收后再将Array转成Buffer即可。

前端代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>转ArrayBuffer上传文件,带其他参数</title>
</head>
<body>
<input type="file" id="file">
</body>
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<script>
    let $file = document.getElementById('file');
    $file.addEventListener('change', function (e) {
        let file = e.target.files[0];
        let fileReader = new FileReader();
        fileReader.onload = function (e) {
            let arraybuffer = e.target.result;//ArrayBuffer
            //ArrayBuffer转Array
            let array = Array.prototype.slice.call(new Uint8Array(arraybuffer));
            $.ajax({
                type: 'post',
                url: 'http://172.31.14.33:3000/arraybuffer_has_options',
                data: JSON.stringify({
                    filename:file.name,
                    u8arr:array,
                }),
                success: function (res) {
                    console.log(res);
                },
                error: function (err) {
                    console.log(err);
                }
            })
        }
        fileReader.readAsArrayBuffer(file);
    })
</script>
</html>

后端代码

const http = require('http')
const port = 3000
const fs = require('fs')
http.createServer((req, res) => {
    //跨域处理
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
    //设置允许的请求方法
    res.setHeader('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS');
    //预检请求的有效期,单位秒,在此期间不会再次发出预检请求
    res.setHeader('Access-Control-Max-Age', 36000);
    if (req.method === 'OPTIONS') {
        res.writeHead(200);
        res.end();
    }
    let path=req.url.split('?')[0];
    if(path==='/arraybuffer_has_options'&&req.method==='POST'){
        let msg='';
        req.on('data',function (chunk) {
            msg+=chunk;
        })
        req.on('end',function () {
            let data=JSON.parse(msg);
            let buffer = new Buffer.from(data.u8arr);//Array转Buffer
            fs.writeFile('./'+data.filename,buffer,function (err) {
                if(err){
                    console.log(err);
                    res.writeHead(500);
                    res.end('error');
                }else {
                    res.writeHead(200);
                    res.end('success');
                }
            })
        })
    }
}).listen(port)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容

  • 一丶前端文件上传方式 前端网页文件上传一般使用 来实现。 在 HTML 文档中: ` `标签每出现一次,一个...
    毒行影客阅读 7,126评论 0 13
  • 前端无法像原生APP一样直接操作本地文件,否则的话打开个网页就能把用户电脑上的文件偷光了,所以需要通过用户触发,用...
    雷波_viho阅读 822评论 0 1
  • 前端无法像原生APP一样直接操作本地文件,否则的话打开个网页就能把用户电脑上的文件偷光了,所以需要通过用户触发,用...
    孙悟空SUN阅读 406评论 0 0
  • 前言: 之前我总以为浏览器上传文件就一种方式——表单(表单包括 HTML 的 Form 表单,和虚拟表单 Form...
    CondorHero阅读 2,713评论 0 2
  • 我们在平时工作中常常会遇到文件上传的需求。但许久以来大多数人都是直接使用一些框架自带的组件去实现,对于一些复杂的上...
    SophieRabbit阅读 3,205评论 0 1