zoukankan      html  css  js  c++  java
  • sqler sql 转rest api 源码解析(四)macro 的执行

    macro 说明

    macro 是sqler 的核心,当前的处理流程为授权处理,数据校验,依赖执行(include),聚合处理,数据转换
    处理,sql 执行以及sql 参数绑定

    授权处理

    这个是通过golang 的js 包处理的,通过将golang 的http 请求暴露为js 的fetch 方法,放在js 引擎执行,通过
    http 状态吗确认是否是执行的权限,对于授权的处理,由宏的配置指定,建议通过http hreader处理
    参考格式:

     
        authorizer = <<JS
            (function(){
                log("use this for debugging")
                token = $input.http_authorization
                response = fetch("http://requestbin.fullcontact.com/zxpjigzx", {
                    headers: {
                        "Authorization": token
                    }
                })
                if ( response.statusCode != 200 ) {
                    return false
                }
                return true
            })()
        JS
     
     
    • 代码
    func (m *Macro) execAuthorizer(input map[string]interface{}) (bool, error) {
      authorizer := strings.TrimSpace(m.Authorizer)
      if authorizer == "" {
        return true, nil
      }
      var execError error
     // 暴露$input  对象到js 引擎
      vm := initJSVM(map[string]interface{}{"$input": input})
     //  执行js 脚本,根据返回的状态,确认请求权限
      val, err := vm.RunString(m.Authorizer)
      if err != nil {
        return false, err
      }
      if execError != nil {
        return false, execError
      }
      return val.ToBoolean(), nil
    }
     
     

    数据校验处理

    主要是对于传递的http 数据,转为是js 的$input 对象,通过js 引擎确认返回的状态
    数据校验配置:

     
    validators {
            user_name_is_empty = "$input.user_name && $input.user_name.trim().length > 0"
            user_email_is_empty = "$input.user_email && $input.user_email.trim(' ').length > 0"
            user_password_is_not_ok = "$input.user_password && $input.user_password.trim(' ').length > 5"
    }
     
     

    代码:

    // validate - validate the input aginst the rules
    func (m *Macro) validate(input map[string]interface{}) (ret []string, err error) {
      vm := initJSVM(map[string]interface{}{"$input": input})
      for k, src := range m.Validators {
        val, err := vm.RunString(src)
        if err != nil {
          return nil, err
        }
        if !val.ToBoolean() {
          ret = append(ret, k)
        }
      }
      return ret, err
    }
     
     

    依赖处理(include)

    获取配置文件中include 配置的数组信息,并执行宏
    一般配置如下:

     
        include = ["_boot"]

    代码:

    func (m *Macro) runIncludes(input map[string]interface{}) error {
      for _, name := range m.Include {
        macro := m.manager.Get(name)
        if nil == macro {
          return fmt.Errorf("macro %s not found", name)
        }
        _, err := macro.Call(input)
        if err != nil {
          return err
        }
      }
      return nil
    }
     
     

    执行聚合操作

    聚合主要是减少rest 端对于宏的调用,方便数据的拼接
    聚合的配置如下,只需要添加依赖的宏即可

     
    databases_tables {
        aggregate = ["databases", "tables"]
    }
     
     

    代码

    func (m *Macro) aggregate(input map[string]interface{}) (map[string]interface{}, error) {
        ret := map[string]interface{}{}
        for _, k := range m.Aggregate {
            macro := m.manager.Get(k)
            if nil == macro {
                err := fmt.Errorf("unknown macro %s", k)
                return nil, err
            }
            out, err := macro.Call(input)
            if err != nil {
                return nil, err
            }
            ret[k] = out
        }
        return ret, nil
    }
     
     

    执行sql

    sql 的处理是通过text/template,同时对于多条sql 需要使用;分开,而且sql 使用的是预处理的
    可以防止sql 注入。。。,同时这个阶段进行了bind 数据的处理,使用buildBind 方法
    格式:

     
    exec = <<SQL
            INSERT INTO users(name, email, password, time) VALUES(:name, :email, :password, UNIX_TIMESTAMP());
            SELECT * FROM users WHERE id = LAST_INSERT_ID();
        SQL
     
     
    • execSQLQuery代码:
    func (m *Macro) execSQLQuery(sqls []string, input map[string]interface{}) (interface{}, error) {
        args, err := m.buildBind(input)
        if err != nil {
            return nil, err
        }
        conn, err := sqlx.Open(*flagDBDriver, *flagDBDSN)
        if err != nil {
            return nil, err
        }
        defer conn.Close()
        for i, sql := range sqls {
            if strings.TrimSpace(sql) == "" {
                sqls = append(sqls[0:i], sqls[i+1:]...)
            }
        }
        for _, sql := range sqls[0 : len(sqls)-1] {
            sql = strings.TrimSpace(sql)
            if "" == sql {
                continue
            }
            if _, err := conn.NamedExec(sql, args); err != nil {
                return nil, err
            }
        }
        rows, err := conn.NamedQuery(sqls[len(sqls)-1], args)
        if err != nil {
            return nil, err
        }
        defer rows.Close()
        ret := []map[string]interface{}{}
        for rows.Next() {
            row, err := m.scanSQLRow(rows)
            if err != nil {
                continue
            }
            ret = append(ret, row)
        }
        return interface{}(ret), nil
    }
     
     
    • buildBind 处理
      bind 配置格式:
    bind {
            name = "$input.user_name"
            email = "$input.user_email"
            password = "$input.user_password"
    }
     
     

    代码:

    func (m *Macro) buildBind(input map[string]interface{}) (map[string]interface{}, error) {
     vm := initJSVM(map[string]interface{}{"$input": input})
     ret := map[string]interface{}{}
     for k, src := range m.Bind {
      val, err := vm.RunString(src)
      if err != nil {
       return nil, err
      }
      ret[k] = val.Export()
     }
     return ret, nil
    }
     
     

    执行数据转换

    我们可能需要更具实际的需要,将数据转换为其他的格式,sqler 使用了js 脚本进行处理,通过暴露
    $result 对象到js 运行是,然后调用转换函数对于数据进行转换
    配置格式:

     
     transformer = <<JS
            // there is a global variable called `$result`,
            // `$result` holds the result of the sql execution.
            (function(){
                newResult = []
                for ( i in $result ) {
                    newResult.push($result[i].Database)
                }
                return newResult
            })()
        JS
     
     

    代码:

    // execTransformer - run the transformer function
    func (m *Macro) execTransformer(data interface{}) (interface{}, error) {
        transformer := strings.TrimSpace(m.Transformer)
        if transformer == "" {
            return data, nil
        }
        vm := initJSVM(map[string]interface{}{"$result": data})
        v, err := vm.RunString(transformer)
        if err != nil {
            return nil, err
        }
        return v.Export(), nil
    }

    sqler 对于dop251/goja 的封装处理

    因为dop251/goja 设计的时候不保证并发环境下的数据一致,所以每次调用都是重新
    实例化,js runtime

    js vm 实例化

    代码如下:
    js.go

     
    // initJSVM - creates a new javascript virtual machine
    func initJSVM(ctx map[string]interface{}) *goja.Runtime {
        vm := goja.New()
        for k, v := range ctx {
            vm.Set(k, v)
        }
        vm.Set("fetch", jsFetchfunc)
        vm.Set("log", log.Println)
        return vm
    }
     

    fetch 、log 方法暴露

    为了方便排查问题,以及授权中集成http 请求,所以sqler暴露了一个fetch 方法(和js 的http fetch 功能类似)
    方便进行http 请求的处理
    代码:

     
    // jsFetchfunc - the fetch function used inside the js vm
    func jsFetchfunc(url string, options ...map[string]interface{}) (map[string]interface{}, error) {
        var option map[string]interface{}
        var method string
        var headers map[string]string
        var body interface{}
        if len(options) > 0 {
            option = options[0]
        }
        if nil != option["method"] {
            method, _ = option["method"].(string)
        }
        if nil != option["headers"] {
            hdrs, _ := option["headers"].(map[string]interface{})
            headers = make(map[string]string)
            for k, v := range hdrs {
                headers[k], _ = v.(string)
            }
        }
        if nil != option["body"] {
            body, _ = option["body"]
        }
        resp, err := resty.R().SetHeaders(headers).SetBody(body).Execute(method, url)
        if err != nil {
            return nil, err
        }
        rspHdrs := resp.Header()
        rspHdrsNormalized := map[string]string{}
        for k, v := range rspHdrs {
            rspHdrsNormalized[strings.ToLower(k)] = v[0]
        }
        return map[string]interface{}{
            "status": resp.Status(),
            "statusCode": resp.StatusCode(),
            "headers": rspHdrsNormalized,
            "body": string(resp.Body()),
        }, nil
    }
    
    

    说明

    基本上sqler 的源码已经完了,本身代码量不大,但是设计很简洁

    参考资料

    https://github.com/dop251/goja
    https://github.com/alash3al/sqler/blob/master/macro.go
    https://github.com/alash3al/sqler/blob/master/js.go

  • 相关阅读:
    day25:接口类和抽象类
    vue1
    How the weather influences your mood?
    机器学习实验方法与原理
    How human activities damage the environment
    Slow food
    Brief Introduction to Esports
    Massive open online course (MOOC)
    Online learning in higher education
    Tensorflow Dataset API
  • 原文地址:https://www.cnblogs.com/rongfengliang/p/10268503.html
Copyright © 2011-2022 走看看