You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 04:44:54