这是我参与「第五届青训营」伴学笔记的第6天
阅前提示:因为我们小组内部决定使用gin框架作为本次大作业的HTTP框架,所以本文记录了一下gin框架的基本使用~
Gin框架的快速入门
要求
- Go 1.13 及以上版本
安装
要安装 Gin 软件包,需要先安装 Go 并设置 Go 工作区。
1.下载并安装 gin:
$ go get -u github.com/gin-gonic/gin
2.将 gin 引入到代码中:
import "github.com/gin-gonic/gin"
3.(可选)如果使用诸如 http.StatusOK 之类的常量,则需要引入 net/http 包:
import "net/http"
- 创建你的项目文件夹并
cd进去
$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_"
- 拷贝一个初始模板到你的项目里
$ curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go > main.go
- 运行你的项目
$ go run main.go
开始
首先,创建一个名为 example.go 的文件
$ touch example.go
接下来, 将如下的代码写入 example.go 中:
package main
import "github.com/gin-gonic/gin"
func main() {
//创建一个默认的路由引擎
r := gin.Default()
//配置路由
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // 监听并在 0.0.0.0:8080 上启动服务(默认)
//你也可以选择运行在别的端口上
//r.Run(":8888")
}
然后, 执行 go run example.go 命令来运行代码:
# 运行 example.go 并且在浏览器中访问 HOST_IP:8080/ping
$ go run example.go
路由
路由是由一个URL和一个特定的HTTP方法(如GET,POST等)组成的,涉及到应用如何响应客户端对某个网络节点的访问
RESTful API
这是一套目前比较成熟的互联网应用程序的API理论,用来规范我们的路由写法
| 请求方法 | 代表操作 |
|---|---|
| GET(SELECT) | 从服务器取出资源 |
| POST(CREATE) | 在服务器新建一个资源 |
| PUT(UPDATE) | 在服务器更新资源(客户端提供改变后的完整资源) |
| DELETE(DELETE) | 从服务器删除资源 |
响应数据
返回一个字符串
//返回一个字符串
r.GET("/?aid=hello", func(c *gin.Context) {
aid := c.Query("aid")
c.String(200, "aid=%s", aid)
//c.String(200, "%s", "hello")
})
返回一个json
type Article struct {
Title string `json:"title"`
Desc string `json:"desc"`
}
//返回一个json
r.GET("/json", func(c *gin.Context) {
a := Article{
Title: "title",
Desc: "desc",
}
//第一个参数是一个状态码;
//第二个参数是一个空接口类型:表示可以传入任何类型
//第二个参数还可以是一个结构体
c.JSON(200, map[string]interface{}{
"success": true,
"msg": "hello",
})
c.JSON(200, a)
})
其中,map[string]interface{}可以用gin.H来代替
响应jsonp请求
jsonp主要用来解决跨域问题
r.GET("/jsonp", func(c *gin.Context) {
a := Article{
Title: "title",
Desc: "desc",
}
//第二个参数是一个空接口类型:表示可以传入任何类型
//第二个参数还可以是一个结构体
c.JSONP(200, a)
})
返回:
返回xml数据
r.GET("/xml", func(c *gin.Context) {
a := Article{
Title: "title",
Desc: "desc",
}
//第二个参数是一个空接口类型:表示可以传入任何类型
//第二个参数还可以是一个结构体
c.XML(200, a)
})
返回:
<Article>
<Title>
title
</Title>
<Desc>
desc
</Desc>
</Article>
获取GET POST传值
Get请求传值
GET /user?uid=20&page=1
router.GET("/user", func(c *gin.Context) {
uid := c.Query("uid")
page := c.DefaultQuery("page", "0")
c.String(200, "uid=%v page=%v", uid, page)
})
动态路由传值
域名/user/20
r.GET("/user/:uid", func(c *gin.Context) {
uid := c.Param("uid")
c.String(200, "userID=%s", uid)
})
Post 请求传值 获取 form 表单数据
router.GET("/addUser", func(c *gin.Context) {
c.HTML(200, "default/add_user.html", gin.H{})
})
router.POST("/doAddUser", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
})
获取 GET POST 传递的数据绑定到结构体
.ShouldBind()可以基于请求自动提取 JSON、form 表单和 QueryString 类型的数据,并把值绑定到指定的结构体对象
//注意首字母大写
type Userinfo struct {
Username string `form:"username" json:"user"` Password string `form:"password" json:"password"` }
Get 传值绑定到结构体
/?username=zhangsan&password=123456
router.GET("/", func(c *gin.Context) {
var userinfo Userinfo
if err := c.ShouldBind(&userinfo); err == nil {
c.JSON(http.StatusOK, userinfo)
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
Post 传值绑定到结构体
router.POST("/doLogin", func(c *gin.Context) {
var userinfo Userinfo
if err := c.ShouldBind(&userinfo); err == nil {
c.JSON(http.StatusOK, userinfo)
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
路由分组
代码如下:
func main() {
router := gin.Default()
// 简单的路由组: v1
v1 := router.Group("/v1")
{
v1.POST("/login", loginEndpoint)
v1.POST("/submit", submitEndpoint)
v1.POST("/read", readEndpoint)
}
// 简单的路由组: v2
v2 := router.Group("/v2")
{
v2.POST("/login", loginEndpoint)
v2.POST("/submit", submitEndpoint)
v2.POST("/read", readEndpoint)
}
router.Run(":8080")
}
这样写仅仅只是简单的将路由组分好,但还是放在了main()中,如果日后要修改还是不太方便,所以我们后续还需要将每一个路由分组抽离:
新建routers.go
package main
import (
"github.com/gin-gonic/gin"
"tiktok/controller"
)
func initRouter(r *gin.Engine) {
// public directory is used to serve static resources
r.Static("/static", "./public")
apiRouter := r.Group("/douyin")
// basic apis
apiRouter.GET("/user/", controller.UserInfo)
apiRouter.POST("/user/register/", controller.Register)
apiRouter.POST("/user/login/", controller.Login)
}
而此时main.go里面是这样的:
func main() {
go service.RunMessageServer()
//创建一个默认的路由引擎
r := gin.Default()
//配置路由
//返回一个字符串
initRouter(r)
r.Run() // 监听并在 0.0.0.0:8080 上启动服务(默认)
//你也可以选择运行在别的端口上
//r.Run(":8888")
}
中间件
Gin 框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。 换而言之:中间件就是匹配路由前和匹配路由完成后执行的一系列操作
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func initMiddleware(ctx *gin.Context) {
fmt.Println("我是一个中间件")
}
func main() {
r := gin.Default()
r.GET("/", initMiddleware, func(ctx *gin.Context) {
ctx.String(200, "首页--中间件演示")
})
r.GET("/news", initMiddleware, func(ctx *gin.Context) {
ctx.String(200, "新闻页面--中间件演示")
})
r.Run(":8080")
}
ctx.Next()调用该请求的剩余处理程序
package main
import ( "fmt"
"time"
"github.com/gin-gonic/gin"
)
func initMiddleware(ctx *gin.Context) {
fmt.Println("1-执行中中间件")
start := time.Now().UnixNano()
// 调用该请求的剩余处理程序
ctx.Next()
fmt.Println("3-程序执行完成 计算时间")
// 计算耗时 Go 语言中的 Since()函数保留时间值,并用于评估与实际时间的差异
end := time.Now().UnixNano()
fmt.Println(end - start)
}
func main() {
r := gin.Default()
r.GET("/", initMiddleware, func(ctx *gin.Context) {
fmt.Println("2-执行首页返回数据")
ctx.String(200, "首页--中间件演示")
})
r.GET("/news", initMiddleware, func(ctx *gin.Context) {
ctx.String(200, "新闻页面--中间件演示")
})
r.Run(":8080")
}
全局中间件
package main
import ( "fmt"
"github.com/gin-gonic/gin"
)
func initMiddleware(ctx *gin.Context) {
fmt.Println("全局中间件 通过 r.Use 配置")
// 调用该请求的剩余处理程序
ctx.Next()
}
func main() {
r := gin.Default()
r.Use(initMiddleware)
r.GET("/", func(ctx *gin.Context) {
ctx.String(200, "首页--中间件演示")
})
r.GET("/news", func(ctx *gin.Context) {
ctx.String(200, "新闻页面--中间件演示")
})
r.Run(":8080")
}
在路由分组中配置中间件
为路由组注册中间件
写法 1:
shopGroup := r.Group("/shop", StatCost())
{
shopGroup.GET("/index", func(c *gin.Context) {...})
... }
写法 2:
shopGroup := r.Group("/shop")
shopGroup.Use(StatCost())
{
shopGroup.GET("/index", func(c *gin.Context) {...})
... }
分组路由 AdminRoutes.go 中配置中间件
package routes
import ( "fmt"
"gin_demo/controller/admin"
"net/http"
"github.com/gin-gonic/gin"
)
func initMiddleware(ctx *gin.Context) {
fmt.Println("路由分组中间件")
// 调用该请求的剩余处理程序
ctx.Next()
}
func AdminRoutesInit(router *gin.Engine) {
adminRouter := router.Group("/admin", initMiddleware)
{
adminRouter.GET("/user", admin.UserController{}.Index)
adminRouter.GET("/user/add", admin.UserController{}.Add)
adminRouter.GET("/news", func(c *gin.Context) {
c.String(http.StatusOK, "news")
})
}
}
中间件和对应控制器之间共享数据
设置值
ctx.Set("username", "张三")
获取值
username, _ := ctx.Get("username")
中间件设置值
func InitAdminMiddleware(ctx *gin.Context) {
fmt.Println("路由分组中间件")
// 可以通过 ctx.Set 在请求上下文中设置值,后续的处理函数能够取到该值
ctx.Set("username", "张三")
// 调用该请求的剩余处理程序
ctx.Next()
}
控制器获取值
func (c UserController) Index(ctx *gin.Context) {
username, _ := ctx.Get("username")
fmt.Println(username)
ctx.String(http.StatusOK, "这是用户首页 111")
}
Cookie
Cookie介绍
- HTTP 是无状态协议。简单地说,当你浏览了一个页面,然后转到同一个网站的另一个页面,服务器无法认识到这是同一个浏览器在访问同一个网站。每一次的访问,都是没有任何关系的。如果我们要实现多个页面之间共享数据的话我们就可以使用 Cookie 或者 Session 实现
- cookie 是存储于访问者计算机的浏览器中。可以让我们用同一个浏览器访问同一个域名的时候共享数据。
设置 Cookie
c.SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) - 第一个参数 key
- 第二个参数 value
- 第三个参数 过期时间.如果只想设置 Cookie 的保存路径而不想设置存活时间,可以在第三个参数中传递 nil
- 第四个参数 cookie 保存的路径,默认为“/”,表示在当前域都有效
- 第五个参数 cookie 的路径 Domain 作用域;本地调试配置成 localhost , 正式上线配置成域名
- 第六个参数是 secure ,当 secure 值为 true 时,cookie 在 HTTP 中是无效,在 HTTPS 中才有效
- 第七个参数 httpOnly,是微软对 COOKIE 做的扩展。如果在 COOKIE 中设置了“httpOnly”属性,则通过程序(JS 脚本、applet 等)将无法读取到 COOKIE 信息,防止 XSS 攻击产生获取 Cookie
cookie, err := c.Cookie("name")
package main
import ( "gin_demo/models"
"html/template"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.SetFuncMap(template.FuncMap{ "unixToDate": models.UnixToDate, })
r.GET("/", func(c *gin.Context) {
c.SetCookie("usrename", "张三", 3600, "/", "localhost", false, true)
c.String(200, "首页")
})
r.GET("/user", func(c *gin.Context) {
username, _ := c.Cookie("usrename")
c.String(200, "用户-"+username)
})
r.Run(":8080")
}