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