一.权限介绍
RBAC 是基于角色的权限访问控制(Role-Based Access Control)。在 RBAC 中,权限与角色相
关联,用户通过成为适当角色的成员而得到这些角色的权限
二.用户 RBAC 权限管理树形图
三.RBAC 实现流程
1、实现角色的增加修改删除
2、实现用户的增加修改删除,增加修改用户的时候需要选择角色
3、实现权限的增加修改删除 (页面菜单)
4、实现角色授权功能
5、判断当前登录的用户是否有访问菜单的权限
6、根据当前登录账户的角色信息动态显示左侧菜单
四.权限控制相关的数据库表
五. 角色的增、删、改、查
1.创建角色模型
在models目录下创建role.go角色模型
package models
//角色模型
type Role struct { // 结构体首字母大写, 和数据库表名对应, 默认访问数据表users, 可以设置访问数据表的方法
Id int
Title string
Description string
Status int
AddTime int
}
//配置数据库操作的表名称
func (Role) TableName() string {
return "role"
}
2.创建角色控制器
在controllers/admin下创建RoleController.go角色控制器
package admin
import (
"github.com/gin-gonic/gin"
"goshop/models"
"net/http"
"strings"
)
type RoleController struct {
BaseController
}
//角色列表
func (con RoleController) Index(c *gin.Context) {
//定义一个角色切片
roleList := []models.Role{}
//获取角色
models.DB.Find(&roleList)
c.HTML(http.StatusOK, "admin/role/index.html", gin.H{
"roleList": roleList,
})
}
//新增角色
func (con RoleController) Add(c *gin.Context) {
c.HTML(http.StatusOK, "admin/role/add.html", gin.H{})
}
//新增角色:提交
func (con RoleController) DoAdd(c *gin.Context) {
//获取表单的提交数据
//strings.Trim(str, cutset), 去除字符串两边的cutset字符
title := strings.Trim(c.PostForm("title"), " ") // 去除字符串两边的空格
description := strings.Trim(c.PostForm("description"), " ")
//判断角色名称是否为空
if title == "" {
con.Error(c, "角色名称不能为空", "/admin/role/add")
return
}
//给角色模型赋值,并保存数据到数据库
role := models.Role{}
role.Title = title
role.Description = description
role.Status = 1
role.AddTime = int(models.GetUnix())
err := models.DB.Create(&role).Error
if err != nil {
con.Error(c, "增加角色失败,请重试", "/admin/role/add")
return
}
con.Success(c, "增加角色成功", "/admin/role")
}
//编辑角色
func (con RoleController) Edit(c *gin.Context) {
//获取角色id
id, err := models.Int(c.Query("id"))
if err != nil {
con.Error(c, "传入数据错误", "/admin/role")
} else {
role := models.Role{Id: id}
models.DB.Find(&role)
c.HTML(http.StatusOK, "admin/role/edit.html", gin.H{
"role": role,
})
}
}
//编辑角色:提交
func (con RoleController) DoEdit(c *gin.Context) {
//获取提交的表单数据
id, err := models.Int(c.PostForm("id"))
if err != nil {
con.Error(c, "传入数据错误", "/admin/role")
return
}
//获取表单的提交数据
//strings.Trim(str, cutset), 去除字符串两边的cutset字符
title := strings.Trim(c.PostForm("title"), " ") // 去除字符串两边的空格
description := strings.Trim(c.PostForm("description"), " ")
//判断角色名称是否为空
if title == "" {
con.Error(c, "角色名称不能为空", "/admin/role/add")
return
}
//查询角色是否存在
role := models.Role{Id: id}
models.DB.Find(&role)
//修改角色属性
role.Title = title
role.Description = description
err = models.DB.Save(&role).Error
if err != nil {
con.Error(c, "修改数据失败", "/admin/role/edit?id="+models.String(id))
return
}
con.Success(c, "修改数据成功", "/admin/role")
}
//删除角色
func (con RoleController) Delete(c *gin.Context) {
//获取提交的表单数据
id, err := models.Int(c.Query("id"))
if err != nil {
con.Error(c, "传入数据错误", "/admin/role")
return
}
//查询角色是否存在
role := models.Role{Id: id}
err = models.DB.Delete(&role).Error
if err != nil {
con.Error(c, "删除数据失败", "/admin/role")
return
}
con.Success(c, "删除数据成功", "/admin/role")
}
3.创建角色html以及js
在templates/admin/role下创建角色相关html
index.html
{{ define "admin/role/index.html" }}
{{ template "admin/public/page_header.html" .}}
<!--
列表展示
-->
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr class="th">
<th>编号</th>
<th>角色名称</th>
<th>角色说明</th>
<th>角色状态</th>
<th class="text-center">操作</th>
</tr>
</thead>
<tbody>
{{ range $key,$value := .roleList}}
<tr>
<td>{{$value.Id}}</td>
<td>{{$value.Title}}</td>
<td>{{$value.Description}}</td>
<td align="center">
{{if eq $value.Status 1}}
<img class="chStatus" data-id="{{$value.Id}}" data-table="role" data-field="status" src="/static/admin/images/yes.gif" />
{{else}}
<img class="chStatus" data-id="{{$value.Id}}" data-table="role" data-field="status" src="/static/admin/images/no.gif" />
{{end}}
</td>
<td class="text-center">
<a href="/admin/role/edit?id={{$value.Id}}">修改</a>
<a class="delete" href="/admin/role/delete?id={{$value.Id}}">删除</a>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</body>
</html>
{{ end }}
add.html
{{ define "admin/role/add.html" }}
{{ template "admin/public/page_header.html" .}}
<div class="panel panel-default">
<div class="panel-heading">
添加角色
</div>
<div class="panel-body">
<div class="table-responsive input-form">
<form action="/admin/role/doAdd" method="post">
<ul>
<li> 角色名称: <input type="text" name="title"/></li>
<li>
角色描述:
<textarea name="description" id="" cols="60" rows="8"></textarea>
</li>
<li>
<br/>
<button type="submit" class="btn btn-default">提交</button>
</li>
</ul>
</form>
</div>
</div>
</div>
</body>
</html>
{{ end }}
edit.html
{{ define "admin/role/edit.html" }}
{{ template "admin/public/page_header.html" .}}
<div class="panel panel-default">
<div class="panel-heading">
编辑角色
</div>
<div class="panel-body">
<div class="table-responsive input-form">
<form action="/admin/role/doEdit" method="post">
<input type="hidden" name="id" value="{{.role.Id}}">
<ul>
<li>角色名称: <input type="text" name="title" value="{{.role.Title}}"/></li>
<li>
角色描述:
<textarea name="description" id="" cols="60" rows="8" >{{.role.Description}}</textarea>
</li>
<li>
<br/>
<button type="submit" class="btn btn-default">提交</button>
</li>
</ul>
</form>
</div>
</div>
</div>
</body>
</html>
{{ end }}
删除角色js提示
在static/admin/js/base.js下增加删除提示
$(function () {
baseApp.init();
//当窗口重置时,重新计算窗口高度
$(window).resize(function () {
baseApp.resizeIframe();
})
})
var baseApp = {
init: function () {
this.initAside()
this.confirmDelete()
this.resizeIframe()
},
initAside: function () { //左侧菜单栏隐藏显示子栏
$(".aside h4").click(function () {
$(this).sibling("ul").slideToggle();
})
},
resizeIframe: function() { // 设置iframe高度
$("rightMain").height($(window).height()-80)
},
confirmDelete: function () { // 删除提示
$(".delete").click(function () {
var flag = confirm("确定要删除该项?")
return flag
})
}
}
4.配置路由
在routes/adminRouters.go下配置角色路由
package routers
import (
"goshop/controllers/admin"
"goshop/middlewares"
"github.com/gin-gonic/gin"
)
//设置admin后台路由
func AdminRoutersInit(r *gin.Engine) {
//路由分组: 配置全局中间件:middlewares.InitMiddleware
adminRouters := r.Group("/admin", middlewares.InitAdminAuthMiddleware)
{
//后台首页
adminRouters.GET("/", admin.MainController{}.Index)
adminRouters.GET("/welcome", admin.MainController{}.Welcome)
//登录页面
adminRouters.GET("/login", admin.LoginController{}.Index) // 实例化控制器,并访问其中方法
adminRouters.POST("/doLogin", admin.LoginController{}.DoIndex)
adminRouters.GET("/loginOut", admin.LoginController{}.LoginOut)
//验证码
adminRouters.GET("/captcha", admin.LoginController{}.Captcha)
//角色路由
adminRouters.GET("/role", admin.RoleController{}.Index)
adminRouters.GET("/role/add", admin.RoleController{}.Add)
adminRouters.POST("/role/doAdd", admin.RoleController{}.DoAdd)
adminRouters.GET("/role/edit", admin.RoleController{}.Edit)
adminRouters.POST("/role/doEdit", admin.RoleController{}.DoEdit)
adminRouters.GET("/role/delete", admin.RoleController{}.Delete)
}
}
5.界面展示如下
列表
增加
编辑
删除
六. 管理员的增、删、改、查以及管理员和角色关联
创建管理员模型
在models目录下创建manager.go角色模型
package models
//管理员表
type Manager struct { // 结构体首字母大写, 和数据库表名对应, 默认访问数据表users, 可以设置访问数据表的方法
Id int
Username string
Password string
Mobile string
Email string
Status int
RoleId int
AddTime int
IsSuper int
Role Role `gorm:"foreignKey:RoleId;references:Id"` // 配置关联关系
}
//配置数据库操作的表名称
func (Manager) TableName() string {
return "manager"
}
创建管理员控制器
在controllers/admin下创建anagerController.go角色控制器
package admin
import (
"github.com/gin-gonic/gin"
"goshop/models"
"net/http"
"strings"
)
type ManagerController struct {
BaseController
}
func (con ManagerController) Index(c *gin.Context) {
//获取管理员列表,以及关联对应的角色
managerList := []models.Manager{}
models.DB.Preload("Role").Find(&managerList)
c.HTML(http.StatusOK, "admin/manager/index.html", gin.H{
"managerList": managerList,
})
}
//添加管理员
func (con ManagerController) Add(c *gin.Context) {
//获取角色
roleList := []models.Role{}
models.DB.Find(&roleList)
c.HTML(http.StatusOK, "admin/manager/add.html", gin.H{
"roleList": roleList,
})
}
//添加管理员:提交
func (con ManagerController) DoAdd(c *gin.Context) {
//获取角色id,判断是否合法
roleId, err := models.Int(c.PostForm("role_id"))
if err != nil {
con.Error(c, "角色不合法", "/admin/manager/add")
return
}
//获取提交的表单信息
username := strings.Trim(c.PostForm("username"), " ")
password := strings.Trim(c.PostForm("password"), " ")
email := strings.Trim(c.PostForm("email"), " ")
mobile := strings.Trim(c.PostForm("mobile"), " ")
//判断用户名和密码是否符合要求
if len(username) < 2 || len(password) < 6 {
con.Error(c, "用户名或密码长度不合法", "/admin/manager/add")
return
}
//判断管理员是否存在
managerList := []models.Manager{}
models.DB.Where("username = ?", username).Find(&managerList)
if len(managerList) > 0 {
con.Error(c, "管理员已存在", "/admin/manager/add")
return
}
//实例化Manager,执行增加管理员
manager := models.Manager{
Username: username,
Password: models.Md5(password),
Email: email,
Mobile: mobile,
AddTime: int(models.GetUnix()),
RoleId: roleId,
Status: 1,
}
err = models.DB.Create(&manager).Error
if err != nil {
con.Error(c, "添加管理员失败", "/admin/manager/add")
return
}
con.Success(c, "添加管理员成功", "/admin/manager")
}
//编辑管理员
func (con ManagerController) Edit(c *gin.Context) {
//获取管理员
id, err := models.Int(c.Query("id"))
if err != nil {
con.Error(c, "传入数据错误", "/admin/manager")
return
}
manager := models.Manager{Id: id}
models.DB.Find(&manager)
if manager.Username == "" {
con.Error(c, "管理员#" + models.String(id) + "不存在", "/admin/manager")
return
}
//获取所有角色
roleList := []models.Role{}
models.DB.Find(&roleList)
c.HTML(http.StatusOK, "admin/manager/edit.html", gin.H{
"manager": manager,
"roleList": roleList,
})
}
//编辑管理员提交
func (con ManagerController) DoEdit(c *gin.Context) {
//获取管理员id,并判断
id, err := models.Int(c.PostForm("id"))
if err != nil {
con.Error(c, "传入数据错误", "/admin/manager")
return
}
//获取角色id,并判断
roleId, err2 := models.Int(c.PostForm("role_id"))
if err2 != nil {
con.Error(c, "传入数据错误", "/admin/manager")
return
}
//获取提交的表单信息
username := strings.Trim(c.PostForm("username"), " ")
password := strings.Trim(c.PostForm("password"), " ")
email := strings.Trim(c.PostForm("email"), " ")
mobile := strings.Trim(c.PostForm("mobile"), " ")
//执行修改
manager := models.Manager{Id: id}
models.DB.Find(&manager)
manager.Username = username
manager.Email = email
manager.RoleId = roleId
manager.Mobile = mobile
//判断密码, 为空 表示不修改密码
if password != "" {
//判断密码长度
if len(password) < 6 {
con.Error(c, "密码长度不合法", "/admin/manager/edit?id" + models.String(id))
return
}
manager.Password = models.Md5(password)
}
//保存
err = models.DB.Save(&manager).Error
if err != nil {
con.Error(c, "修改数据失败", "/admin/manager/edit?id="+models.String(id))
return
}
con.Success(c, "修改数据成功", "/admin/manager")
}
//删除
func (con ManagerController) Delete(c *gin.Context) {
//获取提交的表单数据
id, err := models.Int(c.Query("id"))
if err != nil {
con.Error(c, "传入数据错误", "/admin/manager")
return
}
//查询管理员是否存在
manager := models.Manager{Id: id}
err = models.DB.Delete(&manager).Error
if err != nil {
con.Error(c, "删除数据失败", "/admin/manager")
return
}
con.Success(c, "删除数据成功", "/admin/manager")
}
创建管理员html
在templates/admin/manager下创建相关html
index.html
{{ define "admin/manager/index.html" }}
{{ template "admin/public/page_header.html" .}}
<!--
列表展示
-->
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr class="th">
<th>管理员名称</th>
<th>管理员电话</th>
<th>管理员邮箱</th>
<th>管理员角色</th>
<th>创建时间</th>
<th class="text-center">操作</th>
</tr>
</thead>
<tbody>
{{range $key,$value := .managerList}}
<tr>
<td>{{$value.Username}}</td>
<td>{{$value.Mobile}}</td>
<td>{{$value.Email}}</td>
<td>{{$value.Role.Title}}</td>
<td>{{UnixToTime $value.AddTime}}</td>
<td class="text-center">
<a href="/admin/manager/edit?id={{$value.Id}}">修改</a>
<a class="delete" href="/admin/manager/delete?id={{$value.Id}}">删除</a>
</td>
</tr>
{{end}}
</tbody>
</table>
</body>
</html>
{{end}}
add.html
{{ define "admin/manager/add.html" }}
{{ template "admin/public/page_header.html" .}}
<div class="panel panel-default">
<div class="panel-heading">
增加管理员
</div>
<div class="panel-body">
<div class="table-responsive input-form">
<form action="/admin/manager/doAdd" method="post">
<ul>
<li> 管理员名称: <input type="text" name="username" /></li>
<li> 管理员密码: <input type="password" name="password" /></li>
<li> 管理员电话: <input type="text" name="mobile" /></li>
<li> 管理员邮箱: <input type="text" name="email" /></li>
<li> 管理员角色:
<select name="role_id" id="role_id">
{{range $key,$value := .roleList}}
<option value="{{$value.Id}}">{{$value.Title}}</option>
{{end}}
</select>
</li>
<li>
<br/>
<button type="submit" class="btn btn-default">提交</button>
</li>
</ul>
</form>
</div>
</div>
</div>
</body>
</html>
{{end}}
edit.html
{{ define "admin/manager/edit.html" }}
{{ template "admin/public/page_header.html" .}}
<div class="panel panel-default">
<div class="panel-heading">
修改管理员
</div>
<div class="panel-body">
<div class="table-responsive input-form">
<form action="/admin/manager/doEdit" method="post">
<input type="hidden" name="id" value="{{.manager.Id}}">
<ul>
<li> 管理员名称: <input type="text" name="username" readonly value="{{.manager.Username}}"/></li>
<li> 管理员密码: <input type="password" name="password" placeholder="为空表示不修改密码"/></li>
<li> 管理员电话: <input type="text" name="mobile" value="{{.manager.Mobile}}"/></li>
<li> 管理员邮箱: <input type="text" name="email" value="{{.manager.Email}}"/></li>
<li> 管理员角色:
<select name="role_id" id="role_id">
{{$roleId := .manager.RoleId}}
{{range $key,$value := .roleList}}
{{if eq $value.Id $roleId }}
<option selected value="{{$value.Id}}">{{$value.Title}}</option>
{{else}}
<option value="{{$value.Id}}">{{$value.Title}}</option>
{{end}}
{{end}}
</select>
</li>
<li>
<br/>
<button type="submit" class="btn btn-default">提交</button>
</li>
</ul>
</form>
</div>
</div>
</div>
</body>
</html>
{{end}}
4.配置路由
在routes/adminRouters.go下配置角色路由
package routers
import (
"goshop/controllers/admin"
"goshop/middlewares"
"github.com/gin-gonic/gin"
)
//设置admin后台路由
func AdminRoutersInit(r *gin.Engine) {
//路由分组: 配置全局中间件:middlewares.InitMiddleware
adminRouters := r.Group("/admin", middlewares.InitAdminAuthMiddleware)
{
//后台首页
adminRouters.GET("/", admin.MainController{}.Index)
adminRouters.GET("/welcome", admin.MainController{}.Welcome)
//登录页面
adminRouters.GET("/login", admin.LoginController{}.Index) // 实例化控制器,并访问其中方法
adminRouters.POST("/doLogin", admin.LoginController{}.DoIndex)
adminRouters.GET("/loginOut", admin.LoginController{}.LoginOut)
//验证码
adminRouters.GET("/captcha", admin.LoginController{}.Captcha)
//管理员路由
adminRouters.GET("/manager", admin.ManagerController{}.Index)
adminRouters.GET("/manager/add", admin.ManagerController{}.Add)
adminRouters.POST("/manager/doAdd", admin.ManagerController{}.DoAdd)
adminRouters.GET("/manager/edit", admin.ManagerController{}.Edit)
adminRouters.POST("/manager/doEdit", admin.ManagerController{}.DoEdit)
adminRouters.GET("/manager/delete", admin.ManagerController{}.Delete)
//角色路由
adminRouters.GET("/role", admin.RoleController{}.Index)
adminRouters.GET("/role/add", admin.RoleController{}.Add)
adminRouters.POST("/role/doAdd", admin.RoleController{}.DoAdd)
adminRouters.GET("/role/edit", admin.RoleController{}.Edit)
adminRouters.POST("/role/doEdit", admin.RoleController{}.DoEdit)
}
}
5.界面展示如下
列表
增加
编辑
删除
七. 权限的增、删、改、查以及权限的自关联
创建权限模型
在models目录下创建.go角色模型
package models
//权限模型
type Access struct {
Id int
ModuleName string //模块名称
ActionName string //操作名称
Type int //节点类型 : 1、表示模块 2、表示菜单 3、操作
Url string //路由跳转地址
ModuleId int //此module_id和当前模型的id关联 module_id= 0 表示模块
Sort int
Description string
Status int
AddTime int
AccessItem []Access `gorm:"foreignKey:ModuleId;references:Id"` // 表的自关联,获取该数据的子分类
Checked bool `gorm:"-"` // 用户是否有该权, 忽略本字段,给struct加一个自定义属性,和数据库没有关系
}
func (Access) TableName() string {
return "access"
}
创建权限控制器
在controllers/admin下创建AccessController.go角色控制器
package admin
import (
"github.com/gin-gonic/gin"
"goshop/models"
"net/http"
"strings"
)
type AccessController struct {
BaseController
}
func (con AccessController) Index(c *gin.Context) {
//获取权限列表
accessList := []models.Access{}
models.DB.Where("module_id = ?", 0).Preload("AccessItem").Find(&accessList)
c.HTML(http.StatusOK, "admin/access/index.html", gin.H{
"accessList": accessList,
})
}
func (con AccessController) Add(c *gin.Context) {
//获取顶级模块
accessList := []models.Access{}
models.DB.Where("module_id = ?", 0).Find(&accessList)
c.HTML(http.StatusOK, "admin/access/add.html", gin.H{
"accessList": accessList,
})
}
func (con AccessController) DoAdd(c *gin.Context) {
//获取表单数据
moduleName := strings.Trim(c.PostForm("module_name"), " ")
actionName := strings.Trim(c.PostForm("action_name"), " ")
accessType, err1 := models.Int(c.PostForm("type"))
url := c.PostForm("url")
moduleId, err2 := models.Int(c.PostForm("module_id"))
sort, err3 := models.Int(c.PostForm("sort"))
status, err4 := models.Int(c.PostForm("status"))
description := strings.Trim(c.PostForm("description"), " ")
//判断err
if err1 != nil || err2 != nil || err3 != nil || err4 != nil {
con.Error(c, "传入参数错误", "/admin/access/add")
return
}
//判断moduleName
if moduleName == "" {
con.Error(c, "模块名称不能为空", "/admin/access/add")
return
}
//实例化access
access := models.Access{
ModuleName: moduleName,
ActionName: actionName,
Type: accessType,
Url: url,
ModuleId: moduleId,
Sort: sort,
Status: status,
Description: description,
}
err5 := models.DB.Create(&access).Error
if err5 != nil {
con.Error(c, "添加权限失败", "/admin/access/add")
return
}
con.Success(c, "添加权限成功", "/admin/access")
}
//编辑
func (con AccessController) Edit(c *gin.Context) {
//获取id
id, err := models.Int(c.Query("id"))
if err != nil {
con.Error(c, "传入数据错误", "/admin/access")
return
}
access := models.Access{Id: id}
models.DB.Find(&access)
//获取顶级模块
accessList := []models.Access{}
models.DB.Where("module_id = ?", 0).Find(&accessList)
c.HTML(http.StatusOK, "admin/access/edit.html", gin.H{
"access": access,
"accessList": accessList,
})
}
//编辑:提交
func (con AccessController) DoEdit(c *gin.Context) {
//获取提交的表单数据
id, err := models.Int(c.PostForm("id"))
if err != nil {
con.Error(c, "传入数据错误", "/admin/access/edit?id"+models.String(id))
return
}
//获取表单数据
moduleName := strings.Trim(c.PostForm("module_name"), " ")
actionName := strings.Trim(c.PostForm("action_name"), " ")
accessType, err1 := models.Int(c.PostForm("type"))
url := c.PostForm("url")
moduleId, err2 := models.Int(c.PostForm("module_id"))
sort, err3 := models.Int(c.PostForm("sort"))
status, err4 := models.Int(c.PostForm("status"))
description := strings.Trim(c.PostForm("description"), " ")
//判断err
if err1 != nil || err2 != nil || err3 != nil || err4 != nil {
con.Error(c, "传入数据错误", "/admin/access/edit?id"+models.String(id))
return
}
//判断moduleName
if moduleName == "" {
con.Error(c, "模块名称不能为空", "/admin/access/edit?id"+models.String(id))
return
}
//获取要修改的数据
access := models.Access{Id: id}
models.DB.Find(&access)
access.ModuleName = moduleName
access.ActionName = actionName
access.Type = accessType
access.Url = url
access.ModuleId = moduleId
access.Sort = sort
access.Status = status
access.Description = description
//保存
err5 := models.DB.Save(&access).Error
if err5 != nil {
con.Error(c, "编辑权限失败", "/admin/access/edit?id"+models.String(id))
return
}
con.Success(c, "编辑权限成功", "/admin/access")
}
//删除
func (con AccessController) Delete(c *gin.Context) {
//获取提交的表单数据
id, err := models.Int(c.Query("id"))
if err != nil {
con.Error(c, "传入数据错误", "/admin/access")
return
}
//获取要删除的数据
access := models.Access{Id: id}
models.DB.Find(&access)
if access.ModuleId == 0 { // 顶级模块
accessList := []models.Access{}
models.DB.Where("module_id = ? ", access.Id).Find(&accessList)
if len(accessList) > 0 {
con.Error(c, "当前模块下子菜单,请先删除子菜单后再来删除这个数据", "/admin/access")
return
}
}
// 操作 或者 菜单, 或者顶级模块下面没有子菜单, 可以直接删除
err = models.DB.Delete(&access).Error
if err != nil {
con.Error(c, "删除数据失败", "/admin/access")
return
}
con.Success(c, "删除数据成功", "/admin/access")
}
创建权限html
在templates/admin/access下创建权限相关html
index.html
{{ define "admin/access/index.html" }}
{{ template "admin/public/page_header.html" .}}
<!--
列表展示
-->
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>模块名称</th>
<th>节点类型</th>
<th>操作名称</th>
<th>操作地址</th>
<th>排序</th>
<th>描述</th>
<th class="text-center">操作</th>
</tr>
</thead>
<tbody>
{{range $key,$value := .accessList}}
<tr>
<td>{{$value.ModuleName}}</td>
<td>
{{if eq $value.Type 1}}
模块
{{else if eq $value.Type 2}}
菜单
{{else}}
操作
{{end}}
</td>
<td>{{$value.ActionName}}</td>
<td>{{$value.Url}}</td>
<td>
<span class="chSpanNum" data-id="{{$value.Id}}" data-table="access" data-field="sort">{{$value.Sort}}</span>
</td>
<td>{{$value.Description}}</td>
<td class="text-center">
<a href="/admin/access/edit?id={{$value.Id}}">修改</a>
<a class="delete" href="/admin/access/delete?id={{$value.Id}}">删除</a>
</td>
</tr>
{{range $k,$v := $value.AccessItem}}
<tr>
<td> --{{$v.ModuleName}}</td>
<td>
{{if eq $v.Type 1}}
模块
{{else if eq $v.Type 2}}
菜单
{{else}}
操作
{{end}}
</td>
<td>{{$v.ActionName}}</td>
<td>{{$v.Url}}</td>
<td>
<span class="chSpanNum" data-id="{{$v.Id}}" data-table="access" data-field="sort">{{$v.Sort}}</span>
</td>
<td>{{$v.Description}}</td>
<td class="text-center">
<a href="/admin/access/edit?id={{$v.Id}}">修改</a>
<a class="delete" href="/admin/access/delete?id={{$v.Id}}">删除</a>
</td>
</tr>
{{end}}
{{end}}
</tbody>
</table>
</div>
</body>
</html>
{{end}}
add.html
{{ define "admin/access/add.html" }}
{{ template "admin/public/page_header.html" .}}
<div class="panel panel-default">
<div class="panel-heading">
增加权限
</div>
<div class="panel-body">
<div class="table-responsive input-form">
<form action="/admin/access/doAdd" method="post">
<ul>
<li> 模块名称: <input type="text" name="module_name" /></li>
<li>
节点类型:
<select name="type" id="type">
<option value="1">模块</option>
<option value="2">菜单</option>
<option value="3">操作</option>
</select>
</li>
<li> 操作名称: <input type="text" name="action_name" /></li>
<li> 操作地址: <input type="text" name="url" /></li>
<li> 所属模块:
<select name="module_id" id="module_id">
<option value="0">---顶级模块--</option>
{{range $key,$value := .accessList}}
<option value="{{$value.Id}}">{{$value.ModuleName}}</option>
{{end}}
</select>
</li>
<li> 排 序: <input type="text" name="sort" value="100"/></li>
<li> 描 述 :
<textarea name="description" id="description" cols="60" rows="5"></textarea>
</li>
<li> 状 态:
<input type="radio" name="status" checked value="1" id="a"/> <label for="a">显示</label>
<input type="radio" name="status" value="0" id="b"/><label for="b">隐藏</label>
</li>
<li>
<br/>
<button type="submit" class="btn btn-default">提交</button>
</li>
</ul>
</form>
</div>
</div>
</div>
</body>
</html>
{{end}}
edit.html
{{ define "admin/access/edit.html" }}
{{ template "admin/public/page_header.html" .}}
<div class="panel panel-default">
<div class="panel-heading">
修改权限
</div>
<div class="panel-body">
<div class="table-responsive input-form">
<form action="/admin/access/doEdit" method="post">
<input type="hidden" name="id" value="{{.access.Id}}">
<ul>
<li> 模块名称: <input type="text" name="module_name" value="{{.access.ModuleName}}" /></li>
<li>
节点类型:
<select name="type" id="type">
<option {{if eq .access.Type 1}} selected {{end}} value="1">模块</option>
<option {{if eq .access.Type 2}} selected {{end}} value="2">菜单</option>
<option {{if eq .access.Type 3}} selected {{end}} value="3">操作</option>
</select>
</li>
<li> 操作名称: <input type="text" name="action_name" value="{{.access.ActionName}}" /></li>
<li> 操作地址: <input type="text" name="url" value="{{.access.Url}}" /></li>
<li> 所属模块:
<select name="module_id" id="module_id">
<option value="0">---顶级模块--</option>
{{$moduleId := .access.ModuleId}}
{{range $key,$value := .accessList}}
<option {{if eq $value.Id $moduleId}} selected {{end}} value="{{$value.Id}}">{{$value.ModuleName}}</option>
{{end}}
</select>
</li>
<li> 排 序: <input type="text" name="sort" value="{{.access.Sort}}" /></li>
<li> 描 述 :
<textarea name="description" id="description" cols="60" rows="5">{{.access.Description}}</textarea>
</li>
<li> 状 态:
<input type="radio" name="status" {{if eq .access.Status 1}} checked {{end}} value="1" id="a"/> <label for="a">显示</label>
<input type="radio" name="status" {{if eq .access.Status 0}} checked {{end}} value="0" id="b"/><label for="b">隐藏</label>
</li>
<li>
<br/>
<button type="submit" class="btn btn-default">提交</button>
</li>
</ul>
</form>
</div>
</div>
</div>
</body>
</html>
{{end}}
配置路由
在routes/adminRouters.go下配置权限路由
package routers
import (
"goshop/controllers/admin"
"goshop/middlewares"
"github.com/gin-gonic/gin"
)
//设置admin后台路由
func AdminRoutersInit(r *gin.Engine) {
//路由分组: 配置全局中间件:middlewares.InitMiddleware
adminRouters := r.Group("/admin", middlewares.InitAdminAuthMiddleware)
{
//后台首页
adminRouters.GET("/", admin.MainController{}.Index)
adminRouters.GET("/welcome", admin.MainController{}.Welcome)
//登录页面
adminRouters.GET("/login", admin.LoginController{}.Index) // 实例化控制器,并访问其中方法
adminRouters.POST("/doLogin", admin.LoginController{}.DoIndex)
adminRouters.GET("/loginOut", admin.LoginController{}.LoginOut)
//验证码
adminRouters.GET("/captcha", admin.LoginController{}.Captcha)
//管理员路由
adminRouters.GET("/manager", admin.ManagerController{}.Index)
adminRouters.GET("/manager/add", admin.ManagerController{}.Add)
adminRouters.POST("/manager/doAdd", admin.ManagerController{}.DoAdd)
adminRouters.GET("/manager/edit", admin.ManagerController{}.Edit)
adminRouters.POST("/manager/doEdit", admin.ManagerController{}.DoEdit)
adminRouters.GET("/manager/delete", admin.ManagerController{}.Delete)
//角色路由
adminRouters.GET("/role", admin.RoleController{}.Index)
adminRouters.GET("/role/add", admin.RoleController{}.Add)
adminRouters.POST("/role/doAdd", admin.RoleController{}.DoAdd)
adminRouters.GET("/role/edit", admin.RoleController{}.Edit)
adminRouters.POST("/role/doEdit", admin.RoleController{}.DoEdit)
adminRouters.GET("/role/delete", admin.RoleController{}.Delete)
//权限路由
adminRouters.GET("/access", admin.AccessController{}.Index)
adminRouters.GET("/access/add", admin.AccessController{}.Add)
adminRouters.POST("/access/doAdd", admin.AccessController{}.DoAdd)
adminRouters.GET("/access/edit", admin.AccessController{}.Edit)
adminRouters.POST("/access/doEdit", admin.AccessController{}.DoEdit)
adminRouters.GET("/access/delete", admin.AccessController{}.Delete
}
}
界面展示
列表
增加
编辑
删除
八.角色和权限关联,角色授权,已授权的权限选中
1.在角色列表中增加'授权'操作
在角色index.html中增加授权连接代码,代码如下:
<td class="text-center">
<a href="/admin/role/auth?id={{$value.Id}}">授权</a>
<a href="/admin/role/edit?id={{$value.Id}}">修改</a>
<a class="delete" href="/admin/role/delete?id={{$value.Id}}">删除</a>
</td>
2.授权操作,以及已授权的权限选中
在角色html模板文件下创建auth.html页面
{{ define "admin/role/auth.html" }}
{{ template "admin/public/page_header.html" .}}
<div class="panel panel-default">
<div class="panel-heading">
角色授权
</div>
<div class="panel-body">
<form action="/admin/role/doAuth" method="post">
<input type="hidden" name="role_id" value="{{.roleId}}">
<table class="table table-bordered">
{{range $key, $value := .accessList}}
<tr>
<td align="right" style="background: #D8E2FA; padding-right: 10px; width: 150px;">
<label>
<input type="checkbox" {{if eq $value.Checked true}} checked {{end}}class="module_cbo" value="{{$value.Id}}"
name="access_node[]">
{{$value.ModuleName}}
</label>
</td>
<td>
{{range $k,$v := $value.AccessItem}}
<label>
<input type="checkbox" {{if eq $v.Checked true}} checked {{end}} class="action_cbo" value="{{$v.Id}}"
name="access_node[]">
{{$v.ActionName}} </label>
{{end}}
</td>
</tr>
{{end}}
</table>
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
</div>
</body>
</html>
{{ end }}
3.增加授权方法
在角色控制器中增加授权方法
//授权
func (con RoleController) Auth(c *gin.Context) {
//获取id
id, err := models.Int(c.Query("id"))
if err != nil {
con.Error(c, "传入数据错误", "/admin/role")
return
}
role := models.Role{Id: id}
models.DB.Find(&role)
//获取所有权限列表
accessList := []models.Access{}
models.DB.Where("module_id = ?", 0).Preload("AccessItem").Find(&accessList)
//获取当前角色拥有的权限,并把权限id放在一个map对象中
roleAccess := []models.RoleAccess{}
models.DB.Where("role_id = ?", id).Find(&roleAccess)
roleAccessMap := make(map[int]int)
for _, v := range roleAccess {
roleAccessMap[v.AccessId] = v.AccessId
}
//循环遍历所有权限数据,判断当前权限的id是否在角色权限的map对象中,如果是的话给当前数据加入checked属性
for i := 0; i < len(accessList); i++ { //循环权限列表
if _, ok := roleAccessMap[accessList[i].Id]; ok { // 判断当前权限是否在角色权限的map对象中
accessList[i].Checked = true
}
for j := 0; j < len(accessList[i].AccessItem); j++ { // 判断当前权限的子栏位是否在角色权限的map中
if _, ok := roleAccessMap[accessList[i].AccessItem[j].Id]; ok { // 判断当前权限是否在角色权限的map对象中
accessList[i].AccessItem[j].Checked = true
}
}
}
c.HTML(http.StatusOK, "admin/role/auth.html", gin.H{
"roleId": id,
"accessList": accessList,
})
}
//授权提交
func (con RoleController) DoAuth(c *gin.Context) {
//获取提交的表单数据
roleId, err := models.Int(c.PostForm("role_id"))
if err != nil {
con.Error(c, "传入数据错误", "/admin/role")
return
}
//获取表单提交的权限id切片
accessIds := c.PostFormArray("access_node[]")
//先删除当前角色对应的权限
roleAccess := models.RoleAccess{}
models.DB.Where("role_id = ?", roleId).Delete(&roleAccess)
//循环遍历accessIds,增加当前角色对应的权限
for _, v := range accessIds {
roleAccess.RoleId = roleId
accessId, _ := models.Int(v)
roleAccess.AccessId = accessId
models.DB.Create(&roleAccess)
}
con.Success(c, "角色授权成功", "/admin/role")
}
4.配置路由
在routes/adminRouters.go下配置授权路由
adminRouters.GET("/role/auth", admin.RoleController{}.Auth)
adminRouters.POST("/role/doAuth", admin.RoleController{}.DoAuth)
九.用户权限判断
登录后显示用户名称、根据用户的权限动态显示左侧菜单
判断当前登录用户的权限 、没有权限访问则拒绝
1.后台首页MainControllers.go修改
package admin
// 进入后台系统首页
import (
"encoding/json"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"goshop/models"
"net/http"
)
type MainController struct {
}
func (con MainController) Index(c *gin.Context) {
//获取Session里面保存的用户信息
session := sessions.Default(c)
userinfo := session.Get("userinfo")
//判断Session中的用户信息是否存在,如果不存在跳转到登录页面(注意需要判断) 如果存在继续向下执行
//session.Get获取返回的结果是一个空接口类型,所以需要进行类型断言: 判断userinfo是不是一个string
userinfoStr, ok := userinfo.(string)
if ok { // 说明是一个string
//1.获取用户信息
var userinfoStruct []models.Manager
//把获取到的用户信息转换结构体
json.Unmarshal([]byte(userinfoStr), &userinfoStruct)
//获取所有权限列表
accessList := []models.Access{}
models.DB.Where("module_id = ?", 0).Preload("AccessItem", func(db *gorm.DB) *gorm.DB {
return db.Order("access.sort DESC")
}).Order("sort DESC").Find(&accessList)
//获取当前角色拥有的权限,并把权限id放在一个map对象中
roleAccess := []models.RoleAccess{}
models.DB.Where("role_id = ?", userinfoStruct[0].RoleId).Find(&roleAccess)
roleAccessMap := make(map[int]int)
for _, v := range roleAccess {
roleAccessMap[v.AccessId] = v.AccessId
}
//循环遍历所有权限数据,判断当前权限的id是否在角色权限的map对象中,如果是的话给当前数据加入checked属性
for i := 0; i < len(accessList); i++ { //循环权限列表
if _, ok := roleAccessMap[accessList[i].Id]; ok { // 判断当前权限是否在角色权限的map对象中
accessList[i].Checked = true
}
for j := 0; j < len(accessList[i].AccessItem); j++ { // 判断当前权限的子栏位是否在角色权限的map中
if _, ok := roleAccessMap[accessList[i].AccessItem[j].Id]; ok { // 判断当前权限是否在角色权限的map对象中
accessList[i].AccessItem[j].Checked = true
}
}
}
c.HTML(http.StatusOK, "admin/main/index.html", gin.H{
"username": userinfoStruct[0].Username,
"isSuper": userinfoStruct[0].IsSuper, // 是否超级管理员, 超级管理员显示全部
"accessList": accessList,
})
} else {
c.Redirect(http.StatusFound, "/admin/login")
}
}
func (con MainController) Welcome(c *gin.Context) {
c.HTML(http.StatusOK, "admin/main/welcome.html", gin.H{})
}
2.后台首页templates/main.index.html展示
{{ define "admin/main/index.html" }}
{{ template "admin/public/page_header.html" .}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<!--The content below is only a placeholder and can be replaced.-->
<link rel="stylesheet" href="/static/admin/bootstrap/css/bootstrap.css">
<link rel="stylesheet" href="/static/admin/css/basic.css">
<script type="text/javascript" src="/static/admin/bootstrap/js/jquery-1.10.1.js"></script>
<script type="text/javascript" src="/static/admin/js/base.js"></script>
<nav class="navbar navbar-inverse" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<img src="/static/admin/images/node.jpg" class="logo" />
</div>
<div class="collapse navbar-collapse" id="example-navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<li><a>欢迎您,{{.username}}</a>
</li>
<li><a href="/admin/loginOut">安全退出</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-sm-2">
{{ template "admin/public/page_aside.html" .}}
</div>
<div class="col-sm-10">
<iframe width="100%" height="500px" name="rightMain" src="/admin/welcome"></iframe>
</div>
</div>
</div>
</body>
</html>
{{ end }}
3.admin/public/page_header.html
<!-- 相当于给模板定义一个名字, define end 必须成对出现 -->
{{ define "admin/public/page_header.html" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<!--The content below is only a placeholder and can be replaced.-->
<link rel="stylesheet" href="/static/admin/bootstrap/css/bootstrap.css">
<link rel="stylesheet" href="/static/admin/css/basic.css">
<script type="text/javascript" src="/static/admin/bootstrap/js/jquery-1.10.1.js"></script>
<script type="text/javascript" src="/static/admin/bootstrap/js/bootstrap.js"></script>
<script type="text/javascript" src="/static/admin/js/base.js"></script>
{{end}}
4.左侧菜单栏admin/public/page_aside.html
<!-- 相当于给模板定义一个名字, define end 必须成对出现 -->
{{ define "admin/public/page_aside.html" }}
<ul class="aside">
<!-- 判断当前用户权限,并显示对应目录 -->
{{$isSuper := .isSuper}}
{{range $key, $value := .accessList}}
<!-- 是否超级管理员, 超级管理员显示全部 -->
{{if eq $isSuper 1}}
<li>
<h4>{{$value.ModuleName}}</h4>
<ul>
{{range $k,$v := $value.AccessItem}}
<!-- 判断权限类型: 2 菜单, 3 操作; 当Type=2时,才会显示菜单-->
{{if eq $v.Type 2}}
<li class="list-group-item">
<a href="/admin/{{$v.Url}}" target="rightMain">
{{$v.ActionName}}
</a>
</li>
{{end}}
{{end}}
</ul>
</li>
{{else}}
<!-- 判断用户是否有该权限 -->
{{if eq $value.Checked true}}
<li>
<h4>{{$value.ModuleName}}</h4>
<ul>
{{range $k,$v := $value.AccessItem}}
{{if eq $v.Checked true}}
<!-- 判断权限类型: 2 菜单, 3 操作-->
{{if eq $v.Type 2}}
<li class="list-group-item">
<a href="/admin/{{$v.Url}}" target="rightMain">
{{$v.ActionName}}
</a>
</li>
{{end}}
{{end}}
{{end}}
</ul>
</li>
{{end}}
{{end}}
{{end}}
</ul>
{{end}}
5.权限模型代码修改
package models
//权限模型
type Access struct {
Id int
ModuleName string //模块名称
ActionName string //操作名称
Type int //节点类型 : 1、表示模块 2、表示菜单 3、操作
Url string //路由跳转地址
ModuleId int //此module_id和当前模型的id关联 module_id= 0 表示模块
Sort int
Description string
Status int
AddTime int
AccessItem []Access `gorm:"foreignKey:ModuleId;references:Id"` // 表的自关联,获取该数据的子分类
Checked bool `gorm:"-"` // 用户是否有该权, 忽略本字段,给struct加一个自定义属性,和数据库没有关系
}
func (Access) TableName() string {
return "access"
}
6.中间件middlewares/adminAuth.go修改
package middlewares
//中间件: 作用: 在执行路由之前或者之后进行相关逻辑判断
import (
"encoding/json"
"fmt"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"gopkg.in/ini.v1"
"goshop/models"
"net/http"
"os"
"strings"
)
func InitAdminAuthMiddleware(c *gin.Context) {
//权限判断: 没有登录的用户不能进入后台管理中心
//1、获取Url访问的地址
//当地址后面带参数时:,如: admin/captcha?t=0.8706946438889653,需要处理
//strings.Split(c.Request.URL.String(), "?"): 把c.Request.URL.String()请求地址按照?分割成切片
pathname := strings.Split(c.Request.URL.String(), "?")[0]
//2、获取Session里面保存的用户信息
session := sessions.Default(c)
userinfo := session.Get("userinfo")
//3、判断Session中的用户信息是否存在,如果不存在跳转到登录页面(注意需要判断) 如果存在继续向下执行
//session.Get获取返回的结果是一个空接口类型,所以需要进行类型断言: 判断userinfo是不是一个string
userinfoStr, ok := userinfo.(string)
if ok { // 说明是一个string
var userinfoStruct []models.Manager
//把获取到的用户信息转换结构体
err := json.Unmarshal([]byte(userinfoStr), &userinfoStruct)
if err != nil || !(len(userinfoStruct) > 0 && userinfoStruct[0].Username != "") {
if pathname != "/admin/login" && pathname != "/admin/dologin" && pathname != "/admin/captcha" {
//跳转到登录页面
c.Redirect(http.StatusFound, "/admin/login")
}
} else { //表示用户登录成功
//获取当前访问的URL对应的权限id,判断权限id是否在角色对应的权限中
// strings.Replace 字符串替换
urlPath := strings.Replace(pathname, "/admin/", "", 1)
//排除权限判断:不是超级管理员并且不在相关权限内
if userinfoStruct[0].IsSuper == 0 && !excludeAuthPath("/" + urlPath){
//判断用户权限:当前用户权限是否可以访问url地址
//获取当前角色拥有的权限,并把权限id放在一个map对象中
roleAccess := []models.RoleAccess{}
models.DB.Where("role_id = ?", userinfoStruct[0].RoleId).Find(&roleAccess)
roleAccessMap := make(map[int]int)
for _, v := range roleAccess {
roleAccessMap[v.AccessId] = v.AccessId
}
//实例化access
access := models.Access{}
//查询权限id
models.DB.Where("url = ? ", urlPath).Find(&access)
//判断权限id是否在角色对应的权限中
if _, ok := roleAccessMap[access.Id]; !ok {
c.String(http.StatusOK, "没有权限")
c.Abort() // 终止程序
}
}
}
} else {
//4、如果Session不存在,判断当前访问的URl是否是login doLogin captcha,如果不是跳转到登录页面,如果是不行任何操作
//说明用户没有登录
//需要排除到不需要做权限判断的路由
if pathname != "/admin/login" && pathname != "/admin/dologin" && pathname != "/admin/captcha" {
//跳转到登录页面
c.Redirect(http.StatusFound, "/admin/login")
}
}
}
//排除权限判断的方法
func excludeAuthPath(urlPath string) bool {
//加载配置文件
cfg, err := ini.Load("./conf/app.ini")
if err != nil {
fmt.Printf("Fail to read file: %v", err)
os.Exit(1)
}
//获取需要排除的地址
excludeAuthPath := cfg.Section("").Key("excludeAuthPath").String()
//拆分字符串成为一个切片
excludeAuthPathSlice := strings.Split(excludeAuthPath, ",")
//判断传入的地址是否在排除地址内
for _, v := range excludeAuthPathSlice {
if v == urlPath {
return true
}
}
return false
}
[上一节][golang gin框架] 13.Gin 商城项目-配置公共基类实现公共的成功,失败提示页面 用户登录、退出登录、以及权限判断
[下一节][golang gin框架] 15.Gin 商城项目-封装上传图片方法,轮播图的增删改查以及异步修改状态,数量