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
sqler sql 转rest api 源码解析(四)macro 的执行的更多相关文章
- sqler sql 转rest api 源码解析(一)应用的启动入口
sqler sql 转rest api 的源码还是比较简单的,没有比较复杂的设计,大部分都是基于开源 模块实现的. 说明: 当前的版本为2.0,代码使用go mod 进行包管理,如果本地运行注意gol ...
- sqler sql 转rest api 源码解析(三) rest协议
rest 服务说明 rest 协议主要是将配置文件中的宏暴露为rest 接口,使用了labstack/echo web 框架,同时基于context 模型 进行宏管理对象的共享,同时进行了一些中间件的 ...
- sqler sql 转rest api 源码解析(二) resp 协议
resp 协议主要是方便使用redis 客户端进行连接,resp 主要是依赖 tidwall/redcon golang redis 协议包 resp 服务说明 server_resp.go 文件,干 ...
- Mybatis源码解析(四) —— SqlSession是如何实现数据库操作的?
Mybatis源码解析(四) -- SqlSession是如何实现数据库操作的? 如果拿一次数据库请求操作做比喻,那么前面3篇文章就是在做请求准备,真正执行操作的是本篇文章要讲述的内容.正如标题一 ...
- Sentinel源码解析四(流控策略和流控效果)
引言 在分析Sentinel的上一篇文章中,我们知道了它是基于滑动窗口做的流量统计,那么在当我们能够根据流量统计算法拿到流量的实时数据后,下一步要做的事情自然就是基于这些数据做流控.在介绍Sentin ...
- iOS即时通讯之CocoaAsyncSocket源码解析四
原文 前言: 本文为CocoaAsyncSocket源码系列中第二篇:Read篇,将重点涉及该框架是如何利用缓冲区对数据进行读取.以及各种情况下的数据包处理,其中还包括普通的.和基于TLS的不同读取操 ...
- Dubbo 源码解析四 —— 负载均衡LoadBalance
欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 Dubbo 入门之二 --- 项目结构解析 Dubbo 源码分析系列之三 -- 架构原 ...
- React的React.createContext()源码解析(四)
一.产生context原因 从父组件直接传值到孙子组件,而不必一层一层的通过props进行传值,相比较以前的那种传值更加的方便.简介. 二.context的两种实现方式 1.老版本(React16.x ...
- 第三十四节,目标检测之谷歌Object Detection API源码解析
我们在第三十二节,使用谷歌Object Detection API进行目标检测.训练新的模型(使用VOC 2012数据集)那一节我们介绍了如何使用谷歌Object Detection API进行目标检 ...
随机推荐
- angular4-事件绑定
事件绑定语法(可以通过 (事件名) 的语法,实现事件绑定) <date-picker (dateChanged)="statement()"></date-pic ...
- 学习net core的一些疑问?
所有的内容是否一定都要依赖注入? 获取配置文件的方式是否在类库是获取不到环境变量的? 老出现:InvalidOperationException: Unable to resolve service ...
- 10.Python-第三方库requests详解(二)
Requests 是用Python语言编写,基于 urllib,采用 Apache2 Licensed 开源协议的 HTTP 库.它比 urllib 更加方便,可以节约我们大量的工作,完全满足 HTT ...
- day 51
一 window对象 window 对象表示一个浏览器窗口. 在客户端 JavaScript 中,Window 对象是全局对象,所有的表达式都在当前的环境中计算.也就是说,要引用当前窗口根本不需要特殊 ...
- scrapy--分布式爬虫
14.3 使用scrapy-redis进行分布式爬取了解了scrapy-redis的原理后,我们学习使用scrapy + scrapyredis进行分布式爬取.14.3.1 搭建环境首先搭建scrap ...
- YUM仓库配置
YUM的前身是YUP(Yellow dog Updater,Yellow dog Linux的软件更新器),最初由TSS公司(Terra Soft Solutions,INC.)使用Python语言开 ...
- Python 操作系统介绍 进程的创建
背景知识 顾名思义,进程即正在执行的一个过程.进程是对正在运行程序的一个抽象. 进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一.操作系统的其他所有内 ...
- 什么时候用深搜(dfs)什么时候用广搜(bfs)(转)
1.BFS是用来搜索最短径路的解是比较合适的,比如求最少步数的解,最少交换次数的解,因为BFS搜索过程中遇到的解一定是离根最近的,所以遇到一个解,一定就是最优解,此时搜索算法可以终止.这个时候不适宜使 ...
- OSPF路由协议(二)
实验要求:使用OSPF路由协议,使每个路由器都能收集到所有网段 拓扑如下: 配置如下: R1enableconfigure terminalinterface l0ip address 192.168 ...
- builtroot 添加git 下载方式
1.buildroot/Config.in 配置default git server eg:config xxxx_GIT_SITE string "git site" defau ...