深入剖析服务端框架 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 的优势。

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

相关推荐
喜欢踢足球的老罗10 分钟前
一张跨域图的“四次换乘“:blob URL 与 Chrome 扩展架构里的工程艺术
前端·chrome·架构
程序员黑豆11 分钟前
AI全栈开发 - Java:基本数据类型 vs 引用数据类型的内存存储
java·前端·ai编程
FserSuN12 分钟前
Chrome CORS / PNA / LNA 问题排查与解决方案
前端·chrome
小小小小宇20 分钟前
Claude Code 自动运行方法大全
前端
道友可好22 分钟前
AI 测试全绿,代码却是错的
前端·人工智能·后端
国科安芯41 分钟前
商业航天通信载荷数字处理单元供电架构研究——基于ASP7A84AS的高精度低压差线性稳压器技术分析
前端·单片机·嵌入式硬件·fpga开发·架构·安全性测试
TangentDomain1 小时前
AI 写代码时代,游戏 UI 架构为什么停在 MVP?
前端·游戏·架构
英勇无比的消炎药1 小时前
前端提效神器全新AI组件库TinyRobot改写日常开发模式
前端·vue.js
GuWenyue1 小时前
10分钟搞定TodoList实战!从0搭建Bun+TS的RESTful接口服务
前端·typescript·bun