应用中间件
express提供中间件,对请求中不同格式的数据进行解析,以便于拿到数据。
const express = require('express')
const app = express()
// 对格式为JSON的body数据进行解析
app.use(express.json())
// 对格式为 x-www-form-urlencoded的数据进行解析,并且使用Node内置qs模块(extended:true)
// 否则,使用第三方库 qs
app.use(express.urlencoded({extended:true}))
app.post('/home',(req,res,next)=>{
console.log(req.body);
res.end('home post')
})
// 开启监听
app.listen(8090,()=>{
console.log('中间件服务器开启成功~');
})
Note:
- 若框架没有提供这些中间件,则需要自己在app.use中对不同的req.body的数据格式进行解析,然后使用next调用栈中下一个中间件,进行路径和方法的匹配。
- 使用app.use表示无论什么方法和路径,先对 content-type中的数据进行解析,转为对象,赋值给req.body,使得next调用时,后面匹配的中间件能够拿到解析后的数据。
form-data解析:一般用于上传图片文件
使用第三方库 multer
初步使用
npm install multer
const express = require('express')
const multer = require('multer')
const app = express()
const upload = multer()
// 表示 form-data中普通的数据
app.use(upload.any())
app.post('/upload',(req,res,next)=>{
console.log(req.body);
res.end('用户上传成功~~')
})
app.listen(8090,()=>{
console.log('form-data解析服务器开启成功~');
})
-------------------------------------------
form-data解析服务器开启成功~
[Object: null prototype] { name: 'yuxi' }
但是,若要上传图片文件,并且将上传的图片文件放在服务器中某位置:
const express = require('express')
const multer = require('multer')
const app = express()
// 表示将上传的文件指定地址存放,若没有该文件,则创建文件夹
const upload = multer({
dest:'./uploadImg/'
})
app.use(express.json())
app.use(express.urlencoded({extended:true}))
// 表示 form-data中普通数据的处理,不能用于文件的处理
app.use(upload.any())
// 这里使用连续中间件,upload.single('img')表示解析单个文件夹,且key值为 ‘img’,
// 进行处理后,传递给后面的中间件 next ,然后返回结果给客户端
app.post('/upload',upload.single('file'),(req,res,next)=>{
console.log(req.files);
res.end('用户上传成功~~')
})
app.listen(8090,()=>{
console.log('form-data解析服务器开启成功~');
})
Note
-
此时由于使用
app.use(upload.any()),报错Error: Unexpected end of form at Multipart._final (F:\vscode\练习\Node\express\node_modules\busboy\lib\types\multipart.js:588:17) at callFinal (_stream_writable.js:598:10) at processTicksAndRejections (internal/process/task_queues.js:80:21) -
此时我们图片没有后缀,而且不能直接打开,我们给图片添加后缀和重命名
const path = require('path') const express = require('express') const multer = require('multer') const app = express() // 为上传的文件添加后缀和文件名 const storage = multer.diskStorage({ // cb是callback回调函数的缩写 destination:(req,file,cb)=>{ cb(null,'./uploadImg/') }, filename:(req,file,cb)=>{ // 文件名取 时间戳和原文件的后缀名进行组合 cb(null,Date.now()+path.extname(file.originalname)) } }) // 表示将上传的文件指定地址存放,若没有该文件,则创建文件夹 const upload = multer({ // dest:'./uploadImg/' storage, }) app.use(express.json()) app.use(express.urlencoded({extended:true})) // 表示 form-data中普通数据的处理 // app.use(upload.any()) // 这里使用连续中间件,upload.single('img')表示解析单个文件夹,且key值为 ‘img’, // 进行处理后,传递给后面的中间件 next ,然后返回结果给客户端 app.post('/upload',upload.single('file'),(req,res,next)=>{ // console.log(req.body); console.log(req.file); res.end('用户上传成功~~') }) app.listen(8090,()=>{ console.log('form-data解析服务器开启成功~'); })此时,将
app.use(upload.any())进行注释后,此时图片文件上传没有问题,不会报错。同时也能正常进行图片解析:
错误分析
1.只使用 app.use(upload.any()),同时上传文本域和图片。
app.use(upload.any())
app.post('/upload',(req,res,next)=>{
// console.log(req.body);
console.log(req.file);
res.end('用户上传成功~~')
})
打印结果:
[Object: null prototype] { name: 'yuxi' }
[
{
fieldname: 'file',
originalname: '微信图片_20220704132002.jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
destination: './uploadImg/',
filename: '1660030167604.jpg',
path: 'uploadImg\1660030167604.jpg',
size: 51988
},
{
fieldname: undefined,
originalname: 'v2-f75d8361b30214ea6dff5a3c2ce952de_r.jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
destination: './uploadImg/',
filename: '1660030167607.jpg',
path: 'uploadImg\1660030167607.jpg',
size: 620080
}
]
事实上可以成功处理数据,并且显示解析后的文件和文本域对象。
Note:
这里使用diskStorage处理文件上传的地址和文件名,其中 destination可以为字符串也可以为函数,但是两者使用上有区别:
// 为上传的文件添加后缀和文件名
const storage = multer.diskStorage({
// cb是callback回调函数的缩写
// destination:(req,file,cb)=>{
// cb(null,'./uploadImg/')
// },
destination:"./uploadImg/",
filename:(req,file,cb)=>{
// 文件名取 时间戳和原文件的后缀名进行组合
cb(null,Date.now()+path.extname(file.originalname))
}
})
- 字符串,将根据字符串,创建文件夹
- 回调函数中,传入的字符串表示文件放入的地址不会创建该文件夹(若没有的话)
查看源码(部分):
var fs = require('fs')
var os = require('os')
var path = require('path')
var crypto = require('crypto')
var mkdirp = require('mkdirp')
function getFilename (req, file, cb) {
crypto.randomBytes(16, function (err, raw) {
cb(err, err ? undefined : raw.toString('hex'))
})
}
function getDestination (req, file, cb) {
// 操作系统默认的临时文件夹
cb(null, os.tmpdir())
}
function DiskStorage (opts) {
// 拿到自定义文件名的函数,若没有,则使用默认
this.getFilename = (opts.filename || getFilename)
if (typeof opts.destination === 'string') {
// 创建文件夹
mkdirp.sync(opts.destination)
this.getDestination = function ($0, $1, cb) { cb(null, opts.destination) }
} else {
// 若没有添加自定义的文件路径,使用默认的
this.getDestination = (opts.destination || getDestination)
}
}
但是无论如何,在使用字符串和回调函数后,最终都会将传入的地址传递或赋值给this.getDestination,最后this.storage中有自定义的路径存放的地址和文件名(当然,也有diskStorage原型上的函数,会在后续解析时使用到)。
2.只使用 upload.single('file'),同时上传文本域和图片
- 多张图片:报错
因为传入文件时,会获取文件名的数量,若有多张图片,则报错
源码部分:(function wrappedFileFilter部分)
Multer.prototype._makeMiddleware = function (fields, fileStrategy) {
// console.log(fields);
function setup () {
var fileFilter = this.fileFilter
// 为了规定对应文件名的数量,以便后续处理时,通过key值动态处理其数值,使得程序根据不同值进行判断
var filesLeft = Object.create(null)
fields.forEach(function (field) {
if (typeof field.maxCount === 'number') {
filesLeft[field.name] = field.maxCount
} else {
// 没有限制数量,则为无穷,可以处理多个文件
filesLeft[field.name] = Infinity
}
})
function wrappedFileFilter (req, file, cb) {
//打印不同文件名,在filesLeft表中对应的数值
//console.log(filesLeft[file.fieldname]);
// 需要传入文件名
if ((filesLeft[file.fieldname] || 0) <= 0) {
return cb(new MulterError('LIMIT_UNEXPECTED_FILE', file.fieldname))
}
// 限制文件名的数量:一次处理一个文件,若有多个同名,则第二次处理时,其值 `<=0`,此时将会报错
filesLeft[file.fieldname] -= 1
// 过滤文件
fileFilter(req, file, cb)
}
return {
limits: this.limits,
preservePath: this.preservePath,
storage: this.storage,
fileFilter: wrappedFileFilter,
fileStrategy: fileStrategy
}
}
return makeMiddleware(setup.bind(this))
}
----------------------------
{
fieldname: 'file',
originalname: '微信图片_20220704132002.jpg',
encoding: '7bit',
mimetype: 'image/jpeg'
}
{
fieldname: 'file',
originalname: 'v2-f75d8361b30214ea6dff5a3c2ce952de_r.jpg',
encoding: '7bit',
mimetype: 'image/jpeg'
}
filesLeft[file.fieldname] -= 1 一次处理一个文件,若有多个同名,则第二次处理时,其值 <=0,此时将会报错:
源码中查看:
若是多张不同key值的图片,仍然会报错,因为当进行不同于限制名的key值时,在filesLeft[file.fieldname]无法查找到值,则仍然将会报错。
原因:在文件上传之前,就会根据single()中的值,创建对应的一张表,确定数值,后续图片上传时,非key值就为undefined,表中没有。
- 一张图片,正确解析
总结
无论使用upload.any还是upload.single,都可以对数据进行解析,但是后者只能解析对应key值的一张图片。且两者不可同时使用(不代表不能同时写,而是中间件在匹配的时候,文件不能同时匹配两个,如下:use是不限制方法和路径的)
// app.use(upload.any())
// 这里使用连续中间件,upload.single('img')表示解析单个文件夹,且key值为 ‘img’,
// 进行处理后,传递给后面的中间件 next ,然后返回结果给客户端
app.post('/upload',upload.single('file'),(req,res,next)=>{
console.log(req.body);
console.log(req.file);
res.end('用户上传成功~~')
})
但是仍然存在问题,目前并不是很清楚,为什么两者同时使用错误的原因是什么,给出的错误提示是:
Error: Unexpected end of form
at Multipart._final (F:\vscode\练习\Node\express\node_modules\busboy\lib\types\multipart.js:588:17)
at callFinal (_stream_writable.js:598:10)
at processTicksAndRejections (internal/process/task_queues.js:80:21)
再贴一些在github上找到的一些回答,大佬可以解答下吗?
\