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

  • 相关阅读:
    linux服务器MySQL数据从磁盘拷贝以及恢复
    java编程思想第四版第十四章 类型信息习题
    java编程思想第四版第十四章 类型信息总结
    java编程思想第四版第十三章字符串 总结
    java编程思想第四版第十三章字符串 习题
    java编程思想第四版第十一章习题
    java编程思想第四版第十一章总结
    jQuery—自定义HTTP请求
    JavaScript—Json操作
    JavaScript—字符串(String)用法
  • 原文地址:https://www.cnblogs.com/rongfengliang/p/10268503.html
Copyright © 2011-2022 走看看