Egg.js从0开始学习

19 阅读7分钟

1️⃣ Egg.js 的设计哲学

核心概念

  • 约定优于配置(Convention over Configuration) :你只要遵循 Egg.js 规定的目录和命名规则,就能减少配置和重复工作。
  • 多进程模型(Cluster 模型) :Egg.js 天生支持多进程,让应用可以高并发稳定运行。

类比

  • 想象你在一家餐厅:

    • 约定 = 菜单的固定格式和操作流程
    • 优于配置 = 你不用每次都写完整流程,只要按菜单走就行
  • 多进程模型就像:

    • 有一位总厨(Master)和多个厨师(Worker)同时做菜,保证高峰期客户不等太久。

核心理解

  • 遵循 Egg.js 的约定,你可以快速开发,而不用重复写模板代码。
  • Cluster 模型保证稳定性和高并发。

2️⃣ 目录结构与环境

核心概念

  • 目录结构是 Egg.js 的灵魂,每个目录都有固定作用:

    • app/controller → 控制器,处理请求
    • app/service → 服务层,处理业务逻辑
    • app/router.js → 路由,决定 URL 对应哪个 Controller
    • config → 配置文件,不同环境可区分
  • 环境变量 (EGG_SERVER_ENV) 决定使用哪个配置文件(local、prod、test)。

类比

  • 想象公司大楼:

    • router.js = 前台接待
    • controller = 服务员
    • service = 厨师
    • config = 菜单规范和厨房规则

核心理解

  • 熟悉目录结构,才能快速找到业务逻辑和修改入口。
  • 不同环境配置保证开发和生产环境分离,安全又稳定。

3️⃣ Router(路由)

核心概念

  • Router 决定 前端请求哪个 URL 调用哪个 Controller
  • 支持多种请求方法:GET, POST, PUT, DELETE

类比

  • Router 就像餐厅前台:

    • 客户说 “我要一份炸鸡”,前台决定把请求送到炸鸡窗口的厨师。

核心理解

  • Router 只是“入口指引”,不做业务逻辑。
  • 绑定方法和 URL 是最基本任务。

4️⃣ Controller(控制器)

核心概念

  • Controller 处理路由传来的请求,调用 Service 获取业务逻辑结果,然后返回给客户端。

类比

  • Controller = 服务员

    • 前台把订单交给服务员
    • 服务员向厨师下单
    • 最后把菜端给顾客

核心理解

  • Controller 负责“请求进来 → 调用逻辑 → 响应出去”,不做具体业务处理。
  • Controller 是接口测试的核心对象。

5️⃣ Service(业务逻辑)

核心概念

  • Service 专门处理业务逻辑,可以被多个 Controller 调用。
  • 可以访问数据库、调用 Helper、封装复杂逻辑。

类比

  • Service = 厨师

    • 接到服务员订单后做菜
    • 不管前台和客户,只管菜做好

核心理解

  • Service 的测试重点是“逻辑正确性”,可以独立于 HTTP 测试。
  • Controller 和 Router 调用它,实现接口功能。

6️⃣ Context(上下文对象,ctx)

核心概念

  • ctx 是每次请求的上下文

    • 包含请求信息、响应方法、Service、Helper、日志等

类比

  • ctx = 当天服务员手里拿的“订单本”

    • 上面有客户信息、菜品、餐桌号
    • 服务员可以直接调用厨师做菜(Service)
    • 写日志、记录点餐时间

核心理解

  • ctx 是 贯穿整个请求生命周期的核心对象
  • 方便 Controller 和 Service 获取请求数据、操作响应、调用日志

7️⃣ Config(配置管理)

核心概念

  • Egg.js 配置统一管理,可以分环境:

    • config.default.js → 默认配置
    • config.local.js → 本地开发
    • config.prod.js → 生产
  • 配置可以控制安全、日志、数据库、端口等

类比

  • config = 餐厅规则

    • 菜谱、厨房温度、订单流程
    • 开发环境可以宽松,生产环境严格安全

核心理解

  • 配置中心化,易于维护
  • 不同环境切换简单

8️⃣ Middleware(中间件)

核心概念

  • 中间件是请求和响应之间的处理程序
  • 可以做日志、鉴权、数据校验、性能监控

类比

  • Middleware = 门口安检 + 服务员检查

    • 检查顾客身份(鉴权)
    • 检查点餐格式(校验)
    • 记录点餐时间(日志)

核心理解

  • 每个请求都可以经过中间件链
  • 可以在全局或者局部注册

9️⃣ Extend(扩展工具)

核心概念

  • 可以扩展 Controller、Service、Context、Application 的方法
  • 统一封装常用工具或逻辑

类比

  • Extend = 厨房工具升级

    • 比如:统一封装炒菜机器,让厨师不用每次手工切菜

核心理解

  • 提高开发效率
  • 保持代码 DRY(不要重复自己)

🔟 Schedule(定时任务)

核心概念

  • 定时执行的任务(cron job)
  • 比如清理日志、发送邮件、数据统计

类比

  • Schedule = 厨房自动烤箱

    • 到指定时间自动启动做菜
    • 不需要服务员触发

核心理解

  • 与 Controller 无关
  • 可保证业务定期执行

1️⃣1️⃣ Logger(日常日志)

核心概念

  • Egg.js 提供 Logger 系统
  • 记录 info、warn、error 日志
  • 支持文件分级写入

类比

  • Logger = 厨师的工作记录本

    • 每次做菜、发生问题都记录
    • 方便以后查错、统计

核心理解

  • 不要用 console.log
  • Logger 结构化、可查询、适合运维

1️⃣2️⃣ 单元测试与集成测试

核心概念

  • Controller 测试 → 测接口行为(是否按预期返回)
  • Service 测试 → 测业务逻辑(不走 HTTP)
  • egg-mock 提供 app.mockContext() 创建假 ctx

类比

  • Controller 测试 = 顾客点菜 → 最终上菜是否正确
  • Service 测试 = 厨师做菜是否正确
  • Mock ctx = 模拟服务员拿着订单本,不去餐厅也能测试厨师

核心理解

  • 测试覆盖两层:接口 & 业务逻辑
  • 让代码更稳定、易维护

✅ 总结口诀(方便新手快速复习)

路由指路 → 控制接单 → 厨师做菜 → 上下文拿单 → 配置定规则 → 中间件保安全 → 扩展工具提效率 → 定时任务自动跑 → 日志留痕 → 测试保稳定

换成人话就是:

  1. 前台(Router)告诉服务员(Controller)谁要点什么
  2. 服务员把订单传给厨师(Service)
  3. 厨师通过订单本(ctx)做菜
  4. 厨房工具(Extend)和规则(Config、中间件)保证顺畅
  5. 自动烤箱(Schedule)按时做任务
  6. 日志(Logger)记录每一步
  7. 测试保证菜和流程没问题

1️⃣ Router(路由)

文件位置

app/router.js

作用

  • URL 请求 → 对应 Controller 方法

标准写法

module.exports = app => {
  const { router, controller } = app;

  // GET 请求
  router.get('/ping', controller.home.ping);

  // POST 请求
  router.post('/user/create', controller.user.create);
};

类比

  • Router = 前台接待,指路给服务员(Controller)

2️⃣ Controller(控制器)

文件位置

app/controller/home.js

作用

  • 接收请求,调用 Service,返回响应

标准写法

'use strict';
const Controller = require('egg').Controller;

class HomeController extends Controller {
  async ping() {
    const { ctx } = this;
    ctx.body = { message: 'pong' };
  }
}

module.exports = HomeController;

类比

  • Controller = 服务员,负责拿订单 → 厨师 → 返回给顾客

3️⃣ Service(业务逻辑)

文件位置

app/service/user.js

作用

  • 处理核心业务逻辑,可被多个 Controller 调用

标准写法

'use strict';
const Service = require('egg').Service;

class UserService extends Service {
  async create(user) {
    if (!user.username) throw new Error('username required');
    // 模拟数据库返回
    return { id: 1, username: user.username };
  }
}

module.exports = UserService;

类比

  • Service = 厨师,只管做菜,不管客户

4️⃣ Context(上下文对象 ctx)

使用位置

  • 在 Controller 或 Service 里

示例

class HomeController extends Controller {
  async info() {
    const { ctx } = this;
    // 获取请求参数
    const name = ctx.query.name || 'guest';
    // 调用 Service
    const result = await ctx.service.user.create({ username: name });
    ctx.body = result;
  }
}

类比

  • ctx = 服务员手里的“订单本”,记下客户信息和需求

5️⃣ Config(配置管理)

文件位置

config/config.default.js
config/config.local.js
config/config.prod.js

示例

exports.keys = 'your-cookie-sign-key';

exports.security = {
  csrf: {
    enable: true,
  },
};

exports.logger = {
  level: 'INFO',
};

exports.mysql = {
  client: {
    host: 'localhost',
    port: '3306',
    user: 'root',
    password: '123456',
    database: 'egg_demo',
  },
  app: true,
  agent: false,
};

类比

  • config = 餐厅规则和菜单,规定流程和资源

6️⃣ Middleware(中间件)

文件位置

app/middleware/logger.js

示例

module.exports = () => {
  return async function logger(ctx, next) {
    const start = Date.now();
    await next();
    const ms = Date.now() - start;
    console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
  };
};

注册中间件

// config/config.default.js
exports.middleware = ['logger'];

类比

  • Middleware = 门口安检 + 服务员检查,保证流程和安全

7️⃣ Extend(扩展工具)

文件位置

app/extend/context.js

示例

module.exports = {
  success(data = {}) {
    this.body = { code: 0, data };
  },
};

Controller 使用

async info() {
  const { ctx } = this;
  ctx.success({ message: 'ok' });
}

类比

  • Extend = 厨房工具升级,封装常用操作,提高效率

8️⃣ Schedule(定时任务)

文件位置

app/schedule/clear-log.js

示例

'use strict';
const Subscription = require('egg').Subscription;

class ClearLog extends Subscription {
  static get schedule() {
    return {
      cron: '0 0 * * *', // 每天0点执行
      type: 'all', // 所有 worker 都执行
    };
  }

  async subscribe() {
    const { app } = this;
    app.logger.info('开始清理日志...');
    // 执行清理逻辑
  }
}

module.exports = ClearLog;

类比

  • Schedule = 自动烤箱,按时自动运行任务

9️⃣ Logger(日志)

使用位置

  • Controller / Service / Schedule / Middleware

示例

// info
ctx.logger.info('用户创建成功', userId);

// error
ctx.logger.error(new Error('数据库错误'));

类比

  • Logger = 厨师工作日志,记录每一步方便排查

🔟 单元测试

Controller 测试位置

test/controller/home.test.js

示例

const { app, assert } = require('egg-mock/bootstrap');

describe('Controller: home', () => {
  it('GET /ping should return pong', async () => {
    const result = await app.httpRequest().get('/ping');
    assert(result.body.message === 'pong');
  });
});

Service 测试位置

test/service/user.test.js

示例

const { app, assert } = require('egg-mock/bootstrap');

describe('Service: user', () => {
  let ctx;
  beforeEach(() => { ctx = app.mockContext(); });

  it('should create user', async () => {
    const result = await ctx.service.user.create({ username: 'tom' });
    assert(result.username === 'tom');
  });
});

类比

  • Controller 测试 = 顾客点菜 → 上菜是否正确
  • Service 测试 = 厨师做菜是否正确

复习口诀(配合代码记忆)

Router 指路 → Controller 接单 → Service 做菜 → ctx 拿单 → Config 定规则 → Middleware 保安全 → Extend 提效率 → Schedule 自动跑 → Logger 留痕 → 测试保稳定