Casbin+Gin+XORM的权限控制demo(五)

感觉写得有点偏题了,不过这是疫情期间,无聊,关在家里边学习边写的,错误比较多,我当作笔记一样写,大家就别浪费时间看了.

昨天,所有的c.JSON的输出,错误代码都是手工写的,不好,所以在utils下新建目录e,增加code.go和msg.go这两个文件.

code.go的内容如下:

package e

const (
    SUCCESS        = 200
    ERROR          = 500
    INVALID_PARAMS = 400
)

msg.go的内容如下:

package e

var MsgFlags = map[int]string{
    SUCCESS:               "ok",
    ERROR:                 "fail",
    INVALID_PARAMS:        "请求参数错误",
}

// GetMsg get error information based on Code
func GetMsg(code int) string {
    msg, ok := MsgFlags[code]
    if ok {
        return msg
    }

    return MsgFlags[ERROR]
}

以后的错误代码和信息,只要统一维护这里即可.

昨天所有的Controller都是空接口,今天把userController.go写上内容:

package controller

import (
    "demo/models"
    "demo/utils"
    "demo/utils/e"
    "log"
    "net/http"
    "strconv"
    "strings"

    "github.com/astaxie/beego/validation"
    "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) {
    data := make(map[string]interface{})

    mobile := c.Query("mobile")
    password := c.Query("password")
    realname := strings.TrimSpace(c.Query("realname"))
    gender := c.DefaultQuery("gender", "1")

    valid := validation.Validation{}
    valid.Required(mobile, "mobile").Message("请输入手机号码")
    valid.Mobile(mobile, "mobile").Message("不是有效的手机号码")
    valid.Required(password, "password").Message("请输入密码")
    valid.MinSize(password, 6, "password").Message("密码至少为6位")
    valid.MaxSize(realname, 20, "realname").Message("姓名最多不能超过20个字")

    code := e.INVALID_PARAMS
    if !valid.HasErrors() {
        user, err := new(models.Users).GetByMobile(mobile)
        if err != nil {
            log.Printf("手机查找用户错误: %v", err)
            code = e.ERROR
        } else if user.Id < 1 {
            user.Mobile = mobile
            user.RealName = realname
            user.Gender, _ = strconv.Atoi(gender)
            user.Status = 10

            // 获取salt 和 加密密码
            user.Salt = utils.RandomString(23)
            // 两次加密
            user.Password = utils.SHA256(utils.Md5(password) + user.Salt)

            if err := user.Add(); err != nil {
                log.Printf("添加用户失败: %v", err)
                code = e.ERROR
            } else {
                data["user"] = user
                code = e.SUCCESS
            }
        }
    } else {
        for _, err := range valid.Errors {
            log.Printf("err.key: %s, err.message: %s", err.Key, err.Message)
        }
    }

    c.JSON(http.StatusOK, gin.H{
        "code": code,
        "msg":  e.GetMsg(code),
        "data": data,
    })
}

// @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) {
    data := make(map[string]interface{})

    strId := c.Param("id")
    id, _ := strconv.Atoi(strId)
    realname := strings.TrimSpace(c.Query("realname"))
    gender, _ := strconv.Atoi(c.Query("gender"))

    valid := validation.Validation{}
    valid.Required(strId, "id").Message("id不能为空")
    valid.Min(id, 1, "id").Message("id必须大于0")
    valid.MaxSize(realname, 20, "realname").Message("姓名最多不能超过20个字")
    valid.Range(gender, 0, 1, "gender").Message("性别只能是0或者1")

    code := e.INVALID_PARAMS

    if !valid.HasErrors() {
        user, err := new(models.Users).GetById(int64(id))
        if err != nil {
            log.Printf("编辑用户错误1: %v", err)
            code = e.ERROR
        } 

        if user.Id > 0 {
            user.RealName = realname
            user.Gender = gender
            if err := user.Update("realname", "gender"); err != nil {
                log.Printf("编辑用户错误2: %v", err)
                code = e.ERROR
            } else {
                data["user"] = user
                code = e.SUCCESS
            }
        }
    } else {
        for _, err := range valid.Errors {
            log.Printf("err.key: %s. err.message: %s", err.Key, err.Message)
        }
    }

    c.JSON(http.StatusOK, gin.H{
        "code": code,
        "msg":  e.GetMsg(code),
        "data": data,
    })
}

// @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) {
    strId := c.Param("id")
    id, _ := strconv.Atoi(strId)

    valid := validation.Validation{}
    valid.Required(strId, "id").Message("id不能为空")
    valid.Min(id, 1, "id").Message("id必须大于0")

    code := e.INVALID_PARAMS

    if !valid.HasErrors() {
        user, err := new(models.Users).GetById(int64(id))
        if err != nil {
            log.Printf("删除用户错误1: %v", err)
            code = e.ERROR
        }
        if user.Id > 0 {
            if err := user.Delete(); err != nil {
                log.Printf("删除用户错误2: %v", err)
                code = e.ERROR
            } else {
                code = e.SUCCESS
            }
        }
    } else {
        for _, err := range valid.Errors {
            log.Printf("err.key: %s, err.message: %s", err.Key, err.Message)
        }
    }

    c.JSON(http.StatusOK, gin.H{
        "code": code,
        "msg":  e.GetMsg(code),
        "data": make(map[string]interface{}),
    })
}

// @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{})
    code := e.INVALID_PARAMS

    strId := c.Param("id")
    id, _ := strconv.Atoi(strId)

    valid := validation.Validation{}
    valid.Required(strId, "id").Message("id不能为空")
    valid.Min(id, 1, "id").Message("id必须大于0")

    if !valid.HasErrors() {
        user, err := new(models.Users).GetById(int64(id))
        if err != nil {
            log.Printf("获取用户信息错误: %v", err)
            code = e.ERROR
        } else {
            data["user"] = user
            code = e.SUCCESS
        }
    } else {
        for _, err := range valid.Errors {
            log.Printf("err.key: %s, err.message: %s", err.Key, err.Message)
        }
    }

    c.JSON(http.StatusOK, gin.H{
        "code": code,
        "msg":  e.GetMsg(code),
        "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) {
    data := make(map[string]interface{})

    code := e.SUCCESS

    userList, err := new(models.Users).FindAll()
    if err != nil {
        log.Printf("获取用户列表错误: %v", err)
        code = e.ERROR
    }

    data["user_list"] = userList

    c.JSON(http.StatusOK, gin.H{
        "code": code,
        "msg":  e.GetMsg(code),
        "data": data,
    })
}

由于加密用户密码的需要,在utils/utils.go里加上:

// 返回SHA256加密
func SHA256(s string) string {
    h := sha256.New()
    h.Write([]byte(s))
    rs := hex.EncodeToString(h.Sum(nil))
    return rs
}

//生成随机字符串(大写字母)
func RandomString(len int) string {
    var result bytes.Buffer
    var temp string
    for i := 0; i < len; {
        if string(RandomInt(65, 90)) != temp {
            temp = string(RandomInt(65, 90))
            result.WriteString(temp)
            i++
        }
    }
    return result.String()
}

//生成随机数字
func RandomInt(min int, max int) int {
    rand.Seed(time.Now().UTC().UnixNano())
    return min + rand.Intn(max-min)
}

roleController.go:

package controller

import (
    "demo/models"
    "demo/utils"
    "demo/utils/e"
    "log"
    "net/http"
    "strconv"
    "strings"

    "github.com/astaxie/beego/validation"

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

type Roles struct{}

// @Summary 新角色
// @Description 新角色
// @Accept  json
// @Produce  json
// @Param name query string true "name"
// @Success 200 {string} string "OK"
// @Failure 400 {string} string "We need name!!"
// @Router /api/v1/roles/ [post]
func (r *Roles) Add(c *gin.Context) {
    name := strings.TrimSpace(c.Query("name"))

    valid := validation.Validation{}
    valid.Required(name, "name").Message("角色名字不能为空")
    valid.MinSize(name, 2, "name").Message("角色名字不能少于2个字符")

    code := e.INVALID_PARAMS

    if !valid.HasErrors() {
        role := models.Roles{Name: name}
        if err := role.Add(); err != nil {
            log.Printf("新增角色错误: %v", err)
            code = e.ERROR
        } else {
            code = e.SUCCESS
        }
    } else {
        for _, err := range valid.Errors {
            log.Printf("err.key: %s, err.message: %s", err.Key, err.Message)
        }
    }

    c.JSON(http.StatusOK, gin.H{
        "code": code,
        "msg":  e.GetMsg(code),
        "data": make(map[string]interface{}),
    })
}

// @Summary 修改角色信息
// @Description 修改角色信息
// @Accept  json
// @Produce  json
// @Param Id query int64 true "Id"
// @Param name query string true "name"
// @Success 200 {string} string "OK"
// @Failure 400 {string} string "We need Id!!"
// @Failure 404 {string} string "Can not find Id"
// @Router /api/v1/roles/:id [patch]
func (r *Roles) Edit(c *gin.Context) {
    data := make(map[string]interface{})
    code := e.INVALID_PARAMS

    strId := c.Param("id")
    id, _ := strconv.Atoi(strId)
    name := strings.TrimSpace(c.Query("name"))

    valid := validation.Validation{}
    valid.Required(strId, "id").Message("id不能为空")
    valid.Min(id, 1, "id").Message("id不能小于0")
    valid.Required(name, "name").Message("角色名称不能为空")

    if !valid.HasErrors() {
        role, err := new(models.Roles).GetById(int64(id))
        if err != nil {
            log.Printf("编辑角色错误1: %v", err)
            code = e.ERROR
        }

        if role.RoleId > 0 {
            role.Name = name
            if err := role.Update("name"); err != nil {
                log.Printf("编辑角色错误2: %v", err)
                code = e.ERROR
            } else {
                data["role"] = role
                code = e.SUCCESS
            }
        }
    } else {
        for _, err := range valid.Errors {
            log.Printf("err.key: %s, err.message: %s", err.Key, err.Message)
        }
    }

    c.JSON(http.StatusOK, gin.H{
        "code": code,
        "msg":  e.GetMsg(code),
        "data": data,
    })
}

// @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/roles/:id [delete]
func (r *Roles) Delete(c *gin.Context) {
    strId := c.Param("id")
    id, _ := strconv.Atoi(strId)

    valid := validation.Validation{}
    valid.Required(strId, "id").Message("id不能为空")
    valid.Min(id, 1, "id").Message("id不能小于0")

    code := e.INVALID_PARAMS

    if !valid.HasErrors() {
        role, err := new(models.Roles).GetById(int64(id))
        if err != nil {
            log.Printf("删除角色错误1: %v", err)
            code = e.ERROR
        }
        if role.RoleId > 0 {
            if err := role.Delete(); err != nil {
                log.Printf("删除角色错误2: %v", err)
                code = e.ERROR
            } else {
                code = e.SUCCESS
            }
        }
    } else {
        for _, err := range valid.Errors {
            log.Printf("err.key: %s, err.message: %s", err.Key, err.Message)
        }
    }

    c.JSON(http.StatusOK, gin.H{
        "code": code,
        "msg":  e.GetMsg(code),
        "data": make(map[string]interface{}),
    })
}

// @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/roles/:id [get]
func (r *Roles) GetRole(c *gin.Context) {
    h := utils.Gin{C: c}
    data := make(map[string]interface{})

    strId := c.Param("id")
    id, _ := strconv.Atoi(strId)

    valid := validation.Validation{}
    valid.Required(strId, "id").Message("id不能为空")
    valid.Min(id, 1, "id").Message("id不能小于0")

    code := e.INVALID_PARAMS

    if valid.HasErrors() {
        utils.MarkErrors(valid.Errors)
        h.Response(http.StatusOK, e.INVALID_PARAMS, data)
        return
    }

    role, err := new(models.Roles).GetById(int64(id))
    if err != nil {
        log.Printf("查看角色信息错误1: %v", err)
        code = e.ERROR
    }
    if role.RoleId > 0 {
        data["role"] = role
        code = e.SUCCESS
    }

    h.Response(http.StatusOK, code, data)
}

// @Summary 获取角色列表
// @Description 获取角色列表
// @Accept  json
// @Produce  json
// @Success 200 {string} string "OK"
// @Router /api/v1/roles/ [get]
func (r *Roles) GetRoles(c *gin.Context) {
    h := utils.Gin{C: c}
    data := make(map[string]interface{})

    code := e.SUCCESS

    roleList, err := new(models.Roles).FindAll()
    if err != nil {
        log.Printf("获取角色列表错误: %v", err)
        code = e.ERROR
    }

    data["list"] = roleList

    h.Response(http.StatusOK, code, data)
}

写到后面,发现c.JSON每次都重复很多,新引入的校验,也是重复的写,参考"煎鱼"的做法,新增utlis/http.go:

package utils

import (
    "demo/utils/e"
    "log"

    "github.com/astaxie/beego/validation"
    "github.com/gin-gonic/gin"
)

// 简化response代码
type Gin struct {
    C *gin.Context
}

func (g *Gin) Response(httpCode, errCode int, data interface{}) {
    g.C.JSON(httpCode, gin.H{
        "code": errCode,
        "msg":  e.GetMsg(errCode),
        "data": data,
    })

    return
}

// 输入验证的错误处理
func MarkErrors(errors []*validation.Error) {
    for _, err := range errors {
        log.Println(err.Key, err.Message)
    }

    return
}

使用postman验证的时候,老是提示权限问题,到casbin的在线权限编辑验证去测试了一把,最后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())
    {
        //hello测试
        apiv1.GET("/hello", controller.Hello)

        userRoutes := apiv1.Group("/users")
        {
            userController := new(controller.UserInfo)
            // 用户列表
            userRoutes.GET("/", userController.GetUsers)
            // 单个用户信息
            userRoutes.GET("/:id", userController.GetUser)
            // 新增用户
            userRoutes.POST("/", userController.Add)
            // 修改用户信息
            userRoutes.PATCH("/:id", userController.Edit)
            // 删除用户
            userRoutes.DELETE("/:id", userController.Delete)
        }

        roleRoutes := apiv1.Group("/roles")
        {
            roleController := new(controller.Roles)
            // 角色列表
            roleRoutes.GET("/", roleController.GetRoles)
            // 单个角色信息
            roleRoutes.GET("/:id", roleController.GetRole)
            // 新增角色
            roleRoutes.POST("/", roleController.Add)
            // 修改角色信息
            roleRoutes.PATCH("/:id", roleController.Edit)
            // 删除角色
            roleRoutes.DELETE("/:id", roleController.Delete)
        }
    }

    return r
}

而数据库的casbin_rule表中,也做相应的修改:
Casbin+Gin+XORM的权限控制demo(五)

再用postman验证,权限就没有问题了.

上一篇:《并行计算的编程模型》一3.7.1 选择集合参与者


下一篇:二叉树三种遍历方法(递归)