golang实现rest server

发布时间:2019-09-25 08:14:17编辑:auto阅读(1442)

    第一篇:用golang对数据库标准操作进行封装(mysql)

    背景

    用golang对数据库标准操作进行封装,为后面的rest server提供数据库访问层。实现的目标是:能根据rest请求参数自动生成数据库操作语句,提供增、删、改、查、批量写入、事务等必要的数据库操作封装。并可以方便的扩展到多种数据库,让所有的数据库操作对于rest server来说表现为一致的访问接口。

    一些关键点

    1. 接口设计做到恰到好处,够用且不繁杂。
    2. 函数参数的设计,go不支持函数重载,如何善用interface{}。
    3. 用map[string]interface{}来处理rest的json请求参数,并自动生成相应的sql。
    4. 数据库查询结果能方便的转化为json,让rest server返回给用户。

    代码解析

    按功能模块对核心代码进行说明

    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来实现,期望有惊喜,写代码我是认真的。

关键字