插件概念 #

一、插件系统概述 #

Fastify的插件系统是其核心特性之一,遵循"一切皆插件"的设计理念。

1.1 什么是插件 #

插件是一个可重用的代码模块,可以:

  • 注册路由
  • 添加钩子
  • 装饰Fastify实例
  • 注册其他插件

1.2 插件的优势 #

优势 说明
模块化 将功能拆分为独立模块
可复用 插件可在多个项目中使用
可测试 独立测试每个插件
可维护 易于更新和维护
可扩展 轻松添加新功能

1.3 插件基本结构 #

javascript
async function myPlugin(fastify, options) {
  fastify.decorate('utility', () => {
    return 'Hello from plugin'
  })
  
  fastify.get('/plugin-route', async (request, reply) => {
    return { message: 'From plugin' }
  })
}

module.exports = myPlugin

二、插件注册 #

2.1 基本注册 #

javascript
const fastify = require('fastify')()

fastify.register(require('./my-plugin'))

fastify.listen({ port: 3000 })

2.2 带选项注册 #

javascript
fastify.register(require('./my-plugin'), {
  prefix: '/api',
  option1: 'value1',
  option2: 'value2'
})

2.3 异步注册 #

javascript
fastify.register(async function (fastify, opts) {
  await fastify.register(require('./plugin-a'))
  await fastify.register(require('./plugin-b'))
})

2.4 条件注册 #

javascript
if (process.env.ENABLE_AUTH === 'true') {
  fastify.register(require('./auth-plugin'))
}

三、插件作用域 #

3.1 封装作用域 #

Fastify插件默认是封装的,内部注册的内容不会泄漏到外部。

javascript
const fastify = require('fastify')()

fastify.register(async function (fastify, opts) {
  fastify.decorate('internal', 'value')
  
  fastify.get('/internal', async (request, reply) => {
    return { internal: fastify.internal }
  })
})

fastify.get('/external', async (request, reply) => {
  return { internal: fastify.internal }
})

fastify.listen({ port: 3000 })

访问 /external 会报错,因为 internal 装饰器只在插件作用域内有效。

3.2 破坏封装 #

使用 fastify-plugin 可以破坏封装:

javascript
const fp = require('fastify-plugin')

async function myPlugin(fastify, opts) {
  fastify.decorate('shared', 'global value')
}

module.exports = fp(myPlugin)

现在 shared 装饰器在整个应用中都可用。

3.3 作用域继承 #

子插件可以访问父插件的内容:

javascript
fastify.register(async function (fastify, opts) {
  fastify.decorate('parent', 'value')
  
  fastify.register(async function (fastify, opts) {
    console.log(fastify.parent)
  })
})

四、fastify-plugin #

4.1 基本用法 #

javascript
const fp = require('fastify-plugin')

async function myPlugin(fastify, opts) {
  fastify.decorate('utility', function () {
    return 'Utility function'
  })
}

module.exports = fp(myPlugin)

4.2 插件元数据 #

javascript
module.exports = fp(myPlugin, {
  name: 'my-plugin',
  fastify: '4.x',
  dependencies: ['other-plugin']
})

4.3 依赖声明 #

javascript
module.exports = fp(myPlugin, {
  name: 'auth-plugin',
  dependencies: ['database-plugin', 'cache-plugin']
})

4.4 封装级别控制 #

javascript
module.exports = fp(myPlugin, {
  name: 'my-plugin',
  encapsulate: true
})

五、装饰器 #

5.1 装饰Fastify实例 #

javascript
fastify.decorate('db', {
  users: [],
  addUser(user) {
    this.users.push(user)
  },
  getUsers() {
    return this.users
  }
})

fastify.get('/users', async (request, reply) => {
  return fastify.db.getUsers()
})

5.2 装饰Request #

javascript
fastify.decorateRequest('user', null)

fastify.addHook('onRequest', async (request, reply) => {
  request.user = { id: 1, name: 'John' }
})

fastify.get('/me', async (request, reply) => {
  return request.user
})

5.3 装饰Reply #

javascript
fastify.decorateReply('success', function (data) {
  return this.send({
    success: true,
    data
  })
})

fastify.get('/data', async (request, reply) => {
  reply.success({ message: 'Hello' })
})

5.4 检查装饰器是否存在 #

javascript
if (!fastify.hasDecorator('db')) {
  fastify.decorate('db', createDatabase())
}

if (!fastify.hasRequestDecorator('user')) {
  fastify.decorateRequest('user', null)
}

5.5 装饰器最佳实践 #

javascript
const fp = require('fastify-plugin')

async function databasePlugin(fastify, opts) {
  if (fastify.hasDecorator('db')) {
    throw new Error('Database decorator already exists')
  }
  
  const db = await createConnection(opts)
  
  fastify.decorate('db', db)
  
  fastify.addHook('onClose', async (instance) => {
    await instance.db.close()
  })
}

module.exports = fp(databasePlugin, {
  name: 'database'
})

六、插件生命周期 #

6.1 注册顺序 #

javascript
fastify.register(async function (fastify, opts) {
  console.log('Plugin A')
})

fastify.register(async function (fastify, opts) {
  console.log('Plugin B')
})

fastify.ready(() => {
  console.log('All plugins loaded')
})

6.2 异步初始化 #

javascript
fastify.register(async function (fastify, opts) {
  const connection = await connectDatabase()
  fastify.decorate('db', connection)
  
  console.log('Database connected')
})

fastify.ready(err => {
  if (err) throw err
  console.log('App ready, database is available')
})

6.3 插件就绪检测 #

javascript
fastify.after((err) => {
  if (err) throw err
  console.log('Previous plugins are ready')
})

fastify.ready((err) => {
  if (err) throw err
  console.log('All plugins are ready')
})

七、插件配置 #

7.1 环境配置 #

javascript
const fp = require('fastify-plugin')

async function configPlugin(fastify, opts) {
  const config = {
    development: {
      db: 'mongodb://localhost:27017/dev'
    },
    production: {
      db: process.env.DB_URL
    }
  }
  
  const env = process.env.NODE_ENV || 'development'
  fastify.decorate('config', config[env])
}

module.exports = fp(configPlugin)

7.2 选项验证 #

javascript
async function myPlugin(fastify, opts) {
  if (!opts.apiKey) {
    throw new Error('apiKey is required')
  }
  
  if (typeof opts.timeout !== 'number') {
    opts.timeout = 5000
  }
  
  fastify.decorate('api', {
    key: opts.apiKey,
    timeout: opts.timeout
  })
}

7.3 默认选项 #

javascript
const fp = require('fastify-plugin')

async function myPlugin(fastify, opts) {
  const options = {
    enabled: true,
    timeout: 5000,
    retries: 3,
    ...opts
  }
  
  fastify.decorate('options', options)
}

module.exports = fp(myPlugin)

八、插件类型 #

8.1 功能插件 #

提供特定功能的插件:

javascript
const fp = require('fastify-plugin')

async function emailPlugin(fastify, opts) {
  const nodemailer = require('nodemailer')
  
  const transporter = nodemailer.createTransport(opts)
  
  fastify.decorate('email', {
    async send(to, subject, body) {
      await transporter.sendMail({
        from: opts.from,
        to,
        subject,
        html: body
      })
    }
  })
}

module.exports = fp(emailPlugin, {
  name: 'email'
})

8.2 路由插件 #

提供路由的插件:

javascript
module.exports = async function (fastify, opts) {
  fastify.get('/health', async (request, reply) => {
    return { status: 'ok' }
  })
  
  fastify.get('/ready', async (request, reply) => {
    return { ready: true }
  })
}

8.3 集成插件 #

集成第三方服务的插件:

javascript
const fp = require('fastify-plugin')

async function redisPlugin(fastify, opts) {
  const Redis = require('ioredis')
  const redis = new Redis(opts.url)
  
  fastify.decorate('redis', redis)
  
  fastify.addHook('onClose', async (instance) => {
    await instance.redis.quit()
  })
}

module.exports = fp(redisPlugin, {
  name: 'redis'
})

8.4 中间件插件 #

提供中间件功能的插件:

javascript
const fp = require('fastify-plugin')

async function authPlugin(fastify, opts) {
  fastify.decorate('authenticate', async function (request, reply) {
    const token = request.headers.authorization
    
    if (!token) {
      reply.code(401).send({ error: 'Unauthorized' })
      return
    }
    
    try {
      request.user = await verifyToken(token)
    } catch (err) {
      reply.code(401).send({ error: 'Invalid token' })
    }
  })
}

module.exports = fp(authPlugin, {
  name: 'auth'
})

九、插件调试 #

9.1 打印插件树 #

javascript
fastify.ready(() => {
  console.log(fastify.printPlugins())
})

9.2 检查装饰器 #

javascript
fastify.ready(() => {
  console.log('Has db:', fastify.hasDecorator('db'))
  console.log('Has user:', fastify.hasRequestDecorator('user'))
})

9.3 插件加载错误 #

javascript
fastify.register(require('./my-plugin'))

fastify.ready(err => {
  if (err) {
    console.error('Plugin loading failed:', err)
    process.exit(1)
  }
})

十、最佳实践 #

10.1 插件命名 #

javascript
module.exports = fp(myPlugin, {
  name: '@myorg/fastify-mysql'
})

10.2 错误处理 #

javascript
async function myPlugin(fastify, opts) {
  try {
    const connection = await connect(opts)
    fastify.decorate('db', connection)
  } catch (err) {
    fastify.log.error('Failed to connect:', err)
    throw err
  }
}

10.3 资源清理 #

javascript
async function myPlugin(fastify, opts) {
  const resource = await createResource()
  
  fastify.decorate('resource', resource)
  
  fastify.addHook('onClose', async (instance) => {
    await instance.resource.close()
  })
}

10.4 文档化 #

javascript
/**
 * Fastify MySQL Plugin
 * 
 * @param {FastifyInstance} fastify - Fastify instance
 * @param {Object} opts - Plugin options
 * @param {string} opts.host - MySQL host
 * @param {number} opts.port - MySQL port
 * @param {string} opts.database - Database name
 * @param {string} opts.user - Database user
 * @param {string} opts.password - Database password
 */
async function mysqlPlugin(fastify, opts) {
  // Implementation
}

十一、总结 #

本章我们学习了:

  1. 插件概念:什么是插件,插件的优势
  2. 插件注册:基本注册、带选项注册、异步注册
  3. 插件作用域:封装作用域、破坏封装、作用域继承
  4. fastify-plugin:基本用法、元数据、依赖声明
  5. 装饰器:装饰实例、Request、Reply
  6. 插件生命周期:注册顺序、异步初始化、就绪检测
  7. 插件类型:功能插件、路由插件、集成插件、中间件插件
  8. 插件调试:打印插件树、检查装饰器
  9. 最佳实践:命名、错误处理、资源清理

接下来让我们学习如何注册插件!

最后更新:2026-03-28