天天看點

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驗證,權限就沒有問題了.