做权限管理模块

This commit is contained in:
2025-11-03 18:00:12 +08:00
parent 88d2f527f4
commit ca1d265e34
7 changed files with 1674 additions and 91 deletions
+264
View File
@@ -0,0 +1,264 @@
package controllers
import (
"encoding/json"
"fmt"
"server/models"
"strconv"
beego "github.com/beego/beego/v2/server/web"
"github.com/beego/beego/v2/core/logs"
)
// PermissionController 权限管理控制器
type PermissionController struct {
beego.Controller
}
// GetAllMenuPermissions 获取所有菜单权限列表(用于分配权限)
func (c *PermissionController) GetAllMenuPermissions() {
menus, err := models.GetAllMenuPermissions()
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "获取菜单列表失败",
"error": err.Error(),
}
} else {
c.Data["json"] = map[string]interface{}{
"success": true,
"message": "获取菜单列表成功",
"data": menus,
}
}
c.ServeJSON()
}
// GetRolePermissions 获取指定角色的权限
func (c *PermissionController) GetRolePermissions() {
roleId, err := c.GetInt(":roleId")
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "角色ID参数错误",
}
c.ServeJSON()
return
}
permissions, err := models.GetRolePermissions(roleId)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "获取角色权限失败",
"error": err.Error(),
}
} else {
c.Data["json"] = map[string]interface{}{
"success": true,
"message": "获取角色权限成功",
"data": permissions,
}
}
c.ServeJSON()
}
// AssignRolePermissions 为角色分配权限
func (c *PermissionController) AssignRolePermissions() {
roleId, err := c.GetInt(":roleId")
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "角色ID参数错误",
}
c.ServeJSON()
return
}
// 解析请求体
var requestData struct {
MenuIds []int `json:"menu_ids"`
}
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "请求参数错误",
"error": err.Error(),
}
c.ServeJSON()
return
}
// 获取当前用户ID(从JWT中获取)
userIdData := c.Ctx.Input.GetData("userId")
var createBy string
if userIdData != nil {
userId, ok := userIdData.(int)
if ok {
createBy = strconv.Itoa(userId)
}
}
// 记录日志(用于调试)
logs.Info(fmt.Sprintf("开始为角色 %d 分配权限,共 %d 个菜单", roleId, len(requestData.MenuIds)))
// 分配权限
err = models.AssignRolePermissions(roleId, requestData.MenuIds, createBy)
if err != nil {
logs.Error(fmt.Sprintf("分配权限失败: %v", err))
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "分配权限失败",
"error": err.Error(),
}
} else {
logs.Info(fmt.Sprintf("角色 %d 权限分配成功", roleId))
c.Data["json"] = map[string]interface{}{
"success": true,
"message": "分配权限成功",
}
}
c.ServeJSON()
}
// GetUserPermissions 获取当前登录用户的权限
func (c *PermissionController) GetUserPermissions() {
// 从JWT中获取用户ID
userIdData := c.Ctx.Input.GetData("userId")
if userIdData == nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "未获取到用户信息",
}
c.ServeJSON()
return
}
userId, ok := userIdData.(int)
if !ok {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "用户ID格式错误",
}
c.ServeJSON()
return
}
permissions, err := models.GetUserPermissions(userId)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "获取用户权限失败",
"error": err.Error(),
}
} else {
c.Data["json"] = map[string]interface{}{
"success": true,
"message": "获取用户权限成功",
"data": permissions,
}
}
c.ServeJSON()
}
// GetUserMenuTree 获取当前用户有权限访问的菜单树
func (c *PermissionController) GetUserMenuTree() {
// 从JWT中获取用户ID
userIdData := c.Ctx.Input.GetData("userId")
if userIdData == nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "未获取到用户信息",
}
c.ServeJSON()
return
}
userId, ok := userIdData.(int)
if !ok {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "用户ID格式错误",
}
c.ServeJSON()
return
}
menuTree, err := models.GetUserMenuTree(userId)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "获取用户菜单失败",
"error": err.Error(),
}
} else {
c.Data["json"] = map[string]interface{}{
"success": true,
"message": "获取用户菜单成功",
"data": menuTree,
}
}
c.ServeJSON()
}
// CheckPermission 检查用户是否拥有指定权限
func (c *PermissionController) CheckPermission() {
// 从JWT中获取用户ID
userIdData := c.Ctx.Input.GetData("userId")
if userIdData == nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "未获取到用户信息",
}
c.ServeJSON()
return
}
userId, ok := userIdData.(int)
if !ok {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "用户ID格式错误",
}
c.ServeJSON()
return
}
// 获取权限标识
permission := c.GetString("permission")
if permission == "" {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "权限标识不能为空",
}
c.ServeJSON()
return
}
hasPermission, err := models.CheckUserPermission(userId, permission)
if err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "检查权限失败",
"error": err.Error(),
}
} else {
c.Data["json"] = map[string]interface{}{
"success": true,
"message": "权限检查完成",
"data": map[string]interface{}{
"has_permission": hasPermission,
},
}
}
c.ServeJSON()
}
+188
View File
@@ -0,0 +1,188 @@
package middleware
import (
"server/models"
"strings"
"github.com/beego/beego/v2/server/web/context"
)
// PermissionMiddleware 权限验证中间件
// 根据路由的权限标识检查用户是否有访问权限
func PermissionMiddleware() func(ctx *context.Context) {
return func(ctx *context.Context) {
// 获取当前请求的路径
path := ctx.Input.URL()
// 不需要权限验证的路径列表
publicPaths := []string{
"/api/login",
"/api/logout",
"/api/reset-password",
"/api/program-categories/public",
"/api/program-infos/public",
"/api/files/public",
}
// 检查是否为公开路径
for _, p := range publicPaths {
if path == p {
return
}
}
// 检查是否为公开预览接口
if strings.HasPrefix(path, "/api/files/public-preview/") {
return
}
// 获取用户ID
userIdData := ctx.Input.GetData("userId")
if userIdData == nil {
// 如果没有用户ID,说明未登录,这个应该在JWT中间件中处理
// 这里直接返回,因为JWT中间件已经拦截了
return
}
userId, ok := userIdData.(int)
if !ok {
ctx.Output.JSON(map[string]interface{}{
"success": false,
"message": "用户ID格式错误",
}, false, false)
return
}
// 获取当前路由对应的权限标识
permission := getPermissionByPath(path, ctx.Input.Method())
// 如果没有权限标识,说明该接口不需要权限控制
if permission == "" {
return
}
// 检查用户是否拥有该权限
hasPermission, err := models.CheckUserPermission(userId, permission)
if err != nil {
ctx.Output.JSON(map[string]interface{}{
"success": false,
"message": "权限验证失败",
"error": err.Error(),
}, false, false)
return
}
if !hasPermission {
ctx.Output.JSON(map[string]interface{}{
"success": false,
"message": "您没有权限访问此接口",
"code": 403,
}, false, false)
return
}
}
}
// getPermissionByPath 根据路径和方法获取权限标识
// 这是一个简化版本,实际应该从数据库中动态获取路由-权限映射关系
func getPermissionByPath(path, method string) string {
// 权限映射表(路径模式 -> 权限标识)
// 这里只列举了部分示例,实际应该从数据库中加载
permissionMap := map[string]string{
// 用户管理
"GET:/api/allUsers": "user:list",
"GET:/api/user/:id": "user:detail",
"POST:/api/addUser": "user:add",
"POST:/api/editUser/:id": "user:edit",
"DELETE:/api/deleteUser/:id": "user:delete",
"POST:/api/changePassword/:id":"user:changePassword",
// 角色管理
"GET:/api/roles": "role:list",
"POST:/api/roles": "role:create",
"GET:/api/roles/:id": "role:detail",
"POST:/api/roles/:id": "role:update",
"DELETE:/api/roles/:id": "role:delete",
// 菜单管理
"GET:/api/allmenu": "menu:list",
"POST:/api/menu": "menu:create",
"PUT:/api/menu/:id": "menu:update",
"DELETE:/api/menu/:id": "menu:delete",
// 文件管理
"GET:/api/files": "file:list",
"POST:/api/files": "file:upload",
"GET:/api/files/my": "file:my",
"GET:/api/files/download/:id": "file:download",
"GET:/api/files/preview/:id": "file:preview",
"GET:/api/files/:id": "file:detail",
"PUT:/api/files/:id": "file:update",
"DELETE:/api/files/:id": "file:delete",
"GET:/api/files/search": "file:search",
"GET:/api/files/statistics": "file:statistics",
// 租户管理
"GET:/api/tenant/list": "tenant:list",
"POST:/api/tenant": "tenant:create",
"PUT:/api/tenant/:id": "tenant:update",
"DELETE:/api/tenant/:id": "tenant:delete",
"POST:/api/tenant/:id/audit": "tenant:audit",
"GET:/api/tenant/:id": "tenant:detail",
// 知识库
"GET:/api/knowledge/list": "knowledge:list",
"GET:/api/knowledge/detail": "knowledge:detail",
"POST:/api/knowledge/create": "knowledge:create",
"POST:/api/knowledge/update": "knowledge:update",
"POST:/api/knowledge/delete": "knowledge:delete",
}
// 匹配路径(简化版本,不支持动态参数匹配)
key := method + ":" + path
if perm, ok := permissionMap[key]; ok {
return perm
}
// 尝试匹配动态路由(简单的ID参数替换)
// 例如:/api/user/123 -> /api/user/:id
pathParts := strings.Split(path, "/")
for pattern, perm := range permissionMap {
parts := strings.Split(pattern, ":")
if len(parts) != 2 {
continue
}
methodPart := parts[0]
pathPattern := parts[1]
if methodPart != method {
continue
}
patternParts := strings.Split(pathPattern, "/")
if len(patternParts) != len(pathParts) {
continue
}
match := true
for i, part := range patternParts {
if strings.HasPrefix(part, ":") {
// 动态参数,跳过
continue
}
if part != pathParts[i] {
match = false
break
}
}
if match {
return perm
}
}
// 如果没有找到匹配的权限标识,返回空字符串(表示不需要权限控制)
return ""
}
+305
View File
@@ -0,0 +1,305 @@
package models
import (
"fmt"
"strings"
"time"
"github.com/beego/beego/v2/client/orm"
)
// RoleMenu 角色-菜单关联表模型
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"`
}
// TableName 指定表名
func (r *RoleMenu) TableName() string {
return "yz_role_menus"
}
// RolePermission 角色权限响应结构(包含菜单信息)
type RolePermission struct {
RoleId int `json:"role_id"`
RoleName string `json:"role_name"`
MenuIds []int `json:"menu_ids"`
Permissions []string `json:"permissions"` // 权限标识列表
}
// MenuPermission 菜单权限信息
type MenuPermission struct {
MenuId int `json:"menu_id"`
MenuName string `json:"menu_name"`
Path string `json:"path"`
MenuType int `json:"menu_type"` // 1: 页面菜单, 2: API接口
Permission string `json:"permission"` // 权限标识
ParentId int `json:"parent_id"`
}
func init() {
orm.RegisterModel(new(RoleMenu))
}
// GetRoleMenus 获取指定角色的所有菜单权限
func GetRoleMenus(roleId int) ([]int, error) {
o := orm.NewOrm()
var menuIds []int
_, err := o.Raw("SELECT menu_id FROM yz_role_menus WHERE role_id = ?", roleId).QueryRows(&menuIds)
if err != nil {
return nil, fmt.Errorf("获取角色菜单失败: %v", err)
}
return menuIds, nil
}
// GetRolePermissions 获取角色的详细权限信息(包括菜单和API权限)
func GetRolePermissions(roleId int) (*RolePermission, error) {
o := orm.NewOrm()
// 获取角色信息
var role Role
err := o.Raw("SELECT * FROM yz_roles WHERE role_id = ? AND delete_time IS NULL", roleId).QueryRow(&role)
if err != nil {
return nil, fmt.Errorf("角色不存在: %v", err)
}
// 获取角色关联的所有菜单ID
menuIds, err := GetRoleMenus(roleId)
if err != nil {
return nil, err
}
// 获取权限标识列表
var permissions []string
if len(menuIds) > 0 {
// 构建IN查询的占位符和参数
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 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)
}
}
return &RolePermission{
RoleId: role.RoleId,
RoleName: role.RoleName,
MenuIds: menuIds,
Permissions: permissions,
}, nil
}
// GetAllMenuPermissions 获取所有菜单权限列表(用于分配权限时展示)
func GetAllMenuPermissions() ([]*MenuPermission, error) {
o := orm.NewOrm()
var menus []*MenuPermission
_, err := o.Raw("SELECT id as menu_id, name as menu_name, path, menu_type, permission, parent_id FROM yz_menus WHERE status = 1 ORDER BY parent_id, `order`").QueryRows(&menus)
if err != nil {
return nil, fmt.Errorf("获取菜单列表失败: %v", err)
}
return menus, nil
}
// AssignRolePermissions 为角色分配权限(菜单)
func AssignRolePermissions(roleId int, menuIds []int, createBy string) error {
o := orm.NewOrm()
// 先删除该角色的所有权限(使用更快的方式)
_, err := o.Raw("DELETE FROM yz_role_menus WHERE role_id = ?", roleId).Exec()
if err != nil {
return fmt.Errorf("删除旧权限失败: %v", err)
}
// 如果没有新权限,直接返回
if len(menuIds) == 0 {
return nil
}
// 使用更高效的批量插入方式
// 如果数据量太大,分批插入以避免超时
batchSize := 500 // 每批500条,MySQL可以高效处理
total := len(menuIds)
for i := 0; i < total; i += batchSize {
end := i + batchSize
if end > total {
end = total
}
batch := menuIds[i:end]
// 构建批量INSERT语句
query := "INSERT INTO yz_role_menus (role_id, menu_id, create_by) VALUES "
values := make([]interface{}, 0, len(batch)*3)
placeholders := make([]string, 0, len(batch))
for _, menuId := range batch {
placeholders = append(placeholders, "(?, ?, ?)")
values = append(values, roleId, menuId, createBy)
}
query += strings.Join(placeholders, ", ")
// 执行批量插入
_, err = o.Raw(query, values...).Exec()
if err != nil {
return fmt.Errorf("插入新权限失败(批次 %d/%d): %v", i/batchSize+1, (total+batchSize-1)/batchSize, err)
}
}
return nil
}
// GetUserPermissions 获取用户的所有权限(通过用户角色)
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)
}
// CheckUserPermission 检查用户是否拥有指定权限
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
}
// MenuTreeNode 菜单树节点(包含子节点)
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"`
}
// GetUserMenuTree 获取用户有权限访问的菜单树(仅页面菜单)
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
}
// 获取角色的菜单ID列表
menuIds, err := GetRoleMenus(user.Role)
if err != nil {
return nil, err
}
if len(menuIds) == 0 {
return []*MenuTreeNode{}, nil
}
// 获取菜单信息(仅页面菜单)
var menus []*Menu
// 构建IN查询的占位符和参数
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 status = 1 ORDER BY parent_id, `order`", strings.Join(placeholders, ","))
_, err = o.Raw(query, args...).QueryRows(&menus)
if err != nil {
return nil, fmt.Errorf("获取菜单列表失败: %v", err)
}
// 转换为MenuTreeNode
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
}
// buildMenuTree 构建菜单树
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
}
+8
View File
@@ -128,6 +128,14 @@ func init() {
beego.Router("/api/roles/:id", &controllers.RoleController{}, "post:UpdateRole")
beego.Router("/api/roles/:id", &controllers.RoleController{}, "delete:DeleteRole")
// 权限管理路由
beego.Router("/api/permissions/menus", &controllers.PermissionController{}, "get:GetAllMenuPermissions")
beego.Router("/api/permissions/role/:roleId", &controllers.PermissionController{}, "get:GetRolePermissions")
beego.Router("/api/permissions/role/:roleId", &controllers.PermissionController{}, "post:AssignRolePermissions")
beego.Router("/api/permissions/user", &controllers.PermissionController{}, "get:GetUserPermissions")
beego.Router("/api/permissions/user/menus", &controllers.PermissionController{}, "get:GetUserMenuTree")
beego.Router("/api/permissions/check", &controllers.PermissionController{}, "get:CheckPermission")
// 手动配置特殊路由(无法通过自动路由处理的)
beego.Router("/api/allmenu", &controllers.MenuController{}, "get:GetAllMenus")
beego.Router("/api/program-categories/public", &controllers.ProgramCategoryController{}, "get:GetProgramCategoriesPublic")