6、Go Web 编程 - 打通 Web 开发的最后一步,操作数据库
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_info
、post
、follower
三张数据表,且 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
开发流程我们已经完成,且成功与数据库进行了交互,从数据库中读取数据并在模板中显示了出来。后续的所有操作基本都是这个流程。从控制器
到模型取数据
再到模板显示数据
。