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

本文涉及的产品
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS AI 助手,专业版
简介: Go reflect为何需要使用 reflect 获取:减少重复代码1. API 接口中抽取参数的逻辑大量重复  API 接口自然是要获取传过来的数据,不同接口要获取的数据自然也不一样,如果不做特殊处理,必然是每个接口都有一堆功能重复的从 request 里获取参数的代码。
img_4a211a244946d57d596491438917a358.jpe
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")
}
相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
目录
相关文章
|
5月前
|
供应链 搜索推荐 数据挖掘
探秘京东 API 接口的神奇应用场景
京东API如同数字钥匙,助力商家实现商品、库存、订单等多平台高效同步,提升效率超80%。支持物流实时追踪,增强用户满意度;赋能精准营销与数据分析,决策准确率提升20%以上,全面优化电商运营。
162 1
|
6月前
|
人工智能 自然语言处理 机器人
使用 API 编程开发扣子应用
扣子(Coze)应用支持通过 API 编程,将 AI 聊天、内容生成、工作流自动化等功能集成至自有系统。主要 API 包括 Bot API(用于消息交互与会话管理)及插件与知识库 API(扩展功能与数据管理)。开发流程包括创建应用、获取密钥、调用 API 并处理响应,支持 Python 等语言。建议加强错误处理、密钥安全与会话管理,提升集成灵活性与应用扩展性。
1827 0
|
7月前
|
监控 供应链 搜索推荐
电商数据开发实践:深度剖析1688商品详情 API 的技术与应用
在电商数字化转型中,数据获取效率与准确性至关重要。本文介绍了一款高效商品详情API,具备全维度数据采集、价格库存管理、多媒体资源获取等功能,结合实际案例探讨其在电商开发中的应用价值与优势。
|
5月前
|
Ubuntu API C++
C++标准库、Windows API及Ubuntu API的综合应用
总之,C++标准库、Windows API和Ubuntu API的综合应用是一项挑战性较大的任务,需要开发者具备跨平台编程的深入知识和丰富经验。通过合理的架构设计和有效的工具选择,可以在不同的操作系统平台上高效地开发和部署应用程序。
227 11
|
5月前
|
存储 安全 Java
【Golang】(4)Go里面的指针如何?函数与方法怎么不一样?带你了解Go不同于其他高级语言的语法
结构体可以存储一组不同类型的数据,是一种符合类型。Go抛弃了类与继承,同时也抛弃了构造方法,刻意弱化了面向对象的功能,Go并非是一个传统OOP的语言,但是Go依旧有着OOP的影子,通过结构体和方法也可以模拟出一个类。
305 2
|
6月前
|
人工智能 数据可视化 测试技术
AI 时代 API 自动化测试实战:Postman 断言的核心技巧与实战应用
AI 时代 API 自动化测试实战:Postman 断言的核心技巧与实战应用
782 11
|
6月前
|
安全 API 数据安全/隐私保护
【Azure 环境】Microsoft Graph API实现对Entra ID中应用生成密码的时间天数
本文介绍如何通过 Azure 的 App Management Policy 限制用户在创建 AAD 应用程序的 Client Secret 时设置最长 90 天的有效期。通过 Microsoft Graph API 配置 defaultAppManagementPolicy,可有效控制密码凭据的生命周期,增强安全管理。
174 4
|
6月前
|
Java API 开发者
揭秘淘宝详情 API 接口:解锁电商数据应用新玩法
淘宝详情API是获取商品信息的“金钥匙”,可实时抓取标题、价格、库存等数据,广泛应用于电商分析、比价网站与智能选品。合法调用,助力精准营销与决策,推动电商高效发展。(238字)
241 0
|
7月前
|
存储 搜索推荐 安全
几个常用的电商API接口及其应用场景
电商平台依赖商品、订单、支付、客户、营销及数据分析六大API,实现商品管理、订单追踪、安全支付、用户个性化服务及精准营销等功能,全面支撑电商高效运营与业务拓展,推动行业智能化发展。
|
7月前
|
Cloud Native Go API
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
496 0