GIN入门至实战
1.导入第三方包地址
github.com/gin-gonic/gin
2.main函数
func main() { //ctrl+c 停止运行
//1.创建 一个 默认的 路由引擎
r := gin.Default()
//2.配置路由 请求第一个参数地址 触发回调函数
r.GET("/", func(c *gin.Context) {
c.String(200, "值:%v", "你好GIN") //200表示成功 给浏览器返回内容
})
r.GET("/inews", func(c *gin.Context) {
c.String(200, "我是新闻本身") //200表示成功 给浏览器返回内容
})
//3.启动一个web服务 r.Run() 启动http服务,默认在 0.0.0.0:8080启动服务
r.Run()
执行程序后:在浏览器 输入 http://localhost:8080/ 显示值:你好GIN
输入 http://localhost:8080/inews 显示值:我是新闻本身
}
也可以指定端口号
r.Run(":8050")
在浏览器输入 http://localhost:8050/ 显示值:你好GIN
输入 http://localhost:8050/inews 显示值:我是新闻本身
3. GOLANG程序的热加载
所谓热加载就是当我们对代码进行修改时,程序能够自动重新加载并执行。
go get github.com/pilu/fresh
#终端运行
fresh
或
go install github.com/pilu/fresh (理由好像是17版本后 不用go get 推荐用 go install)
这样子在Gopath文件下的 bin的文件夹下 生成了一个 fresh.exe
将该fresh.exe 复制至 你的项目目录下 与 main.go 在同一等级
便可在终端执行 fresh 或 .\fresh 或 绝对路径 D:\GoProject\Web\fresh.exe
或
go get -u github.com/codegangsta/gin
#终端运行
gin run main.go
4.post请求 put请求 delete请求同理
浏览器中输入的都是 get 请求
post 请求 我们可以用postman 进行模拟
r.POST("/add", func(c *gin.Context) {
c.String(200, "我是post请求返回的数据") //200表示成功 给浏览器返回内容
})
在 postman 中选择 post请求 然后地址为 localhost:8050/add
得到结果
5.RESTful架构中,每个网址代表一个资源,不同请求方式表示执行不同的操作
GET(SELECT) 从服务器取出资源
POST(CREATE) 从服务器新建一个资源
PUT(UPDATE) 在服务器更新资源
DELETE(DELETE) 从服务器删除资源
6.c.string(),C.JSON(),C.JSONP().C.XML(),C.HTML
1.返回json数据
main函数
//map生成json
r.GET("/json1", func(c *gin.Context) {
c.JSON(http.StatusOK, map[string]interface{}{
"success": true,
"msg": "你好 json",
})
})
或
//map 生成 json
r.GET("/json2", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{ //type H map[string]any
"success": true,
"msg": "你好 jsonA",
})
})
type Article struct {
Title string `json:"title"` //返回 title 而不是 Title
Desc string `json:"desc"`
Content string `json:"content"`
}
// 结构体 生成 json
r.GET("/json3", func(c *gin.Context) {
a := Article{
Title: "我是标题",
Desc: "我是描述",
Content: "我是内容",
}
c.JSON(http.StatusOK, a)
})
浏览器输入:http://localhost:8080/json1 2 3 即可得到
2.返回JSONP
jsonp 会把数据 放入 至 回调函数 里 主要用来解决跨域问题
JSONP在浏览器响应内容与json一致
输入 http://localhost:8080/jsonp
结果为 {“title”:“我是标题”,“desc”:“我是描述”,“content”:“我是内容–JSONP”}
但如果输入 http://localhost:8080/jsonp?callback=xxx
返回却是 xxx({“title”:“我是标题”,“desc”:“我是描述”,“content”:“我是内容–JSONP”});
// 响应Jsonp请求
//http://localhost:8080/jsonp?callback=xxx
//xxx({"title":"我是标题","desc":"我是描述","content":"我是内容--JSONP"});
r.GET("/jsonp", func(c *gin.Context) {
a := Article{
Title: "我是标题",
Desc: "我是描述",
Content: "我是内容--JSONP",
}
c.JSONP(http.StatusOK, a)
})
3.返回XML数据
r.GET("/xml", func(c *gin.Context) {
c.XML(http.StatusOK, gin.H{
"success": true,
"msg": "你好 XML",
})
})
4.渲染模板
1. 先 建立文件夹templates 和文件 取名 goods.html news.html
1.news.html
输入 html:5 回车 可得
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h2>我是一个新闻页面</h2>
<H3>{{.Title}}</H3> <!-- 业务逻辑里的 也就是main.go里的 {{.XXX}} 调用方式-->
</body>
</html>
2.main函数
r := gin.Default()
//配置模板的文件
r.LoadHTMLGlob("templates/*") //加载templates里所有模板
//渲染模板
r.GET("/news", func(c *gin.Context) {
// 注意:r.LoadHTMLGlob("templates/*") 添加才能加载模板
c.HTML(http.StatusOK, "news.html", gin.H{
"Title": "我是新闻后台的数据",
})
})
1.goods.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h2>我是一个商品页面</h2> <!-- -->
<h3>{{.Good_Title}}</h3> <!-- 业务逻辑里的 也就是main.go里的 {{.XXX}} 调用方式-->
</body>
</html>
- main函数
r.GET("/goods", func(c *gin.Context) {
c.HTML(http.StatusOK, "goods.html", gin.H{ //第二个参数为 html文件名
"Good_Title": "我是商品后台数据",
})
})
------------------------------------------------------------------------------------------------------
7. HTML模板渲染以及模板语法
1.模板文件有好几层
1.先看结构
例 1 gin文件中 图片分层1
用这个配置模板
r.LoadHTMLGlob("templates/*") // *代表该文件夹下所有
例 2 gin文件中 图片分层2
用这个配置模板
r.LoadHTMLGlob("templates/**/*"
若 后续还有依次类推 增加 **(表示一层目录)
如
r.LoadHTMLGlob("templates/**/**/*"
2.html文件
因为有多个名字一样的html文件,故给这些html文件起名
如 图片分层2中的 admin下的index.html文件 news.html 这样子起名
在该html文件中
{{define "admin/index.html"}}中间为内容{{end}}
{{define "admin/news.html"}}中间为内容{{end}}
3.main.go
//admin
r.GET("/admin", func(c *gin.Context) {
c.HTML(http.StatusOK, "admin/index.html", gin.H{//"admin/index.html"这里对应起的名字
"Title": "admin首页",
})
})
//news
r.GET("/admin/news", func(c *gin.Context) {
News := &Article{
Title: "后台新闻标题",
Content: "后台新闻内容",
}
c.HTML(http.StatusOK, "admin/news.html", gin.H{
"Page": "新闻页面",
"News": News,
})
})
8.模板语法
1.变量
<!--定义变量-->
{{$tTTT:=.Title}}
<!--输出-->
<H3>{{$tTTT}}</H3>
2.比较函数
eq 如果 a1==a2 则返回真
ne 如果 a1!=a2 则返回真
lt 如果 a1< a2 则返回真
le 如果 a1<=a2 则返回真
gt 如果 a1>a2 则返回真
ge 如果 a1>=a2 则返回真
3.条件判断
<!--条件判断-->
<!--如果 .Score的值 大于等于 60 输出 及格 反之输出不及格 -->
{{if ge .Score 60}}
<P>及格</P>
{{else}}
<p>不及格</p>
{{end}}
<!--如果 .Score的值 大于 90 输出 优秀 -->
{{if gt .Score 90}}
<P>优秀</P>
<!--如果 .Score的值 大于 80 输出 良好 -->
{{else if gt .Score 80}}
<p>良好</p>
{{else if gt .Score 60}}
<p>合格</p>
{{else}}
<p>不及格 回家吧</p>
{{end}}
4.range循环遍历数据
1."Hobby": []string{"吃饭", "睡觉", "写代码"},
<!--循环遍历-->
<ul>
{{range $key,$value:=.Hobby}}
<li>{{$key}}-----{{$value}}</li>
{{end}}
</ul>
"News": []interface{}{
&Article{
Title: "后台新闻标题1111",
Content: "后台新闻内容1111",
},
&Article{
Title: "后台新闻标题2222",
Content: "后台新闻内容2222",
},
},
<ul>
{{range $key,$value:=.News}}
<li>{{$key}}----{{$value.Title}}----{{$value.Content}}</li>
{{end}}
</ul>
3.没有数据的话输出定义的值
range else end
"Testslice": []string{}, //空切片
<ul>
{{range $key,$value:=.Testslice}}
<li>{{$key}}------{{$value}}</li>
{{else}}
<li>没有值欸</li>
{{end}}
</ul>
输出 没有值欸
5.with解构结构体
"newsstruct": Article{
Title: "后台新闻标题1111",
Content: "后台新闻内容1111",
},
<!--表示把 .newsstruct 赋值给一个 . -->
{{with .newsstruct}}
<!--直接调用-->
{{.Title}}
{{.Content}}
{{end}}
6.自定义函数
//时间戳转换成日期
func UnixToTime(timestamp int) string {
t := time.Unix(int64(timestamp), 0)
return t.Format("2006-01-02 15:04:05")
}
func PRINT(str1, str2 string) string {
return str1 + "***" + str2
}
//自定义模板函数 注意要把这个函数放在加载模板前
r.SetFuncMap(template.FuncMap{
"UnixToTime": UnixToTime,
"PRINT": PRINT,
})
{{UnixToTime .date}}
{{PRINT .Title .NAME}}
输出
2022-08-26 11:30:37
admin***ocean
7.嵌套template
在templates文件下 创建 public文件夹 并创建page_header.html
文件夹如图
page_header.html
<!--相当于给模板定义一个名字 define end 成对出现-->
{{define "public/page_header.html"}}
<style>
h1{
background: #000;
color: #fff;
text-align: center;
}
</style>
<h1>
<!--虽然是公共模板,但是{{.Title}}所调用的值为后台也就是main.go里面所设置的 因此可以不同-->
我是一个公共标题----{{.Title}}
</h1>
{{end}}
在admin/index.html 中 嵌套 page_header.html (意思为把page_header页面加入至admin/index.html页面)
在admin/index.html 中
{{template "public/page_header.html" .}}
结果生成图
9.静态文件服务
· 1.创建目录文件
· 2.base.css
h1{
background: #000;
color: #fff;
text-align: center;
}
h2{
color:red;
}
· 3.main.go
//配置静态Web目录, 第一个参数表示路由(任意名称) 第二个参数表示映射的目录
//意思访问http://localhost:8080/stat后 就进入了 static这个文件夹
r.Static("/stat", "./static") // ./是 当前目录的意思
如果要在浏览器查看base.css文件 输入
http://localhost:8080/stat/css/base.css
· 4.admin.index.html
<!--表示引用base.css文件里的代码-->
<!--下面的 stat 与 r.Static("/stat", "./static") 里的 stat 对应一致 -->
<link REL="stylesheet" href="/stat/css/base.css">
<!--添加图片 路由 stat 表示 进入static这个文件夹中 -->
<img src="/stat/images/0013.png" ALT="">
10.GET POST 传值
1.获取 get 传的值
r.GET("/", func(c *gin.Context) {
username := c.Query("username") //获取 url中 query参数为 username 的值
age := c.Query("age")
page := c.DefaultQuery("page", "1") //page有值就传值 没值传1
c.JSON(http.StatusOK, gin.H{
"username": username,
"age": age,
"page": page,
})
})
浏览器输入 http://localhost:8080/?username=zhangsan&age=20
结果如下:
r.GET("/article", func(c *gin.Context) {
id := c.DefaultQuery("id", "1")
c.JSON(http.StatusOK, gin.H{
"id": id,
"msg": "新闻详情",
})
})
浏览器输入 http://localhost:8080/article?id=5
结果如下
2.获取 post 传的值
1.先创建user.html文件
post传值目录
2.编写user.html文件
<body>
<!--指定了 /doAddUser路由 来处理被提交的表单-->
<form action="/doAddUser" method="post">
用户名:<input type="text" name="username"/> <br><br>
密码:<input type="password" name="password"/><br><br>
年龄:<input type="text" name="age"/><br><br>
<input type="submit" value="提交">
</form>
</body>
3.编写go文件
// POST演示 如何获取POST传来的值
// 1.当访问user时 加载default/user.html
r.GET("/user", func(c *gin.Context) {
c.HTML(http.StatusOK, "default/user.html", gin.H{}) //通过html渲染页面
})
// 2.当点击 提交按钮 需要给 /doAddUser 进行一个 POST数据
//获取表单POST过来的数据
r.POST("/doAddUser", func(c *gin.Context) {
username := c.PostForm("username") //获取表单中参数为username传过来的值
password := c.PostForm("password") //获取表单中参数为password传过来的值
age := c.DefaultPostForm("age", "20")
c.JSON(http.StatusOK, gin.H{ //传入 map类型数据 转换为 json
"username": username,
"password": password,
"age": age,
})
})
4.执行
1.访问 http://localhost:8080/user
2.点击提交后 网址变为 http://localhost:8080/doAddUser
3.获取 GET POST 传递的数据 绑定到结构体
定义结构体
type UserInfo struct {
Username string `json:"username" form:"username"` //获取表单数据 传入至 结构体
Password string `json:"password" form:"password"`
}
1.get
// 1.GET 至 结构体
r.GET("/getUser", func(c *gin.Context) {
user := &UserInfo{}
if err := c.ShouldBind(&user); err == nil {
fmt.Printf("%#v", user) //终端 控制台 输出可以看见
c.JSON(http.StatusOK, user)
} else {
c.JSON(http.StatusOK, gin.H{
"err": err.Error(),
})
}
})
访问网址:http://localhost:8080/getUser?username=zhangsan&password=1111
结果如下:
2.post
userp.html
<body>
<!--指定了 /doAddUser2路由 来处理被提交的表单-->
<form action="/doAddUser2" method="post">
用户名:<input type="text" name="username"/> <br><br>
密码:<input type="password" name="password"/><br><br>
<input type="submit" value="提交">
</form>
</body>
go文件
//2.post 至 结构体
//
r.GET("/userp", func(c *gin.Context) {
c.HTML(http.StatusOK, "default/userp.html", gin.H{}) //通过html渲染页面
})
r.POST("/doAddUser2", func(c *gin.Context) {
user := &UserInfo{}
if err := c.ShouldBind(&user); err == nil {
fmt.Printf("%#v", user) //终端 控制台 输出可以看见
c.JSON(http.StatusOK, user)
} else {
c.JSON(http.StatusBadRequest, gin.H{
"err": err.Error(),
})
}
})
访问网址 http://localhost:8080/userp
执行如下 :
点击提交后
4.获取post传来的 xml数据 放入至结构体
1.先定义结构体
type Article struct {
Title string `json:"title" xml:"title"`
Content string `json:"content "xml:"content"`
}
2.编写go文件
// 获取 接收 Post 过来的 Xml 数据
r.POST("/xml", func(c *gin.Context) {
article := &Article{}
//通过 POST 发送到服务器的数据存储在 HTTP 请求的请求主体中
XmlSliceData, _ := c.GetRawData() //从 c.Request.Body 读取请求数据
if err := xml.Unmarshal(XmlSliceData, &article); err == nil { //将[]byte数据转化为结构体
c.JSON(http.StatusOK, article)
} else {
c.JSON(http.StatusBadRequest, gin.H{
"err": err.Error(),
})
}
})
3.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<article>
<content type="string">我是张三</content>
<title type="string">张三</title>
</article>
4.模拟postxml数据
5.动态路由传值
go文件
// 动态路由传值
// list/123 list/456
r.GET("/list/:cid", func(c *gin.Context) {
cid := c.Param("cid") //获得动态值
c.String(200, "%v", cid)
})
访问 http://localhost:8080/list/123
结果如下:
----------------------------------------------------------------------------
11. 路由分组
1.路由分组
//main.go
//配置路由
//路由分组
defaultRouters := r.Group("/")
{ // url: http://localhost:8080/
defaultRouters.GET("/", func (c *gin.Context) {
c.String(200, "首页")
})
// url: http://localhost:8080/news
defaultRouters.GET("/news", func (c *gin.Context) {
c.String(200, "新闻")
})
}
apiRouters := r.Group("/api") //api组
{
//url:http://localhost:8080/api/
apiRouters.GET("/", func (c *gin.Context) {
c.String(200, "我是一个api接口")
})
//url:http://localhost:8080/api/userlist
apiRouters.GET("/userlist", func (c *gin.Context) {
c.String(200, "我是一个api接口-userlist")
})
//url:http://localhost:8080/api/plist
apiRouters.GET("/plist", func (c *gin.Context) {
c.String(200, "我是一个api接口-plist")
})
}
adminRouters := r.Group("/admin") //admin组
{
adminRouters.GET("/", func (c *gin.Context) {
c.String(200, "后台首页")
})
adminRouters.GET("/user", func (c *gin.Context) {
c.String(200, "用户列表")
})
adminRouters.GET("/article", func (c *gin.Context) {
c.String(200, "新闻列表")
})
}
2.分组路由 抽离 成 单个文件
1.先建立文件夹和文件
路由分组图片
- eg adminRouters.go
func AdminRoutersInit(r *gin.Engine) {
adminRouters := r.Group("/admin") //admin组
{
adminRouters.GET("/", func (c *gin.Context) {
c.String(200, "后台首页")
})
adminRouters.GET("/user", func (c *gin.Context) {
c.String(200, "用户列表")
})
adminRouters.GET("/article", func (c *gin.Context) {
c.String(200, "新闻列表")
})
}
}
main.go主函数中
//初始化配置路由
import "Web4/routers" //导入包
routers.AdminRoutersInit(r)
其他类似
12. 自定义控制器
创建控制器文件夹和分组图片
1.控制器 处理 业务逻辑
- eg indexController.go
//admin包
//这样子是没法继承的
func UserIndex(c *gin.Context) {
c.String(200, "admin首页---")
}
- eg adminRouters.go
//而不是 admin.UserIndex()原因: 加()表示 执行方法 如果只有 admin.UserIndex 表示 注册方法 没有执行
//当访问路由时 会执行
import "Web4/controllers/admin"
adminRouters.GET("/", admin.UserIndex)
2.添加结构体
articleController.go
type ArticleController struct {//结构体
}
func (con ArticleController) Index(c *gin.Context) {//为 ArticleController类型 添加方法
c.String(200, "文章---1")
}
func (con ArticleController) Add(c *gin.Context) {
c.String(200, "文章列表--add")
}
func (con ArticleController) Edit(c *gin.Context) {
c.String(200, "文章列表-edit")
}
adminRouters.go
func AdminRoutersInit(r *gin.Engine) {
adminRouters := r.Group("/admin")
{
// 而不是 admin.IndexController{}.Index()原因: 加()表示 执行方法
//如果只有 admin.IndexController{}.Index 表示 注册方法 没有执行
// 当访问路由时 会执行
adminRouters.GET("/", admin.IndexController{}.Index)
//admin.UserController{} 表示 实例化结构体 再 .User 表示调用User方法
adminRouters.GET("/user", admin.UserController{}.User)
adminRouters.GET("/user/add", admin.UserController{}.Add)
adminRouters.GET("/user/edit", admin.UserController{}.Edit)
adminRouters.GET("/article", admin.ArticleController{}.Index)
adminRouters.GET("/article/add", admin.ArticleController{}.Add)
adminRouters.GET("/article/edit", admin.ArticleController{}.Edit)
}
3.继承方法 控制器继承
控制器继承图
1.在 controllers/admin下 创建 baseController.go
baseController.go
type BaseController struct {
}
func (con BaseController) Success(c *gin.Context) {
c.String(200, "成功")
}
func (con BaseController) Defeat(c *gin.Context) {
c.String(200, "失败")
}
articleController.go
type ArticleController struct {
// 匿名字段
BaseController // 有个 ArticleController,继承了 BaseController 字段,成员和方法都继承了
}
func (con ArticleController) Index(c *gin.Context) {
c.String(200, "文章---1")
con.Success(c) //BaseController类型的方法
}
func (con ArticleController) Add(c *gin.Context) {
c.String(200, "文章列表--add")
con.Defeat(c)// BaseController类型的方法
}
func (con ArticleController) Edit(c *gin.Context) {
c.String(200, "文章列表-edit")
}
13.Gin中间件
Gin框架允许开发者在处理请求的过程中,加入用户自己的 钩子(Hook)函数。这个钩子函数就叫 中间件,中间件适合处理
一些公共的业务逻辑,比如登陆认证,权限校验、数据分页,记录日志,耗时统计等通俗的讲:中间件 就是 匹配路由前 和 匹配路由完成后 执行的一系列操作
1.匹配路由前 打印
r.GET("/news", func(c *gin.Context) {
fmt.Println("AAA")
},
func(c *gin.Context) {
c.String(http.StatusOK, "gin首页")
}
)
路由匹配前打印 图片
我们可以在Terminal控制器看见 打印了 AAA
2.中间件 抽离出来 设置函数 InitMiddleWare
- main.go
func InitMiddleWare(c *gin.Context) {
fmt.Println("123456")
}
//InitMiddleWare就是 中间件
//先执行中间件,触发中间件方法,在执行回调函数
r.GET("/", InitMiddleWare, func(c *gin.Context) { //第二个参数可以为 多个回调函数
c.String(http.StatusOK, "gin首页")
})
中间件设置函数 打印图
3.next()函数 调用 该请求的 剩余处理程序
//next()函数 实现 统计程序执行时间
func InitMiddleWare(c *gin.Context) {
fmt.Println("111111111111111") // 1.
//调用 该请求的 剩余处理程序
c.Next()
fmt.Println("222222222222222") // 3.
}
r.GET("/", InitMiddleWare, func(c *gin.Context) { //第二个参数可以为 多个回调函数
fmt.Println("这是一个首页") // 2.
c.String(http.StatusOK, "gin首页")
})
执行顺序 :进入InitMiddleWare函数,执行1 ,进入 c.Next() 执行func(c *gin.Context)后 , 返回 执行3
执行结果 :next函数图
1.next函数实现实例 调用 该请求的 剩余处理程序
func InitMiddleWare(c *gin.Context) {
start := time.Now().UnixNano() //获取执行开始前的时间
fmt.Println("111111111")
c.Next()//调用 该请求的 剩余处理程序
fmt.Println("222222222222222")
end := time.Now().UnixNano() //获取执行完成后的时间
fmt.Println("剩余时间:", end-start) //程序执行时间
}
//InitMiddleWare就是 中间件
r.GET("/", InitMiddleWare, func(c *gin.Context) { //第二个参数可以为 多个回调函数
fmt.Println("这是一个首页")
time.Sleep(time.Second)
c.String(http.StatusOK, "gin首页")
})
next函数实例图
4.Abort()函数 终止调用该请求的剩余处理程序
c.Abort()//表示终止调用该请求的剩余处理程序 但是 中间件的内容还是会执行
func InitMiddleWare(c *gin.Context) {
start := time.Now().UnixNano() //获取执行开始前的时间
fmt.Println("111111111")
c.Abort() //终止调用该请求的剩余处理程序
fmt.Println("222222222222222")
end := time.Now().UnixNano() //获取执行完成后的时间
fmt.Println("剩余时间:", end-start) //程序执行时间
}
//InitMiddleWare就是 中间件
r.GET("/", InitMiddleWare, func(c *gin.Context) { //第二个参数可以为 多个回调函数
fmt.Println("这是一个首页")
time.Sleep(time.Second)
c.String(http.StatusOK, "gin首页")
})
从图中我们可以得到 Terminal中 “这是一个首页” 没有打印,浏览器中"gin首页"也没加载出来
意味着 func(c *gin.Context)没有执行,但是Abort()函数后的"222222222222222"却打印了出来
abort函数()执行结果图
5.多个中间件执行顺序 先进后出
1.设置两个函数
func InitMiddleWareone(c *gin.Context) {
fmt.Println("11--One")// 1.
//调用 该请求的 剩余处理程序
c.Next()// 2.
fmt.Println("22--One")//7.
}
func InitMiddleWaretwo(c *gin.Context) {
fmt.Println("11--Two")// 3.
//调用 该请求的 剩余处理程序
c.Next()// 4.
fmt.Println("22--Two")//6.
}
r.GET("/", InitMiddleWareone, InitMiddleWaretwo, func(c *gin.Context) {
fmt.Println("我是love首页")//5.
c.String(200, "LOVE")
})
所以执行顺序为 next()函数前 先进先出(语句执行顺序),执行完最后一个回调函数后,
开始执行next()函数, next()函数后 先进后出
多个中间件执行顺序图
6.全局中间件
所有路由均可使用
//全局中间件
r.Use(InitMiddleWareone, InitMiddleWaretwo)
7.路由分组中配置中间件
先创建middlewares文件夹后 创建init.go
中间件抽离目录图
init.go
package middlewares
func InitMiddleWares(c *gin.Context) {
fmt.Println(time.Now())
}
1.第一种调用方式
func ApiRoutersInit(r *gin.Engine) {
apiRouters := r.Group("/api")
//middlewares.InitMiddleWares中间件
apiRouters.Use(middlewares.InitMiddleWares)//路由分组中配置中间件
{
apiRouters.GET("/", api.ApiController{}.Index)
apiRouters.GET("/userlist", api.ApiController{}.Userlist)
apiRouters.GET("/plist", api.ApiController{}.Plist)
}
}
2.第二种调用方式
func AdminRoutersInit(r *gin.Engine) {
//middlewares.InitMiddleWares中间件
adminRouters := r.Group("/admin", middlewares.InitMiddleWares)//路由分组中配置中间件
{
// 而不是 admin.IndexController{}.Index()原因: 加()表示 执行方法
//如果只有 admin.IndexController{}.Index 表示 注册方法 没有执行
// 当访问路由时 会执行
adminRouters.GET("/", admin.IndexController{}.Index)
//admin.UserController{}表示 实例化结构体 再 .User 表示调用方法
adminRouters.GET("/user", admin.UserController{}.User)
adminRouters.GET("/user/add", admin.UserController{}.Add)
adminRouters.GET("/user/edit", admin.UserController{}.Edit)
adminRouters.GET("/article", admin.ArticleController{}.Index)
adminRouters.GET("/article/add", admin.ArticleController{}.Add)
adminRouters.GET("/article/edit", admin.ArticleController{}.Edit)
}
}
调用方式图 结果一致
8.中间件 和 对应控制器 之间共享数据
init.go
c.Set("username", "张三")
apicontroller.go
func (con ApiController) Plist(c *gin.Context) {
c.String(200, "我是一个api接口-plist3")
username, _ := c.Get("username")
fmt.Println(username)//终端输出
v, ok := username.(string) //类型断言
if ok { //true or false
c.String(200, "PLIST--"+v) //输出至页面
} else {
c.String(200, "\nERROR!!!")
}
}
共享数据图
9.中间件事项
1.GIN.Default()自带两个中间件
如果不想使用上面两个默认的中间件 可以使用 gin.New()新建一个没有任何中间件的路由
2.gin中间件中使用goroutine
当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context)
必须使用其只读副本(c.Copy()) cCP=c.Copy() 后 cCP.方法
init.go
func InitMiddleWares(c *gin.Context) {
//判断用户登录
fmt.Println(time.Now())
c.Set("username", "张三")
//定义一个goroutine统计日志
cCP := c.Copy()
go func() {
time.Sleep(3 * time.Second)
fmt.Println("Done! in path" + cCP.Request.URL.Path)
}()
}
中间件使用goroutine
14.Gin中自定义Model
复制项目用来创建新项目时 为避免引入的包变成原来的项目下的包名 应删除原有mod 建立新mod
mvc m-models v-templates1(视图) c-controllers
14.1 关于Model
若应用简单,可以在Controller中处理常见的业务逻辑。但如果我们有一个功能想在多个控制器、或多个模板里复用的话,
我们可以把公共的功能单独抽取出来作为一个模块(Model)。Model是逐步抽象的过程,一般会在Model里面封装一些
公共的方法让不同的Controller使用。
14.2 案例 时间戳
- 建立 models 文件夹 ,在此文件夹下 创建tools.go
tools.go
//时间戳转换成日期
func UnixToTime(timestamp int) string {
t := time.Unix(int64(timestamp), 0)
return t.Format("2006-01-02 15:04:05")
}
main.go
import "Godemo03/models"
r.SetFuncMap(template.FuncMap{
"UnixToTime": models.UnixToTime,
})
defaultController.go
func (con DefaultController) Index(c *gin.Context) {
fmt.Println(models.UnixToTime(1662007020)) //terminal 里展现
c.HTML(200, "default/index.html", gin.H{
"msg": "我有一个msg****",
"time": 1662006689,
})
}
default/index.html
<body>
<h1>我是一个首页</h1>
<br>
<H2>{{.msg}}</H2>
<br>
{{UnixToTime .time}}
时间戳实例
15.文件上传
15.1 单文件上传
在 templates1 下的 admin文件夹中 建立useradd.html
单文件图
useradd.html
<!--项目中实现文件上传 需要在上传文件的form表单上面需要加入 enctype="multipart/form-data"-->
<body>
<H2>演示文件上传</H2>
<form action="/admin/user/DoUpload" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username" placeholder="用户名">
<br>
<br>
<!--图片-->
头 像:<input type="file" name="face" >
<br> <br>
<input type="submit" value="提交">
</form>
</body>
adminrouter.go
adminRouters.GET("/user/add", admin.UserController{}.Add)
//使用post原因是useradd.html里 method="post"
adminRouters.POST("/user/DoUpload", admin.UserController{}.DoUpload)
usercontroller.go
func (con UserController) Add(c *gin.Context) {
c.HTML(http.StatusOK, "admin/useradd.html", gin.H{})
}
func (con UserController) DoUpload(c *gin.Context) {
username := c.PostForm("username") //获取post过来的username
file, err := c.FormFile("face") //获取上传的文件 , "face" 对应 html里的 "face"
// file.Filename表示获取文件名称,
//若文件名称(file.Filename)为 aaa.jpg 则拼接路径为 ./static1/upload/aaa.jpg
dst := path.Join("./static1/upload", file.Filename) //路径拼接
if err == nil {
// 上传文件至指定的完整文件路径
c.SaveUploadedFile(file, dst)
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"username": username,
"dst": dst,
})
}
执行过程:先访问 http://localhost:8080/admin/user/add
输入信息后点击提交 跳转至页面 http://localhost:8080/admin/user/DoUpload 显示详情
15.2 多文件上传 不同名字的多个文件
multifile.html
<!--项目中实现文件上传 需要在上传文件的form表单上面需要加入 enctype="multipart/form-data"-->
<body>
<H2>多文件上传</H2>
<form action="/admin/user/Multifile" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username" placeholder="用户名">
<br>
<br>
<!-- 不同名字 face1 face2-->
头 像1:<input type="file" name="face1" >
<br> <br>
头 像2:<input type="file" name="face2" >
<br> <br>
<input type="submit" value="提交">
</form>
</body>
adminrouter.go
adminRouters.GET("/user/edit", admin.UserController{}.Edit)
adminRouters.POST("/user/Multifile", admin.UserController{}.Multifile)
usercontroller.go
func (con UserController) Edit(c *gin.Context) {
c.HTML(http.StatusOK, "admin/multifile.html", gin.H{})
}
func (con UserController) Multifile(c *gin.Context) {
username := c.PostForm("username") //获取post过来的username
file1, err1 := c.FormFile("face1") //获取上传的文件 , "face1" 对应 html里的 "face1"
// file1.Filename表示获取文件名称,
//若文件名称(file1.Filename)为 aaa.jpg 则拼接路径为 ./static1/upload/aaa.jpg
dst1 := path.Join("./static1/upload", file1.Filename) //路径拼接
if err1 == nil {
// 上传文件至指定的完整文件路径
c.SaveUploadedFile(file1, dst1)
}
file2, err2 := c.FormFile("face2")
dst2 := path.Join("./static1/upload", file2.Filename)
if err2 == nil {
// 上传文件至指定的完整文件路径
c.SaveUploadedFile(file1, dst2)
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"username": username,
"dst1": dst1,
"dst2": dst2,
})
}
执行过程:先访问 http://localhost:8080/admin/user/edit
输入信息后点击提交 跳转至页面 http://localhost:8080/admin/user/Multifile 显示详情
15.3 多文件上传 相同名字的多个文件
multifile2.html
<!--项目中实现文件上传 需要在上传文件的form表单上面需要加入 enctype="multipart/form-data"-->
<body>
<H2>多文件上传 相同名字-多个文件</H2>
<form action="/admin/user/Multifile2" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username" placeholder="用户名">
<br>
<br>
<!--图片-->
<!--相同名字的 多个文件上传-->
头 像1:<input type="file" name="face[]" >
<br> <br>
头 像2:<input type="file" name="face[]" >
<br> <br>
头 像3:<input type="file" name="face[]" >
<br> <br>
<input type="submit" value="提交">
</form>
</body>
adminrouter.go
adminRouters.GET("/user/edit2", admin.UserController{}.Edit2)
adminRouters.POST("/user/Multifile2", admin.UserController{}.Multifile2)
usercontroller.go
func (con UserController) Edit2(c *gin.Context) {
c.HTML(http.StatusOK, "admin/multifile2.html", gin.H{})
}
func (con UserController) Multifile2(c *gin.Context) {
username := c.PostForm("username") //获取post过来的username
form, _ := c.MultipartForm() //获取表单
files := form.File["face[]"] //获取相同名字的上传文件 , "face[]" 对应 html里的 "face[]"
for _, file := range files {
dst := path.Join("./static1/upload", file.Filename) //路径拼接
//上传文件至指定目录
c.SaveUploadedFile(file, dst)
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"username": username,
})
}
执行过程:先访问 http://localhost:8080/admin/user/edit2
输入信息后点击提交 跳转至页面 http://localhost:8080/admin/user/Multifile2 显示详情
14.4 文件上传 按照日期存储
可以上传多个同样文件 日期不一样 创建该日期文件夹
func (con UserController) DoUpload(c *gin.Context) {
username := c.PostForm("username")
//1.获取上传文件
file, err1 := c.FormFile("face")
if err1 == nil {
//2.获取后缀名 判断类型是否正确 .jpg .png .gif .jpeg
extName := path.Ext(file.Filename)
allowExtMap := map[string]bool{
".jpg": true,
".png": true,
".gif": true,
".jpeg": true,
".PNG": true,
}
//判断获取的后缀名是否存在
if _, ok := allowExtMap[extName]; !ok {
c.String(200, "上传文件类型不合法!")
return
}
//3.创建图片保存目录 static1/upload/20210624
day := models.GetDay() //获取年月日
dir := "./static1/upload/" + day //拼接路径
//创建一个名为dir的目录
err2 := os.MkdirAll(dir, 0666) //0666代表新创建的文件
if err2 != nil {
fmt.Println(err2)
c.String(200, "MkdirAll失败!")
return
}
//4.生成文件名称 和 文件保存目录
filename := strconv.FormatInt(models.GetUinx(), 10) + extName //生成文件名称
dst := path.Join(dir, filename) //拼接文件保存目录
//5.执行上传
c.SaveUploadedFile(file, dst)
c.JSON(200, gin.H{
"success": true,
"username": username,
})
}
}
按照日期上传文件123
15.Cookie
15.1 Cookie介绍
HTTP是无状态协议。简单地说:当你浏览了一个页面,然后转到同一个网站的另一个页面,服务器无法认识到这是同一个浏览器
在访问同一个网站,每一次的访问,都是没有任何关系的。如果我们要实现多个页面之间共享数据的话,我们就可以使用Cookie或Session实现cookie 是存储于访问者计算机的浏览器中。可以让我们用同一个浏览器访问同一个域名的时候共享数据。
15.2 Cookie能实现的功能
1.保持用户登录状态
2.保存用户浏览的历史记录
3.猜你喜欢,智能推荐
4.电商网站的加入购物车
15.3 设置和获取cookie
c.SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
第一个参数 key
第二个参数 value
第三个参数 过期时间。如果只想设置cookie的保存路径而不想设置存活时间,可以在第三个参数中传递nil或0
第四个参数 cookie的路径
第五个参数 cookie的路径domain作用域 本地调试配置为loaclhost,正式上线配置成域名
第六个参数 secure的值为true时,cookie在HTTP中是无效的,在HTTPS中才有效
第七个参数 httpOnly是微软对cookie做的扩展,如果设置了其httpOnly属性,则通过程序(JS applet)将无法读取到cookie信息,防止XSS攻击产生。
设置为true表示在后端才能操作cookie 设置为false表示在前端通过JavaScript来操作cookie
defaultcontroller.go
func (con DefaultController) Index(c *gin.Context) {
fmt.Println(models.UnixToTime(1662007020))
//设置cookie
c.SetCookie("username", "ocean", 300, "/", "localhost", false, true)
c.HTML(200, "default/index.html", gin.H{
"msg": "我有一个msg****",
"time": 1662006689,
})
}
func (con DefaultController) Shop(c *gin.Context) {
//获取cookie
username, _ := c.Cookie("username")
c.String(200, username)
}
在访问http://localhost:8080/ 设置cookie后 在http://localhost:8080/shop可以查看到cookie
设置与查看cookie
15.4 删除cookie
在15.3基础上 添加代码
defaultcontroller.go
func (con DefaultController) DeleteCookie(c *gin.Context) {
//删除cookie
c.SetCookie("username", "ocean", -1, "/", "localhost", false, true)
c.String(200, "删除成功")
}
defaultrouters.go
defaultRouters.GET("/DeleteCookie", ocean.DefaultController{}.DeleteCookie)
首先访问http://localhost:8080/ 设置cookie 在 http://localhost:8080/shop 查看设置的cookie
删除cookie1
访问 http://localhost:8080/DeleteCookie 删除cookie
删除cookie2
再次访问 http://localhost:8080/shop 发现cookie已被删除
删除cookie3
15.5 多个二级域名共享cookie
15.5.1 把a.ocean.com b.ocean.com 解析到我们的服务器
在计算机中找到路径c:\windows\system32\drivers\etc\hosts 修改hosts文件
修改hosts
在main.go里 将路由改为 80 端口 意味着 不用访问localhost:8080 直接访问 localhost 或 127.0.0.1 即可
//启动路由
r.Run(":80")
:80: bind: An attempt was made to access a socket in a way forbidden by its access permissions.
代表 80端口被占用 因此我们应该在任务管理器杀死后台
因为修改了hosts文件 将 a.ocean.com 和 b.ocean.com 解析到我们的服务器 因此 也可以 访问a.ocean.com b.ocean.com 达到访问localhost
多个二级域名共享cookie思路::我们想的是用户在 a.ocean.com 中设置 Cookie 信息后在 b.ocean.com 中获取刚才设置的
cookie,也就是实现多个二级域名共享 cookie
defaultController.go
func (con DefaultController) Index(c *gin.Context) {
fmt.Println(models.UnixToTime(1662007020))
//设置cookie
c.SetCookie("username", "ocean", 300, "/", "a.ocean.com", false, true)//第五个参数修改
c.HTML(200, "default/index.html", gin.H{
"msg": "我有一个msg****",
"time": 1662006689,
})
}
func (con DefaultController) Shop(c *gin.Context) {
//获取cookie
username, _ := c.Cookie("username")
c.String(200, username)
}
我们访问 a.ocean.com 设置 cookie 在访问 http://a.ocean.com/shop 可以查看到 cookie
二级域名共享1
但是 访问 http://b.ocean.com/shop 发现并不能查看cookie
二级域名共享2
为此我们做出以下变动
defaultController.go
func (con DefaultController) Index(c *gin.Context) {
fmt.Println(models.UnixToTime(1662007020))
//设置cookie
c.SetCookie("username", "ocean", 300, "/", ".ocean.com", false, true)//第五个参数修改
c.HTML(200, "default/index.html", gin.H{
"msg": "我有一个msg****",
"time": 1662006689,
})
}
我们访问 a.ocean.com 设置 cookie 在访问 http://a.ocean.com/shop 可以查看到 cookie
访问 http://b.ocean.com/shop 发现可以查看到cookie
二级域名共享cookie3
16.Session
16.1 Session 简单介绍
session 是另一种记录客户状态的机制,不同的是 Cookie 保存在客户端浏览器中,而 session 保存在服务器上
16.2 Session 的工作流
当客户端浏览器第一次访问服务器并发送请求时,服务器端会创建一个 session 对象,生成
一个类似于 key,value 的键值对,然后将value保存到服务器 将key(cookie)返回到浏览器(客户)端。浏览器下次访问时会携带key(cookie),找到对应的session(value)
16.3 Gin 中使用 Session
Gin 官方没有给我们提供 Session 相关的文档,这个时候我们可以使用第三方的 Session 中间件来实现
https://github.com/gin-contrib/sessions
16.4 基于 Cookie 存储 Session
main.go
//配置session中间件
//创建基于 cookie 的存储引擎,secret11111 参数是用于加密的密钥
store := cookie.NewStore([]byte("secret111"))
// 设置 session 中间件,参数 mysession,指的是 session 的名字,也是 cookie 的名字
// store 是前面创建的存储引擎,我们可以替换成其他存储引擎
r.Use(sessions.Sessions("mysession", store)) //全局中间件
defaultcontroller.go
func (con DefaultController) Index(c *gin.Context) {
//初始化 session 对象
session := sessions.Default(c)
//设置 session
session.Set("username", "OCEAN 111")
session.Save() //设置session时候 必须调用
c.HTML(200, "default/index.html", gin.H{
"msg": "我有一个msg****",
"time": 1662006689,
})
}
func (con DefaultController) News(c *gin.Context) {
//初始化 session 对象
session := sessions.Default(c)
//获取session
username := session.Get("username")
c.String(200, "news***\n")
c.String(200, "username=%v", username)
}
就算结束程序后 再次运行程序 访问 http://localhost:8080/news 还可以看见获取了session,说明服务器已经把session保存到了服务器本地 也就是计算机本地
16.5 基于 Redis 存储 Sessio
如果我们想将 session 数据保存到 redis 中,只要将 session 的存储引擎改成 redis 即可 其他不变
main.go
// 初始化基于 redis 的存储引擎
// 参数说明:
// 第 1 个参数 - redis 最大的空闲连接数
// 第 2 个参数 - 数通信协议 tcp 或者 udp
// 第 3 个参数 - redis 地址, 格式,host:port
// 第 4 个参数 - redis 密码
// 第 5 个参数 - session 加密密钥
//基于 Redis 存储 Session
store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret"))
r.Use(sessions.Sessions("mysession", store)) //全局中间件
访问 http://localhost:8080/ 设置 cookie
访问http://localhost:8080/news 路由后 查看设置的cookie 把本地cookie传至服务器 可以在redis数据库查看 key 与 value
所以session工作流程:
当设置session时候,他首先会在服务器生成一个key value键值对,会把key下发到客户端,
下次你要获取session的时候,需要把key传到服务器,服务器拿到key会匹配对应的值,返回到客户端。
16.6 配置session过期时间
func (con DefaultController) Index(c *gin.Context) {
//初始化 session 对象
session := sessions.Default(c)
//配置session过期时间
session.Options(sessions.Options{ //修改地方
MaxAge: 3600 * 6, //6 hours
})
//设置 session
session.Set("username", "OCEAN 111")
session.Save() //设置session时候 必须调用
c.HTML(200, "default/index.html", gin.H{
"msg": "我有一个msg****",
"time": 1662006689,
})
}
17 GORM配置以及实现数据库表的增删改查
17.1 GORM 简单介绍
GORM 是 Golang 的一个 orm 框架。简单说,ORM 就是通过实例对象的语法,完成关系型数据库的操作的技术,是"对象-关系映射"(Object/Relational Mapping) 的缩写。使用 ORM
框架可以让我们更方便的操作数据库。
官方文档:https://gorm.io/zh_CN/docs/index.html
17.2 Gin 中使用 GORM
1.导入第三方包:
go get -u gorm.io/gorm
go get -u gorm.io/driver/
2.在models文件夹创建core.go
Gin 中使用 Gorm 连接数据库
core.go
package models
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var DB *gorm.DB
var err error
func init() {
//使用 gorm 连接到 mysql 数据库
//第一个root 数据库用户名 第二个root 数据库密码 127.0.0.1:3306 主机:端口号, gin 具体哪个数据库
dsn := "root:root@tcp(127.0.0.1:3306)/gin?charset=utf8mb4&parseTime=True&loc=Local"
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) // 将 DB 定义成公有的
if err != nil {
fmt.Println(err)
}
}
3.在models文件夹创建user.go意味着用来操作gin数据库中的user表
user.go
package models
//定义操作数据库user表的model
//实现对象和关系的映射
type User struct {
Id int
Username string
Age int
Email string
AddTime int
}
//表示配置 操作数据库的表名称
//表示把 User 结构体默认操作的表改为 user 表
func (User) TableName() string {
return "user"
}
在实际项目中定义数据库模型注意以下几点:
1、结构体的名称必须首字母大写 ,并和数据库表名称对应。例如:表名称为 user 结构体名称定义成 User,表名称为 article_cate 结构体名称定义成 ArticleCate
2、结构体中的字段名称首字母必须大写,并和数据库表中的字段一一对应。例如:下面结构体中的 Id 和数据库中的 id 对应,Username 和数据库中的 username 对应,Age 和数据库中 的 age 对应,Email
和数据库中的 email 对应,AddTime 和数据库中的 add_time 字段对应
3、默认情况表名是结构体名称的复数形式。如果我们的结构体名称定义成 User,表示这个模型默认操作的是users表
4、我们可以使用结构体中的自定义方法 TableName 改变结构体的默认表名称,如下:
func (User) TableName() string {
return "user"
}
表示把 User 结构体默认操作的表改为 user 表
17.3 Gin 中使用 GORM 查看数据
usercontroller.go
func (con UserController) User(c *gin.Context) {
//查询数据库
userList := []models.User{}
models.DB.Find(&userList)
c.JSON(200, gin.H{
"result": userList,
})
}
func (con UserController) User(c *gin.Context) {
//查询age大于20的用户
userList := []models.User{}
models.DB.Where("age>20").Find(&userList)
c.JSON(200, userList)
}
func (con UserController) User(c *gin.Context) {
//查询id=5的数据
user := models.User{}
models.DB.Where("id =?", 5).Find(&user)
fmt.Println(user)
}
17.4 Gin 中使用 GORM 增加数据
usercontroller.go
func (con UserController) Add(c *gin.Context) {
user := models.User{
ID:18
Username: "OCEAN",
Age: 11,
Email: "27@qq.com",
AddTime: int(models.GetUinx()),
}
models.DB.Create(&user)
fmt.Println(user)
c.String(200, "增加用户成功!")
}
增加前的数据库
增加数据1
访问 http://localhost:8080/admin/user/add 后 查询数据库
增加数据2
17.5 Gin 中使用 GORM 修改数据
usercontroller.go
func (con UserController) Edit(c *gin.Context) { //1.
//先查询id=7的数据
user := models.User{Id: 7}
models.DB.Find(&user)
fmt.Println(user)
//更新数据
user.Username = "small-ocean"
user.Age = 50
user.AddTime = int(models.GetUinx())
models.DB.Save(&user) //保存数据
c.String(200, "修改用户成功!")
}
func (con UserController) Edit(c *gin.Context) {//2.
user := models.User{}
models.DB.Model(&user).Where("age = ?", 34).Update("username", "hello")
models.DB.Save(&user)
c.String(200, "修改用户成功!")
}
func1
修改前的数据库
修改数据1
访问 http://localhost:8080/admin/user/edit 后查看数据库
修改数据2
17.6 Gin 中使用 GORM 删除数据
usercontroller.go
func (con UserController) Delete(c *gin.Context) {//1
user := models.User{Id: 1}
models.DB.Delete(&user)
c.String(200, "删除用户成功!")
}
func (con UserController) Delete(c *gin.Context) {//2.
user := models.User{}
models.DB.Where("username = ?", "OCEAN").Delete(&user)
c.String(200, "删除用户成功!")
}
func2
删除前的数据库
删除数据1
访问 http://localhost:8080/admin/user/delete 后查看数据库
删除数据2