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