Golang 泛型使用在HTTP Request 应用示例
Request 请求参数#
通常使用 go 编写 http 服务总会需要定义接口接收的参数, 前端上传(接口请求)通常是将数据上传方式修改为
application/json。
同理在 go 中也通常使用自定义一个struct进行接收, 使用类似 json.Unmarshal() 对上传的 json 字符串进行反序列化将对应的key通过struct.tag
方式映射到对应 field 上。
经典示例#
net/http#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| type Releases struct {
Version string `json:"version"`
Name string `json:"name"`
}
func ReleasesApi(w http.ResponseWriter, r *http.Request) {
//get upload param
b, err := io.ReadAll(r.Body)
if err != nil {
panic(err)
return
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(r.Body)
req := ReleasesPkg{}
err = json.Unmarshal(b, &req)
if err != nil {
panic(err)
return
}
return
}
|
gin#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| type Releases struct {
Version string `json:"version"`
Name string `json:"name"`
}
func ReleasesApi(ctx *gin.Context) {
//get upload param
req := Releases{}
err := ctx.ShouldBindJSON(&req)
if err != nil {
panic(err)
return
}
return
}
|
以上方式都可以做到将json请求参数反序列化到变量 req 。
规范请求 json 结构#
实际开发中遇到有固定请求层级的 json 规范 例如
1
2
3
4
5
6
7
8
9
10
11
12
13
| {
"funcId": "",
"mid": "",
"sn": "",
"payload": {
"params": {
"version": "v1.0.0",
"name": "test"
},
"page":1,
"page_size": 2
}
}
|
对应 go struct
1
2
3
4
5
6
7
8
9
10
11
12
13
| type Request struct {
FuncId string `json:"funcId"`
Mid string `json:"mid"`
Sn string `json:"sn"`
Payload struct {
Params struct {
Version string `json:"version"`
Name string `json:"name"`
} `json:"params"`
Page int `json:"page"`
PageSize int `json:"page_size"`
} `json:"payload"`
}
|
其中规范除 Params 之外全部一致,这样就无法复用通用结构体除 Params 之外 field 例如
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| type Request struct {
FuncId string `json:"funcId"`
Mid string `json:"mid"`
Sn string `json:"sn"`
Payload struct {
Params any `json:"params"`
Page int `json:"page"`
PageSize int `json:"page_size"`
} `json:"payload"`
}
type Releases struct {
Version string `json:"version"`
Name string `json:"name"`
}
|
解决方案#
1. 反复序列化#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| func ReleasesApi(ctx *gin.Context) {
var (
err error
message Request
req Releases
)
b, err := io.ReadAll(ctx.Request.Body)
if err != nil {
panic(err)
return
}
//first Unmarshal get params
err = json.Unmarshal(b, &message)
if err != nil {
panic(err)
return
}
b, err = json.Marshal(message.Payload.Params)
if err != nil {
panic(err)
return
}
err = json.Unmarshal(b, &req)
if err != nil {
panic(err)
return
}
fmt.Println(message)
fmt.Println(req)
return
}
|
第一层解析通用结构体 Request 再二次进行 解析 Releases
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| func ReleasesApi(ctx *gin.Context) {
var (
err error
message Request
req Releases
)
b, err := io.ReadAll(ctx.Request.Body)
if err != nil {
panic(err)
return
}
message.Payload.Params = req
//first Unmarshal get params
err = json.Unmarshal(b, &req)
if err != nil {
panic(err)
return
}
err = mapstructure.Decode(message.Payload.Params, &req)
if err != nil {
panic(err)
return
}
fmt.Println(message)
fmt.Println(req)
return
}
|
- 引入第三方库(稳定性考虑)
- 第三方库使用依然使用反射机制
3. 泛型#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| type Request[T any] struct {
FuncId string `json:"funcId"`
Mid string `json:"mid"`
Sn string `json:"sn"`
Payload struct {
Params T `json:"params"`
} `json:"payload"`
}
func ReleasesApi(ctx *gin.Context) {
var (
err error
req Request[Releases]
)
err = ctx.ShouldBindJSON(&req)
if err != nil {
panic(err)
return
}
fmt.Println(req)
return
}
|
使用泛型将 Request 进行改造 使其达到 Params 这个 field 能兼容任意类型。
类似 gin 中 ShouldBind() 这个默认通用的 Bind struct 是无效的, 需要强行执行 ShouldBindJSON()。
- go版本需要从 1.18 起(支持泛型最低版本)
- go泛型目前还处于类似 beta 阶段需要考虑是否使用
- 配合第三方框架需要注意是否能进行正常的绑定数据
- 允许使用泛型方式的前提下,优先使用泛型
- 优先使用顺序为 泛型>反射>反复序列化