You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
hutao/docs/book/router.md

8.2 KiB

在Fastify中,可以通过路由拆分来组织和维护大型应用的代码。路由拆分通常意味着你将相关的路由处理函数放到单独的文件或模块中,并在应用中相应地注册它们。下面是如何做到路由拆分的一般步骤:

  1. 创建一个路由文件或模块。
  2. 在该文件中定义路由。
  3. 导出这些路由。
  4. 在你的主应用文件中注册这些路由。

例子:创建和使用路由模块

以下给出一个非常基础的例子来演示如何拆分路由。

user-routes.js

// user-routes.js
async function routes(fastify, options) {
  fastify.get('/users', async (request, reply) => {
    // 处理获取用户的逻辑...
    return { users: [] };
  });

  fastify.get('/users/:id', async (request, reply) => {
    // 处理获取单个用户的逻辑...
    const { id } = request.params;
    return { user: id };
  });
}

module.exports = routes;

item-routes.js

// item-routes.js
async function routes(fastify, options) {
  fastify.get('/items', async (request, reply) => {
    // 处理获取物品的逻辑...
    return { items: [] };
  });

  fastify.get('/items/:id', async (request, reply) => {
    // 处理获取单个物品的逻辑...
    const { id } = request.params;
    return { item: id };
  });
}

module.exports = routes;

主应用文件(比如app.js或server.js)

// app.js
const fastify = require('fastify')({ logger: true });

// 注册路由模块
fastify.register(require('./user-routes'));
fastify.register(require('./item-routes'));

// 启动服务器
fastify.listen({ port: 3000, host: '0.0.0.0' }, (err, address) => {
  if (err) {
    fastify.log.error(err);
    process.exit(1);
  }
  console.log(`Server listening on ${address}`);
});

在这个例子中,我们有两个路由模块(user-routes.jsitem-routes.js),它们被导入到主应用文件 app.js 中,使用 fastify.register 方法来注册。每个路由文件处理特定路径的请求,并有自己的路由处理函数。这种方式使得代码结构更清晰,并且容易维护,特别是当应用程序规模变大时。

为了更好的代码组织,你甚至可以进一步把路由处理函数分离到独立的控制器文件中。但以上示例提供了一个如何开始拆分路由的基础结构。


如果你想进一步组织你的代码,尤其是对于大型应用程序,以下是一些可以考虑的额外步骤:

1. 分离控制器

可以创建一个控制器目录,用于存放处理路由逻辑的函数。这能让你的路由定义更加简洁,因为控制器将包含大部分业务逻辑。

controllers/userController.js

async function getUsers(request, reply) {
  // 处理逻辑...
  return { users: [] };
}

async function getUserById(request, reply) {
  const { id } = request.params;
  // 处理逻辑...
  return { user: id };
}

module.exports = {
  getUsers,
  getUserById,
};

然后,在路由文件中,你可以导入并使用这些控制器函数:

routes/user-routes.js

const userController = require('../controllers/userController');

async function routes(fastify, options) {
  fastify.get('/users', userController.getUsers);
  fastify.get('/users/:id', userController.getUserById);
}

module.exports = routes;

2. 路由前缀

当注册路由时,可以为一组路由指定一个公共前缀。Fastify 允许你在注册插件时提供一个 prefix 选项,该选项会应用于该插件中定义的所有路由。

fastify.register(require('./user-routes'), { prefix: '/users' });
fastify.register(require('./item-routes'), { prefix: '/items' });

这将相应地更改 user-routes.jsitem-routes.js 中的路径定义,因为你不再需要在每个路由路径中包含 /users/items

3. 插件化

为了进一步的模块化,可以将某些功能片段封装为插件。Fastify 天然支持插件架构,以便于封装和共享代码。例如,可以创建一个为特定资源服务的插件,将路由处理和相关功能封装在内。

plugins/users.js

const userRoutes = require('../routes/user-routes');

async function usersPlugin(fastify, options) {
  fastify.decorate('someUtility', () => {/* ... */});
  fastify.register(userRoutes);
}

module.exports = usersPlugin;

注册插件:

app.js

fastify.register(require('./plugins/users'), { prefix: '/users' });

4. 结构化错误处理和验证

Fastify 提供了丰富的钩子(hooks)和插件来处理错误和验证请求。你可以创建共享的钩子和模式,在你的应用中使用它们,以保持代码的一致性和减少重复。

5. 使用 fastify-cli 工具

Fastify 社区还提供了 fastify-cli 命令行工具,用于快速地搭建和管理 Fastify 项目。它可以自动为你生成项目结构,包括路由、插件和其他配置,从而让你更容易地按照最佳实践组织代码。

通过使用上述步骤,你将能够创建一个更加模块化、易于维护和可扩展的 Fastify 应用程序。


在 Fastify 中,可以使用钩子(Hooks)来执行拦截和验证操作。钩子可以在请求的生命周期的不同阶段被触发,例如在请求被路由之前(onRequest)、在执行路由处理函数之前(preValidationpreHandler)、在发送响应之前(onSend)等等。

若要针对不同的路由模块执行不同的拦截验证,你可以在每个路由模块中分别声明这些钩子。这样,只有当请求达到特定模块的路由时,相应的钩子才会被触发。以下是如何为不同的路由模块添加特定逻辑的示例:

创建一个带有 onRequest 钩子的路由模块

// routes/user-routes.js
module.exports = async function (fastify, options) {
  // 在 '/users' 路径及其子路径上应用 onRequest 钩子
  fastify.addHook('onRequest', async (request, reply) => {
    // 添加一些用户身份验证逻辑
    if (!request.headers['auth-token']) {
      throw new Error('Missing auth token!');
    }
    // 检查 auth-token 是否有效...
  });

  // 用户路由
  fastify.get('/users', async (request, reply) => {
    // 返回用户列表
  });

  fastify.get('/users/:id', async (request, reply) => {
    // 返回单个用户
  });
};

创建另一个带有 preHandler 钩子的路由模块

// routes/item-routes.js
module.exports = async function (fastify, options) {
  // 在 '/items' 路径及其子路径上应用 preHandler 钩子
  fastify.addHook('preHandler', async (request, reply) => {
    // 添加一些权限检查逻辑
    const userRole = request.user.role; // 假设我们已经在earlier hook中设置了用户
    if (userRole !== 'admin') {
      throw new Error('Unauthorized!');
    }
    // 执行其它必要的权限检查...
  });

  // 物品路由
  fastify.get('/items', async (request, reply) => {
    // 返回物品列表
  });

  fastify.get('/items/:id', async (request, reply) => {
    // 返回单个物品详情
  });
};

在主应用文件中,你需要注册上述路由模块:

// app.js
const fastify = require('fastify')();

// 注册用户路由模块,'onRequest' 钩子将适用于此路由及其子路由
fastify.register(require('./routes/user-routes'), { prefix: '/users' });

// 注册物品路由模块,'preHandler' 钩子将适用于此路由及其子路由
fastify.register(require('./routes/item-routes'), { prefix: '/items' });

// 运行服务器...

在这个例子中,用户相关的路由会检查请求头中是否包含 auth-token,而物品相关的路由则会在路由处理之前检查用户的角色。这种方式能够让你按模块进行细粒度的拦截和验证控制,同时保持代码的整洁和模块化。

值得注意的是,钩子内部抛出的错误将被 Fastify 捕获,并作为 HTTP 错误被发送给客户端。这样,你可以根据需要定制错误响应。此外,Fastify 还允许你通过 fastify.decorateRequestfastify.decorateReply 方法给 requestreply 对象添加自定义属性和方法,进一步增强拦截和验证的能力。