You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

396 lines
17 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// ============================================================================
// 【文件说明】HwProductInfoService.cs - 产品信息业务服务类
// ============================================================================
// 这个服务类负责处理产品信息的业务逻辑,包括:
// - 产品信息的 CRUD 操作
// - 产品与明细的关联查询
// - 产品明细树形结构的构建
//
// 【核心知识点】
// 1. LINQ GroupBy 分组操作
// 2. 递归树构建算法
// 3. 扁平数据转树形结构
// ============================================================================
namespace Admin.NET.Plugin.HwPortal;
/// <summary>
/// 产品信息业务服务类。
/// <para>
/// 【服务职责】
/// 产品信息服务负责:
/// 1. 基础 CRUD增删改查
/// 2. 关联查询:产品 + 明细的 JOIN 查询
/// 3. 数据转换:扁平结果集转树形结构
/// </para>
/// </summary>
public class HwProductInfoService : ITransient
{
/// <summary>
/// MyBatis 映射器名称。
/// </summary>
private const string Mapper = "HwProductInfoMapper";
/// <summary>
/// MyBatis 执行器。
/// </summary>
private readonly HwPortalMyBatisExecutor _executor;
/// <summary>
/// 构造函数(依赖注入)。
/// </summary>
/// <param name="executor">MyBatis 执行器</param>
public HwProductInfoService(HwPortalMyBatisExecutor executor)
{
_executor = executor;
}
/// <summary>
/// 根据产品ID查询产品信息。
/// </summary>
/// <param name="productInfoId">产品ID</param>
/// <returns>产品实体</returns>
public Task<HwProductInfo> SelectHwProductInfoByProductInfoId(long productInfoId)
{
// 【匿名对象传参】
// new { productInfoId } 创建一个包含 productInfoId 属性的匿名对象。
// 编译器自动推断类型,比 Java 的 HashMap 更简洁。
return _executor.QuerySingleAsync<HwProductInfo>(Mapper, "selectHwProductInfoByProductInfoId", new { productInfoId });
}
/// <summary>
/// 查询产品列表。
/// </summary>
/// <param name="input">查询条件</param>
/// <returns>产品列表</returns>
public Task<List<HwProductInfo>> SelectHwProductInfoList(HwProductInfo input)
{
// 【空合并运算符 ??】
// input ?? new HwProductInfo():如果 input 为 null创建默认对象。
return _executor.QueryListAsync<HwProductInfo>(Mapper, "selectHwProductInfoList", input ?? new HwProductInfo());
}
/// <summary>
/// 新增产品信息。
/// </summary>
/// <param name="input">产品数据</param>
/// <returns>影响行数</returns>
public async Task<int> 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;
}
/// <summary>
/// 更新产品信息。
/// </summary>
/// <param name="input">产品数据</param>
/// <returns>影响行数</returns>
public Task<int> UpdateHwProductInfo(HwProductInfo input)
{
input.UpdateTime = HwPortalContextHelper.Now();
return _executor.ExecuteAsync(Mapper, "updateHwProductInfo", input);
}
/// <summary>
/// 批量删除产品信息。
/// </summary>
/// <param name="productInfoIds">产品ID数组</param>
/// <returns>影响行数</returns>
public Task<int> DeleteHwProductInfoByProductInfoIds(long[] productInfoIds)
{
return _executor.ExecuteAsync(Mapper, "deleteHwProductInfoByProductInfoIds", new { array = productInfoIds });
}
/// <summary>
/// 查询产品信息及其明细列表(关联查询)。
/// <para>
/// 【核心算法:扁平数据转树形结构】
/// 这个方法展示了如何把"扁平的 JOIN 结果"转换成"树形的对象结构"。
///
/// 步骤:
/// 1. 执行 SQL JOIN 查询,得到扁平结果集
/// 2. 按 ProductInfoId 分组
/// 3. 每组的第一行作为产品主数据
/// 4. 每组的所有行作为明细列表
/// 5. 如果配置模式是树形13递归构建明细树
/// </para>
/// </summary>
/// <param name="input">查询条件</param>
/// <returns>产品列表(包含明细)</returns>
public async Task<List<HwProductInfo>> SelectHwProductInfoJoinDetailList(HwProductInfo input)
{
// 【第一步:查询扁平结果集】
// SQL JOIN 查询返回的是扁平结构:
// 产品1 | 明细1
// 产品1 | 明细2
// 产品1 | 明细3
// 产品2 | 明细4
// 产品2 | 明细5
//
// 每行包含产品字段 + 明细字段。
List<HwProductInfoJoinDetailRow> rows = await _executor.QueryListAsync<HwProductInfoJoinDetailRow>(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<HwProductInfo> 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;
}
/// <summary>
/// 查询产品信息关联列表。
/// </summary>
/// <param name="input">查询条件</param>
/// <returns>产品列表</returns>
public Task<List<HwProductInfo>> SelectHwProductInfoJoinList(HwProductInfo input)
{
return _executor.QueryListAsync<HwProductInfo>(Mapper, "selectHwProductInfoJoinList", input ?? new HwProductInfo());
}
/// <summary>
/// 构建产品明细树形结构。
/// <para>
/// 【树形结构构建算法】
/// 这是一个经典的"扁平列表转树形结构"算法:
///
/// 输入:扁平的节点列表,每个节点有 parentId 指向父节点
/// 输出:树形结构,顶级节点包含 children 子节点
///
/// 算法步骤:
/// 1. 找出所有顶级节点parentId 为空/0/不在列表中)
/// 2. 对每个顶级节点,递归查找子节点
/// 3. 子节点再递归查找孙节点,直到叶子节点
///
/// 对比 Java 若依:
/// 若依的 TreeBuildUtils.buildTree() 做同样的事情。
/// 这是若依框架的标准树构建算法。
/// </para>
/// </summary>
/// <param name="productInfoDetails">扁平的明细列表</param>
/// <returns>树形结构的明细列表</returns>
public List<HwProductInfoDetail> BuildProductInfoDetailTree(List<HwProductInfoDetail> productInfoDetails)
{
// 【收集所有节点 ID】
// 用于判断某个 parentId 是否在列表中。
//
// .Select(item => item.ProductInfoDetailId) 提取所有 ID。
// .ToList() 转换为列表。
List<HwProductInfoDetail> returnList = new();
List<long?> tempList = productInfoDetails.Select(item => item.ProductInfoDetailId).ToList();
// 【遍历查找顶级节点】
foreach (HwProductInfoDetail detail in productInfoDetails)
{
// 【顶级节点判定】
// 满足以下任一条件即为顶级节点:
// 1. !detail.ParentId.HasValueparentId 为 null
// 2. detail.ParentId == 0parentId 为 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;
}
/// <summary>
/// 递归构建子树。
/// <para>
/// 【递归算法说明】
/// 递归是一种"自己调用自己"的编程技巧。
///
/// 这里的递归逻辑:
/// 1. 找出当前节点的所有直接子节点
/// 2. 把子节点挂到当前节点的 Children 属性
/// 3. 对每个子节点,递归执行步骤 1-2
///
/// 终止条件节点没有子节点HasChild 返回 false
/// </para>
/// </summary>
/// <param name="list">所有节点列表</param>
/// <param name="current">当前节点</param>
private static void RecursionFn(List<HwProductInfoDetail> list, HwProductInfoDetail current)
{
// 找出当前节点的所有直接子节点。
List<HwProductInfoDetail> 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);
}
}
/// <summary>
/// 获取当前节点的直接子节点列表。
/// </summary>
/// <param name="list">所有节点列表</param>
/// <param name="current">当前节点</param>
/// <returns>子节点列表</returns>
private static List<HwProductInfoDetail> GetChildList(List<HwProductInfoDetail> 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();
}
/// <summary>
/// 判断当前节点是否有子节点。
/// </summary>
/// <param name="list">所有节点列表</param>
/// <param name="current">当前节点</param>
/// <returns>是否有子节点</returns>
private static bool HasChild(List<HwProductInfoDetail> list, HwProductInfoDetail current)
{
return GetChildList(list, current).Count > 0;
}
}