手机版

节点文件上传界面转发的实现

时间:2021-08-20 来源:互联网 编辑:宝哥软件园 浏览:

在最近的项目中,已经使用了这样的项目架构:前端-nodejs-java

前端负责显示和交互业务逻辑。nodejs包括维护一些数据和接口转发。java负责维护剩余的数据。它截取nodejs接口转发中的一些接口,然后区分请求的方法。请求后台数据后,它返回。现有的接口只使用get和post,但是上传文件时存在问题。

节点层使用Eggjs,一般的post请求可以直接在ctx.body中得到请求的参数,但是/upload的接口不能,得到的body是{}。我们一步一步来分析。

js中的文件

网站中的Blob、文件和表单数据

Blob(二进制大对象)对象表示原始数据的不可变的类似文件的对象。Blob表示的数据不一定是JavaScript原生格式。文件接口基于Blob,继承了Blob函数,并对其进行扩展以支持用户系统上的文件。

前端上传文件的方式无非是用:1。表单会自动上传;2.使用ajax上传。我们可以使用以下代码创建一个表单并打印出一个文件

form方法=' POST ' id=' uploadForm ' enctype=' multipart/form-data '输入类型=' file ' id=' file ' name=' file '/form button id=' submit ' submit/button script src=' http :https://cdn . bootscs.com/jquery/3 . 4 . 1/jquery . min . js '/script script $(' # submit ')。单击(function(){ console . log($(“# file”)[0])。文件[0])});/脚本

从F12可以看出,文件原型链是Blob。

简单来说,Blob可以理解为Web中的二进制文件。File是一个基于Blob的类,它添加了一些关于文件的信息。

FormData对象类似于Jq的serialize()方法,但是FormData是浏览器的原生对象,支持二进制文件。Ajax通过formData发送表单请求。无论是原生的XMLHttpRequest,还是jq的ajax方法,axios都直接指定在Data中上传FormData类型的数据,在body中获取api上传。

数据的生成有两种方式,比如formData和formData2的区别。formData2可以通过传入一个元素进行初始化,初始化后仍然可以调用formData的append方法。

!DOCTYPE htmlhtml表单方法=' POST ' id=' uploadForm ' name=' uploadFormName ' enctype=' multipart/form-data '输入类型=' file ' id=' fileImag ' name=' config file '/form div id=' show '/div button id=' submit ' submit/button script src=' http :https://cdn . bootcss.com/jquery/3 . 4 . 1/jquery . min . js '/script/html script $(单击(function(){ const file=$(' # fileImag ')[0]。文件[0];const form data=new form data();formData.append('fileImag ',文件);console . log(formdata . getall(' file imag '));const FormData 2=new FormData(document . queryselector(' # uploadForm '));//const form data 2=new form data(document . forms . namedetm(' uploadFormName '););console . log(formdata 2 . get(' config file '));});/scriptconsole.log()无法直接打印出formData的数据,所以可以使用get(key)或getAll(key)

在创建新的FormData(元素)的情况下,上面的键是input/上的名称字段。在添加了追加的数据的情况下,get/getAll中的键是追加指定的键。节点中的缓冲区、流、文件系统

缓冲区和流是节点看起来让js必须在后端处理二进制文件的数据结构。

通过名称,我们可以看到缓冲区意味着缓存。它存储在内存中,因此大小有限。缓冲区在C级分配,获得的内存不在V8中。

流可以用来描述数据流,文件I/O和网络I/O中数据的传输可以称为流。

从两个fs的API可以看出,readFile默认不指定字符编码,返回缓冲区类型,而createReadStream将文件转换为流,nodejs中的流可以通过数据事件一点一点获取文件内容,直到end事件响应。

const fs=require(' fs ');fs.readFile(' ./package.json ',函数(err,buffer){ if(err)throw err;console.log('buffer ',buffer);});函数读取线(输入,func){ var restrict=' ';input.on('data ',function(data){ remain=data;var索引=受限。index of(' \ n ');var last=0;而(index-1){ var line=retaining。子串(最后一个,索引);最后=索引1;func(线条);索引=受限。indexof(' \ n,last);}剩余=剩余。子串(最后一个);});input.on('end ',function(){ if(retrieve。长度0){ func(retrieve));} });} func func(数据){ console.log('Line: '数据);}var input=fs.createReadStream(' ./package。JSON’);输入。setencoding(' binary ');读取线(输入,func);fs.readFile()函数会缓冲整个文件。为了最小化内存成本,尽可能通过fs.createReadStream()进行流式传输。

使用开发创建uoload api

超文本传送协议(超文本传输协议的缩写)协议中的文件上传

在超文本传送协议(超文本传输协议的缩写)的请求头中内容类型是多部分/表单数据时,请求的内容如下:

POST/HTTP/1.1Content-Type:多部分/表单-数据;边界=-webkitformboundaryomwe4 xvn 0 iuf1s 4 rigin : http://localhost :3000 referre : http://localhost :3000/uploadSec-Fetch-mode :导航-Fetch-user :一升级-不安全-请求: 1用户-代理: Mozilla/5.0(Macintosh;英特尔MAC OS X 10 _ 14 _ 5)applebwebkit/537.36(KHTML,像壁虎)Chrome/76。0 .3809 .132 Safari/537.36-webkitformboundryoqbx 9 oybhx4sf 1 yqcontent-disposition :表单-数据;name=' upload ' http://localhost :3000-webkitformboundaryomwe 4 xvn 0 iuf1s 4内容-处置:表单-数据;名称='上传;文件名='IMG_9429 .使用jpeg文件交换格式存储的编码图像文件扩展名的内容-类型:图像/jpeg、JIF、C /文件的二进制数据…- webkitformboundaryomwe4 xvn 0 iuf1s 4-根据webkitformboundaryomwe4 xvn 0 iuf1s 4可以分割出文件的二进制内容

原生结节

使用原生的结节写一个文件上传的演示

const http=require(' http ');const fs=require(' fs ');const util=require(' util ');const查询字符串=require('查询字符串');//用超文本传送协议(超文本传输协议的缩写)模块创建一个超文本传送协议(超文本传输协议的缩写)服务端http .createServer(函数(请求,RES){ if(请求。URL=='/upload '请求。方法。tolowercase()==' get '){//显示一个用于文件上传的form res.writeHead(200,{ ' content-type ' : ' text/html ' });RES . end(' form action='/Upload ' enctype=' multipart/form-data '方法=' post ' ' '输入类型=' file '名称=' Upload ' multiple=' multiple '/输入类型=' submit '值=' Upload '/form ');} else if(req。URL=='/upload '请求。方法。tolowercase()==' post '){ if(req。标题['内容类型'].索引Of('多部分/表单数据')!==-1) parseFile(req,RES);} else { RES . end(' pelease upload img ');} }) .听(3000);函数解析文件。setencoding(' binary ');让身体=' ';//文件数据让fileName=//文件名//边界字符串-webkitformboundaryomwe4 xvn 0 iuf1s 4 const boundary=req。标题['内容类型'].拆分(';')[1] .替换(' boundary=',' ');req.on('data ',function(chunk){ body=chunk;});req.on('end ',function(){ const file=查询字符串。解析(正文,' \r\n ',' : ');//只处理图片文件;如果(文件['内容类型']。indexOf('image ')!==-1) { //获取文件名var fileInfo=文件['内容-处置']。拆分(';');用于(文件信息中的值){ if(文件信息[值])。indexOf('filename=')!=-1) {文件名=文件信息[值]。子字符串(10,文件信息[值]。长度-1);if (fileName.indexOf('\\ ')!=-1){ fileName=fileName。子字符串(fileName。的最后一个索引(' \ \ ')1);} console.log('文件名: ' FIlename);} } //获取图片类型(如:图像/gif或image/png))常量整数值=身体。tostring();const Content type regex=/Content-type : image \/.*/;内容类型=文件['内容类型']。子串(1);//获取文件二进制数据开始位置,即内容类型的结尾常量上限=英语数据。(内容类型)内容类型的索引。长度;const shorterData=完整的数据。子串(上边界);//替换开始位置的空格const binaryDataAlmost几乎=shorterData .replace(/^\s\s*/,"。替换(/\s\s*$/,' ');//去除数据末尾的额外数据,即: '-'边界'-'常量二进制数据=binaryData几乎。子字符串(0,binarydata几乎。indexof('-' boundary '-');//console.log('binaryData ',二进制数据);常量缓冲区数据=新缓冲区。from(二进制数据,‘binary’);console.log('bufferData ',缓冲数据);//fs.writeFile(fileName,binaryData,' binary ',function(err){//RES . end(' success ');//});fs.writeFile(fileName,bufferData,function(err){ RES . end(' success ');});} else { RES . end(' reupload ');} });}通过req.setEncoding(')二进制');拿到图片的二进制数据。可以通过以下两种方式处理二进制数据,写入文件。

fs.writeFile(fileName,binaryData,' binary ',函数(错误){ RES . end('成功');});fs.writeFile(fileName,bufferData,function(err){ RES . end(' success ');});寇阿相思树

在寇阿相思树中使用寇阿相思树体可以通过ctx.request.files拿到上传的文件对象。下面是例子。

使用"严格";const Koa=require(' Koa ');const app=new Koa();const router=require(' KOA-router ')();const koaBody=require('./index ')({ multipart : true });router.post('/users ',koaBody,(CTX)={ console。原木(CTX。请求。身体);//=POST正文CTX。body=JSON。斯特林菲(CTX)。请求。body,null,2);});router.get('/',(ctx)={ ctx.set('Content-Type ',' text/html ');ctx.body=`!doctype htmlhtml正文表单操作='/' enctype=' multipart/form-data '方法='post '输入类型='text '名称='username '占位符=“用户名”br输入类型='text '名称='title '占位符=“胶片平铺”br输入类型='文件'名称='上传'多个='多个' br按钮类型=' submit '上传/按钮/正文/html `;});router.post('/'),koaBody,(CTX)={ console。日志('字段s 3360 ',CTX。请求。身体);//={username: ''} -如果是空的console.log('files: ',CTX。请求。文件);/*={ uploads 3360[{ ' size ' : 748831,' path ' : '/tmp/f 7777 b 4269 bf6 e 64518 f 96248537 c 0 ab。png ',' name': 'some-image.png ',' type': 'image/png ',' mtime ' : ' 2014-06-17t 1:80808 })app。使用(路由器。routes());常量端口=进程。环境。端口| | 3333;app.listen(端口);console.log('带有koa-body解析器的寇阿相思树服务器开始监听端口%s,端口);控制台。日志(' curl-I http://localhost :% s/users-d ' user=admin ' ',端口);控制台。日志(' curl-I http://localhost :% s/-F '[电子邮件保护]/路径/to/file。png ' ',端口);我们来看一下寇阿相思树体的实现

const forms=require('令人生畏');函数请求体(opts){ opts=opts | | { };opts.multipart='multipart '在选择中?opts.multipart : falseopts。令人生畏='令人生畏'在opts?选择。模拟: { };//@todo:下一个主要版本严格的支持应该被删除,如果(opts。严格的选择。parsed methods){ 0抛出新错误('不能同时使用严格的和parsemethods选项)} if(opts中的“严格”){ console。warn('已弃用:选项。严格的已被弃用,取而代之的是选择。“解析方法”)if(opts。严格){ opts。parsedMethods=[' POST ',' PUT ',' PATCH ',' GET ',' HEAD ',' DELETE ']} }选项。opts中的parsedMethods=' parsemethods '?opts.parsedMethods : ['POST ',' PUT ',' PATCH ']opts。parsedmethods=opts。解析方法。map(函数(方法){ return method。touppercase()})返回函数(CTX,下一个){ var body promise//仅在特别选择的方法if(opts。解析方法。包括(CTX。方法。touppercase()){ try { if(opts。JSON CTX。is(JSON types)){ body promise=buddy。JSON(CTX,{ encoding: opts.encoding,limit: opts.jsonLimit,strict: opts.jsonStrict,returnrawbody : opts。includeunanalyzed })上解析正文;} else if(opts。多部分CTX。is('多部分'){ body promise=formy(CTX,opts。管理);} } catch(parsingError){ if(opts的类型。onerror==' function '){ opts。one rror(CTX parsingError);} else { throw parsingError } } } Body Promise=Body Promise | | Promise。解析({ });/** *检查是否启用了多部分处理,并且这是一个多部分请求* * @参数{对象} CTX * @参数{对象}选择* @如果请求是多部分请求并且被视为多部分请求,则返回{布尔} true * @ API private */函数是多部分的(CTX,opts){ return opts。多部分CTX。'是('多部分');}/** *多纳布尔强大* * @ param { Stream } CTX * @ param { Object } opts * @ return { Promise } * @ API private */function formy(CTX,opts){ return new Promise(function(解析,拒绝){ var field={ };定义变量文件={ };定义变量表单=新表单传入表单(opts);form.on('end ',function(){ return resolve({ field s : field,files : files });}).开启('错误',函数(err){退货拒绝(err);}).on('field ',function (field,value){ if(field[field]){ if(array。isarray(field[field])} { field[field]} .推送(值);} else { field[field]=[field[field],value];} } else { field[field]=value;} }).on('file ',function (field,file){ if(files[field]){ if(array。isarray(文件[字段]){文件[字段]).推送(文件);} else { files[field]=[files[field],file];} } else { files[field]=file;} });if(opts。onfilebegin){ form。on(' fileBegin ',opts。onfile begin);}表单。解析(CTX。请求);});}代码中删除了影响有关文件上传的相关逻辑

首先几部分的为真实的是开启文件上传的关键。然后formy函数处理了超文本传送协议(超文本传输协议的缩写)解析和保存的一系列过程,最终将文件抛出进行统一处理。代码中依赖了可怕的这个库,我们其实也可以直接使用这个库对文件进行处理。(上面的原生节点上传只是简单地处理了一下)选择。令人生畏是可怕的的配置可以设置文件大小,保存的文件路径等等艾格斯。

使用eggjs进行文件上传需要现在配置文件中开启

配置。multipart={ mode : ' file ',file size : ' 600 MB ' };然后通过ctx.request.files[0]就能取到文件信息。

文件上传接口的转发

一千个观众眼中有一千个哈姆雷特,通过以上知识点的梳理,我相信你也有了自己得想法。在这里说一下我是怎么处理的。在蛋中我使用了请求-承诺去做接口转发,通过查看美国石油学会(美国石油协会)和ctx.request.files[0]拿到的信息,我做了以下处理。

if(method===' POST '){ options . body=request . body;options.json=trueif(URL===uploadeUrl){ delete options . body;options.formData={ //Like输入类型=' text ' name=' name ' name : ' file ',//Like输入类型=' file ' name=' file ' file : { value : fs . createreadstream(CTX . request . files[0])。filepath),options : { filename : CTX . request . files[0]。filename,content type : CTX . get(' content-type ')} } };} } else { options.qs=query}摘要

在http中上传文件的第一步是将内容类型设置为多部分/表单数据的标题。区分web端和节点端的js。处理文件有不同的方法。有些npm模块的自述不是很清楚,可以直接从源代码中读取示例,也可以直接读取源代码。比如上面没有提到的在koa-body中的令人生畏的用法,在他的自述中没有写,所以直接看源代码会发现更多的用法。文中很多知识点略有提及,可以进一步了解与他相关的知识。例如网络上的文件阅读器等。最后,如果文中有错误,请及时指出,有问题可以讨论。参考

https://www.jb51.net/article/170637.htm

https://www.npmjs.com/package/formidable

https://github.com/dlau/koa-body

以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。

版权声明:节点文件上传界面转发的实现是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。