如何将Node文件上传接口实现为长尾词?
- 内容介绍
- 相关推荐
本文共计1883个文字,预计阅读时间需要8分钟。
近期项目中采用了如下项目架构:前端 + Node.js + Java。前端负责实现业务逻辑的展示和交互;Node.js 包含维护某些数据和接口开发;Java 负责维护数据。在 Node.js 的接口开发中截断一。
近期的项目里使用了这样一个项目架构: 前端 -> nodejs -> java
- 前端负责实现业务逻辑的展示和交互
- nodejs 包括维护某些数据和接口转发
- java 负责维护剩下的数据
在 nodejs 的接口转发中拦截一部分接口,再对请求的方法进行区分,请求后台数据后,再进行返回。现有的接口中基本只用到了 get 和 post 两种,但是在文件上传的时候遇到了问题。
node 层使用 eggjs ,一般的 post 的请求直接在 ctx.body 就能拿到请求的参数,但是 /upload 的接口就不行,拿到的 body 是 {} ,下面我们来逐步分析。
js 中的文件
web 中的 Blob 、File 和 Formdate
一个 Blob ( Binary Large Object ) 对象表示一个不可变的, 原始数据的类似文件对象。Blob表示的数据不一定是一个JavaScript原生格式。 File 接口基于Blob,继承 Blob 功能并将其扩展为支持用户系统上的文件。
前端上传文件的方式无非就是使用:1、表单自动上传;2、使用 ajax 上传。我们可以使用以下代码创建一个 Form,并打印出 file
<form method="POST" id="uploadForm" enctype="multipart/form-data"> <input type="file" id="file" name="file" /> </form> <button id="submit">submit</button> <script src="img.558idc.com/uploadfile/allimg/210405/224S0H15-0.jpg"></script> <script> $("#submit").click(function() { console.log($("#file")[0].files[0]) }); </script>
从 F12 中可以看出 File 原型链上是 Blob。
简单地说 Blob 可以理解为 Web 中的二进制文件。 而 File 是基于 Blob 实现的一个类,新增了关于文件有关的一些信息。
FormData对象的作用就类似于 Jq 的 serialize() 方法,不过 FormData 是浏览器原生的,且支持二进制文件。 ajax 通过 FormData 这个对象发送表单请求,无论是原生的 XMLHttpRequest 、jq 的 ajax 方法、 axios 都是在 data 里直接指定上传 formData 类型的数据,fetch api 是在 body 里上传。
forData 数据有两种方式生成,如下 formData 和 formData2 的区别,而 formData2 可以通过传入一个 element 的方式进行初始化,初始化之后依然可以调用 formData 的 append 方法。
<!DOCTYPE html> <html> <form method="POST" id="uploadForm" name="uploadFormName" enctype="multipart/form-data"> <input type="file" id="fileImag" name="configFile" /> </form> <div id="show"></div> <button id="submit">submit</button> <script src="img.558idc.com/uploadfile/allimg/210405/224S0H15-0.jpg"></script> </html> <script> $("#submit").click(function() { const file = $("#fileImag")[0].files[0]; const formData = new FormData(); formData.append("fileImag", file); console.log(formData.getAll("fileImag")); const formData2 = new FormData(document.querySelector("#uploadForm")); // const formData2 = new FormData(document.forms.namedItem("uploadFormName");); console.log(formData2.get("configFile")); }); </script>
console.log() 无法直接打印出 formData 的数据,可以使用 get(key) 或者 getAll(key)
- 如果是使用 new FormData(element) 的创建方式,上面 key 为 <input /> 上的 name 字段。
- 如果是使用 append 添加的数据,get/getAll 时 key 为 append 所指定的 key。
node 中的 Buffer 、 Stream 、fs
Buffer 和 Stream 是 node 为了让 js 在后端拥有处理二进制文件而出现的数据结构。
通过名字可以看出 buffer 是缓存的意思。存储在内存当中,所以大小有限,buffer 是 C++ 层面分配的,所得内存不在 V8 内。
stream 可以用水流形容数据的流动,在文件 I/O、网络 I/O中数据的传输都可以称之为流。
通过两个 fs 的 api 看出,readFile 不指定字符编码默认返回 buffer 类型,而 createReadStream 将文件转化为一个 stream , nodejs 中的 stream 通过 data 事件能够一点一点地拿到文件内容,直到 end 事件响应为止。
const fs = require("fs"); fs.readFile("./package.json", function(err, buffer) { if (err) throw err; console.log("buffer", buffer); }); function readLines(input, func) { var remaining = ""; input.on("data", function(data) { remaining += data; var index = remaining.indexOf("\n"); var last = 0; while (index > -1) { var line = remaining.substring(last, index); last = index + 1; func(line); index = remaining.indexOf("\n", last); } remaining = remaining.substring(last); }); input.on("end", function() { if (remaining.length > 0) { func(remaining); } }); } function func(data) { console.log("Line: " + data); } var input = fs.createReadStream("./package.json"); input.setEncoding("binary"); readLines(input, func);
fs.readFile() 函数会缓冲整个文件。 为了最小化内存成本,尽可能通过 fs.createReadStream() 进行流式传输。
使用 nodejs 创建 uoload api
localhost:3000
Referer: localhost:3000/upload
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36
------WebKitFormBoundaryoqBx9oYBhx4SF1YQ
Content-Disposition: form-data; name="upload"
localhost:3000
------WebKitFormBoundaryoMwe4OxVN0Iuf1S4
Content-Disposition: form-data; name="upload"; filename="IMG_9429.JPG"
Content-Type: image/jpeg
����JFIF��C // 文件的二进制数据
……
--------WebKitFormBoundaryoMwe4OxVN0Iuf1S4--
根据 WebKitFormBoundaryoMwe4OxVN0Iuf1S4 可以分割出文件的二进制内容 原生 node 使用原生的 node 写一个文件上传的 demo
const localhost:%s/users -d "user=admin"', port);
console.log('curl -i localhost:%s/ -F "source=@/path/to/file.png"', port);
我们来看一下 koa-body 的实现
const forms = require('formidable');
function requestbody(opts) {
opts = opts || {};
...
opts.multipart = 'multipart' in opts ? opts.multipart : false;
opts.formidable = 'formidable' in opts ? opts.formidable : {};
...
// @todo: next major version, opts.strict support should be removed
if (opts.strict && opts.parsedMethods) {
throw new Error('Cannot use strict and parsedMethods options at the same time.')
}
if ('strict' in opts) {
console.warn('DEPRECATED: opts.strict has been deprecated in favor of opts.parsedMethods.')
if (opts.strict) {
opts.parsedMethods = ['POST', 'PUT', 'PATCH']
} else {
opts.parsedMethods = ['POST', 'PUT', 'PATCH', 'GET', 'HEAD', 'DELETE']
}
}
opts.parsedMethods = 'parsedMethods' in opts ? opts.parsedMethods : ['POST', 'PUT', 'PATCH']
opts.parsedMethods = opts.parsedMethods.map(function (method) { return method.toUpperCase() })
return function (ctx, next) {
var bodyPromise;
// only parse the body on specifically chosen methods
if (opts.parsedMethods.includes(ctx.method.toUpperCase())) {
try {
if (opts.json && ctx.is(jsonTypes)) {
bodyPromise = buddy.json(ctx, {
encoding: opts.encoding,
limit: opts.jsonLimit,
strict: opts.jsonStrict,
returnRawBody: opts.includeUnparsed
});
} else if (opts.multipart && ctx.is('multipart')) {
bodyPromise = formy(ctx, opts.formidable);
}
} catch (parsingError) {
if (typeof opts.onError === 'function') {
opts.onError(parsingError, ctx);
} else {
throw parsingError;
}
}
}
bodyPromise = bodyPromise || Promise.resolve({});
/**
* Check if multipart handling is enabled and that this is a multipart request
*
* @param {Object} ctx
* @param {Object} opts
* @return {Boolean} true if request is multipart and being treated as so
* @api private
*/
function isMultiPart(ctx, opts) {
return opts.multipart && ctx.is('multipart');
}
/**
* Donable formidable
*
* @param {Stream} ctx
* @param {Object} opts
* @return {Promise}
* @api private
*/
function formy(ctx, opts) {
return new Promise(function (resolve, reject) {
var fields = {};
var files = {};
var form = new forms.IncomingForm(opts);
form.on('end', function () {
return resolve({
fields: fields,
files: files
});
}).on('error', function (err) {
return reject(err);
}).on('field', function (field, value) {
if (fields[field]) {
if (Array.isArray(fields[field])) {
fields[field].push(value);
} else {
fields[field] = [fields[field], value];
}
} else {
fields[field] = value;
}
}).on('file', function (field, file) {
if (files[field]) {
if (Array.isArray(files[field])) {
files[field].push(file);
} else {
files[field] = [files[field], file];
}
} else {
files[field] = file;
}
});
if (opts.onFileBegin) {
form.on('fileBegin', opts.onFileBegin);
}
form.parse(ctx.req);
});
}
代码中删除了影响有关文件上传的相关逻辑
formy 函数处理了 www.jb51.net/article/170637.htm
www.npmjs.com/package/formidable
github.com/dlau/koa-body
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持易盾网络。
本文共计1883个文字,预计阅读时间需要8分钟。
近期项目中采用了如下项目架构:前端 + Node.js + Java。前端负责实现业务逻辑的展示和交互;Node.js 包含维护某些数据和接口开发;Java 负责维护数据。在 Node.js 的接口开发中截断一。
近期的项目里使用了这样一个项目架构: 前端 -> nodejs -> java
- 前端负责实现业务逻辑的展示和交互
- nodejs 包括维护某些数据和接口转发
- java 负责维护剩下的数据
在 nodejs 的接口转发中拦截一部分接口,再对请求的方法进行区分,请求后台数据后,再进行返回。现有的接口中基本只用到了 get 和 post 两种,但是在文件上传的时候遇到了问题。
node 层使用 eggjs ,一般的 post 的请求直接在 ctx.body 就能拿到请求的参数,但是 /upload 的接口就不行,拿到的 body 是 {} ,下面我们来逐步分析。
js 中的文件
web 中的 Blob 、File 和 Formdate
一个 Blob ( Binary Large Object ) 对象表示一个不可变的, 原始数据的类似文件对象。Blob表示的数据不一定是一个JavaScript原生格式。 File 接口基于Blob,继承 Blob 功能并将其扩展为支持用户系统上的文件。
前端上传文件的方式无非就是使用:1、表单自动上传;2、使用 ajax 上传。我们可以使用以下代码创建一个 Form,并打印出 file
<form method="POST" id="uploadForm" enctype="multipart/form-data"> <input type="file" id="file" name="file" /> </form> <button id="submit">submit</button> <script src="img.558idc.com/uploadfile/allimg/210405/224S0H15-0.jpg"></script> <script> $("#submit").click(function() { console.log($("#file")[0].files[0]) }); </script>
从 F12 中可以看出 File 原型链上是 Blob。
简单地说 Blob 可以理解为 Web 中的二进制文件。 而 File 是基于 Blob 实现的一个类,新增了关于文件有关的一些信息。
FormData对象的作用就类似于 Jq 的 serialize() 方法,不过 FormData 是浏览器原生的,且支持二进制文件。 ajax 通过 FormData 这个对象发送表单请求,无论是原生的 XMLHttpRequest 、jq 的 ajax 方法、 axios 都是在 data 里直接指定上传 formData 类型的数据,fetch api 是在 body 里上传。
forData 数据有两种方式生成,如下 formData 和 formData2 的区别,而 formData2 可以通过传入一个 element 的方式进行初始化,初始化之后依然可以调用 formData 的 append 方法。
<!DOCTYPE html> <html> <form method="POST" id="uploadForm" name="uploadFormName" enctype="multipart/form-data"> <input type="file" id="fileImag" name="configFile" /> </form> <div id="show"></div> <button id="submit">submit</button> <script src="img.558idc.com/uploadfile/allimg/210405/224S0H15-0.jpg"></script> </html> <script> $("#submit").click(function() { const file = $("#fileImag")[0].files[0]; const formData = new FormData(); formData.append("fileImag", file); console.log(formData.getAll("fileImag")); const formData2 = new FormData(document.querySelector("#uploadForm")); // const formData2 = new FormData(document.forms.namedItem("uploadFormName");); console.log(formData2.get("configFile")); }); </script>
console.log() 无法直接打印出 formData 的数据,可以使用 get(key) 或者 getAll(key)
- 如果是使用 new FormData(element) 的创建方式,上面 key 为 <input /> 上的 name 字段。
- 如果是使用 append 添加的数据,get/getAll 时 key 为 append 所指定的 key。
node 中的 Buffer 、 Stream 、fs
Buffer 和 Stream 是 node 为了让 js 在后端拥有处理二进制文件而出现的数据结构。
通过名字可以看出 buffer 是缓存的意思。存储在内存当中,所以大小有限,buffer 是 C++ 层面分配的,所得内存不在 V8 内。
stream 可以用水流形容数据的流动,在文件 I/O、网络 I/O中数据的传输都可以称之为流。
通过两个 fs 的 api 看出,readFile 不指定字符编码默认返回 buffer 类型,而 createReadStream 将文件转化为一个 stream , nodejs 中的 stream 通过 data 事件能够一点一点地拿到文件内容,直到 end 事件响应为止。
const fs = require("fs"); fs.readFile("./package.json", function(err, buffer) { if (err) throw err; console.log("buffer", buffer); }); function readLines(input, func) { var remaining = ""; input.on("data", function(data) { remaining += data; var index = remaining.indexOf("\n"); var last = 0; while (index > -1) { var line = remaining.substring(last, index); last = index + 1; func(line); index = remaining.indexOf("\n", last); } remaining = remaining.substring(last); }); input.on("end", function() { if (remaining.length > 0) { func(remaining); } }); } function func(data) { console.log("Line: " + data); } var input = fs.createReadStream("./package.json"); input.setEncoding("binary"); readLines(input, func);
fs.readFile() 函数会缓冲整个文件。 为了最小化内存成本,尽可能通过 fs.createReadStream() 进行流式传输。
使用 nodejs 创建 uoload api
localhost:3000
Referer: localhost:3000/upload
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36
------WebKitFormBoundaryoqBx9oYBhx4SF1YQ
Content-Disposition: form-data; name="upload"
localhost:3000
------WebKitFormBoundaryoMwe4OxVN0Iuf1S4
Content-Disposition: form-data; name="upload"; filename="IMG_9429.JPG"
Content-Type: image/jpeg
����JFIF��C // 文件的二进制数据
……
--------WebKitFormBoundaryoMwe4OxVN0Iuf1S4--
根据 WebKitFormBoundaryoMwe4OxVN0Iuf1S4 可以分割出文件的二进制内容 原生 node 使用原生的 node 写一个文件上传的 demo
const localhost:%s/users -d "user=admin"', port);
console.log('curl -i localhost:%s/ -F "source=@/path/to/file.png"', port);
我们来看一下 koa-body 的实现
const forms = require('formidable');
function requestbody(opts) {
opts = opts || {};
...
opts.multipart = 'multipart' in opts ? opts.multipart : false;
opts.formidable = 'formidable' in opts ? opts.formidable : {};
...
// @todo: next major version, opts.strict support should be removed
if (opts.strict && opts.parsedMethods) {
throw new Error('Cannot use strict and parsedMethods options at the same time.')
}
if ('strict' in opts) {
console.warn('DEPRECATED: opts.strict has been deprecated in favor of opts.parsedMethods.')
if (opts.strict) {
opts.parsedMethods = ['POST', 'PUT', 'PATCH']
} else {
opts.parsedMethods = ['POST', 'PUT', 'PATCH', 'GET', 'HEAD', 'DELETE']
}
}
opts.parsedMethods = 'parsedMethods' in opts ? opts.parsedMethods : ['POST', 'PUT', 'PATCH']
opts.parsedMethods = opts.parsedMethods.map(function (method) { return method.toUpperCase() })
return function (ctx, next) {
var bodyPromise;
// only parse the body on specifically chosen methods
if (opts.parsedMethods.includes(ctx.method.toUpperCase())) {
try {
if (opts.json && ctx.is(jsonTypes)) {
bodyPromise = buddy.json(ctx, {
encoding: opts.encoding,
limit: opts.jsonLimit,
strict: opts.jsonStrict,
returnRawBody: opts.includeUnparsed
});
} else if (opts.multipart && ctx.is('multipart')) {
bodyPromise = formy(ctx, opts.formidable);
}
} catch (parsingError) {
if (typeof opts.onError === 'function') {
opts.onError(parsingError, ctx);
} else {
throw parsingError;
}
}
}
bodyPromise = bodyPromise || Promise.resolve({});
/**
* Check if multipart handling is enabled and that this is a multipart request
*
* @param {Object} ctx
* @param {Object} opts
* @return {Boolean} true if request is multipart and being treated as so
* @api private
*/
function isMultiPart(ctx, opts) {
return opts.multipart && ctx.is('multipart');
}
/**
* Donable formidable
*
* @param {Stream} ctx
* @param {Object} opts
* @return {Promise}
* @api private
*/
function formy(ctx, opts) {
return new Promise(function (resolve, reject) {
var fields = {};
var files = {};
var form = new forms.IncomingForm(opts);
form.on('end', function () {
return resolve({
fields: fields,
files: files
});
}).on('error', function (err) {
return reject(err);
}).on('field', function (field, value) {
if (fields[field]) {
if (Array.isArray(fields[field])) {
fields[field].push(value);
} else {
fields[field] = [fields[field], value];
}
} else {
fields[field] = value;
}
}).on('file', function (field, file) {
if (files[field]) {
if (Array.isArray(files[field])) {
files[field].push(file);
} else {
files[field] = [files[field], file];
}
} else {
files[field] = file;
}
});
if (opts.onFileBegin) {
form.on('fileBegin', opts.onFileBegin);
}
form.parse(ctx.req);
});
}
代码中删除了影响有关文件上传的相关逻辑
formy 函数处理了 www.jb51.net/article/170637.htm
www.npmjs.com/package/formidable
github.com/dlau/koa-body
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持易盾网络。

