// ============================================================================
// 【文件说明】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);
}
}
}