【基于koa实现elpis-core总结】
设计理念
为了编写代码可维护性高阅读性好,便有了约定俗成,模块抽离这个概念。简单来说就是分工明确,什么模块干什么活。实现一个elpis-core 通过它实现核心模块配置。
实现elpis-core
约定配置项目结构
elpis-core项目结构
elpis-core可以抽离成一个package,可以通过require("elpis-core")的形式使用
通过index.js启动整个项目
const path = require("path");
const { sep } = path; //兼容不同操作系统上的斜杠 (\ 或 /)
const env = require("./env");
const configLoader = require("./loader/config");
const controllerLoader = require("./loader/controller");
const extendLoader = require("./loader/extend");
const middlewareLoader = require("./loader/middleware");
const routerSchemaLoader = require("./loader/router-schema");
const routerLoader = require("./loader/router");
const serviceLoader = require("./loader/service");
module.exports = {
/*
*启动项目
*@params options 项目配置项
*/
start(options = {}) {
//koa实例
const app = new Koa();
//应用配置
app.options = options;
//基础路径
app.baseDir = process.cwd();
//业务文件路径
app.businessPath = path.resolve(app.baseDir, `.${sep}app`);
//初始化环境配置
app.env = env();
console.log(`start env: ${app.env.get()} ---`);
// 加载 middleware
middlewareLoader(app);
console.log(`start loader: middleware done ---`);
// 加载 routerSchema
routerSchemaLoader(app);
console.log(`start loader: routerSchema done ---`);
//加载 controller
controllerLoader(app);
console.log(`start loader: controller done ---`);
// 加载 service
serviceLoader(app);
console.log(`start loader: service done ---`);
//加载 config
configLoader(app);
console.log(`start loader: config done ---`);
//加载 extend
extendLoader(app);
console.log(`start loader: extend done ---`);
//注册全局中间件
try {
require(`${app.businessPath}${sep}middleware.js`)(app);
console.log(`start global middleware done ---`);
} catch (err) {
console.log("[expection] there is no global middleware file");
}
// 注册路由
routerLoader(app);
console.log(`start loader: router done ---`);
//启动服务
try {
const port = process.env.port || 9000;
const host = process.env.ip || "0.0.0.0";
app.listen(port, host);
console.log(`Server runnin on port ${port}`);
} catch (err) {
console.log(err);
}
},
};
通过env.js定义项目开发、测试、生产对应的环境变量,在使用npm run dev时设置对应的环境变量
"scripts": {
"dev": "set _ENV=dev&& node ./index.js", //使用set_ENV=dev设置环境变量
"test": "set _ENV=test&& node ./index.js",
"prod": "set _ENV=production&& node ./index.js"
},
通过config.js导入存在config下config.dev.js, config.test.js, config.prod.js
//找到 config/ 目录
const configPath = path.resolve(app.baseDir, `.${sep}config`);
//获取 config.default
let defaultConfig = {};
try {
defaultConfig = require(path.resolve(
configPath,
`.${sep}config.default.js`
));
} catch (err) {
console.log("[expection] there is no default.config file");
}
// 获取 env.config
let envConfig = {};
try {
if (app.env.isDev()) {
//本地环境
envConfig = require(path.resolve(configPath, `.${sep}config.dev.js`));
} else if (app.env.isTest()) {
//测试环境
envConfig = require(path.resolve(configPath, `.${sep}config.test.js`));
} else if (app.env.isProduction()) {
//生产环境
envConfig = require(path.resolve(configPath, `.${sep}config.prod.js`));
}
} catch (err) {
console.log("[expection] there is no env.config file");
}
// 覆盖并加载 config 配置
app.config = Object.assign({}, defaultConfig, envConfig);
通过controller.js 导入存在controller下所有的文件以文件名作为key,导出的对象作为值并挂载到app.controller下
// 读取 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-folder/custom-controller.js => custom-folder/custom-controller.js
name = name.substring(
name.lastIndexOf(`controller${sep}`) + `controller${sep}`.length,
name.lastIndexOf(".")
);
// 把 '-' 统一修改为驼峰式 /custom-folder/custom-controller.js => customFolder.customController
name = name.replace(/[_-][a-z]/gi, (s) => s.substring(1).toUpperCase());
// 挂载 controller 到内存 app 对象中
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();
} else {
if (!tempController[names[i]]) {
tempController[names[i]] = {};
}
tempController = tempController[names[i]];
}
}
});
app.controller = controller;
通过extend.js 导入存在extend下所有的文件以文件并挂载都app下
// 读取 app/extend/**.js 下所有文件
同上省略***
// 遍历所有文件目录,把所有内容加载到 app.extend 下
fileList.forEach((file) => {
// 提取文件名称
let name = path.resolve(file);
// 截取路径 app/extend/custom-extend.js => custom-extend.js
name = name.substring(
name.lastIndexOf(`extend${sep}`) + `extend${sep}`.length,
name.lastIndexOf(".")
);
// 把 '-' 统一修改为驼峰式 custom-extend.js =>customExtend
name = name.replace(/[_-][a-z]/gi, (s) => s.substring(1).toUpperCase());
for (let key in app) {
if (key === name) {
console.log(`[expection load error] name:${name} is already in app`);
return;
}
}
app[name] = require(path.resolve(file))(app);
});
通过middleware.js 导入存在middleware下所有的文件以文件以文件名作为key,导出的对象作为值并挂载都app.middlewares下
// 读取 app/middleware/**/**.js 下所有文件
同上省略***
// 遍历所有文件目录,把所有内容加载到 app.middlewares 下
const middlewares = {};
fileList.forEach((file) => {
// 提取文件名称
let name = path.resolve(file);
// 截取路径 app/middleware/custom-folder/custom-middleware.js => custom-folder/custom-middleware.js
name = name.substring(
name.lastIndexOf(`middleware${sep}`) + `middleware${sep}`.length,
name.lastIndexOf(".")
);
// 把 '-' 统一修改为驼峰式 /custom-folder/custom-middleware.js => customFolder.customMiddleware
name = name.replace(/[_-][a-z]/gi, (s) => s.substring(1).toUpperCase());
// 挂载 middleware 到内存 app 对象中
let tempMiddleWare = middlewares;
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);
} else {
if (!tempMiddleWare[names[i]]) {
tempMiddleWare[names[i]] = {};
}
tempMiddleWare = tempMiddleWare[names[i]];
}
}
});
app.middlewares = middlewares;
通过router-schema.js 导入router-schema下所有的文件并挂载到app.routerSchema下
// 读取 app/routerSchema/**/**.js 下所有文件
同上省略***
let routerSchema = {};
fileList.forEach((file) => {
routerSchema = {
...routerSchema,
...require(path.resolve(file)),
};
});
app.routerSchema = routerSchema;
通过router.js 导入router下所有路由并注册
//找到路由文件路径
同上省略***
//实例化 KoaRouter
const router = new KoaRouter();
// 注册所有的路由
fileList.forEach((file) => {
require(path.resolve(file))(app, router);
});
//路由兜底
router.get("*", async (ctx, next) => {
ctx.status = 302;
ctx.redirect(app?.options?.homePage ?? `${sep}`);
});
//路由注册到 app 上
app.use(router.routes());
app.use(router.allowedMethods());
通过service.js 导入service下所有的文件以文件名作为key,导出的对象作为值并挂载到app.service下
// 读取 app/service/**/**.js 下所有文件
同上省略***
// 遍历所有文件目录,把所有内容加载到 app.service 下
const service = {};
fileList.forEach((file) => {
// 提取文件名称
let name = path.resolve(file);
// 截取路径 app/service/custom-folder/custom-service.js => custom-folder/custom-service.js
name = name.substring(
name.lastIndexOf(`service${sep}`) + `service${sep}`.length,
name.lastIndexOf(".")
);
// 把 '-' 统一修改为驼峰式 /custom-folder/custom-service.js => customFolder.customService
name = name.replace(/[_-][a-z]/gi, (s) => s.substring(1).toUpperCase());
// 挂载 service 到内存 app 对象中
let tempService = service;
const names = name.split(sep);
for (let i = 0, len = names.length; i < len; i++) {
if (i === len - 1) {
const serviceModule = require(path.resolve(file))(app);
tempService[names[i]] = new serviceModule();
} else {
if (!tempService[names[i]]) {
tempService[names[i]] = {};
}
tempService = tempService[names[i]];
}
}
});
app.service = service;
app下的middleware.js 是全局中间件
//静态资源加载
const koaStatic = require("koa-static");
app.use(koaStatic(path.resolve(process.cwd(), "./app/public/")));
//页面模版渲染
const koaNunjucks = require("koa-nunjucks-2");
app.use(
koaNunjucks({
ext: "tpl",
path: path.resolve(process.cwd(), "./app/public"),
nunjucksConfig: {
noCache: true,
trimBlocks: true,
},
})
);
// 引入 ctx.body 解析中间件
const bodyParser = require("koa-bodyparser");
app.use(
bodyParser({
formList: "1000mb",
enalbeTypes: ["form", "json", "text"],
})
);
// 引入异常捕获中间件
app.use(app.middlewares.errorHandler);
// 引入API签名合法性校验
app.use(app.middlewares.apiSignVerify);
//引入API参数校验
app.use(app.middlewares.apiParamsVerify);
总结
止于目前对于elpis-core的理解,也是抱着学习的心态来的,有理解错的地方多指点一二,一起学习探讨
注:抖音“哲玄前端*