ElpisCore:一套企业级应用落地框架,帮助你摆脱项目规范烦恼

120 阅读4分钟

你是否为项目规范而烦恼?是否因为不同项目环境需要引入不同配置而发愁?是否反复定义和注册各种路由?别担心,我有一套解决方案——ElpisCore,它能帮助你快速启动项目,专注于核心逻辑的开发。

ElpisCore 的核心理念

ElpisCore 的主要职责是处理一些与业务无关的基础配置,使得开发人员可以更加专注于业务逻辑的实现。它将项目文件细分为七个维度,分别是:

  • config(配置项)
  • middleware(中间件)
  • router(路由)
  • router-schema(路由配置)
  • service(服务)
  • controller(控制器)
  • extend(扩展)

这样一来,项目结构清晰、模块化,使得开发工作更加高效。

ElpisCore 基于 Koa

ElpisCore 是基于 Koa 构建的, const app = new Koa() 中的 app 对象贯穿整个项目,它是框架的核心。我们约定,项目中的 /config 目录存放配置信息,包含不同环境下的配置文件(如 config.default.jsconfig.local.jsconfig.beta.jsconfig.prod.js)。这些配置文件将通过 module.exports = {}; 方式进行自定义,并在框架启动时合并,最终挂载到 app 对象中。

项目目录结构

  • /config:存放各种配置文件。
  • /app:存放业务相关文件,包含中间件、路由、服务等。
  • /app/middleware:存放中间件。
  • /app/router:存放路由定义。
  • /app/router-schema`:存放路由参数校验规范。
  • /app/service:存放服务文件。
  • /app/controller:存放控制器。
  • /app/extend:存放扩展功能(如日志工具)。

中间件

ElpisCore 中,所有中间件文件都会被挂载到 app.middlewares 对象上,而不需要手动引入每个中间件文件。我们只需在 /app/middleware 目录下按规定格式书写中间件即可。

module.exports = (app) => {
  // 返回一个中间件
  return async (ctx, next) => {
    // 逻辑处理
  };
};

路由定义与注册

ElpisCore 自动处理路由的注册。我们只需要在 /app/router 目录下定义路由文件,并按照一定格式进行书写,框架会自动加载和注册路由。

module.exports = (app, router) => {
  const { view: viewController } = app.controller;

  // 用户输入 http://ip:port/view/xxx 能渲染出对应的页面
  router.get("/view/:page", viewController.renderPage.bind(viewController));
};

ElpisCore 将自动注册路由,而不需要手动引入并注册每个路由文件。

路由参数校验

大部分 API 都需要进行参数校验,ElpisCore 将校验规则放置在 /app/router-schema 目录下,采用 JSON Schema 规范书写。校验规则按 [path].[method] 的方式进行配置。

module.exports = {
  "/api/project/list": {
    get: {
      query: {
        type: "object",
        properties: {
          proj_key: { type: "string" },
        },
        required: ["proj_key"],
      },
    },
  },
};

/app/middleware 中定义接口参数校验中间件 api-params-verify.js,该中间件将自动验证请求的参数。

const Ajv = require("ajv");
const ajv = new Ajv();

module.exports = (app) => {
  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;

    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;
    let validate;

    // 校验 headers, body, query 和 params
    if (valid && headers && schema.headers) {
      schema.headers.$schema = "http://json-schema.org/draft-07/schema";
      validate = ajv.compile(schema.headers);
      valid = validate(headers);
    }
    if (valid && body && schema.body) {
      schema.body.$schema = "http://json-schema.org/draft-07/schema";
      validate = ajv.compile(schema.body);
      valid = validate(body);
    }
    if (valid && query && schema.query) {
      schema.query.$schema = "http://json-schema.org/draft-07/schema";
      validate = ajv.compile(schema.query);
      valid = validate(query);
    }
    if (valid && params && schema.params) {
      schema.params.$schema = "http://json-schema.org/draft-07/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;
    }

    return await next();
  };
};

服务文件

服务文件存放在 /app/service 目录,定义与业务逻辑相关的服务。每个服务通过继承 BaseService 来实现,方便进行复用。

module.exports = (app) => {
  const BaseService = require("./base")(app);
  return class ProjectService extends BaseService {
    async getList() {
      return [
        { name: "project1", desc: "project1 desc" },
        { name: "project2", desc: "project2 desc" },
        { name: "project3", desc: "project3 desc" },
      ];
    }
  };
};

ElpisCore 会自动实例化服务,并将其挂载到 app.service 对象中,无需手动引入每个服务文件。

控制器

控制器文件存放在 /app/controller 目录,负责接收请求并返回响应。控制器中的方法与服务进行交互,返回处理结果。

module.exports = (app) => {
  const BaseController = require("./base")(app);
  return class ProjectController extends BaseController {
    async getList(ctx) {
      const { project: projectService } = app.service;
      const projectList = await projectService.getList();
      this.success(ctx, projectList);
    }
  };
};

ElpisCore 会自动实例化控制器,并将其挂载到 app.controller 对象中,无需手动引入每个控制器文件。

扩展功能

扩展功能文件存放在 /app/extend 目录,用于提供一些额外的工具或功能,如日志记录。ElpisCore 会自动加载扩展,并将其挂载到 app 对象中。

const log4js = require("log4js");

module.exports = (app) => {
  let logger;
  if (app.env.isLocal()) {
    logger = console;
  } else {
    log4js.configure({
      appenders: {
        console: { type: "console" },
        dateFile: {
          type: "dateFile",
          filename: "./logs/application.log",
          pattern: ".yyyy-MM-dd",
        },
      },
      categories: {
        default: { appenders: ["console", "dateFile"], level: "trace" },
      },
    });
    logger = log4js.getLogger();
  }
  return logger;
};

总结

ElpisCore 是一套成熟的企业级框架,通过分离配置项、路由、服务、控制器等,使得开发者能够专注于业务逻辑的开发。它通过自动化的方式处理路由注册、中间件管理、参数校验等,极大地简化了开发流程,提高了开发效率。无论是配置、路由、服务还是扩展,ElpisCore 都提供了清晰的结构和优雅的实现,帮助开发者快速、高效地构建和维护项目。