Gin框架学习笔记

GIN框架学习

前言

这是一场试炼,战胜了过去的自己才能见到更好的未来!

DAY 1

今天用了一整天时间来学习go语言的语法。大致做了以下内容。

搭建GO语言环境

用vscode写代码时,需要打开一个工程文件夹,其中只能包含一个main.go文件和一个package main。当需要引入第三方的包时,需要在当前目录下用go mod init <工程名字> 来创建一个go.mod文件来存储第三方下载的包的依赖关系,然后再main.go中写代码即可。

写一个最简单go语言程序

= 表示赋值

:= 表示定义加赋值

go语言的语法中,凡是定义的变量,引入的包都必须使用

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func sayhello(w http.ResponseWriter, r *http.Request) {//响应,请求
	b, _ := ioutil.ReadFile("./hello.html")//读文件,将结果保存到b中
	_, _ = fmt.Fprintln(w, string(b))//将结果通过w返回给浏览器
}
func main() {
	http.HandleFunc("/hello", sayhello)//如果输入的是/hello,则执行sayhello函数
	err := http.ListenAndServe(":9090", nil)//使用9090端口
	if err != nil {
		fmt.Printf("http serve failed, err :%v\n", err)
		return
	}
}

第一个gin程序
package main

import (
	"net/http"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()//设置路由r
	r.GET("/book", func(c *gin.Context) {//用GET方式发送信息
		c.JSON(http.StatusOK, gin.H{
			"message": "getsomething",//键值对信息
		})
	})
	r.Run(":9090")//使用9090端口号运行,默认是8080
}

注意网址中要用‘/’而不是’\'哦

扩展到四种操作:GET,PUT,POST,DELETE

GET: 表示查询

PUT: 表示修改

POST: 表示添加

DELETE: 表示删除

其实是返回操作信息的一种方式,提供四种接口,可以用postman来检测

package main

import (
	"net/http"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.GET("/book", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"get": "get something",
		})
	})
	r.PUT("/book", func(c *gin.Context) {
		c.JSON(
			http.StatusOK, gin.H{
				"put": "change something",
			})
	})
	r.POST("/book", func(c *gin.Context) {
		c.JSON(
			http.StatusOK, gin.H{
				"post": "add something",
			})
	})
	r.DELETE("/book", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"delete": "delete some books to free some space",
		})
	})
	r.Run(":8080")
}

模板的解析和渲染

模板:需要调用html/template,模板文件用.tmpl结尾,本质上还是html文件,将需要填充的字符串用{{ . }}代替

解析:

t, err := template.ParseFiles("./hello.tmpl")

t用来存储解析后的文件,err用于保存错误信息,括号中为待解析的模板文件

渲染:

其实就是填充模板文件中的{{ . }}

t.Execute(w, "GOLANG")

将字符串GOLANG填充模板,并放入w中返回

也可以

name:= "LTH"

t.Execute(w, name)

用原生的http(第一个go程序)来渲染:

package main

import (
	"fmt"
	"html/template"
	"net/http"
)

func sayhello(w http.ResponseWriter, r *http.Request) {
	t, err := template.ParseFiles("./hello.tmpl")
	if err != nil {
		fmt.Println("error")
	}
	t.Execute(w, "GOLANG")
}
func main() {
	http.HandleFunc("/", sayhello)
	http.ListenAndServe(":9000", nil)
}

DAY2

模板进阶用法
传入结构体

定义结构体

type User struct{

​ Name string

​ gender string

​ Age int

}

定义了一个名为User的结构体,在GO语言的结构体中,小写字母为私有项(不许外界访问),大写字母为公有项(允许外界访问)。

定义一个对象:

u1:=User{

​ Name “LTH”

​ gender “男”

​ Age 20

}

用u1.Name来访问Name变量

响应方法与之前相同,只是这里可以向模板传入结构体来填充

package main

import (
	"fmt"
	"html/template"
	"net/http"
)
type User struct {
	Name   string
	Gender string
	Age    int
}
func sayhello(w http.ResponseWriter, r *http.Request) {
	//定义模板
	//解析模板
	t, err := template.ParseFiles("./hello.tmpl")
	if err != nil {
		fmt.Println("HTTP template error:%v", err)
	}
	//渲染模板
	u1 := User{
		Name:   "LTH",
		Gender: "男",
		Age:    20,
	}
	t.Execute(w, u1)
}
func main() {
	http.HandleFunc("/", sayhello)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println("HTTP server error:%v", err)
		return
	}
}

而在模板hello.tmpl中:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>hello</title>
</head>
<body>
    <p>姓名:{{ .Name }}</p>
    <p>性别:{{ .Gender }}</p>
    <p>年龄:{{ .Age }}</p>
</body>
</html>

用{{ .Name }}来用结构体中的Name来替换(如果首字母小写,会因为是私有变量而无法访问,则会显示空白)

传入哈希表

定义哈希表:

m1:=map[string]interface{}{

​ “name”:“LTH”

​ “age”:20

​ “gender”:“男”

}

和结构体类似,但是因为是按键值来访问,所以首字母大小写都可以正确访问

interface{} 表示接口,可以容纳任意类型的变量

package main

import (
	"fmt"
	"html/template"
	"net/http"
)

type User struct {
	Name   string
	Gender string
	Age    int
}

func sayhello(w http.ResponseWriter, r *http.Request) {
	//定义模板
	//解析模板
	t, err := template.ParseFiles("./hello.tmpl")
	if err != nil {
		fmt.Println("HTTP template error:%v", err)
	}
	//渲染模板
	m1 := map[string]interface{}{
		"name":   "LTH",
		"age":    20,
		"gender": "男",
	}
	t.Execute(w, m1)
}
func main() {
	http.HandleFunc("/", sayhello)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println("HTTP server error:%v", err)
		return
	}

}
传入复合类型

由于interface{}可以容纳任意类型的变量,包括各种结构体,哈希表,字符串,数字等,我们可以把这些变量封装成一个map或者struct,然后一起传给模板。写模板的时候用 .<键值>.<变量>来访问即可。

package main

import (
	"fmt"
	"html/template"
	"net/http"
)

type User struct {
	Name   string
	Gender string
	Age    int
}

func sayhello(w http.ResponseWriter, r *http.Request) {
	//定义模板
	//解析模板
	t, err := template.ParseFiles("./hello.tmpl")
	if err != nil {
		fmt.Println("HTTP template error:%v", err)
	}
	//渲染模板
	u1 := User{
		Name:   "LTH",
		Gender: "男",
		Age:    20,
	}
	m1 := map[string]interface{}{
		"name":   "LTH",
		"age":    20,
		"gender": "男",
	}
	t.Execute(w, map[string]interface{}{
		"u1": u1,
		"u2": m1,
	})
}
func main() {
	http.HandleFunc("/", sayhello)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println("HTTP server error:%v", err)
		return
	}
}

在模板中:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>hello</title>
</head>
<body>
    <p>u1</p>
    <p>姓名:{{ .u1.Name }}</p>
    <p>性别:{{ .u1.Gender }}</p>
    <p>年龄:{{ .u1.Age }}</p>
    <p>u2</p>
    <p>姓名: {{ .u2.name }}</p>
    <p>性别: {{ .u2.gender }}</p>
    <p>年龄:{{ .u2.age }}</p>
</body>
</html>

{{/* */}} 注释,可以换行,其他的语句不能换行

{{ $a:= 100 }} 直接定义变量

{{ $b:=.u1.name }} 用传入的参数定义变量

{{- }} 去除左边空格

{{ -}} 去除右边空格

{{ if KaTeX parse error: Expected 'EOF', got '}' at position 3: v }̲} 判断语句,如果v有值,则执行下条语句,否则执行else下面的语句,以{{end}}结尾

{{ $v }}

{{else}}

None

{{ if lt a b }} 如果a<b

类似得还有:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GXce0nQh-1650528153467)(C:\Users\黎明终点x\AppData\Roaming\Typora\typora-user-images\image-20210927145354988.png)]

eq可以有多个参数

{{}}实际上是用于区分字符串和变量的标志,用$来取值

定义一个字符串数组:

hobby := [] string{

​ “c”,

​ “c++”,

​ “java”,

​ “python”,

​ “go”,

}

range: 返回字符串数组的每个编号和对应的字符串,进行循环,以{{end}}结束

{{ range $idx, $hobby := .hobby }}
        <p>{{$idx}} - {{$hobby}}</p>
{{else}}
TO
{{end}}

用range遍历map的时候,返回的分别是key和value

如果hobby里面为空,则执行T0

{{ with .u1 }}

//为其中的变量加上前缀.u1,相当于vat . .u1

{{end}}

{{ index .hobby 2 }} 访问hobby[2]

学习链接:https://www.liwenzhou.com/posts/Go/go_template/

模板嵌套函数

自定义一个函数:

k := func(name string) (string,error){
	return name+"真棒",erroe
}

k代表这个函数,name是参数,(string,error)是返回值,返回值的第二个参数要么不写,要写就必须是error

创建模板对象:

t := template.New("hello.tmpl")//文件名称

建立映射关系,告诉模板哪些是自定义函数:

t.Funcs(template.FuncMap{
		"kua": k,
	})

解析模板:

_, err := t.ParseFiles("./hello.tmpl")//文件路径

渲染模板:

t.Execute(w, "LTH")

总代码:

package main

import (
	"fmt"
	"html/template"
	"net/http"
)

type User struct {
	Name   string
	Gender string
	Age    int
}

func sayhello(w http.ResponseWriter, r *http.Request) {
	k := func(name string) string {
		if name == "LTH" {
			return "真棒!"
		} else {
			return "搞他!"
		}
	}
	t := template.New("hello.tmpl")
	t.Funcs(template.FuncMap{
		"kua": k,
	})
	t.ParseFiles("./hello.tmpl")
	t.Execute(w, "dange")
}
func main() {
	http.HandleFunc("/", sayhello)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println("HTTP server error:%v", err)
		return
	}

}

模板中调用函数:

{{ kua . }}

模板中调用其他模板:

{{template <文件名>}} 例如:{{template “t.tmpl”}}

申明模板:

1、在本文件申明

{{define ”ol.txt“}}

{{/* 一些http内容*/}}

{{end}}

注意不要和文件名重名

2、在其他文件申明

创建文件t.tmpl

文件中:

{{define ”t.tmpl“}}

{{/* 一些http内容*/}}

{{end}}

注意文件名和define后面的必须相同

源文件:

package main

import (
	"fmt"
	"html/template"
	"net/http"
)

type User struct {
	Name   string
	Gender string
	Age    int
}

func sayhello(w http.ResponseWriter, r *http.Request) {
	k := func(name string) string {
		if name == "LTH" {
			return "真棒!"
		} else {
			return "搞他!"
		}
	}
	t := template.New("hello.tmpl")
	t.Funcs(template.FuncMap{
		"kua": k,
	})
	t.ParseFiles("./hello.tmpl")
	t.Execute(w, "dange")
}
func main() {
	http.HandleFunc("/", sayhello)
	http.HandleFunc("/thing", func(rw http.ResponseWriter, r *http.Request) {
		t, err := template.ParseFiles("./t.tmpl", "./ul.tmpl")//被调用的模板要在调用它的模板之前解析
		t.Execute(rw, "LTH")
		if err != nil {
			fmt.Println("HTTP server error:%v", err)
			return
		}
	})
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println("HTTP server error:%v", err)
		return
	}
}

t.tmpl

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>hello</title>
</head>
<body>
    <p>hello {{.}}</p>
    {{template "ul.tmpl"}}
    <br>
    <hr>
    {{template "ol.tmpl"}}
</body>
</html>

{{define "ol.tmpl"}}
<ol>
    <li>go</li>
    <li>c</li>
    <li>java</li>
</ol>
{{end}}

ul.tmpl

{{define "ul.tmpl"}}
<p>要干什么</p>
<ul>
    <li>考试</li>
    <li>工作</li>
    <li>竞赛</li>
</ul>
{{end}}

DAY3

使用css的html文件

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="index" content="width=device-width, initial-scale=1.0">
    <title>index界面</title>
    <style>
    * {
        margin: 0;
    }
    .nav{
        height: 50px;
        width: 100%;
        position: fixed;
        top: 0;
        background-color: burlywood;
    }
    .main{
        margin-top: 50px;
    }
    .menu{
        width: 20%;
        height: 100%;
        position: fixed;
        left: 0;
        background-color: cornflowerblue;
    }
    .center{
        text-align: center;
    }
    </style>
</head>
<body>
<div class = "nav"></div>
<div class = "main"> 
    <div class = "menu"></div>
    <div class = "content center" >
        <h1>这是home界面</h1>
        <p>hello {{ . }}</p>
    </div>
</div>
</body>
</html>
模板的继承

所谓继承就是在根模板的基础上进行添加和修改,根模板在可以修改的地方加上block来代替,在继承时define对应的内容来代替block即可实现一个全新的模板。

根模板base.tmpl

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="index" content="width=device-width, initial-scale=1.0">
    <title>mywork界面</title>
    <style>
    * {
        margin: 0;
    }
    .nav{
        height: 50px;
        width: 100%;
        position: fixed;
        top: 0;
        background-color: burlywood;
    }
    .main{
        margin-top: 50px;
    }
    .menu{
        width: 20%;
        height: 100%;
        position: fixed;
        left: 0;
        background-color: cornflowerblue;
    }
    .center{
        text-align: center;
    }
    </style>
</head>
<body>
<div class = "nav">
    <h1 class = "content center">这是标题栏</h1>
</div>
<div class = "main"> 
    <div class = "menu">
        <h1 class = "content center">这是菜单</h1>
    </div>
    <div class = "content center" >
        {{ block "content" . }}
        {{end}}
    </div>
</div>
</body>
</html>

其中把需要修改的地方放在这里:

{{ block "content" . }}
{{end}

"content"是这一块内容的名称 . 来传入数据

子模板(继承后的模板)home2.tmpl

{{ template "base.tmpl" . }}
{{ define "content" }}
<h1>这是home2页面</h1>
<h2>hello {{ . }}</h2>
{{end}}

{{ template “base.tmpl” . }} 用来表示继承哪个模板 其中的 . 接受传入的数据

{{ define “content” }} 用来表示代替根模板中哪一块的内容,名字一定要对应

{{/* */}} 更改的内容

{{end}}

在源文件main.go中:

t, _ := template.ParseFiles("./templates/base.tmpl", "./templates/home2.tmpl")

解析模板,其中上层模板要放在前面,底层模板放在后面。也就是调用其他模板的文件一定要在被调用的文件之前解析。注意这里要表示路径。

t.ExecuteTemplate(w, "home2.tmpl", "LTH")

渲染模板,因为之前一次只解析了一个模板,所以用Execute函数即可,这里解析了多个模板,所以要指定名称。注意这里是需要使用的模板的名称。

通常把模板都放在templates文件夹中,然后用template.ParseGlob(templates/*.tmpl)来解析所有模板。

标识符改写

用得很少,一般用于避免和其他框架的冲突

main.go

package main

import (
	"html/template"
	"net/http"
)
func main() {
	http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
		t, _ := template.New("hello.tmpl").Delims("{[", "]}").ParseFiles("./hello.tmpl")
		t.Execute(w, "LTH")
	})
	http.ListenAndServe(":8080", nil)
}

还是注意New函数中为名称,ParseFiles函数中为路径

hello.tmpl

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>hello</title>
    <style> 
    .center{
        text-align: center;
    }
    </style>
</head>
<body>
    <h1 class = "center">hello {[ . ]}</h1>
</body>
</html>

加了可以居中的格式

text/template和html/template的区别

html/template会让字符串“……”里的内容原样输出,而不执行具体的内容,无论里其中是一段程序或者是一个网址,都会以字符串的形式出现。

如果想要不想让它被转译,则需要自定义一个函数来实现。

package main

import (
	"html/template"
	"net/http"
)
func main() {
	http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
		t, _ := template.New("hello.tmpl").Funcs(template.FuncMap{
			"tourl": func(url string) template.HTML {
				return template.HTML(url)
			},
		}).ParseFiles("./hello.tmpl")
		t.Execute(w, "<a href = https://www.acwing.com/activity/content/11/>基础课</a>")
	})
	http.ListenAndServe(":8080", nil)
}

创建模板,执行函数,告诉模板函数的映射关系,然后解析模板,最后执行模板

在hello.tmpl中:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>hello</title>
    <style> 
    .center{
        text-align: center;
    }
    </style>
</head>
<body>
    <h1 class = "center">语法基础课</h1>
    <p class="center">{{ . | tourl }}</p>
</body>
</html>

用|表示把参数 . 传递给函数tourl,也可以用tourl . 来调用

html知识补充:

展示一个链接:

<a href = https://www.acwing.com/activity/content/11/>基础课</a>

href后面为具体的链接,< a >和< /a > 中间为链接的名称

使用Gin框架渲染
package main

import (
	"net/http"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()//设置路由
	r.LoadHTMLFiles("./templates/hello.tmpl")//加载模板
	r.GET("/hello", func(c *gin.Context) {//当输入/hello时,执行函数显示网页
		c.HTML(http.StatusOK, "hello.tmpl", gin.H{//渲染模板
			"url": "https://www.acwing.com/",//替换.url内容
		})
	})
	r.Run(":8080")
}

如果要分析在不同文件夹中有相同文件名的文件,则需要给他们自定义一个函数名

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	//r.LoadHTMLFiles("templates/posts/base.tmpl")
	r.LoadHTMLGlob("templates/**/*")
	r.GET("/posts/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "/posts/index.tmpl", gin.H{
			"name": "李天航",
		})
	})
	r.GET("/users/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.tmpl", gin.H{
			"name": "李天航",
		})
	})
	r.Run(":8080")
}

第一个起个一个新的名字"/posts/index.tmpl",在该文件中:

{{ define "/posts/index.tmpl" }}
<ul>
    <li><h1>{{ .name }}</h1></li>
    <li><h1>C语言入门到入土</h1></li>
    <li><h1>JAVA入门到入土</h1></li>
    <li><h1>Python入门到入土</h1></li>
    <li><h1>GO入门到入土</h1></li>
</ul>
{{end}}

第二个使用了模板的继承,但是使用模板继承时会与文件名定义冲突,所以只能用原来的名称。

尽量定义不同的文件名

r.LoadHTMLGlob(“templates/**/*”) 这条语句可以用正则表达式解析大量模板

使用函数
package main

import (
	"html/template"
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.SetFuncMap(template.FuncMap{
		"safe": func(str string) template.HTML {
			return template.HTML(str)//这里这是为了让他不被转译,并不能把它变成链接
		},
	})
	r.LoadHTMLFiles("./hello.tmpl")
	r.GET("/hello", func(c *gin.Context) {
		c.HTML(http.StatusOK, "hello.tmpl", gin.H{
			"url": "<a href='https://www.acwing.com'/>学习</a>",
		})
	})
	r.Run(":8080")
}
加载静态文件css,jss

注意路径哦,静态文件一般放在statics目录下

package main

import (
	"html/template"
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	//加载静态文件(.css .js)
	r.Static("/xxx", "./statics")
	//添加自定义函数
	r.SetFuncMap(template.FuncMap{
		"safe": func(str string) template.HTML {
			return template.HTML(str)
		},
	})
	//解析模板
	r.LoadHTMLGlob("templates/**/*")
	//渲染模板
	r.GET("/posts", func(c *gin.Context) {
		c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
			"url": "<a href='https://www.acwing.com'/>学习</a>",
		})
	})
	r.GET("/users", func(c *gin.Context) {
		c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
			"url": "<a href='https://www.acwing.com'/>学习</a>",
		})
	})
	r.Run(":8080")
}

用r.Static(“/xxx”, “./statics”)在两者间建立映射关系

在http文件中加载.css文件:

.css文件在head中添加,在接受页面时会执行一次,后续刷新也不会更新

<link rel="stylesheet" href="/xxx/index.css">

在http文件中加载.js文件:

.js文件一般放在body的最后,接受页面时,每次刷新都会执行一次

<script src="/xxx/index.js"> </script>
{{define "posts/index.tmpl"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/xxx/index.css">
    <title>Document</title>
</head>
<body>
    <h1> hello {{ safe .url }} </h1>
    <script src="/xxx/index.js"> </script>
</body>
</html>
{{end}}
从网上下载前端模板并使用

在下载完前端模板后,将html文件放入templates下的文件夹中,然后修改映射关系进行适配即可。

DAY4

gin框架返回json形式的数据

之前都是直接返回一个完整的http文件,而这里则是返回json格式的数据供前端工作人员处理。

最简单的json程序:

方法一:

用map[string]interface{}类型返回数据

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)
func main() {
	r := gin.Default()
	r.GET("/json", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"name":  "LTH",
			"age":   20,
			"hobby": "code",
		})
	})
	r.Run()
}

gin.H其实等价于map[string]interface{}

方法二:

用结构体返回数据(注意定义时,首字母要大写)

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)
func main() {
	r := gin.Default()
	type msg struct {
		Name  string
		Age   int
		Hobby string
	}
	r.GET("/json", func(c *gin.Context) {
		c.JSON(http.StatusOK, msg{
			"LTH",
			20,
			"code",
		})
	})
	r.Run()
}

如何让字母为小写:定义结构题时加上tag

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	type msg struct {
		Name  string `json:"name"`
		Age   int
		Hobby string
	}
	r.GET("/json", func(c *gin.Context) {
		c.JSON(http.StatusOK, msg{
			"LTH",
			20,
			"code",
		})
	})
	r.Run()
}

json:"name" 使用json包时,遇到Name字段用name代替

在网址中传入参数

例如网址:192.168.0.105:8080/s?query=zech

192.168.0.105:8080/s是ip地址和端口号和响应关键词,而?表示后面会传入参数,然后会有键值对query=zech,表示参数"query"的值是"zech"。

在代码中用

name := c.Query("query")

来接受对应的参数(不限于query)

也可以用

name := c.DefaultQuery("query", "没输入东西哦")

在没有输入参数时,返回第二个参数代表默认值

也可以用

name, ok := c.GetQuery("query")

如果有参数ok=1,没有参数ok=0

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.GET("/s", func(c *gin.Context) {
		//name := c.Query("query")
		//name := c.DefaultQuery("query", "没输入东西哦")
		name, ok := c.GetQuery("query")
		if !ok {
			name = "something"
		}
		c.JSON(http.StatusOK, gin.H{
			"name": name,
		})
	})
	r.Run(":8080")
}

这些接收的参数可以用于在数据库中搜索,实现搜索引擎的功能

在?后面也可以传入多个键值对作为参数用&连接,在访问时也同样可以用以上三种方式

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.GET("/s", func(c *gin.Context) {
		//name := c.Query("query")
		//name := c.DefaultQuery("query", "没输入东西哦")
		name, ok := c.GetQuery("query")
		if !ok {
			name = "something"
		}
		c.JSON(http.StatusOK, gin.H{
			"name": name,
		})
	})
	r.GET("/q", func(c *gin.Context) {
		name := c.DefaultQuery("name", "a man")
		age := c.DefaultQuery("age", "a num")
		hobby := c.DefaultQuery("ahobby", "a thing")
		c.JSON(http.StatusOK, gin.H{
			"name":  name,
			"age":   age,
			"hobby": hobby,
		})
	})
	r.Run(":8080")
}
html交互页面控件

学习链接:https://blog.csdn.net/biyuhuaping/article/details/78211589

表单作用:输入信息,收集信息
表达组成:提示信息、表单控件(输入框)、表单域;

基本框架
<form action="xxx" method="get/post">//action表示请求的目的地,method表示请求方法
  用户名:<input type="text" name="username">
  密码:<input type="password" name="pwd">
  <input type="submit">
</form>
<form action="/upload" enctype="multipart/form-data">

预览图:

用户名: 密码:

action:处理信息;
method:有两个值可取,get和post。get:通过地址栏提供(传输)信息,安全性差;post:通过xxx来处理信息,安全性相对较高。

文本输入框
<input type="text" maxlength="8" readonly="readonly" name="username" value="jiangjiang" placeholder="请输入用户名">

maxlength:设置输入的最大字符长度
readonly:设置输入框为只读状态
value:设置默认值
placeholder:提示用户进行操作

<input type="text" maxlength="8" disable="disable" name="username">

disable:输入框没有激活
name:给输入框设置名字,以便进行区分

密码输入框

输入的信息会变成暗文,文本输入框的所有属性对密码输入框都有效

<input type="password" maxlength="8" disable="disable" name="username">
单选框
<input type="radio" name="sex" checked="checked">男 <input type="radio" name="sex">女

一组单选按钮必须要设置同样的name,否则单选无效;
通过checked来设置默认选中项

多选框
<input type="checkbox" name="hobby" checked="checked">喝酒
<input type="checkbox" name="hobby">抽烟
<input type="checkbox" name="hobby">烫头发
多行文本框
<textarea cols="30" rows="10"></textarea>

预览图:

cols:控制输入字符的长度;
rows:控制输入的行数;

文件上传控件
<input type="file" name="f1">
按钮

提交按钮:可以实现信息提交

<input type="submit" value="提交">

普通按钮:不能提交,通常配合js使用

<input type="button" value="普通按钮">;

图片按钮:可实现信息提交功能

<input type="image" src="xxx.jpg">;

重置按钮:将信息重置到默认状态

<input type="reset">
将表单信息分组

将表单内一组的内容放到中,表单名称放到中

<form action="xxx" method="get/post">
<fieldset>
    <legend>个人信息提交</legend>
    用户名:<input type="text" name="username">
    密码:<input type="password" name="pwd">
    <input type="submit">
</fieldset>
</form>

预览图:

个人信息提交 用户名: 密码:
表单的其它控件

网址输入框:

<input type="url">

会要求输入正确的网址格式,但是空也可以提交,以后会用js进行判断;
日期控件:

<input type="date">

时间控件:

<input type="time">

邮件控件:

<input type="email">

要求输入正确的邮件格式,但是空也可以提交;
数字控件:

<input type="number" step="2">

有一个上下的小三角,可以步进,每次调整的值的大小为2;
滑块控件:

<input type="range" step=20>
下拉列表

普通下拉列表

<select multiple="multiple">
  <option>下拉列表选项1</option>
  <option>下拉列表选项2</option>
  <option selected="selected">下拉列表选项2</option>
</select>

预览图:

下拉列表选项1 下拉列表选项2 下拉列表选项2

multiple:设置多选;
selected:设置默认选中项,如果不设置,默认选择第一个选项;

分组下拉列表:

<select>
  <optgroup label="江苏">
      <option>苏州</option>
      <option>无锡</option>
      <option>常州</option>
  </optgroup>
  <optgroup label="浙江">
      <option>杭州</option>
      <option>温州</option>
      <option>绍兴</option>
  </optgroup>
</select>

预览图:

苏州 无锡 常州 杭州 温州 绍兴
概述详细信息标签
<details>
    <summary>简介</summary>
    发动机卡拉的交罚款了打飞机考虑到九分裤了打手机发开发阶段
</details>
简介 发动机卡拉的交罚款了打飞机考虑到九分裤了打手机发开发阶段
用gin框架返回表单数据

main.go文件

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.LoadHTMLFiles("./login.html")
	r.GET("/login", func(c *gin.Context) {
		c.HTML(http.StatusOK, "login.html", nil)
	})
	r.Run(":8080")
}

login.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>login</title>
</head>
<body>
    <form action="/login" type="post">
        <label for="username">username:</label>
        <input type="text" name="username" id="username">
        <br>
        <label for="password">password:</label>
        <input type="text" name="password" id="password">
        <br>
        <input type="button" value="登录">
    </form>
</body>
</html>

这里用了许多html控件来表示表单

网页向服务器有两种请求方式:

get方式:写入网址后回车

post方式:由action控制返回表单

在main.go中能处理接受get请求,但是当接受到post请求时却无法处理,所以我们需要增添响应函数

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.LoadHTMLFiles("./login.html", "./index.html")
	r.GET("/login", func(c *gin.Context) {
		c.HTML(http.StatusOK, "login.html", nil)
	})
	r.POST("/login", func(c *gin.Context) {
		//name := c.PostForm("username")
		//password := c.PostForm("password")
		//name := c.DefaultPostForm("username", "LTH")
		//password := c.DefaultPostForm("password", "123456")
		name, ok := c.GetPostForm("username")
		if !ok {
			name = "LTH"
		}
		password, ok := c.GetPostForm("password")
		if !ok {
			name = "123456"
		}
		c.HTML(http.StatusOK, "index.html", gin.H{
			"name":     name,
			"password": password,
		})
	})
	r.Run(":8080")
}

于是我们写一个类似的函数,只是这里要把GET方式改成POST方式,在响应函数中,可以用以下三种方式接受参数:

方法一:

name := c.PostForm("username")
password := c.PostForm("password")

直接接收参数

方法二:

name := c.DefaultPostForm("username", "LTH")
password := c.DefaultPostForm("password", "123456")

如果能找到关键字就返回值(包括空),如果找不到关键字就返回默认值。

方法三:

name, ok := c.GetPostForm("username")
		if !ok {
			name = "LTH"
		}
		password, ok := c.GetPostForm("password")
		if !ok {
			name = "123456"
		}

返回两个参数,如果能找到关键字ok为1,否则为0

关键字和html文件中控件的name属性对应,与id无关

login.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>login</title>
</head>
<body>
    <form action="/login" method="post" novalidate autocomplete="off">
        <div>
            <label for="username">username:</label>
        <input type="text" name="username" id="username1">
        </div>
        <div>
            <label for="password">password:</label>
        <input type="text" name="password" id="password1">
        </div>
        <div>
            <input type="submit" value="登录">//这里必须用submit,普通的按钮不会返回数据,类似的还有reset
        </div>
    </form>
</body>
</html>

这个过程中web系统发送了两次请求,输入网址时会发送一次GET请求,点击提交时会发送POST请求。每一种请求都需要由对应的相应处理函数。

这里我们只是对数据进行了简单处理,后续可以用来链接数据库,就能实现用户的登录注册等功能。

DAY5

获取url中的路径参数
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)
func main() {
	r := gin.Default()
	r.GET("/user/:name/:age", func(c *gin.Context) {
		name := c.Param("name")
		age := c.Param("age")
		c.JSON(http.StatusOK, gin.H{
			"name": name,
			"age":  age,
		})
	})
	r.GET("/blog/:year/:month", func(c *gin.Context) {
		year := c.Param("year")
		month := c.Param("month")
		c.JSON(http.StatusOK, gin.H{
			"year":  year,
			"month": month,
		})
	})
	r.Run(":8080")
}

当url和我们预先设置好的格式相匹配时,可以用Param根据对应的参数返回对应的值,返回值类型是字符串。

其实我们也可以在点击某个按钮时,进行某些操作时,会有页面的跳转操作,此时就相当于输入了新的,格式可以由我们控制的url,这样就也可以在不同网页间传递信息。

使用shouldbind绑定结构体获取参数

在前面我们知道可以用query获取?后的参数,但是一个一个获取太繁琐,所以我们可以将参数和结构体中的变量建立一个映射关系,从而快速获取参数。

可以用post和get两种方式,ShouldBind函数不仅能处理url,也能处理表单,还能处理JSON形式的数据。

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
)

type user struct {
	Username string `form:"username"`
	Password string `form:"password"`
}

func main() {
	r := gin.Default()
	r.LoadHTMLFiles("login.html")
	r.GET("/user", func(c *gin.Context) {
		/*username := c.Query("username")
		password := c.Query("password")
		u := user{
			username: username,
			password: password,
		}
		*/
		var u user
		c.ShouldBind(&u)
		fmt.Printf("%#v\n", u)
		c.JSON(http.StatusOK, gin.H{
			"message": "ok",
		})
	})
	r.GET("/login", func(c *gin.Context) {
		c.HTML(http.StatusOK, "login.html", nil)
	})
	r.POST("/post", func(c *gin.Context) {
		var u user
		c.ShouldBind(&u)
		fmt.Printf("%#v\n", u)
		c.JSON(http.StatusOK, gin.H{
			"message": "ok",
		})
	})
	r.Run(":8080")
}

这里由几点要注意:

1、这个函数实际上是根据映射关系修改结构体u的值,所以要将u的引用(地址)传进去才能修改

c.ShouldBind(&u)

2、要修改结构体的值,结构体的变量就必须是公有成员也就是要首字母大写,同时要在每个变量后面加上tag标记来标识这个成员对应url中的哪个变量。

type user struct {
	Username string `form:"username"`
	Password string `form:"password"`
}

可以写一个html文件来测试,也可以直接用postman来测试

使用post方式接受数据时,可以同时处理form数据和json数据,而用get方式可以处理url数据,ShouldBind函数会根据需要进行适配,调用合适的函数,因而我们函数体可以基本不变,只用修改请求方式即可。如果在json和form数据的名称有差别,只需修改结构体中的tag即可。

type user struct {
	Username string `form:"username" json:"user"`
	Password string `form:"password" json:"pwd"`
}
上传文件
上传单个文件

main.go

package main

import (
	"fmt"
	"net/http"
	"path"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.MaxMultipartMemory = 8 << 20
	r.LoadHTMLFiles("./file.html")
	r.GET("/file", func(c *gin.Context) {
		c.HTML(http.StatusOK, "file.html", nil)
	})
	r.POST("/upload", func(c *gin.Context) {
		//下载文件
		f, err := c.FormFile("f1")
		if err != nil {
			fmt.Println("upload error:%v", err)
		}
		//保存文件
		//dst := fmt.Sprintf("./%v", f.Filename)
		dst := path.Join("./", f.Filename)
		c.SaveUploadedFile(f, dst)
		c.JSON(http.StatusOK, gin.H{
			"status": "ok",
		})
	})
	r.Run(":8080")
}

file.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>uploadfile</title>
</head>
<body>
<form action="/upload" method="POST" enctype="multipart/form-data">
    <input type="file" name="f1">
    <input type="submit" value="提交">
</form>
</body>
</html>

f, err := c.FormFile(“f1”) 用于接受文件,并保存在f中

dst := fmt.Sprintf(“./%v”, f.Filename) 用于拼接字符串的两种方式,用于合成本地的存储路径
dst := path.Join(“./”, f.Filename)

c.SaveUploadedFile(f, dst) 用于保存文件,将文件f保存到dst中

r.MaxMultipartMemory = 8 << 20 用于设置最大容量大小,这里设置为8M

多个文件上传

main.go

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
)

type user struct {
	Username string `form:"username" json:"user"`
	Password string `form:"password" json:"pwd"`
}

func main() {
	r := gin.Default()
	r.MaxMultipartMemory = 8 << 20
	r.LoadHTMLFiles("./file.html")
	r.GET("/file", func(c *gin.Context) {
		c.HTML(http.StatusOK, "file.html", nil)
	})
	r.POST("/upload", func(c *gin.Context) {
		//下载文件
		form, err := c.MultipartForm()//把下载的文件都保存在表单变量form中
		if err != nil {
			fmt.Println("upload error:%v", err)
		}
		files := form.File["f"]//获取name为f的文件并保存在数组files中
		//保存文件
		for idx, file := range files {//for循环
			log.Println(file.Filename)//输出名称
			dst := fmt.Sprintf("./files/%s_%d", file.Filename, idx)//拼接地址
			c.SaveUploadedFile(file, dst)//保存文件
		}
		c.JSON(http.StatusOK, gin.H{
			"message": fmt.Sprintf("upload file number:%d", len(files)),//显示数组的容量
		})
	})
	r.Run(":8080")
}

go语言小知识:for循环

1、类似c语言,有其实条件,终止添加,变量递增

sum := 0
for i := 1; i <= 100; i++ {
	sum += i
}

2、类似python的 for i in range 的用法

for i := <集合名称>{
}

然后就会穷举集合中的每个变量,变量的数量要对应,如果是数组就需要两个参数idx(下标),num(值)

file.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>uploadfile</title>
</head>
<body>
<form action="/upload" method="POST" enctype="multipart/form-data">
    <input type="file" name="f"><br>
    <input type="file" name="f"><br>
    <input type="file" name="f"><br>
    <input type="file" name="f"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

这里name相同,在按名称访问时,会访问所有name="f"的文件,保存为一个数组。

路由重定向

vscode和goland使用c.Redirect函数时会有差异,vscode不能跳转到其他网站而goland可以。

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {

	r := gin.Default()
	r.GET("/test", func(c *gin.Context) {
		c.Redirect(http.StatusMovedPermanently, "http://www.sogo.com")
	})
	r.GET("/a", func(c *gin.Context) {
		c.Request.URL.Path = "/b"
		r.HandleContext(c)
	})
	r.GET("/b", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "ok",
		})
	})
	r.Run(":8080")
}

可以用

c.Redirect(http.StatusMovedPermanently, "http://www.sogo.com")

直接跳转到站外的网站

也可以用

c.Request.URL.Path = "/b"
r.HandleContext(c)

交给其他函数处理,例如这里就交给了负责处理"/b"的函数处理

gin框架的路由
基本路由

GET 访问操作

POST 创建操作

DELETE 删除操作

PUT 修改操作

这些操作使用方法完全一样,但是能传递不同的信息,是一直约定俗成的使用方式。输入url然后回车的请求只能用用GET来接收,点击submit按钮提交表单的请求只能用POST来接收。

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {

	r := gin.Default()
	r.GET("/index", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "GET",
		})
	})
	r.PUT("/index", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "PUT",
		})
	})
	r.DELETE("/index", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "DELETE",
		})
	})
	r.POST("/index", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "POST",
		})
	})
	
	r.Run(":8080")
}
Any 可以处理所有类型的请求
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {

	r := gin.Default()
	r.Any("/index",func(c *gin.Context) {
		switch c.Request.Method{//可以用switch来做区分
		case http.MethodGet:
			c.JSON(http.StatusOK,gin.H{"message":"get"})
		case http.MethodPost:
			c.JSON(http.StatusOK,gin.H{"message":"post"})
			//...
		}
	})
    r.Any("/any", func(c *gin.Context) {//也可以直接处理
		c.JSON(http.StatusOK, gin.H{"method": c.Request.Method})
	})
	r.Run(":8080")
}

http.MethodGet “GET”

MethodPost “POST”

…………

用c.Request.Method来获取请求的方式

NoRoute 当输入的路径不存在时会调用这个函数,实际上是代替了404notfound页面
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {

	r := gin.Default()
	r.Any("/index", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"method": c.Request.Method})
	})
	r.NoRoute(func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "小伙子,你输错啦!"})
	})
	r.Run(":8080")
}

Group路由组

其实没有实际上的作用,但是可以将一些请求块中公共的前缀提取出来,提高程序的可读性。

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	gr := r.Group("/video")
	{//这个大括号只是为了提高可读性,无实际作用
		gr.GET("/1", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "1"}) })
		gr.GET("/2", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "2"}) })
		gr.GET("/3", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "3"}) })
		gr.GET("/4", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "4"}) })
	}
	r.Run(":8080")
}

路由组也可以嵌套。

DAY6

gin框架的中间件

所谓中间件呢,就是在执行其他函数前需要优先执行的函数,可以用于在访问页面前的登录操作等等。

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
)

func f1(c *gin.Context) {
	fmt.Println("f1")
}
func f2(c *gin.Context) {
	fmt.Println("f2")
	c.JSON(http.StatusOK, gin.H{
		"message": "goto f2",
	})
}
func main() {

	r := gin.Default()
	r.GET("/index", f1, f2)
	r.Run()
}

GET函数的第二个参数可以接收多个函数作为参数,他们会按照传入的先后次序来执行。

这里的f1其实就是最简单的中间件

c.Next() 执行之后的所有函数然后再返回本函数
package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
)

func f1(c *gin.Context) {
	fmt.Println("f1 in...")
	start := time.Now()
	c.Next()
	cost := time.Since(start)
	fmt.Println(cost)
	fmt.Println("f1 out...")
}
func f2(c *gin.Context) {
	fmt.Println("f2")
	c.JSON(http.StatusOK, gin.H{"message": "index"})
}
func f3(c *gin.Context) {
	fmt.Println("f3")
	c.JSON(http.StatusOK, gin.H{"message": "index"})
}
func main() {

	r := gin.Default()
	r.GET("/index", f1, f2, f3)
	r.Run()
}

记录用时:

start := time.Now()//记录当前时间
cost := time.Since(start)//记录相比start过了多长时间

这里f1就起到了中间件的作用。

这里注意:c.Next()会把后面的函数都执行完后再返回本函数。

image-20211001092809867

如果这里用

r.GET("/index", f1, f1, f2, f3)

后面的中间件使用next函数执行过的函数,前面的中间件不会再执行

image-20211001093231848
c.Abort() 跳过后面的所有函数不执行,哪怕是当前函数执行完成后也不会执行

return 可以直接结束当前函数

package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
)

func f1(c *gin.Context) {
	fmt.Println("f1 in...")
	start := time.Now()
	c.Next()
	cost := time.Since(start)
	fmt.Println(cost)
	fmt.Println("f1 out...")
}
func f4(c *gin.Context) {
	fmt.Println("f4 in...")
	start := time.Now()
	c.Abort()
	cost := time.Since(start)
	fmt.Println(cost)
	fmt.Println("f4 out...")
}
func f2(c *gin.Context) {
	fmt.Println("f2")
	c.JSON(http.StatusOK, gin.H{"message": "index"})
}
func f3(c *gin.Context) {
	fmt.Println("f3")
	c.JSON(http.StatusOK, gin.H{"message": "index"})
}
func main() {

	r := gin.Default()
	r.GET("/index", f1, f4, f2, f1, f3)
	r.Run()
}

在这个程序中,f1会调用f4,f4会跳过后面所有的函数(包括中间件),所以既不会输出f2,f3,也不会返回页面。

Next函数和Abort函数就像是海关,执行Next代表放行,后面的工作交给其他函数处理,而Abort表示不通过,直接挡在门外

r.Use() 全局使用中间件

每个请求都要在前面写一个f1可能会比较繁琐,我们可以用r.Use(f1)来定义一个全局中间件,每个请求前都需要调用该函数。

package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
)

func f1(c *gin.Context) {
	fmt.Println("f1 in...")
	start := time.Now()
	c.Next()
	cost := time.Since(start)
	fmt.Println(cost)
	fmt.Println("f1 out...")
}
func main() {

	r := gin.Default()
	r.Use(f1, f1)//定义全局中间件,可以有多个参数
	r.GET("/index", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "index"}) })
	r.GET("/index1", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "index1"}) })
	r.GET("/index2", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "index2"}) })
	r.Run()
}
定义组的中间件
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func f1(name string) gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Set("name", name)
	}
}
func f(flag bool) gin.HandlerFunc {
	//连接数据库,校验信息,设置bool型变量flag
	//其他准备工作
	return func(c *gin.Context) {
		//做是否运行登录的判断
		if flag == true {
			c.Next()
			name, ok := c.Get("name")
			if !ok {
				name = "no name"
			}
			c.JSON(http.StatusOK, gin.H{
				"name": name,
			})
		} else {
			c.Abort()
		}
	}
}
func main() {

	r := gin.Default()
	r.GET("/test", f1("123"), func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"123": c.MustGet("name"),
		})
	})
	group1 := r.Group("/xx", f(true))
	{
		group1.GET("index1", f1("index1"))
		group1.GET("index2", f1("index2"))
		group1.GET("index3", f1("index3"))
	}
	group2 := r.Group("/xx1")
	group2.Use(f(true))
	{
		group2.GET("index4", f1("index4"))
		group2.GET("index5", f1("index5"))
		group2.GET("index6", f1("index6"))
	}
	r.Run()
}

全局中间件范围过大,不够灵活,可以用以上两种方式定义组的中间件,组的中间件只会对组中的成员生效。(大括号只是为了增加可读性)

函数的上下文中传递信息:

设置信息:

c.Set("name", name)//第一个参数表示取得值的关键字,第二个参数代表内容

获取信息:

name, ok := c.Get("name")//根据关键字"name"来获取值,能获取到ok为1,否则ok为0
name := c.MustGet("name")//同样根据关键字"name"来获取值,但是只有一个返回值

响应函数的类型一定是func(c *gin.Context)类型的,go语言中叫它gin.HandlerFunc类型,我们想向其中传入参数,可以用这种写法:

func f1(name string) gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Set("name", name)
	}
}
中间件注意事项
gin默认中间件

gin.Default()默认使用了LoggerRecovery中间件,其中:

  • Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release
  • Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。

如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。(但是其实没必要)

gin中间件中使用goroutine

当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。

Tip: 什么是goroutine?

goroutine是指一个go程序中的线程,Go 程序在启动时,运行时(runtime)会默认为 main() 函数创建一个 goroutine。在 main() 函数的 goroutine 中执行到 go running 语句时,归属于 running() 函数的 goroutine 被创建,running() 函数开始在自己的 goroutine 中执行。此时,main() 继续执行,两个 goroutine 通过 Go 程序的调度机制同时运作。

使用go running()来开启线程。

结合上面的例子,如果让多个线程一起访问并修改(c *gin.Context),那么c的状态就会变得未知,会造成并发不安全。所以我们是让它访问c的副本(只读),来提高安全性。

Gorm使用方法(mysql)
库操作

命令行启动数据库:

mysql -u root -p

建立表结构:

set foreign_key_checks=0;
drop table if exists `usert`;
mysql> create table `usert`(
`id` int(11) not null auto_increment,
`name` varchar(255) default null,
`password` varchar(255) default null,
 `email` varchar(255) default null,
`birthday` date default null,
`money` float(255,0) default null,
primary key(`id`) using btree
)engine=InnoDB auto_increment=8 default charset = utf8 row_format=dynamic;

展示所有的数据库:

show databases;

删除数据库

drop database test;

展示数据库

show databases;

使用数据库

use <数据库名称>

创建数据库

create database <名称>
表操作

(将库操作的database换成table即可)

展示表中的所有数据

select * from <表名称>;

展示表中每种属性的数据类型

desc <表名称>
gorm基本操作
package main

import (
	"fmt"

	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

type UserInfo struct {
	ID     uint
	Name   string
	Gender string
	Hobby  string
}

func main() {
	//连接数据库
	db, err := gorm.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/db1?charset=utf8")
	//db.SingularTable(true)
	if err != nil {
		panic(err) //抛出异常
	}
	//使用完数据库记得关闭
	defer db.Close()
	//数据库自动迁移
	db.AutoMigrate(&UserInfo{})
	//创建一个元组(一行)
	u1 := UserInfo{1, "lth", "male", "code"}
	db.Create(&u1)
	//查询第一个元素
	var u UserInfo
	db.First(&u)
	fmt.Printf("%#v\n", u)
    //修改hobby对应的值,并保存到u1中
	db.Model(&u1).Update("hobby", "web")
    //删除
	db.Delete(&u1)
}

总结:

增: db.Create(&u1)

删: db.Delete(&u1)

改: db.Model(&u1).Update("hobby", "web")

查: db.First(&u)

数据库使用模板:

完整的模型:

type User struct {
	gorm.Model
	Name         string
	Age          sql.NullInt64
	Birthday     *time.Time
	Email        string  `gorm:"type:varchar(100);unique_index"`
	Role         string  `gorm:"size:255"` // 设置字段大小为255
	MemberNumber *string `gorm:"unique;not null"` // 设置会员号(member number)唯一并且不为空
	Num          int     `gorm:"AUTO_INCREMENT"` // 设置 num 为自增类型
	Address      string  `gorm:"index:addr"` // 给address字段创建名为addr的索引
	IgnoreMe     int     `gorm:"-"` // 忽略本字段
  }
//定义结构体User
    //连接数据库
	db, err := gorm.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/db1?charset=utf8")
	//db.SingularTable(true)
	if err != nil {
		panic(err) //抛出异常
	}
	//使用完数据库记得关闭
	defer db.Close()
	//数据库自动迁移
	db.AutoMigrate(&User{})

结构体常用tag:

结构体标记(Tag)描述
Column指定列名
Type指定列数据类型
Size指定列大小, 默认值255
PRIMARY_KEY将列指定为主键
UNIQUE将列指定为唯一
DEFAULT指定列默认值
PRECISION指定列精度
NOT NULL将列指定为非 NULL
AUTO_INCREMENT指定列是否为自增类型
INDEX创建具有或不带名称的索引, 如果多个索引同名则创建复合索引
UNIQUE_INDEXINDEX 类似,只不过创建的是唯一索引
EMBEDDED将结构设置为嵌入
EMBEDDED_PREFIX设置嵌入结构的前缀
-忽略此字段

gorm会默认将ID字段设置为主键,可以使用gorm.Model中定义的ID,也自己定义一个ID作为主键。

也可以在属性后面加上tag: gorm:primary_key 设置属性值为主键

type User struct {} // 默认表名是 `users`,表名默认将字母转为小写然后加上s

// 将 User 的表名设置为 `profiles`,但是一般不会直接修改原来的表格,而是生成一个新表
func (User) TableName() string {
  return "profiles"
}
// 禁用默认表名的复数形式,如果置为 true,则 `User` 的默认表名是 `user`
db.SingularTable(true)

db.Table("lth").CreateTable(User{}) 创建一个名字为lth,类型为User的表

修改默认表名(不会对自己定义的表名生效)

gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string  {
  return "prefix_" + defaultTableName;
}

列名由字段名称进行下划线分割来生成

type User struct {
  ID        uint      // column name is `id`
  Name      string    // column name is `name`
  Birthday  time.Time // column name is `birthday`
  CreatedAt time.Time // column name is `created_at`
}

可以使用结构体tag指定列名:

gorm:"column:<名称>"

type Animal struct {
  AnimalId    int64     `gorm:"column:beast_id"`         // set column name to `beast_id`
  Birthday    time.Time `gorm:"column:day_of_the_beast"` // set column name to `day_of_the_beast`
  Age         int64     `gorm:"column:age_of_the_beast"` // set column name to `age_of_the_beast`
}
时间戳跟踪
CreatedAt

如果模型有 CreatedAt字段,该字段的值将会是初次创建记录的时间。

db.Create(&user) // `CreatedAt`将会是当前时间

// 可以使用`Update`方法来改变`CreateAt`的值
db.Model(&user).Update("CreatedAt", time.Now())
UpdatedAt

如果模型有UpdatedAt字段,该字段的值将会是每次更新记录的时间。

db.Save(&user) // `UpdatedAt`将会是当前时间

db.Model(&user).Update("name", "jinzhu") // `UpdatedAt`将会是当前时间
DeletedAt

如果模型有DeletedAt字段,调用Delete删除该记录时,将会设置DeletedAt字段为当前时间,而不是直接将记录从数据库中删除。

gorm增加
package main

import (
	"fmt"

	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

type User struct {
	ID   int64
	Name string
	Age  int64
}
func main() {
	//连接数据库
	db, err := gorm.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/db1?charset=utf8")
	//db.SingularTable(true)
	if err != nil {
		panic(err) //抛出异常
	}
	//使用完数据库记得关闭
	defer db.Close()
	//数据库自动迁移
	db.AutoMigrate(&User{})
	u1 := User{Name: "lth", Age: 20}//ID为默认主键,在不指定时,系统会自动分配一个ID
	if db.NewRecord(&u1) {//查询主键是否在数据库中出现,如果出现返回1,否则返回0
		fmt.Println("NO")
	}
	fmt.Println("YES")
	db.Create(&u1)

}

设置默认值:

Name string `gorm:"default:'未命名'"`

在不写改字段或者将该字段设置为0值(比如:“” , 0 , false 等等)都会使用默认值,默认值为0值则不会使用

想让它使用可以用sql.NullInt64或者sql.NullString类型,传值时也需要传sql.NullInt64或者sql.NullString类型的结构体

package main

import (
	"database/sql"

	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

type User struct {
	ID   int64
	Name string `gorm:"default:''"`
	Age  sql.NullInt64
}

func main() {
	//连接数据库
	db, err := gorm.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/db1?charset=utf8")
	//db.SingularTable(true)
	if err != nil {
		panic(err) //抛出异常
	}
	//使用完数据库记得关闭
	defer db.Close()
	//数据库自动迁移
	db.AutoMigrate(&User{})
	u1 := User{Age: sql.NullInt64{0, true}}
	db.Create(&u1)
}

db.debug() 展示操作的名SQL语句

user:=new(int) 顶一个int变量的指针

gorm查询
一般查询(随机访问)
// 根据主键查询第一条记录
db.First(&user)
 SELECT * FROM users ORDER BY id LIMIT 1;

// 随机获取一条记录
db.Take(&user)
 SELECT * FROM users LIMIT 1;

// 根据主键查询最后一条记录
db.Last(&user)
 SELECT * FROM users ORDER BY id DESC LIMIT 1;

// 查询所有的记录
db.Find(&users)
 SELECT * FROM users;

// 查询指定的某条记录(仅当主键为整型时可用)
db.First(&user, 10)
 SELECT * FROM users WHERE id = 10;

Where 条件

普通SQL查询
// Get first matched record	第一个name为?的记录
db.Where("name = ?", "jinzhu").First(&user)
 SELECT * FROM users WHERE name = 'jinzhu' limit 1;

// Get all matched records	所有name为?的记录
db.Where("name = ?", "jinzhu").Find(&users)
 SELECT * FROM users WHERE name = 'jinzhu';

// <>	所有name不等于jinzhu的记录
db.Where("name <> ?", "jinzhu").Find(&users)	
 SELECT * FROM users WHERE name <> 'jinzhu';

// IN	所有name在这个范围内的记录
db.Where("name IN (?)", []string{"jinzhu", "jinzhu 2"}).Find(&users)	
 SELECT * FROM users WHERE name in ('jinzhu','jinzhu 2');

// LIKE	名字里面保含jin的记录
db.Where("name LIKE ?", "%jin%").Find(&users)
 SELECT * FROM users WHERE name LIKE '%jin%';

// AND	满足这两个条件的记录
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
 SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;

// Time	修改时间大于?的记录
db.Where("updated_at > ?", lastWeek).Find(&users)
 SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';

// BETWEEN	创建时间在?和?之间的记录
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
 SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';
Struct & Map查询
// Struct	满足Name: "jinzhu", Age: 20的记录
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
 SELECT * FROM users WHERE name = "jinzhu" AND age = 20 LIMIT 1;

// Map	Name: "jinzhu", Age: 20的记录
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
 SELECT * FROM users WHERE name = "jinzhu" AND age = 20;

// 主键的切片	主键(ID)为这几个数的记录
db.Where([]int64{20, 21, 22}).Find(&users)
 SELECT * FROM users WHERE id IN (20, 21, 22);

**提示:**当通过结构体进行查询时,GORM将会只通过非零值字段查询,这意味着如果你的字段值为0''false或者其他零值时,将不会被用于构建查询条件,例如:

db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
 SELECT * FROM users WHERE name = "jinzhu";

(太坑了)

Not(与where用法相同,含义相反)
Or条件(多个条件满足一个即可,返回这样的记录)
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
 SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';

// Struct
db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2"}).Find(&users)
 SELECT * FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2';

// Map
db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2"}).Find(&users)
 SELECT * FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2';
内联条件

作用与Where查询类似,只不过将查询条件放到了函数里面,当内联条件与多个立即执行方法一起使用时, 内联条件不会传递给后面的立即执行方法。

实际上真正起到查询的作用的是这些立即实现方法,where等只是起到辅助作用。

// 根据主键获取记录 (只适用于整形主键)
db.First(&user, 23)
 SELECT * FROM users WHERE id = 23 LIMIT 1;
// 根据主键获取记录, 如果它是一个非整形主键
db.First(&user, "id = ?", "string_primary_key")
 SELECT * FROM users WHERE id = 'string_primary_key' LIMIT 1;

// Plain SQL
db.Find(&user, "name = ?", "jinzhu")
 SELECT * FROM users WHERE name = "jinzhu";

db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)
 SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;

// Struct
db.Find(&users, User{Age: 20})
 SELECT * FROM users WHERE age = 20;

// Map
db.Find(&users, map[string]interface{}{"age": 20})
 SELECT * FROM users WHERE age = 20;
FirstOrCreate(能找就找返回,找不到创建一个记录再返回)

获取匹配的第一条记录, 否则根据给定的条件创建一个新的记录 (仅支持 struct 和 map 条件)

// 未找到
db.FirstOrCreate(&user, User{Name: "non_existing"})
 INSERT INTO "users" (name) VALUES ("non_existing");
 user -> User{Id: 112, Name: "non_existing"}

// 找到
db.Where(User{Name: "Jinzhu"}).FirstOrCreate(&user)
 user -> User{Id: 111, Name: "Jinzhu"}
Attrs

如果记录未找到,将使用参数创建 struct 和记录

db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
Assign

不管记录是否找到,都将参数赋值给 struct 并保存至数据库.

// 未找到
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
 SELECT * FROM users WHERE name = 'non_existing';
 INSERT INTO "users" (name, age) VALUES ("non_existing", 20);
 user -> User{Id: 112, Name: "non_existing", Age: 20}

// 找到
db.Where(User{Name: "jinzhu"}).Assign(User{Age: 30}).FirstOrCreate(&user)
 SELECT * FROM users WHERE name = 'jinzhu';
 UPDATE users SET age=30 WHERE id = 111;
 user -> User{Id: 111, Name: "jinzhu", Age: 30}

高级查询

子查询

基于 *gorm.expr 的子查询

db.Where("amount > ?", db.Table("orders").Select("AVG(amount)").Where("state = ?", "paid").SubQuery()).Find(&orders)
// SELECT * FROM "orders"  WHERE "orders"."deleted_at" IS NULL AND (amount > (SELECT AVG(amount) FROM "orders"  WHERE (state = 'paid')));
选择字段

Select,指定你想从数据库中检索出的字段,默认会选择全部字段。

db.Select("name, age").Find(&users)
 SELECT name, age FROM users;

db.Select([]string{"name", "age"}).Find(&users)
 SELECT name, age FROM users;

db.Table("users").Select("COALESCE(age,?)", 42).Rows()
 SELECT COALESCE(age,'42') FROM users;
排序

Order,指定从数据库中检索出记录的顺序。设置第二个参数 reorder 为 true ,可以覆盖前面定义的排序条件。

db.Order("age desc, name").Find(&users)
 SELECT * FROM users ORDER BY age desc, name;

// 多字段排序
db.Order("age desc").Order("name").Find(&users)
 SELECT * FROM users ORDER BY age desc, name;

// 覆盖排序
db.Order("age desc").Find(&users1).Order("age", true).Find(&users2)
 SELECT * FROM users ORDER BY age desc; (users1)
 SELECT * FROM users ORDER BY age; (users2)
数量

Limit,指定从数据库检索出的最大记录数。

db.Limit(3).Find(&users)
 SELECT * FROM users LIMIT 3;

// -1 取消 Limit 条件
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
 SELECT * FROM users LIMIT 10; (users1)
 SELECT * FROM users; (users2)
偏移

Offset,指定开始返回记录前要跳过的记录数。

db.Offset(3).Find(&users)
 SELECT * FROM users OFFSET 3;

// -1 取消 Offset 条件
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
 SELECT * FROM users OFFSET 10; (users1)
 SELECT * FROM users; (users2)
总数

Count,该 model 能获取的记录总数。

db.Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Find(&users).Count(&count)
 SELECT * from USERS WHERE name = 'jinzhu' OR name = 'jinzhu 2'; (users)
 SELECT count(*) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'; (count)

db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
 SELECT count(*) FROM users WHERE name = 'jinzhu'; (count)

db.Table("deleted_users").Count(&count)
 SELECT count(*) FROM deleted_users;

db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
 SELECT count( distinct(name) ) FROM deleted_users; (count)

注意 Count 必须是链式查询的最后一个操作 ,因为它会覆盖前面的 SELECT,但如果里面使用了 count 时不会覆盖

Group & Having
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows()
for rows.Next() {
  ...
}

// 使用Scan将多条结果扫描进事先准备好的结构体切片中
type Result struct {
	Date time.Time
	Total int
}
var rets []Result
db.Table("users").Select("date(created_at) as date, sum(age) as total").Group("date(created_at)").Scan(&rets)

rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Rows()
for rows.Next() {
  ...
}

type Result struct {
  Date  time.Time
  Total int64
}
db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results)
连接

Joins,指定连接条件

rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows()
for rows.Next() {
  ...
}

db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)

// 多连接及参数
db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)
Pluck

Pluck,查询 model 中的一个列作为切片,如果您想要查询多个列,您应该使用 Scan

var ages []int64
db.Find(&users).Pluck("age", &ages)

var names []string
db.Model(&User{}).Pluck("name", &names)

db.Table("deleted_users").Pluck("name", &names)

// 想查询多个字段? 这样做:
db.Select("name, age").Find(&users)
扫描

Scan,扫描结果至一个 struct.

type Result struct {
  Name string
  Age  int
}

var result Result
db.Table("users").Select("name, age").Where("name = ?", "Antonio").Scan(&result)

var results []Result
db.Table("users").Select("name, age").Where("id > ?", 0).Scan(&results)

// 原生 SQL
db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result)

链式操作相关

链式操作

Method Chaining,Gorm 实现了链式操作接口,所以你可以把代码写成这样:

// 创建一个查询
tx := db.Where("name = ?", "jinzhu")

// 添加更多条件
if someCondition {
  tx = tx.Where("age = ?", 20)
} else {
  tx = tx.Where("age = ?", 30)
}

if yetAnotherCondition {
  tx = tx.Where("active = ?", 1)
}

在调用立即执行方法前不会生成Query语句,借助这个特性你可以创建一个函数来处理一些通用逻辑。

立即执行方法

Immediate methods ,立即执行方法是指那些会立即生成SQL语句并发送到数据库的方法, 他们一般是CRUD方法,比如:

Create, First, Find, Take, Save, UpdateXXX, Delete, Scan, Row, Rows

这有一个基于上面链式方法代码的立即执行方法的例子:

tx.Find(&user)

生成的SQL语句如下:

SELECT * FROM users where name = 'jinzhu' AND age = 30 AND active = 1;
范围

Scopes,Scope是建立在链式操作的基础之上的。

基于它,你可以抽取一些通用逻辑,写出更多可重用的函数库。

func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
  return db.Where("amount > ?", 1000)
}

func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
  return db.Where("pay_mode_sign = ?", "C")
}

func PaidWithCod(db *gorm.DB) *gorm.DB {
  return db.Where("pay_mode_sign = ?", "C")
}

func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB {
  return func (db *gorm.DB) *gorm.DB {
    return db.Scopes(AmountGreaterThan1000).Where("status IN (?)", status)
  }
}

db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
// 查找所有金额大于 1000 的信用卡订单

db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
// 查找所有金额大于 1000 的 COD 订单

db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
// 查找所有金额大于 1000 且已付款或者已发货的订单
多个立即执行方法

Multiple Immediate Methods,在 GORM 中使用多个立即执行方法时,后一个立即执行方法会复用前一个立即执行方法的条件 (不包括内联条件) 。

db.Where("name LIKE ?", "jinzhu%").Find(&users, "id IN (?)", []int{1, 2, 3}).Count(&count)

生成的 Sql

SELECT * FROM users WHERE name LIKE 'jinzhu%' AND id IN (1, 2, 3)

SELECT count(*) FROM users WHERE name LIKE 'jinzhu%'

DAY7

更新

更新所有字段

Save()默认会更新该对象的所有字段,即使你没有赋值。

db.First(&user)

user.Name = "七米"
user.Age = 99
db.Save(&user)

  UPDATE `users` SET `created_at` = '2020-02-16 12:52:20', `updated_at` = '2020-02-16 12:54:55', `deleted_at` = NULL, `name` = '七米', `age` = 99, `active` = true  WHERE `users`.`deleted_at` IS NULL AND `users`.`id` = 1
更新修改字段

如果你只希望更新指定字段,可以使用Update或者Updates

// 更新单个属性,如果它有变化
db.Model(&user).Update("name", "hello")
 UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

// 根据给定的条件更新单个属性
db.Model(&user).Where("active = ?", true).Update("name", "hello")
 UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;

// 使用 map 更新多个属性,只会更新其中有变化的属性
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
 UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

// 使用 struct 更新多个属性,只会更新其中有变化且为非零值的字段
db.Model(&user).Updates(User{Name: "hello", Age: 18})
 UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;

// 警告:当使用 struct 更新时,GORM只会更新那些非零值的字段
// 对于下面的操作,不会发生任何更新,"", 0, false 都是其类型的零值
db.Model(&user).Updates(User{Name: "", Age: 0, Active: false})
更新选定字段

如果你想更新或忽略某些字段,你可以使用 SelectOmit

db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
 UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
 UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
无Hooks更新

上面的更新操作会自动运行 model 的 BeforeUpdate, AfterUpdate 方法,更新 UpdatedAt 时间戳, 在更新时保存其 Associations, 如果你不想调用这些方法,你可以使用 UpdateColumnUpdateColumns

// 更新单个属性,类似于 `Update`
db.Model(&user).UpdateColumn("name", "hello")
 UPDATE users SET name='hello' WHERE id = 111;

// 更新多个属性,类似于 `Updates`
db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})
 UPDATE users SET name='hello', age=18 WHERE id = 111;
批量更新

批量更新时Hooks(钩子函数)不会运行。

db.Table("users").Where("id IN (?)", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
 UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);

// 使用 struct 更新时,只会更新非零值字段,若想更新所有字段,请使用map[string]interface{}
db.Model(User{}).Updates(User{Name: "hello", Age: 18})
 UPDATE users SET name='hello', age=18;

// 使用 `RowsAffected` 获取更新记录总数
db.Model(User{}).Updates(User{Name: "hello", Age: 18}).RowsAffected
使用SQL表达式更新

先查询表中的第一条数据保存至user变量。

var user User
db.First(&user)
db.Model(&user).Update("age", gorm.Expr("age * ? + ?", 2, 100))
 UPDATE `users` SET `age` = age * 2 + 100, `updated_at` = '2020-02-16 13:10:20'  WHERE `users`.`id` = 1;

db.Model(&user).Updates(map[string]interface{}{"age": gorm.Expr("age * ? + ?", 2, 100)})
 UPDATE "users" SET "age" = age * '2' + '100', "updated_at" = '2020-02-16 13:05:51' WHERE `users`.`id` = 1;

db.Model(&user).UpdateColumn("age", gorm.Expr("age - ?", 1))
 UPDATE "users" SET "age" = age - 1 WHERE "id" = '1';

db.Model(&user).Where("age > 10").UpdateColumn("age", gorm.Expr("age - ?", 1))
 UPDATE "users" SET "age" = age - 1 WHERE "id" = '1' AND quantity > 10;
修改Hooks中的值

如果你想修改 BeforeUpdate, BeforeSave 等 Hooks 中更新的值,你可以使用 scope.SetColumn, 例如:

func (user *User) BeforeSave(scope *gorm.Scope) (err error) {
  if pw, err := bcrypt.GenerateFromPassword(user.Password, 0); err == nil {
    scope.SetColumn("EncryptedPassword", pw)//密码加密
  }
}
其它更新选项
// 为 update SQL 添加其它的 SQL
db.Model(&user).Set("gorm:update_option", "OPTION (OPTIMIZE FOR UNKNOWN)").Update("name", "hello")
 UPDATE users SET name='hello', updated_at = '2013-11-17 21:34:10' WHERE id=111 OPTION (OPTIMIZE FOR UNKNOWN);

删除

删除记录

警告 删除记录时,请确保主键字段有值,GORM 会通过主键去删除记录,如果主键为空,GORM 会删除该 model 的所有记录。

// 删除现有记录
db.Delete(&email)
 DELETE from emails where id=10;

// 为删除 SQL 添加额外的 SQL 操作
db.Set("gorm:delete_option", "OPTION (OPTIMIZE FOR UNKNOWN)").Delete(&email)
 DELETE from emails where id=10 OPTION (OPTIMIZE FOR UNKNOWN);
批量删除

删除全部匹配的记录

db.Where("email LIKE ?", "%jinzhu%").Delete(Email{})
 DELETE from emails where email LIKE "%jinzhu%";

db.Delete(Email{}, "email LIKE ?", "%jinzhu%")
 DELETE from emails where email LIKE "%jinzhu%";
软删除

如果一个 model 有 DeletedAt 字段,他将自动获得软删除的功能! 当调用 Delete 方法时, 记录不会真正的从数据库中被删除, 只会将DeletedAt 字段的值会被设置为当前时间

db.Delete(&user)
 UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;

// 批量删除
db.Where("age = ?", 20).Delete(&User{})
 UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;

// 查询记录时会忽略被软删除的记录
db.Where("age = 20").Find(&user)
 SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;

// Unscoped 方法可以查询被软删除的记录
db.Unscoped().Where("age = 20").Find(&users)
 SELECT * FROM users WHERE age = 20;
var users []User
	db.Unscoped().Find(&users)
	for i := range users {
		fmt.Printf("%v\n", users[i])
	}

展示所有被软删除的记录

物理删除
// Unscoped 方法可以物理删除记录
db.Unscoped().Delete(&order)
 DELETE FROM orders WHERE id=10;

DAY8 小清单项目启动

r.Static("/static", "static")

html文件中的路径是相对于它本身的,这个函数可以将相对于html文件的路径转换成相对于main.go文件的路径(html文件和main文件在同一目录下就不用)

根据前端搭好了一个框架

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

type Todo struct {
	ID     int    `json:"id"`
	Title  string `json:"title"`
	Status bool   `json:"status"`
}

func main() {
	r := gin.Default()
	//告诉gin框架在哪里找静态文件
	r.Static("/static", "static")
	//告诉gin框架在哪里找hmtl文件
	r.LoadHTMLGlob("templates/*")
	r.GET("/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", nil)
	})
	//v1
	v1group := r.Group("v1")
	{
		//代办事项
		//添加
		v1group.POST("/todo", func(c *gin.Context) {

		})
		//查看
		//查看所有的代办事项
		v1group.GET("/todo", func(c *gin.Context) {

		})
		//查看某一个代办事项
		v1group.GET("todo/:id", func(c *gin.Context) {

		})
		//修改
		v1group.PUT("todo/:id", func(c *gin.Context) {

		})
		//删除
		v1group.DELETE("todo/:id", func(c *gin.Context) {

		})
	}
	r.Run(":8080")

}

实现后:

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

var (
	db *gorm.DB
)

type Todo struct {
	ID     int    `json:"id"`
	Title  string `json:"title"`
	Status bool   `json:"status"`
}

func InitMySQL() (err error) {
	//注意不能加:,否则会生成一个局部变量而没有初始化全局变量
	db, err = gorm.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/bubble?charset=utf8")
	if err != nil {
		return
	}
	return db.DB().Ping() //如果有错返回错误信息,否则返回nil
}
func main() {
	//连接数据库
	err := InitMySQL()
	if err != nil {
		panic(err)
	}
	defer db.Close() //用完记得关闭数据库

	//绑定模型
	db.AutoMigrate(&Todo{}) //创建的表名为todos,如果手动创建表名一定要一致

	r := gin.Default()
	//告诉gin框架在哪里找静态文件
	r.Static("/static", "static")
	//告诉gin框架在哪里找hmtl文件
	r.LoadHTMLGlob("templates/*")
	r.GET("/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", nil)
	})
	//v1
	v1group := r.Group("v1")
	{
		//代办事项
		//添加
		v1group.POST("/todo", func(c *gin.Context) {
			//接受数据
			var todo Todo
			c.BindJSON(&todo)
			//保存数据
			err := db.Create(&todo).Error
			if err != nil {
				c.JSON(http.StatusOK, gin.H{"error": err.Error()})
			} else {
				c.JSON(http.StatusOK, gin.H{
					"data": todo,
				})
			}
			//返回信息
		})
		//查看
		//查看所有的代办事项
		v1group.GET("/todo", func(c *gin.Context) {
			var todolist []Todo
			err := db.Find(&todolist).Error
			if err != nil {
				c.JSON(http.StatusOK, gin.H{"error": err})
			} else {
				c.JSON(http.StatusOK, todolist)
			}

		})
		//查看某一个代办事项
		v1group.GET("todo/:id", func(c *gin.Context) {

		})
		//修改(前端会返回一个带id的url)
		v1group.PUT("todo/:id", func(c *gin.Context) {
			var todo Todo
			id, ok := c.Params.Get("id")
			if !ok {
				c.JSON(http.StatusOK, gin.H{"error": err})
			}
			db.Where("id=?", id).First(&todo) //找到在数据库中的索引
			c.BindJSON(&todo)                 //这里因为前端修改了status,所以这里要和前端同步
			err := db.Save(&todo).Error
			if err != nil {
				c.JSON(http.StatusOK, gin.H{"error": err})
			} else {
				c.JSON(http.StatusOK, todo)
			}
		})
		//删除,前端会把删除信息传入到DELETE接口上,所以需要用DELETE接口来接收(前面几个也一样)
		v1group.DELETE("todo/:id", func(c *gin.Context) {
			var todo Todo
			id, ok := c.Params.Get("id")
			if !ok {
				c.JSON(http.StatusOK, gin.H{"error": "error"})
				return
			}
			err := db.Where("id=?", id).Find(&todo).Error
			if err != nil {
				c.JSON(http.StatusOK, gin.H{"error": err})
			} else {
				db.Delete(todo)
				c.JSON(http.StatusOK, gin.H{id: "deleted"})
			}
		})
	}
	r.Run(":8080")

}

对项目进行拆分:

​ url ----> controller ----> logic ----> models

请求来了 ----> 控制器 ----> 业务逻辑 ----> 模型层的增删改查

最好软件应当由一下部分组成:

controller 控制器,是路由的相应函数

dao 数据库,用于完成数据库的初始化等操作

models 模型,用于模型在数据库中的增删改查

routers 路由组,为不同的url分配相应函数

static 静态文件,用于保存前端的风格主题文件

templates 模板库,展示给用户的页面

main.go 程序入口

结语

在这七天的学习中,完成了GIN-GORM基本框架的学习,离完成自己的博客又近了一步。革命尚未完成,我仍需继续努力!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值