1. 项目概述
"2026年15天学习完eggjs 第3天"这个标题背后隐藏着一个系统化的前端学习计划。作为阿里系企业级Node.js框架,eggjs凭借其约定优于配置的设计理念和插件机制,正在成为全栈开发者的必备技能。这个15天的学习计划显然是为希望快速掌握eggjs核心能力的开发者设计的。
我在实际企业级项目中使用eggjs已有三年时间,从简单的RESTful API开发到复杂的微服务架构都有涉及。这个第3天的学习内容,通常对应着eggjs学习曲线上的第一个关键转折点——从基础环境搭建转向核心概念的实际应用。
2. 第3天学习内容解析
2.1 当日学习目标拆解
根据标准的eggjs学习路径,第3天通常会聚焦以下几个核心目标:
- 项目结构深度理解:超越简单的目录创建,理解MVC架构在egg中的具体实现
- 路由配置进阶:RESTful风格路由与动态路由的实际应用
- 控制器(Controller)编写规范:企业级项目中的最佳实践
- 基础中间件开发:自定义中间件的开发与调试技巧
2.2 典型课程安排推测
基于我的教学经验,这天的课程很可能是这样的时间分配:
-
上午(3小时):
- 项目结构详解(1小时)
- 路由配置实战(2小时)
-
下午(4小时):
- 控制器开发规范(2小时)
- 中间件开发入门(2小时)
-
晚上(2小时):
- 综合练习与问题复盘
3. 核心知识点详解
3.1 项目结构背后的设计哲学
eggjs的项目结构看似简单,实则暗藏玄机:
bash复制egg-project
├── app
│ ├── controller
│ ├── service
│ ├── middleware
│ └── router.js
├── config
│ ├── config.default.js
│ └── plugin.js
└── test
关键提示:不要被表面目录迷惑,eggjs通过目录结构强制实施最佳实践,这是"约定优于配置"的核心体现
各目录的隐藏规则:
controller/:必须返回符合koa规范的中间件函数service/:业务逻辑的纯度要求(禁止包含任何HTTP相关操作)middleware/:必须遵循options => async (ctx, next)的签名
3.2 路由配置的三种境界
基础路由配置
javascript复制// app/router.js
module.exports = app => {
app.get('/user/:id', 'user.info');
};
RESTful风格进阶
javascript复制app.resources('posts', '/api/posts', 'posts');
这会自动生成7个标准RESTful路由:
| HTTP Method | Path | Controller Action |
|---|---|---|
| GET | /posts | posts.index |
| GET | /posts/new | posts.new |
| GET | /posts/:id | posts.show |
| GET | /posts/:id/edit | posts.edit |
| POST | /posts | posts.create |
| PUT | /posts/:id | posts.update |
| DELETE | /posts/:id | posts.destroy |
动态路由的高级技巧
javascript复制app.get('/proxy/:service/:api*', 'proxy.handler');
这个路由可以匹配:
/proxy/user/info/proxy/order/list/2023/proxy/payment/status/123456/details
3.3 控制器开发的七个黄金法则
- 单一职责原则:每个控制器方法只处理一个业务场景
- 参数校验前置:使用egg-validate插件在入口处校验
- 异常处理统一:通过middleware统一处理错误
- 业务逻辑下沉:控制器只做流程编排,业务逻辑放入service
- 响应格式规范:统一使用
ctx.helper.success/error封装 - 日志记录完整:关键操作必须记录操作日志
- 性能监控埋点:重要接口添加性能打点
典型控制器示例:
javascript复制// app/controller/user.js
const Controller = require('egg').Controller;
class UserController extends Controller {
async show() {
const { ctx } = this;
// 参数校验
ctx.validate({
id: { type: 'int', required: true }
});
try {
const user = await ctx.service.user.find(ctx.params.id);
if (!user) {
ctx.throw(404, '用户不存在');
}
ctx.helper.success({ data: user });
} catch (e) {
ctx.logger.error('获取用户失败', e);
ctx.helper.error(e.message);
}
}
}
3.4 中间件开发实战指南
基础中间件模板
javascript复制// app/middleware/request_logger.js
module.exports = options => {
return async (ctx, next) => {
const start = Date.now();
ctx.logger.info(`[Request] ${ctx.method} ${ctx.url}`);
await next();
const duration = Date.now() - start;
ctx.set('X-Response-Time', `${duration}ms`);
ctx.logger.info(`[Response] ${ctx.status} ${duration}ms`);
};
};
中间件配置技巧
javascript复制// config/config.default.js
exports.middleware = ['requestLogger'];
exports.requestLogger = {
ignore: ['/healthcheck'],
match: ['/api']
};
常见中间件开发陷阱
- 忘记调用next():会导致请求挂起
- 错误处理缺失:应该用try-catch包裹next()
- 性能问题:避免在中间件中进行耗时操作
- 执行顺序混淆:中间件按配置数组顺序执行
4. 实战演练:构建用户管理系统
4.1 项目初始化
bash复制mkdir day3-project && cd day3-project
npm init egg --type=simple
npm install
4.2 核心功能实现
路由配置
javascript复制// app/router.js
module.exports = app => {
const { router, controller } = app;
router.resources('users', '/api/users', controller.user);
router.post('/api/users/:id/activate', controller.user.activate);
router.get('/api/users/search', controller.user.search);
};
控制器实现
javascript复制// app/controller/user.js
const Controller = require('egg').Controller;
class UserController extends Controller {
async index() {
const { ctx } = this;
const users = await ctx.service.user.list(ctx.query);
ctx.helper.success({ data: users });
}
async activate() {
const { ctx } = this;
ctx.validate({
id: 'int'
});
await ctx.service.user.activate(ctx.params.id);
ctx.helper.success();
}
}
服务层实现
javascript复制// app/service/user.js
const Service = require('egg').Service;
class UserService extends Service {
async list(query = {}) {
const { page = 1, pageSize = 10 } = query;
return await this.app.model.User.findAndCountAll({
offset: (page - 1) * pageSize,
limit: parseInt(pageSize)
});
}
async activate(id) {
const user = await this.app.model.User.findByPk(id);
if (!user) {
throw new Error('用户不存在');
}
await user.update({ status: 'active' });
}
}
5. 常见问题与调试技巧
5.1 路由不生效的排查步骤
- 检查
app/router.js是否导出函数 - 确认controller方法是否正确定义
- 使用
router.list()查看已注册路由 - 检查是否有同名路由覆盖
5.2 控制器方法未被调用的原因
- 方法名是否与路由配置一致
- 是否忘记在类中声明方法
- 方法是否被错误覆盖
- 是否使用了async/await语法
5.3 中间件调试技巧
javascript复制// 临时添加调试中间件
app.use(async (ctx, next) => {
console.log('请求进入:', ctx.path);
await next();
console.log('响应状态:', ctx.status);
});
5.4 性能优化建议
-
路由优化:
- 高频接口使用
router.verb替代router.resources - 静态路由优先于动态路由
- 高频接口使用
-
控制器优化:
- 避免在控制器中进行数据库操作
- 使用并行请求处理独立逻辑
-
中间件优化:
- 非必要中间件配置match/ignore规则
- 耗时操作放入队列异步处理
6. 学习路线建议
完成第3天学习后,建议按照以下路线继续深入:
- 第4天:Service层设计与数据库集成
- 第5天:插件机制与社区插件使用
- 第6天:单元测试与Mock技巧
- 第7天:部署与性能监控
在实际教学中发现,很多学习者在第3天会遇到第一个学习高原期。这时候最重要的是动手实践——建议创建至少三个不同类型的控制器(RESTful API、页面渲染、文件处理),并尝试编写两个自定义中间件(日志记录、权限校验)。