基于Koa服务端脚手架

193 阅读3分钟

前言

这是一个基于 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基础配置进行初始化、按顺序加载各个模块以及启动服务

  1. 基础配置
app.options = options;    // 存储启动配置
app.baseDir = process.cwd();    // 项目根目录
app.businessPath = path.resolve(app.baseDir, `.${sep}app`);    // 业务代码目录
app.env = env(app);    // 环境配置
  1. 按顺序加载各个模块
middlewareLoader(app);      // 先加载中间件
routerSchemaLoader(app);    // 加载路由校验规则
controllerLoader(app);      // 加载控制器
serviceLoader(app);         // 加载服务层
configLoader(app);          // 加载配置
extendLoader(app);          // 加载扩展
require(`${app.businessPath}${sep}middleware.js`)(app) // 注册全局中间件
routerLoader(app);          // 注册路由
  1. 启动
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}`);
});

让我们详细介绍核心部分:模块的加载顺序。

这个加载顺序经过精心设计,原因如下:

  1. 中间件加载(middlewareLoader)最先进行,确保基础功能可用,特别是错误处理中间件需要最早介入请求处理流程。
  2. 路由校验规则加载(routerSchemaLoader)紧随其后,因为控制器需要使用这些校验规则。
  3. 控制器加载(controllerLoader)在前,作为路由的处理函数需要提前准备就绪。
  4. 服务层加载(serviceLoader)随后进行。
  5. 配置加载器(configLoader)处理环境相关配置。
  6. 扩展功能加载(extendLoader)作为基础配置的补充,在后期进行。
  7. 用户自定义中间件加载。
  8. 路由加载(routerLoader)作为最后一步,因为它需要使用前面加载的所有模块。

这种加载顺序确保了以下优势:

依赖关系:先加载被依赖的模块

  • 依赖关系:确保基础模块优先加载,为其他模块提供支持
  • 扩展性:开发者可以灵活地扩展或覆盖框架功能
  • 可维护性:清晰的加载顺序让调试和维护变得简单直观
  • 灵活性:模块间保持松耦合,便于单独替换或修改

这种设计体现了框架的分层思想依赖注入原则,使应用结构清晰且易于扩展维护。

加载器具体实现

加载器主要是通过逻辑加载 约定式目录 下的文件,将他们统一加载并挂载到ap上

  1. 共同特点:所有加载器都遵循类似的模式
    1. 扫描指定目录下的文件
    2. 处理文件路径
      1. 提取相对路径
      2. 统一命名风格(驼峰命名)
    3. 加载模块并挂载到 ap 上
  2. 各个加载器的特点

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 ?? '/');
});

核心模块结束

全文特别鸣谢: 抖音“哲玄前端”,《全栈实践课》