yunzer_go/server/models/permission.go
2025-11-07 17:38:59 +08:00

503 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package models
import (
"database/sql"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/beego/beego/v2/client/orm"
)
// 角色-菜单关联表
type RoleMenu struct {
Id int `orm:"auto" json:"id"`
RoleId int `orm:"column(role_id)" json:"role_id"`
MenuId int `orm:"column(menu_id)" json:"menu_id"`
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
CreateBy string `orm:"column(create_by);size(50);null" json:"create_by"`
}
func (r *RoleMenu) TableName() string {
return "yz_role_menus"
}
// 角色权限响应结构
type RolePermission struct {
RoleId int `json:"role_id"`
RoleName string `json:"role_name"`
MenuIds []int `json:"menu_ids"`
Permissions []string `json:"permissions"`
}
// 菜单权限信息结构
type MenuPermission struct {
MenuId int `json:"menu_id"`
MenuName string `json:"menu_name"`
Path string `json:"path"`
MenuType int `json:"menu_type"` // 1页面菜单, 2API
Permission string `json:"permission"` // 权限标识
ParentId int `json:"parent_id"`
Default int8 `json:"default"` // 0全局, 1平台用户, 2租户用户
}
func init() {
orm.RegisterModel(new(RoleMenu))
}
// 获取指定角色的菜单ID列表
func GetRoleMenus(roleId int) ([]int, error) {
o := orm.NewOrm()
var menuIdsJson sql.NullString
var menuIdsStr string
// 优先尝试解析JSON字段
err := o.Raw("SELECT IFNULL(JSON_UNQUOTE(JSON_EXTRACT(menu_ids, '$')), '[]') FROM yz_roles WHERE role_id = ? AND delete_time IS NULL", roleId).QueryRow(&menuIdsStr)
if err == nil && menuIdsStr != "" && menuIdsStr != "[]" {
menuIdsJson = sql.NullString{String: menuIdsStr, Valid: true}
} else {
// 若失败用CAST再次尝试依然失败直接返回空
menuIdsStr = ""
err2 := o.Raw("SELECT CAST(IFNULL(menu_ids, '[]') AS CHAR) FROM yz_roles WHERE role_id = ? AND delete_time IS NULL", roleId).QueryRow(&menuIdsStr)
if err2 != nil {
if err2 == orm.ErrNoRows {
return []int{}, nil
}
return []int{}, nil
}
if menuIdsStr != "" && menuIdsStr != "[]" && menuIdsStr != "null" {
menuIdsJson = sql.NullString{String: menuIdsStr, Valid: true}
} else {
return []int{}, nil
}
}
if !menuIdsJson.Valid || menuIdsJson.String == "" {
return []int{}, nil
}
jsonStr := strings.TrimSpace(menuIdsJson.String)
jsonStr = strings.ReplaceAll(jsonStr, "\n", "")
jsonStr = strings.ReplaceAll(jsonStr, "\r", "")
jsonStr = strings.ReplaceAll(jsonStr, " ", "")
if jsonStr == "" || jsonStr == "[]" || jsonStr == "null" || jsonStr == "NULL" {
return []int{}, nil
}
var menuIds []int
err = json.Unmarshal([]byte(jsonStr), &menuIds)
if err != nil {
return []int{}, nil
}
return menuIds, nil
}
// min 辅助函数
func min(a, b int) int {
if a < b {
return a
}
return b
}
// 获取角色的详细权限菜单和API权限
func GetRolePermissions(roleId int) (*RolePermission, error) {
o := orm.NewOrm()
role, err := GetRoleById(roleId)
if err != nil {
return nil, fmt.Errorf("角色不存在: %v", err)
}
menuIds := role.MenuIds
if menuIds == nil {
menuIds = []int{}
}
permissions := []string{}
if len(menuIds) > 0 {
placeholders := make([]string, len(menuIds))
args := make([]interface{}, len(menuIds))
for i, id := range menuIds {
placeholders[i] = "?"
args[i] = id
}
query := fmt.Sprintf("SELECT DISTINCT permission FROM yz_menus WHERE id IN (%s) AND delete_time IS NULL AND permission IS NOT NULL AND permission != ''", strings.Join(placeholders, ","))
_, err = o.Raw(query, args...).QueryRows(&permissions)
if err != nil {
return nil, fmt.Errorf("获取权限标识失败: %v", err)
}
if permissions == nil {
permissions = []string{}
}
}
return &RolePermission{
RoleId: role.RoleId,
RoleName: role.RoleName,
MenuIds: menuIds,
Permissions: permissions,
}, nil
}
// 获取全部菜单权限信息(未删除)
func GetAllMenuPermissions() ([]*MenuPermission, error) {
o := orm.NewOrm()
var results []struct {
MenuId int
MenuName string
Path string
MenuType int
Permission sql.NullString
ParentId int
}
_, err := o.Raw("SELECT id as menu_id, name as menu_name, path, menu_type, permission, parent_id FROM yz_menus WHERE delete_time IS NULL ORDER BY parent_id, `order`").QueryRows(&results)
if err != nil {
return nil, fmt.Errorf("获取菜单列表失败: %v", err)
}
menus := make([]*MenuPermission, 0, len(results))
for _, r := range results {
menu := &MenuPermission{
MenuId: r.MenuId,
MenuName: r.MenuName,
Path: r.Path,
MenuType: r.MenuType,
ParentId: r.ParentId,
Default: 0,
}
if r.Permission.Valid {
menu.Permission = r.Permission.String
} else {
menu.Permission = ""
}
menus = append(menus, menu)
}
return menus, nil
}
// 获取可分配菜单列表,支持平台/租户员工
func GetAllMenuPermissionsForUser(userId int, userType string, roleId int) ([]*MenuPermission, error) {
o := orm.NewOrm()
var roleDefault int8 = 0
if roleId > 0 {
role, err := GetRoleById(roleId)
if err == nil && role != nil {
roleDefault = role.Default
}
}
if userType == "user" {
allMenus, err := GetAllMenuPermissions()
if err != nil {
return nil, err
}
if roleDefault > 0 {
filteredMenus := make([]*MenuPermission, 0)
for _, menu := range allMenus {
if menu.Default == 0 || menu.Default == roleDefault {
filteredMenus = append(filteredMenus, menu)
}
}
return filteredMenus, nil
}
return allMenus, nil
}
if userType == "employee" {
var employee Employee
err := o.Raw("SELECT * FROM yz_tenant_employees WHERE id = ? AND delete_time IS NULL", userId).QueryRow(&employee)
if err != nil {
return nil, fmt.Errorf("员工不存在: %v", err)
}
if employee.Role == 0 {
return []*MenuPermission{}, nil
}
menuIds, err := GetRoleMenus(employee.Role)
if err != nil {
return nil, fmt.Errorf("获取角色菜单失败: %v", err)
}
if len(menuIds) == 0 {
return []*MenuPermission{}, nil
}
// 获取全部菜单父子关系内存递归收集所有父菜单ID
type menuParent struct {
Id int
ParentId int
}
var allMenuParents []menuParent
_, err = o.Raw("SELECT id, parent_id FROM yz_menus WHERE delete_time IS NULL").QueryRows(&allMenuParents)
if err != nil {
return nil, fmt.Errorf("获取菜单父子关系失败: %v", err)
}
menuParentMap := make(map[int]int)
for _, mp := range allMenuParents {
menuParentMap[mp.Id] = mp.ParentId
}
parentIds := make(map[int]bool)
var findParents func(pid int)
findParents = func(pid int) {
if pid == 0 || parentIds[pid] {
return
}
parentIds[pid] = true
if parentId, exists := menuParentMap[pid]; exists && parentId > 0 {
findParents(parentId)
}
}
for _, menuId := range menuIds {
if parentId, exists := menuParentMap[menuId]; exists && parentId > 0 {
findParents(parentId)
}
}
allMenuIds := make(map[int]bool)
for _, id := range menuIds {
allMenuIds[id] = true
}
for pid := range parentIds {
allMenuIds[pid] = true
}
finalMenuIds := make([]int, 0, len(allMenuIds))
for id := range allMenuIds {
finalMenuIds = append(finalMenuIds, id)
}
placeholders := make([]string, len(finalMenuIds))
args := make([]interface{}, len(finalMenuIds))
for i, id := range finalMenuIds {
placeholders[i] = "?"
args[i] = id
}
type menuResult struct {
MenuId int
MenuName string
Path string
MenuType int
Permission sql.NullString
ParentId int
}
var results []menuResult
query := fmt.Sprintf("SELECT id as menu_id, name as menu_name, path, menu_type, permission, parent_id FROM yz_menus WHERE id IN (%s) AND delete_time IS NULL ORDER BY parent_id, `order`", strings.Join(placeholders, ","))
_, err = o.Raw(query, args...).QueryRows(&results)
if err != nil {
return nil, fmt.Errorf("获取菜单列表失败: %v", err)
}
menus := make([]*MenuPermission, 0, len(results))
for _, r := range results {
menu := &MenuPermission{
MenuId: r.MenuId,
MenuName: r.MenuName,
Path: r.Path,
MenuType: r.MenuType,
ParentId: r.ParentId,
Default: 0,
}
if r.Permission.Valid {
menu.Permission = r.Permission.String
} else {
menu.Permission = ""
}
menus = append(menus, menu)
}
// 菜单表没有default字段下面逻辑实际上不会筛选掉任何菜单仅保留
if roleDefault > 0 {
filteredMenus := make([]*MenuPermission, 0)
for _, menu := range menus {
if menu.Default == 0 || menu.Default == roleDefault {
filteredMenus = append(filteredMenus, menu)
}
}
return filteredMenus, nil
}
return menus, nil
}
return []*MenuPermission{}, nil
}
// 分配角色权限更新role菜单ID
func AssignRolePermissions(roleId int, menuIds []int, createBy string) error {
var jsonData []byte
var err error
if len(menuIds) == 0 {
jsonData = []byte("[]")
} else {
jsonData, err = json.Marshal(menuIds)
if err != nil {
return fmt.Errorf("序列化菜单ID失败: %v", err)
}
}
// 使用重试机制处理连接失效问题
maxRetries := 3
var lastErr error
for i := 0; i < maxRetries; i++ {
o := orm.NewOrm()
// 在执行前检查连接有效性
db, dbErr := orm.GetDB("default")
if dbErr == nil {
// 尝试 Ping 检查连接
if pingErr := db.Ping(); pingErr != nil {
// 连接失效,等待后重试
if i < maxRetries-1 {
time.Sleep(200 * time.Millisecond)
lastErr = fmt.Errorf("数据库连接失效: %v", pingErr)
continue
}
}
}
// 尝试执行更新
_, err = o.Raw("UPDATE yz_roles SET menu_ids = ?, update_by = ?, update_time = NOW() WHERE role_id = ?", string(jsonData), createBy, roleId).Exec()
if err == nil {
// 成功,返回
return nil
}
// 检查是否是连接错误
errStr := err.Error()
if strings.Contains(errStr, "invalid connection") ||
strings.Contains(errStr, "driver: bad connection") ||
strings.Contains(errStr, "connection reset") {
lastErr = err
// 如果是连接错误且不是最后一次重试,等待后继续
if i < maxRetries-1 {
time.Sleep(200 * time.Millisecond)
continue
}
} else {
// 非连接错误,直接返回
return fmt.Errorf("更新角色权限失败: %v", err)
}
}
// 所有重试都失败了
return fmt.Errorf("更新角色权限失败(已重试%d次: %v", maxRetries, lastErr)
}
// 获取用户所有权限(从所属角色)
func GetUserPermissions(userId int) (*RolePermission, error) {
o := orm.NewOrm()
var user User
err := o.Raw("SELECT * FROM yz_users WHERE id = ? AND delete_time IS NULL", userId).QueryRow(&user)
if err != nil {
return nil, fmt.Errorf("用户不存在: %v", err)
}
if user.Role == 0 {
return &RolePermission{
RoleId: 0,
RoleName: "无角色",
MenuIds: []int{},
Permissions: []string{},
}, nil
}
return GetRolePermissions(user.Role)
}
// 检查用户是否拥有指定权限
func CheckUserPermission(userId int, permission string) (bool, error) {
if permission == "" {
return true, nil
}
userPerms, err := GetUserPermissions(userId)
if err != nil {
return false, err
}
for _, perm := range userPerms.Permissions {
if perm == permission {
return true, nil
}
}
return false, nil
}
// 菜单树节点
type MenuTreeNode struct {
Id int `json:"id"`
Name string `json:"name"`
Path string `json:"path"`
ParentId int `json:"parent_id"`
Icon string `json:"icon"`
Order int `json:"order"`
Status int8 `json:"status"`
ComponentPath string `json:"component_path"`
IsExternal int8 `json:"is_external"`
ExternalUrl string `json:"external_url"`
MenuType int8 `json:"menu_type"`
Permission string `json:"permission"`
Children []*MenuTreeNode `json:"children"`
}
// 获取用户菜单树(仅页面菜单)
func GetUserMenuTree(userId int) ([]*MenuTreeNode, error) {
o := orm.NewOrm()
var user User
err := o.Raw("SELECT * FROM yz_users WHERE id = ? AND delete_time IS NULL", userId).QueryRow(&user)
if err != nil {
return nil, fmt.Errorf("用户不存在: %v", err)
}
if user.Role == 0 {
return []*MenuTreeNode{}, nil
}
menuIds, err := GetRoleMenus(user.Role)
if err != nil {
return nil, err
}
if len(menuIds) == 0 {
return []*MenuTreeNode{}, nil
}
var menus []*Menu
placeholders := make([]string, len(menuIds))
args := make([]interface{}, len(menuIds))
for i, id := range menuIds {
placeholders[i] = "?"
args[i] = id
}
query := fmt.Sprintf("SELECT * FROM yz_menus WHERE id IN (%s) AND menu_type = 1 AND delete_time IS NULL ORDER BY parent_id, `order`", strings.Join(placeholders, ","))
_, err = o.Raw(query, args...).QueryRows(&menus)
if err != nil {
return nil, fmt.Errorf("获取菜单列表失败: %v", err)
}
var nodes []*MenuTreeNode
for _, m := range menus {
nodes = append(nodes, &MenuTreeNode{
Id: m.Id,
Name: m.Name,
Path: m.Path,
ParentId: m.ParentId,
Icon: m.Icon,
Order: m.Order,
Status: m.Status,
ComponentPath: m.ComponentPath,
IsExternal: m.IsExternal,
ExternalUrl: m.ExternalUrl,
MenuType: m.MenuType,
Permission: m.Permission,
Children: []*MenuTreeNode{},
})
}
return buildMenuTree(nodes, 0), nil
}
// 构建菜单树
func buildMenuTree(menus []*MenuTreeNode, parentId int) []*MenuTreeNode {
var tree []*MenuTreeNode
for _, menu := range menus {
if menu.ParentId == parentId {
menu.Children = buildMenuTree(menus, menu.Id)
tree = append(tree, menu)
}
}
return tree
}