阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 大型项目路由拆分方案

大型项目路由拆分方案

作者:陈川 阅读数:50031人阅读 分类: Node.js

路由拆分背景

随着项目规模扩大,单个路由文件会变得臃肿难维护。一个典型的Koa2项目初始阶段可能把所有路由都写在app.js里,当路由数量超过50个时,文件可能达到上千行代码。这种情况下,查找特定路由变得困难,团队协作时容易产生冲突,测试用例也难以定位。

基础路由拆分方案

最直接的方式是按业务模块拆分路由文件。假设有个电商项目,可以创建以下目录结构:

routes/
  ├── user.js
  ├── product.js
  ├── order.js
  └── cart.js

每个文件导出自己的路由实例:

// routes/user.js
const router = require('koa-router')();

router.get('/users', async (ctx) => {
  ctx.body = await UserService.list();
});

router.post('/users', async (ctx) => {
  ctx.body = await UserService.create(ctx.request.body);
});

module.exports = router;

然后在主文件中统一加载:

// app.js
const userRouter = require('./routes/user');
const productRouter = require('./routes/product');

app.use(userRouter.routes());
app.use(productRouter.routes());

自动化路由加载

当路由文件增多时,手动require每个文件变得繁琐。可以使用fs模块自动加载路由:

const fs = require('fs');
const path = require('path');

const routesPath = path.join(__dirname, 'routes');
fs.readdirSync(routesPath).forEach(file => {
  const router = require(path.join(routesPath, file));
  app.use(router.routes());
});

多级路由结构

对于更复杂的项目,可能需要多级路由。例如管理后台和用户端API分开:

routes/
  ├── admin/
  │   ├── user.js
  │   └── system.js
  └── api/
      ├── v1/
      │   ├── user.js
      │   └── product.js
      └── v2/
          ├── user.js
          └── order.js

实现方式是在子路由中使用prefix:

// routes/api/v1/user.js
const router = require('koa-router')({
  prefix: '/api/v1'
});

router.get('/users', ...);

路由中间件隔离

不同路由可能需要不同的中间件组合。可以在路由级别配置中间件:

// routes/admin/user.js
const auth = require('../../middlewares/admin-auth');
const router = require('koa-router')();

router.use(auth);
router.get('/users', ...);

动态路由注册

某些场景需要根据配置动态注册路由:

// routes/dynamic.js
module.exports = function(config) {
  const router = require('koa-router')();
  
  config.routes.forEach(route => {
    router[route.method](route.path, ...route.middlewares);
  });
  
  return router;
}

路由元信息管理

为路由添加元数据便于统一管理:

// decorators/route.js
function GET(path) {
  return function(target, name, descriptor) {
    const handler = descriptor.value;
    handler.__route = {
      method: 'get',
      path,
      handler
    };
    return descriptor;
  }
}

// routes/user.js
class UserController {
  @GET('/users')
  async listUsers(ctx) {
    // ...
  }
}

性能优化考虑

路由数量过多时会影响匹配性能。可以:

  1. 将高频路由前置
  2. 使用路由缓存
  3. 避免复杂正则路径
// 高频路由单独处理
const frequentRouter = require('koa-router')();
frequentRouter.get('/api/products/hot', ...);
app.use(frequentRouter.routes());

// 其他路由
app.use(mainRouter.routes());

测试友好设计

为方便测试,路由文件应该:

  1. 导出原始router实例
  2. 不自动注册中间件
  3. 支持依赖注入
// routes/user.js
module.exports = function(userService) {
  const router = require('koa-router')();
  
  router.get('/users', async ctx => {
    ctx.body = await userService.list();
  });
  
  return router;
}

版本控制方案

API版本演进时,推荐以下几种方案:

  1. URL路径版本控制
// routes/v1/user.js
router.prefix('/v1/users');
  1. 请求头版本控制
router.get('/users', ctx => {
  const version = ctx.headers['x-api-version'] || 'v1';
  // 根据version返回不同数据
});
  1. 查询参数版本控制
router.get('/users', ctx => {
  const version = ctx.query.version || 'v1';
});

路由文档生成

结合JSDoc自动生成路由文档:

/**
 * @api {get} /users 获取用户列表
 * @apiVersion 1.0.0
 * @apiName GetUsers
 */
router.get('/users', ...);

使用工具解析注释生成API文档。

错误处理统一

每个路由文件可以定义自己的错误处理:

// routes/user.js
router.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500;
    ctx.body = { 
      error: 'User module error',
      details: err.message 
    };
  }
});

路由权限控制

实现细粒度的权限控制:

// routes/admin.js
const permissions = {
  '/users': ['admin'],
  '/settings': ['superadmin']
};

router.use(async (ctx, next) => {
  const userRole = ctx.state.user.role;
  const requiredRoles = permissions[ctx.path];
  
  if (!requiredRoles.includes(userRole)) {
    ctx.throw(403);
  }
  
  await next();
});

路由性能监控

添加路由级性能追踪:

router.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const duration = Date.now() - start;
  metrics.track(ctx.path, duration);
});

路由缓存策略

根据路由特性设置缓存:

// routes/product.js
const cache = require('../middlewares/cache');

router.get('/products/:id', 
  cache({ ttl: 3600 }),
  async ctx => {
    // ...
  }
);

路由流量控制

限制高频路由的访问:

const rateLimit = require('koa-ratelimit');

router.get('/api/search',
  rateLimit({
    duration: 60000,
    max: 30
  }),
  async ctx => {
    // ...
  }
);

路由参数校验

使用Joi等库校验参数:

const Joi = require('joi');

router.post('/users', 
  validate({
    body: Joi.object({
      name: Joi.string().required(),
      email: Joi.string().email().required()
    })
  }),
  async ctx => {
    // ...
  }
);

微服务路由网关

在微服务架构中作为网关路由:

const { proxy } = require('koa-http-proxy');

router.all('/user-service/(.*)',
  proxy({
    target: 'http://user-service:3000',
    rewrite: path => path.replace('/user-service', '')
  })
);

路由配置中心

从配置中心动态加载路由:

const configClient = require('config-client');

router.use(async (ctx, next) => {
  const routes = await configClient.get('routes');
  const routeConfig = routes.find(r => r.path === ctx.path);
  
  if (routeConfig?.maintenance) {
    ctx.status = 503;
    return;
  }
  
  await next();
});

路由AB测试

支持路由级别的AB测试:

router.get('/new-feature', 
  abTest({
    variants: [
      { weight: 50, handler: variantAHandler },
      { weight: 50, handler: variantBHandler }
    ]
  })
);

路由数据mock

开发环境mock路由数据:

const isDev = process.env.NODE_ENV === 'development';

router.get('/products',
  isDev ? mockHandler : realHandler
);

function mockHandler(ctx) {
  ctx.body = [{ id: 1, name: 'Mock Product' }];
}

路由请求转换

统一处理请求数据:

router.use(async (ctx, next) => {
  // 转换驼峰命名到下划线
  if (ctx.request.body) {
    ctx.request.body = convertKeys(ctx.request.body);
  }
  await next();
});

路由响应格式化

统一响应格式:

router.use(async (ctx, next) => {
  await next();
  
  if (ctx.body && !ctx.body.error) {
    ctx.body = {
      code: 200,
      data: ctx.body,
      timestamp: Date.now()
    };
  }
});

路由健康检查

添加健康检查端点:

router.get('/health', (ctx) => {
  ctx.body = {
    status: 'UP',
    services: {
      db: checkDb(),
      cache: checkCache()
    }
  };
});

路由过时标记

标记即将废弃的路由:

router.get('/old-api',
  deprecated({
    message: 'Use /new-api instead',
    sunset: '2023-12-31'
  }),
  oldApiHandler
);

路由实验特性

隔离实验性功能:

router.get('/experimental/feature-x',
  experimental({
    flag: 'FEATURE_X_ENABLED'
  }),
  experimentalHandler
);

路由流量染色

为特定路由标记流量:

router.use('/admin', (ctx, next) => {
  ctx.state.tags = ctx.state.tags || [];
  ctx.state.tags.push('admin');
  return next();
});

路由数据脱敏

敏感数据自动脱敏:

router.use('/users', (ctx, next) => {
  await next();
  if (ctx.body?.email) {
    ctx.body.email = maskEmail(ctx.body.email);
  }
});

路由请求录制

录制生产环境请求:

router.use((ctx, next) => {
  if (shouldRecord(ctx.path)) {
    requestRecorder.record(ctx);
  }
  return next();
});

路由智能降级

异常时自动降级:

router.get('/recommendations',
  circuitBreaker({
    fallback: getCachedRecommendations
  }),
  getLiveRecommendations
);

路由蓝绿部署

支持蓝绿部署切换:

router.get('/products',
  blueGreen({
    blue: blueHandler,
    green: greenHandler,
    selector: ctx => ctx.headers['x-deployment-group']
  })
);

路由地域路由

根据地域路由请求:

router.get('/content',
  geoRouter({
    'US': usContentHandler,
    'EU': euContentHandler,
    'default': globalContentHandler
  })
);

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌