用 Go 操作 MongoDB:从零构建一个博客系统

不再只是"连接数据库"------本文带你用 Go + Gin + MongoDB 从零搭建一个完整 CRUD 博客应用,代码清晰、结构规范、即拿即用!

在现代后端开发中,MongoDB 凭借其灵活的文档模型和高扩展性,成为许多 Go 开发者的首选 NoSQL 数据库。

而 Go 官方提供的 go.mongodb.org/mongo-driver 驱动,让操作 MongoDB 变得既安全又高效。

本文将手把手教你:

  1. 连接 MongoDB
  2. 实现完整的 CRUD(增删改查)
  3. 构建一个带 Web 界面的博客系统(使用 Gin 框架)

🛠️ 前置准备

确保你已安装:

  • Go 1.25+
  • MongoDB (本地运行于 localhost:27017
  • 基础 Go 和 NoSQL 知识

💡 提示:可用 Docker 快速启动 MongoDB:

bash 复制代码
docker run -d -p 27017:27017 --name mongo mongo:latest

🔌 第一步:初始化项目 & 安装依赖

bash 复制代码
mkdir go-blog && cd go-blog
go mod init blog
go get github.com/gin-gonic/gin
go get go.mongodb.org/mongo-driver/mongo
go get go.mongodb.org/mongo-driver/bson

📦 第二步:定义数据模型

MongoDB 存储的是 BSON(Binary JSON),Go 通过 struct + tag 映射:

go 复制代码
// models/post.go
package models

import (
	"time"
	"go.mongodb.org/mongo-driver/bson/primitive"
)

type Post struct {
	ID        primitive.ObjectID `bson:"_id,omitempty" json:"id"`
	Title     string             `bson:"title" json:"title"`
	Content   string             `bson:"content" json:"content"`
	CreatedAt time.Time          `bson:"created_at" json:"created_at"`
	UpdatedAt time.Time          `bson:"updated_at" json:"updated_at"`
}

omitempty:插入时若 _id 为空,MongoDB 会自动生成

primitive.ObjectID:MongoDB 的唯一 ID 类型


🌐 第三步:建立 MongoDB 连接

go 复制代码
// main.go
package main

import (
	"context"
	"log"
	"time"
	"blog/handlers"

	"github.com/gin-gonic/gin"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

func main() {
	// 创建带超时的上下文
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	// 连接 MongoDB
	client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017"))
	if err != nil {
		log.Fatal(err)
	}
	defer client.Disconnect(ctx)

	// 测试连接
	if err = client.Ping(ctx, nil); err != nil {
		log.Fatal(err)
	}
	log.Println("✅ 成功连接 MongoDB!")

	// 获取数据库实例
	db := client.Database("blog_db")

	// 初始化 Gin 路由
	router := gin.Default()
	router.LoadHTMLGlob("templates/*")

	// 注册处理器
	h := handlers.NewHandler(db)
	router.GET("/", h.HomePage)
	router.GET("/post/:id", h.ViewPost)
	router.GET("/create", h.CreatePost)
	router.POST("/save", h.SavePost)
	router.GET("/edit/:id", h.EditPost)
	router.GET("/delete/:id", h.DeletePost)

	log.Println("🚀 服务启动于 http://localhost:8080")
	router.Run(":8080")
}

⚠️ 注意:MongoDB 不会预先创建数据库或集合,首次插入文档时自动创建。


🧠 第四步:实现核心 Handler(CRUD 逻辑)

go 复制代码
// handlers/main.go
package handlers

import (
	"context"
	"log"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"blog/models"
)

type Handler struct {
	collection *mongo.Collection
}

func NewHandler(db *mongo.Database) *Handler {
	return &Handler{
		collection: db.Collection("posts"),
	}
}

// 首页:列出所有文章
func (h *Handler) HomePage(c *gin.Context) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	cursor, err := h.collection.Find(ctx, bson.M{})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	defer cursor.Close(ctx)

	var posts []models.Post
	if err = cursor.All(ctx, &posts); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.HTML(http.StatusOK, "index.html", posts)
}

// 查看单篇文章
func (h *Handler) ViewPost(c *gin.Context) {
	id, _ := primitive.ObjectIDFromHex(c.Param("id"))
	var post models.Post
	err := h.collection.FindOne(context.TODO(), bson.M{"_id": id}).Decode(&post)
	if err != nil {
		c.AbortWithStatus(http.StatusNotFound)
		return
	}
	c.HTML(http.StatusOK, "post.html", post)
}

// 保存文章(创建 or 更新)
func (h *Handler) SavePost(c *gin.Context) {
	now := time.Now()
	id := c.PostForm("id")
	title := c.PostForm("title")
	content := c.PostForm("content")

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if id == "" {
		// 创建新文章
		post := models.Post{
			Title:     title,
			Content:   content,
			CreatedAt: now,
			UpdatedAt: now,
		}
		result, err := h.collection.InsertOne(ctx, post)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		log.Printf("📝 创建文章 ID: %v", result.InsertedID)
	} else {
		// 更新现有文章
		objID, _ := primitive.ObjectIDFromHex(id)
		update := bson.M{
			"$set": bson.M{
				"title":      title,
				"content":    content,
				"updated_at": now,
			},
		}
		h.collection.UpdateOne(ctx, bson.M{"_id": objID}, update)
		log.Printf("✏️ 更新文章 ID: %s", id)
	}
	c.Redirect(http.StatusSeeOther, "/")
}

// 删除文章
func (h *Handler) DeletePost(c *gin.Context) {
	id, _ := primitive.ObjectIDFromHex(c.Param("id"))
	h.collection.DeleteOne(context.TODO(), bson.M{"_id": id})
	log.Printf("🗑️ 删除文章 ID: %s", id)
	c.Redirect(http.StatusSeeOther, "/")
}

// 渲染表单
func (h *Handler) CreatePost(c *gin.Context) { c.HTML(http.StatusOK, "create.html", nil) }
func (h *Handler) EditPost(c *gin.Context) {
	id, _ := primitive.ObjectIDFromHex(c.Param("id"))
	var post models.Post
	h.collection.FindOne(context.TODO(), bson.M{"_id": id}).Decode(&post)
	c.HTML(http.StatusOK, "edit.html", post)
}

🎨 第五步:前端模板(Tailwind CSS)

项目结构:

go 复制代码
go-blog/
├── main.go
├── models/
│   └── post.go
├── handlers/
│   └── main.go
└── templates/
    ├── index.html
    ├── post.html
    ├── create.html
    └── edit.html

每个模板均使用 Tailwind CDN,无需构建步骤。

例如 index.html 展示文章列表,并支持"创建/编辑/删除":

html 复制代码
<!-- templates/index.html 片段 -->
{{range .}}
<div class="bg-white rounded shadow p-4">
  <h2 class="text-xl font-bold">{{.Title}}</h2>
  <p>{{slice .Content 0 100}}...</p>
  <a href="/edit/{{.ID.Hex}}">编辑</a>
  <a href="/delete/{{.ID.Hex}}" onclick="return confirm('确定删除?')">删除</a>
</div>
{{end}}

💡 完整模板代码可参考 freeCodeCamp 原文


▶️ 启动并测试

bash 复制代码
go mod tidy
go run main.go

访问 http://localhost:8080,即可看到你的博客系统!

  • 点击 "Create New Post" 发布文章
  • 编辑、删除、查看详情全部支持

🔒 安全与最佳实践

问题 建议
ID 注入攻击 使用 primitive.ObjectIDFromHex() 校验 ID 格式
XSS 风险 模板中避免直接输出用户输入(本例用 whitespace-pre-line 限制)
连接泄漏 始终使用 defer client.Disconnect(ctx)
超时控制 所有 DB 操作都应带 context.WithTimeout

🔚 结语

Go 与 MongoDB 的组合,兼具高性能开发效率

通过本文的完整示例,你不仅学会了如何操作数据库,更掌握了一个生产级 Web 应用的基本架构

📌 记住

MongoDB 的"无模式"不是"无设计",合理的 struct 定义和索引策略,才是高性能的关键。


相关推荐
恒者走天下1 小时前
cpp / c++部分岗位招聘要求分享
后端
ZaneAI1 小时前
🚀 Vercel AI SDK 使用指南: 循环控制 (Loop Control)
后端·agent
jDPvYjdteF2 小时前
用Matlab开启高光谱数据处理之旅
后端
jay神2 小时前
基于SpringBoot的英语自主学习系统
java·spring boot·后端·学习·毕业设计
qinaoaini2 小时前
Spring 简介
java·后端·spring
Cache技术分享2 小时前
322. Java Stream API - 使用 Finisher 对 Collector 结果进行后处理
前端·后端
何中应2 小时前
记录一次pom.xml依赖顺序产生的错误
后端·maven
dfyx9992 小时前
SpringBoot教程(三十二) SpringBoot集成Skywalking链路跟踪
spring boot·后端·skywalking
风的归宿552 小时前
一次openresty的网关性能优化之旅
后端