mongo.connect()必须用context.withtimeout设置超时,否则dns慢或网络不通时会阻塞数十秒;连接后须调client.ping验证,并确保结构体字段含json、bson、form三标签,id字段form="-"忽略,查询前校验objectid合法性。

mongo.Connect() 必须带 context.WithTimeout
不加超时的 mongo.Connect() 会卡死或 panic,不是 URI 写错,而是 DNS 解析慢或网络不通时阻塞几十秒。Go driver 强制要求传 context.Context,但 context.Background() 没有超时机制,不可用于连接阶段。
正确做法是:
- 用
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)包一层 -
defer cancel()防止 goroutine 泄漏 - 连接后立刻调
client.Ping(ctx, nil)验证连通性(注意:这里要传新 ctx,不能复用 Connect 时已可能 cancel 的那个) - 别把
*mongo.Client声明为全局变量,它线程安全,但应通过参数传递或依赖注入,否则单元测试难 mock
结构体字段标签必须三者齐备
Gin 对 application/json 和 application/x-www-form-urlencoded 使用不同绑定逻辑,缺任一标签都会丢数据。
典型写法示例:
type User struct {
ID primitive.ObjectID `json:"id" bson:"_id" form:"-"`
Username string `json:"username" bson:"username" form:"username"`
CreatedAt time.Time `json:"created_at" bson:"created_at" form:"-"`
}
-
form:"-"表示忽略表单提交,防止伪造_id或时间字段 - 所有字段首字母必须大写(导出),否则
c.ShouldBind()反射失败,值始终为空 - 嵌套结构体每个字段也得加
bson:标签,否则子字段不会写入数据库
InsertOne 后 ID 不在结构体里,得从 result.InsertedID 提取
插入成功 ≠ 结构体自动更新 ID。MongoDB 不会把生成的 ObjectID 回填到你传入的 struct 字段中。
正确提取方式:
result, err := collection.InsertOne(ctx, user)
if err != nil {
// handle error
}
id := result.InsertedID.(primitive.ObjectID).Hex() // ← 这才是可用的字符串 ID
- 别写
user.ID.Hex()—— 此时user.ID是零值 - 若手动用
primitive.NewObjectID()设 ID 再插入,需确保该 ID 未被占用,否则因唯一键冲突报错 - 查询前务必校验
primitive.IsValidObjectID(idStr),避免ObjectIDFromHexpanic
Find/Update 时 filter 和 update 参数顺序不能反
collection.UpdateOne() 第一个参数是查询条件(filter),第二个才是更新内容(update)。写反了不报编译错误,但行为完全错乱:MongoDB 会把你的更新语句当查询条件去匹配。
常见错误写法:
// ❌ 错:update 写前面,filter 写后面
collection.UpdateOne(ctx, bson.M{"$set": bson.M{"name": "new"}}, bson.M{"_id": id})
// ✅ 对:filter 在前,update 在后
collection.UpdateOne(ctx, bson.M{"_id": id}, bson.M{"$set": bson.M{"name": "new"}})
- 游标(
*mongo.Cursor)必须显式defer cursor.Close(ctx),否则 goroutine 和内存泄漏 - 遍历游标必须先
cursor.Next(),再cursor.Decode(&item);跳过Next()直接Decode()会 panic - 高并发或大数据量场景慎用
cursor.All(),建议用for cursor.Next() { ... }流式处理防 OOM











