// ============================================================================ // 【文件说明】HwWebMenuService.cs - 网站菜单服务类 // ============================================================================ // 这个服务类负责处理网站菜单的业务逻辑,包括: // - 菜单的 CRUD 操作 // - 树形菜单结构构建 // - 搜索索引重建 // // 【业务背景】 // 网站菜单模块用于管理网站的前端导航菜单,支持多级树形结构。 // 菜单变更时会自动触发搜索索引重建。 // // 【与 Java Spring Boot 的对比】 // Java Spring Boot: // @Service // public class HwWebMenuServiceImpl implements HwWebMenuService { ... } // // ASP.NET Core + Furion: // public class HwWebMenuService : ITransient { ... } // ============================================================================ namespace Admin.NET.Plugin.HwPortal; /// /// 网站菜单服务类。 /// /// 【服务职责】 /// 1. 管理网站菜单的增删改查 /// 2. 构建树形菜单结构 /// 3. 菜单变更时自动重建搜索索引 /// /// /// 【树形结构说明】 /// - Parent:父菜单ID,null或0表示顶级菜单 /// - Ancestors:祖先路径,格式为"0,1,2" /// - Order:排序号,用于控制菜单显示顺序 /// /// public class HwWebMenuService : ITransient { /// /// MyBatis 映射器名称(保留用于回滚)。 /// private const string Mapper = "HwWebMenuMapper"; /// /// MyBatis 执行器(保留用于回滚)。 /// private readonly HwPortalMyBatisExecutor _executor; /// /// SqlSugar 数据访问对象。 /// private readonly ISqlSugarClient _db; /// /// 搜索索引重建服务。 /// private readonly IHwSearchRebuildService _searchRebuildService; /// /// 日志记录器。 /// private readonly ILogger _logger; /// /// 构造函数(依赖注入)。 /// /// MyBatis 执行器 /// SqlSugar 数据访问对象 /// 搜索索引重建服务 /// 日志记录器 public HwWebMenuService(HwPortalMyBatisExecutor executor, ISqlSugarClient db, IHwSearchRebuildService searchRebuildService, ILogger logger) { _executor = executor; _db = db; _searchRebuildService = searchRebuildService; _logger = logger; } /// /// 根据菜单ID查询菜单信息。 /// /// 【软删除过滤】 /// 查询时自动过滤已删除的记录(IsDelete == "0")。 /// /// /// 菜单ID /// 菜单实体 public async Task SelectHwWebMenuByWebMenuId(long webMenuId) { // 回滚到 XML 方案时可直接恢复: // return await _executor.QuerySingleAsync(Mapper, "selectHwWebMenuByWebMenuId", new { webMenuId }); return await _db.Queryable() .Where(item => item.IsDelete == "0" && item.WebMenuId == webMenuId) .FirstAsync(); } /// /// 查询菜单列表。 /// /// 【动态查询条件】 /// 支持按父ID、祖先路径、状态、菜单名、租户ID、图片、类型、排序号、英文名等条件筛选。 /// /// /// 【排序】 /// 按 Parent、Order、WebMenuId 排序,确保菜单按层级和顺序显示。 /// /// /// 查询条件 /// 菜单列表 public async Task> SelectHwWebMenuList(HwWebMenu input) { HwWebMenu query = input ?? new HwWebMenu(); // 回滚到 XML 方案时可直接恢复: // return await _executor.QueryListAsync(Mapper, "selectHwWebMenuList", query); return await _db.Queryable() .Where(item => item.IsDelete == "0") .WhereIF(query.Parent.HasValue, item => item.Parent == query.Parent) .WhereIF(!string.IsNullOrWhiteSpace(query.Ancestors), item => item.Ancestors == query.Ancestors) .WhereIF(!string.IsNullOrWhiteSpace(query.Status), item => item.Status == query.Status) .WhereIF(!string.IsNullOrWhiteSpace(query.WebMenuName), item => item.WebMenuName.Contains(query.WebMenuName)) .WhereIF(query.TenantId.HasValue, item => item.TenantId == query.TenantId) .WhereIF(!string.IsNullOrWhiteSpace(query.WebMenuPic), item => item.WebMenuPic == query.WebMenuPic) .WhereIF(query.WebMenuType.HasValue, item => item.WebMenuType == query.WebMenuType) .WhereIF(query.Order.HasValue, item => item.Order == query.Order) .WhereIF(!string.IsNullOrWhiteSpace(query.WebMenuNameEnglish), item => item.WebMenuNameEnglish == query.WebMenuNameEnglish) .OrderBy(item => item.Parent) .OrderBy(item => item.Order) .OrderBy(item => item.WebMenuId) .ToListAsync(); } /// /// 查询菜单树。 /// /// 【树形结构构建】 /// 先查询菜单列表,然后调用 BuildWebMenuTree 构建树形结构。 /// /// /// 查询条件 /// 树形菜单列表 public async Task> SelectMenuTree(HwWebMenu input) { List menus = await SelectHwWebMenuList(input); return BuildWebMenuTree(menus); } /// /// 新增菜单。 /// /// 【搜索索引重建】 /// 菜单新增后,自动触发搜索索引重建。 /// /// /// 菜单数据 /// 影响行数 public async Task InsertHwWebMenu(HwWebMenu input) { // 【软删除标记初始化】 input.IsDelete = string.IsNullOrWhiteSpace(input.IsDelete) ? "0" : input.IsDelete; // 回滚到 XML 方案时可直接恢复: // int rows = await _executor.ExecuteAsync(Mapper, "insertHwWebMenu", input); int rows = await _db.Insertable(input).ExecuteCommandAsync(); // 【搜索索引重建】 if (rows > 0) { await RebuildSearchIndexQuietly("hw_web_menu"); } return rows; } /// /// 更新菜单信息。 /// /// 【字段级更新策略】 /// 只更新输入对象中不为 null 的字段。 /// /// /// 【搜索索引重建】 /// 菜单更新后,自动触发搜索索引重建。 /// /// /// 更新的数据 /// 影响行数 public async Task UpdateHwWebMenu(HwWebMenu input) { // 回滚到 XML 方案时可直接恢复: // int rows = await _executor.ExecuteAsync(Mapper, "updateHwWebMenu", input); HwWebMenu current = await SelectHwWebMenuByWebMenuId(input.WebMenuId ?? 0); if (current == null) { return 0; } // 【父菜单ID】 if (input.Parent.HasValue) { current.Parent = input.Parent; } // 【祖先路径】 if (input.Ancestors != null) { current.Ancestors = input.Ancestors; } // 【状态】 if (input.Status != null) { current.Status = input.Status; } // 【菜单名】 if (input.WebMenuName != null) { current.WebMenuName = input.WebMenuName; } // 【租户ID】 if (input.TenantId.HasValue) { current.TenantId = input.TenantId; } // 【菜单图片】 if (input.WebMenuPic != null) { current.WebMenuPic = input.WebMenuPic; } // 【菜单类型】 if (input.WebMenuType.HasValue) { current.WebMenuType = input.WebMenuType; } // 【排序号】 if (input.Order.HasValue) { current.Order = input.Order; } // 【英文名】 if (input.WebMenuNameEnglish != null) { current.WebMenuNameEnglish = input.WebMenuNameEnglish; } int rows = await _db.Updateable(current).ExecuteCommandAsync(); // 【搜索索引重建】 if (rows > 0) { await RebuildSearchIndexQuietly("hw_web_menu"); } return rows; } /// /// 批量删除菜单(软删除)。 /// /// 【软删除实现】 /// 将 IsDelete 字段更新为"1",而不是物理删除。 /// /// /// 【搜索索引重建】 /// 菜单删除后,自动触发搜索索引重建。 /// /// /// 菜单ID数组 /// 影响行数 public async Task DeleteHwWebMenuByWebMenuIds(long[] webMenuIds) { // 回滚到 XML 方案时可直接恢复: // int rows = await _executor.ExecuteAsync(Mapper, "deleteHwWebMenuByWebMenuIds", new { array = webMenuIds }); // 【查询待删除的菜单】 List menus = await _db.Queryable() .Where(item => item.WebMenuId.HasValue && webMenuIds.Contains(item.WebMenuId.Value)) .ToListAsync(); // 【软删除标记】 foreach (HwWebMenu menu in menus) { menu.IsDelete = "1"; } // 【批量更新】 int rows = menus.Count == 0 ? 0 : await _db.Updateable(menus) .UpdateColumns(item => new { item.IsDelete }) .ExecuteCommandAsync(); // 【搜索索引重建】 if (rows > 0) { await RebuildSearchIndexQuietly("hw_web_menu"); } return rows; } /// /// 构建菜单树形结构。 /// /// 【树形结构构建算法】 /// 1. 收集所有节点ID /// 2. 遍历找出顶级节点(Parent为null/0或不在列表中) /// 3. 对每个顶级节点递归构建子树 /// 4. 返回树形结构列表 /// /// /// 扁平菜单列表 /// 树形菜单列表 public List BuildWebMenuTree(List menus) { List returnList = new(); List tempList = menus.Select(item => item.WebMenuId).ToList(); foreach (HwWebMenu menu in menus) { // 【顶级节点判定】 if (!menu.Parent.HasValue || menu.Parent == 0 || !tempList.Contains(menu.Parent)) { RecursionFn(menus, menu); returnList.Add(menu); } } return returnList.Count == 0 ? menus : returnList; } /// /// 递归构建子树。 /// /// 所有节点列表 /// 当前节点 private static void RecursionFn(List list, HwWebMenu current) { List childList = GetChildList(list, current); current.Children = childList; foreach (HwWebMenu child in childList.Where(child => HasChild(list, child))) { RecursionFn(list, child); } } /// /// 获取子节点列表。 /// /// 所有节点列表 /// 当前节点 /// 子节点列表 private static List GetChildList(List list, HwWebMenu current) { return list.Where(item => item.Parent.HasValue && current.WebMenuId.HasValue && item.Parent.Value == current.WebMenuId.Value).ToList(); } /// /// 判断是否有子节点。 /// /// 所有节点列表 /// 当前节点 /// 是否有子节点 private static bool HasChild(List list, HwWebMenu current) { return GetChildList(list, current).Count > 0; } /// /// 静默重建搜索索引。 /// /// 数据来源 private async Task RebuildSearchIndexQuietly(string source) { try { await _searchRebuildService.RebuildAllAsync(); } catch (Exception ex) { _logger.LogError(ex, "rebuild portal search index failed after {Source} changed", source); } } }