golang實現rest server框架(一)

第一篇:用golang對數據庫標準操做進行封裝(mysql)

背景

用golang對數據庫標準操做進行封裝,爲後面的rest server提供數據庫訪問層。實現的目標是:能根據rest請求參數自動生成數據庫操做語句,提供增、刪、改、查、批量寫入、事務等必要的數據庫操做封裝。並能夠方便的擴展到多種數據庫,讓全部的數據庫操做對於rest server來講表現爲一致的訪問接口。java

一些關鍵點

  1. 接口設計作到恰到好處,夠用且不繁雜。
  2. 函數參數的設計,go不支持函數重載,如何善用interface{}。
  3. 用map[string]interface{}來處理rest的json請求參數,並自動生成相應的sql。
  4. 數據庫查詢結果能方便的轉化爲json,讓rest server返回給用戶。

代碼解析

按功能模塊對核心代碼進行說明node

IBock.go

數據庫標準操做接口定義,根據個人實踐經驗,如下的接口設計已經可以很好的支持大部分的數據庫操做,這些操做包括了根據json參數自動完成的CURD、手寫sql支持、批量插入(更新)心及事務操做。
type IBock interface{
    //根據參數,自動完成數據庫查詢
    Retrieve(params map[string]interface{}, args ...interface{}) map[string]interface{}
    //根據參數,自動完成數據庫插入
    Create(params map[string]interface{}, args ...interface{}) map[string]interface{}
    //根據參數,自動完成數據庫更新(只支持單條)
    Update(params map[string]interface{}, args ...interface{}) map[string]interface{}
    //根據參數,自動完成數據庫刪除(只支持單條)
    Delete(params map[string]interface{}, args ...interface{}) map[string]interface{}
    //手寫查詢sql支持
    QuerySql(sql string, values []interface{}, params map[string]interface{}) map[string]interface{}
    //手寫非查詢sql支持
    ExecSql(sql string, values []interface{}) map[string]interface{}
    //批量插入或更新
    InsertBatch(tablename string, els []interface{}) map[string]interface{}
    //事務支持
    TransGo(objs map[string]interface{}) map[string]interface{}
}
參數說明
  • params, 對應rest server接收到用戶數據,由json對象轉換而來。
  • args,這個參數的目標是接收id(信息ID),fields(表字段數組),session(用戶session)這三個參數,這樣作的初衷是既要統一接口函數形式,又能夠在編碼時少傳入做爲點位符的nil
  • values,爲sql查詢參數化提供的參數列表
  • els,批量插入的每一行數據對象集
  • objs,事務對象集
  • 返回參數爲go的映射,很容易轉化爲json。

Bock.go

接口的具體實現,本文是對mysql的實現,暫只實現了基本的CURD,項目中會逐步完善。
//咱們把操做對象定義在一個表上
type Bock struct {
    Table string
}
//parseArgs函數的功能是解析args參數中包括的可變參數,實如今下面
func (b *Bock) Retrieve(params map[string]interface{}, args ...interface{}) map[string]interface{} {
    //查詢時咱們通常只關注查詢哪些表字段
    _, fields, _ := parseArgs(args)
    //調用具體的查詢接口,查詢接口將根據輸入參數params自動實現sql查詢語句,支持多樣的查詢定義,如:lks(從多個字體查詢相同內容),ors(或查詢),ins(in查詢)等
    return Query(b.Table, params, fields)
}

func (b *Bock) Create(params map[string]interface{}, args ...interface{}) map[string]interface{} {
    //新建接口,通常都會關注用戶在session的ID
    _, _, session := parseArgs(args)
    uId := session["userid"].(string)
    params["u_id"] = uId
    //調用具體的插入接口
    return Insert(b.Table, params)
}

func (b *Bock) Update(params map[string]interface{}, args ...interface{}) map[string]interface{} {
    //只支持單個更新,因此ID必須存在
    id, _, _ := parseArgs(args)
    if len(id) == 0 {
        rs := make(map[string]interface{})
        rs["code"] = 301
        rs["err"] = "Id must be input."
        return rs
    }
    return Update(b.Table, params)
}

func (b *Bock) Delete(params map[string]interface{}, args ...interface{}) map[string]interface{} {
    //只支持單個刪除,因此ID必須存在
    id, _, _ := parseArgs(args)
    if len(id) == 0 {
        rs := make(map[string]interface{})
        rs["code"] = 301
        rs["err"] = "Id must be input."
        return rs
    }
    return Delete(b.Table, params)
}
parseArgs函數的實現
func parseArgs(args []interface{}) (string, []string, map[string]interface{}) {
    //解析指定的參數
    var id string                                //信息ID
    var fields []string                          //查詢字段集
    var session map[string]interface{}           //用戶session對象
    for _, vs := range args {
        switch vs.(type) {
        case map[string]interface{}:            //只接收指定類型
            for k, v := range vs.(map[string]interface{}) {
                if k == "id" {
                    id = v.(string)
                }
                if k == "fields" {
                    fields = v.([]string)
                }
                if k == "session" {
                    session = v.(map[string]interface{})
                }
            }
        default:
        }
    }
    return id, fields, session    //返回解析成功的參數
}

Helper.go

數據操做的具體實現,大可能是僞代碼,項目後續會逐步完善,查詢接口最重要,後面會有單獨文章進行解析
func Query(tablename string, params map[string]interface{}, fields []string ) map[string]interface{} {
    //調用具體實現的私用函數,接口中分自動和手動兩個函數,在私用函數中屏蔽差別內聚功能
    return query(tablename, params, fields, "", nil)
}

func Insert(tablename string, params map[string]interface{}) map[string]interface{} {
    sql := "Insert into " + tablename
    values := make([]interface{},0)
    return execute(sql, values)
}

func Update(tablename string, params map[string]interface{}) map[string]interface{} {
    sql := "Update " + tablename + " set "
    values := make([]interface{},0)
    return execute(sql, values)
}

func Delete(tablename string, params map[string]interface{}) map[string]interface{} {
    sql := "Delete from " + tablename + " where"
    values := make([]interface{},0)
    return execute(sql, values)
}
私用查詢函數定義
//五個輸入參數,分別適配自動與手動查詢
func query(tablename string, params map[string]interface{}, fields []string, sql string, vaules []interface{}) map[string]interface{} {
    if vaules == nil {
        vaules = make([]interface{},0)
    }
    //調用真正的數據庫操做函數
    return execQeury("select "+ strings.Join(fields, ",")+" from " + tablename, vaules)
}
非查詢類具體操做函數
//由於golang把有結果集的和無結果集的操做是分開的,不象在java或node.js中,能夠有高級函數進行統一操做,只能分開。
func execute(sql string, values []interface{}) map[string]interface{}  {
    //返回json對象,以map形式表達
    rs := make(map[string]interface{})
    rs["code"] = 200
    return rs
}
查詢類具體操做(已經實現),結果集以json對象封裝,存儲在map中
func execQeury(sql string, values []interface{}) map[string]interface{}  {
    var configs interface{}
    ...//省略數據配置獲取代碼,請參照之前的文章
    dao, err := mysql.Open(dialect, dbUser + ":"+dbPass+"@tcp("+dbHost+":"+dbPort+")/"+dbName+"?charset="+dbCharset)
    stmt, err := dao.Prepare(sql)
    rows, err := stmt.Query(values...)

    columns, err := rows.Columns()       //取出字段名稱
    vs := make([]mysql.RawBytes, len(columns))
    scans := make([]interface{}, len(columns))

    for i := range vs {                 //預設取值地址
        scans[i] = &vs[i]
    }

    var result []map[string]interface{}
    for rows.Next() {
        _ = rows.Scan(scans...)        //塡入一列值
        each := make(map[string]interface{})

        for i, col := range vs {
            if col != nil {
                each[columns[i]] = string(col)        //增值
            }else{
                each[columns[i]] = nil
            }
        }

        result = append(result, each)
    }
    rs["code"] = 200
    //data, _ := json.Marshal(result)            //這樣就能轉換爲json
    rs["rows"] = result
    return rs
}
數據庫的批量操做,在前面的文章中已經用golang實現,只是還未封裝,有興趣的朋友能夠看我前面的文章。

bock.go(程序入口)

最終目標的入口將是一個網絡服務,提供標準的restful服務,如今只是用來測試,再這說明一下願景。
table := Bock.Bock{                    //上體實例
        Table: "role",                     //對role表時行操做
    }
    var params map[string] interface{}     //模擬json參數
    args := make(map[string] interface{})  //其它參數
    db := make([]DB.IBock, 1)              //對接口編程
    db[0] = &table                         //接口指向實例對象,這裏能夠現時處理多個不一樣的實例
    fields := []string {"id", "name"}
    args["fields"] = fields
    rs, _ := db[0].Retrieve(params, args)  //在這能夠循環處理多個不一樣的實例,咱們最終的目標就是在這接受用戶的http請求,由路由自動分發不一樣的請求,咱們的數據庫封裝自動生成sql語句完成用戶的基本需求。
    fmt.Println(rs)

項目地址

https://github.com/zhoutk/goTools

使用方法

git clone https://github.com/zhoutk/goTools
cd goTools
go get
go run bock.go

go buid bock.go
./bock

小結

通過多種方案的對比,發現go語言做爲網絡服務的吞吐率是最棒的,因此有了將以往在其它平臺上的經驗(node.js,java,python3),用go來實現,指望有驚喜,寫代碼我是認真的。python

相關文章
相關標籤/搜索