gin学习

文章目录

      • 零、知识补充
        • GOPROXY地址
      • 一、准备工作
        • 1、安装gin包(mod模式)
        • 2、文档
        • 3、测试 hello gin
      • 二、GET POST PUT DELETE请求的使用
        • 1、修改端口号
        • 2、GET 查
        • 3、POST 增
        • 4、DELETE 删
        • 5、PUT 改
        • 6、如何取出参数
          • 6.1、GET
          • 6.2、POST DELETE PUT
          • 6.3、URI
      • 三、Bind模式获取参数和表单验证
        • 3.1、bind模式如何使用
        • 3.2、MustBind
        • 3.3、ShouldBind
      • 四、gin对于文件的接收和返回
        • 1、读取文件
        • 2、本地写文件
        • 3、给前端返回文件
      • 五、gin中间件和路由分组
        • 1、什么是分组(如何创建)
        • 2、为什么要分组
        • 3、什么是中间件
        • 4、如何使用中间件
        • 5、如何创建中间件
      • 六、gin日志和日志格式
        • 1、为什么要使用日志
        • 2、gin自带日志写入中间件
        • 3、第三方日志中工具
        • 4、日志切割
      • 七、gorm初探
        • 1、什么是orm
        • 2、orm如何链接数据库
        • 3、自动化创建数据库表
        • 4、最最简单的增删改查
      • 八、结构体的创建技巧和结合gin使用
        • 1、tag设置
        • 2、自定义表名(动态表名)
        • 3、结构体声明 1对1 1对多 多对多
        • 4、使用gin接受参数并且经过处理入库或者返回
      • 九、jwt-go
        • 1、什么是jwt
        • 2、如何创建一个JWT
          • 两种常用 Claims的实现方式
            • 匿名函数实现接口
            • map形式
          • 创建一个Token
        • 3、如何解析一个JWT
      • 十、casbin模型
        • 1、casbin模型基础
          • 1.1、PERM 元模型
          • 1.2、role_definition 角色域
        • 2、实战模型
          • 2.1、RBAC
          • 2.2、RBAC with domains/tenants
        • 3、实战策略
          • 3.1、角色为基础的
          • 3.2、带域的
      • 十一、使用casbin
        • 1、本地文件模式初体验
        • 2、使用数据库存储policy
        • 3、对policy进行增删改查
        • 4、自定义比较函数


golang 1010工作室
视频地址:https://www.bilibili.com/video/BV12i4y1x7AG/?spm_id_from=333.788&vd_source=eba330e2ab2e59ae2e4ecace161e0983

零、知识补充

URI和URL的概念和区别 - 掘金 (juejin.cn)

URL是统一资源定位符(像人的地址),URI是统一资源标识符(像人的身份证)

GOPROXY地址

GOPROXY=https://goproxy.cn,direct

一、准备工作

1、安装gin包(mod模式)
  • go 版本 >= v1.11
  • 的东西可能无法下载 GOPROXY=https://goproxy.io
  • GO111MODULE = auto
  • go mod init 你自己想叫的项目名字 包名字 或者github项目地址 如果你开发的是一个共用包的话 最好是github地址
2、文档

https://www.kancloud.cn/shuangdeyu/gin_book/949411

golang找不到对应的包。go get github.com/gin-gonic/gin

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

func main() {
   r := gin.Default() //携带基础中间件启动路由
   r.GET("/ping", func(c *gin.Context) {
      c.JSON(200, gin.H{
         "message": "pong",
      })
   })
   r.Run() // listen and serve on 0.0.0.0:8080
}

找到一个最适合自己的中文文档

3、测试 hello gin

localhost:8080/ping

二、GET POST PUT DELETE请求的使用

1、修改端口号
Run(":1010")
2、GET 查

参数挂在url中 uri中传参

r := gin.Default() //携带基础中间件    启动路由
r.GET("/path/:id", func(c *gin.Context) {
   //c 上下文
   id := c.Param("id")
   //user和pwd 地址栏后面的,用query传参.默认传参,如果不存在给一个默认值
   user := c.DefaultQuery("user", "qimiao")
   pwd := c.Query("pwd")

   c.JSON(200, gin.H{
      "id":   id,
      "user": user,
      "pwd":  pwd,
   })
})
r.Run()

gin学习_第1张图片

3、POST 增

参数在form body中 或者uri

r.POST("/path", func(c *gin.Context) {
   //从form表单中取数据
   user := c.DefaultPostForm("user", "qimiao")
   pwd := c.PostForm("pwd")
   c.JSON(200, gin.H{
      "user": user,
      "pwd":  pwd,
   })
})

gin学习_第2张图片

4、DELETE 删

一般情况为uri 同样也可以用body

r.DELETE("/path/:id", func(c *gin.Context) {
   id := c.Param("id")
   c.JSON(200, gin.H{
      "id": id,
   })
})

gin学习_第3张图片

5、PUT 改

参数在form body 或者uri

r.PUT("/path", func(c *gin.Context) {
   //从form表单中取数据
   user := c.DefaultPostForm("user", "qimiao")
   pwd := c.PostForm("pwd")
   c.JSON(200, gin.H{
      "user": user,
      "pwd":  pwd,
   })
})

gin学习_第4张图片

6、如何取出参数
6.1、GET

一般情况下为 地址栏的query

   //user和pwd 地址栏后面的,用query传参.默认传参,如果不存在给一个默认值
   user := c.DefaultQuery("user", "qimiao")
   pwd := c.Query("pwd")
6.2、POST DELETE PUT

一般情况下 为FORM参数

   //从form表单中取数据
   user := c.DefaultPostForm("user", "qimiao")
   pwd := c.PostForm("pwd")
6.3、URI

地址栏定义占位符 通过占位符取参数

三、Bind模式获取参数和表单验证

3.1、bind模式如何使用

bind模式一定要设置tag

如果一个字段用binding:"required"修饰,并且在绑定时该字段的值为空,那么将返回一个错误。json uri form

type PostParams struct {
	Name string `json:"name" uri:"name" form:"name"`
	Sex bool	`json:"sex"  uri:"sex"  form:"sex"`
}

func main() {
	r := gin.Default()
	//r.POST("/restBind", func(c *gin.Context) {
	//r.POST("/restBind/:name/:sex", func(c *gin.Context) {
	r.POST("/restBind", func(c *gin.Context) {
		var p PostParams
		//err := c.ShouldBindJSON(&p)
		//err := c.ShouldBindUri(&p)   //restBind/你好/true
		err := c.ShouldBindQuery(&p)   //restBind?name=qimiao&sex=true
		if err != nil {
			c.JSON(400, gin.H{
				"mes":  "报错了",
				"data": gin.H{},
			})
		} else {
			c.JSON(200, gin.H{
				"mes":  "成功",
				"data": p,
			})
		}
	})

	r.Run(":8080")
}
3.2、MustBind
3.3、ShouldBind
  • 表单验证
binding:"required"  //传入参数的不能为空
  • 自定义验证

gin学习_第5张图片

type PostParams struct {
	//		ShouldBindJSON、ShouldBindUri、ShouldBindQuery
	Name string `json:"name" uri:"name" form:"name" `
	Sex  bool   `json:"sex" uri:"sex" form:"sex" `
	Age  int    `json:"age" uri:"sex" form:"sex"  binding:"required,mustBig"`
}

/*
{
    "name":"奇妙",
    "sex":true,
    "age":17
}
报错信息:Key: 'PostParams.Age' Error:Field validation for 'Age' failed on the 'mustBig' tag
*/

func mustBig(fl validator.FieldLevel) bool {
	if fl.Field().Interface().(int) <= 18 {
		return false
	}
	return true
}

func main(){
	r := gin.Default()

	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		v.RegisterValidation("mustBig", mustBig)
	}

	//r.POST("/restBind", func(c *gin.Context) {
	//r.POST("/restBind/:name/:sex", func(c *gin.Context) {
	r.POST("/restBind", func(c *gin.Context) {
		var p PostParams
		err := c.ShouldBindJSON(&p)
		//err := c.ShouldBindUri(&p)   //restBind/你好/true
		//err := c.ShouldBindQuery(&p) //restBind?name=qimiao&sex=true
		fmt.Println(err)
		if err != nil {
			c.JSON(400, gin.H{
				"mes":  "报错了",
				"data": gin.H{},
			})
		} else {
			c.JSON(200, gin.H{
				"mes":  "成功",
				"data": p,
			})
		}
	})

	r.Run(":8080")
}

四、gin对于文件的接收和返回

1、读取文件

读取到的文件就可以进行文件的操作

c.FormFile("前端放到file里面的name")
2、本地写文件
  • 原生:这里使用os.create方法来写

  • gin封装的 c.SaveUploadedFile(file, dst)

    r := gin.Default()
    
    r.POST("/testUpload", func(c *gin.Context) {
       file, _ := c.FormFile("file")
       name := c.PostForm("name")
       //c.SaveUploadedFile(file, "./"+file.Filename)  //等于以下手写封装功能
       in, _ := file.Open()
       defer in.Close()
       out, _ := os.Create("./" + file.Filename)
       io.Copy(out, in)
       defer out.Close()
    
       c.JSON(200, gin.H{
          "mes": file,
          "name": name,
       })
    
    })
    r.Run(":8080")
    

gin学习_第6张图片

多文件上传

上传文件 · Gin中文文档 · 看云 (kancloud.cn)

3、给前端返回文件
c.Writer.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename=%s", "文件名")) //fmt.Sprintf("attachment; filename=%s", filename)对下载的文件重命名
r := gin.Default()

r.POST("/testUpload", func(c *gin.Context) {
   file, _ := c.FormFile("file")
   //name := c.PostForm("name")
   //c.SaveUploadedFile(file, "./"+file.Filename)  //以下手写封装功能
   in, _ := file.Open()
   defer in.Close()
   out, _ := os.Create("./" + file.Filename)
   defer out.Close()
   io.Copy(out, in)
   //文件写回前端
   c.Writer.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename=%s", file.Filename))
   c.File("./" + file.Filename)   //保存文件到前端
   
})

gin学习_第7张图片

五、gin中间件和路由分组

1、什么是分组(如何创建)
  • 对 router 创建 Group就是分组
    同一分组会拥有同一前缀和同一中间件

  • 写法:

    router:= gin.Default()   
    v1 := router.Group("/v1") {
    v1.POST("/login", loginEndpoint)
    v1.POST("/submit", submitEndpoint)
    v1.POST("/read", readEndpoint) }
    

案例

func main() {
   r := gin.Default()
   v1 := r.Group("v1")
   v1.GET("/test", func(c *gin.Context) {
      fmt.Println("我在分组v1里面")
      c.JSON(200, gin.H{
         "success": true,
      })
   })
   v1.GET("/test2", func(c *gin.Context) {
      fmt.Println("我在分组v1里面")
      c.JSON(200, gin.H{
         "success": true,
      })
   })
   r.Run(":8080")
// 访问:http://localhost:8080/v1/test
}
2、为什么要分组
  • 路由结构更加清晰
  • 更加方便管理路由
3、什么是中间件

在请求到达路由的方法的前和后进行的一系列操作

4、如何使用中间件

在路由器(路由组)上进行use操作 后面传入中间件函数即可

5、如何创建中间件

有点类似洋葱,从外往里走,走完在从里走出去。属于洋葱中间件.

func funcname() gin.HandlerFunc {
  	return func(c *gin.Context) {
		fmt.Println("before request")
		c.Next() 
   	 	fmt.Println("after request")
  	}
}

案例

//创建中间件
func middle() gin.HandlerFunc {
	return func(c *gin.Context) {
		fmt.Println("我在方法前,我是1")
		c.Next() //是否往下面走
		fmt.Println("我在方法后,我是1")
	}
}

//创建中间件
func middle2() gin.HandlerFunc {
	return func(c *gin.Context) {
		fmt.Println("我在方法前,我是2")
		c.Next() //是否往下面走,走到2后next发现没有中间件了,走方法内部,然后走2,走完走1
		fmt.Println("我在方法后,我是2")
	}
}

func main() {
	r := gin.Default()
	v1 := r.Group("v1").Use(middle(), middle2())//等效于middle(). middle2()
	v1.GET("/test", func(c *gin.Context) {
		fmt.Println("我在分组v1里面")
		c.JSON(200, gin.H{
			"success": true,
		})
	})
	v1.GET("/test2", func(c *gin.Context) {
		fmt.Println("我在分组v1里面")
		c.JSON(200, gin.H{
			"success": true,
		})
	})
	r.Run(":8080")
	//	访问:http://localhost:8080/v1/test
}
我在方法前,我是1
我在方法前,我是2
我在分组v1里面
我在方法后,我是2
我在方法后,我是1

六、gin日志和日志格式

1、为什么要使用日志
  • 记录参数信息
  • 猜测用户行为
  • 复现系统bug并修复
2、gin自带日志写入中间件

耦合度较高,自定义起来比较麻烦

3、第三方日志中工具
  • go-logging
  • logrus
4、日志切割
  • 自行根据时间在写入时判断进行切割日志
  • 借助成品的日志包:go-file-rotatelogs file-rotatelogs

七、gorm初探

1、什么是orm
  • 一种数据库操作辅助工具
  • 在我们go的结构体和数据库之间产生映射,让我们对数据库的关系,表的内容,直观得体现在结构体上。
  • 使用结构体即可完成增删改查操作
2、orm如何链接数据库
  • 导入 gorm

  • 导入mysql驱动器

  • 使用open链接 得到 数据库操作对象(以mysql为例)

 db, err := gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True&loc=Local")
 defer db.Close()
3、自动化创建数据库表

gorm支持自动迁移模式 使用 AutoMigrate 方法来帮我们自动化创建数据库表

 // 自动迁移模式
db.AutoMigrate(&Product{})
4、最最简单的增删改查
  • 增:Create (跟结构体指针地址)

  • 删:Delete (跟结构体指针地址)或者条件 会根据主键自动去查询单条或者根据条件删除多条

  • 改:Update 更新单一数据 还有 Updates 更新数据中指定内容 Save更新所有内容

  • 查:First (跟结构体示例指针地址,查符合条件的第一个)

    	var user Userinfo
    	db.First(&user, "name=?", "辉**")
    	fmt.Println(user)
    
  • 查: Find (跟结构体切片指针地址,查符合条件的所有)

    	var user []Userinfo
    	db.Find(&user)  //不跟条件,查所有
    	fmt.Println(user)
    

    gin学习_第8张图片

  • 条件:Where Or 填写简单的sql查询语句执行得到model

    gin学习_第9张图片

  • 模型:Model

import (
   "gorm.io/driver/mysql"
   "gorm.io/gorm"
)

type Userinfo struct {
   Id     uint
   Name   string
   Gender string
   Hobby  string
   gorm.Model
}

func main() {
   // 连接数据库
   dsn := "root:123456@tcp(127.0.0.1:3306)/db2?charset=utf8mb4&parseTime=True&loc=Local" //是否格式化时间:是,时区本地
   db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
   if err != nil {
      panic(err)
   }
   //自动迁移
   db.AutoMigrate(&Userinfo{})

   //增
   /*
   u1 := Userinfo{
         Name:   "亚**",
         Gender: "男",
         Hobby:  "编程",
    }
    db.Create(&u1)*/

   /*
      //查
      var user []Userinfo
      db.Where("id<=?", 2).Find(&user)
      fmt.Println(user)
      //更新一条
      db.Where("id=?", 2).First(&Userinfo{}).Update("gender", "男")
   */
   
   /*
      //更新多条
      db.Where("id in (?)", []int{1, 2}).Find(&[]Userinfo{}).Updates(map[string]interface{}{
         "Name":   "六六",
         "Gender": "男",
      })*/
   /*
      db.Where("id in (?)", []int{1, 3}).Find(&[]Userinfo{}).Updates(Userinfo{
         Name:   "七七",
         Gender: "女",
      })*/

   //db.Where("id=?", 1).Delete(&Userinfo{})  //软删除
   db.Where("id in (?)", []int{1, 2}).Unscoped().Delete(&Userinfo{}) //硬删除
}

八、结构体的创建技巧和结合gin使用

1、tag设置

模型定义 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.

  • 设置主键:gorm:“primaryKey”
  • 自定义字段名字:column:user_id
  • 忽略:“-” (以后gorm跟这个字段就没有什么关系了)
  • 指定数据类型 type:varchar(100);
  • 非空 not null
  • 创建索引:index
  • 设置外键 ForeignKey
  • 关联外键 AssociationForeignKey
  • 多对多 many2many:表名;
2、自定义表名(动态表名)
type User struct {
	gorm.Model
	Name string `gorm:"primaryKey;column:user_name;type:varchar(100)"`
}
//例子一
func (User) TableName() string {
	return "qm_users"
}
//例子二
func (u User) TableName() string {
	if u.Role == "admin" {
		return "admin_users"
	} else {
		return "qm_users"
	}
} 
3、结构体声明 1对1 1对多 多对多
  • 多对多使用many2many关键字

    手写一对一、一对多、多对多关系

    //班级里有多个学生(一对多) --  学生属于classId班级(多对一) -- 一个学生有一个idCard(一对一) -- 一个学生多个老师,多个老师对一个学生(多对多)
    
    type Class struct {
       gorm.Model
       ClassName string
       Students  []Student //班级里有多个学生
    }
    
    type Student struct {
       gorm.Model
       StudentName string
       // 学生属于classId班级
       ClassID uint
       //一个学生一个学生卡
       IdCard IdCard
       //多对多   学生有多个老师,并且知道老师的id
       Teachers []Teacher `gorm:"many2many:Student_Teachers"`
       //TeacherID uint
    
    }
    
    type IdCard struct {
       gorm.Model
       StudentID uint
       Num       int
    }
    
    type Teacher struct {
       gorm.Model
       TeacherName string
       //老师有多个学生,老师知道学生的id
       //StudentID uint
       Students []Student `gorm:"many2many:Student_Teachers"`
    }
    
    func main() {
       dsn := "root:123456@tcp(127.0.0.1:3306)/db2?charset=utf8mb4&parseTime=True&loc=Local"
       db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
       db.AutoMigrate(&Teacher{}, &Class{}, &Student{}, &IdCard{})
    
       i := IdCard{Num: 123456}
       t := Teacher{
          TeacherName: "老师夫",
          //Students: []Student{s},
       }
    
       s := Student{
          StudentName: "qm",
          IdCard:      i,
          Teachers:    []Teacher{t},
       }
    
       c := Class{
          ClassName: "奇妙的班级",
          Students:  []Student{s},
       }
       _ = db.Create(&c).Error //把班级创建,班级把学生创建,学生把学生卡创建,学生把老师创建
       //_ = db.Create(&t).Error
    }
    
  • 分页查询使用 Count记录总数 使用Limit 和 Offset 指定记录位置

  • 预加载 Preload(可以把嵌套结构体数据也查出来)

    db.Preload("Teachers").Preload("IdCard").Where("id=?", id).First(&student)
    

    嵌套预加载

    db.Preload("Students").Preload("Students.IdCard").Preload("Students.Teachers").Where("id=?", id).First(&class)
    

gin学习_第10张图片

4、使用gin接受参数并且经过处理入库或者返回
import (
   "github.com/gin-gonic/gin"
   "gorm.io/driver/mysql"
   "gorm.io/gorm"
)

//班级里有多个学生(一对多) --  学生属于classId班级(多对一) -- 一个学生有一个idCard(一对一) -- 一个学生多个老师,多个老师对一个学生(多对多)

type Class struct {
   gorm.Model
   ClassName string
   Students  []Student //班级里有多个学生
}

type Student struct {
   gorm.Model
   StudentName string
   // 学生属于classId班级
   ClassID uint
   //一个学生一个学生卡
   IdCard IdCard
   //多对多   学生有多个老师,并且知道老师的id
   Teachers []Teacher `gorm:"many2many:Student_Teachers"`
   //TeacherID uint

}

type IdCard struct {
   gorm.Model
   StudentID uint
   Num       int
}

type Teacher struct {
   gorm.Model
   TeacherName string
   //老师有多个学生,老师知道学生的id
   //StudentID uint
   Students []Student `gorm:"many2many:Student_Teachers"`
}

func main() {
   dsn := "root:123456@tcp(127.0.0.1:3306)/db2?charset=utf8mb4&parseTime=True&loc=Local"
   db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
   db.AutoMigrate(&Teacher{}, &Class{}, &Student{}, &IdCard{})

   r := gin.Default()
   r.POST("/student", func(c *gin.Context) {
      var student Student
      _ = c.BindJSON(&student)
      db.Create(&student)
   })

   r.GET("/student/:ID", func(c *gin.Context) {
      id := c.Param("ID")
      var student Student
      db.Preload("Teachers").Preload("IdCard").Where("id=?", id).First(&student)
      c.JSON(200, gin.H{
         "mes":  "成功",
         "data": student,
      })
   })

   r.GET("/class/:ID", func(c *gin.Context) {
      id := c.Param("ID")
      var class Class
      db.Preload("Students").Preload("Students.IdCard").Preload("Students.Teachers").Where("id=?", id).First(&class)
      c.JSON(200, gin.H{
         "mes":  "成功",
         "data": class,
      })
   })

   r.Run(":8888")

   /*
      {
          "StudentName":"qm",
          "ClassID":1,
          "IdCard":{
              "Num":555
          },
          "Teachers":[{
              "TeacherName":"老师1"
          },{
              "TeacherName":"老师2"
          }]
      }
   */
}

九、jwt-go

1、什么是jwt

官网

  • 全称 JSON WEB TOKEN
  • 一种后台不做存储的前端身份验证的工具
  • 分为三部分 Header Claims Signature
2、如何创建一个JWT

通常使用 NewWithClaims
因为我们可以通过匿名结构体来实现 Claims接口 从而可以携带自己的参数

这边我把一个Claims结构体从源码中粘贴出来

type StandardClaims struct {
Audience string `json:"aud,omitempty"`
ExpiresAt int64 `json:"exp,omitempty"`
Id string `json:"jti,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
Issuer string `json:"iss,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Subject string `json:"sub,omitempty"`
}

其中最重要的三个参数

  • ExpiresAt //过期时间
  • Issuer //签发人
  • NotBefore //开始生效时间
两种常用 Claims的实现方式
匿名函数实现接口
type MyClaims struct {
	MyType string `json:"myType"`
	jwt.StandardClaims `json:"standardClaims"`
}

匿名函数实现接口

案例加密

type MyClaims struct {
   UserName string `json:"username"`
   jwt.StandardClaims
}

func main() {

   mySigningKey := []byte("qimiao")
   c := MyClaims{
      UserName: "qimiao",
      StandardClaims: jwt.StandardClaims{
         NotBefore: time.Now().Unix() - 60, //base64解成秒  60s
         ExpiresAt: time.Now().Unix() + 60*60*2,
         Issuer:    "qimiao",
      },
   }
   t := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
   fmt.Println(t)
   	s, e := t.SignedString(mySigningKey)  //把t用密钥k加密
	if e != nil {
		fmt.Printf("%s", e)
	}
	fmt.Println(s)
}
//  加密串			头						体
//&{ 0xc000114108 map[alg:HS256 typ:JWT] {qimiao { 1670931390  0 qimiao 1670938530 }}  false}
//eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InFpbWlhbyIsImV4cCI6MTY3MDkzMTk5NCwiaXNzIjoicWltaWFvIiwibmJmIjoxNjcwOTM5MTM0fQ.fo8_JrIbrb3tT4Awg2nok2povhpKh_3YQ-NOYxyBwvA

案例解密解密

import (
   "fmt"
   "github.com/dgrijalva/jwt-go"
   "time"
)

type MyClaims struct {
   UserName string `json:"username"`
   jwt.StandardClaims
}

func main() {
   mySigningKey := []byte("qimiao")
   c := MyClaims{
      UserName: "qimiao",
      StandardClaims: jwt.StandardClaims{
         NotBefore: time.Now().Unix() - 60, //base64解成秒  60s
         ExpiresAt: time.Now().Unix() + 60*60*2,
         Issuer:    "qimiao",
      },
   }
   t := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
   s, e := t.SignedString(mySigningKey)
   if e != nil {
      fmt.Printf("%s", e)
   }
   fmt.Println(s)

   time.Sleep(2 * time.Second)

   token, err := jwt.ParseWithClaims(s, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
      return mySigningKey, nil
   })

   if err != nil {
      fmt.Printf("%s", err)
      return
   }
   //fmt.Println(token.Claims) //&{qimiao { 1670990981  0 qimiao 1670983721 }}
   fmt.Println(token.Claims.(*MyClaims).UserName)
}
map形式
jwt.MapClaims{
	"myType": "myType",
	"nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
	"exp": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
}

map形式

案例

func main() {
   mySigningKey := []byte("qimiao")

   t := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
      "exp":      time.Now().Unix() + 60,
      "iss":      "qimiao",
      "nbf":      time.Now().Unix() - 5,
      "username": "my",
   })
   s, e := t.SignedString(mySigningKey)
   if e != nil {
      fmt.Printf("%s", e)
   }
   fmt.Println(s)

   time.Sleep(2 * time.Second)

   token, err := jwt.ParseWithClaims(s, &jwt.MapClaims{}, func(token *jwt.Token) (interface{}, error) {
      return mySigningKey, nil
   })

   if err != nil {
      fmt.Printf("%s", err)
      return
   }
   //fmt.Println(token.Claims) //&{qimiao { 1670990981  0 qimiao 1670983721 }}
   fmt.Println(token.Claims.(*jwt.MapClaims))
}
创建一个Token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // 创建
ss,err := token.SignedString(mySigningKey) // 签发
HS256(对称加密)  RS256(非对称加密  需要一个结构体指针 三个属性
 {ECDSAKeyD,
ECDSAKeyX,
ECDSAKeyY},ES256)
3、如何解析一个JWT
token,err := jwt.ParseWithClaims(token,claims,func)
  • token:我们拿到的token字符串 (ss)
  • 我们用哪个claims结构体发的 这里就传入哪个结构体
  • func: 一个特殊的回调函数 需要固定接受 *Token类型指针 返回一个 i和一个err 此时的i就是我们的密钥

token是一个jwtToken类型的数据 我们需要的就是其中 的Claims

对Claims进行断言 然后进行取用即可

   token, err := jwt.ParseWithClaims(s, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
      return mySigningKey, nil
   })

   if err != nil {
      fmt.Printf("%s", err)
      return
   }
   //fmt.Println(token.Claims) //&{qimiao { 1670990981  0 qimiao 1670983721 }}
   fmt.Println(token.Claims.(*MyClaims).UserName)

十、casbin模型

Model 的语法 | Casbin

1、casbin模型基础
1.1、PERM 元模型

p策略(Policy )、e影响(Effect )、r请求( Request )、m匹配规则(Matchers )

Editor | Casbin

  • subject(sub 访问实体), object(obj 访问的资源)和 action(act 访问方法)eft(策略结果 一般为空 默认指定 allow) 还可以定义为 deny

  • Policy 策略 p={sub, obj, act, eft}

    • 策略一般存储到数据库 因为会有很多

    • [policy_definition]
      p = sub,obj,act
      
  • Matchers 匹配规则 Request和Policy的匹配规则。

    • m = r.sub == p.sub && r.act == p.act && r.obj == p.obj
      
    • r 请求 p 策略

    • 这时候会把 r 和 p按照上述描述进行匹配 从而返回匹配结果(eft)如果不定义 会返回 allow 如果定义过了 会返回我们定义过的那个结果

  • Effect 影响

    • 它决定我们是否可以放行
      • e = some(where(p.eft == allow)) 这种情况下 我们的一个 matchers匹配完成 得到了allow 那么这条请求将被放行
      • e = some(where(p.eft == allow)) && !some(where(p.eft == deny))
      • 这里的规则是定死的
  • Request 请求 r={sub, obj,act}

Request带着匹配规则(matchers)和Policy匹配,返回一个结果,再通过effect校验是否能校验通过

gin学习_第11张图片

gin学习_第12张图片

1.2、role_definition 角色域
  • g = _ , _ 表示以角色为基础(用户是哪个角色)
  • g=_, _, _ 表示以域为基础(多商户模式)(用户是哪个角色 属于哪个商户)

gin学习_第13张图片

gin学习_第14张图片

gin学习_第15张图片

加完g——我的入参可以是alice也可以是data2_admin

2、实战模型
2.1、RBAC

以角色为基础

  • [request_definition]
    r = sub, obj, act
    
  • 人话:请求入参(实体,资源,方法)

  • [policy_definition]
    p = sub, obj, act
    
  • 人话:策略(实体,资源,方法)

    [role_definition]
    g = _, _ 
    
  • 人话:这个情况下 g写啥都行 毕竟match里面根本没有涉及到g 不过我们规范一点 按照角色权限 这里意思是g收两个参数 g=用户,角色

  • [policy_effect]
    e = some(where (p.eft == allow))
    
  • 人话 :看看经过下面那些个匹配规则后的返回值是否有一条等于里面那个 allow

  • [matchers]
    m = r.sub == p.sub && ParamsMatch(r.obj,p.obj) && r.act == p.act
    
  • 人话:进来的实体 资源 方法 能不能在 权限表(p)里面找到一个一模一样的

2.2、RBAC with domains/tenants
  • 多租户模型

    [request_definition]r = sub, dom, obj, act
    
  • 人话:入参(实体,域【商户】,资源,方法)

  • [policy_definition]p = sub, dom, obj, act
    
  • 人话:权限模型(实体,域【商户】,资源,方法)

  • [role_definition]g = _, _, _
    
  • 半句人话:域匹配规则 后面g会说 这里意思是 g收三个参数(属于哪个用户,用户属于哪个角色,角色属于哪个商户)

  • [policy_effect]e = some(where (p.eft == allow))
    
  • 人话 :看看经过下面那些个匹配规则后的返回值是否等于里面那个 allow

  • [matchers]m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
    
  • 先列出来一个权限定义里面的东西

  • g,qm,teacher,classOne
    
  • 然后用g做一些解析动作

  • 一般我们会收到前端的入参 大概长这样 “qm”,“classOne”,“/base/api”,“get”

  • 上面我们定义的g就是我们需要的模型 经过g以后 g已经把我们的入参 qm 解析成了 r.sub 为 teacher r.rom 为 classOne 先从这个商户找一下角色 找到就过了 然后用 dom 去匹配然后用一个类似于这样的请求 去 策略里面找

  • teacher,classOne ,/api/base,get
    

gin学习_第16张图片

gin学习_第17张图片

先匹配,然后根据p获得eft,然后去policy获得策略效果

3、实战策略
3.1、角色为基础的
  • p admin,/api/base,get
  • p admin,/api/base,post
  • p user,/api/base,get
  • g qimiao,admin
  • g qimiao,user
  • g qm admin
3.2、带域的
  • p admin,classOne,/api/base,get
  • g qimiao,admin,classOne
  • g qm,admin,classOne
  • g qimiao,admin,classTwo

十一、使用casbin

开始使用 | Casbin

1、本地文件模式初体验
  • 引入包 github.com/casbin/casbin/v2

  • 创建模型和policy 并且引入 e, err := casbin.NewEnforcer(“path/to/model.conf”, “path/to/policy.csv”)

  • 调用api并且使用

    sub := "alice" // the user that wants to access a resource.
    obj := "data1" // the resource that is going to be accessed.
    act := "read" // the operation that the user performs on the resource.
    
    ok, err := e.Enforce(sub, obj, act)
    
    if err != nil {
    // handle err
    }
    
    if ok == true {
    // permit alice to read data1
    } else {
    // deny the request, show an error
    }
    

案例

model.conf

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

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

[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

policy.csv

p,zhangsan,data1,read

mian.go

import (
   "fmt"
   "github.com/casbin/casbin/v2"
)

func main() {
   e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
   sub := "alice" // 想要访问资源的用户。
   obj := "data1" // 将被访问的资源。
   act := "read"  // 用户对资源执行的操作。

   added, err := e.AddPolicy("alice", "data1", "read")

   fmt.Println(added)
   fmt.Println(err)

   //进行检测
   ok, err := e.Enforce(sub, obj, act)

   if err != nil {
      // 处理err
      fmt.Printf("%s", err)
   }

   if ok == true {
      // 允许alice读取data1
      fmt.Println("通过")
   } else {
      // 拒绝请求,抛出异常
      fmt.Printf("未通过")
   }

   // 您可以使用BatchEnforce()来批量执行一些请求
   // 这个方法返回布尔切片,此切片的索引对应于二维数组的行索引。
   // 例如results[0] 是{"alice", "data1", "read"}的结果
   //results, err := e.BatchEnforce([[] []interface{}{"alice", "data1", "read"}, {"bob", datata2", "write"}, {"jack", "data3", "read"}})
   //e.BatchEnforce([][]interface{}{{"alice", "data1", "read"}, {"bob", "datata2", "write"}, {"jack", "data3", "read"}})
}
2、使用数据库存储policy

GitHub - casbin/gorm-adapter: Gorm adapter for Casbin

  • 使用gorm适配器进行存储

    • 引用包 github.com/casbin/gorm-adapter

    • a, _ := gormadapter.NewAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/",true) // Your driver and data source.
      e, _ := casbin.NewEnforcer("examples/rbac_model.conf", a)
      连接数据库并自动建表
      
  • 初体验

    • // Load the policy from DB.
       e.LoadPolicy()
      
    • // Check the permission.
      e.Enforce("alice", "data1", "read")
      
    • // Modify the policy.
      // e.AddPolicy(...)
       // e.RemovePolicy(...)
       // Save the policy back to DB.
      e.SavePolicy()
      

案例

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

func main() {
   //e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
   a, _ := gormadapter.NewAdapter("mysql", "root:123456@tcp(127.0.0.1:3306)/casbin", true) // Your driver and data source.
   e, _ := casbin.NewEnforcer("./model.conf", a)
   sub := "alice" // 想要访问资源的用户。
   obj := "data1" // 将被访问的资源。
   act := "read"  // 用户对资源执行的操作。

   added, err := e.AddPolicy("alice", "data1", "read")
   fmt.Println(added)
   fmt.Println(err) 

   //进行检测
   ok, err := e.Enforce(sub, obj, act)

   if err != nil {
      // 处理err
      fmt.Printf("%s", err)
   }

   if ok == true {
      // 允许alice读取data1
      fmt.Println("通过")
   } else {
      // 拒绝请求,抛出异常
      fmt.Printf("未通过")
   }

}
3、对policy进行增删改查
  • filteredPolicy := e.GetFilteredPolicy(0, "alice")//第v0列是alice的数据
    
  • p AddPolicy()
    g e.AddGroupingPolicy("group1", "data2_admin")
    
  • removed := e.RemovePolicy("alice", "data1", "read")
    
  • updated, err := e.UpdatePolicy([]string{"eve", "data3", "read"}, []string{"eve", "data3", "write"})
    
4、自定义比较函数

函数 | Casbin

func KeyMatch(key1 string, key2 string) bool {

	return key1 == key2

	/*	i := strings.Index(key2, "*")
		if i == -1 {
			return key1 == key2
		}

		if len(key1) > i {
			return key1[:i] == key2[:i]
		}
		return key1 == key2[:i]*/
}

func KeyMatchFunc(args ...interface{}) (interface{}, error) {
	name1 := args[0].(string)
	name2 := args[1].(string)

	return (bool)(KeyMatch(name1, name2)), nil
}

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) && my_func(r.obj, p.obj) && r.act == p.act
# m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
# m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

你可能感兴趣的:(后端冲鸭,gin,学习,java)