# gin-blog
**Repository Path**: wangfanbest/gin-blog
## Basic Information
- **Project Name**: gin-blog
- **Description**: go框架gin简单使用
- **Primary Language**: Go
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2022-03-24
- **Last Updated**: 2022-06-15
## Categories & Tags
**Categories**: Uncategorized
**Tags**: Go语言, Gin, HTML
## README
# 安装gin
```
go get -u github.com/gin-gonic/gin
```
## 实现简单测试代码
```
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run(":8888")
}
```
官方给的简单案例
https://github.com/gin-gonic/examples/blob/master/basic/main.go
通过命令执行
```
go run main.go
```
访问浏览器
http://localhost:8888/ping
```
{message: "pong"}
```
## 实现简单表单提交页面跳转
创建页面存储文件夹view
创建表单页面form_test.html
```
form_test
```
创建表单提交后跳转页面form_to.test
```
form_to
欢迎--->{{ .username }}
```
main.go测试代码使用路由分组来区分每次提交区别
```
package main
import "github.com/gin-gonic/gin"
func Formtest(c *gin.Context) {
c.HTML(200, "form_test.html", nil)
}
func Formto(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
c.HTML(200, "form_to.html", gin.H{
"username": username,
"password": password,
})
}
func main() {
r := gin.Default()
// 加载视图模板文件
r.LoadHTMLGlob("view/*")
// http://localhost:8888/v1/ping
v1 := r.Group("/v1")
{
v1.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
}
v2 := r.Group("/v2")
{
v2.GET("/form_test", Formtest)
v2.POST("/form_to", Formto)
}
r.Run(":8888")
}
```
## 获取请求参数
### get请求参数
```
c.Query("参数名")
c.DefaultQuery("参数名", "默认值")
```
代码示例,在main.go中添加如下代码
```
func TestQueryString(c *gin.Context) {
username := c.Query("username")
age := c.DefaultQuery("age", "18")
c.String(200, "username:%s, site:%s", username, age)
}
```
路由设置添加如下
```
v3 := r.Group("/v3")
{
// http://localhost:8888/v3/testQueryString?username=我叫王帆&age=20
// http://localhost:8888/v3/testQueryString?username=我叫王帆
v3.GET("/testQueryString", TestQueryString)
}
```
运行结果分别为
```
username:我叫王帆, site:20
```
```
username:我叫王帆, site:18
```
### post请求参数(在实现简单表单提交页面跳转已使用)
```
username := c.PostForm("参数名")
password := c.DefaultPostForm("参数名", "默认值")
```
restful风格路径参数
```
// http://localhost:8888/v3/hello/我叫王帆
v3.GET("/hello/:username", TestPathParam)
```
```
func TestPathParam(c *gin.Context) {
s := c.Param("username")
c.String(200, "Username:%s", s)
}
```
运行结果
```
Username:我叫王帆
```
### 既有get有有post
```
// http://localhost:8888/v3/query?page=1
v3.POST("/query", TestGetAndPost)
```
```
func TestGetAndPost(c *gin.Context) {
page := c.DefaultQuery("page", "0")
key := c.PostForm("key")
c.String(200, "Page:%s, Key:%s", page, key)
}
```
通过postman测试
```
Page:1, Key:我是王帆
```
## 表单处理
在view下面创建一个表单页面form_info.html
```
表单处理
```
在main.go中添加路由以及handle操作
```
func Forminfo(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
hobby := c.PostFormArray("hobby")
gender := c.PostForm("gender")
city := c.PostForm("city")
c.String(200, "Username:%s, Password:%s, hobby:%s, gender:%s, city:%s", username, password, hobby, gender, city)
}
func Goforminfo(c *gin.Context) {
c.HTML(200, "form_info.html", nil)
}
```
```
v4 := r.Group("/v4")
{
// http://localhost:8888/v4/form_info
v4.GET("/form_info", Goforminfo)
v4.POST("/form_info", Forminfo)
}
```
运行结果(随便填写提交表单就行)
```
Username:我是王帆, Password:mima, hobby:[eat play], gender:bm, city:shanghai
```
## 框架热重启
每次改完代码都得重新go run太麻烦
```
go get github.com/pilu/fresh
```
go1.17版本后使用go install 后面加上版本
```
go install github.com/pilu/fresh@latest
```
进去项目目录执行
```
fresh
```
修改之前简单测试代码进行测试证明是好用的
```
v1 := r.Group("/v1")
{
v1.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong pong",
})
})
}
```
另一个热重启框架
```
go install github.com/codegangsta/gin@latest
gin help // 查看是否安装成功
gin run main.go// 运行
```
## 数据绑定
### 绑定form表单
在view下面创建一个表单页面form_info_v5.html
```
表单处理
```
在main.go中添加路由以及handle操作
```
type User struct {
Username string `form:"username"`
Password string `form:"password"`
Hobby []string `form:"hobby"`
Gender string `form:"gender"`
City string `form:"city"`
}
```
```
func Forminfov5(c *gin.Context) {
var user User
c.ShouldBind(&user)
c.String(200, "User:%s", user)
}
func Goforminfov5(c *gin.Context) {
c.HTML(200, "form_info_v5.html", nil)
}
```
```
v5 := r.Group("/v5")
{
// http://localhost:8888/v5/form_info
v5.GET("/form_info", Goforminfov5)
v5.POST("/form_info", Forminfov5)
}
```
运行结果(随便填写提交表单就行)
```
User:{123 123 [eat play] m beijing}
```
### 绑定查询参数
在main.go中添加路由以及handle操作
```
func TestGetBind(c *gin.Context) {
type User1 struct {
Username string `form:"username"`
Password string `form:"password"`
}
var user User1
err := c.ShouldBind(&user)
if err != nil {
log.Fatal(err)
}
c.String(200, "User:%s", user)
}
```
```
// http://localhost:8888/v5/testGetBind?username=wf&password=957
v5.GET("/testGetBind", TestGetBind)
```
运行结果
```
User:{wf 957}
```
### 路径请求参数绑定
在main.go中添加路由以及handle操作
```
func TestGetBind1(c *gin.Context) {
type User2 struct {
Username string `uri:"username"`
Password string `uri:"password"`
}
var user User2
err := c.ShouldBindUri(&user)
if err != nil {
log.Fatal(err)
}
c.String(200, "User:%s", user)
}
```
```
// http://localhost:8888/v5/testGetBind/wf/957
v5.GET("/testGetBind/:username/:password", TestGetBind1)
```
运行结果
```
User:{wf 957}
```
## 访问静态文件集成BootStrap框架
下载bootstrap
https://getbootstrap.com
创建一个assets文件夹,将css和js文件添加到该文件夹
在view下面创建一个表单页面bs_test.html
```
访问静态文件集成BootStrap框架
.text-primary
.text-secondary
.text-success
.text-danger
.text-warning
.text-info
.text-light
.text-dark
.text-body
.text-muted
.text-white
.text-black-50
.text-white-50
```
在main.go中添加路由以及handle操作
```
func Gobstest(c *gin.Context) {
c.HTML(200, "bs_test.html", nil)
}
```
```
v6 := r.Group("/v6")
{
// http://localhost:8888/v6/bs_test
v6.GET("/bs_test", Gobstest)
}
```
切记一定要加载文件
```
// 加载静态资源
r.Static("/assets", "./assets")
// r.StaticFS("/croot", http.Dir("c:/"))
// r.StaticFile("/favicon.ico", "./assets/favicon.ico")
```
## 中间件
### 默认中间件
使用`Gin.Default()`实例化gin引擎,默认有两个中间件,`Logger`和`Recovery`,分别用来处理日志和处理错误
使用`gin.New()`需要重新添加
### 自定义中间件
```
func MyMiddleware1(c *gin.Context) {
fmt.Println("我的第一个中间件")
}
func MyMiddleware2(c *gin.Context) {
fmt.Println("我的第二个中间件")
}
```
定义路由对应的处理handle
```
func TestMW(c *gin.Context) {
c.String(200, "hello,%s", "wf")
}
```
路由代码如下,使用use方法调用
```
v7 := r.Group("/v7")
{
v7.Use(MyMiddleware1, MyMiddleware2)
// http://localhost:8888/v7/testmw
v7.GET("testmw", TestMW)
}
```
## BasicAuth中间件
模拟数据
```
// 模拟一些私人数据
var secrets = gin.H{
"foo": gin.H{"email": "foo@bar.com", "phone": "123433"},
"austin": gin.H{"email": "austin@example.com", "phone": "666"},
"lena": gin.H{"email": "lena@guapa.com", "phone": "523443"},
}
```
修改main.go增加路由
```
// 路由组使用 gin.BasicAuth() 中间件
// gin.Accounts 是 map[string]string 的一种快捷方式
v8 := r.Group("/v8", gin.BasicAuth(gin.Accounts{
"foo": "bar",
"austin": "1234",
"lena": "hello2",
"manu": "4321",
}))
// http://localhost:8888/v8/secrets
v8.GET("/secrets", func(c *gin.Context) {
// 获取用户,它是由 BasicAuth 中间件设置的
user := c.MustGet(gin.AuthUserKey).(string)
if secret, ok := secrets[user]; ok {
c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
} else {
c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
}
})
```
输入foo bar运行结果
```
{"secret":{"email":"foo@bar.com","phone":"123433"},"user":"foo"}
```
输入manu 4321运行结果
```
{"secret":"NO SECRET :(","user":"manu"}
```
## cookie的使用
cookie是服务器向客户端写的一些数据
基于安全的考虑,需要给cookie加上Secure和HttpOnly属性,HttpOnly比较好理解,设置HttpOnly=true的cookie不能被js获取到,无法用document.cookie打出cookie的内容。
Secure属性是说如果一个cookie被设置了Secure=true,那么这个cookie只能用https协议发送给服务器,用http协议是不发送的。
```
func HandlerCookie(c *gin.Context) {
// 获得cookie
s, err := c.Cookie("username")
if err != nil {
s = "wf"
// 设置cookie
c.SetCookie("username", s, 60*60, "/", "localhost", false, true)
}
c.String(200, s)
}
```
增加路由分组处理
```
v9 := r.Group("/v9")
{
// http://localhost:8888/v9/cookie_test
v9.GET("/cookie_test", HandlerCookie)
}
```
## session的使用
gin本身没有对session的支持,可以使用第三方中间件
安装
```
go get github.com/gin-contrib/sessions
```
import导入
```
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
```
main.go代码如下
```
v10 := r.Group("/v10")
{
store := cookie.NewStore([]byte("secret"))
v10.Use(sessions.Sessions("mysession", store))
// http://localhost:8888/v10/hello
v10.GET("/hello", func(c *gin.Context) {
session := sessions.Default(c)
if session.Get("hello") != "world" {
session.Set("hello", "world")
session.Save()
}
c.JSON(200, gin.H{"hello": session.Get("hello")})
})
}
```
## 上传文件
在view创建上传图片表单
```
upload_test
```
创建uploads文件夹用来保存上传的图
在main.go中添加操作以及路由
```
// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
r.MaxMultipartMemory = 8 << 20 // 8 MiB
v11 := r.Group("/v11")
{
// http://localhost:8888/v11/upload_test
v11.GET("/upload_test", GoUpload)
v11.POST("/upload_test", Upload)
}
```
```
func Upload(c *gin.Context) {
// 单文件
file, _ := c.FormFile("file")
log.Println(file.Filename)
// 上传文件到项目根目录下面uploads,使用原文件名
c.SaveUploadedFile(file, "uploads/"+file.Filename)
c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
}
func GoUpload(c *gin.Context) {
c.HTML(200, "upload_test.html", nil)
}
```
## 集成gorm
安装
```
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
```
在mysql中创建数据库
```
CREATE DATABASE `gin_blog` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
```
创建database文件夹创建tag.go存储结构体与数据库表关联
```
package database
import "gorm.io/gorm"
// 标签结构体
type Tag struct {
gorm.Model
Name string
}
```
创建model文件夹创建tag.go存储对数据库的操作
```
package model
import (
"gin-blog/database"
"log"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type Manager interface {
AddTag(tag *database.Tag)
}
type manager struct {
db *gorm.DB
}
var Mgr Manager
func init() {
dsn := "root:root@tcp(127.0.0.1:3306)/gin_blog?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to init db:", err)
}
Mgr = &manager{db: db}
db.AutoMigrate(&database.Tag{})
}
func (mgr *manager) AddTag(tag *database.Tag) {
mgr.db.Create(tag)
}
```
创建controller文件夹创建tag.go存main.go中对应的路由操作
```
package controller
import (
"gin-blog/database"
"gin-blog/model"
"github.com/gin-gonic/gin"
)
func AddTagAction(c *gin.Context) {
name := c.PostForm("name")
tag := database.Tag{
Name: name,
}
model.Mgr.AddTag(&tag)
}
```
在main.go中添加路由
```
v12 := r.Group("/v12")
{
// http://localhost:8888/v12/tag/
v12.POST("/tag/", controller.AddTagAction)
}
```
使用postman测试,发现创建了表tags以及插入了一条数据
## 验证器
通过https://pkg.go.dev/搜索go-playground/validator
安装
```
go get github.com/go-playground/validator/v10
```
导入
```
import "github.com/go-playground/validator/v10"
```
在https://github.com/go-playground/validator查找example
在controller下面tag.go
创建验证器结构体
```
type TagValidateAdd struct {
Name string `form:"name" binding:"required,min=2,max=10"`
Code string `form:"code" binding:"required"`
}
```
绑定模型获取验证错误的方法
```
func (r *TagValidateAdd) GetError(err validator.ValidationErrors) string {
for _, v := range err {
fmt.Printf("v.StructField(): %v\n", v.StructField())
fmt.Printf("v.Tag(): %v\n", v.Tag())
if v.StructField() == "Name" && v.Tag() == "required" {
return "标签名称必填"
}
if v.StructField() == "Name" && v.Tag() == "min" {
return "标签名称至少两个字"
}
if v.StructField() == "Name" && v.Tag() == "max" {
return "标签名称至多十个字"
}
if v.StructField() == "Code" && v.Tag() == "required" {
return "验证码必填"
}
}
return "参数错误"
}
```
创建路由处理handle
```
func AddTagActionValidate1(c *gin.Context) {
var tagValidateAdd TagValidateAdd
if err := c.ShouldBind(&tagValidateAdd); err == nil {
// 参数接收正确,存入数据库
tag := database.Tag{
Name: tagValidateAdd.Name,
}
model.Mgr.AddTag(&tag)
c.JSON(http.StatusOK, tagValidateAdd)
} else {
// 验证错误
c.JSON(http.StatusUnprocessableEntity, gin.H{
"message": tagValidateAdd.GetError(err.(validator.ValidationErrors)), // 注意这里要将 err 进行转换
})
}
}
```
在main.go中添加路由
```
v13 := r.Group("/v13")
{
// http://localhost:8888/v13/tag/
// v13.POST("/tag/", controller.AddTagActionValidate)
v13.POST("/tag/", controller.AddTagActionValidate1)
}
```
validator.ValidationErrors循环后单项中有以下方法
```
fmt.Println(err.Namespace())
fmt.Println(err.Field())
fmt.Println(err.StructNamespace())
fmt.Println(err.StructField())
fmt.Println(err.Tag())
fmt.Println(err.ActualTag())
fmt.Println(err.Kind())
fmt.Println(err.Type())
fmt.Println(err.Value())
fmt.Println(err.Param())
```
通过postman测试(返回错误信息结果和代码中判断顺序有关)
```
测试1 name Goooooooooooo code 123
结果1 {"message":"标签名称至多十个字"}
测试2 name G code 123
结果2 {"message":"标签名称至少两个字"}
测试3 name Go
结果3 {"message":"验证码必填"}
测试4
结果4 {"message":"标签名称必填"}
测试5 name Go code 123
结果5 {"Name":"Go","Code":"123"}
```
单个变量进行验证
在main.go中添加路由
```
// http://localhost:8888/v13/tag_var
v13.GET("/tag_var", controller.ValidateVariable)
```
在controller中tag.go中添加处理方法
```
func ValidateVariable(c *gin.Context) {
validate = validator.New()
myEmail := "123@qq.com"
errs := validate.Var(myEmail, "required,email")
if errs != nil {
// output: Key: "" Error:Field validation for "" failed on the "email" tag
for _, err := range errs.(validator.ValidationErrors) {
fmt.Println(err.ActualTag())
if err.Tag() == "require" {
c.JSON(http.StatusUnprocessableEntity, gin.H{
"message": "邮箱必填",
})
}
if err.Tag() == "email" {
c.JSON(http.StatusUnprocessableEntity, gin.H{
"message": "邮箱格式错误",
})
}
}
}
c.JSON(http.StatusOK, myEmail)
}
```
## 博客项目实现路由控制
在main.go中添加路由
```
v14 := r.Group("/v14")
{
// http://localhost:8888/v14/home
v14.GET("/home", controller.GoHomePage)
v14.GET("/types", controller.GoTypesPage)
v14.GET("/tags", controller.GoTagsPage)
v14.GET("/archives", controller.GoArchivesPage)
v14.GET("/about", controller.GoAboutPage)
v14.GET("/blog/:id", controller.GoBlogPage)
}
```
创建controller实现路由跳转
```
package controller
import "github.com/gin-gonic/gin"
func GoHomePage(c *gin.Context) {
c.HTML(200, "index.html", nil)
}
func GoTypesPage(c *gin.Context) {
c.HTML(200, "types.html", nil)
}
func GoTagsPage(c *gin.Context) {
c.HTML(200, "tags.html", nil)
}
func GoArchivesPage(c *gin.Context) {
c.HTML(200, "archives.html", nil)
}
func GoAboutPage(c *gin.Context) {
c.HTML(200, "about.html", nil)
}
func GoBlogPage(c *gin.Context) {
c.HTML(200, "blog.html", nil)
}
```
## 集成markdown编辑器
https://pandao.github.io/editor.md/
在view下面创建add.html页面导入编辑器
## 博客数据结构图设计
分类和文章属于一对多关系,标签和文章属于多对多关系
### 文章结构体
```
type Blog struct {
gorm.Model
Name string
Content string
Describe string
Visit uint
TypeID uint
Tags []Tag `gorm:"many2many:blog_tags"`
}
```
### 分类结构体
```
type Type struct {
gorm.Model
Name string
Blogs []Blog
}
```
### 标签结构体
```
type Tag struct {
gorm.Model
Name string `validate:"required"`
Blogs []Blog `gorm:"many2many:blog_tags"`
}
```
### 获取分类列表
```
// 获取分类列表
func (mgr *manager) GetTypeList() []database.Type {
var types = make([]database.Type, 10)
mgr.db.Find(&types)
return types
}
```
### 获取分类下的数量
```
// 获取分类下数量
func (mgr *manager) GetBlogTypeCount(tid uint) int64 {
var count int64
mgr.db.Model(&database.Blog{}).Where("type_id", tid).Count(&count)
return count
}
```
### 获取分类下的文章
```
// 获取分类下的文章
func (mgr *manager) GetBlogType(tid int) []database.Blog {
var blogs = make([]database.Blog, 10)
mgr.db.Where("type_id", tid).Find(&blogs)
return blogs
}
```
### 获取标签列表
```
// 获取标签列表
func (mgr *manager) GetTagList() []database.Tag {
var tags = make([]database.Tag, 10)
mgr.db.Find(&tags)
return tags
}
```
### 获取标签下的数量
### 获取标签下的文章
```
// 获取标签下的文章
func (mgr *manager) GetBlogTag(tid int) []database.Blog {
var blogs []database.Blog
var tag database.Tag
mgr.db.Model(&database.Tag{}).Where("id", tid).Find(&tag)
mgr.db.Model(&tag).Association("Blogs").Find(&blogs)
return blogs
}
```
### 获取文章下的标签
```
// 获取文章下的标签
func (mgr *manager) GetTagBlog(tid int) []database.Tag {
var tags []database.Tag
var blog database.Blog
mgr.db.Model(&database.Blog{}).Where("id", tid).Find(&blog)
mgr.db.Model(&blog).Association("Tags").Find(&tags)
return tags
}
```
### 获取文章时候访问数量+1
```
func (mgr *manager) GetBlog(pid int) database.Blog {
var blog database.Blog
mgr.db.First(&blog, pid)
mgr.db.Model(&database.Blog{}).Where("id", blog.ID).Update("Visit", blog.Visit+1)
return blog
}
```
# ORM框架Gorm
## 一对多关联
定义用户结构体和地址结构体
```go
type User struct {
gorm.Model
Name string
Age int
Active bool
Addresss []Address
}
type Address struct {
gorm.Model
Name string
Code string
UserID uint
}
```
初始化数据库
```go
var db *gorm.DB
func init() {
dsn := "root:root@tcp(127.0.0.1:3306)/go_orm?charset=utf8mb4&parseTime=True&loc=Local"
d, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Info)})
if err != nil {
log.Fatal(err)
}
db = d
db.AutoMigrate(&User{}, &Address{})
}
```
创建模拟数据
```go
// 批量插入用户数据
func insertManyUser() {
var users = []User{
{Name: "1", Age: 1}, {Name: "2", Age: 2},
{Name: "3", Age: 3}, {Name: "4", Age: 4},
}
d := db.Select("Name", "Age").Create(&users)
fmt.Printf("d.RowsAffected: %v\n", d.RowsAffected)
}
// 批量插入用户地址数据
func insertManyAddress() {
var addresss = []Address{
{Name: "1-1", Code: "code1", UserID: 1},
{Name: "1-2", Code: "code2", UserID: 1},
{Name: "2-1", Code: "code1", UserID: 2},
{Name: "2-2", Code: "code2", UserID: 2},
{Name: "2-3", Code: "code3", UserID: 2},
{Name: "3-2", Code: "code1", UserID: 3},
}
d := db.Create(&addresss)
fmt.Printf("d.RowsAffected: %v\n", d.RowsAffected)
}
```
初始化模拟数据运行结果


### 查找关联
查询单个人信息以及单个人下面地址信息加条件
```go
func test() {
var user User
db.Model(&User{}).Where("id", 2).Find(&user)
var addresss []Address
// `user` 是源模型,它的主键不能为空
// 关系的字段名是 `Addresss`
db.Model(&user).Where("code IN ?", []string{"code1", "code3"}).Association("Addresss").Find(&addresss)
user.Addresss = addresss
fmt.Printf("user: %+v\n", user)
}
```

### 添加关联
为单个人添加一个地址信息或添加多个信息
```go
func test() {
// 为用户1添加地址1-3 code3
var user1 User
db.Model(&User{}).Where("id", 1).Find(&user1)
db.Model(&user1).Association("Addresss").Append(&Address{Name: "1-3", Code: "code3"})
// 为用户3添加地址3-1 code1 3-3 code3
var user3 User
db.Model(&User{}).Where("id", 3).Find(&user3)
address31 := Address{Name: "3-1", Code: "code1"}
address33 := Address{Name: "3-3", Code: "code3"}
db.Model(&user3).Association("Addresss").Append([]Address{address31, address33})
}
```
运行后数据库结果如下

### 替换关联
用一个新的关联替换当前关联(原来所有的关联变为新的关联)
```go
func test() {
// 为用户3替换地址3-1 code1 3-2 code2
var user3 User
db.Model(&User{}).Where("id", 3).Find(&user3)
address31 := Address{Name: "3-1", Code: "code1"}
address32 := Address{Name: "3-2", Code: "code2"}
db.Model(&user3).Association("Addresss").Replace([]Address{address31, address32})
}
```
运行后数据库结果如下

### 关联计数
查询用户下地址数量并进行条件筛选
```go
func test() {
var user1 User
db.Model(&User{}).Where("id", 1).Find(&user1)
// 关联计数
i := db.Model(&user1).Association("Addresss").Count()
fmt.Printf("i: %v\n", i)
// 提交件计数
i2 := db.Model(&user1).Where("code IN ?", []string{"code1", "code4"}).Association("Addresss").Count()
fmt.Printf("i2: %v\n", i2)
}
```
运行结果

### 删除关联
如果存在,则删除源模型与参数之间的关系,只会删除引用,不会从数据库中删除这些对象。
```go
func test() {
var user1 User
db.Model(&User{}).Where("id", 1).Find(&user1)
var user12, user13 Address
db.Model(&Address{}).Where("id", 7).Find(&user13)
// 注意这条不属于user1
db.Model(&Address{}).Where("id", 10).Find(&user12)
db.Model(&user1).Association("Addresss").Delete([]Address{user13, user12})
}
```
运行后数据库

### 清空关联
删除源模型与关联之间的所有引用,但不会删除这些关联
```go
func test() {
var user3 User
db.Model(&User{}).Where("id", 3).Find(&user3)
db.Model(&user3).Association("Addresss").Clear()
}
```
运行后数据库结果如下

### 预加载
查询所有用户时候加载地址信息
```go
func test() {
var users []User
db.Preload("Addresss").Find(&users)
for _, user := range users {
fmt.Printf("user.ID: %v\n", user.ID)
fmt.Printf("user.Name: %v\n", user.Name)
for _, address := range user.Addresss {
fmt.Printf("address.ID: %v\n", address.ID)
fmt.Printf("address.Code: %v\n", address.Code)
}
}
}
```
运行结果如下

### 带条件预加载
查询用户表id在1,2中的用户并且预加载地址code在code1,code2的地址信息
```go
func test() {
var users []User
db.Where("Id IN ?", []uint{1, 2}).Preload("Addresss", "code IN ?", []string{"code1", "code2"}).Find(&users)
for _, user := range users {
fmt.Printf("user: %v--%v\n", user.ID, user.Name)
for _, address := range user.Addresss {
fmt.Printf("user: %v--%v\n", address.ID, address.Code)
}
}
}
```

### 自定义预加载sql
查询用户表id在1,2中的用户并且预加载地信息按照倒序排列
```go
func test() {
var users []User
db.Where("Id IN ?", []uint{1, 2}).
Preload("Addresss", func(db *gorm.DB) *gorm.DB {
return db.Order("ID DESC")
}).
Find(&users)
for _, user := range users {
fmt.Printf("user: %v--%v\n", user.ID, user.Name)
for _, address := range user.Addresss {
fmt.Printf("user: %v--%v\n", address.ID, address.Code)
}
}
}
```

## 多对多关联
### 定义博客标签结构体
```go
// 博客文章
type Blog struct {
gorm.Model
Name string
Content string `gorm:"type:text"`
Tags []Tag `gorm:"many2many:blog_tags"`
}
// 标签结构体
type Tag struct {
gorm.Model
Name string
Blog []Blog `gorm:"many2many:blog_tags"`
}
```
### 连接数据库并初始化数据库
```go
var db *gorm.DB
func init() {
dsn := "root:root@tcp(127.0.0.1:3306)/go_orm?charset=utf8mb4&parseTime=True&loc=Local"
d, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db = d
db.AutoMigrate(&Blog{}, &Tag{})
}
```
### 添加模拟标签数据
```go
// 创建测试标签
func CreateTags() {
var tags = []Tag{
{Name: "php"},
{Name: "go"},
{Name: "html"},
{Name: "js"},
{Name: "bootstrap"},
{Name: "mysql"},
}
d := db.Create(tags)
fmt.Printf("d.RowsAffected: %v\n", d.RowsAffected)
}
```
### 创建模拟博客数据
```go
func CreateBlogTags() {
var tags []Tag
db.Model(&Tag{}).Find(&tags)
for _, tag := range tags {
fmt.Printf("tag.ID: %v--tag.Name: %v\n", tag.ID, tag.Name)
}
var blogs = []Blog{
{Name: "blog1", Content: "blog1-blog2", Tags: []Tag{tags[0], tags[1]}},
{Name: "blog2", Content: "blog2-blog2", Tags: []Tag{tags[2], tags[3], tags[4]}},
{Name: "blog3", Content: "blog3-blog3", Tags: []Tag{tags[0], tags[2], tags[5]}},
}
d := db.Create(blogs)
fmt.Printf("d.RowsAffected: %v\n", d.RowsAffected)
}
```
### 数据库表数据如下



### 预加载
#### 查询所有博客并查询出对应下的标签
```go
func testSelect1() {
var blogs []Blog
db.Preload("Tags").Find(&blogs)
for _, blog := range blogs {
fmt.Printf("blog.Name: %v\n", blog.Name)
var tagsName []string
for _, v := range blog.Tags {
tagsName = append(tagsName, v.Name)
}
fmt.Printf("tagsName: %v\n", tagsName)
}
fmt.Println("----------------------------------------------")
}
```
#### 查询所有标签并查询出对应的博客
```go
func testSelect2() {
var tags []Tag
db.Preload("Blog").Find(&tags)
for _, tag := range tags {
fmt.Printf("tag.Name: %v\n", tag.Name)
var blogsId []uint
for _, v := range tag.Blog {
blogsId = append(blogsId, v.ID)
}
fmt.Printf("blogsId: %v\n", blogsId)
}
fmt.Println("----------------------------------------------")
}
```
运行结果如下

### 嵌套预加载
查询所有博客以及博客下的标签,而且也要查到标签下面的所有博客,类似做相关性推荐
```go
func testSelect3() {
var blogs []Blog
db.Preload("Tags.Blog").Find(&blogs)
for _, blog := range blogs {
fmt.Printf("blog.Name: %v\n", blog.Name)
var tagsName []string
for _, v := range blog.Tags {
tagsName = append(tagsName, v.Name)
var blogsId []uint
for _, v1 := range v.Blog {
blogsId = append(blogsId, v1.ID)
}
fmt.Printf("blogsId: %v\n", blogsId)
}
fmt.Printf("tagsName: %v\n", tagsName)
}
fmt.Println("----------------------------------------------")
}
```

### 添加关联
为博客在原有基础上添加新的标签
```go
func addBlogTag() {
var tags []Tag
db.Model(&Tag{}).Find(&tags)
var blog Blog
db.Model(&Blog{}).Where("id", 1).Find(&blog)
// 原来已有1 2标签
db.Model(&blog).Association("Tags").Append([]Tag{tags[2], tags[3]})
}
```
运行结果后新增了3 4标签id,因为切片下标原因上面写的2,3

### 替换关联
把博客原有的标签替换为新的,相当于做博客更新
```go
func replaceBLogTag() {
var tags []Tag
db.Model(&Tag{}).Find(&tags)
var blog Blog
db.Model(&Blog{}).Where("id", 1).Find(&blog)
// 原来已有1 2 3 4标签
db.Model(&blog).Association("Tags").Replace([]Tag{tags[4], tags[1]})
}
```
运行结果后只剩下2,5标签id

### 关联计数
查询具体博客有几个标签
查询具体标签有几个博客再用
查询所有标签分别有几个博客再用
```go
func countBlogTag() {
var blog Blog
db.Model(&Blog{}).Where("id", 1).Find(&blog)
i := db.Model(&blog).Association("Tags").Count()
fmt.Printf("i: %v\n", i)
fmt.Println("-----------------------------------------------")
}
func countTagBlog() {
var tag Tag
db.Model(&Tag{}).Where("id", 1).Find(&tag)
i := db.Model(&tag).Association("Blog").Count()
fmt.Printf("i: %v\n", i)
fmt.Println("-----------------------------------------------")
}
func countAllTagBlog() {
var tags []Tag
db.Model(&Tag{}).Find(&tags)
for _, tag := range tags {
i := db.Model(&tag).Association("Blog").Count()
fmt.Printf("tag.Name: %v--count:%v\n", tag.Name, i)
}
}
```
运行结果如下

## 一对一关系
has one 与另一个模型建立一对一的关联,但它和一对一关系有些许不同。 这种关联表明一个模型的每个实例都包含或拥有另一个模型的一个实例。
### 定义动物猫狗玩具结构体
```go
type Cat struct {
gorm.Model
Name string
Toy Toy `gorm:"polymorphic:Owner"`
}
type Dog struct {
gorm.Model
Name string
Toy Toy `gorm:"polymorphic:Owner"`
}
type Toy struct {
gorm.Model
Name string
OwnerID int
OwerType string
}
```
### 连接数据库并初始化数据库
```go
var db *gorm.DB
func init() {
dsn := "root:root@tcp(127.0.0.1:3306)/go_orm?charset=utf8mb4&parseTime=True&loc=Local"
d, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db = d
db.AutoMigrate(&Cat{}, &Dog{}, &Toy{})
}
```
### 添加模拟数据
```go
func CreateTestData() {
db.Create(&Dog{Name: "dog1", Toy: Toy{Name: "dog1-toy1"}})
db.Create(&Dog{Name: "dog2", Toy: Toy{Name: "dog2-toy2"}})
db.Create(&Dog{Name: "dog3"})
db.Create(&Cat{Name: "cat1", Toy: Toy{Name: "cat1-toy1"}})
}
```
### 预加载
```go
func testSelect4() {
var dogs []Dog
db.Preload("Toy").Find(&dogs)
for _, dog := range dogs {
fmt.Printf("dog.Name: %v\n", dog.Name)
fmt.Printf("dog.Toy.Name: %v\n", dog.Toy.Name)
}
fmt.Println("----------------------------------------------------------")
var cats []Cat
db.Preload("Toy").Find(&cats)
for _, cat := range cats {
fmt.Printf("cat.Name: %v\n", cat.Name)
fmt.Printf("cat.Toy.Name: %v\n", cat.Toy.Name)
}
}
```
运行结果如下

### 查找关联
查找1,3的数据,因为1有关联3没有关联
```go
func findAssociation(id uint) {
var dog1 Dog
db.Model(&Dog{}).Where("id", id).Find(&dog1)
fmt.Printf("dog1.Name: %v\n", dog1.Name)
var toy11 Toy
db.Model(&dog1).Association("Toy").Find(&toy11)
fmt.Printf("toy11.Name: %v\n", toy11.Name)
fmt.Println("----------------------------------------------------------")
}
findAssociation(1)
findAssociation(3)
```
运行结果如下

### 添加关联
给id为3添加一个关联
```go
func addAssociation() {
var dog3 Dog
db.Model(&Dog{}).Where("id", 3).Find(&dog3)
db.Model(&dog3).Association("Toy").Append(&Toy{Name: "dog3-add-toy"})
}
```
运行结果

### 替换关联
把dog的id为1的玩具替换为新的玩具
```go
func replaceAssociation() {
var dog1 Dog
db.Model(&Dog{}).Where("id", 1).Find(&dog1)
db.Model(&dog1).Association("Toy").Replace(&Toy{Name: "dog1--new-toy1"})
}
```
运行结果

### 关联计数
查询dogi的id为1的玩具个数,因为是一对一所有个数是1或者0,0是没有添加对应的玩具
```go
func countAssociation() {
var dog1 Dog
db.Model(&Dog{}).Where("id", 1).Find(&dog1)
i := db.Model(&dog1).Association("Toy").Count()
fmt.Printf("i: %v\n", i)
}
```
运行结果

### 删除关联
删除dog的id为2的玩具对应玩具表id也是2,之前创建时候刚好对应上
```go
func deleteAssociation() {
var dog2 Dog
db.Model(&Dog{}).Where("id", 2).Find(&dog2)
var toy Toy
db.Model(&Toy{}).Where("id", 2).Find(&toy)
db.Model(&dog2).Association("Toy").Delete([]Toy{toy})
}
```
运行结果

### 清空关联
```go
func clearAssociation() {
var dog1 Dog
db.Model(&Dog{}).Where("id", 1).Find(&dog1)
db.Model(&dog1).Association("Toy").Clear()
}
```
运行结果

## 高级查询
测试数据在我的gitee里面blogs.sql
### 分组统计查询
按照年月分组查博客条数
```go
// 分组查询
func GroupSelect() {
var results []map[string]interface{}
db.Table("blogs").Select("group_concat(id)", "count(id) as num", "date_format(created_at,'%Y-%m') as ym").Group("ym").Find(&results)
fmt.Println(results)
}
```

### in
```go
func YmSelect() {
var results []map[string]interface{}
db.Table("blogs").Select("id", "name").Where("id IN ?", []uint{3, 5}).Find(&results)
fmt.Println(results)
}
```

### between and
```go
func YmSelect() {
var results []map[string]interface{}
db.Table("blogs").Select("id", "name").Where("created_at BETWEEN ? AND ?", "2022-03-01 00:00:00", "2022-03-31 00:00:00").Find(&results)
fmt.Println(results)
}
```

### 时间查询
```go
func YmSelect() {
var results []map[string]interface{}
db.Table("blogs").Select("id", "name").Where("date_format(created_at,'%Y-%m') = ?", "2022-03").Find(&results)
fmt.Println(results)
}
```

下面##二级