gin框架
一个使用较广的web框架,这里来快速学习一下。
因为是外部的库,所以需要下载gin框架依赖包,先更新goproxy为阿里云:
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
然后再下载并运行如下命令即可:
go get -u github.com/gin-gonic/gin
运行完毕后即可在go.mod文件中看到成功添加:

下面来简单学习相关的语法。
基本利用了解
路由参数(GET传参):
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
g := gin.Default()
g.GET("/user/:name/*action", func(c *gin.Context) {
name := c.Param("name")
//*表示通配符,即匹配后面的所有路径,并且需要放在最后
action := c.Param("action")
c.String(200, "Hello %s,你的剩余路径为:%s", name, action)
})
g.GET("/user", func(c *gin.Context) {
name := c.Query("name")
age := c.DefaultQuery("age", "0")
c.JSON(http.StatusOK, gin.H{
"name": name,
"age": age,
})
})
g.Run(":8081")
}
理解一下,然后自己敲一边即可,不是非常难。
关于通配符的输出,如下demo:

————————
处理POST表单数据
处理POST请求中的表单数据:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
g := gin.Default()
g.POST("/login", func(c *gin.Context) {
username := c.PostForm("username")
password := c.DefaultPostForm("password", "")
if username == "admin" && password == "123456" {
c.JSON(http.StatusOK, gin.H{
"status": "账号密码正确",
"Username": username,
})
} else {
c.JSON(http.StatusOK, gin.H{
"status": "登陆失败",
})
}
})
g.Run(":8081")
}
处理POST JSON数据
主要记住一个点,需要结合结构体来进行利用。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type LoginRequests struct {
//后面的为结构体的tag,当解析这个结构体时会使用到,比如这里就是Username表示在json中为username,并且required表示必须要有这个字段
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
func main() {
g := gin.Default()
//用结构体来接收JSON数据:
g.POST("/login", func(c *gin.Context) {
var loginRequest LoginRequests
var message string
//绑定请求体到结构体上:
if err := c.BindJSON(&loginRequest); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
if loginRequest.Username == "admin" && loginRequest.Password == "123456" {
message = "sucess"
} else {
message = "false"
}
c.JSON(http.StatusOK, gin.H{
"message": message,
"username": loginRequest.Username,
})
})
//直接使用map来接收JSON数据。适合不确定请求包参数的json请求体
g.POST("/raw", func(c *gin.Context) {
var recieve_map map[string]interface{}
if err := c.BindJSON(&recieve_map); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
c.JSON(http.StatusOK, gin.H{
"message": recieve_map,
})
})
g.Run(":8081")
}
验证:
curl -X POST http://127.0.0.1:8081/raw \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}'
自己验证一下即可。
其他的同理,什么XML以及前面的表单格式等等,但是都是需要和结构体绑定的,注意一下tag(这个tag用途很多,可以在接收参数同时对参数进行一些要求校验,后续真正开始开发的时候记得使用),遇到多试试。
文件上传
package main
import (
"errors"
"github.com/gin-gonic/gin"
)
func main() {
g := gin.Default()
//单文件上传
g.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
errors.New("happen worse")
}
c.SaveUploadedFile(file, "./uploads/"+file.Filename)
})
g.Run(":8081")
}
测试:
curl -F "file=@123.jpg;filename=custom_name.jpg" http://localhost:8081/upload
#
curl -F "file=@123.jpg" http://localhost:8081/upload
随后成功上传到uploads目录下,有custom_name.jpg以及123.jpg的文件。
还存在多文件上传,但是这里就不涉及了,后面真有这方面需求再补充。
路由分组
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type LoginUser struct {
Username string `json:"username"`
Password string `json:"password"`
}
func main() {
g := gin.Default()
v1 := g.Group("/v1")
{
v1.GET("/user", RepeatName)
v1.POST("/login", Loginrequest)
}
g.Run(":8081")
}
func RepeatName(c *gin.Context) {
name := c.Query("name")
age := c.DefaultQuery("age", "0")
c.JSON(http.StatusOK, gin.H{
"name": name,
"age": age,
})
}
func Loginrequest(c *gin.Context) {
var login_req LoginUser
var message string
if err := c.BindJSON(&login_req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{})
}
if login_req.Username == "admin" && login_req.Password == "123456" {
message = "sucess"
} else {
message = "false"
}
c.JSON(http.StatusOK, gin.H{
"status": message,
"username": login_req.Username,
})
}
测试一下就行:
curl -X POST http://127.0.0.1:8081/v1/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}'
通过分组,我们可以很方便定义不同路由下的功能分类。另外从代码中可以看到,这里是绑定的路由以及方法名称,没有直接将路由和方法具体内容写一起了,和前面对比一下然后理解一下就行,本质只是把方法提取出来了而已。
中间件
分全局、局部中间件,简单给个demo:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func main() {
g := gin.Default()
g.Use(MiddleHandler())
{
g.GET("/shop", func(c *gin.Context) {
time.Sleep(5 * time.Second)
now_message, _ := c.Get("exists")
c.String(http.StatusOK, now_message.(string))
})
}
g.Run(":8081")
}
func MiddleHandler() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Set("exists", "not exists")
c.Next()
end := time.Since(start)
fmt.Println("此次程序用时:", end)
}
}
成功页面输出not exists,并且控制台如下:

中间件成功实现。里面值得注意的就是Next()函数,大概意思就是跳过执行后续中间件和最终 handler,理解一下即可。还有很多东西,值得后面再挖掘挖掘。
另外中间件代码其实可以直接写成如下格式:
func MiddleHandler(c *gin.Context) {
start := time.Now()
c.Set("exists", "not exists")
c.Next()
end := time.Since(start)
fmt.Println("此次程序用时:", end)
}
//注册中间件部分需要改成g.Use(MiddleHandler)
原来的写法就是返回一个函数,故后续需要调用他来获取到需要的函数本身(算中间件函数生成工厂),从Use()形参也能看出来:

注意区分不同函数类型,参考形参gin.HandlerFunc,是否为需要的中间件函数。
身份验证
比较重要的模块,用于身份验证。
利用Cookie
可以结合中间件来进行判断,代码如下:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
g := gin.Default()
g.POST("/login", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
if username == "admin" && password == "123456" {
c.SetCookie("username", username, 120, "/", "localhost", false, true)
c.Redirect(http.StatusFound, "/home")
return
}
c.JSON(http.StatusUnauthorized, gin.H{"error": "wrong username or password"})
})
//局部中间件,其实本质就是先后执行顺序的问题,在我看来(看形参)
g.GET("/home", MiddleHandler, func(c *gin.Context) {
username, _ := c.Cookie("username")
c.JSON(http.StatusOK, gin.H{
"status": "ok",
"username": username,
})
})
g.Run(":8081")
}
func MiddleHandler(c *gin.Context) {
username, _ := c.Cookie("username")
if username == "admin" {
c.Next()
} else {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "please login"})
}
}
代码没什么问题,自己运行看看结果即可,但是注意一个点,登陆成功:

可以看到这里是以键值对的形式来存储的cookie,这种分分钟被越权:

通过伪造cookie即可成功进入home页面,在一个动态交互的网站,这样鉴权不是很行,但是如果是内部使用,就可以考虑这个,明文内容写复杂点即可,注意防止明文泄露。
也许关于Cookie还有其他更好的设置方式,待以后发掘了。
利用JWT
JWT就不多说了,直接看代码可以怎么利用吧。需要下载并引入JWT的包:
go get -u github.com/golang-jwt/jwt/v5
用最常见的HS256来加解密,demo如下:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"math/rand"
"net/http"
"time"
)
var jwtSecret = []byte("your_secret_key")
type Claims struct {
//uint代表无符号整数,就是非负数
UserID uint `json:"user_id"`
Isadmin bool `json:"isadmin"`
//用于jwt自定义的数据
jwt.RegisteredClaims
}
func main() {
g := gin.Default()
v1 := g.Group("/v1/public")
{
v1.GET("/getToken", func(c *gin.Context) {
judg := c.DefaultQuery("need", "no")
if judg == "yes" {
token, _ := GenerateToken(uint(rand.Intn(3)), true)
c.SetCookie("token", token, 3600, "/", "", false, true)
return
}
token, _ := GenerateToken(uint(rand.Intn(3)), false)
c.SetCookie("token", token, 3600, "/", "", false, true)
})
}
v2 := g.Group("/v2/admin")
{
v2.Use(MiddlewareParseJWT)
v2.GET("/info", func(c *gin.Context) {
user_id, _ := c.Get("user_id")
c.String(http.StatusOK, "Hello admin , your user id: %v", user_id)
})
}
g.Run(":8081")
}
func MiddlewareParseJWT(g *gin.Context) {
claims := &Claims{}
jwt_token, err := g.Cookie("token")
if err != nil {
}
_, err1 := jwt.ParseWithClaims(jwt_token, claims, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if err1 != nil {
}
if claims.Isadmin {
g.Set("user_id", claims.UserID)
g.Next()
} else {
g.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": fmt.Sprintf(
"you are not admin, your user_id is %d",
claims.UserID,
),
})
}
}
// 生成 JWT Token
func GenerateToken(user_id uint, is_admin bool) (string, error) {
claim := Claims{
UserID: user_id,
Isadmin: is_admin,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(2 * time.Hour)), //两小时有效期
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim)
return token.SignedString(jwtSecret)
}
//解析jwt
func ParserToken(tokenString string) (*Claims, error) {
claims := &Claims{}
_, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
return claims, err
}
理解一下,然后跑一下框架来验证一下,后面直接用轮子即可。
————————
并且由此也可以知道设置身份验证的中间件存在如下三种:全局中间件、单API中间件、路由组中间件,还是可以简单记一下这个。
gin框架暂时完结。
参考文章:
https://golangstar.cn/go_series/go_framework/gin.html#%E5%B0%8F%E7%BB%93