首选 ES6 class + extends,配合 super() 显式调用父类构造函数;控制器基类应封装统一响应格式、请求上下文访问、基础校验、日志与追踪 ID 注入等共性能力,子类继承后重写具体业务方法,职责分离清晰,语义明确且与 Express/Koa 天然契合。
在 node.js 服务端开发中,用最常用、最稳妥的继承方式构建控制器基类,首选 es6 class + extends,配合 super() 显式调用父类构造函数。这种方式语义清晰、调试友好、与现代框架(如 express、fastify)天然契合,且被所有主流 node.js 版本(v14+)原生支持,无需额外 polyfill 或 util.inherits 兼容层。
控制器基类应封装哪些共性能力
一个实用的控制器基类不是为了“炫技继承”,而是集中管理高频复用逻辑:
-
统一响应格式封装:比如
success(data, msg)、fail(code, msg)方法,避免每个路由手动拼 JSON -
请求上下文快捷访问:把
req、res、next或 Koa 的ctx提前绑定或透传,减少参数重复传递 - 基础校验与错误预处理:如自动解析 query/body、检查必填字段、捕获异步异常并委托给全局错误中间件
-
日志与追踪 ID 注入:在构造时读取请求 ID(如
req.id或ctx.state.traceId),供后续方法打点使用
标准写法:子类继承并重写具体方法
基类不直接处理路由,只提供可复用骨架;子类负责业务实现,保持职责分离:
// controllers/BaseController.js
class BaseController {
constructor(ctx) {
this.ctx = ctx;
this.logger = ctx.logger || console;
}
<p>success(data = null, msg = 'OK') {
this.ctx.status = 200;
this.ctx.body = { code: 0, msg, data };
}</p><p>fail(code = 500, msg = 'Server Error', data = null) {
this.ctx.status = code;
this.ctx.body = { code, msg, data };
}</p><p>// 可被子类覆盖的钩子
async beforeAction() {}
async afterAction() {}
}</p><p>module.exports = BaseController;// controllers/UserController.js
const BaseController = require('./BaseController');
<p>class UserController extends BaseController {
constructor(ctx) {
super(ctx); // 必须调用,确保 this.ctx 正确初始化
}</p><p>async list() {
await this.beforeAction();
try {
const users = await db.query('SELECT * FROM users');
this.success(users);
} catch (err) {
this.fail(500, 'Query failed');
this.logger.error(err);
} finally {
await this.afterAction();
}
}</p><p>async create() {
const { name, email } = this.ctx.request.body;
if (!name || !email) return this.fail(400, 'Missing required fields');
// ... 业务逻辑
}
}</p><p>module.exports = UserController;搭配路由注册的典型模式
Express 示例(推荐用箭头函数绑定 this,避免丢失上下文):
ZenHeart zenlink 与 zenlink-mcp 为 OpenClaw,涵盖安装、MCP 注册、钩子接线、入站轮询及升级维护。
const UserController = require('./controllers/UserController');
<p>router.get('/users', async (req, res, next) => {
const ctrl = new UserController({ req, res, next, logger: req.app.logger });
await ctrl.list();
});</p><p>router.post('/users', async (req, res, next) => {
const ctrl = new UserController({ req, res, next, logger: req.app.logger });
await ctrl.create();
});Koa 更简洁,可直接传 ctx:
router.get('/users', async (ctx) => {
const ctrl = new UserController(ctx);
await ctrl.list();
});为什么不推荐 util.inherits?
虽然 util.inherits 是 Node.js 早期经典方案,但它存在明显局限:
- 仅复制原型链,无法继承构造函数内定义的实例属性和方法(如
this.logger) - 子类
constructor与父类无自动关联,需手动调用Parent.call(this, ...),易出错 - 无法使用
super.xxx(),语法割裂,不利于 IDE 智能提示和类型推导 - ES6 class 是当前标准,
util.inherits已属于历史兼容方案,新项目无需回退

















