美文网首页Go语言程序猿阵线联盟-汇总各类技术干货Go
Go 语言反射和范型在 API 服务中的应用

Go 语言反射和范型在 API 服务中的应用

作者: blackpiglet | 来源:发表于2018-04-27 22:30 被阅读58次
    Go reflect
    1. 为何需要使用 reflect 获取:减少重复代码

    1. API 接口中抽取参数的逻辑大量重复

      API 接口自然是要获取传过来的数据,不同接口要获取的数据自然也不一样,如果不做特殊处理,必然是每个接口都有一堆功能重复的从 request 里获取参数的代码。

    2. API 框架提供的抽取参数的方式并不满足需求

      当然 API 框架会提供这些功能,不过有些情况不能满足需求,比如gin-gonic,提供了将将 request 转为对应结构体的函数,但存在两个问题,第一个问题是参数区分大小写,我觉得应该实现大小写的通配,这样健壮性更高;第二是结构体直接对应数据库表结构,部分数据是不应该从接口请求中读取的,比如创建时间和删除标志,全转换的方式就很有问题。
      不过也有可能是因为我对 gin 不熟悉,不知道更好的用法,才自己造轮子,如果大家有更简洁漂亮的写法,请不吝赐教。

    3. Golang 强类型语言的限制

      Go 语言是强类型语言,函数间传递参数或者返回值,必须有特定的类型,如果要实现这种范类型的处理相对 Python 等弱类型的语言要困难一些。
      Python 对于 struct 参数没有严格的限制,传什么内容都行,Golang 就没那么友好了,这部分要靠范型来处理。

    # struct 是要获得的数据结果,params 是要抽取的参数名称数组,request 是接口的请求结构体。
    def ExtractParamFromBody(struct, params, request):
        ...
    

      还有一点就是要能获取到 struct 结构体中每个参数的类型,并且给其赋值,Golang 提供的 reflect 机制可以很好的完成这项功能。

    4. 实例

      以下代码先是建立了数据库连接(请注意,数据的连接需要提前建立好,并按照代码中的用户名、密码、地址、端口和数据库名称建立,不然代码无法运行成功);之后在数据库中建立了一个叫 User 的表;之后有一个创建用户的接口 "POST /users",对应的函数为 CreateUser。
      ExtractParamFromBody 是通用的参数抽取函数,不光是 User 类型,interface{} 是 Golang 中范型,可以对应任何结构体。

    package main
    
    import (
        "fmt"
        "reflect"
        "strconv"
        "strings"
    
        "github.com/gin-gonic/gin"
        "github.com/jinzhu/gorm"
    )
    
    type User struct {
        ID              uint   `json:"id" gorm:"PRIMARY_KEY;AUTO_INCREMENT"`
        Name            string `json:"name" gorm:"INDEX:name;UNIQUE;NOT NULL;type:varchar(100)"`
        Password        string `json:"password" gorm:"NOT NULL"`
        Mobile          string `json:"mobile"`
        Email           string `json:"email"`
        Role_id         uint   `json:"role_id"`
        Create_Time     uint   `json:"create_time"`
        Login_Time      uint   `json:"login_time"`
        Last_Login_Time uint   `json:"last_login_time`
        Login_Count     uint   `json:"login_count"`
        Deleted         bool   `json:"deleted" gorm:"DEFAULT:0"`
    }
    
    var db *gorm.DB
    var err error
    
    func ExtractParamFromBody(s interface{}, params []string, c *gin.Context) {
        var typ reflect.Type
        var val reflect.Value
    
        ptyp := reflect.TypeOf(s)
    
        if ptyp.Kind() == reflect.Ptr {
            val = reflect.ValueOf(s).Elem()
            typ = reflect.TypeOf(s).Elem()
        } else {
            val = reflect.ValueOf(s)
        }
    
        for _, param := range params {
            ret := c.PostForm(param)
            if ret != "" {
                for i := 0; i < typ.NumField(); i++ {
                    if strings.ToLower(typ.Field(i).Name) == param {
                        if val.Field(i).Kind() == reflect.String && val.CanSet() {
                            val.Field(i).SetString(ret)
                        } else if val.Field(i).Kind() == reflect.Uint && val.CanSet() {
                            ret_int, _ := strconv.Atoi(ret)
                            val.Field(i).SetUint(uint64(ret_int))
                        }
                    }
                }
            }
        }
    }
    
    func InitMysql() *gorm.DB {
        mysql_connection_string := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local",
            "root",
            "mysql",
            "127.0.0.1",
            "3306",
            "test_db")
        db, err = gorm.Open("mysql", mysql_connection_string)
    
        if err != nil {
            log.Logger.Critical("Fail to connect MySQL: %s. Exit.", err)
            os.Exit(5)
        }
    
        db.AutoMigrate(&User{})
        return db
    }
    
    func CreateUser(c *gin.Context) {
        var user mysql.User
    
        params := []string{"name", "password", "email", "mobile", "role_id"}
        ExtractParamFromBody(&user, params, c)
    
        if err := db.Create(&user).Error; err != nil {
            c.JSON(200, gin.H{
                "code":   3,
                "result": "failed",
                "msg":    "Fail to create user",
            })
        } else {
            c.JSON(200, gin.H{
                "code":       0,
                "result":     "success",
                "msg":        "success",
                "resultBean": user,
            })
        }
    }
    
    func main() {
        InitMysql()
    
        r := gin.Default()
        r.POST("/v1/users", CreateUser)
        r.Run(":8080")
    }
    

    相关文章

      网友评论

        本文标题:Go 语言反射和范型在 API 服务中的应用

        本文链接:https://www.haomeiwen.com/subject/krxwlftx.html