深入剖析服务端框架 elpis-core

引言

在当今的软件开发领域,服务端框架的选择至关重要,它直接影响着项目的开发效率、可维护性和性能。elpis-core 作为一个服务端框架,具备丰富的功能和灵活的架构,能够帮助开发者快速搭建稳定的服务端应用。本文将深入探讨 elpis-core 的架构设计、核心功能以及实际应用中的一些要点。

整体架构设计

elpis-core 采用模块化设计,各个模块职责清晰,相互协作,共同构建了一个完整的服务端框架。主要模块包括环境配置、中间件加载、路由配置、控制器和服务层等。

环境配置(env.js)

环境配置模块负责根据不同的环境(本地、测试、生产)提供相应的配置信息。通过 process.env._ENV 变量来判断当前环境,提供了 isLocalisBetaisProductionget 等方法。

javascript 复制代码
module.exports = (app) => {
    return {
        //判断是否本地环境
        isLocal() {
            return process.env._ENV === 'local';
        },
        //判断是否测试环境
        isBeta() {
            return process.env._ENV === 'beta';
        },
        //判断是否生成环境
        isProduction() {
            return process.env._ENV === 'production';
        },
        //获取当前环境
        get() {
            return process.env._ENV ?? 'local';
        }
    }
}

这种设计使得项目在不同环境下能够灵活配置,提高了代码的可移植性和可维护性。

模块加载器

elpis-core 提供了多个模块加载器,用于加载配置、中间件、路由、控制器和服务等。这些加载器使用 glob 模块来查找相应的文件,并将其挂载到 app 对象上。

配置加载器(config.js)

配置加载器根据当前环境加载不同的配置文件,将默认配置和环境配置合并到 app.config 中。

javascript 复制代码
module.exports = (app) => {
    //找到 config/ 目录
    const configPath = path.resolve(app.baseDir, `.${sep}config`);

    //获取 default.config
    let defaultConfig = {};
    try {
        defaultConfig = require(path.resolve(configPath, `.${sep}config.default.js`))
    } catch (error) {
        console.error('[exception] there is no default.config file');
    }

    //获取 env.config
    let 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.isProduction()) { //生产环境
            envConfig = require(path.resolve(configPath, `.${sep}config.prod.js`))
        }
    } catch (error) {
        console.error('[exception] there is no env.config file');
    }

    //覆盖并加载 config 配置
    app.config = Object.assign({}, defaultConfig, envConfig)
}

中间件加载器(middleware.js

中间件加载器负责加载所有的中间件,并将其挂载到 app.middlewares 对象上。

ini 复制代码
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 middlewares = {};
    fileList.forEach(file => {
        //提取文件名
        let name = path.resolve(file);

        //截取路径 app/middleware/custom-module/custom-middleware.js => custom-module/custom-middleware
        name = name.substring(name.lastIndexOf(`middleware${sep}`) + `middleware${sep}`.length, name.lastIndexOf('.'));

        //把 '-' 统一改为驼峰式,custom-module/custom-middleware.js => customModule/customMiddleware.js
        name = name.replace(/[_-][a-z]/ig, (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.js)

路由加载器解析所有 app/router/ 下的路由文件,并将其注册到 KoaRouter 中。

javascript 复制代码
module.exports = (app) => {
    //找到路由文件路径
    const routerPath = path.resolve(app.businessPath, `.${sep}router`);

    //实例化 KoaRouter
    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?.homePage ?? '/' }`);
    })

    //路由注册到app上
    app.use(router.routes());
    app.use(router.allowedMethods());
}

控制器和服务层

elpis-core 支持控制器和服务层的分离,控制器负责处理请求和响应,服务层负责处理业务逻辑。

控制器(controller

ProjectController 为例,它继承自 BaseController,提供了获取项目列表的方法。

javascript 复制代码
module.exports = (app) => {
  const BaseController = require("./base")(app);
  return class ProjectController extends BaseController {
    /**
     * 获取项目列表
     * @param {object} ctx
     */
    async getList(ctx) {
      const { proj_key: projKey } = ctx.request.query;
      console.log(projKey);
      const { project: projectService } = app.service;
      const projectList = await projectService.getList();
      this.success(ctx, projectList);
    }
  };
};

服务层(service

ProjectService 继承自 BaseService,实现了获取项目列表的具体业务逻辑。

javascript 复制代码
module.exports = (app)=>{
    const BaseService = require('./base')(app);
    return class ProjectService extends BaseService {
        async getList(){
            return [{
                name:'阿萨德',
                dic:'2'
            },{
                name:'sad',
                dic:'23'
            }]
        }
    }
}

核心功能

中间件机制

elpis-core 提供了丰富的中间件,包括静态文件服务、模板渲染、请求体解析、异常处理、API 签名校验和参数校验等。

异常处理中间件(error-handler.js)

异常处理中间件负责捕获所有异常,并提供统一的错误处理和响应。

ini 复制代码
module.exports = (app) => {
  return async (ctx, next) => {
    try {
      await next();
    } catch (error) {
      //异常处理
      const { status, message, detail } = error;

      app.logger.info(JSON.stringify(error));
      app.logger.info("[-- exception --]:", error);
      app.logger.info("[-- exception --]:", status, message, detail);

      if(message && message.indexOf('template not found') > -1){
        //重定向页面
        ctx.status = 302; //临时重定向
        ctx.redirect(`${app.options?.homePage}`);
        return;
      }

      const resBody = {
        success:false,
        code:50000,
        message:'网络异常 请稍后重试'
      }

      ctx.status = 200;
      ctx.body = resBody;
    }
  };
};

API 参数校验中间件(api-params-verify.js)

API 参数校验中间件使用 ajv 库对请求参数进行校验,确保请求的合法性。

ini 复制代码
const Ajv = require("ajv");
const ajv = new Ajv();

module.exports = (app) => {
  const $schema = "http://json-schema.org/draft-07/schema#";
  return async (ctx, next) => {
    //只对 API 请求做签名校验
    if (ctx.path.indexOf("/api") < 0) {
      return await next();
    }

    //获取请求参数
    const { body, query, headers } = ctx.request;
    const { params, path, method } = ctx;
    console.log(params)

    app.logger.info(`[${method} ${path}] body: ${JSON.stringify(body)}`);
    app.logger.info(`[${method} ${path}] query: ${JSON.stringify(query)}`);
    app.logger.info(`[${method} ${path}] params: ${JSON.stringify(params)}`);
    app.logger.info(`[${method} ${path}] headers: ${JSON.stringify(headers)}`);

    const schema = app.routerSchema[path]?.[method.toLowerCase()];

    if (!schema) {
      return await next();
    }

    let valid = true;

    //ajv校验器
    let validate;

    //校验 headers
    if (valid && headers && schema.headers) {
      schema.headers.$schema = $schema;
      validate = ajv.compile(schema.headers);
      valid = validate(headers);
    }

    //校验 body
    if (valid && body && schema.body) {
      schema.body.$schema = $schema;
      validate = ajv.compile(schema.body);
      valid = validate(body);
    }

    //校验 query
    if (valid && query && schema.query) {
      schema.query.$schema = $schema;
      validate = ajv.compile(schema.query);
      valid = validate(query);
    }

    //校验 params
    if (valid && params && schema.params) {
      schema.params.$schema = $schema;
      validate = ajv.compile(schema.params);
      valid = validate(params);
    }

    if (!valid) {
      ctx.status = 200;
      ctx.body = {
        success: false,
        message: `request validate fail: ${ajv.errorsText(validate.errors)}`,
        code: 442,
      };
      return;
    }

    await next();
  };
};

路由配置

elpis-core 通过路由加载器自动加载 app/router/ 下的路由文件,支持动态路由和路由兜底。

javascript 复制代码
module.exports = (app, router) => {
  const { view: viewController } = app.controller;
  //用户输入 http://ip:port/view/xxxx 能渲染出对应的页面
  router.get("/view/:page", viewController.renderPage.bind(viewController));
};

模板渲染

elpis-core 使用 koa-nunjucks-2 作为模板渲染引擎,支持模板文件的渲染。

php 复制代码
const koaNunjucks = require("koa-nunjucks-2");
app.use(
  koaNunjucks({
    ext: "tpl",
    path: path.resolve(process.cwd(), "./app/public"),
    nunjucksConfig: {
      noCache: true,
      trimBlocks: true,
    },
  })
);

实际应用要点

项目启动

在 index.js 中调用 ElpisCore.start 方法启动项目,并传入配置选项。

php 复制代码
const ElpisCore = require("./elpis-core");

//启动项目
ElpisCore.start({
  name: "Elpis",
  homePage: "/",
});

环境配置

根据不同的环境(本地、测试、生产),在 config 目录下创建相应的配置文件,确保项目在不同环境下的配置正确。

总结

elpis-core 是一个功能强大、架构灵活的服务端框架,通过模块化设计和丰富的功能模块,能够帮助开发者快速搭建稳定的服务端应用。其环境配置、中间件机制、路由配置和模板渲染等功能,为项目的开发和维护提供了便利。在实际应用中,开发者可以根据项目需求进行定制和扩展,充分发挥 elpis-core 的优势。

抖音"哲玄前端"《大前端全栈实践》

相关推荐
Yvonne爱编码3 分钟前
HTML-3.3 表格布局(学校官网简易布局实例)
前端·html·github·html5·hbuilder
jllllyuz43 分钟前
matlab实现蚁群算法解决公交车路径规划问题
服务器·前端·数据库
小屁孩大帅-杨一凡1 小时前
一个简单点的js的h5页面实现地铁快跑的小游戏
开发语言·前端·javascript·css·html
读心悦2 小时前
CSS 布局系统深度解析:从传统到现代的布局方案
前端·css
椒盐螺丝钉2 小时前
CSS盒子模型:Padding与Margin的适用场景与注意事项
前端·css
萧鼎3 小时前
构建全栈 Web 应用的新选择:NextPy 技术详解与实战指南
前端
这个一个非常哈3 小时前
VUE篇之自定义组件使用v-model
前端·javascript·vue.js
purpleseashell_Lili3 小时前
react 基本写法
java·服务器·前端
哎哟喂_!3 小时前
Node.js 循环依赖问题详解:原理、案例与解决方案
前端·chrome·node.js
热爱前端的小君同学3 小时前
长按拖拽移动的vue3组件
前端·javascript·vue.js