Go學習筆記-Gin常用功能

IT小馬發表於2022-05-25

路由

gin 框架中採用的路由庫是基於httprouter做的

Restful風格的API

Representational State Transfer 表現層狀態轉化,是一種網際網路應用程式的API設計理念:

  • URL定位資源,用HTTP描述操作
  • 增 POST / 刪 DELETE / 改 PUT / 查 GET

引數

  • API引數:Param方法

    r.GET("/:name/*action", func(c *gin.Context) {
            name := c.Param("name")
            action := c.Param("action")        // action = /yyy
            action = strings.Trim(action, "/") //擷取
            c.String(http.StatusOK, name+" - "+action)
        })
  • URL引數

    • DefaultQuery():引數不存在,返回預設值

      c.DefaultQuery("name", "枯藤")
    • Query():引數不存在,返回空
  • 表單引數:PostForm方法

    types := c.DefaultPostForm("type", "post")
    username := c.PostForm("username")

上傳檔案

  • multipart/form-data格式用於檔案上傳
  • gin檔案上傳與原生的net/http方法類似,不同在於gin把原生的request封裝到c.Request中

上傳單個檔案

<form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
      上傳檔案:<input type="file" name="file" >
      <input type="submit" value="提交">
</form>
//限制上傳最大尺寸
r.MaxMultipartMemory = 8 << 20
r.POST("/upload", func(c *gin.Context) {
    file, err := c.FormFile("file")
    if err != nil {
        c.String(500, "上傳圖片出錯")
        return
    }
    // c.JSON(200, gin.H{"message": file.Header.Context})
    c.SaveUploadedFile(file, file.Filename)
    c.String(http.StatusOK, file.Filename)
})

上傳限制

  • 限制檔案型別:headers.Header.Get("Content-Type")
  • 限制檔案大小:headers.Size
r.POST("/upload-limit", func(c *gin.Context) {
    _, headers, err := c.Request.FormFile("file")
    if err != nil {
        c.String(500, "上傳圖片出錯")
    return
    }
    if headers.Size > 1024*1024*2 {
        c.String(500, "圖片太大了")
        return
    }
    t := headers.Header.Get("Content-Type")
    if t != "image/jpeg" {
        c.String(500, "圖片格式錯誤:"+t)
        return
    }
    // c.JSON(200, gin.H{"message": file.Header.Context})
    c.SaveUploadedFile(headers, "./upload/"+headers.Filename)
    c.String(http.StatusOK, headers.Filename)
})

上傳多個檔案

<form action="http://localhost:8000/upload" method="post" enctype="multipart/form-data">
      上傳檔案:<input type="file" name="files" multiple>
      <input type="submit" value="提交">
</form>
r.POST("/upload", func(c *gin.Context) {
   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 {
      // 逐個存
      if err := c.SaveUploadedFile(file, file.Filename); err != nil {
         c.String(http.StatusBadRequest, fmt.Sprintf("upload err %s", err.Error()))
         return
      }
   }
   c.String(200, fmt.Sprintf("upload ok %d files", len(files)))
})

分組 Group

v1 := r.Group("/v1")
{
   v1.GET("/login", login)
   v1.GET("submit", submit)
}

資料解析和繫結

type Login struct {
   // binding:"required"修飾的欄位,若接收為空值,則報錯,是必須欄位
   User    string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
   Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}

Json

var json Login
// 將request的body中的資料,自動按照json格式解析到結構體
c.ShouldBindJSON(&json); //json.User json.Password

表單

<form action="http://localhost:8000/loginForm" method="post" enctype="application/x-www-form-urlencoded">
    使用者名稱<input type="text" name="username"><br>
    密碼<input type="password" name="password">
    <input type="submit" value="提交">
</form>
var form Login
c.Bind(&form); // form.User form.Password

URI

r.GET("/:user/:password", func(c *gin.Context) {
    var login Login
    c.ShouldBindUri(&login);// login.User login.Password
}  

渲染

資料渲染

  • JSON

    c.JSON(200, gin.H{"msg": "json"}) //{"msg":"json"}
  • XML

    c.XML(200, gin.H{"msg": "xml"}) //<map><msg>xml</msg></map>
  • YAML

    c.YAML(200, gin.H{"msg": "yaml"}) //檔案下載 msg: yaml
  • ProtoBuf

    reps := []int64{int64(1), int64(2)}
    // 定義資料
    label := "label"
    // 傳protobuf格式資料
    data := &protoexample.Test{
        Label: &label,
        Reps:  reps,
    }
    r.GET("/proto", func(c *gin.Context) {
        c.ProtoBuf(200, data) //檔案下載 label
    })

HTML渲染

  • gin支援載入HTML模板, 然後根據模板引數進行配置並返回相應的資料,本質上就是字串替換
  • LoadHTMLGlob()方法可以載入模板檔案
r.LoadHTMLGlob("html/*.html")
r.GET("/html", func(c *gin.Context) {
    c.HTML(200, "index.html", gin.H{"name": "test"})
})

重定向

//301重定向
c.Redirect(http.StatusMovedPermanently, "https://blog.itxiaoma.cn")

非同步

  • 在啟動新的goroutine時,不應該使用原始上下文,必須使用它的只讀副本
copyContext := c.Copy() // Context副本
go func() {
   time.Sleep(3 * time.Second)
   log.Println("非同步執行:" + copyContext.Request.URL.Path) //非同步執行:/async
}()
c.String(200, "Sync...")

中介軟體

  • 中介軟體(Middleware)指的是可以攔截http請求-響應生命週期的特殊函式
  • Gin預設使用了Logger(), Recovery()兩個全域性中介軟體

    //去除預設全域性中介軟體
    r := gin.New()//不帶中介軟體

全域性中介軟體

func FullMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Set("request", "中介軟體")
    }
}
r.Use(FullMiddleware())//使用中介軟體
r.GET("/middleware", func(c *gin.Context) {
   req, _ := c.Get("request")
   c.String(200, req.(string)) 
})

Next

  • c.Next() 之前的操作是在 Handler 執行之前就執行;一般做驗證處理,訪問是否允許。
  • c.Next() 之後的操作是在 Handler 執行之後再執行;一般是做總結處理,比如格式化輸出、響應結束時間,響應時長計算。
func NextMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        fmt.Println("中介軟體開始執行")
        c.Next()
        fmt.Println("中介軟體執行完畢")
    }
}

func NextResponse(r *gin.Engine) {
    r.Use(NextMiddleware())
    r.GET("/next", func(c *gin.Context) {
        fmt.Println("請求執行...")
        // 中介軟體開始執行
        // 請求執行...
        // 中介軟體執行完畢
    })
}

Abort

  • 表⽰終⽌,也就是說,執⾏Abort的時候會停⽌所有的後⾯的中介軟體函式的調⽤。
func AbortMiddleware() gin.HandlerFunc {
   return func(c *gin.Context) {
      fmt.Println("中介軟體開始執行")
      if c.Query("key") == "abort" {
         c.String(200, "Abort")
         c.Abort()
      }
   }
}

func AbortResponse(r *gin.Engine) {
   r.Use(AbortMiddleware())
   r.GET("/abort", func(c *gin.Context) {
      fmt.Println("請求執行...")
      c.String(200, "OK")
   })
}

區域性中介軟體

//區域性中介軟體
func PartMiddleware() gin.HandlerFunc {
   return func(c *gin.Context) {
      fmt.Println("中介軟體開始執行")
   }
}

func PartResponse(r *gin.Engine) {
   r.GET("/part", PartMiddleware(), func(c *gin.Context) {
      fmt.Println("請求執行...")
      // 中介軟體開始執行
      // 請求執行...
   })
}

中介軟體推薦

  • RestGate - REST API端點的安全身份驗證
  • staticbin - 用於從二進位制資料提供靜態檔案的中介軟體/處理程式
  • gin-cors - CORS杜松子酒的官方中介軟體
  • gin-csrf - CSRF保護
  • gin-health - 通過gocraft/health報告的中介軟體
  • gin-merry - 帶有上下文的漂亮 列印 錯誤的中介軟體
  • gin-revision - 用於Gin框架的修訂中介軟體
  • gin-jwt - 用於Gin框架的JWT中介軟體
  • gin-sessions - 基於mongodb和mysql的會話中介軟體
  • gin-location - 用於公開伺服器的主機名和方案的中介軟體
  • gin-nice-recovery - 緊急恢復中介軟體,可讓您構建更好的使用者體驗
  • gin-limit - 限制同時請求;可以幫助增加交通流量
  • gin-limit-by-key - 一種記憶體中的中介軟體,用於通過自定義鍵和速率限制訪問速率。
  • ez-gin-template - gin簡單模板包裝
  • gin-hydra - gin中介軟體Hydra
  • gin-glog - 旨在替代Gin的預設日誌
  • gin-gomonitor - 用於通過Go-Monitor公開指標
  • gin-oauth2 - 用於OAuth2
  • static gin框架的替代靜態資產處理程式。
  • xss-mw - XssMw是一種中介軟體,旨在從使用者提交的輸入中“自動刪除XSS”
  • gin-helmet - 簡單的安全中介軟體集合。
  • gin-jwt-session - 提供JWT / Session / Flash的中介軟體,易於使用,同時還提供必要的調整選項。也提供樣品。
  • gin-template - 用於gin框架的html / template易於使用。
  • gin-redis-ip-limiter - 基於IP地址的請求限制器。它可以與redis和滑動視窗機制一起使用。
  • gin-method-override - _method受Ruby的同名機架啟發而被POST形式引數覆蓋的方法
  • gin-access-limit - limit-通過指定允許的源CIDR表示法的訪問控制中介軟體。
  • gin-session - 用於Gin的Session中介軟體
  • gin-stats - 輕量級和有用的請求指標中介軟體
  • gin-statsd - 向statsd守護程式報告的Gin中介軟體
  • gin-health-check - check-用於Gin的健康檢查中介軟體
  • gin-session-middleware - 一個有效,安全且易於使用的Go Session庫。
  • ginception - 漂亮的例外頁面
  • gin-inspector - 用於調查http請求的Gin中介軟體。
  • gin-dump - Gin中介軟體/處理程式,用於轉儲請求和響應的標頭/正文。對除錯應用程式非常有幫助。
  • go-gin-prometheus - Gin Prometheus metrics exporter
  • ginprom - Gin的Prometheus指標匯出器
  • gin-go-metrics - Gin middleware to gather and store metrics using rcrowley/go-metrics
  • ginrpc - Gin 中介軟體/處理器自動繫結工具。通過像beego這樣的註釋路線來支援物件註冊

原文: https://github.com/gin-gonic/...

會話控制

Cookie

func CookieHandler(r *gin.Engine) {
   r.GET("/cookie", func(c *gin.Context) {
      cookie, err := c.Cookie("test")
      if err != nil {
         c.SetCookie(
            "test",
            "value",
            60,          //maxAge int, 單位為秒
            "/",         //path,cookie所在目錄
            "localhost", //domain string,域名
            false,       //secure 是否智慧通過https訪問
            true,        //httpOnly bool  是否允許別人通過js獲取自己的cookie
         )
      }
      c.String(200, cookie)
   })
}

Cookie校驗

func AuthMiddleWare() gin.HandlerFunc {
   return func(c *gin.Context) {
      // 獲取客戶端cookie並校驗
      if cookie, err := c.Cookie("abc"); err == nil {
         if cookie == "123" {
            c.Next()
            return
         }
      }
      // 返回錯誤
      c.JSON(http.StatusUnauthorized, gin.H{"error": "err"})
      // 若驗證不通過,不再呼叫後續的函式處理
      c.Abort()
      return
   }
}

Session

安裝

go get github.com/gin-contrib/sessions

使用

var store = cookie.NewStore([]byte("secret"))

func SessionHandler(r *gin.Engine) {
   r.GET("/session",
      sessions.Sessions("mysession", store), //路由上加入session中介軟體
      func(c *gin.Context) {
         session := sessions.Default(c)
         if session.Get("name") != "itxiaoma" {
            session.Set("name", "itxiaoma")
            //記著呼叫save方法,寫入session
            session.Save()
         }
         c.JSON(200, gin.H{"name": session.Get("name")}) //{"name":"itxiaoma"}
      })
}

引數驗證

用gin框架的資料驗證,可以不用解析資料,減少if else,會簡潔許多。

結構體驗證

type Person struct {
   Name string `form:"name"`
}

func JsonHandler(r *gin.Engine) {
   r.GET("/structure", func(c *gin.Context) {
      var person Person
      c.ShouldBind(&person)
      c.String(200, fmt.Sprintf("%#v", person))
      //訪問:http://localhost:8080/structure?name=xxx
      //輸出:structure.Person{Name:"xxx"}
   })
}

自定義驗證

對繫結解析到結構體上的引數,自定義驗證功能

官網示例:https://github.com/gin-gonic/...

引入

go get github.com/go-playground/validator/v10

使用:

package validator

import (
    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    "github.com/go-playground/validator/v10"
    "net/http"
)

type Person struct {
    Name string `form:"name" binding:"NotAdmin"` // 1、自定義註冊名稱
}

// 2、自定義校驗方法
var notAdmin validator.Func = func(fl validator.FieldLevel) bool {
    name, ok := fl.Field().Interface().(string)
    if ok {
        return name != "admin"
    }
    return true
}

func MyValidatorHandler(r *gin.Engine) {
    // 3、將自定義的校驗方法註冊到 validator 中
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        // 這裡的 key 和 fn 可以不一樣最終在 struct 使用的是 key
        v.RegisterValidation("NotAdmin", notAdmin)
    }
    r.GET("/validator", func(c *gin.Context) {
        var person Person
        if e := c.ShouldBind(&person); e == nil {
            c.String(http.StatusOK, "%v", person)
        } else {
            c.String(http.StatusOK, "person bind err:%v", e.Error())
            //person bind err:Key: 'Person.Name' Error:Field validation for 'Name' failed on the 'NotAdmin' tag
        }
    })
}

Multipart/Urlencoded 繫結

package main

import (
    "github.com/gin-gonic/gin"
)

type LoginForm struct {
    User     string `form:"user" binding:"required"`
    Password string `form:"password" binding:"required"`
}

func main() {
    router := gin.Default()
    router.POST("/login", func(c *gin.Context) {
        // 你可以使用顯式繫結宣告繫結 multipart form:
        // c.ShouldBindWith(&form, binding.Form)
        // 或者簡單地使用 ShouldBind 方法自動繫結:
        var form LoginForm
        // 在這種情況下,將自動選擇合適的繫結
        if c.ShouldBind(&form) == nil {
            if form.User == "user" && form.Password == "password" {
                c.JSON(200, gin.H{"status": "you are logged in"})
            } else {
                c.JSON(401, gin.H{"status": "unauthorized"})
            }
        }
    })
    router.Run(":8080")
}

測試:

curl -v --form user=user --form password=password http://localhost:8080/login

其他型別:URL引數

c.ShouldBindWith(&p, binding.Query);

其他

日誌

f, _ := os.Create("log/gin.log")
gin.DefaultWriter = io.MultiWriter(f)
r = gin.Default()
r.GET("/log", func(c *gin.Context) {
   //同時將日誌寫入檔案和控制檯
   //gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
   c.String(200, "log ok")
})
r.Run()

驗證碼

執行邏輯:

  1. 先在session裡寫入鍵值對(k->v),把值寫在圖片上,然後生成圖片,顯示在瀏覽器上面
  2. 前端將驗證碼傳送給後後端,後端根據session中的k取得v,比對校驗
package captcha

import (
   "bytes"
   "github.com/dchest/captcha"
   "github.com/gin-contrib/sessions"
   "github.com/gin-contrib/sessions/cookie"
   "github.com/gin-gonic/gin"
   "net/http"
   "time"
)

var sessionMaxAge = 3600
var sessionSecret = "itxiaoma"

func SessionMiddleware(keyPairs string) gin.HandlerFunc {
   var store sessions.Store
   store = cookie.NewStore([]byte(sessionSecret))
   store.Options(sessions.Options{
      MaxAge: sessionMaxAge, //seconds
      Path:   "/",
   })
   return sessions.Sessions(keyPairs, store)
}

func Captcha(c *gin.Context, length ...int) {
   l := captcha.DefaultLen
   w, h := 107, 36
   if len(length) == 1 {
      l = length[0]
   }
   if len(length) == 2 {
      w = length[1]
   }
   if len(length) == 3 {
      h = length[2]
   }
   captchaId := captcha.NewLen(l)
   session := sessions.Default(c)
   session.Set("captcha", captchaId)
   _ = session.Save()
   _ = Serve(c.Writer, c.Request, captchaId, ".png", "zh", false, w, h)
}

func CaptchaVerify(c *gin.Context, code string) bool {
   session := sessions.Default(c)
   if captchaId := session.Get("captcha"); captchaId != nil {
      session.Delete("captcha")
      _ = session.Save()
      if captcha.VerifyString(captchaId.(string), code) {
         return true
      } else {
         return false
      }
   } else {
      return false
   }
}

func Serve(w http.ResponseWriter, r *http.Request, id, ext, lang string, download bool, width, height int) error {
   w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
   w.Header().Set("Pragma", "no-cache")
   w.Header().Set("Expires", "0")

   var content bytes.Buffer
   switch ext {
   case ".png":
      w.Header().Set("Content-Type", "image/png")
      _ = captcha.WriteImage(&content, id, width, height)
   case ".wav":
      w.Header().Set("Content-Type", "audio/x-wav")
      _ = captcha.WriteAudio(&content, id, lang)
   default:
      return captcha.ErrNotFound
   }

   if download {
      w.Header().Set("Content-Type", "application/octet-stream")
   }
   http.ServeContent(w, r, id+ext, time.Time{}, bytes.NewReader(content.Bytes()))
   return nil
}

func main() {
  r := gin.Default()
  r.LoadHTMLGlob("captcha/*.html")
  r.Use(SessionMiddleware("itxiaoma"))
  r.GET("/captcha", func(c *gin.Context) {
     Captcha(c, 4)
  })
  r.GET("/captcha-html", func(c *gin.Context) {
     c.HTML(http.StatusOK, "captcha.html", nil)
  })
  r.GET("/captcha/verify/:value", func(c *gin.Context) {
     value := c.Param("value")
     if CaptchaVerify(c, value) {
        c.JSON(http.StatusOK, gin.H{"status": 0, "msg": "success"})
     } else {
        c.JSON(http.StatusOK, gin.H{"status": 1, "msg": "failed"})
     }
  })
  r.Run()
}

JWT

package jwt

import (
   "fmt"
   "github.com/dgrijalva/jwt-go"
   "github.com/gin-gonic/gin"
   "net/http"
   "time"
)

//自定義字串
var jwtkey = []byte("itxiaoma")
var str string

type Claims struct {
   UserId uint
   jwt.StandardClaims
}

//頒發token
func setting(ctx *gin.Context) {
   expireTime := time.Now().Add(7 * 24 * time.Hour)
   claims := &Claims{
      UserId: 2,
      StandardClaims: jwt.StandardClaims{
         ExpiresAt: expireTime.Unix(), //過期時間
         IssuedAt:  time.Now().Unix(),
         Issuer:    "127.0.0.1",  // 簽名頒發者
         Subject:   "user token", //簽名主題
      },
   }
   token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
   // fmt.Println(token)
   tokenString, err := token.SignedString(jwtkey)
   if err != nil {
      fmt.Println(err)
   }
   str = tokenString
   ctx.JSON(200, gin.H{"token": tokenString})
}

//解析token
func getting(ctx *gin.Context) {
   tokenString := ctx.GetHeader("Authorization")
   fmt.Println(tokenString)
   //vcalidate token formate
   if tokenString == "" {
      ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "許可權不足1"})
      ctx.Abort()
      return
   }

   token, claims, err := ParseToken(tokenString)
   if err != nil || !token.Valid {
      ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "許可權不足2"})
      ctx.Abort()
      return
   }
   ctx.JSON(200, gin.H{"claims": claims})
}

func ParseToken(tokenString string) (*jwt.Token, *Claims, error) {
   Claims := &Claims{}
   token, err := jwt.ParseWithClaims(tokenString, Claims, func(token *jwt.Token) (i interface{}, err error) {
      return jwtkey, nil
   })
   return token, Claims, err
}

func main() {
     r := gin.Default()
   r.GET("/set-jwt", setting)
   r.GET("/get-jwt", getting)
   r.Run()
}

除錯

curl http://localhost:8080/get-jwt -H "Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjIsImV4cCI6MTY1NDA1NzQ1MCwiaWF0IjoxNjUzNDUyNjUwLCJpc3MiOiIxMjcuMC4wLjEiLCJzdWIiOiJ1c2VyIHRva2VuIn0.IN_Tj-M6CMHFlunnRIvUgog2GMDyWpj7iOsjwUeD0Sk"

許可權管理 Casbin

Casbin是用於Golang專案的功能強大且高效的開源訪問控制庫。

許可權實際上就是控制能對什麼資源進行什麼操作。

Casbin將訪問控制模型抽象到一個基於 PERM(Policy,Effect,Request,Matchers) 元模型的配置檔案(模型檔案)中。因此切換或更新授權機制只需要簡單地修改配置檔案。

引入:

go get github.com/casbin/casbin/v2

模型檔案 model.conf

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
  • request是對訪問請求的抽象,它與e.Enforce()函式的引數是一一對應的
  • policy是策略或者說是規則的定義。它定義了具體的規則。
  • request是對訪問請求的抽象,它與e.Enforce()函式的引數是一一對應的matcher匹配器會將請求與定義的每個policy一一匹配,生成多個匹配結果。
  • effect根據對請求運用匹配器得出的所有結果進行彙總,來決定該請求是允許還是拒絕。

sub:訪問實體 obj:訪問物件 act:訪問動作

上面模型檔案規定了許可權由sub,obj,act三要素組成,只有在策略列表中有和它完全相同的策略時,該請求才能通過。匹配器的結果可以通過p.eft獲取,some(where (p.eft == allow))表示只要有一條策略允許即可。

檔案儲存策略 policy.csv

即誰能對什麼資源進行什麼操作:

p, dajun, data1, read
p, lizi, data2, write

檔案的兩行內容表示dajun對資料data1read許可權,lizi對資料data2write許可權

func check(e *casbin.Enforcer, sub, obj, act string) {
  ok, _ := e.Enforce(sub, obj, act)
  if ok {
    fmt.Printf("%s CAN %s %s\n", sub, act, obj)
  } else {
    fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
  }
}

func main() {
  e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
  if err != nil {
    log.Fatalf("NewEnforecer failed:%v\n", err)
  }

  check(e, "dajun", "data1", "read") //dajun CAN read data1
  check(e, "lizi", "data2", "write") //lizi CAN write data2
  check(e, "dajun", "data1", "write")//dajun CANNOT write data1
  check(e, "dajun", "data2", "read") //dajun CANNOT read data2
}

超級管理員:

[matchers]
e = r.sub == p.sub && r.obj == p.obj && r.act == p.act || r.sub == "root"

校驗

check(e, "root", "data1", "read")
check(e, "root", "data2", "write")
check(e, "root", "data1", "execute")
check(e, "root", "data3", "rwx")

RBAC模型

模型檔案:

[role_definition]
g = _, _

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act

g = _,_定義了使用者——角色,角色——角色的對映關係,前者是後者的成員,擁有後者的許可權。

g(r.sub, p.sub)用來判斷請求主體r.sub是否屬於p.sub這個角色。

Gorm Adapter

資料:

CREATE DATABASE IF NOT EXISTS casbin;

USE casbin;

CREATE TABLE IF NOT EXISTS casbin_rule (
  p_type VARCHAR(100) NOT NULL,
  v0 VARCHAR(100),
  v1 VARCHAR(100),
  v2 VARCHAR(100),
  v3 VARCHAR(100),
  v4 VARCHAR(100),
  v5 VARCHAR(100)
);

INSERT INTO casbin_rule VALUES
('p', 'dajun', 'data1', 'read', '', '', ''),
('p', 'lizi', 'data2', 'write', '', '', '');

然後使用Gorm Adapter載入policyGorm Adapter預設使用casbin庫中的casbin_rule表:

package main

import (
  "fmt"

  "github.com/casbin/casbin/v2"
  gormadapter "github.com/casbin/gorm-adapter/v2"
  _ "github.com/go-sql-driver/mysql"
)

func check(e *casbin.Enforcer, sub, obj, act string) {
  ok, _ := e.Enforce(sub, obj, act)
  if ok {
    fmt.Printf("%s CAN %s %s\n", sub, act, obj)
  } else {
    fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
  }
}

func main() {
  a, _ := gormadapter.NewAdapter("mysql", "root:12345@tcp(127.0.0.1:3306)/")
  e, _ := casbin.NewEnforcer("./model.conf", a)

  check(e, "dajun", "data1", "read")
  check(e, "lizi", "data2", "write")
  check(e, "dajun", "data1", "write")
  check(e, "dajun", "data2", "read")
}

執行:

dajun CAN read data1
lizi CAN write data2
dajun CANNOT write data1
dajun CANNOT read data2

其他

Gin中文文件:https://learnku.com/docs/gin-...

Reference

gin框架

Gin 框架中文文件

gin 中使用session

Gin+Gorm+Casbin實現許可權控制demo

Go 每日一庫之 casbin

相關文章