前言
这是一个基于 Koa 的企业级 Node.js 框架。它通过约定式的目录结构和自动加载机制,构建了一个易于扩展的 BFF 层框架。框架充分利用了 Koa 的中间件机制,提供了环境配置、参数校验、错误处理等完整的工程化解决方案。这使开发者能够专注于业务逻辑的开发。
什么是Koa?
Koa.js 是一个新型 web 框架,由 Express 团队开发,致力于为 Node.js 提供更精简、更稳健的 Web 开发基础。它解决了回调函数嵌套的问题,并简化了错误处理流程。Koa 采用轻量化设计,不预置任何中间件,而是提供了一套优雅的方法,让开发者能够轻松愉快地构建服务器端应用。
约定式项目结构
app/
├── controller/ // 控制器层
├── service/ // 服务层
├── middlewares/ // 中间件
├── router/ // 路由定义
├── router-schema/ // API参数校验
└── extend/ // 功能扩展
lpis-core项目介绍
入口文件
入口文件中主要是对Koa基础配置进行初始化、按顺序加载各个模块以及启动服务
- 基础配置
app.options = options; // 存储启动配置
app.baseDir = process.cwd(); // 项目根目录
app.businessPath = path.resolve(app.baseDir, `.${sep}app`); // 业务代码目录
app.env = env(app); // 环境配置
- 按顺序加载各个模块
middlewareLoader(app); // 先加载中间件
routerSchemaLoader(app); // 加载路由校验规则
controllerLoader(app); // 加载控制器
serviceLoader(app); // 加载服务层
configLoader(app); // 加载配置
extendLoader(app); // 加载扩展
require(`${app.businessPath}${sep}middleware.js`)(app) // 注册全局中间件
routerLoader(app); // 注册路由
- 启动
const port = process.env.PORT || 3000;
const host = process.env.HOST || '0.0.0.0';
app.listen(port, host, () => {
console.log(`Server is running on port ${port}`);
});
让我们详细介绍核心部分:模块的加载顺序。
这个加载顺序经过精心设计,原因如下:
- 中间件加载(middlewareLoader)最先进行,确保基础功能可用,特别是错误处理中间件需要最早介入请求处理流程。
- 路由校验规则加载(routerSchemaLoader)紧随其后,因为控制器需要使用这些校验规则。
- 控制器加载(controllerLoader)在前,作为路由的处理函数需要提前准备就绪。
- 服务层加载(serviceLoader)随后进行。
- 配置加载器(configLoader)处理环境相关配置。
- 扩展功能加载(extendLoader)作为基础配置的补充,在后期进行。
- 用户自定义中间件加载。
- 路由加载(routerLoader)作为最后一步,因为它需要使用前面加载的所有模块。
这种加载顺序确保了以下优势:
依赖关系:先加载被依赖的模块
- 依赖关系:确保基础模块优先加载,为其他模块提供支持
- 扩展性:开发者可以灵活地扩展或覆盖框架功能
- 可维护性:清晰的加载顺序让调试和维护变得简单直观
- 灵活性:模块间保持松耦合,便于单独替换或修改
这种设计体现了框架的分层思想和依赖注入原则,使应用结构清晰且易于扩展维护。
加载器具体实现
加载器主要是通过逻辑加载 约定式目录 下的文件,将他们统一加载并挂载到ap上
- 共同特点:所有加载器都遵循类似的模式
- 扫描指定目录下的文件
- 处理文件路径
- 提取相对路径
- 统一命名风格(驼峰命名)
- 加载模块并挂载到 ap 上
- 各个加载器的特点
congifLoader:
通过环境变量直接读取config目录下相对应的环境配置 config/config.${当前环境}.js
// 特点:合并配置
module.exports = (app) => {
// 1. 加载默认配置
const defaultConfig = require(path.resolve(configPath, 'config.default.js'));
// 2. 加载环境配置
const envConfig = require(path.resolve(configPath, `config.${env}.js`));
// 3. 合并配置
app.config = Object.assign({}, defaultConfig, envConfig);
}
controllerLoader
加载所有controller,将app.controller.{文件}下的controller文件都读区出来,因为controller中返回的是 class,所以需要实例化后挂载到app上
// 特点:支持多级目录结构
fileList.forEach(file => {
// 支持 custom-module/custom-controller => customModule.customController
name = name.replace(/[_-][a-z]/ig, (s) => s.substring(1).toUpperCase());
// 实例化控制器
const ControllerModule = require(file)(app);
tempController[names[i]] = new ControllerModule();
});
extendLoader
// 特点:直接挂载到 app 根对象
fileList.forEach(file => {
// 检查命名冲突
if (Object.prototype.hasOwnProperty.call(app, name)) {
console.error(`[extend] ${name} 已在 app 中定义`);
return;
}
app[name] = require(file)(app);
});
middlewareLoader
// 特点:支持多级目录结构
const middlewares = {};
fileList.forEach(file => {
// 支持目录嵌套
let tempMiddleware = middlewares;
const names = name.split(sep);
// 加载中间件函数
tempMiddleware[names[i]] = require(file)(app);
});
routerSchemaLoader
// 特点:扁平化合并所有 schema
let routerSchema = {}
fileList.forEach(file => {
routerSchema = {
...routerSchema,
...require(file)
}
});
最后将所有的routerSchema整合到一起
app.rouSchema = {
'${app1}': ${jsonSchema},
'${app2}': ${jsonSchema},
'${app3}': ${jsonSchema},
}
routerLoader
// 特点:使用 koa-router 注册路由
const router = new KoaRouter();
fileList.forEach(file => {
require(file)(app, router);
});
// 添加默认路由
router.get('*', async (ctx) => {
ctx.redirect(app?.options?.homePath ?? '/');
});
核心模块结束
全文特别鸣谢: 抖音“哲玄前端”,《全栈实践课》