Gin Context中结构体不能直接赋值因浅拷贝会导致请求间数据污染;推荐用json序列化、ulule/deepcopier或手动Clone()实现深拷贝,需注意字段导出、类型断言、不可拷贝类型处理等细节。

为什么 Gin Context 中的结构体不能直接赋值
因为 Gin 的 c.Request.Context() 或中间件里塞进 c.Set() 的结构体,一旦含 []string、map[string]interface{}、*User 这类字段,直接 dst = src 就只是浅拷贝。后续在 handler 里修改 dst.Config.Options,可能意外改到上一个请求缓存的原始配置。
用 json.Marshal/json.Unmarshal 在 Gin 中最常用但要绕开三个坑
这是 Gin 项目里最常写的临时方案,但必须手动处理:
- 所有字段必须导出(首字母大写),否则
json包静默丢弃 —— 比如type Request struct { id int }里的id不会出现在副本中 -
time.Time字段默认转成字符串再解析,时区和纳秒精度可能丢失;建议提前给结构体加JSON标签或实现MarshalJSON -
c.MustGet("data")返回的是interface{},强制类型断言前得先确认类型,否则 panic:src, ok := c.MustGet("data").(MyStruct)
示例:
func DeepCopyForGin(src interface{}) (interface{}, error) {
data, err := json.Marshal(src)
if err != nil {
return nil, err
}
var dst interface{}
err = json.Unmarshal(data, &dst)
return dst, err
}
用 ulule/deepcopier 在 Gin 中做编译期安全拷贝
适合把请求上下文里的 DTO、Config、Filter 等结构体复制到业务层,避免污染中间件状态。它不走反射,IDE 可跳转,性能零损耗。
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
立即学习“go语言免费学习笔记(深入)”;
- 源和目标结构体字段都必须导出,且目标字段可写(不能是只读嵌套)
- 传参必须是双指针:
deecopier.Copy(&src).To(&dst),传值或 nil 指针会 panic - 支持字段映射:
Name string `deepcopier:"field:FullName"`,适合从c.Param("id")构建请求结构体后再深拷 - 忽略字段用
-标签:Logger *log.Logger `deepcopier:"-"`,防止把中间件注入的 logger 带进业务逻辑
自己写 Clone() 方法才是 Gin 中最稳的路径
尤其当你在中间件里构造了带 sync.RWMutex、chan、func() 的上下文结构体时,任何序列化或泛型库都会失败。这时必须显式控制每个字段行为。
- 基本字段直接赋值:
Name: src.Name - 切片用
make+copy:Hobbies: append([]string(nil), src.Hobbies...) - map 要新建并遍历复制:
Labels: func(m map[string]string) map[string]string { r := make(map[string]string); for k, v := range m { r[k] = v }; return r }(src.Labels) - 嵌套结构体调其
Clone(),指针字段判空再解引用:User: func(u *User) *User { if u == nil { return nil }; return u.Clone() }(src.User) - 不可拷贝类型(如
sync.Mutex)必须初始化新实例,不能复制锁状态
真正难的不是写 Clone,而是想清楚哪些字段该深拷(比如 Params)、哪些该共享(比如 Context 或 logger 实例)——这取决于你在 Gin 中的职责分界点。

















