Gin框架
基本安装
1.首先需要安装Go(需要1.10+版本),然后可以使用下面的Go命令安装Gin。
go get -u github.com/gin-gonic/gin
2.将其导入您的代码中:
import “github.com/gin-gonic/gin”
使用范例:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "hello World!")
})
r.Run(":8000")
}
路由使用
路由的本质是前缀树,利用前缀树来实现路由的功能。建议使用postman来进行测试学习,省时省力
基本使用
路由路径的设置,遵循Restful风格(采用URL定位,HTTP描述操作):
router := gin.Default()
router.GET("/Get", getting)
router.POST("/Post", posting)
router.PUT("/Put", putting)
router.DELETE("/Delete", deleting)
router.Run()
路由分组
v1 := r.Group("/v1")
{
v1.GET("/hello", sayHello)
v1.GET("/world", sayWorld)
}
v2 := r.Group("/v2")
{
v2.GET("/hello", sayHello)
v2.GET("/world", sayWorld)
}
r.Run(":8080")
大量路由实现
当我们的路由变得非常多的时候,那么建议遵循以下步骤:
- 建立
routers 包,将不同模块拆分到多个go文件 - 每个文件提供
一个方法 ,该方法注册实现所有的路由 - 之后main方法在调用文件的方法实现注册
func LoadRouter(e *gin.Engine) {
e.Group("v1")
{
v1.GET("/post", postHandler)
v1.GET("/get", getHandler)
}
...
}
main文件实现:
func main() {
r := gin.Default()
routers.LoadRouter(r)
routers.LoadRouterXXX(r)
r.Run()
}
规模如果继续扩大也有更好的处理方式(建议别太大,将服务拆分好):
项目规模更大的时候,我们可以遵循以下步骤:
- 建立
routers 包,内部划分模块(包),每个包有个router.go 文件,负责该模块的路由注册
├── routers
│ │
│ ├── say
│ │ ├── sayWorld.go
│ │ └── router.go
│ │
│ ├── hello
│ │ ├── helloWorld.go
│ │ └── router.go
│ │
│ └── setup_router.go
│
└── main.go
- 建立
setup_router.go 文件,并编写以下方法:
type Register func(*gin.Engine)
func Init(routers ...Register) *gin.Engine {
rs := append([]Register{}, routers...)
r := gin.New()
for _, register := range rs {
register(r)
}
return r
}
- main.go中按如下方式写入需要注册的路由,可进行路由的初始化:
func main() {
r := routers.Init(
say.Routers,
hello.Routers,
)
r.Run(":8080")
}
获取参数
路径参数
: 只能匹配1个,* 可以匹配任意个数
router.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, "Hello %s", name)
})
router.GET("/user/:name/*action", func(c *gin.Context) {
name := c.Param("name")
action := c.Param("action")
message := name + " is " + action
c.String(http.StatusOK, message)
})
Get方法
- URL参数可以通过DefaultQuery()或Query()方法获取
- DefaultQuery()若参数不村则,返回默认值,Query()若不存在,返回空串
r.GET("/user", func(c *gin.Context) {
name := c.DefaultQuery("name", "normal")
age := c.Query("age")
c.String(http.StatusOK, fmt.Sprintf("hello %s, your age is %s", name, age))
})
Post方法
r.POST("/form", func(c *gin.Context) {
types := c.DefaultPostForm("type", "post")
username := c.PostForm("username")
password := c.PostForm("password")
name := c.Query("name")
c.JSON(200, gin.H{
"username": username,
"password": password,
"types": types,
"name": name,
})
})
文件获取
单个文件获取:
r.MaxMultipartMemory = 8 << 20
r.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.String(500, "上传文件出错")
}
c.SaveUploadedFile(file, "C:/desktop/"+file.Filename)
c.String(http.StatusOK, "fileName:", file.Filename)
})
多个文件获取(只展示核心部分):
form, err := c.MultipartForm()
if err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error()))
}
files := form.File["files"]
for _, file := range files {
fmt.Println(file.Filename)
}
c.String(200, fmt.Sprintf("upload ok %d files", len(files)))
接收处理
后面的例子都是基于该结构体开展:
type Login struct {
User string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}
设置校验
如果required 字段没有收到,错误日志会告知:
Error:Field validation for ‘User’ failed on the ‘required’ tag
除了在tag设置范围,例如
binding:"required,gt=10" =》 代表该值需要大于10
time_format:"2006-01-02" time_utc:"1" =》 时间格式
还允许**自定义校验方式:**gopkg.in/go-playground/validator.v8,待完善
content-type绑定(推荐)
使用Bind 方法,需要注意结构体需要先设置好tag 才行
r.POST("/loginJSON", func(c *gin.Context) {
var login Login
if err := c.Bind(&login); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "200",
"user": login.User,
"password": login.Password,
})
})
指定json绑定
使用Context提供的ShouldBindJSON 方法,注意发送的数据要是json才可以
r.POST("/loginJSON", func(c *gin.Context) {
var json Login
if err := c.ShouldBindJSON(&json); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "200",
"user": json.User,
"password": json.Password,
})
})
响应处理
数据返回类型
常见的三种响应数据:JSON 、XML 、YAML
r.GET("/someJSON", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "Json",
"status": 200,
})
})
r.GET("/someXML", func(c *gin.Context) {
c.XML(200, gin.H{"message": "abc"})
})
r.GET("/someYAML", func(c *gin.Context) {
c.YAML(200, gin.H{"name": "zhangsan"})
})
r.GET("/someProtoBuf", func(c *gin.Context) {
reps := []int64{1, 2}
data := &protoexample.Test{
Reps: reps,
}
c.ProtoBuf(200, data)
})
重定向
r.GET("/index", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
})
异步执行
r.GET("/long_async", func(c *gin.Context) {
copyContext := c.Copy()
go func() {
time.Sleep(3 * time.Second)
log.Println("异步执行:" + copyContext.Request.URL.Path)
}()
})
会话控制
cookie相关
r.GET("/getCookie", func(c *gin.Context) {
cookie, err := c.Cookie("key_cookie")
if err != nil {
cookie = "cookie"
c.SetCookie("key_cookie", "value_cookie",
60,
"/",
"localhost",
false,
true,
)
}
fmt.Printf("cookie的值是: %s\n", cookie)
})
session相关
- 导入包:go get -u “github.com/gin-contrib/sessions”
- 加入
session 中间件 (后面一节的内容展开将中间件,无需焦虑) - 采用
Get / Set + Save 来实现
package main
import (
"fmt"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
store := cookie.NewStore([]byte("secret"))
r.Use(sessions.Sessions("mySession", store))
r.GET("/setSession", func(c *gin.Context) {
session := sessions.Default(c)
session.Set("key", "value")
session.Save()
})
r.GET("/getSession", func(c *gin.Context) {
session := sessions.Default(c)
v := session.Get("key")
fmt.Println(v)
})
r.Run(":8080")
}
token相关
通常为了分布式和安全性,我们会采取更好的方式,比如使用token 认证,来实现跨域访问,避免 CSRF 攻击,还能在多个服务间共享。
中间件
学过Java的同学可以把中间件 类比为拦截器 ,作用就是在处理具体的route请求时,提前做一些业务,还可以在业务执行完后执行一些操作。比如身份校验、日志打印等操作。
中间件分为:全局中间件 和 路由中间件,区别在于前者会作用于所有路由。
其实使用router := gin.Default() 定义route时,默认带了Logger() 和Recovery() 。
默认中间件
Gin本身也提供了一些中间件给我们使用:
func BasicAuth(accounts Accounts) HandlerFunc
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc
func Bind(val interface{}) HandlerFunc
func ErrorLogger() HandlerFunc
func ErrorLoggerT(typ ErrorType) HandlerFunc
func Logger() HandlerFunc
func LoggerWithConfig(conf LoggerConfig) HandlerFunc
func LoggerWithFormatter(f LogFormatter) HandlerFunc
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc
func Recovery() HandlerFunc
func RecoveryWithWriter(out io.Writer) HandlerFunc
func WrapF(f http.HandlerFunc) HandlerFunc
func WrapH(h http.Handler) HandlerFunc
自定义中间件
自定义中间件的方式很简单,我们只需要实现一个函数,返回gin.HandlerFunc 类型的参数即可:
type HandlerFunc func(*Context)
示例代码,完成日志打印(输出客户ip + 发送request):
func MyLogMiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("[MyLog] 用户ip:", c.ClientIP())
fmt.Println("[MyLog] 用户request:", c.Request)
}
}
func main() {
r := gin.Default()
r.Use(MyLogMiddleWare())
r.GET("/say", func(c *gin.Context) {
c.String(200, "request: %s", c.Request)
})
r.Run(":8080")
}
中间件控制的方法
gin提供了两个函数Abort() 和Next() ,二者区别在于:
- next()函数会跳过当前中间件中next()后的逻辑,当下一个中间件执行完成后再执行剩余的逻辑
- abort()函数执行终止当前中间件以后的中间件执行,但是会执行当前中间件的后续逻辑
举例子更好理解:
我们注册中间件顺序为m1 、m2 、m3 ,如果采用next() :
执行顺序就是
m1的next()前面 、m2的next()前面 、m3的next()前面 、业务逻辑 m3的next()后续 、m2的next()后续 、m1的next()后续 。
那如果m2 中间调用了Abort() ,则m3 和业务逻辑 不会执行,只会执行m2的next()后续 、m1的next()后续 。
局部中间件
如果我们自定义的中间件只需要在某个路由上使用,只需要在该路由路径上使用该方法即可,可以从GET() 方法,看到本质。
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)
}
中间件使用:
r.GET("/test", MyLogMiddleWare(), func(c *gin.Context) {
c.JSON(200, gin.H{"success": "ok"})
})
v1 := r.Group("v1", MyLogMiddleWare())
v1.GET("/c1", func(c *gin.Context) {
c.JSON(200, gin.H{"request": "ok"})
})
v1.GET("/c2", func(c *gin.Context) {
c.JSON(200, gin.H{"request": "ok"})
})
处理后续工作
我们还可以使用中间件来处理一下后续工作,巧用next() 来实现后续工作。
func CalcTimeMiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
since := time.Since(start)
fmt.Println("程序用时:", since)
}
}
func main() {
r := gin.Default()
r.GET("/time", CalcTimeMiddleWare(), func(c *gin.Context) {
time.Sleep(2 * time.Second)
c.String(200, "ok")
})
r.Run(":8080")
}
输出结果:
程序用时: 2.0002348s
[GIN] 2021/09/26 - 15:40:48 | 200 | 2.0002348s | ::1 | GET "/time"
身份验证中间件
还可以实现基于cookie 的身份验证中间件。
核心代码:
func AuthMiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
if cookie, err := c.Cookie("key_cookie"); err == nil {
if cookie == "value_cookie" {
return
}
}
c.JSON(http.StatusUnauthorized, gin.H{"error": "err"})
c.Abort()
}
}
测试程序是否正确执行:
func main() {
r := gin.Default()
r.GET("/loginIn", func(c *gin.Context) {
_, err := c.Cookie("key_cookie")
if err != nil {
c.SetCookie("key_cookie", "value_cookie",
10,
"/",
"localhost",
false,
true,
)
c.String(200, "login success")
return
}
c.String(200, "already login")
})
r.GET("/sayHello", AuthMiddleWare(), func(c *gin.Context) {
c.String(200, "Hello World!")
})
r.Run(":8080")
}
测试步骤:
- 首先不登陆直接访问
localhost:8080/sayHello ,由于检测不到cookie 会显示 {"error":"err"} - 接下来访问
localhost:8080/loginIn ,第一次访问会显示:login success ,在有效期10s内,再次访问会显示:already login - 在有效期内,访问
localhost:8080/sayHello ,会显示Hello World! ,代表登陆成功 - 等待有效期超过,再次访问
localhost:8080/sayHello ,会显示 {"error":"err"} ,代表身份过期
未读完待续,后续会继续增加Gin框架相关的运用,例如token 、日志 等中间件的实现模板,有需求可留言讨论
|