数据库

这里以目前流行的 pgsql为例子,这里我们以ORM库Gorm为例(不手撸SQL,所以抛弃jackc的pgx)

安装依赖

# 环境变量插件包
go get github.com/joho/godotenv

# 数据库驱动和Gorm包
go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres

代码示例

封装一个工具

utils/db-helper.go
package utils

import (
	"fmt"
	"os"

	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
)

// DB 定义一个全局变量,以便其他地方调用
var DB *gorm.DB

func InitDB() {
	var err error
	databaseUrl := os.Getenv("PGSQL_PARAMS")
	println(databaseUrl)

	// 注意:这里使用 = 而不是 :=,因为 DB 已经在外部声明了
	DB, err = gorm.Open(postgres.Open(databaseUrl), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 强制使用单数表名
		},
	})
	if err != nil {
		panic("无法连接数据库: " + err.Error())
	}
}

func CloseDB() {
	sqlDB, err := DB.DB()
	if err != nil {
		fmt.Println("获取 sql.DB 失败:", err)
		return
	}
	err = sqlDB.Close()
	if err != nil {
		fmt.Println("关闭数据库连接失败:", err)
	} else {
		fmt.Println("--- 数据库连接已安全关闭 ---")
	}
}

然后在主程序中注册它

main.go

func main() {
	// 加载环境变量
	err := godotenv.Load()
	if err != nil {
		log.Fatal("加载环境变量文件出错")
	}

	// 初始化连接池,程序结束时关闭连接池
	utils.InitDB()
	defer utils.CloseDB()

    // 注册路由并启动服务
	r := routers.InitRouter()
    r.Run()
}

service层里就可以使用了,比如crud:只要定义好模型,直接使用模型操作数据库,这都是 gorm 插件做的事情,它就是 orm库

model/article.go
package model

import "time"

type Article struct {
	// 必须大写开头!后面反引号里的才是数据库真实的列名
	Id          int       `gorm:"column:id" json:"id"`
	Title       string    `gorm:"column:title" json:"title"`
	Description string    `gorm:"column:description" json:"description"`
	TypeId      string    `gorm:"column:type_id" json:"typeId"`
	CreateTime  time.Time `gorm:"column:create_time" json:"createTime"`
	Content     string    `gorm:"column:content" json:"content"`
}

// ArticleVO 展示模型 (VO),直接在下面定义,不用谢重复字段,用匿名嵌套
type ArticleVO struct {
	Article         // 组合 Article 的所有字段
	TypeName string `json:"typeName" gorm:"column:type_name"` // 扩展字段
}
ackage service

import (
	"blog-2026-server/model"
	"blog-2026-server/utils"
)

type ArticleService struct{}

func (s *ArticleService) GetList(current, size int) (model.PageResult[model.ArticleVO], error) {
	var count int64
	var results []model.ArticleVO

	// 1. 统计总数
	if err := utils.DB.Model(&model.Article{}).Count(&count).Error; err != nil {
		return model.PageResult[model.ArticleVO]{}, err
	}

	// 2. 只有有数据时才查询详情
	if count > 0 {
		err := utils.DB.Table("article a").
			Select("a.*, t.name as type_name").
			Joins("left join type t on a.type_id = t.id").
			Order("a.create_time desc").
			Offset((current - 1) * size).
			Limit(size).
			Scan(&results).Error

		if err != nil {
			return model.PageResult[model.ArticleVO]{}, err
		}
	}

	// 3. 组装返回结果
	return model.PageResult[model.ArticleVO]{
		List:    results,
		Total:   count,
		Current: current,
		Size:    size,
		HasMore: int64(current*size) < count,
	}, nil
}

func (s *ArticleService) GetOne(id int) (model.ArticleVO, error) {
	var result model.ArticleVO
	err := utils.DB.Table("article a").
		Select("a.*, t.name as type_name").
		Joins("left join type t on a.type_id::int = t.id").
		Where("a.id = ?", id).
		Scan(&result).Error
	return result, err
}

func (s *ArticleService) Save(article *model.Article) error {
	// 自动生成摘要:取前 120 个字
	article.Description = utils.MdGetSummary(article.Content)
	// Save 成功后,GORM 会自动把数据库生成的 ID、CreatedAt 等回填到 article 指针指向的内存,我们只需要解引用,返回这个结构体的值即可
	return utils.DB.Save(article).Error
}

func (s *ArticleService) Update(article *model.Article) error {
	return utils.DB.Model(article).Updates(article).Error
}

func (s *ArticleService) Delete(id int) error {
	return utils.DB.Delete(&model.Article{}, id).Error
}