好坑啊,调用了同事写的基础代码,bug藏得还挺深!!

起因

事情的起因是我调用了同事的一个函数,这个函数返回了一个map[string]string结构体的变量optionMap(请忽略为什么要返回map结构体,后面有机会再讲),这个函数主要是查DB取获取当前系统的space_id和pkey,返回的内容基本上如下

c 复制代码
 // 返回
 optionMap = map[string]string{
     "space_id":"xxx",
     "pkey": "xxxx",
 }

然后我修改了这个变量,添加了

css 复制代码
optionMap["is_base"] = 1

然后我就return出我当前的函数了,然后在同一个请求内,但是当我再一次请求同事的函数时,返回给我的却是

c 复制代码
 optionMap = map[string]string{
     "space_id":"xxx",
     "pkey": "xxxx",
     "is_base": 1,
 }

what! 怎么后面再次请求同事的的函数总是会多一个参数呢!!!!

经过

看了同事写的函数我才发现,原来他在内部使用了gin上下文去做了一个缓存,大概的代码意思减少重复space基础信息的查询,存入上下文中做缓存,提高代码效率,这里我写了一个示例大家可以看下

go 复制代码
// 同事的代码
func BadReturnMap(ctx *gin.Context, key string) map[string]interface{} {
	m := make(map[string]interface{})
	// 查询缓存
	value, ok := ctx.Get(key)
	if ok {
		bm, ok := value.(map[string]interface{})
		if ok {
			return bm
		}
	}
	// io查询后存入变量
	m["a"] = 1
	// 保存缓存
	fmt.Println("set cache: ")
	fmt.Println(m) // map[a:1]
	ctx.Set(key, m)
	return m
}

// 我的使用
func TestBadReturnMap() {
	fmt.Println("bad return map start")
	ctx := &gin.Context{}
	key := "cached:map_key"
	mapOpt := BadReturnMap(ctx, key)
	fmt.Printf("%p\n", mapOpt) // 0xc0003a6750 指向地址
	mapOpt["b"] = 1
	value, ok := ctx.Get(key)
	fmt.Printf("%p\n", mapOpt) // 0xc0003a6750 指向地址
	if ok {
		fmt.Println("get cache: ")
		fmt.Println(value.(map[string]interface{})) // map[a:1 def:1]
	} else {
		fmt.Println("unknown")
	}
	fmt.Println("bad return map end")
}

打印结果是

arduino 复制代码
bad return map start
set cache: 
map[a:1]
0xc0003a6750
0xc0003a6750
get cache: 
map[a:1 b:1]
bad return map end

解释

在Go语言中,map是引用类型,当将一个map赋值给另一个变量时,实际上是将它们指向同一个底层的map对象。因此,当你修改其中一个变量的map时,另一个变量也会受到影响。

当你将函数内m变量赋值给外部函数内的变量时,它们实际上指向同一个map对象。所以当你在外部函数内修改mapOpt的值时,原始的缓存也会被修改。

如图所示

如何修改

当然修改方式有很多种,我这里列举了一种就是序列化存储到缓存然后反序列化取,如果你有更好的方式可下方留言

go 复制代码
func ReturnMap(ctx *gin.Context, key string) map[string]interface{} {
	m := make(map[string]interface{})
	value, ok := ctx.Get(key)
	if ok {
		bytes := value.([]byte)
		err := json.Unmarshal(bytes, &m)
		if err != nil {
			panic(err)
		}
		return m
	}
	// io查询后存入变量
	m["a"] = 1
	jsonBytes, err := json.Marshal(m)
	if err != nil {
		panic(err)
	}
	fmt.Println("set cache: ")
	fmt.Println(m)
	ctx.Set(key, jsonBytes)

	return m
}

func TestReturnMap() {
	fmt.Println("return map start")
	ctx := &gin.Context{}
	key := "cached:map_key"
	mapOpt := ReturnMap(ctx, key)
	fmt.Printf("%p\n", mapOpt)

	mapOpt["b"] = 1
	fmt.Printf("%p\n", mapOpt)

	value, ok := ctx.Get(key)
	if ok {
		m := make(map[string]interface{})
		bytes := value.([]byte)
		err := json.Unmarshal(bytes, &m)
		if err != nil {
			panic(err)
		}
		fmt.Println("get cache: ")
		fmt.Println(m)
	} else {
		fmt.Println("unknown")
	}
	fmt.Println("return map end")
}

打印的结果为:

arduino 复制代码
return map start
set cache: 
map[a:1]
0xc0003a6870
0xc0003a6870
get cache: 
map[a:1]
return map end

知识点

相关推荐
考虑考虑6 分钟前
使用jpa中的group by返回一个数组对象
spring boot·后端·spring
GiraKoo15 分钟前
【GiraKoo】C++11的新特性
c++·后端
MO2T20 分钟前
使用 Flask 构建基于 Dify 的企业资金投向与客户分类评估系统
后端·python·语言模型·flask
Wo3Shi4七23 分钟前
双向队列
数据结构·算法·go
Wo3Shi4七27 分钟前
列表
数据结构·算法·go
光溯星河27 分钟前
【实践手记】Git重写已提交代码历史信息
后端·github
Wo3Shi4七33 分钟前
链表
数据结构·算法·go
PetterHillWater1 小时前
Trae中实现OOP原则工程重构
后端·aigc
圆滚滚肉肉1 小时前
后端MVC(控制器与动作方法的关系)
后端·c#·asp.net·mvc
SimonKing1 小时前
拯救大文件上传:一文彻底彻底搞懂秒传、断点续传以及分片上传
java·后端·架构