基于koa服务端脚手架搭建(文件加载器) --【elpis全栈项目笔记】

server/2024/12/23 23:07:38/

基于koa服务端脚手架(文件加载器) --【elpis-core】


前言: elpis-core 是一个项目文件加载器。基于一定的约定,将功能不同的代码分类放置到不同的目录下管理。适用于项目代码规范化、减少维护成本、沟通成本,易于扩展。(简易版的 egg-core )

请添加图片描述
其目的,就是将各类约定好的文件夹下的js方法,自动挂载到全局的app的实例上。(这只是其中一类实例,更多请看后文)

|-- app|-- controller|-- project.js|-- ...// 在别的文件下 每次使用都需要手动引入 project.js 文件
const projController = require('app/controller/project.js');
projController.getList()// ==> 接入 elpis-core 之后// 只需将 project.js 放入 controller 文件夹下,会自动挂载到app实例上
const projController = app.controller.project
projController.getList()

一、 elpis-core 目录结构


在这里插入图片描述

|-- app
|-- ...
|-- elpis-core|-- loader|-- config.js 					解析环境配置|-- controller.js				解析公共业务逻辑|-- extend.js 					解析加载外部工具类|-- middleware.js 				解析中间件|-- router-schema.js 			解析路由校验规则|-- router.js 					解析注册路由|-- service.js					解析服务模块|-- env.js							判断环境|-- index.js 						引擎入口文件
|-- index.js 项目入口文件

项目入口文件,引入/启动 elpis-core:

const ElpisCore = require('./elpis-core');
// 启动项目
ElpisCore.start({name: 'Elpis',homePage: '/'});

引擎入口文件, 加载各个处理器:

const Koa = require('koa');
const env = require('./env')
const configLoader = require('./loader/config'); 
const middlewareLoader = require('./loader/middleware');
...
module.exports = {start(options = {}) {const app = new Koa(); // koa 实例app.baseDir = process.cwd() // 基本路径app.env = env();  // 初始化环境配置controllerLoader(app) // 加载contorller处理器configLoader(app) // 加载config处理器... // 启动服务try {const port = process.env.PORTB || 8080;const host = process.env.IP || '0.0.0.0';app.listen(port, host);console.log(`\n --- 🚀 Server running at http://localhost:${port} 🚀 --- \n`);} catch (e) {console.error(e);}}
}

二、解析环境配置 – config.js


要实现的功能:基于当前环境,读取当前环境对象的config配置,可通过app.config读取当前环境配置。

|-- app
|-- config|-- config.default.js|-- config.local.js|-- config.beta.js|-- config.prod.js// config.prod.js
module.exports = {name:'生产', ...}// 生产环境调用时:
const prodName = app.config.name

处理器实现:

const path = require('path');
const { sep } = path/*** config loader* @param {object} app koa实例 * * 配置区分 本地/测试/生产 环境, 通过 env 环境读取不同文件配置* 通过 env.config 覆盖 default.config 加载到 app.config 中* * 目录下对应的 config 配置* 默认配置 config/config.default.js* 本地环境配置 config/config.local.js* 测试环境配置 config/config.beta.js* 生产环境配置 config/config.prod.js*/module.exports = (app) => {// 获取 config/ 目录const configPath = path.resolve(app.baseDir, `.${sep}config`);// 获取 default.configlet defaultConfig = {};try {defaultConfig = require(path.resolve(configPath, `.${sep}config.default.js`));} catch (e) {console.log('[exceprion] there is no default.config file')}// 获取 env.configlet envConfig = {};try {if (app.env.isLocal()) { // 本地环境envConfig = require(path.resolve(configPath, `.${sep}config.local.js`))} else if (app.env.isBeta()) {  // 测试环境envConfig = require(path.resolve(configPath, `.${sep}config.beta.js`))} else if (app.env.isProd()) {  // 生产环境envConfig = require(path.resolve(configPath, `.${sep}config.prod.js`))}} catch (e) {console.log('[exceprion] there is no default.envConfig file')}// 覆盖并加载 config 配置app.config = { ...defaultConfig, ...envConfig }
}

三、 解析业务逻辑-- controller.js


要实现的功能:读取controller文件夹,挂载到app实例上,使其中的函数可通过app.controller.xxx.getxxx()调用

|-- app|-- controller|-- base.js|-- project.js|-- view.js// project.js
module.exports = (app) => {const BaseController = require('./base')(app)return class ProjectController extends BaseController {async getList(ctx) {}}
}// 被router文件调用时:
module.exports = (app, router) => {const { project: projectController } = app.controllerrouter.get('/api/project/list', projectController.getList.bind(projectController))
}

处理器实现:

const glob = require('glob');
const path = require('path')
const { sep } = path/***  controller loader* @param {object} app koa 实例* * 加载所有 controller,,可通过`app.controller.${目录}.${文件}`访问 * * 例子:* app/controller*   |*   | -- custom-module*           |*           | -- custom-controller.js* * => app.controller.customname.customController*/module.exports = (app) => {// 读取app/controller/**/**.js 所有文件const controllerPath = path.resolve(app.businessPath, `.${sep}controller`);const fileList = glob.sync(path.resolve(controllerPath, `.${sep}**${sep}**.js`));// 遍历所有文件目录,把内容加载到 app.controller 下const controller = {}fileList?.forEach(file => {//提取文件名let name = path.resolve(file)// 截取路径 app/controller/custom-module/custom-controller.js => custom-module/custom-controllername = name.substring(name.lastIndexOf(`controller${sep}`) + `controller${sep}`.length, name.lastIndexOf('.'));// 把'-'统一改为驼峰式, custom-module/custom-controller.js => customModule/customControllername = name.replace(/[_-][a-z]/ig, (s) => s.substring(1).toUpperCase());//挂载 controller 到内存 app 对象中;  tempController === { customModule:{ customController:{ } } }let tempController = controller;const names = name.split(sep)for (let i = 0, len = names.length; i < len; ++i) {if (i === len - 1) {const ControllerModule = require(path.resolve(file))(app)tempController[names[i]] = new ControllerModule()return} else {if (!tempController[names[i]]) {tempController[names[i]] = {}}tempController = tempController[names[i]]}}})app.controller = controller
}

四、解析外部工具类 – extend.js


要实现的功能:读取extend文件夹,挂载到app实例上,使其中的函数可通过app.xxx.xxx()调用

|-- app|-- extend|-- logger.js// logger.js
module.exports = (app) => {let logger...logger = log4js.getLogger();...return logger
}// 被其他文件调用时:
module.exports = (app, router) => {...app.logger.info('info');app.logger.error('error');
}

处理器实现:

const glob = require('glob');
const path = require('path')
const { sep } = path/***  extend loader* @param {object} app koa 实例* * 加载所有 extend,,可通过`app.extend.${文件}`访问 * * 例子:* app/extend*   |*   | -- custom-extend.js* * => app.extend.customExtend 访问*/module.exports = (app) => {// 读取app/extend/**/**.js 所有文件const extendPath = path.resolve(app.businessPath, `.${sep}extend`);const fileList = glob.sync(path.resolve(extendPath, `.${sep}**${sep}**.js`));// 遍历所有文件目录,把内容加载到 app.extend 下fileList?.forEach(file => {//提取文件名let name = path.resolve(file)// 截取路径 app/extend/custom-extend.js => custom-extendname = name.substring(name.lastIndexOf(`extend${sep}`) + `extend${sep}`.length, name.lastIndexOf('.'));// 把'-'统一改为驼峰式, custom-extend.js => customExtendname = name.replace(/[_-][a-z]/ig, (s) => s.substring(1).toUpperCase());// 过滤 app 已经存在的keyfor (const key in app) {if (key === name) {console.warn(`[extend load error] ${name} is already in app`)return}}//挂载 extend 到内存 app 对象中;  app[name] = require(path.resolve(file))(app)})
}

五、解析中间件 – middleware.js


要实现的功能:读取middleware文件夹,挂载到app实例上,使其可通过app.middlewares.xxx调用

|-- app|-- middleware|-- error-handler.js// error-handler.js
module.exports = (app) => {return async (ctx, next) => {try {await next()} catch (err) {   // 异常处理...const resBody = { success: false, code: 5000, message: '网络异常,请稍后再试' }ctx.status = 200;ctx.body = resBody;}}
}// 使用全局 middlewares.js 统一维护引入时:
module.exports = (app, router) => {app.use(app.middlewares.errorHandler);  // 引入异常捕获中间件app.use(xxx)...
}

处理器实现:

const glob = require('glob');
const path = require('path')
const { sep } = path/***  middleware loader* @param {object} app koa 实例* * 加载所有 middleware,,可通过`app.middleware.${目录}.${文件}`访问 * * 例子:* app/middleware*   |*   | -- custom-module*           |*           | -- custom-middleware.js* * => app.middlleware.customname.customMiddleware*/module.exports = (app) => {// 读取app/middleware/**/**.js 所有文件 const middlewarePath = path.resolve(app.businessPath, `.${sep}middleware`);const fileList = glob.sync(path.resolve(middlewarePath, `.${sep}**${sep}**.js`));// 遍历所有文件目录,把内容加载到 app.middlewares 下const middleware = {}fileList?.forEach(file => {//提取文件名let name = path.resolve(file)// 截取路径 app/middleware/custom-module/custom-middleware.js => custom-module/custom-middlewarename = name.substring(name.lastIndexOf(`middleware${sep}`) + `middleware${sep}`.length, name.lastIndexOf('.'));// 把'-'统一改为驼峰式, custom-module/custom-middleware.js => customModule/customMiddlewarename = name.replace(/[_-][a-z]/ig, (s) => s.substring(1).toUpperCase());//挂载 middleware 到内存 app 对象中;  tempMiddleware === { customModule:{ customMiddleware:{ } } }let tempMiddleware = middleware;const names = name.split(sep)for (let i = 0, len = names.length; i < len; ++i) {if (i === len - 1) {tempMiddleware[names[i]] = require(path.resolve(file))(app)return} else {if (!tempMiddleware[names[i]]) {tempMiddleware[names[i]] = {}}tempMiddleware = tempMiddleware[names[i]]}}})app.middlewares = middleware
}

koa中间件有一个需要特别关注的地方:洋葱圈模型; 有时间需要单独写一篇;
网友的: 浅谈 Koa 和 Express 的中间件设计模式

六、解析路由校验规则 – router-schema.js


要实现的功能:读取router-schema文件夹,挂载到app实例上,使其可通过app.routerSchema.xxx调用

|-- app|-- router-schema|-- project.js// project.js 是 '/api/project/list' 接口的参数校验规则
module.exports = {'/api/project/list': {get: {query: {type: 'object',... required: ['id']}}}
}// 在API参数校验的中间件 api-params-verify.js 中使用时:
module.exports = (app, router) => {return async (ctx, next) => {const { query } = ctx.request;const { path } = ctx;const schema = app.routerSchema[path]?.[method.toLowerCase()];...validate = ajv.compile(schema.query)valid = validate(query)...}
}

处理器实现:

const glob = require('glob');
const path = require('path')
const { sep } = path/*** router-schema loader* @param {object} app koa实例 * * 通过 'json-schema' & 'ajv' 对 api规则进行约束,配合 api-params-verify 中间件使用* * app/router-schema/api1.js  // { 'api1/data/getDetail' :{} } * app/router-schema/api2.js  // { 'api2/data/getDetail' :{} } * ...* * 输出:*   app.routerSchema = {*      'api1/data/getDetail':{},*      'api2/data/getDetail':{},*     ...*   }*/module.exports = (app) => {// 读取app/router-schema/**/**.js 所有文件 const middlewarePath = path.resolve(app.businessPath, `.${sep}router-schema`);const fileList = glob.sync(path.resolve(middlewarePath, `.${sep}**${sep}**.js`));// 注册所有 routerSchema, 使得 app.routerSchema 可以访问let routerSchema = {}fileList.forEach(file => {routerSchema = {...routerSchema,...require(path.resolve(file))}});app.routerSchema = routerSchema
}

七、解析注册路由 – router.js


要实现的功能:读取router文件夹,引入每个文件的’/xxx/xxx/...‘接口,将其直接注册到koaRouter

|-- app|-- router|-- project.js// project.js 
module.exports = (app, router) => {const { project: projectController } = app.controllerrouter.get('/api/project/list', projectController.getList.bind(projectController))
}// rouer.js 处理器会遍历所有路由,引入并注册
module.exports = (app) => {...fileList.forEach(file => {require(path.resolve(file))(app, router) }); // 将遍历到的所有路由引入...app.use(router.routes()); // 注册app.use(router.allowedMethods()); // 自动响应不支持的 HTTP 方法
}

处理器实现:

const KoaRouter = require('koa-router');
const glob = require('glob');
const path = require('path');
const { sep } = path;/*** router loader* @param {object} app koa实例* * 解析所有 app/router/ 下所有 js 文件, 加载到 KoaRouter 下* */module.exports = (app) => {// 找到路由文件路径const routerPath = path.resolve(app.businessPath, `.${sep}router`);// 实例化所有路由const router = new KoaRouter();// 注册所有路由const fileList = glob.sync(path.resolve(routerPath, `.${sep}**${sep}**.js`));fileList.forEach(file => {require(path.resolve(file))(app, router);});// 路由兜底(健壮性)router.get('*', async (ctx, next) => {ctx.status = 302; // 临时重定向ctx.redirect(`${app?.options?.homePath ?? '/'}`);})// 路由注册到 app 上app.use(router.routes());app.use(router.allowedMethods()); 
}

八、解析服务模块-- service.js


要实现的功能:读取service文件夹,挂载到app实例上,使其可通过app.service.xxx调用

|-- app|-- service|-- base.js|-- project.js// project.js 
module.exports = (app) => {const BaseService = require('./base')(app)return class ProjectService extends BaseService {async getList() {return '数据库拿到的数据'}}
}// 在 controller 中处理接口请求的业务逻辑时: 直接调用app.service
module.exports = (app) => {...return class ProjectController extends BaseController {async getList(ctx) {const { project: projectService } = app.service;const projectList = await projectService.getList();this.success(ctx, projectList);}}
}

处理器实现:

const glob = require('glob');
const path = require('path')
const { sep } = path/***  service loader* @param {object} app koa 实例* * 加载所有 service,,可通过`app.service.${目录}.${文件}`访问 * * 例子:* app/service*   |*   | -- custom-module*           |*           | -- custom-service.js* * => app.service.customname.customService*/module.exports = (app) => {// 读取app/service/**/**.js 所有文件const servicePath = path.resolve(app.businessPath, `.${sep}service`);const fileList = glob.sync(path.resolve(servicePath, `.${sep}**${sep}**.js`));// 遍历所有文件目录,把内容加载到 app.service 下const service = {}fileList?.forEach(file => {//提取文件名let name = path.resolve(file)// 截取路径 app/service/custom-module/custom-service.js => custom-module/custom-servicename = name.substring(name.lastIndexOf(`service${sep}`) + `service${sep}`.length, name.lastIndexOf('.'));// 把'-'统一改为驼峰式, custom-module/custom-service.js => customModule/customServicename = name.replace(/[_-][a-z]/ig, (s) => s.substring(1).toUpperCase());//挂载 service 到内存 app 对象中;  tempService === { customModule:{ customService:{ } } }let tempService = service;const names = name.split(sep)for (let i = 0, len = names.length; i < len; ++i) {if (i === len - 1) {const SerivceModule = require(path.resolve(file))(app)tempService[names[i]] = new SerivceModule()return} else {if (!tempService[names[i]]) {tempService[names[i]] = {}}tempService = tempService[names[i]]}}})app.service = service
}

了解更多:
核心体系egg-core
阮一峰Koa 框架教程


至此elpis-core的核心功能已基本实现

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


http://www.ppmy.cn/server/152601.html

相关文章

PHP医院安全(不良)事件管理系统源码,通过运用RCA分析工具,借助柏拉图、鱼骨图等分析工具,分析问题产生的根本原因

医院安全&#xff08;不良&#xff09;事件管理系统采用无责的、自愿的填报不良事件方式&#xff0c;有效地减轻医护人员的思想压力&#xff0c;实现以事件为主要对象&#xff0c;可以自动、及时、实际地反应医院的安全、不良、近失事件的情况&#xff0c;更好地掌握不良事件的…

彭绍亮教授课题组在人工智能图像分析算法用于从肺癌组织病理图像中预测STAS取得重要进展|文献分享·24-12-22

小罗碎碎念 近日&#xff0c;湖南大学信息科学与工程学院彭绍亮教授课题组与中南大学湘雅二医院胸外科、病理科合作&#xff0c;联合在Nature 子刊npj Precision Oncology发表了题为Feature-interactive Siamese graph encoder-based image analysis to predict STAS from his…

使用 Elasticsearch 查询和数据同步的实现方法

在开发过程中&#xff0c;将数据从数据库同步到 Elasticsearch (ES) 是常见的需求之一。本文将重点介绍如何通过 Python 脚本将数据库中的数据插入或更新到 Elasticsearch&#xff0c;并基于多字段的唯一性来判断是否执行插入或更新操作。此外&#xff0c;我们还将深入探讨如何…

【图形渲染】【Unity Shader】【Nvidia CG】有用的参考资料链接

【背景】 学Shader和学其他任何IT技能一样&#xff0c;需要备有合适的查阅资料的池子&#xff0c;本文就将这些池子一站式备齐给到大家。 【Unity Shader相关学习参考文档链接】 Unity Shader官方文档&#xff1a; http://docs.unity3d.com/Manual/SL-Reference.html 官方…

【软件工程】第一章·软件工程概述

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;软件开发必练内功_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前…

FFmpeg 框架简介和文件解复用

文章目录 ffmpeg框架简介libavformat库libavcodec库libavdevice库 复用&#xff08;muxers&#xff09;和解复用&#xff08;demuxers&#xff09;容器格式FLVScript Tag Data结构&#xff08;脚本类型、帧类型&#xff09;Audio Tag Data结构&#xff08;音频Tag&#xff09;V…

第三章线性判别函数(二)

文章目录 一、基于梯度的方法二、均方误差最小算法三、支持向量机 (SVM) 一、基于梯度的方法 梯度概念 设函数 f ( Y ) f(Y) f(Y) 是向量 Y [ y 1 , y 2 , … , y n ] T Y [y_1, y_2, \dots, y_n]^T Y[y1​,y2​,…,yn​]T 的函数&#xff0c;则 f ( Y ) f(Y) f(Y) 的梯…

Linux性能监控命令_nmon 安装与使用以及生成分析Excel图表

文章目录 Linux性能监控命令_nmon 安装与使用安装解压创建nmono目录解压到nmono目录当中切换到sources目录下解压 配置环境变量创建软链接到 /usr/bin/ 目录下打开 配置文件 配置环境变量在底部增加如下注册 使用使用说明监控监控CPU监控内存监控磁盘监控网络监控文件系统 后台…