Fastify preHandler中间件最佳实践及相关问题咨询
Fastify中间件优化与重复执行问题解析
我创建了一个需要在部分路由执行的Middleware,但不确定当前逻辑实现是否高效。同时遇到了一个问题:在validateMerchant.js中执行done()会导致路由端点执行两次,想知道原因。
路由文件
function routes(fastify, options, done) { fastify.register(require('@fastify/formbody')) fastify.register(require('../../controllers/api')) // Middleware const validateMerchant = require('../../middleware/validateMerchant') fastify.get('/get-config', { preHandler: [validateMerchant(fastify)] }, (req, rep) => { getConfig(req.merchantId, rep) }) done() } module.exports = routes
validateMerchant.js
const validateMerchant = (fastify) => async (req, rep, done) => { const merchant = require('../models/merchant') const srvcMerchant = new merchant(fastify) const merchantUuid = req?.body?.merchantUuid ?? req?.query?.merchantUuid const merchantId = await srvcMerchant.getMerchantIdByUuid(decrypt(merchantUuid)) if (!merchantId) { return rep .code(404) .send({ message: 'Merchant not found' }) } req.merchantId = merchantId // done() } module.exports = validateMerchant
merchant.js
class merchant { constructor(fastify) { this.fastify = fastify } getConfig = async (merchantId) => { const connection = await this.fastify.mysql.getConnection() const [rows] = await connection.query( `SELECT mc.boodil_points, mc.boodil_points_message, mc.logo, mc.qr_code, mc.pay_by_link, mc.bg_colour, mc.text_colour, mc.footer_bg_colour, mc.footer_content, mc.paragraph_1, mc.paragraph_2, mc.paragraph_3, mc.paragraph_4, mc.fields, mc.terms, mc.privacy FROM merchant_config mc JOIN merchants m ON m.id = mc.merchant_id WHERE m.id = :merchantId AND m.state = 1`, { merchantId: merchantId }, ) connection.release() return rows[0] } } module.exports = merchant
问题解答
1. Fastify实例传递的最优方案
你当前层层传递Fastify实例的方式能跑通,但不是最符合Fastify设计的最优解,推荐两种优化方式:
方式一:用Fastify装饰器注册模型实例
在项目初始化阶段(比如根插件或者专门的模型注册文件),把merchant模型实例注册为Fastify的装饰器,这样全局都能直接调用,不用手动传递:
// 示例:在根插件中注册 const Merchant = require('./models/merchant') fastify.decorate('merchant', new Merchant(fastify))
之后中间件里可以直接这么用:
const validateMerchant = async (req, rep) => { // 通过req.server直接获取Fastify实例上的merchant const merchantId = await req.server.merchant.getMerchantIdByUuid(decrypt(merchantUuid)) // ...后续逻辑 }
这种方式避免了重复实例化模型,代码更简洁,也贴合Fastify的设计理念。
方式二:把模型封装成Fastify插件
更规范的做法是把模型类封装成Fastify插件,在插件内部完成实例化和装饰器注册:
// 新建models/merchant-plugin.js const Merchant = require('./merchant') module.exports = async function merchantPlugin(fastify, options) { fastify.decorate('merchant', new Merchant(fastify)) }
然后在路由文件或者根插件中注册这个插件:
fastify.register(require('../../models/merchant-plugin'))
这种方式更利于后续扩展和维护,符合Fastify的插件化生态。
2. done()导致路由执行两次的原因
Fastify的preHandler支持两种写法:回调式和异步函数式,你现在的中间件是异步函数,同时又调用了done(),这就触发了双重通知:
- 异步函数会通过返回Promise告诉Fastify“我执行完了”
- 调用
done()是回调式的完成通知 - Fastify收到两次完成信号,就会执行两次路由处理逻辑
解决方法就是:异步函数风格的中间件不需要调用done(),保持你现在注释掉done()的写法就对了。
内容的提问来源于stack exchange,提问作者Dally
相关产品推荐
相关产品推荐

