ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Egg.js第4天实战:路由、控制器与中间件搞定鉴权接口

Egg.js第4天实战:路由、控制器与中间件搞定鉴权接口 2026年这个学习计划里我给自己排了一个“15天学完eggjs”的打卡清单。今天是第4天进度条刚好走到四分之一。回头看看之前几天其实都在打地基装环境、跑通Hello World、理解目录结构。但从今天开始画风要变了——今天要真正用eggjs写出带业务逻辑的接口动手实现路由、控制器和中间件。如果你也在学Node.js后端或者想找个企业级框架落地一个能上线的项目这个系列应该能帮上忙。文章里的代码都是我今天现敲现跑的没有滤镜直接复制就能跑。1. 学习路径全览eggjs 的15天应该怎么拆1.1 先回答一个关键问题15天真的够吗很多人看到“15天学完”第一反应是不靠谱我一开始也这么想。但后来我给自己定了一条规则不追求把每个API都背下来只追求“每天产出一个可运行的小成果”。比如第1天装环境第2天跑通一个接口第3天理解目录规范这样一天一个进度15天下来其实足够覆盖一个后端框架从入门到能独立做项目的核心路径。Egg.js不是一个大到没边的框架。它基于Koa封装核心思想是“约定优于配置”框架把目录结构、加载机制、插件生态都帮你定好了你要学的东西其实是一套组织代码的规则加上中间件、插件、Service这类扩展机制。这比从零学一个底层库要快得多。以我目前每天投入两小时左右的节奏来看第10天左右就能独立写一个带数据库的API服务后面的时间主要用来打磨部署和测试。当然15天能不能学完取决于你怎么定义“学完”。如果你想达到“文档倒背如流、源码全看懂”的水平那15天肯定不够。但如果目标是“给我一个需求我能用eggjs把接口写出来并且知道去哪里查文档”那15天是一个非常合理的周期。1.2 15天逐日计划表每天都有可验证的产出下面这份计划表是我在开跑之前就排好的第4天正好卡在“基础”和“核心机制”的分界线上。前3天解决环境与项目骨架第4天到第7天集中啃路由、中间件、Service和参数处理第8天开始接数据库后面进入真实项目实战。天数主题当天可验证的产出第1天Node.js环境、npm基础终端能正常执行node和npm命令第2天创建Egg项目、跑通Hello World访问7001端口能看到页面第3天目录结构、配置管理修改配置并观察生效理解默认环境差异第4天路由、控制器、中间件协作写一个带鉴权的接口并验证第5天Service层与统一响应把业务逻辑抽到Service返回统一结构第6天参数校验与异常处理非法参数能返回友好错误提示第7天模板渲染和静态资源做一个简单的列表页面第8天接入MySQL与ORM接口能读写数据库数据第9天JWT登录与完整鉴权用户登录后拿到token访问受保护接口第10天文件上传与第三方接口对接实现头像上传、调用外部API第11天定时任务与队列场景每天定时执行一次报表任务第12天单元测试与基础脚本对核心接口写单测并跑通第13天日志、监控与性能分析每个请求打印耗时日志定位慢接口第14天线上部署与进程守护部署到服务器并能稳定运行第15天项目复盘与面试整理整理一份eggjs核心知识清单这张表里第4天的定位非常明确前3天你只能“让框架跑起来”今天开始你要“让框架为你干活”。中间件和控制器是之后几乎所有功能的基础今天不打牢后面做登录鉴权、日志统计、参数校验都会很吃力。1.3 前3天结束时的基础一条最简接口长什么样学第4天之前先确认一个地基一条最简Route是什么样子。Egg.js项目的入口目录是app路由写在app/router.js控制器放在app/controller。下面这个例子在第三天就能跑通。// app/controller/home.js const { Controller } require(egg); class HomeController extends Controller { async index() { this.ctx.body { message: Hello Egg.js }; } } module.exports HomeController;// app/router.js module.exports app { const { router, controller } app; router.get(/, controller.home.index); };这里最让新手困惑的是为什么router.js里的controller.home.index不需要require因为Egg.js在启动时会自动加载app/controller目录下的所有文件并且按文件名挂载到app.controller对象上。你不需要手动管理依赖关系只需要把文件放到正确的位置。这就是“约定优于配置”的直观体现。前3天还有一个容易被忽视的点this.ctx.body xxx是同步返回响应的方式而且必须在controller方法内部直接赋值。很多人第一次写的时候会在return里写数据发现接口返回空原因就在这里。Egg.js的controller方法不需要return所有响应内容都通过this.ctx来设置。2. 第4天核心任务搞懂路由、控制器和中间件2.1 今天的学习定位从“骨架”切换到“业务开发”前3天学的目录结构、配置文件都只是骨架接口逻辑放在controller里固然能跑但如果所有东西都堆在controller里项目很快就会变成一锅粥。第4天要解决的就是“一个请求从浏览器进来到拿到响应中间到底经过了多少层、每一层负责干什么”。Egg.js处理一次请求的链路大致是请求先进路由路由把请求交给对应的controllercontroller在需要时调用service处理业务逻辑最后把结果写到ctx.body返回给客户端。而中间件就像流水线上的关卡在请求进入controller前后都可以插入逻辑。比如判断用户是否登录、打印请求耗时、设置跨域头这些都是中间件的典型使用场景。你可以把中间件理解成商场入口的一排安检口每个安检口只负责一件事有的看身份证有的查背包全部通过才能进入商场核心区域。Egg.js的中间件也是这个逻辑一个请求要经过多个中间件之后才会到达controller而每个中间件都有机会在请求继续往下走之前或者在controller处理完之后做一些事情。2.2 控制器与路由先让接口立起来先看一眼今天会频繁接触的controller完整写法。下面这个UserController有两个方法list返回一个用户列表profile返回单个用户信息。// app/controller/user.js const { Controller } require(egg); class UserController extends Controller { async list() { const users [ { id: 1, name: 小明, age: 20 }, { id: 2, name: 小红, age: 22 }, ]; this.ctx.body { code: 0, data: users }; } async profile() { this.ctx.body { code: 0, data: { id: 1, name: 小明, age: 20, role: admin }, }; } } module.exports UserController;对应的路由需要这样写// app/router.js module.exports app { const { router, controller } app; router.get(/api/user/list, controller.user.list); router.get(/api/user/profile, controller.user.profile); };这里有一个非常实用的知识点this.ctx身上挂载了几乎所有请求相关信息。ctx.query能拿到URL上的查询参数ctx.params是路由参数ctx.request.body是POST请求体ctx.headers是请求头。第4天至少要记住这几个后面写接口几乎每天都在用。关于route的写法需要注意路径别冲突。比如先注册/api/user/list再注册/api/user/:id如果两个路由都能匹配到同一个地址Egg会按注册顺序优先匹配先定义的那条。所以把精确路由写在参数路由前面是一种好习惯。2.3 第一个中间件算一下每个请求的耗时前面一直说中间件重要不如直接写一个。在app/middleware下新建文件cost.js文件名会被自动注册为中间件名称。// app/middleware/cost.js module.exports () { return async function cost(ctx, next) { const start Date.now(); await next(); const cost Date.now() - start; ctx.logger.info(${ctx.method} ${ctx.url} 耗时 ${cost}ms); }; };然后在config/config.default.js里加上以下配置就能让这个中间件对所有请求生效。// config/config.default.js module.exports () { const config {}; config.middleware [ cost ]; return config; };有两点值得展开说一下。第一中间件导出的必须是一个函数这个函数返回另一个异步函数。外层函数用来接收options配置内层函数才是真正处理请求的中间件逻辑。新手最容易犯的错是少包一层写成module.exports async function(ctx, next) {...}这样框架加载的时候会直接报错。第二config.middleware数组里的顺序就代表执行顺序。cost这个中间件里await next()前面的代码会在请求进入controller之前执行后面的代码会在controller处理完之后执行。这种先进入后返回的模式就是常说的“洋葱模型”中间件越多一层套一层但核心逻辑清晰。换个场景如果只想让某个路由走这个中间件可以不配置config.middleware而是直接在路由里加const { router, controller, middleware } app; router.get(/api/user/list, middleware.cost(), controller.user.list);把middleware.cost()放在路由处理函数前面只有这个接口会执行耗时统计。全局配置和局部配置各有用处今天先掌握两种方式后面用起来就不会局限。3. 实操项目从零写一个带鉴权校验的用户接口3.1 今天这个小目标具体是什么单看知识点还是容易飘所以我给第4天安排了一个小项目做一个用户接口其中列表接口可以直接访问但用户详情接口必须带着正确的token才能访问否则返回401。这个场景非常常见它同时用上了controller、router、中间件三种能力又把“鉴权”这个核心需求落地了一次。需要额外说明的是这里为了专注中间件写法token直接写死在代码里真实项目绝对不能用这种方式。到第9天我们会用JWT做完整的登录鉴权链路今天只为了让中间件的触发逻辑看得见、摸得着。3.2 初始化项目推荐先用官方脚手架如果前3天已经创建过项目直接用旧项目就行。如果今天想从头建一个推荐用egg官方脚手架一条命令搞定。mkdir egg-day4 cd egg-day4 npm init egg --typesimple npm install npm run devnpm init egg --typesimple会生成一个最小的项目模板。安装依赖完成后npm run dev启动默认端口是7001浏览器访问http://127.0.0.1:7001能看到默认页面。如果你之前装了其他目录结构比较乱的项目建议今天统一用脚手架重新初始化一遍。Egg.js对目录结构很敏感文件放错位置不会直接报错但路由和控制器的自动加载会失效排查起来非常痛苦。3.3 核心代码用户列表、详情与鉴权中间件首先新建app/controller/user.js写入用户controller。这里用内存数组模拟数据库好处是不需要安装任何东西思路也直观后续第8天把数据源换成MySQL时只改Service层就行。// app/controller/user.js const { Controller } require(egg); class UserController extends Controller { async list() { const users [ { id: 1, name: 小明, age: 20 }, { id: 2, name: 小红, age: 22 }, ]; this.ctx.body { code: 0, data: users }; } async profile() { this.ctx.body { code: 0, data: { id: 1, name: 小明, age: 20, role: admin }, }; } } module.exports UserController;然后在app/middleware目录下新建auth.js这就是今天的主角——鉴权中间件。// app/middleware/auth.js module.exports () { return async function auth(ctx, next) { const token ctx.headers.token; if (!token || token ! egg-2026) { ctx.status 401; ctx.body { code: 401, message: 未授权请携带正确的 token }; return; } await next(); }; };最后修改app/router.js把接口和中间件串起来。list接口不需要鉴权profile接口在controller之前插入auth中间件。// app/router.js module.exports app { const { router, controller, middleware } app; router.get(/api/user/list, controller.user.list); router.get(/api/user/profile, middleware.auth(), controller.user.profile); };这里有一个细节值得注意middleware.auth()写的是调用不是引用。auth定义的时候是一个函数它返回真正处理请求的中间件函数所以必须加括号调用。如果写成middleware.auth框架只会把函数本身当路由处理逻辑传入而不会执行它接口会直接报错。3.4 启动验证三条curl命令看懂全流程代码写完启动项目npm run dev启动日志如果提示egg started on http://127.0.0.1:7001说明一切正常。接下来用三条命令验证效果。第一条命令请求列表接口不需要任何额外header应该正常返回数据。curl http://127.0.0.1:7001/api/user/list响应{code:0,data:[{id:1,name:小明,age:20},{id:2,name:小红,age:22}]}第二条命令直接请求用户详情接口因为没有携带token中间件会把请求拦下来返回401。curl http://127.0.0.1:7001/api/user/profile响应{code:401,message:未授权请携带正确的 token}第三条命令带上正确的token头再次请求这次中间件放行controller里的profile方法正常执行。curl http://127.0.0.1:7001/api/user/profile -H token: egg-2026响应{code:0,data:{id:1,name:小明,age:20,role:admin}}这三条命令基本把第4天的核心链路走通了不带token被中间件拦截带token放行到controller最终返回业务数据。你在浏览器里也能看到同样的效果只是浏览器设置header比较麻烦推荐用curl或者postman做这类验证。4. 第4天常见问题与排查技巧4.1 高频报错与解决速查表第4天开始涉及多个文件联动报错的概率明显比前3天高。归纳一下今天最容易遇到的几个问题和排查方向。现象可能原因排查与解决访问接口返回404路由没有注册或注册路径和访问路径不一致检查app/router.js路径确认文件和导出结构端口被占用启动失败上一个dev进程没关干净lsof -i:7001找到PID后kill或改config.port接口返回404但路由存在controller返回了undefined或没有赋值ctx.body检查controller方法结尾有没有this.ctx.body ...中间件完全没有执行没有在config或router中声明中间件确认config.middleware数组和文件名匹配中间件执行报错middleware.auth少了括号写成middleware.auth()调用不能只传函数引用修改代码后行为不变dev进程崩了或文件没保存重启npm run dev再看启动日志有没有语法错误这些坑看起来琐碎但每一条都是我实际踩过或者帮别人排查过的。尤其是404问题新手很容易在controller、router、文件路径三者之间绕晕排查顺序就是先看启动日志有没有加载文件再看路由注册对不对再看controller是否执行。4.2 调试技巧让日志替你说话写后端和写前端最大的差异是后端没法随时打开控制台看状态很多问题要靠日志。第4天开始建议有意识地把调试习惯从console.log切换到框架日志。在controller和中间件里可以直接用ctx.logger打印日志ctx.logger.debug(这是调试日志); ctx.logger.info(这是普通日志); ctx.logger.warn(这是一个警告); ctx.logger.error(这是一个错误);ctx.logger会按照级别输出到终端同时写入logs目录下的日志文件。这个习惯要尽早养成因为后面项目大了线上环境根本看不到终端只能靠日志排查问题。如果启动阶段就想看框架内部加载过程可以带DEBUG环境变量启动DEBUGegg* npm run dev启动日志会打印很多内部信息包括路由注册、中间件加载、插件初始化等。新手第一次看到那么多日志可能会懵但只需要重点关注有没有类似router: register和middleware: use的输出。4.3 复盘今天值得记下来的三个点最后对第4天做一个简短复盘我认为有三个点是最值得写进笔记的。第一中间件函数的结构是“箭头式”的外层函数接收参数内层异步函数处理请求await next()前后分别是请求前和响应后逻辑。这个结构理解了以后写任何中间件都能一眼看懂。第二controller不负责“思考”只负责“响应”。这只是一个开始今天数据写在controller里看起来还凑合但后面一旦引入数据库查询、第三方接口调用controller就会变得臃肿。第5天会把业务逻辑抽到Service层今天先记住这个演变方向。第三路由层面可以做鉴权但真正的鉴权往往会落在更底层。今天我们是在每个需要保护的接口前面手动加中间件实际项目里可能有几十个接口都要保护这时候就会用到全局中间件加路由白名单机制这个内容到第9天做JWT时会完整展开。最后再分享一个个人体会。我之前第一次用Egg.js的时候喜欢在一个中间件里写好多逻辑既想校验token又想记录日志还想处理异常结果后来每次改动都心惊胆战。中间件这个东西功能越单一越好维护。今天做完这个带鉴权的小项目最大的收获不是学会了写那个auth.js而是理解了“每个中间件只干一件事”这种设计习惯。带着这个感觉继续学后面的Service、插件、数据库你会发现Egg.js真正厉害的地方不是帮你写好了代码而是帮你养成了一套还算不坏的后端组织方式。
返回列表