路由
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()
驗證碼
執行邏輯:
- 先在session裡寫入鍵值對(k->v),把值寫在圖片上,然後生成圖片,顯示在瀏覽器上面
- 前端將驗證碼傳送給後後端,後端根據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
對資料data1
有read
許可權,lizi
對資料data2
有write
許可權
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
載入policy
,Gorm 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-...