Gin入门
声明:本博客为看李文周大佬gin入门视频笔记gin入门
我的代码仓库6月 · 沉着冷静/2023 - 码云 - 开源中国 (gitee.com)
安装
go get -u github.com/gin-gonic/gin
第一个Gin实例:
package main
import (
"github.com/gin-gonic/gin"
)
func sayHello(c *gin.Context) {
c.JSON(200, gin.H{
"message": "hello go",
})
}
func main() {
//设置默认路由引擎
r := gin.Default()
// 指定用户使用Get访问/hello
r.GET("/hello", sayHello)
//启动服务器
r.Run() //r.Run("9090")
}
RESTful API
REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。
简单来说,REST的含义就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作。
GET
用来获取资源POST
用来新建资源PUT
用来更新资源DELETE
用来删除资源。
只要API程序遵循了REST风格,那就可以称其为RESTful API。
实例
基于RESTful API的框架:图书管理系统
请求方法 | URL | 含义 |
---|---|---|
GET | /book | 查询书籍信息 |
POST | /book | 创建书籍信息 |
PUT | /book | 更新书籍信息 |
DELETE | /book | 删除书籍信息 |
func main() {
//设置默认路由引擎
r := gin.Default()
// 指定用户使用Get访问/hello
r.GET("/hello", sayHello)
//图书管理系统
r.GET("/book", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "GET",
"message": "查询书籍",
})
})
r.POST("/book", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "POST",
"message": "创建书籍",
})
})
r.PUT("/book", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "PUT",
"message": "更新书籍",
})
})
r.DELETE("/book", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "DELETE",
"message": "删除书籍",
})
})
//启动服务器
r.Run() //r.Run("9090")
}
使用ApiPost进行测试
GET:
POST:
PUT:
DELETE:
Go渲染
html渲染实例
{{ . }}
package main
import (
"html/template"
"net/http"
)
func sayHello(w http.ResponseWriter, r *http.Request) {
//解析模版
t, err := template.ParseFiles("./hello.tmpl")
if err != nil {
panic(err)
}
//渲染模版
name := "张三"
err = t.Execute(w, name)
if err != nil {
panic(err)
}
}
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":8080", nil)
if err != nil {
panic(err)
}
}
#hello.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<tittle>Hello GO</tittle>
</head>
<body>
<p>Hello {{ . }}</p>
</body>
</html>
暂时简单理解为字符串替换。
解析结构体
type User struct {
Name string
Gender string
Age int64
}
func sayHello(w http.ResponseWriter, r *http.Request) {
//解析模版
t, err := template.ParseFiles("./hello.tmpl")
if err != nil {
panic(err)
}
//渲染模版
u1 := User{
Name: "张三",
Gender: "男",
Age: 18,
}
//name := "张三"
err = t.Execute(w, u1)
if err != nil {
panic(err)
}
}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello Go Template</title>
</head>
<body>
<p>Hello {{ .Name }}</p>
<p>年龄:{{ .Age }}</p>
<p>性别:{{ .Gender }}</p>
</body>
</html>
注意:结构体首字母需要大写
如果它的首字母是小写,那么它就是未导出的(即字段名首字母为小写)。
在 Go 语言中,未导出的字段和方法只能在同一个包内访问。
解析map
func sayHello(w http.ResponseWriter, r *http.Request) {
//解析模版
t, err := template.ParseFiles("./hello.tmpl")
if err != nil {
panic(err)
}
//渲染模版
m1 := map[string]interface{}{
"name": "李四",
"gender": "女",
"age": 20,
}
//name := "张三"
err = t.Execute(w, m1)
if err != nil {
panic(err)
}
}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello Go Template</title>
</head>
<body>
<p>Hello {{ .name }}</p>
<p>年龄:{{ .age }}</p>
<p>性别:{{ .gender }}</p>
</body>
</html>
注意:首字母无需大写,因为map通过key寻找value
空接口自定义
func sayHello(w http.ResponseWriter, r *http.Request) {
//解析模版
t, err := template.ParseFiles("./hello.tmpl")
if err != nil {
panic(err)
}
//渲染模版
u1 := User{
Name: "张三",
Gender: "男",
Age: 18,
}
m1 := map[string]interface{}{
"name": "李四",
"gender": "女",
"age": 20,
}
//name := "张三"
err = t.Execute(w, map[string]interface{}{
"u1": u1,
"m1": m1,
})
if err != nil {
panic(err)
}
}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello Go Template</title>
</head>
<body>
<p>Hello {{ .m1.name }}</p>
<p>年龄:{{ .m1.age }}</p>
<p>性别:{{ .m1.gender }}</p>
<p>Hello {{ .u1.Name }}</p>
<p>年龄:{{ .u1.Age }}</p>
<p>性别:{{ .u1.Gender }}</p>
</body>
</html>
常用语法
注释
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello Go Template</title>
</head>
<body>
<p>Hello {{ .m1.name }}</p>
<p>年龄:{{ .m1.age }}</p>
<p>性别:{{ .m1.gender }}</p>
{{/*这是一条注释*/}}
<p>Hello {{ .u1.Name }}</p>
<p>年龄:{{ .u1.Age }}</p>
<p>性别:{{ .u1.Gender }}</p>
</body>
</html>
变量
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello Go Template</title>
</head>
<body>
<p>Hello {{ .m1.name }}</p>
<p>年龄:{{ .m1.age }}</p>
<p>性别:{{ .m1.gender }}</p>
{{/*这是一条注释*/}}
<p>Hello {{ .u1.Name }}</p>
<p>年龄:{{ .u1.Age }}</p>
<p>性别:{{ .u1.Gender }}</p>
<hr>
{{/*这是两个变量*/}}
{{ $v1 := 20 }}
{{ $age := .m1.age}}
</body>
</html>
移除空格
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello Go Template</title>
</head>
<hr>
{{/*name的空格被移除*/}}
<p>Hello {{- .m1.name -}}</p>
</body>
</html>
条件判断
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello Go Template</title>
</head>
<hr>
{{/*name的空格被移除*/}}
<p>Hello {{- .m1.name -}}</p>
<p>年龄:{{ .m1.age }}</p>
<p>性别:{{ .m1.gender }}</p>
{{/*这是一条注释*/}}
<p>Hello {{ .u1.Name }}</p>
<p>年龄:{{ .u1.Age }}</p>
<p>性别:{{ .u1.Gender }}</p>
<hr>
{{/*这是两个变量*/}}
{{ $v1 := 20 }}
{{ $age := .m1.age}}
{{ if $v1}}
{{ $v1 }}
{{else}}
啥也没有
{{end}}
</body>
</html>
当然也有else if
比较函数
例如
lt:小于
gt:大于
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello Go Template</title>
</head>
<hr>
{{/*name的空格被移除*/}}
<p>Hello {{- .m1.name -}}</p>
<p>年龄:{{ .m1.age }}</p>
<p>性别:{{ .m1.gender }}</p>
{{/*这是一条注释*/}}
<p>Hello {{ .u1.Name }}</p>
<p>年龄:{{ .u1.Age }}</p>
<p>性别:{{ .u1.Gender }}</p>
<hr>
{{/*这是两个变量*/}}
{{ $v1 := 20 }}
{{ $age := .m1.age}}
{{ if $v1}}
{{ $v1 }}
{{else}}
啥也没有
{{end}}
{{ if lt .m1.age 20 }}
好好学习
{{ else if gt .m1.age 22 }}
好好工作
{{ else }}
666
{{end}}
</body>
</html>
range
//...
hobbylist := []string{
"篮球",
"足球",
"乒乓球",
}
//name := "张三"
err = t.Execute(w, map[string]interface{}{
"u1": u1,
"m1": m1,
"hobby": hobbylist,
})
//...
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello Go Template</title>
</head>
//...
<hr>
{{ range $idx, $hobby := .hobby }}
<p>{{$idx}} - {{$hobby}}</p>
{{end}}
</body>
</html>
也可以加else
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello Go Template</title>
</head>
//...
<hr>
{{ range $idx, $hobby := .hobby }}
<p>{{$idx}} - {{$hobby}}</p>
{{else}}
空空如也
{{end}}
</body>
</html>
with
引一个作用域
//...
{{with .m1}}
<p>{{ .name }}</p>
<p>{{ .gender }}</p>
<p>{{ .age }}</p>
{{end}}
//...
方便替换
//...
<p>Hello {{- .m1.name -}}</p>
<p>年龄:{{ .m1.age }}</p>
<p>性别:{{ .m1.gender }}</p>
//...
例如:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello Go Template</title>
</head>
<hr>
{{/*name的空格被移除*/}}
<p>Hello {{- .m1.name -}}</p>
<p>年龄:{{ .m1.age }}</p>
<p>性别:{{ .m1.gender }}</p>
{{/*这是一条注释*/}}
<p>Hello {{ .u1.Name }}</p>
<p>年龄:{{ .u1.Age }}</p>
<p>性别:{{ .u1.Gender }}</p>
<hr>
{{/*这是两个变量*/}}
{{ $v1 := 20 }}
{{ $age := .m1.age}}
{{ if $v1}}
{{ $v1 }}
{{else}}
啥也没有
{{end}}
{{ if lt .m1.age 20 }}
好好学习
{{ else if gt .m1.age 22 }}
好好工作
{{ else }}
666
{{end}}
<hr>
{{ range $idx, $hobby := .hobby }}
<p>{{$idx}} - {{$hobby}}</p>
{{else}}
空空如也
{{end}}
<hr>
{{with .m1}}
<p>{{ .name }}</p>
<p>{{ .gender }}</p>
<p>{{ .age }}</p>
{{end}}
</body>
</html>
自定义函数
func f1(w http.ResponseWriter, r *http.Request) {
//定义函数
kua := func(name string) (string, error) {
return name + "年轻帅气", nil
}
//定义模版
//解析模版
t := template.New("f.tmpl")
// 告诉引擎多了一个自定义函数kua
t.Funcs(template.FuncMap{
"kua": kua,
})
_, err := t.ParseFiles("./f.tmpl")
if err != nil {
panic(err)
}
name := "张三"
//渲染模版
t.Execute(w, name)
}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Hello Go Template</title>
</head>
<body>
{{ kua . }}
</body>
</html>
模版嵌套
f.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<body>
<h1>测试嵌套模版</h1>
<hr>
{{/*嵌套了另外一个单独的模版文件*/}}
{{template "ul.tmpl"}}
<hr>
{{/*嵌套另一个define定义的模版*/}}
{{template "ol.tmpl"}}
<div>你好 {{ . }}</div>
</body>
</html>
{{/*通过define定义一个模版*/}}
{{ define "ol.tmpl"}}
<ol>
<li>吃饭</li>
<li>睡觉</li>
<li>编程</li>
</ol>
{{end}}
ul.tmpl
<h3>
<li>注册</li>
<li>登录</li>
<li>退出</li>
</h3>
func demo(w http.ResponseWriter, r *http.Request) {
//被包含的写在后面
t, err := template.ParseFiles("./f.tmpl", "./ul.tmpl")
if err != nil {
panic(err)
}
name := "李四"
t.Execute(w, name)
}
func main() {
http.HandleFunc("/", f1)
http.HandleFunc("/tmpl", demo)
err := http.ListenAndServe(":8080", nil)
if err != nil {
panic(err)
}
}
模版继承:block
就相当于以下两个页面中,只有标题内容是不同的,我们完全没必要再写一个重复内容的前端页面
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>模版继承</title>
<style>
* {
margin: 0;
}
.nav {
height: 50px;
width: 100%;
position: fixed;
top: 0;
background-color: burlywood;
}
.main {
margin-top: 50px;
position: relative;
}
.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>{{ . }}</h1> ///...不同
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>模版继承</title>
<style>
* {
margin: 0;
}
.nav {
height: 50px;
width: 100%;
position: fixed;
top: 0;
background-color: burlywood;
}
.main {
margin-top: 50px;
position: relative;
}
.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>{{ . }}</h1> ///...不同
</div>
</div>
</body>
</html>
package main
import (
"html/template"
"net/http"
)
func index(w http.ResponseWriter, r *http.Request) {
//定义模版
//解析模版
t, err := template.ParseFiles("./index.tmpl")
if err != nil {
panic(err)
}
msg := "index页面"
//渲染模版
t.Execute(w, msg)
}
func home(w http.ResponseWriter, r *http.Request) {
//定义模版
//解析模版
t, err := template.ParseFiles("./home.tmpl")
if err != nil {
panic(err)
}
msg := "home页面"
//渲染模版
t.Execute(w, msg)
}
func main() {
http.HandleFunc("/index", index)
http.HandleFunc("/home", home)
err := http.ListenAndServe(":8080", nil)
if err != nil {
panic(err)
}
}
此时我们就可以使用block了。
base
{{/*或者使用define重命名文件*/}}
{{/*{{define "xx"}}*/}}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>模版继承</title>
<style>
* {
margin: 0;
}
.nav {
height: 50px;
width: 100%;
position: fixed;
top: 0;
background-color: burlywood;
}
.main {
margin-top: 50px;
position: relative;
}
.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">
{{block "content" .}}{{end}}
</div>
</div>
</body>
</html>
index2.tmpl
{{/*继承根模版*/}}
{{/*记得接收数据*/}}
{{template "base.tmpl" .}}//这个.就是接收来的数据
{{/*重新定义根模版*/}}
{{define "content"}}
<h1>这是index2页面</h1>
<p>Hello {{ . }}</p>
{{end}}
home2.tmpl
{{/*继承根模版*/}}
{{/*记得接收数据*/}}
{{template "base.tmpl" .}}
{{/*重新定义根模版*/}}
{{define "content"}}
<h1>这是home2页面</h1>
<p>Hello {{ . }}</p>
{{end}}
main
package main
import (
"html/template"
"net/http"
)
func index(w http.ResponseWriter, r *http.Request) {
//定义模版
//解析模版
t, err := template.ParseFiles("./f.tmpl")
if err != nil {
panic(err)
}
msg := "index页面"
//渲染模版
t.Execute(w, msg)
}
func home(w http.ResponseWriter, r *http.Request) {
//定义模版
//解析模版
t, err := template.ParseFiles("./f.tmpl")
if err != nil {
panic(err)
}
msg := "home页面"
//渲染模版
t.Execute(w, msg)
}
func index2(w http.ResponseWriter, r *http.Request) {
//定义模版
//解析模版
t, err := template.ParseFiles("./template/base.tmpl", "./template/index.tmpl")
if err != nil {
panic(err)
}
msg := "张三"
//渲染模版
t.ExecuteTemplate(w, "index.tmpl", msg)
}
func home2(w http.ResponseWriter, r *http.Request) {
//定义模版
//解析模版
t, err := template.ParseFiles("./template/base.tmpl", "./template/home.tmpl")
if err != nil {
panic(err)
}
msg := "李四"
//渲染模版
t.ExecuteTemplate(w, "home.tmpl", msg)
}
func main() {
http.HandleFunc("/index", index)
http.HandleFunc("/home", home)
http.HandleFunc("/index2", index2)
http.HandleFunc("/home2", home2)
err := http.ListenAndServe(":8080", nil)
if err != nil {
panic(err)
}
}
模版补充
修改默认标识符
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>修改模拟引擎的标识符</title>
</head>
<body>
<div>Hello {[ . ]}</div>
</body>
</html>
package main
import (
"html/template"
"net/http"
)
func index(w http.ResponseWriter, r *http.Request) {
//定义模版
//解析模版
t, err := template.New("index.tmpl").
Delims("{[", "]}").
ParseFiles("./index.tmpl")
if err != nil {
panic(err)
}
//渲染模版
name := "张三"
t.Execute(w, name)
}
func main() {
http.HandleFunc("/index", index)
err := http.ListenAndServe(":8080", nil)
if err != nil {
panic(err)
}
}
text/template和html/template的区别
html/template
针对的是需要返回HTML内容的场景,在模板渲染过程中会对一些有风险的内容进行转义,以此来防范跨站脚本攻击。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>xss</title>
</head>
<body>
{{ . }}
</body>
</html>
这个时候传入一段JS代码并使用html/template
去渲染该文件,会在页面上显示出转义后的JS内容。 <script>alert('123')</script>
这就是html/template
为我们做的事。
但是在某些场景下,我们如果相信用户输入的内容,不想转义的话,可以自行编写一个safe
函数,手动返回一个template.HTML
类型的内容。示例如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>xss</title>
</head>
<body>
{{ .str1 }}
{{/* "|" 管道符*/}}
{{ .str2 | safe }}
</body>
</html>
package main
import (
"html/template"
"net/http"
)
func index(w http.ResponseWriter, r *http.Request) {
//定义模版
//解析模版
t, err := template.New("index.tmpl").
Delims("{[", "]}").
ParseFiles("./index.tmpl")
if err != nil {
panic(err)
}
//渲染模版
name := "张三"
err = t.Execute(w, name)
if err != nil {
panic(err)
}
}
func xss(w http.ResponseWriter, r *http.Request) {
//定义模版
//解析模版前自定义一个函数
t, err := template.New("xss.tmpl").Funcs(template.FuncMap{
"safe": func(str string) template.HTML {
return template.HTML(str)
},
}).ParseFiles("./xss.tmpl")//解析模版
if err != nil {
panic(err)
}
//渲染模版
str1 := "<script></<script>>"
str2 := "<a href='qq.com'>QQ</a>"
t.Execute(w, map[string]string{
"str1": str1,
"str2": str2,
})
}
func main() {
http.HandleFunc("/index", index)
http.HandleFunc("/xss", xss)
err := http.ListenAndServe(":8080", nil)
if err != nil {
panic(err)
}
}
这样我们只需要在模板文件不需要转义的内容后面使用我们定义好的safe函数就可以了。
safe前:全部转义
safe后:只转义,我们想转义的
Gin渲染
单个模版渲染
模版解析
r.LoadHTMLFiles("template/index.tmpl")
模版渲染
Gin框架中使用LoadHTMLGlob()
或者LoadHTMLFiles()
方法进行HTML模板渲染。
r.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"tittle": "qq.com",
})
})
结果
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
//模版解析
r.LoadHTMLFiles("./templates/index.tmpl")
//模版渲染
r.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"tittle": "qq.com",
})
})
//运行
r.Run()
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>index</title>
</head>
<body>
{{ .tittle }}
</body>
</html>
多个模版渲染
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
//模版解析
//r.LoadHTMLFiles("./templates/index.tmpl")
r.LoadHTMLGlob("templates/**/*")
//模版渲染
r.GET("/posts/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
"title": "/posts/index:qq.com",
})
})
r.GET("/users/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
"title": "/users/index:qq.com",
})
})
//运行
r.Run()
}
posts/index.tmpl
{{define "posts/index.tmpl" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>index</title>
</head>
<body>
{{ .title }}
</body>
</html>
{{end}}
users/index.tmpl
{{define "users/index.tmpl" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>index</title>
</head>
<body>
{{ .title }}
</body>
</html>
{{end}}
自定义模版函数
//gin框架中添加自定义函数
r.SetFuncMap(template.FuncMap{
"safe": func(str string) template.HTML {
return template.HTML(str)
},
})
package main
import (
"github.com/gin-gonic/gin"
"html/template"
"net/http"
)
func main() {
r := gin.Default()
//gin框架中添加自定义函数
r.SetFuncMap(template.FuncMap{
"safe": func(str string) template.HTML {
return template.HTML(str)
},
})
//模版解析
//r.LoadHTMLFiles("./templates/index.tmpl")
r.LoadHTMLGlob("templates/**/*")
//模版渲染
r.GET("/posts/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
"title": "<a href='qq.com'>QQ</a>",
})
})
r.GET("/users/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
"title": "/users/index:qq.com",
})
})
//运行
r.Run()
}
<body>
{{ .title | safe }}
</body>
静态文件的处理
//加载静态文件
r.Static("/xxx", "./statics")
func main() {
r := gin.Default()
//加载静态文件
r.Static("/xxx", "./statics")
//gin框架中添加自定义函数
r.SetFuncMap(template.FuncMap{
"safe": func(str string) template.HTML {
return template.HTML(str)
},
})
//模版解析
//r.LoadHTMLFiles("./templates/index.tmpl")
r.LoadHTMLGlob("templates/**/*")
//模版渲染
r.GET("/posts/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
"title": "<a href='qq.com'>QQ</a>",
})
})
r.GET("/users/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
"title": "/users/index:qq.com",
})
})
//运行
r.Run()
}
css:
body {
background-color: antiquewhite;
}
js:
alert(123);
tmpl:
{{define "posts/index.tmpl" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="/xxx/index.css">
<title>index</title>
</head>
<body>
{{ .title | safe }}
<script src="/xxx/index.js"></script>
</body>
</html>
{{end}}
模版继承
Gin框架默认都是使用单模板,如果需要使用block template
功能,可以通过"github.com/gin-contrib/multitemplate"
库实现。
例如:
在网上下载一个前端模版
把解压之后index.html
改名为home.html
,放在gin_demo7/templates/posts
路径下,其他的放在gin_demo7/statics
路径下。
增加一个GET
即可。
//从网上下载的模版
r.GET("/home", func(c *gin.Context) {
c.HTML(http.StatusOK, "home.html", nil)
})
因为我们找静态资源是先去statics中寻找,但是我们设定了静态资源是在/xxx
下寻找的,所以去下载的index.html
中把一些css/
改为/xxx/css
。
就可以呈现出:(当然,有点前端部分是不会显示的,ps:我不会改)
Gin框架操作
返回JSON
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/json", func(c *gin.Context) {
data := map[string]interface{}{
"name": "张三",
"age": 18,
"gender": "男",
}
c.JSON(http.StatusOK, data)
})
r.Run()
}
不用切片,也可以使用gin.H
或者结构体
data := gin.H{
"name": "张三",
"age": 18,
"gender": "男",
}
--------------------------------------------
type msg struct {
Name string
Message string
Age int
}
r.GET("/another_json", func(c *gin.Context) {
data := msg{
Name: "张三",
Message: "hello go",
Age: 18,
}
c.JSON(http.StatusOK, data)
})
注意:结构体命名不能小写,比如Name不能写出name。
如果非得写小写,就使用tag即可。
type msg struct {
Name string `json:"name"`
Message string
Age int
}
获取querystring参数
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/web", func(c *gin.Context) {
//获取浏览器那边发请求携带的query string参数
name := c.Query("query")
c.JSON(http.StatusOK, gin.H{
"name": name,
})
})
r.Run()
}
设置默认值:
name := c.DefaultQuery("query", "李四")
GetQuery
name, ok := c.GetQuery("query")
if !ok {
name = "!ok"
}
&
英文AND符
func main() {
r := gin.Default()
r.GET("/web", func(c *gin.Context) {
//获取浏览器那边发请求携带的query string参数
//name := c.Query("query")
//name := c.DefaultQuery("query", "李四")
//name, ok := c.GetQuery("query")
name := c.Query("query")
age := c.Query("age")
c.JSON(http.StatusOK, gin.H{
"name": name,
"age": age,
})
})
r.Run()
}
获取form参数
PostForm
r.GET("/login", func(c *gin.Context) {
c.HTML(http.StatusOK, "login.html", nil)
})
r.POST("/login", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
c.HTML(http.StatusOK, "index.html", gin.H{
"Name": username,
"Password": password,
})
})
html
//login
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<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="username">
</div>
<div>
<label for="password">password:</label></input>
<input type="password" name="password" id="password">
</div>
<div>
<input type="submit" value="登录">
</div>
</form>
</body>
</html>
//index
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<p>Hello, {{ .Name }} !</p>
<p>密码:{{ .Password }} </p>
</body>
</html>
DefaultPostForm
我此时没有填密码,就返回一个空
r.GET("/login", func(c *gin.Context) {
c.HTML(http.StatusOK, "login.html", nil)
})
r.POST("/login", func(c *gin.Context) {
username := c.DefaultPostForm("username", "somebody")
password := c.DefaultPostForm("password", "***")
c.HTML(http.StatusOK, "index.html", gin.H{
"Name": username,
"Password": password,
})
})
我此时没有填密码,找不到对应表单,就使用默认的***
username := c.DefaultPostForm("username", "somebody")
password := c.DefaultPostForm("xxx", "***")
GetPostForm
r.POST("/login", func(c *gin.Context) {
username, ok := c.GetPostForm("username")
if !ok {
username = "李四"
}
password, ok := c.GetPostForm("password")
if !ok {
password = "20"
}
println(username, "-----------", password)
c.HTML(http.StatusOK, "index.html", gin.H{
"Name": username,
"Password": password,
})
})
获取URI路径参数
func main() {
r := gin.Default()
r.GET("/: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.Run()
}
防止路由匹配冲突:
//Param返回的都是string类型
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()
}
参数绑定
之前获取参数的方法
type User struct {
Name string
Password string
}
func main() {
r := gin.Default()
r.GET("/user", func(c *gin.Context) {
username := c.Query("username")
password := c.Query("password")
u1 := User{
Name: username,
Password: password,
}
fmt.Println(u1)
c.JSON(http.StatusOK, gin.H{
"message": "ok",
})
})
}
但是当请求中携带的量比较大,就需要使用gin框架中的绑定函数:ShouldBind
var u User
c.ShouldBind(&u)
注意:记得传指针,否则对形参的修改,不会影响到实参。
c.ShouldBind
通过反射获取结构体中的字段,所以结构体内变量首字母大写。
可以使用tag,让别人查反射时,可以一一对应起来。
type User struct {
Username string `form:"username"`
Password string `form:"password"`
}
func main() {
r := gin.Default()
r.GET("/user", func(c *gin.Context) {
//username := c.Query("username")
//password := c.Query("password")
//u1 := User{
// username: username,
// password: password,
//}
//fmt.Printf("%#v\n", u1)
var u User
err := c.ShouldBind(&u)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
} else {
c.JSON(http.StatusOK, gin.H{
"status": "ok",
})
}
})
r.Run()
}
我们也可以通过获取post方法的form表单
下面演示ApiPost:
再来处理最常处理的json格式的数据
type User struct {
Username string `form:"username" json:"username"`
Password string `form:"password" json:"password"`
}
r.POST("/json", func(c *gin.Context) {
var u User
err := c.ShouldBind(&u)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
} else {
c.JSON(http.StatusOK, gin.H{
"status": "ok",
})
}
fmt.Printf("%#v\n", u)
})
文件上传
单个文件上传
//SaveUploadedFile
func main() {
r := gin.Default()
r.LoadHTMLGlob("index.html")
r.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
})
r.POST("/upload", func(c *gin.Context) {
//从请求中读取文件
f, err := c.FormFile("f1")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
} else {
//把读取到的文件保存在服务器本地
//filepath := fmt.Sprintf("./%s", f.Filename)
filepath := path.Join("./", f.Filename)
_ = c.SaveUploadedFile(f, filepath)
c.JSON(http.StatusOK, gin.H{
"status": "ok",
})
}
})
r.Run()
}
// 或者使用MultipartMemory
// 处理multipart forms提交文件时默认的内存限制是32 MiB
// 可以通过下面的方式修改
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
服务器本地路径中就多了一个图片:
多个文件上传
func main() {
router := gin.Default()
// 处理multipart forms提交文件时默认的内存限制是32 MiB
// 可以通过下面的方式修改
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) {
// Multipart form
form, _ := c.MultipartForm()
files := form.File["file"]
for index, file := range files {
log.Println(file.Filename)
dst := fmt.Sprintf("C:/tmp/%s_%d", file.Filename, index)
// 上传文件到指定的目录
c.SaveUploadedFile(file, dst)
}
c.JSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("%d files uploaded!", len(files)),
})
})
router.Run()
}
重定向
- HTTP重定向
例1:
func main() {
r := gin.Default()
r.GET("/index", func(c *gin.Context) {
//c.JSON(http.StatusOK, gin.H{
// "status": "ok",
//})
c.Redirect(http.StatusMovedPermanently, "http://www.qq.com")
})
r.Run()
}
回车
- 路由重定向
例2:
func main() {
r := gin.Default()
r.GET("/index", func(c *gin.Context) {
//c.JSON(http.StatusOK, gin.H{
// "status": "ok",
//})
c.Redirect(http.StatusMovedPermanently, "http://www.qq.com")
})
r.GET("/a", func(c *gin.Context) {
//从a跳到b
c.Request.URL.Path = "/b" // 修改请求的URI
r.HandleContext(c) //继续后面的处理
})
r.GET("/b", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "你跳到了b",
})
})
r.Run()
}
路由
- 普通路由
func main() {
r := gin.Default()
r.GET("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "GET",
})
})
r.POST("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "POST",
})
})
r.DELETE("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "DELETE",
})
})
r.PUT("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "PUT",
})
})
r.Run()
}
- 匹配所有的请求方法:
r.Any("/user", func(c *gin.Context) {
switch c.Request.Method {
case http.MethodGet:
c.JSON(http.StatusOK, gin.H{
"method": "GET",
})
case http.MethodPost:
r.POST("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "POST",
})
})
case http.MethodDelete:
r.DELETE("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "DELETE",
})
})
//...
}
})
- 匹配失败
r.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{
"msg": "你访问的页面不存在",
})
})
- 路由组
假设一个视频网站的页面所需的路由:
r.GET("/video/a", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{ "msg": "/video/a" })
})
r.GET("/video/b", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{ "msg": "/video/b" })
})
r.GET("/video/c", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{ "msg": "/video/c" })
})
r.GET("/video/d", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{ "msg": "/video/d" })
})
//...
这样写,即繁琐又没有规矩,所以我们使用路由组:
//路由组
videoGroup := r.Group("/video")
{
videoGroup.GET("/a", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "/video/a"})
})
videoGroup.GET("/b", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "/video/b"})
})
videoGroup.GET("/c", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "/video/c"})
})
videoGroup.GET("/d", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "/video/d"})
})
}
Gin中间件
中间件
Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
GET函数
// GET is a shortcut for router.Handle("GET", path, handlers).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)
}
第二个参数必须是HandlerFunc
类型
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
所以说
// handlerFunc
func indexHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
func main() {
r := gin.Default()
r.GET("/index", indexHandler)
r.Run()
}
定义一个中间件m1,记录接口耗时
// handlerFunc
func indexHandler(c *gin.Context) {
fmt.Println("indexHandler ...")
c.JSON(http.StatusOK, gin.H{
"msg": "home",
})
}
// 中间件m1,统计耗时
func m1(c *gin.Context) {
fmt.Println("m1 start...")
// 计时
start := time.Now()
c.Next() // 调用该请求剩余的处理程序
//c.Abort() // 不调用该请求的剩余处理程序
cost := time.Since(start)
fmt.Println(cost)
fmt.Println("m1 end...")
}
func main() {
r := gin.Default()
r.GET("/home", m1, indexHandler)
r.Run()
}
中间件调用流程:
m1:start:time.Now() -> m1:c.Next() -> indexHandler() -> m1:cost:time.Since()
注册中间件
- 全局注册中间接函数
例1:
func main() {
r := gin.Default()
r.Use(m1) //全局注册中间件函数m1
r.GET("/home", m1, indexHandler)
r.GET("/shop", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "shop",
})
})
r.GET("/user", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "user",
})
})
r.Run()
}
分别访问"/shop"
、"/user"
例2:
// 中间件m1,统计耗时
func m1(c *gin.Context) {
fmt.Println("m1 start...")
// 计时
start := time.Now()
c.Next() // 调用该请求剩余的处理程序
//c.Abort() // 不调用该请求的剩余处理程序
cost := time.Since(start)
fmt.Println(cost)
fmt.Println("m1 end...")
}
// 中间件m2,统计耗时
func m2(c *gin.Context) {
fmt.Println("m2 start...")
c.Next()
fmt.Println("m2 end...")
}
func main() {
r := gin.Default()
r.Use(m1, m2) //全局注册中间件函数m1/m2
r.GET("/home", m1, indexHandler)
r.GET("/shop", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "shop",
})
})
r.GET("/user", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "user",
})
})
r.Run()
}
执行流程:main -> m1:Next() -> m2:Next() -> shop() -> m2 end.. -> m1 end..
abort()
// 中间件m2,统计耗时
func m2(c *gin.Context) {
fmt.Println("m2 start...")
//c.Next()
c.Abort() //不调用该请求的剩余处理程序
fmt.Println("m2 end...")
}
main -> m1:Next() -> m2:Abort -> m2 end.. -> m1 end..
如果此时m2也不想执行,在Abort()后面加上return即可
// 中间件m2
func m2(c *gin.Context) {
fmt.Println("m2 start...")
//c.Next()
c.Abort() //不调用该请求的剩余处理程序
return
fmt.Println("m2 end...")
}
- 为某个路由单独注册
r.GET("/user", m1, func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "user",
})
})
- 为路由组注册中间件
写法1
shopGroup := r.Group("/shop", m1)
{
shopGroup.GET("/index", func(c *gin.Context) {...})
...
}
写法2
shopGroup := r.Group("/shop")
shopGroup.Use(m1)
{
shopGroup.GET("/index", func(c *gin.Context) {...})
...
}
中间件一般用法
写法:
// doCheck开关控制
func outMiddleWare(doCheck bool) gin.HandlerFunc {
//连接数据库
//或者其他工作
return func(c *gin.Context) {
if doCheck {
//是否登录
//if 是登录用户
//c.Next
//else
//c.Abort
} else {
c.Next()
}
}
}
func main() {
r := gin.Default()
r.Use(m1, m2, outMiddleWare(false)) //全局注册中间件函数m1/m2
r.GET("/home", m1, indexHandler)
r.GET("/shop", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "shop",
})
})
r.GET("/user", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "user",
})
})
r.Run()
}
用法:
从一个中间件设置信息,在另一个中间件拿到这个信息
c.Set("name", "张三")
///
name, ok := c.Get("name")
if !ok {
name = "匿名用户"
}
// handlerFunc
func indexHandler(c *gin.Context) {
fmt.Println("indexHandler ...")
name, ok := c.Get("name")
if !ok {
name = "匿名用户"
}
c.JSON(http.StatusOK, gin.H{
"name": name,
})
}
// 中间件m1,统计耗时
func m1(c *gin.Context) {
fmt.Println("m1 start...")
// 计时
start := time.Now()
c.Next() // 调用该请求剩余的处理程序
//c.Abort() // 不调用该请求的剩余处理程序
cost := time.Since(start)
fmt.Println(cost)
fmt.Println("m1 end...")
}
// 中间件m2
func m2(c *gin.Context) {
fmt.Println("m2 start...")
c.Set("name", "张三")
c.Next()
//c.Abort() //不调用该请求的剩余处理程序
return
fmt.Println("m2 end...")
}
// doCheck开关控制
func outMiddleWare(doCheck bool) gin.HandlerFunc {
//连接数据库
//或者其他工作
return func(c *gin.Context) {
if doCheck {
//是否登录
//if 是登录用户
//c.Next
//else
//c.Abort
} else {
c.Next()
}
}
}
func main() {
r := gin.Default()
r.Use(m1, m2, outMiddleWare(false)) //全局注册中间件函数m1/m2
r.GET("/home", m1, indexHandler)
r.Run()
}
中间件注意事项
gin默认中间件
gin.Default()
默认使用了Logger
和Recovery
中间件
Logger
中间件将日志写入gin.DefaultWriter
,即使配置了GIN_MODE=release
。Recovery
中间件会recover任何panic
。如果有panic的话,会写入500响应码。
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default(opts ...OptionFunc) *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine.With(opts...)
}
如果想使用一个无中间件的路由:
r := gin.New()
gin中间件中使用goroutine
当在中间件或handler
中启动新的goroutine
时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy()
)。(并发时不安全)