using AmtScanner.Api.Data; using AmtScanner.Api.Models; using AmtScanner.Api.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace AmtScanner.Api.Controllers; /// /// 菜单控制器 /// [ApiController] public class MenuController : ControllerBase { private readonly IMenuService _menuService; private readonly AppDbContext _context; public MenuController(IMenuService menuService, AppDbContext context) { _menuService = menuService; _context = context; } /// /// 获取用户菜单(adminSystem 前端使用的路由) /// [Authorize] [HttpGet("api/v3/system/menus/simple")] public async Task>>> GetUserMenus() { var userIdClaim = User.FindFirst("userId")?.Value; if (string.IsNullOrEmpty(userIdClaim) || !int.TryParse(userIdClaim, out var userId)) { return Ok(ApiResponse>.Fail(401, "无效的用户")); } var menus = await _menuService.GetUserMenusAsync(userId); return Ok(ApiResponse>.Success(menus)); } /// /// 获取所有菜单列表 /// [Authorize] [HttpGet("api/menu/list")] public async Task>>> GetAllMenus() { var menus = await _menuService.GetAllMenusAsync(); return Ok(ApiResponse>.Success(menus)); } /// /// 创建菜单 /// [Authorize] [HttpPost("api/menu")] public async Task>> CreateMenu([FromBody] CreateMenuRequest request) { Console.WriteLine($"[CreateMenu] 收到请求: ParentId={request.ParentId}, Name={request.Name}, Path={request.Path}, Component={request.Component}"); // 处理路径:子菜单(有 ParentId)的路径不能以 / 开头 var menuPath = request.Path; if (request.ParentId.HasValue && menuPath.StartsWith("/")) { // 子菜单:取最后一段路径作为相对路径 var pathParts = menuPath.Split('/', StringSplitOptions.RemoveEmptyEntries); menuPath = pathParts.Length > 0 ? pathParts[^1] : menuPath.TrimStart('/'); Console.WriteLine($"[CreateMenu] 子菜单路径已修正: {request.Path} -> {menuPath}"); } // 目录(无 ParentId)的路径必须以 / 开头 if (!request.ParentId.HasValue && !menuPath.StartsWith("/")) { menuPath = "/" + menuPath; Console.WriteLine($"[CreateMenu] 目录路径已修正: {request.Path} -> {menuPath}"); } // 检查 Name 是否唯一,如果重复则自动生成唯一名称 var menuName = request.Name; if (await _context.Menus.AnyAsync(m => m.Name == menuName)) { // 生成唯一名称:原名称 + 时间戳 menuName = $"{request.Name}_{DateTime.Now:yyyyMMddHHmmss}"; Console.WriteLine($"[CreateMenu] 菜单名称已修正(避免重复): {request.Name} -> {menuName}"); } var menu = new Menu { ParentId = request.ParentId, Name = menuName, Path = menuPath, Component = request.Component, Title = request.Title, Icon = request.Icon, Sort = request.Sort, IsHide = request.IsHide, KeepAlive = request.KeepAlive, Link = request.Link, IsIframe = request.IsIframe, Roles = request.Roles != null ? string.Join(",", request.Roles) : null }; _context.Menus.Add(menu); await _context.SaveChangesAsync(); Console.WriteLine($"[CreateMenu] 菜单已创建: Id={menu.Id}, ParentId={menu.ParentId}"); // 如果有组件路径且不是目录组件,自动创建 Vue 组件文件 if (!string.IsNullOrEmpty(request.Component) && request.Component != "/index/index" && request.AutoCreateComponent) { try { await CreateVueComponentAsync(request.Component, request.Title ?? request.Name); } catch (Exception ex) { Console.WriteLine($"[CreateMenu] 创建组件文件失败: {ex.Message}"); // 不影响菜单创建,只是记录日志 } } return Ok(ApiResponse.Success(menu, "菜单创建成功")); } /// /// 创建 Vue 组件文件 /// private async Task CreateVueComponentAsync(string componentPath, string title) { var configuration = HttpContext.RequestServices.GetRequiredService(); var viewsPath = configuration["Frontend:ViewsPath"] ?? "../adminSystem/src/views"; // 组件路径格式: /test/page -> test/page.vue // 统一使用正斜杠,然后转换为系统路径分隔符 var relativePath = componentPath.TrimStart('/').Replace('\\', '/'); // 构建完整路径,确保路径分隔符正确 var fullPath = Path.GetFullPath(Path.Combine(viewsPath, $"{relativePath}.vue")); var directory = Path.GetDirectoryName(fullPath); Console.WriteLine($"[CreateVueComponent] viewsPath: {viewsPath}"); Console.WriteLine($"[CreateVueComponent] relativePath: {relativePath}"); Console.WriteLine($"[CreateVueComponent] fullPath: {fullPath}"); Console.WriteLine($"[CreateVueComponent] directory: {directory}"); // 如果文件已存在,不覆盖 if (System.IO.File.Exists(fullPath)) { Console.WriteLine($"[CreateVueComponent] 组件文件已存在: {fullPath}"); return; } // 创建目录 if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { Console.WriteLine($"[CreateVueComponent] 创建目录: {directory}"); Directory.CreateDirectory(directory); } // 生成组件名称 (PascalCase) var componentName = string.Join("", relativePath.Split('/', '-', '_') .Where(s => !string.IsNullOrEmpty(s)) .Select(s => char.ToUpper(s[0]) + s.Substring(1))); // 用于 CSS 类名的路径(使用连字符) var cssClassName = relativePath.Replace('/', '-').Replace('_', '-'); // 生成 Vue 组件模板 var template = $@" "; await System.IO.File.WriteAllTextAsync(fullPath, template, System.Text.Encoding.UTF8); Console.WriteLine($"[CreateVueComponent] 组件文件已创建: {fullPath}"); } /// /// 更新菜单 /// [Authorize] [HttpPut("api/menu/{id}")] public async Task>> UpdateMenu(int id, [FromBody] UpdateMenuRequest request) { var menu = await _context.Menus.FindAsync(id); if (menu == null) { return Ok(ApiResponse.Fail(404, "菜单不存在")); } if (request.ParentId.HasValue) menu.ParentId = request.ParentId; if (!string.IsNullOrEmpty(request.Name)) menu.Name = request.Name; if (!string.IsNullOrEmpty(request.Path)) menu.Path = request.Path; if (request.Component != null) menu.Component = request.Component; if (!string.IsNullOrEmpty(request.Title)) menu.Title = request.Title; if (request.Icon != null) menu.Icon = request.Icon; if (request.Sort.HasValue) menu.Sort = request.Sort.Value; if (request.IsHide.HasValue) menu.IsHide = request.IsHide.Value; if (request.KeepAlive.HasValue) menu.KeepAlive = request.KeepAlive.Value; if (request.Link != null) menu.Link = request.Link; if (request.IsIframe.HasValue) menu.IsIframe = request.IsIframe.Value; if (request.Roles != null) menu.Roles = string.Join(",", request.Roles); await _context.SaveChangesAsync(); return Ok(ApiResponse.Success(menu, "菜单更新成功")); } /// /// 删除菜单 /// [Authorize] [HttpDelete("api/menu/{id}")] public async Task>> DeleteMenu(int id) { var menu = await _context.Menus.FindAsync(id); if (menu == null) { return Ok(ApiResponse.Fail(404, "菜单不存在")); } // 检查是否为系统内置菜单 if (menu.IsSystem) { return Ok(ApiResponse.Fail(400, "系统内置菜单不能删除")); } // 检查是否有子菜单 var hasChildren = await _context.Menus.AnyAsync(m => m.ParentId == id); if (hasChildren) { return Ok(ApiResponse.Fail(400, "请先删除子菜单")); } // 删除角色菜单关联 var roleMenus = await _context.RoleMenus.Where(rm => rm.MenuId == id).ToListAsync(); _context.RoleMenus.RemoveRange(roleMenus); _context.Menus.Remove(menu); await _context.SaveChangesAsync(); return Ok(ApiResponse.Success(null, "菜单删除成功")); } /// /// 重置菜单为默认配置 /// [Authorize] [HttpPost("api/menu/reset")] public async Task>> ResetMenus() { // 清空现有菜单和角色菜单关联 _context.RoleMenus.RemoveRange(_context.RoleMenus); _context.Menus.RemoveRange(_context.Menus); await _context.SaveChangesAsync(); // 重新创建默认菜单 var menus = new List { // 仪表盘菜单(系统内置) new() { Id = 1, Name = "Dashboard", Path = "/dashboard", Component = "/index/index", Title = "menus.dashboard.title", Icon = "ri:pie-chart-line", Sort = 1, Roles = "R_SUPER,R_ADMIN,R_USER", IsSystem = true }, new() { Id = 2, ParentId = 1, Name = "Console", Path = "console", Component = "/dashboard/console", Title = "menus.dashboard.console", KeepAlive = false, Sort = 1, Roles = "R_SUPER,R_ADMIN,R_USER", IsSystem = true }, // AMT 设备管理菜单(系统内置) new() { Id = 5, Name = "AmtManage", Path = "/amt", Component = "/index/index", Title = "设备管理", Icon = "ri:computer-line", Sort = 2, Roles = "R_SUPER,R_ADMIN", IsSystem = true }, new() { Id = 6, ParentId = 5, Name = "AmtScan", Path = "scan", Component = "/amt/scan", Title = "网络扫描", KeepAlive = true, Sort = 1, Roles = "R_SUPER,R_ADMIN", IsSystem = true }, new() { Id = 7, ParentId = 5, Name = "AmtDevices", Path = "devices", Component = "/amt/devices", Title = "设备列表", KeepAlive = true, Sort = 2, Roles = "R_SUPER,R_ADMIN", IsSystem = true }, new() { Id = 8, ParentId = 5, Name = "AmtCredentials", Path = "credentials", Component = "/amt/credentials", Title = "AMT凭据", KeepAlive = true, Sort = 3, Roles = "R_SUPER,R_ADMIN", IsSystem = true }, new() { Id = 9, ParentId = 5, Name = "WindowsCredentials", Path = "windows-credentials", Component = "/amt/windows-credentials", Title = "Windows凭据", KeepAlive = true, Sort = 4, Roles = "R_SUPER,R_ADMIN", IsSystem = true }, // 系统管理菜单(系统内置) new() { Id = 10, Name = "System", Path = "/system", Component = "/index/index", Title = "menus.system.title", Icon = "ri:user-3-line", Sort = 99, Roles = "R_SUPER,R_ADMIN", IsSystem = true }, new() { Id = 11, ParentId = 10, Name = "User", Path = "user", Component = "/system/user", Title = "menus.system.user", KeepAlive = true, Sort = 1, Roles = "R_SUPER,R_ADMIN", IsSystem = true }, new() { Id = 12, ParentId = 10, Name = "Role", Path = "role", Component = "/system/role", Title = "menus.system.role", KeepAlive = true, Sort = 2, Roles = "R_SUPER", IsSystem = true }, new() { Id = 13, ParentId = 10, Name = "UserCenter", Path = "user-center", Component = "/system/user-center", Title = "menus.system.userCenter", IsHide = true, KeepAlive = true, Sort = 3, Roles = "R_SUPER,R_ADMIN,R_USER", IsSystem = true }, new() { Id = 14, ParentId = 10, Name = "Menus", Path = "menu", Component = "/system/menu", Title = "menus.system.menu", KeepAlive = true, Sort = 4, Roles = "R_SUPER", IsSystem = true } }; _context.Menus.AddRange(menus); await _context.SaveChangesAsync(); // 重新分配角色菜单 var superRole = await _context.Roles.FirstAsync(r => r.RoleCode == "R_SUPER"); var adminRole = await _context.Roles.FirstAsync(r => r.RoleCode == "R_ADMIN"); var userRole = await _context.Roles.FirstAsync(r => r.RoleCode == "R_USER"); var allMenuIds = await _context.Menus.Select(m => m.Id).ToListAsync(); var adminMenuIds = await _context.Menus .Where(m => m.Roles != null && (m.Roles.Contains("R_ADMIN") || m.Roles.Contains("R_USER"))) .Select(m => m.Id).ToListAsync(); var userMenuIds = await _context.Menus .Where(m => m.Roles != null && m.Roles.Contains("R_USER")) .Select(m => m.Id).ToListAsync(); var roleMenus = new List(); foreach (var menuId in allMenuIds) roleMenus.Add(new RoleMenu { RoleId = superRole.Id, MenuId = menuId }); foreach (var menuId in adminMenuIds) roleMenus.Add(new RoleMenu { RoleId = adminRole.Id, MenuId = menuId }); foreach (var menuId in userMenuIds) roleMenus.Add(new RoleMenu { RoleId = userRole.Id, MenuId = menuId }); _context.RoleMenus.AddRange(roleMenus); await _context.SaveChangesAsync(); return Ok(ApiResponse.Success(null, "菜单已重置为默认配置")); } /// /// 添加 AMT 设备管理菜单(如果不存在) /// [Authorize] [HttpPost("api/menu/seed-amt")] public async Task>> SeedAmtMenus() { // 检查是否已存在 AMT 菜单 if (await _context.Menus.AnyAsync(m => m.Name == "AmtManage")) { return Ok(ApiResponse.Success(null, "AMT菜单已存在")); } // 获取当前最大 ID var maxId = await _context.Menus.MaxAsync(m => (int?)m.Id) ?? 0; var startId = Math.Max(maxId + 1, 5); var amtMenus = new List { new() { Name = "AmtManage", Path = "/amt", Component = "/index/index", Title = "设备管理", Icon = "ri:computer-line", Sort = 2, Roles = "R_SUPER,R_ADMIN", IsSystem = true }, }; _context.Menus.AddRange(amtMenus); await _context.SaveChangesAsync(); // 获取刚创建的目录菜单 ID var amtManageId = amtMenus[0].Id; // 添加子菜单 var childMenus = new List { new() { ParentId = amtManageId, Name = "AmtScan", Path = "scan", Component = "/amt/scan", Title = "网络扫描", KeepAlive = true, Sort = 1, Roles = "R_SUPER,R_ADMIN", IsSystem = true }, new() { ParentId = amtManageId, Name = "AmtDevices", Path = "devices", Component = "/amt/devices", Title = "设备列表", KeepAlive = true, Sort = 2, Roles = "R_SUPER,R_ADMIN", IsSystem = true }, new() { ParentId = amtManageId, Name = "AmtCredentials", Path = "credentials", Component = "/amt/credentials", Title = "AMT凭据", KeepAlive = true, Sort = 3, Roles = "R_SUPER,R_ADMIN", IsSystem = true }, new() { ParentId = amtManageId, Name = "WindowsCredentials", Path = "windows-credentials", Component = "/amt/windows-credentials", Title = "Windows凭据", KeepAlive = true, Sort = 4, Roles = "R_SUPER,R_ADMIN", IsSystem = true }, }; _context.Menus.AddRange(childMenus); await _context.SaveChangesAsync(); // 为超级管理员和管理员分配菜单权限 var superRole = await _context.Roles.FirstOrDefaultAsync(r => r.RoleCode == "R_SUPER"); var adminRole = await _context.Roles.FirstOrDefaultAsync(r => r.RoleCode == "R_ADMIN"); var allNewMenuIds = new List { amtManageId }; allNewMenuIds.AddRange(childMenus.Select(m => m.Id)); var roleMenus = new List(); foreach (var menuId in allNewMenuIds) { if (superRole != null) roleMenus.Add(new RoleMenu { RoleId = superRole.Id, MenuId = menuId }); if (adminRole != null) roleMenus.Add(new RoleMenu { RoleId = adminRole.Id, MenuId = menuId }); } _context.RoleMenus.AddRange(roleMenus); await _context.SaveChangesAsync(); return Ok(ApiResponse.Success(null, "AMT菜单已添加成功")); } } /// /// 创建菜单请求 /// public class CreateMenuRequest { public int? ParentId { get; set; } public string Name { get; set; } = string.Empty; public string Path { get; set; } = string.Empty; public string? Component { get; set; } public string Title { get; set; } = string.Empty; public string? Icon { get; set; } public int Sort { get; set; } public bool IsHide { get; set; } public bool KeepAlive { get; set; } public string? Link { get; set; } public bool IsIframe { get; set; } public List? Roles { get; set; } public bool AutoCreateComponent { get; set; } = true; // 是否自动创建组件文件 } /// /// 更新菜单请求 /// public class UpdateMenuRequest { public int? ParentId { get; set; } public string? Name { get; set; } public string? Path { get; set; } public string? Component { get; set; } public string? Title { get; set; } public string? Icon { get; set; } public int? Sort { get; set; } public bool? IsHide { get; set; } public bool? KeepAlive { get; set; } public string? Link { get; set; } public bool? IsIframe { get; set; } public List? Roles { get; set; } }