这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天
Gin框架快速入门
一、gin框架介绍
Gin 是一个用 Go (Golang) 编写的 Web 框架。 它具有类似 martini 的 API,性能要好得多,多亏了 httprouter,速度提高了 40 倍,是GO语言中最流行的Web框架
gin的安装
go get -u github.com/gin-gonic/gin
将gin导入到代码中
import "github.com/gin-gonic/gin"
注:go语言应在1.13版本以上
二、第一个gin程序
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
// GET:请求方式;/hello:请求的路径
// 当客户端以GET方法请求/hello路径时,会执行后面的函数
//为方便,此处用匿名函数
r.GET("/hello", func(c *gin.Context) {
// c.JSON:返回JSON格式的数据
// code:返回码
c.JSON(200, gin.H{
"message": "Hello world!",
})
})
// 启动HTTP服务,默认在8080端口启动服务
r.Run()
}
将上面的代码编译并执行,然后到浏览器打开127.0.0.1:8080/hello,可以看到一串JSON字符串
同时,我们可以在运行窗口中看到返回码、url路径、get方法、请求时间
三、gin路由
路由引擎
在gin框架中, 路由引擎 是一个 一个结构体,其中包含了路由组、中间件、页面渲染接口、等相关内容
engine1 = gin.Default() // 创建默认的 路由引擎
engine2 = gin.New() // 创建一个新的 路由引擎
- gin.Default其实也使用gin.New()创建engine实例,但是会默认使用Logger和Recovery中间件
- Logger是负责进行打印并输出日志的中间件,方便开发者进行程序调试
- Recovery中间件的作用是如果程序执行过程中遇到panic中断了服务,则Recovery会恢复程序执行,并返回服务器500内部错误
1.普通路由
r.GET("/index", func(c *gin.Context) {...})
r.GET("/login", func(c *gin.Context) {...})
r.POST("/login", func(c *gin.Context) {...})
2.路由组
func main() {
r := gin.Default()
userGroup := r.Group("/user")
{
userGroup.GET("/index", func(c *gin.Context) {...})
userGroup.GET("/login", func(c *gin.Context) {...})
userGroup.POST("/login", func(c *gin.Context) {...})
}
shopGroup := r.Group("/shop")
{
shopGroup.GET("/index", func(c *gin.Context) {...})
shopGroup.GET("/cart", func(c *gin.Context) {...})
shopGroup.POST("/checkout", func(c *gin.Context) {...})
}
r.Run()
}
嵌套路由组
func ActiveRouterList(e *gin.Engine) {
group := e.Group("/aaa")
{
group.GET("/b1", new(controller.Testing).HandGetTest)
group.GET("/b2", new(controller.Testing).HandGetTest)
group2 := group.Group("b3")
{
group2.GET("/c1", new(controller.Testing).HandGetTest)
group2.GET("/c2", new(controller.Testing).HandGetTest)
}
}
// 路由组访问地址: /aaa/b1 和 /aaa/b3/c1
}
3.静态路由
为静态文件指定固定的访问路径
r.Static("static", "./static")
在该项目中,需要创建一个static文件夹,来保存静态文件
四、中间件
gin框架允许开发者在 处理请求 的过程中,加入用户自己的 钩子函数。这个 钩子函数 就是 中间件。 中间件适合处理一些 公共的 业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等
中间件的定义
一般中间件都建议定义在middleware包中,方便管理
package middleware
import (
"crypto/sha1"
"encoding/hex"
"github.com/gin-gonic/gin"
)
func SHA1(s string) string {
o := sha1.New()
o.Write([]byte(s))
return hex.EncodeToString(o.Sum(nil))
}
func SHAMiddleWare() gin.HandlerFunc {
return func(context *gin.Context) {
password := context.Query("password")
if password == "" {
password = context.PostForm("password")
}
context.Set("password", SHA1(password))
context.Next()
}
}
中间件的注册
其实就是为中间件注册路由,可以注册单路由、路由组,还可以全局注册
func InitDouyinRouter() *gin.Engine {
models.InitDB()
r := gin.Default()
r.Static("static", "./static")
baseGroup := r.Group("/douyin")
//根据灵活性考虑是否加入JWT中间件来进行鉴权,还是在之后再做鉴权
// basic apis
baseGroup.GET("/feed/", video.FeedVideoListHandler)
baseGroup.GET("/user/", middleware.JWTMiddleWare(), user_info.UserInfoHandler)
baseGroup.POST("/user/login/", middleware.SHAMiddleWare(), user_login.UserLoginHandler)
baseGroup.POST("/user/register/", middleware.SHAMiddleWare(), user_login.UserRegisterHandler)
baseGroup.POST("/publish/action/", middleware.JWTMiddleWare(), video.PublishVideoHandler)
baseGroup.GET("/publish/list/", middleware.NoAuthToGetUserId(), video.QueryVideoListHandler)
//extend 1
baseGroup.POST("/favorite/action/", middleware.JWTMiddleWare(), video.PostFavorHandler)
baseGroup.GET("/favorite/list/", middleware.NoAuthToGetUserId(), video.QueryFavorVideoListHandler)
baseGroup.POST("/comment/action/", middleware.JWTMiddleWare(), comment.PostCommentHandler)
baseGroup.GET("/comment/list/", middleware.JWTMiddleWare(), comment.QueryCommentListHandler)
//extend 2
baseGroup.POST("/relation/action/", middleware.JWTMiddleWare(), user_info.PostFollowActionHandler)
baseGroup.GET("/relation/follow/list/", middleware.NoAuthToGetUserId(), user_info.QueryFollowListHandler)
baseGroup.GET("/relation/follower/list/", middleware.NoAuthToGetUserId(), user_info.QueryFollowerHandler)
//baseGroup.GET("/publish/List", video.List)
return r
}
五、JSON渲染
由于是前后端分离的架构,所以后端返回给前端的数据是用JSON格式返回的,可以自己拼接json也可以使用结构体,一般在业务代码中,基本是访问的数据库内容,所以需要定义一个结构体来存储数据库中的内容
func (p *ProxyFeedVideoList) FeedVideoListOk(videoList *video.FeedVideoList) {
p.JSON(http.StatusOK, FeedResponse{
CommonResponse: models.CommonResponse{
StatusCode: 0,
},
FeedVideoList: videoList,
},
)
}
models包
package models
type CommonResponse struct {
StatusCode int32 `json:"status_code"`
StatusMsg string `json:"status_msg,omitempty"`
}
六、获取参数
获取从前端返回的数据
1.获取querystring参数
querystring是指URL后面的参数,/user/search?username=123&password=123456
func Register(c *gin.Context) {
username := c.Query("username")
password := c.Query("password")
token := username + password
if _, exist := usersLoginInfo[token]; exist {
c.JSON(http.StatusOK, UserLoginResponse{
CommonResponse: models.CommonResponse{StatusCode: 1, StatusMsg: "User already exist"},
})
} else {
atomic.AddInt64(&userIdSequence, 1)
newUser := models.User{
Id: userIdSequence,
Name: username,
}
usersLoginInfo[token] = newUser
c.JSON(http.StatusOK, UserLoginResponse{
CommonResponse: models.CommonResponse{StatusCode: 1, StatusMsg: "User already exist"},
UserId: userIdSequence,
Token: username + password,
})
}
}
2.获取form参数
当前端请求的数据通过form表单提交时,即发送的是POST请求,获取数据的方法
func main() {
//Default返回一个默认的路由引擎
r := gin.Default()
r.POST("/user/search", func(c *gin.Context) {
// DefaultPostForm取不到值时会返回指定的默认值
//username := c.DefaultPostForm("username", "小王子")
username := c.PostForm("username")
address := c.PostForm("address")
//输出json结果给调用方
c.JSON(http.StatusOK, gin.H{
"message": "ok",
"username": username,
"address": address,
})
})
r.Run(":8080")
}
七、上传文件
单文件上传
func main() {
router := gin.Default()
// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) {
// 单文件
file, _ := c.FormFile("file")
log.Println(file.Filename)
dst := "./" + file.Filename
// 上传文件至指定的完整文件路径
c.SaveUploadedFile(file, dst)
c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
})
router.Run(":8080")
}
多文件上传
// PublishVideoHandler 发布视频,并截取一帧画面作为封面
func PublishVideoHandler(c *gin.Context) {
//准备参数
rawId, _ := c.Get("user_id")
userId, ok := rawId.(int64)
if !ok {
PublishVideoError(c, "解析UserId出错")
return
}
title := c.PostForm("title")
form, err := c.MultipartForm()
if err != nil {
PublishVideoError(c, err.Error())
return
}
//支持多文件上传
files := form.File["data"]
for _, file := range files {
suffix := filepath.Ext(file.Filename) //得到后缀
if _, ok := videoIndexMap[suffix]; !ok { //判断是否为视频格式
PublishVideoError(c, "不支持的视频格式")
continue
}
name := util.NewFileName(userId) //根据userId得到唯一的文件名
filename := name + suffix
savePath := filepath.Join("./static", filename)
err = c.SaveUploadedFile(file, savePath)
if err != nil {
PublishVideoError(c, err.Error())
continue
}
//截取一帧画面作为封面
err = util.SaveImageFromVideo(name, true)
if err != nil {
PublishVideoError(c, err.Error())
continue
}
//数据库持久化
err := video.PostVideo(userId, filename, name+util.GetDefaultImageSuffix(), title)
if err != nil {
PublishVideoError(c, err.Error())
continue
}
PublishVideoOk(c, file.Filename+"上传成功")
}
}