Koa 项目中间件设计技巧与最佳实践
1. 应用级中间件 vs 路由级中间件
类型差异
在 Koa 项目中,应用级中间件和路由级中间件接收的 Context 类型是不同的:
// 应用级中间件 - 标准 Koa Context
import { Context, Next } from "koa";
export const appMiddleware = async (ctx: Context, next: Next) => {
// ctx 是标准的 koa Context
};
// 路由级中间件 - 增强的 Router Context
import { IMiddleware } from "koa-router";
export const routeMiddleware: IMiddleware = async (ctx, next) => {
// ctx 包含路由信息:params, router, _matchedRoute 等
console.log("路由参数:", ctx.params);
console.log("匹配的路由:", ctx._matchedRoute);
};适用场景
- 应用级中间件:全局功能(日志、错误处理、全局速率限制)
- 路由级中间件:特定路由的精细控制(权限检查、参数验证)
2. 中间件工厂模式设计
为什么使用工厂模式?
工厂模式允许同一个中间件根据不同参数做出不同处理,提高代码复用性:
// 路由级中间件工厂模式 - 灵活配置
export const authMiddleware = (
options: {
required?: boolean;
roles?: string[];
} = {}
) => {
return async (ctx: RouterContext, next: Next) => {
const { required = true, roles = [] } = options;
// 根据参数做不同处理
if (required && !token) {
throw new AuthenticationError("缺少令牌");
}
if (!required && !token) {
return await next(); // 可选认证,继续执行
}
if (roles.length > 0) {
// 检查角色权限
const hasPermission = await checkUserRoles(userId, roles);
if (!hasPermission) {
throw new AuthorizationError("权限不足");
}
}
await next();
};
};使用示例
// 路由配置中的灵活使用
const routes = [
{
path: "/public",
method: "GET",
controller: "PublicController",
action: "getData",
middleware: [authMiddleware({ required: false })], // 可选认证
},
{
path: "/admin",
method: "GET",
controller: "AdminController",
action: "dashboard",
middleware: [authMiddleware({ roles: ["admin"] })], // 需要管理员权限
},
{
path: "/user",
method: "GET",
controller: "UserController",
action: "profile",
middleware: [authMiddleware()], // 必须认证(默认配置)
},
];
// 路由注册器
class RouteRegistry {
private router: Router;
constructor() {
this.router = new Router();
}
// 注册路由配置
registerRoutes(routeConfigs: RouteConfig[]): Router {
routeConfigs.forEach((route) => {
const handlers = [
...route.middleware, // 应用中间件
async (ctx: RouterContext, next: Next) => {
// 控制器处理逻辑
const controller = getController(route.controller);
await controller[route.action](ctx, next);
},
];
// 动态注册路由
this.router[route.method.toLowerCase()](route.path, ...handlers);
});
return this.router;
}
}
// 使用示例
const app = new Koa();
const registry = new RouteRegistry();
const router = registry.registerRoutes(routes);
app.use(router.routes());
app.use(router.allowedMethods());3. 预设中间件模式
为了平衡灵活性和便利性,可以提供常用的预设版本:
// 工厂函数
export const authMiddleware = (options: AuthOptions = {}) => {
/* ... */
};
// 预设版本 - 常用配置
export const requireAuth: IMiddleware = authMiddleware();
export const optionalAuth: IMiddleware = authMiddleware({ required: false });
export const adminOnly: IMiddleware = authMiddleware({ roles: ["admin"] });
export const userOrAdmin: IMiddleware = authMiddleware({
roles: ["user", "admin"],
});
// 使用时更简洁
middleware: [requireAuth]; // 而不是 authMiddleware()4. 中间件类型兼容性处理
问题场景
当项目中定义路由级中间件类型时,会遇到类型不兼容问题:
// 错误:类型不匹配
interface RouteConfig {
middleware: IMiddleware[]; // 期望路由中间件
}
const routes = [
{
middleware: [rateLimitMiddleware()], // 实际是应用级中间件
},
];解决方案
方案 1:分层设计
// 应用级中间件
export const appRateLimitMiddleware = (options) => {
return async (ctx: Context, next: Next) => {
/* ... */
};
};
// 路由级中间件
export const routeRateLimitMiddleware = (options): IMiddleware => {
return async (ctx: RouterContext, next: Next) => {
// 可以利用路由信息做更精确的限制
const key = `rate_limit:${ctx.ip}:${ctx._matchedRoute}`;
// ...
};
};5. 中间件职责分离
全局中间件 vs 路由中间件
正确的职责分离能避免类型冲突:
// ❌ 错误:同一个中间件既用于全局又用于路由
app.use(rateLimitMiddleware({ max: 100 })); // 应用级使用
routes: [{ middleware: [rateLimitMiddleware({ max: 10 })] }]; // 路由级使用
// ✅ 正确:职责分离
// 全局速率限制
app.use(globalRateLimitMiddleware({ max: 100 }));
// 特定路由的精细限制(如果需要)
routes: [{ middleware: [routeSpecificMiddleware] }];6. 最佳实践建议
1. 命名约定
// 应用级中间件
export const appAuthMiddleware = () => {
/* ... */
};
export const globalRateLimitMiddleware = () => {
/* ... */
};
// 路由级中间件
export const routeAuthMiddleware: IMiddleware = () => {
/* ... */
};
export const paramValidationMiddleware: IMiddleware = () => {
/* ... */
};2. 文件组织
middleware/
├── app/ # 应用级中间件
│ ├── auth.ts
│ ├── rate-limit.ts
│ └── logging.ts
├── route/ # 路由级中间件
│ ├── validation.ts
│ ├── permissions.ts
│ └── cache.ts
└── index.ts # 统一导出3. 类型安全
// 定义明确的中间件接口
export interface AppMiddlewareFactory<T = any> {
(options?: T): Middleware;
}
export interface RouteMiddlewareFactory<T = any> {
(options?: T): IMiddleware;
}
// 实现时明确类型
export const authMiddleware: AppMiddlewareFactory<AuthOptions> = (options) => {
return async (ctx: Context, next: Next) => {
/* ... */
};
};7. 总结
工厂模式提供了中间件的灵活配置能力
预设模式在保持灵活性的同时提供了便利性
正确的类型处理避免了开发时的类型错误
职责分离让应用架构更清晰,避免类型冲突
通过这些设计技巧,可以构建出既灵活又类型安全的 Koa 中间件系统。