一. 根据用户的权限动态显示左侧菜单微服务
1.引入
func (con MainController) Index(c *gin.Context) {
//获取Session里面保存的用户信息
session := sessions.Default(c)
userinfo := session.Get("userinfo_admin")
//判断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")
}
}
2. 完善handler/rbacRole.go下的RoleAuth()方法
//授权
func (e *RbacRole) RoleAuth(ctx context.Context, req *pb.RoleAuthRequest, res *pb.RoleAuthResponse) error {
//1、获取角色id req.RoleId
//获取所有权限列表
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 = ?", req.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[int(accessList[i].Id)]; ok { // 判断当前权限是否在角色权限的map对象中, 需要进行类型转换
accessList[i].Checked = true
}
for j := 0; j < len(accessList[i].AccessItem); j++ { // 判断当前权限的子栏位是否在角色权限的map中
if _, ok := roleAccessMap[int(accessList[i].AccessItem[j].Id)]; ok { // 判断当前权限是否在角色权限的map对象中
accessList[i].AccessItem[j].Checked = true
}
}
}
//处理数据:进行类型转换匹配操作
var tempList []*pb.AccessModel
for _, v := range accessList {
var tempItemList []*pb.AccessModel
for _, k := range v.AccessItem {
tempItemList = append(tempItemList, &pb.AccessModel{
Id: int64(k.Id),
ModuleName: k.ModuleName,
ActionName: k.ActionName,
Type: int64(k.Type),
Url: k.Url,
ModuleId: int64(k.ModuleId),
Sort: int64(k.Sort),
Description: k.Description,
Status: int64(k.Status),
Checked: k.Checked, //注意,不要忘了角色是否有这个权限的判断
AddTime: int64(k.AddTime),
})
}
tempList = append(tempList, &pb.AccessModel{
Id: int64(v.Id),
ModuleName: v.ModuleName,
ActionName: v.ActionName,
Type: int64(v.Type),
Url: v.Url,
ModuleId: int64(v.ModuleId),
Sort: int64(v.Sort),
Description: v.Description,
Status: int64(v.Status),
AddTime: int64(v.AddTime),
Checked: v.Checked, //注意,不要忘了角色是否有这个权限的判断
AccessItem: tempItemList,
})
}
res.AccessList = tempList
return nil
}
3. controllers/admin/mainController.go的Index()调用微服务的RoleAuth()方法,判断是否显示对应的菜单
func (con MainController) Index(c *gin.Context) {
//获取Session里面保存的用户信息
session := sessions.Default(c)
userinfo := session.Get("userinfo_admin")
//判断Session中的用户信息是否存在,如果不存在跳转到登录页面(注意需要判断) 如果存在继续向下执行
//session.Get获取返回的结果是一个空接口类型,所以需要进行类型断言: 判断userinfo是不是一个string
userinfoStr, ok := userinfo.(string)
if ok { // 说明是一个string
//1.获取用户信息
var userinfoStruct []models.Manager
//把获取到的用户信息转换结构体
json.Unmarshal([]byte(userinfoStr), &userinfoStruct)
//调用微服务获取当前角色拥有权限(菜单)
rbacClient := pbRbac.NewRbacRoleService("rbac", models.RbacClient)
res, _ := rbacClient.RoleAuth(context.Background(), &pbRbac.RoleAuthRequest{
RoleId: int64(userinfoStruct[0].RoleId),
})
c.HTML(http.StatusOK, "admin/main/index.html", gin.H{
"username": userinfoStruct[0].Username,
"accessList": res.AccessList, // 从微服务返回中获取数据
"isSuper": userinfoStruct[0].IsSuper, // 是否超级管理员, 超级管理员显示全部
})
} else {
c.Redirect(http.StatusFound, "/admin/login")
}
}
二.权限判断: 没有登录的用户不能进入后台管理中心
1.抽离adminAuth.go中间件代码,做成一个微服务方法
(1).proto/rbacRole.proto增加rpc MiddlewaresAuth方法
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_admin")
//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
}
//权限判断:获取当前访问的URL对应的权限id,判断权限id是否在角色对应的权限中
rpc MiddlewaresAuth(MiddlewaresAuthRequest) returns (MiddlewaresAuthResponse) {}
//权限判断:角色是否有访问urlPath的权限,请求参数
message MiddlewaresAuthRequest{
int64 roleId=1;
string urlPath=2;
}
//权限判断:响应参数 是否具有该访问权限
message MiddlewaresAuthResponse{
bool hasPermission=1;
}
syntax = "proto3";
package rbac;
option go_package = "./proto/rbacRole";
//角色管理
service RbacRole {
//获取角色rpc方法: 请求参数RoleGetRequest, 响应参数RoleGetResponse
rpc RoleGet(RoleGetRequest) returns (RoleGetResponse) {}
//增加角色rpc方法: 请求参数RoleAddRequest, 响应参数RoleAddResponse
rpc RoleAdd(RoleAddRequest) returns (RoleAddResponse) {}
//编辑角色rpc方法: 请求参数RoleEditRequest, 响应参数RoleEditResponse
rpc RoleEdit(RoleEditRequest) returns (RoleEditResponse) {}
//删除角色rpc方法: 请求参数RoleDeleteRequest, 响应参数RoleDeleteResponse
rpc RoleDelete(RoleDeleteRequest) returns (RoleDeleteResponse) {}
//授权
rpc RoleAuth(RoleAuthRequest) returns (RoleAuthResponse) {}
//授权提交
rpc RoleDoAuth(RoleDoAuthRequest) returns (RoleDoAuthResponse) {}
//权限判断:获取当前访问的URL对应的权限id,判断权限id是否在角色对应的权限中
rpc MiddlewaresAuth(MiddlewaresAuthRequest) returns (MiddlewaresAuthResponse) {}
}
//角色相关model
message RoleModel{
int64 id=1;
string title=2;
string description=3;
int64 status=4;
int64 addTime =5;
}
//权限相关模型:参考models/access.go
message AccessModel{
int64 id=1;
string moduleName =2;
string actionName=3;
int64 type=4;
string url=5;
int64 moduleId=6;
int64 sort =7;
string description=8;
int64 status=9;
int64 addTime=10;
bool checked=11;
repeated AccessModel accessItem=12;
}
//获取角色请求参数
message RoleGetRequest{
//角色id
int64 id =1;
}
//获取角色响应参数
message RoleGetResponse{
//角色model切片
repeated RoleModel roleList=1;
}
//增加角色请求参数
message RoleAddRequest{
//角色名称
string title=1;
//说明
string description=2;
//状态
int64 status=3;
//增加时间
int64 addTime =4;
}
//增加角色响应参数
message RoleAddResponse{
//是否增加成功
bool success=1;
//返回状态说明
string message=2;
}
//编辑角色请求参数
message RoleEditRequest{
//角色id
int64 id=1;
//角色名称
string title=2;
//说明
string description=3;
//状态
int64 status=4;
//增加时间
int64 addTime =5;
}
//编辑角色响应参数
message RoleEditResponse{
//是否编辑成功
bool success=1;
//返回状态说明
string message=2;
}
//删除角色请求参数
message RoleDeleteRequest{
//角色id
int64 id=1;
}
//删除角色响应参数
message RoleDeleteResponse{
//是否删除成功
bool success=1;
//返回状态说明
string message=2;
}
//角色授权参数
message RoleAuthRequest{
int64 roleId=1;
}
//角色授权响应参数
message RoleAuthResponse{
repeated AccessModel accessList=1;
}
//角色授权提交参数
message RoleDoAuthRequest{
int64 roleId=1;
repeated string accessIds=2;
}
//角色授权提交响应参数
message RoleDoAuthResponse{
bool success=1;
string message=2;
}
//权限判断:角色是否有访问urlPath的权限,请求参数
message MiddlewaresAuthRequest{
int64 roleId=1;
string urlPath=2;
}
//权限判断:响应参数 是否具有该访问权限
message MiddlewaresAuthResponse{
bool hasPermission=1;
}
(2).更新proto文件夹下对应rbacRole的方法
(3).handler/rbacRole.go下实现上面的rpc MiddlewaresAuth方法
//权限判断
func (e *RbacRole) MiddlewaresAuth(ctx context.Context, req *pb.MiddlewaresAuthRequest, res *pb.MiddlewaresAuthResponse) error {
//判断用户权限:当前用户权限是否可以访问url地址
// 1、根据角色获取当前角色的权限列表,然后把权限id放在一个map类型的对象里面
roleAccess := []models.RoleAccess{}
models.DB.Where("role_id=?", req.RoleId).Find(&roleAccess)
roleAccessMap := make(map[int]int)
for _, v := range roleAccess {
roleAccessMap[v.AccessId] = v.AccessId
}
// 2、获取当前访问的url对应的权限id 判断权限id是否在角色对应的权限
// pathname /admin/manager
access := models.Access{}
models.DB.Where("url = ?", req.UrlPath).Find(&access)
//3、判断当前访问的url对应的权限id 是否在权限列表的id中
if _, ok := roleAccessMap[int(access.Id)]; !ok {
res.HasPermission = false
} else {
res.HasPermission = true
}
return nil
}
2.项目端中间件adminAuth.go调用上面微服务方法,进行权限判断
(1).复制server端proto下rbacRole.go以及rbacRole文件夹到项目的proto下
(2).在项目的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"
pbRbac "goshop/proto/rbacRole"
"net/http"
"os"
"context"
"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_admin")
//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地址
//调用微服务进行权限判断
rbacClient := pbRbac.NewRbacRoleService("rbac", models.RbacClient)
res, _ := rbacClient.MiddlewaresAuth(context.Background(), &pbRbac.MiddlewaresAuthRequest{
RoleId: int64(userinfoStruct[0].RoleId),
UrlPath: urlPath,
})
//3、判断当前访问的url对应的权限id 是否在权限列表的id中
if !res.HasPermission {
c.String(200, "没有权限")
c.Abort()
}
//获取当前角色拥有的权限,并把权限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
}
(3).验证上面的微服务方法是否正确
三. Rbac微服务数据库抽离
1.抽离rbac相关数据表
app_name = rbac
# possible values: DEBUG, INFO, WARNING, ERROR, FATAL
[mysql]
ip = 127.0.0.1
port = 3306
user = root
password = 123456
database = ginshop
[consul]
addr = localhost:8082
2.修改app.ini配置
app_name = rbac
# possible values: DEBUG, INFO, WARNING, ERROR, FATAL
[mysql]
ip = 127.0.0.1
port = 3306
user = root
password = 123456
database = ginshoprbac
[consul]
addr = localhost:8082
3.删除客户端rbac相关model
package models
//管理员表
type Manager struct { // 结构体首字母大写, 和数据库表名对应, 默认访问数据表users, 可以设置访问数据表的方法
Id int
Username string
Password string
Mobile string
Email string
Status int
RoleId int
AddTime int
IsSuper int
}
好了,到这里,项目微服务相关实战内容就到这里了,查看更多文章,请看主页
[上一节]wiefuw[golang gin框架] 45.Gin商城项目-微服务实战之后台Rbac微服务之角色权限关联