sqler sql 转rest api 源码解析(四)macro 的执行

macro 说明

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

授权处理

这个是通过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 注入。。。
格式:

exec = <<SQL
        INSERT INTO users(name, email, password, time) VALUES(:name, :email, :password, UNIX_TIMESTAMP());
        SELECT * FROM users WHERE id = LAST_INSERT_ID();
    SQL

代码:

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
}

执行数据转换

我们可能需要更具实际的需要,将数据转换为其他的格式,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

原文地址:https://www.cnblogs.com/rongfengliang/p/10268503.html

时间: 2024-10-06 23:39:49

sqler sql 转rest api 源码解析(四)macro 的执行的相关文章

sqler sql 转rest api 源码解析(一)应用的启动入口

sqler sql 转rest api 的源码还是比较简单的,没有比较复杂的设计,大部分都是基于开源 模块实现的. 说明: 当前的版本为2.0,代码使用go mod 进行包管理,如果本地运行注意golang 版本,我使用docker 运行, 参考 https://github.com/rongfengliang/sqler-docker-compose/blob/master/Dockerfile 依赖的开源包 配置解析的(比如bind,exec,validates,include...) 使用

Mybatis源码解析(四) —— SqlSession是如何实现数据库操作的?

Mybatis源码解析(四) -- SqlSession是如何实现数据库操作的? ??如果拿一次数据库请求操作做比喻,那么前面3篇文章就是在做请求准备,真正执行操作的是本篇文章要讲述的内容.正如标题一样,本篇文章最最核心的要点就是 SqlSession实现数据库操作的源码解析.但按照惯例,我这边依然列出如下的问题: 1. SqlSession 是如何被创建的? 每次的数据库操作都会创建一个新的SqlSession么?(也许有很多同学会说SqlSession是通过 SqlSessionFactor

Spring 源码解析之ViewResolver源码解析(四)

Spring 源码解析之ViewResolver源码解析(四) 1 ViewResolver类功能解析 1.1 ViewResolver Interface to be implemented by objects that can resolve views by name. View state doesn't change during the running of the application, so implementations are free to cache views. I

Spring Boot 启动源码解析系列六:执行启动方法一

1234567891011121314151617181920212223242526272829303132333435363738394041424344 public ConfigurableApplicationContext (String... args) { StopWatch stopWatch = new StopWatch(); // 开始执行,记录开始时间 stopWatch.start(); ConfigurableApplicationContext context =

iOS即时通讯之CocoaAsyncSocket源码解析四

原文 前言: 本文为CocoaAsyncSocket源码系列中第二篇:Read篇,将重点涉及该框架是如何利用缓冲区对数据进行读取.以及各种情况下的数据包处理,其中还包括普通的.和基于TLS的不同读取操作等等.注:由于该框架源码篇幅过大,且有大部分相对抽象的数据操作逻辑,尽管楼主竭力想要简单的去陈述相关内容,但是阅读起来仍会有一定的难度.如果不是诚心想学习IM相关知识,在这里就可以离场了... 注:文中涉及代码比较多,建议大家结合源码一起阅读比较容易能加深理解.这里有楼主标注好注释的源码,有需要的

第三十四节,目标检测之谷歌Object Detection API源码解析

我们在第三十二节,使用谷歌Object Detection API进行目标检测.训练新的模型(使用VOC 2012数据集)那一节我们介绍了如何使用谷歌Object Detection API进行目标检测,以及如何使用谷歌提供的目标检测模型训练自己的数据.在训练自己的数据集时,主要包括以下几步: 制作自己的数据集,注意这里数据集在进行标注时,需要按照一定的格式.然后调object_detection\dataset_tools下对应的脚本生成tfrecord文件.如下图,如果我们想调用create

AFNetworking2.0源码解析&lt;四&gt;

结构 AFURLResponseSerialization负责解析网络返回数据,检查数据是否合法,把NSData数据转成相应的对象,内置的转换器有json,xml,plist,image,用户可以很方便地继承基类AFHTTPResponseSerializer去解析更多的数据格式,AFNetworking这一套响应解析机制结构很简单,主要就是两个方法: 1.-validateResponse:data:error: 基类AFHTTPResponseSerializer的这个方法检测返回的HTTP

Hangfire源码解析-任务是如何执行的?

一.Hangfire任务执行的流程 任务创建时: 将任务转换为Type并存储(如:HangFireWebTest.TestTask, HangFireWebTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null) 将参数序列化后存储 任务执行时: 根据Type值判断是否是静态方法,若非静态方法就根据Type从IOC容器中取实例. 反序列化参数 使用反射调用方法:MethodInfo.Invoke 二.Hangfire执行任务 从源码

vuex 源码解析(四) mutation 详解

mutation是更改Vuex的store中的状态的唯一方法,mutation类似于事件注册,每个mutation都可以带两个参数,如下: state ;当前命名空间对应的state payload   ;传入的参数,一般是一个对象 创建Vuex.Store()仓库实例时可以通过mutations创建每个mutation 我们不能直接调用一个mutation,而是通过 store.commit来调用,commit可以带两个参数,如下: type ;对应的mutation名 payload ;传入