// ============================================================================ // 【文件说明】HwProductInfoService.cs - 产品信息业务服务类 // ============================================================================ // 这个服务类负责处理产品信息的业务逻辑,包括: // - 产品信息的 CRUD 操作 // - 产品与明细的关联查询 // - 产品明细树形结构的构建 // // 【核心知识点】 // 1. LINQ GroupBy 分组操作 // 2. 递归树构建算法 // 3. 扁平数据转树形结构 // ============================================================================ namespace Admin.NET.Plugin.HwPortal; /// /// 产品信息业务服务类。 /// /// 【服务职责】 /// 产品信息服务负责: /// 1. 基础 CRUD:增删改查 /// 2. 关联查询:产品 + 明细的 JOIN 查询 /// 3. 数据转换:扁平结果集转树形结构 /// /// public class HwProductInfoService : ITransient { /// /// MyBatis 映射器名称。 /// private const string Mapper = "HwProductInfoMapper"; /// /// MyBatis 执行器。 /// private readonly HwPortalMyBatisExecutor _executor; /// /// 构造函数(依赖注入)。 /// /// MyBatis 执行器 public HwProductInfoService(HwPortalMyBatisExecutor executor) { _executor = executor; } /// /// 根据产品ID查询产品信息。 /// /// 产品ID /// 产品实体 public Task SelectHwProductInfoByProductInfoId(long productInfoId) { // 【匿名对象传参】 // new { productInfoId } 创建一个包含 productInfoId 属性的匿名对象。 // 编译器自动推断类型,比 Java 的 HashMap 更简洁。 return _executor.QuerySingleAsync(Mapper, "selectHwProductInfoByProductInfoId", new { productInfoId }); } /// /// 查询产品列表。 /// /// 查询条件 /// 产品列表 public Task> SelectHwProductInfoList(HwProductInfo input) { // 【空合并运算符 ??】 // input ?? new HwProductInfo():如果 input 为 null,创建默认对象。 return _executor.QueryListAsync(Mapper, "selectHwProductInfoList", input ?? new HwProductInfo()); } /// /// 新增产品信息。 /// /// 产品数据 /// 影响行数 public async Task InsertHwProductInfo(HwProductInfo input) { // 【静态工具类调用】 // HwPortalContextHelper.Now() 获取当前时间。 // 这是"上下文助手"模式:封装了请求上下文相关的操作。 // // 对比 Java Spring: // Java 通常用 new Date() 或 LocalDateTime.now()。 // 这里封装一层的好处:可以统一控制时间来源(如测试时 mock)。 input.CreateTime = HwPortalContextHelper.Now(); long identity = await _executor.InsertReturnIdentityAsync(Mapper, "insertHwProductInfo", input); // 回填自增主键。 input.ProductInfoId = identity; return identity > 0 ? 1 : 0; } /// /// 更新产品信息。 /// /// 产品数据 /// 影响行数 public Task UpdateHwProductInfo(HwProductInfo input) { input.UpdateTime = HwPortalContextHelper.Now(); return _executor.ExecuteAsync(Mapper, "updateHwProductInfo", input); } /// /// 批量删除产品信息。 /// /// 产品ID数组 /// 影响行数 public Task DeleteHwProductInfoByProductInfoIds(long[] productInfoIds) { return _executor.ExecuteAsync(Mapper, "deleteHwProductInfoByProductInfoIds", new { array = productInfoIds }); } /// /// 查询产品信息及其明细列表(关联查询)。 /// /// 【核心算法:扁平数据转树形结构】 /// 这个方法展示了如何把"扁平的 JOIN 结果"转换成"树形的对象结构"。 /// /// 步骤: /// 1. 执行 SQL JOIN 查询,得到扁平结果集 /// 2. 按 ProductInfoId 分组 /// 3. 每组的第一行作为产品主数据 /// 4. 每组的所有行作为明细列表 /// 5. 如果配置模式是树形(13),递归构建明细树 /// /// /// 查询条件 /// 产品列表(包含明细) public async Task> SelectHwProductInfoJoinDetailList(HwProductInfo input) { // 【第一步:查询扁平结果集】 // SQL JOIN 查询返回的是扁平结构: // 产品1 | 明细1 // 产品1 | 明细2 // 产品1 | 明细3 // 产品2 | 明细4 // 产品2 | 明细5 // // 每行包含产品字段 + 明细字段。 List rows = await _executor.QueryListAsync(Mapper, "selectHwProductInfoJoinDetailList", input ?? new HwProductInfo()); // 【第二步:LINQ GroupBy 分组转换】 // rows.GroupBy(row => row.ProductInfoId) // 按 ProductInfoId 分组,相同 ProductInfoId 的行归为一组。 // // .Select(group => { ... }) // 对每个分组进行转换,生成一个 HwProductInfo 对象。 // // 对比 Java Stream: // Java 写法: // rows.stream() // .collect(Collectors.groupingBy(HwProductInfoJoinDetailRow::getProductInfoId)) // .entrySet().stream() // .map(entry -> { // HwProductInfoJoinDetailRow first = entry.getValue().get(0); // HwProductInfo product = new HwProductInfo(); // product.setProductInfoId(first.getProductInfoId()); // // ... 更多字段 // product.setDetailList(entry.getValue().stream() // .map(row -> { // HwProductInfoDetail detail = new HwProductInfoDetail(); // // ... 字段映射 // return detail; // }) // .collect(Collectors.toList())); // return product; // }) // .collect(Collectors.toList()); // // C# 的 LINQ 更简洁,特别是对象初始化器语法。 List products = rows .GroupBy(row => row.ProductInfoId) .Select(group => { // group.First() 获取分组中的第一行。 // 第一行的产品字段就是该产品的数据。 HwProductInfoJoinDetailRow first = group.First(); // 【对象初始化器】 // new HwProductInfo { ... } 直接在创建时赋值。 HwProductInfo product = new() { ProductInfoId = first.ProductInfoId, ConfigTypeId = first.ConfigTypeId, TabFlag = first.TabFlag, ConfigModal = first.ConfigModal, ProductInfoEtitle = first.ProductInfoEtitle, ProductInfoCtitle = first.ProductInfoCtitle, ProductInfoOrder = first.ProductInfoOrder, CreateTime = first.CreateTime, CreateBy = first.CreateBy, UpdateTime = first.UpdateTime, UpdateBy = first.UpdateBy, ParentId = first.ParentId, ConfigTypeName = first.ConfigTypeName, // 【嵌套 LINQ:构建明细列表】 // group.Where(...).Select(...).ToList() // 从当前分组中筛选出明细行,转换为明细对象列表。 // // .Where(item => item.SubProductInfoDetailId.HasValue) // 过滤掉没有明细的行(SubProductInfoDetailId 为 null)。 HwProductInfoDetailList = group .Where(item => item.SubProductInfoDetailId.HasValue) .Select(item => new HwProductInfoDetail { ProductInfoDetailId = item.SubProductInfoDetailId, ParentId = item.SubParentId, ProductInfoId = item.SubProductInfoId, ProductInfoDetailTitle = item.SubProductInfoDetailTitle, ProductInfoDetailDesc = item.SubProductInfoDetailDesc, ProductInfoDetailOrder = item.SubProductInfoDetailOrder, ProductInfoDetailPic = item.SubProductInfoDetailPic, Ancestors = item.SubAncestors, CreateTime = item.SubCreateTime, CreateBy = item.SubCreateBy, UpdateTime = item.SubUpdateTime, UpdateBy = item.SubUpdateBy, ConfigModel = item.ConfigModel }) .ToList() }; return product; }) .ToList(); // 【第三步:判断是否需要构建树形结构】 // string.Equals(a, b, StringComparison.Ordinal) // Ordinal 表示"按字节比较",最快但区分大小写。 // // 对比 Java: // Java: a.equals(b) 或 a.equalsIgnoreCase(b) // C#: string.Equals(a, b) 或 a == b(等价于 Equals) if (string.Equals(input?.ConfigModal, HwPortalConstants.ProductInfoConfigModalTree, StringComparison.Ordinal)) { // 配置模式 13 要求把明细转换成树形结构。 // .Where(u => u.HwProductInfoDetailList.Count > 0) 筛选有明细的产品。 foreach (HwProductInfo productInfo in products.Where(u => u.HwProductInfoDetailList.Count > 0)) { productInfo.HwProductInfoDetailList = BuildProductInfoDetailTree(productInfo.HwProductInfoDetailList); } } // 【额外判断:明细内部的配置模式】 // 除了主查询条件,还需要检查每个明细的 ConfigModel。 // 这是源代码的业务规则,保持原行为。 foreach (HwProductInfo productInfo in products) { foreach (HwProductInfoDetail detail in productInfo.HwProductInfoDetailList) { if (string.Equals(detail.ConfigModel, HwPortalConstants.ProductInfoConfigModalTree, StringComparison.Ordinal)) { productInfo.HwProductInfoDetailList = BuildProductInfoDetailTree(productInfo.HwProductInfoDetailList); break; // 找到一个就跳出,避免重复构建。 } } } return products; } /// /// 查询产品信息关联列表。 /// /// 查询条件 /// 产品列表 public Task> SelectHwProductInfoJoinList(HwProductInfo input) { return _executor.QueryListAsync(Mapper, "selectHwProductInfoJoinList", input ?? new HwProductInfo()); } /// /// 构建产品明细树形结构。 /// /// 【树形结构构建算法】 /// 这是一个经典的"扁平列表转树形结构"算法: /// /// 输入:扁平的节点列表,每个节点有 parentId 指向父节点 /// 输出:树形结构,顶级节点包含 children 子节点 /// /// 算法步骤: /// 1. 找出所有顶级节点(parentId 为空/0/不在列表中) /// 2. 对每个顶级节点,递归查找子节点 /// 3. 子节点再递归查找孙节点,直到叶子节点 /// /// 对比 Java 若依: /// 若依的 TreeBuildUtils.buildTree() 做同样的事情。 /// 这是若依框架的标准树构建算法。 /// /// /// 扁平的明细列表 /// 树形结构的明细列表 public List BuildProductInfoDetailTree(List productInfoDetails) { // 【收集所有节点 ID】 // 用于判断某个 parentId 是否在列表中。 // // .Select(item => item.ProductInfoDetailId) 提取所有 ID。 // .ToList() 转换为列表。 List returnList = new(); List tempList = productInfoDetails.Select(item => item.ProductInfoDetailId).ToList(); // 【遍历查找顶级节点】 foreach (HwProductInfoDetail detail in productInfoDetails) { // 【顶级节点判定】 // 满足以下任一条件即为顶级节点: // 1. !detail.ParentId.HasValue:parentId 为 null // 2. detail.ParentId == 0:parentId 为 0 // 3. !tempList.Contains(detail.ParentId):parentId 不在列表中(父节点不存在) if (!detail.ParentId.HasValue || detail.ParentId == 0 || !tempList.Contains(detail.ParentId)) { // 找到顶级节点后,递归构建其子树。 RecursionFn(productInfoDetails, detail); returnList.Add(detail); } } // 如果没找到顶级节点,返回原列表(兜底处理)。 return returnList.Count == 0 ? productInfoDetails : returnList; } /// /// 递归构建子树。 /// /// 【递归算法说明】 /// 递归是一种"自己调用自己"的编程技巧。 /// /// 这里的递归逻辑: /// 1. 找出当前节点的所有直接子节点 /// 2. 把子节点挂到当前节点的 Children 属性 /// 3. 对每个子节点,递归执行步骤 1-2 /// /// 终止条件:节点没有子节点(HasChild 返回 false) /// /// /// 所有节点列表 /// 当前节点 private static void RecursionFn(List list, HwProductInfoDetail current) { // 找出当前节点的所有直接子节点。 List childList = GetChildList(list, current); // 把子节点挂到当前节点。 // Children 和 HwProductInfoDetailList 都设置,兼容不同的访问方式。 current.Children = childList; current.HwProductInfoDetailList = childList; // 【递归调用】 // 对每个有子节点的子节点,继续递归构建子树。 // .Where(child => HasChild(list, child)) 筛选有子节点的节点。 foreach (HwProductInfoDetail child in childList.Where(child => HasChild(list, child))) { RecursionFn(list, child); } } /// /// 获取当前节点的直接子节点列表。 /// /// 所有节点列表 /// 当前节点 /// 子节点列表 private static List GetChildList(List list, HwProductInfoDetail current) { // 【LINQ Where 过滤】 // item.ParentId.Value == current.ProductInfoDetailId.Value // 条件:item 的 parentId 等于 current 的 id。 // // .HasValue 检查可空类型是否有值(不为 null)。 // .Value 获取可空类型的实际值。 // // 对比 Java: // Java: list.stream().filter(item -> item.getParentId() != null && current.getProductInfoDetailId() != null && item.getParentId().equals(current.getProductInfoDetailId())).collect(Collectors.toList()); // // C# 的可空类型处理更优雅。 return list.Where(item => item.ParentId.HasValue && current.ProductInfoDetailId.HasValue && item.ParentId.Value == current.ProductInfoDetailId.Value).ToList(); } /// /// 判断当前节点是否有子节点。 /// /// 所有节点列表 /// 当前节点 /// 是否有子节点 private static bool HasChild(List list, HwProductInfoDetail current) { return GetChildList(list, current).Count > 0; } }