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
这个顺序是不可变更的硬约束,原因如下:
- config 最先 :controller/service 的构造函数依赖
app.config - extend 其次 :
app.logger等基础设施依赖app.env和app.config - middleware 第三 :中间件工厂注册到
app.middlewares,供下一步使用 - router-schema 第四 :校验规则注册到
app.routerSchema - service 第五 :业务服务实例化,构造函数可读取
app.config/app.extend - controller 第六 :控制器实例化,路由文件依赖
app.controller已就绪 - 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,对请求的 headers、body、query、params 分别校验。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 文件的混淆),自动向模板注入 name、env 和 options 变量,开发环境关闭缓存保证热更新。
十二、技术选型原则
| 领域 | 选型 | 理由 |
|---|---|---|
| Web 框架 | Koa 2 | 洋葱模型 + async/await,轻量无历史包袱 |
| 路由 | koa-router | 社区标准,stack 可编程操控 |
| 参数校验 | AJV + JSON Schema | 声明式校验,标准格式,前后端可共享 |
| 日志 | log4js | 分级 + 按日切割,满足生产需求 |
| 模板 | Nunjucks | 类 Jinja2 语法,功能完备 |
| 请求库 | superagent | 轻量 HTTP 客户端 |
十三.设计理念总结
"理解底层,方能驾驭上层。"
Elpis 的设计目标不是成为下一个 Express 或 Egg.js,而是通过亲手构建一个最小可用的 Web 框架,深入理解:
- 框架的启动流程与模块加载机制
- Koa 洋葱模型的中间件执行顺序
- 路由匹配与中间件注入的时序关系
- 约定优于配置的实际落地方式
- API 安全(签名、校验、错误处理)的完整链路
每一个设计决策都有其明确的理由和边界,不追求功能的完备性,而是追求对核心机制理解的深度。