【基于koa实现elpis-core总结】

363 阅读3分钟

【基于koa实现elpis-core总结】

设计理念

为了编写代码可维护性高阅读性好,便有了约定俗成,模块抽离这个概念。简单来说就是分工明确,什么模块干什么活。实现一个elpis-core 通过它实现核心模块配置。

实现elpis-core

约定配置项目结构

image.png

elpis-core项目结构

image.png

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的理解,也是抱着学习的心态来的,有理解错的地方多指点一二,一起学习探讨

:抖音“哲玄前端*