对于权限管理,我们最熟悉的是RBAC模型,如下图
casbin针对RBAC,给出了更友好的API.今天我们来用RBAC API来实现简单的权限管理.
从之前的demo中,我们可以看到,casbin仅仅保存了权限管理的必要信息,其它的比如用户信息,角色名称,状态等等,是没有保存的,所以我们要自行在数据库里设计users表和roles表,保存这些信息.一般的rbac设计,还需要一个user_role的关联表,使用casbin的话,就不需要了.
先创建models目录,初始化xorm的数据库连接,init.go的代码如下:
package models
import (
"log"
_ "github.com/go-sql-driver/mysql"
"xorm.io/core"
"xorm.io/xorm"
)
//数据库连接
var DB *xorm.Engine
func Setup() {
var err error
//数据库链接
DB, err = xorm.NewEngine("mysql", "rbac:123456@tcp(127.0.0.1:3306)/rbac_db?charset=utf8")
if err != nil {
log.Printf("创建DB连接错误: %v\n", err)
return
}
// 控制台打印SQL语句
DB.ShowSQL(true)
DB.Logger().SetLevel(core.LOG_WARNING)
}
users的结构体以及相应的增删改查方法(users.go):
package models
import (
"demo/utils"
"errors"
"log"
"time"
"xorm.io/xorm"
)
type Users struct {
Id int64 `xorm:"pk autoincr"` //主键ID
RealName string `xorm:"varchar(128)"` //用户真实名称
NamePinyin string `xorm:"varchar(128)"` //名字拼音
NamePinyinPre string `xorm:"varchar(5)"` //名字拼音前缀
Portrait string `xorm:"varchar(512)"` //头像地址
Mobile string `xorm:"varchar(50)"` //用户手机号
Password string `xorm:"varchar(128)"` //用户密码
Salt string `xorm:"varchar(128)"` //23位盐
Gender int64 `xorm:"tinyint(1)"` //用户性别
Status int64 `xorm:"smallint(6)"` // 状态:-20:逻辑删除;10:正常; 20:无效
CreateAt time.Time `xorm:"datetime created"` //创建时间
UpdateAt time.Time `xorm:"datetime updated"` //最后更新时间
}
// 构建查询
func GetUsersQS() *xorm.Session {
return DB.Table("users")
}
// 增加
func (r *Users) Add() error {
_, err := DB.Table("users").Insert(r)
if err != nil {
log.Printf("增加用户错误: %v", err)
return err
}
return nil
}
// 更新给定的字段
func (r *Users) Update(cols ...string) error {
m := make(map[string]interface{})
data := utils.Struct2Map(r)
for _, col := range cols {
m[col] = data[col]
}
_, err := DB.Table("users").Id(r.Id).Update(m)
if err != nil {
log.Printf("更新用户错误: %v", err)
return err
}
return nil
}
// 删除(软删除)
func (r *Users) Delete() error {
if r.Status != -20 {
return errors.New("传入参数错误")
}
_, err := DB.Table("users").Id(r.Id).Cols("status").Update(r)
if err != nil {
log.Printf("删除用户错误: %v", err)
return err
}
return nil
}
// 查找
// 通过Id查找
func (r *Users) GetById(id int64) (*Users, error) {
_, err := GetUsersQS().Id(id).Get(r)
if err != nil {
log.Printf("查找用户错误: %v", err)
return nil, err
}
return r, nil
}
roles的结构体(roles.go):
package models
import (
"demo/utils"
"errors"
"log"
"time"
"xorm.io/xorm"
)
type Roles struct {
RoleId int64 `xorm:"pk autoincr"` // 主键
RegionId int64 `xorm:"int(11)"` // 域id
Name string `xorm:"varchar(300)"` // 名称
Status int `xorm:"smallint(6)"` // 状态:-20:逻辑删除;10:正常; 20:无效
CreatedAt time.Time `xorm:"datetime created"`
UpdatedAt time.Time `xorm:"datetime updated"`
}
// 构建查询
func GetRolesQS() *xorm.Session {
return DB.Table("roles")
}
// 增加
func (r *Roles) Add() error {
_, err := DB.Table("roles").Insert(r)
if err != nil {
log.Printf("增加角色错误: %v", err)
return err
}
return nil
}
// 更新给定的字段
func (r *Roles) Update(cols ...string) error {
m := make(map[string]interface{})
data := utils.Struct2Map(r)
for _, col := range cols {
m[col] = data[col]
}
_, err := DB.Table("roles").Id(r.RoleId).Update(m)
if err != nil {
log.Printf("更新角色错误: %v", err)
return err
}
return nil
}
// 删除(软删除)
func (r *Roles) Delete() error {
if r.Status != -20 {
return errors.New("传入参数错误")
}
_, err := DB.Table("roles").Id(r.RoleId).Cols("status").Update(r)
if err != nil {
log.Printf("删除角色错误: %v", err)
return err
}
return nil
}
// 查找
// 通过Id查找
func (r *Roles) GetById(id int64) (*Roles, error) {
_, err := GetRolesQS().Id(id).Get(r)
if err != nil {
log.Printf("查找角色错误: %v", err)
return nil, err
}
return r, nil
}
新建utils目录,里面放公共函数utils.go,当前的内容:
package utils
//公共方法
import (
"reflect"
)
// 结构体转映射
func Struct2Map(obj interface{}) map[string]interface{} {
t := reflect.TypeOf(obj)
v := reflect.ValueOf(obj)
var data = make(map[string]interface{})
for i := 0; i < t.NumField(); i++ {
data[t.Field(i).Name] = v.Field(i).Interface()
}
return data
}
接下来,我们在controller目录下,新建userController.go文件.为了便于与前端的同学交流,我们使用swagger规定的格式写注释.大部分都是空的接口.
package controller
import (
"net/http"
"github.com/gin-gonic/gin"
)
type UserInfo struct{}
// @Summary 新用户
// @Description 新用户
// @Accept json
// @Produce json
// @Param mobile query string true "mobile"
// @Param password query string true "password"
// @Param realname query string false "realname"
// @Param gender query string false "gender"
// @Success 200 {string} string "OK"
// @Failure 400 {string} string "We need mobile or password!!"
// @Router /api/v1/users [post]
func (u *UserInfo) Add(c *gin.Context) {
}
// @Summary 修改用户信息
// @Description 修改用户信息
// @Accept json
// @Produce json
// @Param Id query int64 true "Id"
// @Param realname query string false "realname"
// @Param gender query string false "gender"
// @Success 200 {string} string "OK"
// @Failure 400 {string} string "We need Id!!"
// @Failure 404 {string} string "Can not find Id"
// @Router /api/v1/users/:id [patch]
func (u *UserInfo) Edit(c *gin.Context) {
}
// @Summary 删除用户
// @Description 删除用户
// @Accept json
// @Produce json
// @Param Id query int64 true "Id"
// @Success 200 {string} string "OK"
// @Failure 400 {string} string "We need Id!!"
// @Failure 404 {string} string "Can not find Id"
//@Router /api/v1/users/:id [delete]
func (u *UserInfo) Delete(c *gin.Context) {
}
// @Summary 查看用户信息
// @Description 查看用户信息
// @Accept json
// @Produce json
// @Param Id query int64 true "Id"
// @Success 200 {string} string "OK"
// @Failure 400 {string} string "We need Id!!"
// @Failure 404 {string} string "Can not find Id"
//@Router /api/v1/users/:id [get]
func (u *UserInfo) GetUser(c *gin.Context) {
data := make(map[string]interface{})
id := c.Param("id")
data["id"] = id
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "success",
"data": data,
})
}
// @Summary 获取用户列表
// @Description 获取用户列表
// @Accept json
// @Produce json
// @Success 200 {string} string "OK"
// @Router /api/v1/users [get]
func (u *UserInfo) GetUsers(c *gin.Context) {
}
我们修改一下router/router.go的代码,主要是加入分组路由:
package routers
import (
"demo/controller"
"demo/middleware"
"github.com/gin-gonic/gin"
)
func InitRouter() *gin.Engine {
//获取router路由对象
r := gin.New()
apiv1 := r.Group("/api/v1")
//使用自定义拦截器中间件
apiv1.Use(middleware.Authorize())
{
//创建请求
apiv1.GET("/hello", controller.Hello)
userController := new(controller.UserInfo)
apiv1.GET("/users", userController.GetUsers)
apiv1.GET("/users/:id", userController.GetUser)
apiv1.POST("/users", userController.Add)
apiv1.PATCH("/users/:id", userController.Edit)
apiv1.DELETE("/users/:id", userController.Delete)
}
return r
}
main.go也要修改,把数据库的初始化方法放到init()里:
package main
import (
"demo/models"
"demo/routers"
"demo/service"
)
func init() {
models.Setup()
service.CasbinSetup()
}
func main() {
r := routers.InitRouter()
r.Run(":9000") //参数为空 默认监听8080端口
}
因为我们准备使用Restful的api,所以需要修改conf/rbac_models.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) && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)
最后,在数据库的casbin_rule表中,加入如下内容:
用postman访问
给出到今天为止的目录结构: