中间件介绍
express支持多种中间件,中间件的作用是在请求到达路由之前,对请求进行一些预处理,或者在路由处理之后,对响应进行一些处理。
express内置了一些中间件,可以通过app.use()来使用这些中间件。
express.static
express.static是express内置的唯一一个中间件,它是用来处理静态资源的,比如图片、css、js等。 我们可以通过以下代码来使用express.static中间件:
app.use(express.static('public'))
为了方便开发,可以另起一个别名,托管目标目录下的静态资源
app.use('/uploads', express.static('./public/upload'))
上述代码中,将public/upload目录下的静态资源托管到/uploads目录下。这样的话,我们在访问/uploads目录下的静态资源时,就不需要加上public/upload前缀了。 例如,我们在public/upload目录下有一张图片1.jpg,那么我们可以通过以下地址来访问这张图片:
http://localhost:3000/uploads/1.jpg
body-parser
body-parser
是一个HTTP请求体解析的中间件,使用这个模块可以解析JSON、Raw、文本、URL-encoded格式的请求体,Express框架中就是使用这个模块做为请求体解析中间件。
const bodyParser = require('body-parser')
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: false}))
在新版本中,express已经内置了body-parser,所以我们不需要再安装这个模块了,直接引入即可。
const express = require('express')
const app = express()
app.use(express.json())
app.use(express.urlencoded({extended: false}))
cookie-parser
cookie-parser
是一个解析Cookie的工具。通过req.cookies可以取到传过来的cookie,并把它们转成对象。
安装
npm install cookie-parser --save
使用
const cookieParser = require('cookie-parser')
app.use(cookieParser())
cors
cors
是一个跨域中间件,它可以解决跨域问题。
安装
npm install cors --save
使用
const cors = require('cors')
app.use(cors())
morgan
morgan
是一个日志中间件,可以记录请求日志。
安装
npm install morgan --save
使用
const morgan = require('morgan')
app.use(morgan('dev'))
上述代码中,使用了dev参数,表示以开发者模式输出日志,所以在控制台中看到的日志信息颜色会很醒目。 例如,我们访问http://localhost:3000/,控制台会输出以下日志信息:
GET / 304 2.304 ms - -
带有路径的日志信息,例如访问http://localhost:3000/api/users,控制台会输出以下日志信息:
GET /api/users 304 1.753 ms - -
multer
multer
是一个node.js中间件,用来处理multipart/form-data类型的表单数据,它主要用于上传文件。在使用之前,需要先安装:
npm install multer --save
在路由中使用multer中间件:
const multer = require('multer')
const upload = multer({dest: __dirname + '/public/upload'})
// 上传文件路由
app.post('/api/upload', upload.single('file'), (req, res) => {
// req.file 是 `file` 文件的信息
// req.body 将具有文本域数据,如果存在的话
res.send(req.file)
})
上述multer({dest: __dirname + '/public/upload'})
的作用是设置文件上传后保存的路径,如果不设置,则保存在系统的临时目录下。 upload.single('file')
表示上传单个文件,file是上传字段,上传多个文件的时候,需要更改为upload.array('file',maxCount)
,maxCount是最大上传数量,如果不设置,则不限制数量。
express-jwt
express-jwt
是用来验证JWT的,它会根据secret自动的去验证token的合法性,并将token中的数据解析出来保存到req.user中,因此我们可以通过req.user获取到用户的信息。
安装
npm install express-jwt --save
使用
const expressJwt = require('express-jwt')
// 引入加密秘钥
const {jwtSecretKey} = require("./index.md/index.md")
// 配置解析token的中间件
app.use(expressJWT({
secret: jwtSecretKey,
}).unless({path: [/^\/api\/admin/]}))
其中unless的作用是排除掉/api/admin开头的接口,其他的接口都需要验证token。
JWT
JWT是JSON Web Token的缩写,它是一种用于通信双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信任的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。
安装
npm install jsonwebtoken --save
使用
var jwt = require('jsonwebtoken')
可以对其部分功能,如解析和验证方法进行封装
const jwt = require('jsonwebtoken')
// 签名
exports.sign = promisify(jwt.sign)
// 验证
exports.verify = promisify(jwt.verify)
// 不验证直接解析
// exports.decode = jwt.decode()
exports.decode = promisify(jwt.decode)
在使用时,可以直接调用封装好的方法
const {sign} = require('./util/jwt')
const token = await sign({id: user.id}, jwtSecretKey, {expiresIn: 60 * 60 * 24})
实际项目中的使用
const jwt = require('../util/jwt')
const {jwtSecretKey} = require('../index.md/index.md')
// 登录
exports.login = async (req, res) => {
const userInfo = req.body
//---定义使用MD5加密
const md5 = crypto.createHash('md5')
//---输出加密的密码
const md5crypto = md5.update(userInfo.password).digest('hex')// 获取结果
// 确认登录密码是否正确
const sql = 'select * from admin where username=?'
let result = await query(sql, userInfo.username, db)
if (result.length > 0) {
const user = {...result[0], password: ''}
const token = 'Bearer ' + await jwt.sign(user, jwtSecretKey, {expiresIn: '72h'})
result[0].password === md5crypto ? res.send({code: 20000, msg: '登陆成功', data: {token}}) : res.send({
code: 40000,
msg: '密码错误,登录失败'
})
} else {
res.send({code: 40000, msg: '用户不存在'})
}
}
其中,token的有效期为72小时,可以根据实际情况进行调整.
token的验证
const {jwtSecretKey} = require('../index.md/index.md')
const {verify} = require('../util/jwt')
verify(token, jwtSecretKey).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
获取用户信息
const userInfo = req.user
joi
joi
是一个对象模式描述语言和验证器,用于验证JavaScript对象的完整性。Joi允许您创建蓝图或模式,以强制执行输入有效性。例如,如果您有一个帖子对象,您可以使用Joi来强制执行标题是字符串,内容是字符串,且最多包含255个字符。
安装
npm install joi --save
使用
const Joi = require('joi')
// 验证规则
const schema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')),
repeat_password: Joi.ref('password'),
email: Joi.string().email({minDomainSegments: 2, tlds: {allow: ['com', 'net']}})
}).with('username', 'birth_year').xor('password', 'access_token').with('password', 'repeat_password')
// 暴露
module.exports = schema
上述代码中,我们定义了一个验证规则,其中
username必须是字符串,且长度在3-30之间
password必须是字符串,且必须符合正则表达式^[a-zA-Z0-9]{3,30}$
repeat_password必须和password相同
email必须是邮箱格式,且域名后缀必须是com或者net
其中with表示username和birth_year必须同时存在
xor表示password和access_token必须同时不存在
with表示password和repeat_password必须同时存在。
with和xor的区别在于,with表示必须同时存在,xor表示必须同时不存在。
如果想要自定义错误信息,可以在每个验证规则后面加上messages
方法
const Joi = require('joi')
// 验证规则
const schema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required().messages({
'string.base': '用户名必须是字符串',
'string.empty': '用户名不能为空',
'string.min': '用户名长度不能小于3',
'string.max': '用户名长度不能大于30',
'any.required': '用户名不能为空'
})
}).with('password', 'repeat_password')
// 或者
const username = Joi.string().alphanum().min(1).max(10).required().error(new Error('用户名不符合验证规则'))
const schema = Joi.object({
username
})
// 暴露
module.exports = schema
具体使用方式可以参考官方文档
validate
validate
是一个用于验证的中间件,它可以将验证规则和验证的数据传入,然后进行验证。
手动封装一个validate中间件
// 在middleware文件夹下新建validate.js.md
exports.validate = (reg_schema) => {
return async (req, res, next) => {
try {
await reg_schema.validateAsync(req.body);
next()
} catch (err) {
return res.send({
code: 40001,
msg: err.message,
})
}
}
}
在路由中使用
const {validate} = require('../middleware/validate')
const reg_schema = require('../schema/reg_schema')
router.post('/reg', validate(reg_schema), userController.reg)
express-ip
express-ip
是一个用于获取客户端IP的中间件。
安装
npm install express-ip --save
项目中使用的是express-ip
的1.0.4版本
使用
const express = require('express')
const app = express()
const expressIp = require('express-ip')
app.use(expressIp().getIpInfoMiddleware)
// 获取客户端IP
app.get('/', function (req, res) {
console.log(req.ipInfo)
res.send('Hello World')
})
具体使用方式可以参考官方文档
nodemailer
nodemailer
是一个用于发送邮件的库。
安装
npm install nodemailer --save
使用
为了方便使用,我们可以将nodemailer封装成一个模块,然后在项目中使用。
// 在utils文件夹下新建nodeMail.js.md
const nodemailer = require('nodemailer')
let nodeMail = nodemailer.createTransport({
service: 'qq', // 类型qq邮箱
port: 465, // 端口号
secure: true, // true for 465, false for other ports
auth: {
user: '1878656671@qq.com', // 发送方的邮箱,可以选择你自己的qq邮箱
pass: 'xxxxx' // 从官网获取的stmp授权码
}
})
module.exports = nodeMail
在项目中使用
const nodeMail = require('./utils/nodeMail')
// 留言
exports.leaveMessage = async (req, res) => {
const myEmail = '1436393509@qq.com'
const content = req.body.content
const mail = {
from: `"学生管理系统"<1878656671@qq.com>`,// 发件人
subject: '留言信息',//邮箱主题
to: myEmail,//收件人,这里由post请求传递过来
// 邮件内容,用html格式编写
html: `
<p>您好!</p>
<p>留言信息是:<strong style="color:orangered;">${content}</strong></br>邮箱: ${email}</p>
`
}
await nodeMail.sendMail(mail, async (err, info) => {
if (!err) {
// 保存验证码和时间
res.json({code: 20000, msg: "留言成功"})
} else {
res.json({code: 40001, msg: "留言失败"})
}
})
}
错误处理
为了方便错误处理,我们可以将错误处理封装成一个中间件,然后在路由中使用。
// 在middleware文件夹下新建error-handler.js.md
const joi = require("joi")
module.exports = () => {
return (err, req, res, next) => {
if (err instanceof joi.ValidationError) {
return res.send({
status: 0,
code: 40001,
msg: err.message,
})
} else if (err.name === 'UnauthorizedError') {
return res.send({
status: 0,
code: 40100,
msg: 'token认证失败, 未经授权访问',
})
// 捕获数据库错误
} else if (err.name === 'SequelizeDatabaseError') {
return res.send({
status: 0,
code: 40001,
msg: err.message,
})
} else if (err.name === 'SequelizeValidationError') {
return res.send({
status: 0,
code: 40001,
msg: err.message,
})
}
return res.send({
status: 0,
code: 40100,
msg: err,
})
}
}
在项目中使用
const errorHandler = require('./middleware/error-handler')
// 错误处理中间件
app.use(errorHandler())
需要注意的是,错误处理中间件必须放在路由之后,否则无法捕获路由中的错误。