中间件介绍

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的工具。通过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

具体使用方式可以参考官方文档open in new window

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')
})

具体使用方式可以参考官方文档open in new window

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())

需要注意的是,错误处理中间件必须放在路由之后,否则无法捕获路由中的错误。

Last Updated:
Contributors: huangdingxin, 黄定鑫