Elpis 学习笔记之 elpis-core

Elpis-core 核心设计思想

一. 项目定位

Elpis 是一个基于 Koa2 的全栈联系项目,Elpis-code 目标是在实践中理解 Node.js Web框架的底层工作原理, 它是一个按"框架先行,业务后至"的思路,封层递进构建的 最小化可复用框架

二.核心理念

整个框架遵循 约定优于配置的设计原则:

  • 文件即模块:不需要手动注册 controller、service、middleware等,只需要将指定文件放入对应的目录即可通过 框架 glob 自动发现并解析加载。
  • 目录即命名空间 :子目录嵌套自动转换为对象的命名空间层级,例如:app/middleware/custom-middleware.js ==> app.middleware.customMiddleware
  • 命名约定自动转换 :文件名中的-_会自动过滤并生成驼峰命名
bash 复制代码
约定示例:
  app/controller/project.js       →  app.controller.project
  app/service/project.js           →  app.services.project
  app/middleware/error-handle.js   →  app.middlewares.errorHandle

三.架构设计

项目在结构上将框架 elpis-core/业务 app/分离。

elpis-core/中不包含任何业务逻辑m只负责:创建 Koa 实例 → 加载配置 → 加载扩展 → 加载业务模块 → 注册中间件 → 注册路由 → 启动服务。这使得 elpis-core 理论上可被其他项目直接引入复用

四.加载器系统:框架的心脏

项目启动的本质是七个加载器按照顺序依次注册进入koa实例 加载器的依赖关系

arduino 复制代码
config  →  extend  →  middleware  →  router-schema  →  service  →  controller  →  router
  │          │            │               │               │            │            │
  │          │            │               │               │            │            │
  ▼          ▼            ▼               ▼               ▼            ▼            ▼
app.config app.extend app.middlewares app.routerSchema app.services app.controller app.router
           app.logger

这个顺序是不可变更的硬约束,原因如下:

  1. config 最先 :controller/service 的构造函数依赖 app.config
  2. extend 其次app.logger 等基础设施依赖 app.envapp.config
  3. middleware 第三 :中间件工厂注册到 app.middlewares,供下一步使用
  4. router-schema 第四 :校验规则注册到 app.routerSchema
  5. service 第五 :业务服务实例化,构造函数可读取 app.config / app.extend
  6. controller 第六 :控制器实例化,路由文件依赖 app.controller 已就绪
  7. router 最后:路由注册依赖以上所有模块均已就绪

五.依赖注入:"变异注入" 模式

Elpis 不使用 DI 容器(Inversify/Awilix),而是采用一种基于 Koa app 对象的变异注入模式:

  • 每个模块的工厂函数接收 app 参数
  • 通过 app 可以访问所有已加载的模块
  • 后续加载的模块能从 app.xxx 读取前置模块的成果

六.请求生命周期

scss 复制代码
请求进入
  │
  ▼
koa-static          ← 静态文件(命中则直接返回)
  │
  ▼
koa-nunjucks-2      ← 模板渲染(.tpl 文件)
  │
  ▼
koa-bodyparser      ← 请求体解析(JSON / Form / Text)
  │
  ▼
errorHandle         ← 全局异常捕获(最外层安全网)
  │
  ▼
apiSignVerify       ← API 签名校验(MD5 + 时间戳)
  │
  ▼
[路由匹配]          ← koa-router 匹配合适的 handler
  │
  ▼
apiParamsVerify     ← 参数校验(路由级,注入到 /api 路由 stack 头部)
  │
  ▼
Controller Handler  ← 业务处理

关键设计决策:参数校验为什么是路由级中间件

参数校验(apiParamsVerify)不能注册为 app 级中间件,因为在 Koa 中 app 级中间件在路由匹配之前执行,此时 ctx.params 尚未填充(始终为空对象)。

解决方案:通过 router.js 加载器在所有 /api 路由注册完毕后,将 apiParamsVerify 注入到每个路由 layer 的 stack 头部,确保它在路由匹配后、handler 执行前运行,此时 ctx.params 已正确填充。

javascript 复制代码
// router.js 核心逻辑
const routeMiddleware = app.apiRouteMiddleware;
if (routeMiddleware) {
    router.stack.forEach(layer => {
        if (layer.path.includes('/api') && layer.methods.length > 0) {
            layer.stack.unshift(routeMiddleware); // 注入到 handler 之前
        }
    });
}

七.配置管理:分层合并

配置采用 默认配置 + 环境覆盖 的两层模型:

lua 复制代码
config/config.default.js      ← 所有环境通用的基线配置
      │
      ├── _ENV=local ───→ config/config.local.js      ← 本地开发覆盖
      ├── _ENV=beta  ───→ config/config.beta.js       ← 测试环境覆盖
      └── _ENV=production → config/config.prod.js     ← 生产环境覆盖

合并方式为简单的 Object.assign({}, default, envConfig),由 _ENV 环境变量驱动环境选择。

八.错误处理哲学:统一响应,永不爆栈

Elpis 的 API 层采用统一 HTTP 200 响应策略,所有的错误通过响应体中的业务状态码来区分,而非 HTTP 状态码:

场景 HTTP 状态码 业务状态码 含义
成功 200 200 { code: 200, data, message: 'success' }
参数校验失败 200 442 { success: false, code: 442, message }
签名校验失败 200 445 { success: false, code: 445, message }
服务器异常 200 500000 { success: false, code: 500000, message, traceId }

设计考量 :这种做法常见于通过 API 网关或需要统一前端响应处理的场景,前端无需根据 HTTP 状态码做分支处理,仅需检查 code 字段。

每个服务器异常都会被赋予一个 UUID v4 的 traceId,便于日志追踪。

九.API 安全设计

签名校验

所有 /api 请求需携带两个头部:

  • s_sign:签名值 = md5(signKey + "_" + timestamp)
  • s_t:Unix 时间戳(秒级,60s 有效期防重放)

参数校验

基于 JSON Schema (draft-07) + AJV,对请求的 headersbodyqueryparams 分别校验。Schema 定义在 app/router-schema/ 下,路径自动映射:

javascript 复制代码
// app/router-schema/project.js
module.exports = {
    '/api/project/list': {
        get: {
            query: {
                type: 'object',
                properties: {
                    page: { type: 'number' },
                    pageSize: { type: 'number' }
                }
            }
        }
    }
};

无对应 schema 的路由会自动跳过校验,保证向后兼容。

十.日志策略:环境感知

  • 本地开发 (_ENV=local):直接输出到控制台(console.log),零学习成本
  • 测试/生产 :使用 log4js,按日期轮转写入 logs/app.log,同时输出控制台

切换完全由 app.env.isLocal() 驱动,业务代码只需调用 app.logger.info() / app.logger.error(),无需关心底层实现。

十一、模板渲染

使用 Nunjucks 模板引擎,采用 .tpl 文件扩展名(刻意区别于 .html,以减少与前端 HTML 文件的混淆),自动向模板注入 nameenvoptions 变量,开发环境关闭缓存保证热更新。

十二、技术选型原则

领域 选型 理由
Web 框架 Koa 2 洋葱模型 + async/await,轻量无历史包袱
路由 koa-router 社区标准,stack 可编程操控
参数校验 AJV + JSON Schema 声明式校验,标准格式,前后端可共享
日志 log4js 分级 + 按日切割,满足生产需求
模板 Nunjucks 类 Jinja2 语法,功能完备
请求库 superagent 轻量 HTTP 客户端

十三.设计理念总结

"理解底层,方能驾驭上层。"

Elpis 的设计目标不是成为下一个 Express 或 Egg.js,而是通过亲手构建一个最小可用的 Web 框架,深入理解:

  1. 框架的启动流程与模块加载机制
  2. Koa 洋葱模型的中间件执行顺序
  3. 路由匹配与中间件注入的时序关系
  4. 约定优于配置的实际落地方式
  5. API 安全(签名、校验、错误处理)的完整链路

每一个设计决策都有其明确的理由和边界,不追求功能的完备性,而是追求对核心机制理解的深度。

相关推荐
Agent手记3 小时前
制造业生产流程自动化,Agent需要具备哪些能力?深度拆解2026工业级智能体落地范式与核心架构
大数据·人工智能·ai·架构·自动化
Yunzenn4 小时前
深度分析字节最新研究cola-DLM 第 07 章:推理流水线逐行拆解 —— 从 prompt 到生成文本
人工智能·驱动开发·深度学习·chatgpt·架构·prompt·github
颖火虫盟主5 小时前
Linux 系统分层架构:从硬件通电到 systemd 进程管理
linux·运维·架构
ฅ ฅBonnie6 小时前
Hermes 与 Cloud Code/OpenClaw 架构对比分析及部署实践
人工智能·ai·架构·ai编程
实在智能RPA6 小时前
实在Agent针对金融行业Agent灾备与高可用是如何进行设计的?深度拆解金融级智能体的架构安全与连续性保障
人工智能·安全·ai·金融·架构
zhangfeng11337 小时前
主流推理模型架构的协议对比表格,和专利坑 专利埋雷
人工智能·语言模型·自然语言处理·架构·开源·开源协议
这是谁的博客?7 小时前
LangChain 框架深度解析:从 LCEL 到 Agent 架构的核心原理
ai·架构·langchain·llm·agent·架构设计
Championship.23.247 小时前
Linux 3.0 中断机制深度解析:从传统PIC到现代中断架构的转折点
linux·运维·架构·中断
高级c7 小时前
Ascend C 算子开发:10 分钟写一个高性能 MatMul
深度学习·架构·cann
@insist1237 小时前
系统架构设计师-企业信息化核心知识体系
架构·系统架构·软考·系统架构设计师·软件水平考试