一、里程碑目标
系统目标:
开发一个支持多网站建设的系统平台或应用框架,通过配置化沉淀80%的重复性工作,并提供定制化能力支持20%的定制化需求
里程碑1目标:
实现系统的BFF(Backend for Frontend)层,实现了一个基于nodejs的应用框架,采用 Koa 实现,通过 Loader 实现模块化、可扩展的架构,实现一个后端 API 服务 + 服务端渲染的应用框架
二、BFF层架构
1.接入层:包括路由定义、参数校验规则、中间件等。
2.业务层:包括各种controller,处理具体的业务逻辑,调用服务层的service。
3.服务层:由各个service组成,每个service包含多种原子性方法,针对数据层的原子性操作
三、BFF的优势
1.解耦展示层和后端接口:后端只需关注接口业务逻辑,前端在BFF层完成展示逻辑。
2.接口请求收拢:所有对外请求由BFF层处理,减少内网到外网的请求,利用服务对服务的处理,在同一个集群速度会更快,提升系统性能。
3.签名和密钥管理:BFF层管理后端签名和密钥,保护后端API服务器。
4.SSR能力:BFF层具备SSR能力,可以实现前端业务的更多可能性。
四、loader机制
1、loader设计模式
核心是 Loader 系统 ,采用统一的加载模式:
// 每个 loader 都是 (app) => {} 函数
module.exports = (app) => {
// 1. 读取目录文件
const fileList = glob.sync(path.resolve(path, '**/*.js'));
// 2. 遍历文件,解析模块名(支持多级目录)
// custom-module/custom-controller.js => customModule.customController
// 3. 实例化模块并挂载到 app 上
app.controller = { customModule: { customController: instance } };
};
2、核心loader
configLoader - 配置管理
功能 :根据环境加载不同配置文件
// 加载顺序:defaultConfig → envConfig(覆盖)
app.config = { ...defaultConfig, ...envConfig };
-
支持三种环境:local/beta/production
-
通过 process.env._ENV 控制环境
-
默认配置 + 环境配置的继承覆盖模式
extendLoader - Koa 实例扩展
功能 :向 Koa 实例添加自定义属性/方法
示例:日志扩展
module.exports = (app) => {
let logger;
if (app.env.isLocal()) {
logger = console; // 本地使用 console
} else {
log4js.configure({...}); // 生产环境使用 log4js
logger = log4js.getLogger();
}
return logger;
};
middlewareLoader - 中间件加载器
功能 :加载 app/middleware/ 下的所有中间件到 app.middlewares
-
支持多级目录结构
-
自动将 kebab-case 转换为 camelCase
-
示例: api-sign-verify.js → app.middlewares.apiSignVerify
controllerLoader & serviceLoader - 业务模块加载器
功能:加载 app/controller/ 下的所有 controller 到app.controller,加载app/service/ 下的所有 service到app.service
Controller 依赖 Service
routerSchemaLoader - API 参数校验
功能:将 JSON Schema 与路由绑定
// router-schema/project.js
module.exports = {
'/api/project/list': {
get: {
query: { type: 'object', properties: {...}, required: ['proj_key'] },
body: {},
params: {}
}
}
};
JSON Schema介绍
- JSON Schema用于描述JSON数据的结构,确保数据符合预期的格式和规则。
- 通过JSON Schema,可以定义JSON对象中的属性、类型、是否必传等规则
校验流程:
请求 → api-params-verify 中间件 → 读取 app.routerSchema[path][method] → 使用 Ajv 校验参数 → 校验失败返回 400 错误
routerLoader - 路由注册
功能 :注册所有路由到 Koa
-
支持多级目录路由
-
路由兜底处理(匹配失败重定向到首页)
五、中间件系统
1、中间件执行顺序
// middleware.js
app.use(koaStatic(...)); // 静态资源
app.use(koaNunjucks(...)); // 模板渲染
app.use(bodyparser()); // 请求体解析
app.use(errorHandler); // 错误处理(兜底)
app.use(apiSignVerify); // 签名验证
app.use(apiParamsVerify); // 参数校验
2、签名验证中间件
签名 = md5(signKey + '_' + timestamp)
API签名校验
- 签名的时效性:确保API请求有时效性,防止被重复利用
- 签名校验中间件:API.verify,用于验证API请求的签名合法性
- 签名的生成与验证:使用MD5算法,通过密钥和时效性生成签名,并验证签名的正确性
前端签名生成
- 前端签名生成:在前端生成签名和时效性,并将其作为请求头发送到后端
- 签名验证逻辑:后端使用相同的密钥和时效性验证签名,确保请求合法
3、错误处理中间件
兜底策略 :
try {
await next();
} catch (error) {
// 模板未找到 → 重定向
if (error.message.indexOf('template not found') > -1) {
ctx.redirect(app.options.homePage);
return;
}
// 其他错误 → 返回统一格式
ctx.status = 200;
ctx.body = {
success: false,
code: 50000,
message: '网络异常,请稍后重试'
};
}
-
.中间件的作用:处理洋葱圈模型中的错误,确保错误被捕获并处理
-
通过try catch捕获后续中间件和业务逻辑中的异常,统一处理错误
-
异常处理逻辑:记录错误信息、生成错误码,并返回给前端
六、日志扩展
1.日志落地的重要性:日志需要写在文件上,以便在出现问题时可以重新查看和分析。
2.log4js工具:用于在service层输出日志,确保日志落地。
3.日志的时效性:日志应记录具体的时间点和动作,以便定位问题。
4.日志级别的定义:包括info、warn、error等级别,用于区分日志的重要性。
5.日志的输出环境:本地开发环境下打印在控制台,测试和生产环境下输出并落地到磁盘。
七、启动顺序
// 1. 环境配置
app.env = env();
// 2. 配置加载器(尽早加载,供其他模块使用)
configLoader(app);
// 3. 扩展加载器(扩展可能用到配置)
extendLoader(app);
// 4. 中间件加载器(中间件加载器本身不依赖,但中间件可能依赖)
middlewareLoader(app);
// 5. 路由 Schema 加载器
routerSchemaLoader(app);
// 6. Service 加载器(Service 用到 app.config, app.extend)
serviceLoader(app);
// 7. Controller 加载器(Controller 用到 app.config, app.extend, app.service)
controllerLoader(app);
// 8. 全局中间件(全局中间件用到所有已加载的模块)
require(middleware.js)(app);
// 9. 路由加载器(路由用到 controller, service)
routerLoader(app);