6、Go Web 编程 - 打通 Web 开发的最后一步,操作数据库

作者: 温新

图书: 【Go Web 开发简易 Blog】

阅读: 426

时间: 2024-09-08 00:15:50

hi,我是温新,一名 PHPer

我们使用 MySQL 作为持久化存储数据的地方,因此开始本章之前,请确保你的电脑已经安装了 MySQL。

我使用的是 MySQL8.0.30

Gorm

可以使用 SQL 来操作数据库,也可以使用 ORM 来操作数据库。MySQL 作为一个关系型数据库,而 Gorm 就是对关系数据的一个映射,可以很好的把数据用关系的形式映射到数据库中。

熟悉 Laravel 的开发者,对 ORM 肯定很熟悉,操作起来爽的不得了,有了它即使不会数据库也能进行相关操作。

本次开发就是使用 Gorm 关系映射来操作数据库。按照以往,会先把建表语句写出来,然后进行后续的操作。现在我们将不再给出 SQL 建表语句,请按照顺序一步一步操作,就一定能够成功。为了便于核对数据表,文章的最后我还是会把建表语句写出来。

关于步骤问题,纯属为了方便阅读。

第一步:准备工作

1)创建数据库

$ mysql -u root -p

mysql> create DATABASE goweb-blog;

2)引入第三包

$ pwd
/home/www-go/blog/4

$ go get -u github.com/jinzhu/gorm
$ go get -u github.com/spf13/viper
$ go get -u github.com/go-sql-driver/mysql
  • gorm 包:用于简化与关系型数据库交互的 Go 语言对象关系映射(Object-Relational Mapping, ORM)框架。
  • go-sql-driver/mysql 包: MySQL 数据库交互的驱动程序。
  • github.com/spf13/viper 包:用于配置文件管理的库。用它解析JSON、YAML和TOML格式的配置文件,并提供了一些方便的方法来访问配置数据。

3)创建 MySQL 配置信息文件

4/config/config.yml

mysql:
  charset: utf8
  db: goweb-blog
  host: localhost
  user: root
  password: 123456

4)连接数据库程序

4/config/baseConfig.go

package config

import (
	"fmt"
	"github.com/spf13/viper"
)

// init 函数在包加载时自动执行,用于初始化 viper 配置管理器
func init() {
	getConfig()
}

// getConfig 函数负责从配置文件中读取配置信息,并将它们存储到 viper 中
func getConfig() {
	// 添加配置文件路径
	viper.AddConfigPath("/home/www-go/blog/4/config/")
    // 读取配置文件
    err := viper.ReadInConfig()
	if err != nil {
		panic(fmt.Errorf("配置文件出错: %s", err))
	}
}

// GetMysqlConnect 函数用于从 viper 中获取 MySQL 连接字符串
func GetMysqlConnect() string {
	// 从 viper 中获取 MySQL 用户名、密码、主机地址、数据库名称和字符集设置
	user := viper.GetString("mysql.user")
	password := viper.GetString("mysql.password")
	host := viper.GetString("mysql.host")
	db := viper.GetString("mysql.db")
	charset := viper.GetString("mysql.charset")

	// 根据获取的信息构建 MySQL 连接字符串
	return fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?charset=%s&parseTime=true", user, password, host, db, charset)
}

我们在这里是尽量贴近真实的开发,因此没有直接把 MySQL 的连接直接写在程序中,而是单独分离了出来,因此,你看到了它的配置信息是存放在一个单独的文件中。然后使用 viper 进行内容解析。

第二步:使用关系映射创建数据表

1)创建 Userinfo

models/user.go

package models

// UserInfo 结构体定义了用户信息
type UserInfo struct {
	// ID 是主键
	ID int `gorm:"primary_key"`
	// Username 是用户名,类型为varchar(80)
	Username string `gorm:"type:varchar(80)"`
	// Email 是电子邮件地址,类型为varchar(80)
	Email string `gorm:"type:varchar(80)"`
	// Password 是密码,类型为varchar(150)
	Password string `gorm:"type:varchar(150)"`
    
	// Posts 字段是关联的Post结构体切片,表示用户创建的所有文章
	Posts []Post
	// Followers 字段是一个UserInfo指针切片,通过many2many关系与另一个UserInfo表建立联系
	Followers []*UserInfo `gorm:"many2many:follower;association_jointable_foreignkey:follower_id"`
}

UserInfo 结构体会被映射为 user_info 数据表,Followers 会被映射为 follower 数据表,它与 user_info 是一个多对多关系。一个用户可以有多篇文章,也就是 post

修改之后,如果 IDE 中提示 views/index.go 报错,暂时不用管。

2)创建 Post

models/post.go

package models

// Post 结构体定义了文章信息
type Post struct {
	// ID 是主键
	ID int `gorm:"primary_key"`
	// UserInfoID 表示文章所属用户的信息ID
	UserInfoID int
	// UserInfo 字段是关联的UserInfo结构体,表示文章所属用户的信息
	UserInfo UserInfo
	// Title 是文章标题,类型为varchar(180)
	Title string `gorm:"type:varchar(180)"`
	// Content 是文章内容,类型为text
	Content string `gorm:"type:text"`
}

3)创建基础模型

models/base.go

package models

import (
	"4/config"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
	"log"
)

// db 变量用于存储数据库连接实例
var db *gorm.DB

// SetDB 函数将传入的数据库连接实例设置为全局变量db
func SetDB(database *gorm.DB) {
	db = database
}

// ConnectDB 函数负责建立与MySQL数据库的连接,并返回连接实例
func ConnectDB() *gorm.DB {
	// 获取配置文件中的MySQL连接信息
	connectMysql := config.GetMysqlConnect()
	log.Println("连接数据库 ...")
    
	// 使用 GORM 库打开数据库连接
	db, err := gorm.Open("mysql", connectMysql)
	if err != nil {
		// 如果连接失败,则输出错误信息并终止程序运行
		panic(fmt.Sprintf("数据库连接失败..., %s", err))
	}
	// 设置表名不使用复数形式
	db.SingularTable(true)
	return db
}

4)自定义一个主程序用于创建数据表

$ pwd
/home/www-go/blog/4

$ mkdir -p cmd/db_init

cmd/db_init/main.go

package main

import (
	"4/models"
	"log"
)

// main 函数是程序的入口点
func main() {
	log.Println("数据库初始化...")
	// 建立数据库连接
	db := models.ConnectDB()
	defer db.Close()

	// 设置全局数据库连接实例
	models.SetDB(db)

	// 如果表已经存在,则删除它们
	db.DropTableIfExists(models.UserInfo{}, models.Post{})
	// 创建表
	db.CreateTable(models.UserInfo{}, models.Post{})

	// 定义用户信息数据
	userinfos := []models.UserInfo{
		{
			Username: "test",
			Password: models.GeneratePasswordHash("123123"),
			Email:    "test@qq.com",
			Posts: []models.Post{
				{
					Title:   "我是王美丽",
					Content: "我正在学习 Go Web 编程呀",
				},
			},
		},
		{
			Username: "lili",
			Password: models.GeneratePasswordHash("123123"),
			Email:    "lili@qq.com",
			Posts: []models.Post{
				{
					Title:   "我是丽丽",
					Content: "我正在吹风呀",
				},
			},
		},
	}

	// 循环遍历并插入用户信息到数据库
	for _, u := range userinfos {
		db.Debug().Create(&u)
	}
}

执行程序

$ go run cmd/db_init/main.go

程序运行后,请查看 goweb-blog 数据库,会生成 user_infopostfollower 三张数据表,且 user_info 和 post 数据表都有数据。

下面是建表语句

CREATE TABLE `user_info` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(80) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `email` varchar(80) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `password` varchar(150) COLLATE utf8mb4_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

CREATE TABLE `post` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_info_id` int DEFAULT NULL,
  `title` varchar(180) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `content` text COLLATE utf8mb4_general_ci,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

CREATE TABLE `follower` (
  `user_info_id` int NOT NULL,
  `follower_id` int NOT NULL,
  PRIMARY KEY (`user_info_id`,`follower_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

修改模板

上面改动后,数据已经发生了一点小变化,现在我们来修改模板。

1)修改首页模板

views/index.go

package views

import "4/models"

// IndexView 结构体表示首页视图,包含基础视图、用户信息和帖子列表
type IndexView struct {
	// BaseView 基础视图结构体,包含标题等基本信息
	BaseView
	// UserInfo 用户信息结构体,包含用户名、密码、电子邮件等信息
	models.UserInfo
	// Posts 帖子列表结构体,包含帖子的标题和内容等信息
	Posts []models.Post
}

// IndexViewData 结构体表示首页视图数据,用于获取首页视图
type IndexViewData struct {
}

// GetIndex 方法返回一个 IndexView 类型的对象,包含首页视图的信息
func (IndexViewData) GetIndex() IndexView {
	// 获取用户名为 "test" 的用户信息
	userinfo, _ := models.GetUserinfoByUsername("test")
	// 获取该用户的所有帖子
	posts, _ := models.GetPostByUserId(userinfo.ID)

	// 创建并返回一个新的 IndexView 对象,包含基础视图、用户信息和帖子列表
	return IndexView{BaseView{Title: "Go Web 开发 Blog"}, *userinfo, *posts}
}

2)主程序初始化数据库连接

main.go

package main

import (
	"4/controllers"
	"4/models"
	"net/http"
)

func main() {
	// 连接数据库
	db := models.ConnectDB()
	// 延迟关闭数据库连接
	defer db.Close()
	// 设置数据库
	models.SetDB(db)

	// 启动控制器
	controllers.Startup()
	// 启动 HTTP 服务器,监听本地的 8888 端口
	http.ListenAndServe(":8888", nil)
}

3)修改模板视图

templates/content/index.html

{{define "content"}}
    <div>
        <h1>姓名: {{ .UserInfo.Username }}</h1>
    </div>
    <div>
        <h1>文章列表</h1>
        <ul>
            {{ range .Posts }}
            <li>{{ .Title }} -- {{ .Content }}</li>
            {{ end }}
        </ul>
    </div>
{{end}}

4)启动程序

$ go run main.go 
2023/12/14 23:34:37 连接数据库 ...

浏览器中通过 http://localhost:8888/ 访问,看到的页面显示如下内容,就已经改造成功。

Go Blog:首页 登录
姓名: test
文章列表
- 我是王美丽 -- 我正在学习 Go Web 编程呀

总结

到这里,MVC 开发流程我们已经完成,且成功与数据库进行了交互,从数据库中读取数据并在模板中显示了出来。后续的所有操作基本都是这个流程。从控制器模型取数据再到模板显示数据

请登录后再评论