多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

15天实战复盘:从Koa原理到Egg.js企业级部署与安全加固

15天实战复盘:从Koa原理到Egg.js企业级部署与安全加固 去年这个时候我给自己定了个目标15天系统学完 Egg.js从 Koa 的原理到企业级项目的落地一天不落。今天是第 15 天正好是整个学习周期的收尾日。按我的习惯收官这天不学新东西而是把前面 14 天攒下来的知识点重新串一遍再做一次完整的实战验收——把一个真实项目从代码推到服务器用公网域名访问跑通日志、监控、部署这一整套企业级流程。这篇文章就是今天的完整记录包含我对 Egg.js 架构设计的理解、线上部署的关键操作、性能优化和安全加固的实际做法以及这 15 天里踩过的那些坑。不管你是刚开始学 Node 框架还是已经在用 Egg 做业务开发这篇内容都能当作一份查漏补缺的参考。1. 第15天学习目标与整体回顾1.1 为什么最终选定了 Egg.js先说结论我选 Egg.js 不是因为它是国产框架或者社区热度高而是因为它解决了 Koa 和 Express 在企业场景下的几个核心痛点。Koa 足够轻量洋葱模型中间件设计非常优雅但框架本身几乎不对项目结构做任何约束。一个小项目三个人开发三个人的目录结构可能都不一样有人把路由写在 app.js 里有人单独建 routes 文件夹有人习惯在中间件里直接查数据库。这种自由度在小项目里没问题一旦团队扩大维护成本会呈指数级增长。Express 的生态成熟但同样存在这个问题而且它把太多东西都交给开发者自己做决定。路由写法、中间件加载顺序、静态资源处理方式每个项目都不一样。新人接手老项目光理解代码结构就要花掉好几天时间。Egg.js 的思路完全不同约定优于配置框架把你可能出现的各种无序状态全部收拢起来。什么文件放在什么位置、什么时候加载、加载顺序如何都是确定的。你打开任何一个 Egg 项目看到的是同一种目录结构同一种加载机制。这种“确定性”在团队协作和企业级项目里价值极大。1.2 14 天的学习路线全景回顾我给自己设计的学习路线分三个阶段每个阶段的侧重点完全不同。第一阶段是基础扫盲包括 Node.js 的事件循环机制、Koa 中间件原理、HTTP 协议的关键特性。这个阶段用了 3 天时间核心目标是把后端开发的基本功打牢。没有这部分基础直接上手 Egg 很容易被各种概念绕晕。比如不理解洋葱模型就看不懂中间件执行顺序不理解事件循环线上遇到性能问题就无从下手。第二阶段是从第 4 天到第 10 天在 Egg.js 框架的各个核心模块里挨个过一遍。从 Router、Controller、Service 到 Middleware、Extend、Config、Plugin再到 Sequelize 集成、Redis 缓存、JWT 鉴权、定时任务、WebSocket 实时通信。每个模块我都做了一个小的 demo确保不是“看了就算会了”。第三阶段是第 11 天到第 14 天重点做项目实战和测试。我搭建了一个简化版的待办事项 SaaS 系统包含用户注册登录、任务管理、标签分类、任务提醒、团队协作几个模块。这个项目复杂度适中既不会因为太简单而漏掉企业级开发的细节也不会因为过于复杂导致 4 天做不完。单元测试、接口测试、代码覆盖率也在这一阶段补齐。第 15 天的工作就是把前 14 天的内容整合成一张知识地图然后完成部署上线这个最后的闭环。1.3 今天要完成的三个核心任务复盘这天我给自己定了三个必须完成的任务。第一把 Egg.js 的架构设计原理彻底吃透。不是简单地知道“框架帮你做了什么”而是搞清楚它“为什么这么做”。只有理解了设计者的思路遇到框架覆盖不到的场景时才能自己找到解决方案。第二把待办事项 SaaS 项目从本地开发环境推上线上服务器。这一步涉及的链路很长配置生产环境、构建代码、启动服务、Nginx 反向代理、HTTPS 证书、日志切割、进程守护任何一个环节出问题线上服务就起不来。这也是全网大量 Egg 教程覆盖最少的部分——大部分教程都停在“本地跑通了”这个阶段。第三做一次完整的安全检查。企业的服务端代码不能只考虑功能实现还要面对真实攻击SQL 注入、XSS 脚本注入、CSRF 伪造请求、接口被刷、文件上传漏洞。这些内容如果不在学习阶段重视起来上线后会付出巨大代价。2. Egg.js 架构原理解码从 Koa 到企业级的关键差距2.1 进程模型设计Master、Agent 与 Worker 的分工先把这个概念放在最前面讲因为它是 Egg.js 和大部分 Node 框架最大的区别。一个 Egg 应用在线上启动后服务器里会同时存在三类进程。Master 进程是管理进程负责启动、监控和管理其他进程它本身不处理业务请求。Agent 进程是辅助进程负责处理一些不适合在 Worker 里做的后台任务比如拉取远程配置、执行定时任务、与第三方中间件建立长连接。Worker 进程是真正处理 HTTP 请求的进程一般会启动多个由 Master 统一调度实现多核 CPU 的负载均衡。使用过 Node.js Cluster 模块的同学对这个模型应该不陌生。Egg 等于把 Cluster 模块封装好了并且在上面扩展了 Agent 这个角色。为什么需要 Agent我举个例子假设你的服务需要监听 RabbitMQ 消息队列。如果每个 Worker 都建立一条 RabbitMQ 连接那么 4 个 Worker 就有 4 条连接不但浪费资源还可能出现消息被多个 Worker 重复消费的问题。正确的做法是让 Agent 进程单独建立一条连接收到消息后通过框架提供的消息机制转发给某个 Worker 去处理。我在第 6 天单独写了一篇文章讲这个问题因为理解进程模型对于部署部署、排查线上问题非常重要。你看到某个定时任务重复执行了三次第一反应不应该是检查代码逻辑而是要想到这个任务可能被部署在了每个 Worker 上启动了三次。Egg 有自己的约定但不是所有开发者都知道。2.2 加载器机制为什么约定优于配置能落地Egg 框架启动时内部有一个完整的加载流程官方称为 Loader。它按照严格的顺序加载各个模块Config 配置、Plugin 插件、Extend 扩展、Middleware 中间件、Controller 控制器、Service 服务、Router 路由。这个顺序是有讲究的。为什么中间件要在控制器之前加载因为中间件是请求到达控制器之前的处理层必须先准备好。为什么 Extend 要在 Middleware 之前加载因为中间件内部可能会调用ctx.helper这类扩展方法如果扩展还没加载中间件执行时就会报错。加载器的设计保证了所有模块之间的依赖关系是自洽的不需要你在代码里手动控制加载顺序。这也是 Egg 这个框架的聪明之处。把加载逻辑固定下来开发者只需要按照约定把文件放到对应目录框架就会自动完成一切。我自己写过一个轻量级的加载器模拟了 Egg 的这套行为虽然只覆盖了三分之一的功能但深刻体会到了设计这种机制需要思考多少边界场景。读一遍源码之后你会发现框架里每个配置项背后的逻辑都有据可循。2.3 生命周期钩子应用启动的各个阶段Egg 提供了五个生命周期方法分别在不同阶段被触发。configWillLoad在配置加载前触发configDidLoad在配置加载完成后触发didReady在应用启动完成、可以接收请求前触发serverDidReady在 HTTP 服务启动后触发beforeClose在应用关闭前触发。这些钩子的使用场景非常具体。比如你要在应用启动时初始化数据库连接应该放在didReady里你要在应用对外提供服务前做一些检查可以放在configDidLoad之后。我刚学的时候容易把代码一股脑塞进didReady后来才意识到不同阶段的代码执行顺序对应用行为有直接影响。一个典型的场景应用启动时需要从配置中心拉取远程配置。如果放在didReady所有 Worker 进程都会执行一次拉取操作产生 N 次重复请求。正确做法是把拉取操作放到 Agent 进程的didReady里拉取完通过agent.messenger.send广播给各个 Worker。这个细节只有在你深入理解进程模型和生命周期之后才能写出正确实现。3. 第15天实战把一个模块从开发推到公网可访问3.1 生产环境目录结构设计与关键配置解析今天上午我把之前开发的待办事项 SaaS 项目的生产环境配置完整写了一遍。生产环境的目录结构和开发环境大体一致但在 config 目录下多了两个关键文件config.prod.js和config.prod.unittest.js。Egg 的配置加载有优先级规则config.default.js是基础配置所有环境都加载config.prod.js只在生产环境下加载覆盖默认配置中的对应项config.prod.unittest.js是为了在测试环境模拟生产配置而提供的。这个分层设计非常实用不同环境下的数据库地址、Redis 地址、日志级别、密钥都可以分开管理不会互相干扰。我还调整了几个关键配置项。logger.level设置为INFO避免生产环境打印过多无用的 DEBUG 日志logger.consoleLevel设置为NONE关闭控制台日志输出全部写入文件bodyParser的jsonLimit设置为1mb防止超大请求体拖垮服务器。这些参数看起来不起眼但在高并发场景下会直接影响系统稳定性。3.2 部署流程从本地代码到服务器上稳定运行下午两点开始正式部署。我采用的方案是本地用 Git 推代码服务器上用git pull拉取最新代码然后安装依赖、启动服务。首先在服务器上安装了 Node.js 运行环境版本选择 20 LTS这是当前生态兼容性和稳定性最均衡的版本。npm install时我加上了--production参数只安装生产环境依赖避免开发依赖占用磁盘空间也减少潜在的攻击面。启动命令使用的是 Egg 官方提供的egg-scripts。这个工具除了启动服务还负责两个重要的运维任务日志切割和进程守护。以 4 个 Worker 进程为例启动命令是egg-scripts start --workers4 --port7001 --titletodo-app。--title参数给进程起了个名字方便后续用ps命令和日志工具过滤查询。启动成功后我用curl http://127.0.0.1:7001/api/health验证服务是否正常响应。这个健康检查接口返回{ status: ok }确认服务启动无误后才在 Nginx 里配置反向代理把 80 端口的外部请求转发到 7001 端口的 Node 服务上。3.3 Nginx 反向代理配置与 HTTPS 证书部署Node 服务不建议直接暴露在公网一般前端加一层 Nginx 做反向代理。Nginx 处理静态资源、实现负载均衡、配置 HTTPS 终端这些都是它的强项。我的 Nginx 配置核心部分如下server { listen 443 ssl; server_name api.todo-example.com; ssl_certificate /etc/nginx/ssl/todo-example.com.pem; ssl_certificate_key /etc/nginx/ssl/todo-example.com.key; location / { proxy_pass http://127.0.0.1:7001; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }有两点需要注意。第一proxy_set_header不能省略否则 Egg 应用拿到的是 Nginx 的 IP 而不是真实用户 IP日志和风控都受影响。第二HTTPS 证书我用的是云厂商提供的免费 DV 证书有效期一年续期的时候记得提前做好准备。配置完成后我重启了 Nginx用浏览器访问https://api.todo-example.com/api/health看到返回的 JSON 数据整个链路完整跑通了。这一刻差不多是下午四点二十距离动手配置生产环境过去了两个小时二十分钟。4. 性能调优三板斧缓存、Cluster 与并发控制4.1 数据库连接池与 Redis 缓存实战调优服务正常跑起来只是第一步接下来要做的是性能调优。我第一个优化的目标数据库层。Egg 整合 Sequelize 后默认连接池配置相对保守。我把max从默认值调整到 20min设置为 5idle时间设置为 30000 毫秒。连接池是数据库访问的关键机制每次请求都新建数据库连接成本很高复用连接能显著降低延迟。调大max值让系统在高并发下能同时处理更多数据库操作但也不能无脑调大——每个连接都会占用服务器内存连接数超过数据库上限反而会拖垮数据库。第二个优化点是数据缓存。我把用户信息和标签列表这类高频读取、低频修改的数据加入了 Redis 缓存。以用户基本信息为例第一次查询从 MySQL 读取然后写入 Redis设置 600 秒过期时间后续请求直接从 Redis 拿数据读取速度从几十毫秒降到 1 毫秒以内。同时我设置了缓存更新策略用户修改资料时先更新 MySQL然后主动删除 Redis 中的旧缓存下一次请求自动回源拉取新数据避免了缓存和数据库不一致的问题。// app/service/user.js async getById(id) { const cacheKey user:${id}; const cached await this.app.redis.get(cacheKey); if (cached) return JSON.parse(cached); const user await this.ctx.model.User.findByPk(id); if (user) { await this.app.redis.set(cacheKey, JSON.stringify(user), EX, 600); } return user; }4.2 Worker 数量的设置逻辑与压测实战Worker 进程数量是 Node 服务性能的核心参数。合理的设置思路是CPU 核心数 - 1。为什么减一因为要给操作系统和 Agent 进程预留 CPU 资源。我测试用的云服务器是 4 核所以我设置--workers3。为了验证性能我用wrk工具做了一次简单的压测。压测前先关闭了安全中间件的部分保护功能避免干扰性能表现测试方式是对 这个接口请求得到真实数据。wrk -t4 -c100 -d30s http://127.0.0.1:7001/api/tasks压测结果显示在 4 核服务器上运行 3 个 Worker接口 QPS 稳定在 1800 左右平均响应时间约 20 毫秒。这个成绩对于待办事项系统的场景已经完全够用。如果把 Worker 数加满到 4 个QPS 提升幅度不大但 CPU 负载明显上升稳定性反而下降。这说明盲目增加 Worker 数量不一定是好事找到硬件资源和服务吞吐量之间的平衡点才重要。4.3 并发控制与实践中的保护策略高并发场景下服务端除了“承受请求”还要学会“拒绝请求”。我实现了两层保护。第一层是中间件级别的限流。基于 Redis 实现了一个简单的滑动窗口限流器每个用户 ID 每分钟最多允许发送 60 次写操作请求一旦超出直接返回 429 状态码。这种思路在真实业务中很常用比如防止短信接口被恶意刷、防止用户脚本高频爬取数据。// app/middleware/rate_limiter.js module.exports () { return async function rateLimiter(ctx, next) { const userId ctx.state.user ctx.state.user.id; if (!userId) return next(); const key rate:${userId}:${Math.floor(Date.now() / 60000)}; const count await ctx.app.redis.incr(key); if (count 1) await ctx.app.redis.expire(key, 120); if (count 60) { ctx.status 429; ctx.body { code: 429, message: 请求太频繁请稍后再试 }; return; } await next(); }; };第二层是数据库层的并发保护。我在任务表的更新操作里用乐观锁机制更新前先校验任务的version字段如果版本号不匹配就说明数据已在其他地方被修改直接拒绝本次更新并返回提示。这个机制防止了两个用户同时修改同一份数据时发生覆盖写。实践保护策略时有一个原则拒绝请求要快速不要让请求在业务代码里长时间流转。限流中间件放在最前面Redis 的原子操作保证计数准确整个过程耗时不到 5 毫秒。5. 安全加固清单企业开发必须补齐的5个细节5.1 Egg.js 内置安全机制与常见攻击面梳理Node.js 服务最容易遭受的攻击类型我心里有一个清单和它们一一对应的防护方式也存在。XSS 跨站脚本攻击。Egg 内置了egg-security插件默认开启 XSS 防护自动对输出到模板的内容做转义。我自己额外做了一层加固设置完善的 CSP 响应头限制页面只能加载同源资源的 JavaScript 脚本有效降低注入风险和劫持风险。CSRF 跨站请求伪造攻击。Egg 默认启用 CSRF 校验在表单提交时需要携带正确的_csrftoken。我在前端登录成功时从服务端获取这个 token后续所有写请求都在 header 里带上服务端收到请求先校验 token 合法性。要特别注意的是不能因为开发阶段嫌麻烦就关掉这个功能——我见过不少团队因为本地调试不方便直接把 CSRF 全局关闭了这种操作等于给攻击者留了后门。SQL 注入。Egg 项目中通过 Sequelize 统一使用参数化查询ORM 框架的绝大多数场景天然免疫了 SQL 注入。但我写复杂查询时还是坚持只用参数绑定方式绝不拼接字符串。5.2 JWT 鉴权安全细节与 Token 生命周期管理用户登录后我返回的是 Access Token 和 Refresh Token 两个 token。Access Token 有效期设在 15 分钟每次请求都带着它来做身份校验Refresh Token 有效期设为 7 天Access Token 过期后用它换取新 token不需要用户重新登录。这里有两个容易踩的坑。第一Access Token 必须设置较短的过期时间。设置得太长token 一旦泄露攻击者就能在长时间内窃取用户身份。15 分钟的过期时间意味着即使 token 泄露攻击窗口也非常有限。第二token 中只放用户 ID 和必要的非敏感信息绝不能把密码、手机号、邮箱这类敏感数据写进 payload——token 是可以用 base64 解码直接看到的。JWT 密钥的管理也很关键生成 32 字节以上的随机字符串作为密钥保存到环境变量而不是代码仓库里。我把所有密钥都放到了.env文件中并确保.env进入了.gitignore。5.3 文件上传漏洞类型校验与路径安全待办事项系统允许用户上传附件这一功能环节如果不做安全处理会成为严重的攻击突破口。我设置了多重校验强制。第一重限制文件类型只允许图片类型的文件且检查文件的mimetype字段、文件扩展名和文件头部的魔数是否一致三者必须匹配才能通过。第二重限制文件大小单文件最大 5MB超出直接拒绝。第三重使用随机文件名上传的附件用 UUID 重命名文件原名称不进入存储路径杜绝路径穿越漏洞。存储路径的处理同样重要。上传文件统一存放在应用目录之外的独立文件夹里静态访问通过 Nginx 单独配置映射。这样即使某个附件被恶意替换也不会影响 Node 应用本身的代码安全。5.4 请求体大小限制与超时控制攻击者可以利用超大请求体拖垮服务器资源响应很慢的接口也会占用大量连接。我在config.prod.js中设置了bodyParser: { jsonLimit: 1mb, formLimit: 1mb, } timeout: 3000,timeout: 3000的含义是如果接口处理超过 3 秒框架会直接终止进程处理并返回 504 状态码。这是一个主动止损措施长时间占用的请求会占满 Worker 进程影响所有用户的正常使用。这个超时不能设置过大或过小需要根据业务情况调整但绝不能完全关闭。我把这个值在前一天用脚本连续压测验证过一遍确保不会误伤正常请求。6. 生产环境调试方法论日志、监控与告警6.1 Egg.js 日志体系拆解从 console 到分布式日志本地开发可以随时看控制台输出但线上服务无法这么做——服务器上几百个请求的日志挤在一堆没有结构化处理根本没法看。Egg 的日志系统默认按规则切割存储。应用日志写在logs/todo-app/web.log异常日志写在logs/todo-app/error.logAgent 进程日志单独写在agent.log核心日志记录类似 SQL 慢查询、未捕获异常等事件。每天零点egg-scripts 会自动完成日志切割按天归档避免单个日志文件无限膨胀。我还在关键业务节点打了结构化的日志用户登录成功、任务创建、任务状态变更。这些日志统一通过this.logger.info输出并附带requestId关联字段。requestId是每次 HTTP 请求生成的唯一标识贯穿整个请求链路。排查问题时只要拿到某个用户的requestId就能把他在系统里的所有操作串起来定位问题极为高效。// app/extend/context.js module.exports { get requestId() { return this.get(X-Request-Id) || this.app.uuid.v1(); }, };6.2 性能监控指标不只是看 CPU 和内存监控层面我引入了两类指标硬件指标和应用指标。硬件指标是服务器 CPU、内存、磁盘使用率。这些指标通过云监控平台就能直接看到但我提醒自己不要在收到告警后才去看而是设置了每日定时巡检早晨 8 点检查昨晚的监控数据提前发现异常。应用指标更精细接口平均响应时间、99 分位响应时间、QPS、错误率、事件循环延迟、GC 暂停时间。最后一个指标在 Node 服务里尤其重要事件循环延迟过高意味着主线程被阻塞即使 CPU 看起来空闲用户请求也已经排队很久了。我特意在测试环境模拟了一次事件循环阻塞的场景在一个接口里执行了一个耗时的同步计算结果发现响应时间从 20 毫秒直接飙升到 3 秒页面请求全部排队等待。那一刻我对“保持事件循环不阻塞”这条原则有了切肤体会。6.3 线上问题排查的完整流程一次接口超时的复盘下午四点半我在监控面板看到某接口平均响应时间异常升高从正常的 15 毫秒涨到 400 毫秒。我按照自己定的排查流程走了一遍。第一步打开错误日志搜索刚才时间窗口内的error.log发现大量 Sequelize 连接超时错误。第二步检查 MySQL 数据库连接数发现连接数接近阈值上限。第三步进一步排查发现某个凌晨任务脚本运行异常产生了大量重复的数据库查询请求把连接池打满了。第四步修复脚本并补充了防重入机制加了标志位避免脚本并发执行同时把数据库连接数告警阈值从 200 调低到 120。整个排查过程用了大约 35 分钟。这个案例让我确信一件事没有系统化的日志和监控生产环境的技术问题定位永远只能靠猜。7. 那15天我踩过的10个高频坑复盘向7.1 阶段一的基础设施坑初始化失败与依赖安装现在来做整个 15 天的踩坑回顾。第一个坑是 Egg 脚手架初始化失败。使用npm init egg --typesimple时因为网络原因导致egg-init-config这个辅助包没有成功下载初始化过程直接中断。后来切换了 npm 镜像源才顺利完成。第二个坑是安装依赖时npm install出现ERESOLVE unable to resolve dependency tree错误。当时项目里有多个版本的node-gyp依赖冲突我在命令后加上--legacy-peer-deps参数临时解决。这里要提醒大家加这个参数能跳过依赖冲突检测但只是“跳过”不等于“解决了冲突”。后面有时间还是要认真检查依赖树把根因修掉。第三个坑影响了整整一天开发环境一切正常一部署到生产环境接口全部 404。查了很久才意识到是构建产物没有正确复制到目标目录。Egg 项目的dist文件夹需要执行npm run build重新生成我偷懒直接把源码目录打包上传结果启动流程找不到对应的构建产物。7.2 阶段二的业务代码坑中间件顺序与 Service 周期问题第四个坑是 Egg 中间件加载顺序。我写了一个统计请求耗时的中间件想让它统计所有接口的响应时长却发现日志里记录的耗时明显偏短。后来才明白 Egg 默认中间件按照config.middleware数组的顺序加载但如果某些中间件是通过依赖注入方式注册的它们的执行时机和我预期的不同。自定义的第一个中间件可能已经在路由处理之前执行完自然无法统计后续全部耗时。第五个坑和 Sequelize 相关。我在 Service 里直接调用this.ctx.model.User但项目里另一个同事在普通工具函数里直接require了 model 文件导致应用启动时模型还没完成初始化调用时直接报错。解决方法是规范团队约定访问 Models 一律通过this.ctx.model禁止在其他模块直接 require。第六个坑是在app/extend/context.js里给ctx挂载了一个缓存对象代码逻辑看起来没有任何问题但运行一段时间后发现内存持续增长。问题在于这个缓存对象没有过期清理机制变成了无限增长的 Map。后来我改成在 Redis 中实现缓存并限制了 key 的数量上限问题才解决。7.3 阶段三的部署与运维坑多进程任务与日志权限第七个坑是我在 Controller 里直接使用setInterval执行定时任务上线后发现有 4 个 Worker 进程这个定时器被创建了 4 次重复执行了 4 遍。这是没有理解进程模型的典型表现。正确做法是使用 Egg 自带的定时任务模块它天然支持只在某一个进程中执行或者在 Agent 进程里通过 messenger 逻辑实现。第八个坑和日志文件权限相关。Nginx 跑在nginx用户下Node 应用跑在www用户下两边要写入同一个日志目录。目录权限不是Nginx用户能写的导致请求转发时 Nginx 直接返回 502。排查了很久最后才想到是权限问题。第九个坑是线上环境npm install --production安装依赖后缺少egg-scripts的可执行文件路径。后来我把egg-scripts加到了 package.json 的 dependencies 里而不是 devDependencies问题解决。第十个坑是.env文件在代码提交时被误加入版本库导致数据库密码和 JWT 密钥泄露到私有仓库。虽然仓库是私有的但这种敏感信息出现在版本历史里总是不安全。我用git filter-branch清理了历史记录把所有密钥全部轮换了一轮。7.4 踩坑记录速查表问题现象根本原因正确处理方式脚手架初始化失败辅助包下载失败切换镜像源重试npm 依赖树冲突依赖版本不兼容使用--legacy-peer-deps临时绕过后续修复依赖树生产环境接口 404构建产物未同步打包后先跑 smoke test 再部署中间件耗时统计不准未理解加载顺序用egg-controller内部钩子代替Model 初始化报错绕过了 ctx 访问模型统一从ctx.model获取模型实例内存持续增长缓存对象无回收机制使用 Redis 代替进程内缓存定时任务重复执行多 Worker 重复执行定时器使用 Egg 内置支持单进程的定时器Nginx 返回 502日志目录权限问题统一用户权限或调整目录属主生产启动命令找不到egg-scripts 在 devDependencies移到 dependencies 中密钥配置泄露.env 被 git 误提交轮换密钥清理 Git 历史8. 学完第15天之后第16天怎么选8.1 从 Egg.js 到微服务RPC 与消息队列15 天学完 Egg.js只是拿到了后端开发的入场券。第 16 天开始我有几个明确的方向可以继续深入。第一个方向是微服务化改造。当前项目是单体应用所有功能模块都塞在一个应用里。模块多了以后问题会逐渐暴露某个模块发布新版本整个应用都要重启影响所有用户某个模块流量爆发只能给整个集群扩容成本高。拆分为微服务后每个服务独立部署、独立扩容团队也能按服务拆分独立交付。Egg.js 生态支持通过sofa-rpc这类 RPC 框架接入微服务架构学习曲线相对平缓因为它沿用了统一的进程模型和生命周期机制。第二个方向是消息队列。很多业务场景本身适合异步化处理而不是同步请求。比如用户上传头像后需要生成缩略图完全可以在用户上传成功后发布一条消息让后端异步去处理。Egg 框架提供了 task 机制和 Agent 进程天然适合接入 RabbitMQ 或 Kafka 这类消息中间件。学完 Egg 的基础用法后再学消息队列理解起来会非常顺因为框架本身已经为你处理了进程通信的部分。8.2 扩展知识边界TypeScript、Serverless 与底层机制第三个方向是 TypeScript 化改造。Egg.js 官方对 TypeScript 有完善的支持通过egg-bin dev就能直接运行 TS 代码。把项目迁移到 TypeScript 能显著提升代码质量特别是在多人协作的场景下类型定义本身就是一种文档。我计划把待办事项项目的核心模块先迁过去重点体验类型带来的安全和约束。第四个方向是 Serverless 部署。现在的部署方式是自己管理服务器但 Serverless 架构下不再关心服务器本身只需要部署函数代码即可。Egg 框架也有对应的 Serverless 适配方案把 Egg 应用打包成函数形式运行。对于流量波动大、请求模式不规则的应用Serverless 的弹性伸缩优势非常明显按量计费的模式的成本效率也更高。第五个方向是回到语言和原理层面深入研读 libuv 的事件循环机制理解setTimeout、setImmediate、process.nextTick在事件循环中的执行顺序学习 V8 引擎的内存管理和垃圾回收机制研究 Node 的多线程能力比如worker_threads在计算密集型任务中的应用场景。这些底层知识不会直接体现在业务代码里但会在遇到疑难问题时给你清晰的排查思路。8.3 横向迁移学完 Egg.js 后能更快上手哪些框架经过这 15 天一个明显的好处是再去看其他 Node 后端框架时理解门槛低了很多。NestJS是目前社区热度很高的 Node 框架它的核心思想是依赖注入、模块化、装饰器。它的模块化组织和 Egg 有所区别但因为已经理解了框架中约定与配置的取舍理解 NestJS 的设计思路并不费力。同时 NestJS 对 TypeScript 有原生支持如果你从 Egg 直接切到 NestJS你会发现配置方式、控制器、服务层、中间件的概念都是相通的只是封装方式不同。Midway.js是淘宝开源的企业级框架和 Egg.js 有深厚渊源。如果你已经掌握 Egg再看 Midway 基本能无缝上手它继承了 Egg 的很多理念同时加入了依赖注入和 TypeScript 的完整支持。整体学习成本极低最多一两天就能上手。之前在 Koa 里写业务时反复纠结的项目结构问题、中间件顺序问题、进程管理问题在 Egg 里全部迎刃而解。这种思维方式的转变比会用一个框架更重要的是理解了“框架解决什么问题”和“框架是怎么解决这些问题的”。写在最后一个15天学习者的真实感受下午六点半我又打开服务器的监控面板看到部署上去的服务稳定运行了四个多小时。所有接口响应正常错误日志里干干净净。那一瞬间15 天前只会写几个简单 Koa demo 的我和现在已经把完整项目从开发推到线上并跑通安全、日志、监控、告警全流程的自己确实不太一样了。这其中有个很深的体会学习的顺序很重要。如果一开始就直接啃 Egg.js 的官方文档没有先理解 Koa 的洋葱模型和 Node 的事件循环很多概念会一直糊在脑子里。反过来把这些基础打牢之后Egg 的文档读起来思路非常顺畅官方文档背后的设计意图也能清晰理解。15 天的时间不长但只要每一天都围绕“搞懂原理、动手写过代码”这个标准来做完全可以在有限时间内获得扎实的收获。第 16 天我会从 TypeScript 改造开始继续前进。后端这个方向走到深处发现自己不懂的东西永远比懂的多这恰恰是它最有意思的地方。如果你也打算用 15 天学一遍 Egg.js我的建议是不要只看教程更不要只看不用。每天坚持写至少一个能跑起来的 demo遇到问题记录下来、亲手解决掉。第 15 天复盘那天你的踩坑记录就是整个学习过程中最宝贵的产出。
返回列表