elpis-core

83 阅读3分钟

引言

课程学习来自于“哲玄前端”,哲玄大佬的讲解深入浅出,令我收益匪浅。实在是良心的课程,在实践中学到了很多编程思想和设计思路。

1. 项目概述

Elpis 项目是一个基于 Koa 框架构建的服务端应用,它整合了多种功能模块,包括路由管理、中间件处理、服务调用和配置管理等。支持不同环境(本地、测试、生产)的配置,能满足多样化的开发和部署需求。

2. 项目结构

.eslintignore
.eslintrc.json
.git/
.gitignore
README.md
app/
  middleware.js
  controller/
      base.js
      project.js
      view.js
  extend/
      logger.js
  middleware/
      api-params-verify.js
      api-sign-verify.js
      error-handler.js
  public/
      static/
  router/
      project.js
      view.js
  router-schema/
      project.js
  service/
      base.js
      project.js
config/
  config.beta.js
  config.default.js
  config.local.js
  config.prod.js
elpio-core/
  env.js
  index.js
  loader/
   config.js   
   controller.js
   extend.js
   middleware.js
   router-schema.js
   router.js
   service.js
index.js
package.json

关键目录及功能

  • app:存放业务逻辑代码,包含控制器、中间件、服务、扩展等。
    • controller:控制器层:处理HTTP请求
    • extend:框架扩展点
    • middleware:中间件:请求预处理。
    • router:路由定义:URL映射
    • router-schema:API参数验证规则
    • service:服务层:封装业务逻辑
  • elpio-core:框架核心代码
    • index:框架启动入口
    • env:环境变量处理
    • loader:模块加载器

核心功能实现

1. 洋葱模型中间件系统

基于Koa的洋葱模型,设计了一套灵活的中间件系统。每个请求都会经过一系列中间件处理,形成一个完整的处理管道。(如下图)

image.png

module.exports = (app) =>{
    return async (ctx, next) =>{
        try {
            await next()
        } catch(err) {
            // 异常处理
            const {status, message, detail } = err

            app.logger.info(JSON.stringify(err))
            app.logger.error('[-- exception -- ]', err)
            app.logger.error('[-- 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
        }
    }
}

2.参数校验

基于AJV的强大参数校验系统,自动验证API请求的各部分(headers、query、body、params):

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

        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 = {
                suceess: false,
                message: `request validate fail: ${ajv.errorsText(validate.errors)}`,
                code: 445
            }
            return
        }

        await next()
    }
}

3.服务端渲染

内置Nunjucks模板引擎支持,实现高效的服务端渲染:

module.exports = (app)=>{
    return class ViewController{
        /**
         *  渲染页面
         *  @param {object} ctx 上下文
         */

        async renderPage(ctx) {
            await ctx.render(`dist/entry.${ctx.params.page}`, {
                name: app.options?.name,
                env: app.env.get(),
                options: JSON.stringify(app.options)
            })
        }
    }
}

4.模块加载化

框架通过加载系统,自动加载各类业务信息

配置加载的优化

module.exports = {
    /**
     * 启动项
     * @param {*} options 项目配置
     * options = {
     *  name // 项目名称
     *  homePath // 项目首页
     * }
     */
    start(options = {}) {
        // koa实例
        const app = new Koa()

        // 应用配置
        app.options = options
        // console.log(app.options);

        // 基础路径
        app.baseDir = process.cwd();
        // console.log(app.baseDir);

        // 业务文件路径
        app.businessPath = path.resolve(app.baseDir, `.${sep}app`)
        // console.log( app.businessPath);

        // 初始化环境配置
        app.env = env()
        console.log(`-- [start]  env: ${app.env.get()} `);
        
        // 加载 middleware
        middlewareLoader(app)
        console.log(`-- [start] lod middleware done`);

        // 加载 routerSchema
        routerSchemaLoader(app)
        console.log(`-- [start] lod routerSchema done`);

        // 加载 controller
        controllerLoader(app)
        console.log(`-- [start] lod controller done`);

        // 加载 service
        serviceLoader(app)
        console.log(`-- [start] lod service done`);
        
        // 加载 config
        configLoader(app)
        console.log(`-- [start] lod config done`);

        // 加载 extend
        extendLoader(app)
        console.log(`-- [start] lod extend done`);

        // 注册全局中间件
        try {
            require(`${app.businessPath}${sep}middleware.js`)(app)
            console.log(`-- [start] lod global middleware done -- `);
            
        } catch{
            console.log('[exception] there is no global middleware file');
            
        }

        // 加载 router
        routerLoader(app)
        console.log(`-- [start] lod router done`);
        

        // 启动服务器
        try{
            const port  = process.env.PORT || 8080;
            const host = process.env.IP || '0.0.0.0';
            app.listen(port,host);
            console.log(`Server runnin on port: ${port}`)
        } catch(e){
            console.error(e);
        }
    }
}

总结

Elpis Core 是一个设计良好的轻量级服务引擎框架,其模块化设计和清晰的架构使其非常适合构建中小型服务。虽然存在一些限制,但整体而言是一个值得学习和使用的框架。它的架构设计思想对于学习 Node.js 服务端框架设计很有帮助。

对于想要学习服务端框架设计的开发者来说,Elpis Core 提供了很好的学习范例,特别是在模块化设计、服务架构设计等方面。