必须使用context.WithTimeout初始化MongoDB客户端并调用client.Ping验证连接,结构体字段需同时标注json、bson和form标签,ID字段form:"-"忽略,InsertOne后从result.InsertedID提取ObjectID并转Hex返回,查询前校验ObjectId合法性。

直接用 gin + mongo-go-driver 跑通 CRUD,不需要中间层封装、不搞 Clean Architecture、不加 Redis 缓存——能跑就行,5 分钟内完成。
初始化 MongoDB 客户端别漏掉 context.WithTimeout
很多人连不上数据库,不是 URI 写错,而是没设超时导致连接卡死或 panic。Go 的 mongo driver 强制要求传 context.Context,而 context.Background() 在连接阶段不够安全。
-
context.WithTimeout(context.Background(), 10*time.Second)是必须的,超时后会主动 cancel,避免 goroutine 泄漏 - 调用
client.Ping()验证连接,否则后续collection.Find()可能静默失败 - 不要把
*mongo.Client存成全局变量,它本身是线程安全的,但应由依赖注入或函数参数传递,避免测试时难 mock
结构体字段必须同时支持 JSON、BSON 和 form 绑定
表单提交(application/x-www-form-urlencoded)和 API JSON 请求(application/json)走的是 Gin 不同绑定逻辑,字段标签缺一不可。
-
Username string `json:"username" bson:"username" form:"username"`—— 三者都得写,顺序无关,但缺form就收不到表单值 -
ID primitive.ObjectID `json:"id" bson:"_id" form:"-"`——form:"-"表示忽略,防止前端伪造_id - 时间字段如
CreatedAt time.Time建议form:"-",由服务端生成,避免时区/格式错乱 - 所有字段必须首字母大写(导出),否则
c.ShouldBind()无法反射赋值,哪怕标签全对也为空
InsertOne 后记得从 result.InsertedID 提取 ObjectID
插入成功不代表你能立刻拿到 ID —— result.InsertedID 才是新文档的 _id,不是结构体里的字段自动更新。
-
result, err := collection.InsertOne(ctx, user)后,result.InsertedID是primitive.ObjectID类型 - 如果想返回给前端,需转成字符串:
result.InsertedID.(primitive.ObjectID).Hex() - 别直接用
user.ID.Hex(),因为结构体里 ID 是零值,没被自动赋值 - 若用
primitive.NewObjectID()手动设 ID 再插入,要确保该 ID 未被占用,否则InsertOne会失败(重复键)
Query 条件别硬拼 map,优先用 bson.M 或 bson.D
用 bson.M{"username": "alice"} 比 map[string]interface{}{"username": "alice"} 更安全,前者是 mongo driver 官方推荐类型,序列化行为确定。
-
bson.M是map[string]interface{}的别名,但语义明确,且支持嵌套(如bson.M{"tags": bson.M{"$in": []string{"go", "web"}}}) - 精确匹配 ObjectId 要先 Hex 转换:
bson.M{"_id": primitive.ObjectIDFromHex(id)},否则查不到 - 路径参数提取后,务必校验是否为合法 ObjectId:
primitive.IsValidObjectID(id),再调ObjectIDFromHex,否则 panic - 不要在查询条件里混用
nil和空字符串,MongoDB 对null和""区分严格,bson.M{"email": ""}不等于bson.M{"email": nil}
最常被跳过的其实是 primitive.IsValidObjectID 校验和 form 标签补全——这两处一漏,API 就静默失效,日志里还看不出错在哪。


















