一句话理解elpis-core
elpis-core就像项目的公共底座:把 80% 会反复出现的事情先做好,再给每个项目留下 20% 的空间去写自己的业务。
先看项目目录
ruby
elpis/
├── index.js # 启动入口
├── config/ # 环境配置
├── elpis-core/ # 应用装配器
│ ├── index.js # 启动流程
│ ├── env.js # 环境判断
│ └── loader/ # 各类模块加载器
└── app/ # 业务代码
├── controller/ # 请求处理
├── service/ # 业务逻辑
├── middleware/ # 可复用中间件
├── router/ # 路由定义
├── router-schema/ # API 参数规则
├── extend/ # app 扩展能力
└── public/ # 静态资源与模板
读的时候 先看 index.js 怎么启动,再看 loader 怎么把业务文件找出来。
最简单的分工 elpis-core 管项目怎么跑,app 管项目做什么。
读完应该记住配置先准备环境,controller 和 service 处理业务,router 把请求接进来。
它到底帮我们做了什么?
它是一个用 Koa 实现的,用来完成项目启动和模块注册的核心。
80% 通用能力:启动、配置、日志、中间件加载、模块注册、路由接入
20% 定制能力:业务规则、接口、数据源和项目差异
以后新建项目时,少复制一堆老代码,也少担心几个项目慢慢长得不一样。
没有 elpis-core**→** 手动 require**→** 手动实例化**→** 手动挂载**→**启动应用
使用 elpis-core**→** 遵守目录约定**→** loader 自动装配**→**直接写业务
从入口看起
项目入口很薄,薄到基本只剩一句启动:
php
const ElpisCore = require('./elpis-core')
ElpisCore.start({
name: 'elpis',
homePage: '/'
})
入口只有一次 start() 调用,真正的工作集中在 elpis-core/index.js:
ini
const app = new koa()
app.options = options
app.baseDir = process.cwd()
app.bussinessPath = path.resolve(app.baseDir, './app')
app.env = env()
这里的关键是:所有 loader 都围着同一个 app 工作。前面准备好的东西,后面直接拿来用,所以启动顺序不能随便换。
启动顺序就是依赖关系
当前启动顺序大致是:
创建 Koa app**→** 设置路径与环境**→** 加载 config**→** 加载 extend**→** 加载 middleware**→** 加载 service**→** 加载 controller**→** 加载 router schema**→** 加载全局中间件**→** 加载 router**→**监听端口
配置:默认值加环境覆盖
arduino
config/
config.default.js
config.local.js
config.beta.js
config.prod.js
默认配置 公共配置放在 default。
环境覆盖 local / beta / prod 根据 app.env 选择。
最终入口 调用方只需要读取 app.config。
ini
app.config = Object.assign({}, defaultConfig, envConfig)
extend:把公共能力挂到 app 上
文件名就是注册名,loader 负责把路径变成运行时对象:
app/extend/logger.js→ app.logger
app/middleware/api-params-verify.js→ app.middlewares.apiParamsVerify
app/service/project.js→ app.service.project
业务少写注册代码,核心集中处理命名、加载和挂载。
service 与 controller:工厂接收 app,再创建实例
service 和 controller 都采用"工厂接收 app,再返回类"的形式:
scala
module.exports = app => {
const BaseService = require('./base')(app)
return class ProjectService extends BaseService {
async getList() {
return []
}
}
}
loader 找到文件后,执行工厂,再实例化返回的类,最终得到:
app.service.project
app.controller.project
Controller接收 ctx,调用 service,组织响应。
Service承载业务处理,隐藏数据访问细节。
Base 类共享 app、config、返回格式和公共工具。
路由只绑定 controller 方法:
csharp
const { project: projectController } = app.controller
router.get('/api/project/list', projectController.getList.bind(projectController))
controller 再调用:
kotlin
const projectList = await this.service.project.getList()
this.success(ctx, projectList)
调用链很短:路由 → controller → service → 统一响应。
router-schema 与 API 参数校验
schema 描述参数,router loader 自动把校验中间件插入 API 路由:
arduino
app.routerSchema = {
'/api/project/list': {
get: { /* headers/query/body/params */ }
}
}
router loader 创建 koa-router 后,重写了 get、post、put、patch、delete 的注册方法。路径以 /api 开头时,会自动把 app.middlewares.apiParamsVerify 插到 handler 前面;如果没有对应 schema,校验中间件会直接放行。
路由以 /api 开头**→** 自动插入 apiParamsVerify**→** 按 path + method 查 schema**→**校验 query/body/params
业务路由只需要注册 controller;绑定实例是为了保留 controller 里的 this:
csharp
const { project: projectController } = app.controller
router.get(
'/api/project/list',
projectController.getList.bind(projectController)
)
重点:/api 不只是命名习惯,它还是参数校验的触发条件。
一次 API 请求是怎么走的
以 GET /api/project/list?proj_key=demo 为例:
1.全局中间件:静态资源、模板、body、错误、签名。
2.router 匹配路径,并自动插入参数校验。
3.controller 读取 this.service.project。
4.service 返回业务结果,controller 统一响应。
elpis-core的价值
80%:核心沉淀启动流程环境配置模块扫描路由接入
这些能力跨项目重复出现,适合集中维护、统一升级。
20%:业务定制业务规则接口实现数据访问中间件顺序
这些差异由具体项目自己决定。