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.

356 lines
14 KiB
C#

// ============================================================================
// 【文件说明】HwProductCaseInfoService.cs - 产品案例信息服务类
// ============================================================================
// 这个服务类负责处理产品案例信息的业务逻辑,包括:
// - 产品案例的 CRUD 操作
// - 典型案例查询
// - 关联查询(案例 + 其他信息)
//
// 【业务背景】
// 产品案例模块用于管理企业的产品案例展示,支持标记典型案例。
// 典型案例会在网站首页等重点位置展示。
//
// 【与 Java Spring Boot 的对比】
// Java Spring Boot:
// @Service
// public class HwProductCaseInfoServiceImpl implements HwProductCaseInfoService { ... }
//
// ASP.NET Core + Furion:
// public class HwProductCaseInfoService : ITransient { ... }
// ============================================================================
namespace Admin.NET.Plugin.HwPortal;
/// <summary>
/// 产品案例信息服务类。
/// <para>
/// 【服务职责】
/// 1. 管理产品案例的增删改查
/// 2. 提供典型案例查询功能
/// 3. 支持关联查询获取完整案例信息
/// </para>
/// <para>
/// 【典型案例标记】
/// - HomeTypicalFlag是否在首页展示"0"否,"1"是)
/// - TypicalFlag是否典型案例"0"否,"1"是)
///
/// 典型案例优先级:
/// 1. 优先返回标记为 TypicalFlagYes 的案例
/// 2. 如果没有典型案例,返回第一个案例
/// 3. 如果列表为空,返回空对象
/// </para>
/// <para>
/// 【当前实现说明】
/// 这个服务目前仍使用 XML Mapper 方式访问数据。
/// 后续可以考虑迁移到 SqlSugar以获得更好的类型安全和性能。
/// </para>
/// </summary>
public class HwProductCaseInfoService : ITransient
{
/// <summary>
/// MyBatis 映射器名称。
/// <para>
/// 【C# 语法知识点 - const 常量】
/// const 是"编译期常量",值在编译时就确定了。
///
/// 对比 Java
/// Java: private static final String MAPPER = "HwProductCaseInfoMapper";
/// C#: private const string Mapper = "HwProductCaseInfoMapper";
///
/// C# 的命名约定:
/// - const 通常用 PascalCase首字母大写
/// - Java 的 static final 通常用 UPPER_SNAKE_CASE
/// </para>
/// </summary>
private const string Mapper = "HwProductCaseInfoMapper";
/// <summary>
/// MyBatis 执行器。
/// <para>
/// 【依赖注入】
/// 通过构造函数注入,和控制器注入服务的方式一样。
///
/// HwPortalMyBatisExecutor 是自定义的执行器,封装了 MyBatis 风格的 SQL 执行逻辑。
/// 它负责解析 XML 中的 SQL 语句,执行参数绑定,并将结果映射到实体对象。
///
/// 对比 Java MyBatis
/// Java MyBatis 使用 Mapper 接口 + XML 配置:
/// @Mapper
/// public interface HwProductCaseInfoMapper {
/// @Select("SELECT * FROM hw_product_case_info WHERE case_info_id = #{caseInfoId}")
/// HwProductCaseInfo selectById(Long caseInfoId);
/// }
///
/// C# 这里使用执行器模式:
/// _executor.QuerySingleAsync&lt;HwProductCaseInfo&gt;(Mapper, "selectHwProductCaseInfoByCaseInfoId", new { caseInfoId });
///
/// 两种方式的对比:
/// - Java MyBatis编译期检查类型安全
/// - C# 执行器模式:更灵活,可以动态选择 SQL但缺少编译期检查
/// </para>
/// </summary>
private readonly HwPortalMyBatisExecutor _executor;
/// <summary>
/// 构造函数(依赖注入)。
/// <para>
/// 【C# 语法知识点 - 构造函数】
/// public HwProductCaseInfoService(HwPortalMyBatisExecutor executor)
///
/// 对比 Java
/// Java:
/// @Autowired
/// public HwProductCaseInfoService(HwPortalMyBatisExecutor executor) {
/// this.executor = executor;
/// }
///
/// C#:
/// public HwProductCaseInfoService(HwPortalMyBatisExecutor executor)
/// {
/// _executor = executor;
/// }
///
/// C# 的改进:
/// 1. 不需要 @Autowired 注解,框架自动识别构造函数
/// 2. 使用 _executor 命名约定表示私有字段
/// 3. 可以使用主构造函数C# 12+)进一步简化
/// </para>
/// </summary>
/// <param name="executor">MyBatis 执行器</param>
public HwProductCaseInfoService(HwPortalMyBatisExecutor executor)
{
_executor = executor;
}
/// <summary>
/// 根据案例ID查询产品案例信息。
/// <para>
/// 【方法命名约定】
/// Select + 实体名 + By + 主键名:根据主键查询单条记录
///
/// 对比 Java 若依:
/// 若依通常使用selectXxxById、getXxxById
/// 这里使用SelectXxxByCaseInfoId更符合 C# 的 PascalCase 命名规范
/// </para>
/// <para>
/// 【XML Mapper 调用】
/// _executor.QuerySingleAsync&lt;T&gt;(mapperName, statementId, parameters)
/// - mapperNameXML 文件中 mapper 的 namespace
/// - statementIdSQL 语句的 id
/// - parameters参数对象匿名对象或实体
/// </para>
/// </summary>
/// <param name="caseInfoId">案例ID</param>
/// <returns>产品案例实体</returns>
public Task<HwProductCaseInfo> SelectHwProductCaseInfoByCaseInfoId(long caseInfoId)
{
// 【匿名对象传参】
// new { caseInfoId } 是 C# 的"匿名对象"语法。
// 编译器会自动创建一个包含 caseInfoId 属性的临时类。
//
// 对比 Java
// Java 没有匿名对象语法,通常用:
// - Map&lt;String, Object&gt; params = new HashMap&lt;&gt;();
// - params.put("caseInfoId", caseInfoId);
// 或者:
// - @Param("caseInfoId") Long caseInfoIdMyBatis 注解)
//
// C# 的匿名对象更简洁,编译器自动推断类型。
return _executor.QuerySingleAsync<HwProductCaseInfo>(Mapper, "selectHwProductCaseInfoByCaseInfoId", new { caseInfoId });
}
/// <summary>
/// 查询产品案例列表。
/// <para>
/// 【动态查询条件】
/// 查询条件通过 input 对象传递XML 中使用 &lt;if&gt; 标签动态构建 WHERE 子句。
///
/// 对比 Java MyBatis XML
/// &lt;select id="selectHwProductCaseInfoList" resultType="HwProductCaseInfo"&gt;
/// SELECT * FROM hw_product_case_info
/// &lt;where&gt;
/// &lt;if test="caseName != null and caseName != ''"&gt;
/// AND case_name LIKE CONCAT('%', #{caseName}, '%')
/// &lt;/if&gt;
/// &lt;/where&gt;
/// &lt;/select&gt;
/// </para>
/// </summary>
/// <param name="input">查询条件</param>
/// <returns>产品案例列表</returns>
public Task<List<HwProductCaseInfo>> SelectHwProductCaseInfoList(HwProductCaseInfo input)
{
// 【空合并运算符 ??】
// input ?? new HwProductCaseInfo() 含义:
// 如果 input 不为 null就用 input
// 否则 new 一个默认的 HwProductCaseInfo 对象。
//
// 对比 Java
// Java 需要手写:
// if (input == null) input = new HwProductCaseInfo();
// 或者用 Optional
// input = Optional.ofNullable(input).orElse(new HwProductCaseInfo());
//
// C# 的空合并运算符更简洁!
return _executor.QueryListAsync<HwProductCaseInfo>(Mapper, "selectHwProductCaseInfoList", input ?? new HwProductCaseInfo());
}
/// <summary>
/// 新增产品案例信息。
/// <para>
/// 【审计字段自动填充】
/// CreateTime 由代码自动设置,不需要前端传递。
/// 这是常见的"审计字段"处理模式。
/// </para>
/// <para>
/// 【自增主键回填】
/// InsertReturnIdentityAsync 返回自增主键值,
/// 然后回填到 input 对象,方便调用方获取新记录的 ID。
/// </para>
/// </summary>
/// <param name="input">产品案例数据</param>
/// <returns>影响行数1成功0失败</returns>
public async Task<int> InsertHwProductCaseInfo(HwProductCaseInfo input)
{
// 【静态工具类调用】
// HwPortalContextHelper.Now() 获取当前时间。
// 这是"上下文助手"模式:封装了请求上下文相关的操作。
//
// 对比 Java Spring
// Java 通常用 new Date() 或 LocalDateTime.now()。
// 这里封装一层的好处:可以统一控制时间来源(如测试时 mock
input.CreateTime = HwPortalContextHelper.Now();
// 【执行插入并获取自增主键】
// InsertReturnIdentityAsync 返回插入记录的自增主键值。
// 对比 Java MyBatis
// Java 需要配置 useGeneratedKeys="true" keyProperty="caseInfoId"
// 然后通过 input.getCaseInfoId() 获取主键。
long identity = await _executor.InsertReturnIdentityAsync(Mapper, "insertHwProductCaseInfo", input);
// 【回填自增主键】
// 把自增主键回填到实体对象,方便调用方使用。
input.CaseInfoId = identity;
// 返回 1 表示成功0 表示失败。
return identity > 0 ? 1 : 0;
}
/// <summary>
/// 更新产品案例信息。
/// <para>
/// 【审计字段更新】
/// UpdateTime 由代码自动设置,标记记录更新时间。
/// </para>
/// </summary>
/// <param name="input">产品案例数据</param>
/// <returns>影响行数</returns>
public Task<int> UpdateHwProductCaseInfo(HwProductCaseInfo input)
{
// 【审计字段】
input.UpdateTime = HwPortalContextHelper.Now();
// 【执行更新】
// ExecuteAsync 执行 UPDATE 语句,返回影响行数。
return _executor.ExecuteAsync(Mapper, "updateHwProductCaseInfo", input);
}
/// <summary>
/// 批量删除产品案例信息。
/// <para>
/// 【批量删除】
/// 根据 ID 数组批量删除记录。
/// XML 中使用 &lt;foreach&gt; 标签生成 IN 语句。
///
/// 对比 Java MyBatis XML
/// &lt;delete id="deleteHwProductCaseInfoByCaseInfoIds"&gt;
/// DELETE FROM hw_product_case_info
/// WHERE case_info_id IN
/// &lt;foreach collection="array" item="id" open="(" separator="," close=")"&gt;
/// #{id}
/// &lt;/foreach&gt;
/// &lt;/delete&gt;
/// </para>
/// </summary>
/// <param name="caseInfoIds">案例ID数组</param>
/// <returns>影响行数</returns>
public Task<int> DeleteHwProductCaseInfoByCaseInfoIds(long[] caseInfoIds)
{
// 【匿名对象传参】
// new { array = caseInfoIds } 将数组包装为匿名对象的属性。
// XML 中通过 #{array} 引用整个数组,&lt;foreach&gt; 遍历它。
return _executor.ExecuteAsync(Mapper, "deleteHwProductCaseInfoByCaseInfoIds", new { array = caseInfoIds });
}
/// <summary>
/// 获取首页典型案例信息。
/// <para>
/// 【业务逻辑】
/// 1. 设置查询条件HomeTypicalFlag = "1"(首页展示)
/// 2. 查询符合条件的案例列表
/// 3. 优先返回标记为 TypicalFlagYes 的案例
/// 4. 如果没有典型案例,返回第一个案例
/// 5. 如果列表为空,返回空对象(不是 null
///
/// 【设计模式:空对象模式】
/// 返回 new HwProductCaseInfo() 而不是 null
/// 避免调用方出现 NullReferenceException。
/// 这是一种防御性编程实践。
/// </para>
/// </summary>
/// <param name="input">查询条件</param>
/// <returns>典型案例实体</returns>
public async Task<HwProductCaseInfo> GetTypicalHomeCaseInfo(HwProductCaseInfo input)
{
// 【空合并赋值运算符 ??=】
// input ??= new HwProductCaseInfo() 等价于:
// if (input == null) input = new HwProductCaseInfo();
//
// 这是 C# 8.0 引入的语法糖,让代码更简洁。
input ??= new HwProductCaseInfo();
// 【设置查询条件】
// 只查询首页展示的案例HomeTypicalFlag = "1"
input.HomeTypicalFlag = HwPortalConstants.HomeTypicalFlagYes;
// 【执行查询】
List<HwProductCaseInfo> list = await SelectHwProductCaseInfoList(input);
// 【优先返回典型案例】
// .FirstOrDefault(predicate) 返回第一个满足条件的元素,如果没有则返回 null。
// string.Equals(a, b, StringComparison.Ordinal) 区分大小写的精确比较。
//
// 对比 Java
// Java: list.stream().filter(u -> HwPortalConstants.TypicalFlagYes.equals(u.getTypicalFlag())).findFirst().orElse(null);
// C#: list.FirstOrDefault(u => string.Equals(u.TypicalFlag, HwPortalConstants.TypicalFlagYes, StringComparison.Ordinal));
HwProductCaseInfo typical = list.FirstOrDefault(u => string.Equals(u.TypicalFlag, HwPortalConstants.TypicalFlagYes, StringComparison.Ordinal));
// 【兜底策略】
// 如果没有典型案例,返回第一个案例。
// 如果列表为空,返回空对象。
// ?? 是空合并运算符:如果左边为 null返回右边。
return typical ?? list.FirstOrDefault() ?? new HwProductCaseInfo();
}
/// <summary>
/// 查询产品案例关联列表JOIN查询
/// <para>
/// 【关联查询】
/// 这个方法执行 JOIN 查询,返回包含关联信息的完整案例数据。
/// 关联查询通常在 XML 中定义,使用 &lt;resultMap&gt; 映射结果。
///
/// 对比 Java MyBatis
/// Java MyBatis 使用 &lt;resultMap&gt; + &lt;association&gt; 或 &lt;collection&gt; 处理关联。
/// C# 这里的实现类似,也是通过 XML 配置映射。
/// </para>
/// </summary>
/// <param name="input">查询条件</param>
/// <returns>产品案例列表(含关联信息)</returns>
public Task<List<HwProductCaseInfo>> SelectHwProductCaseInfoJoinList(HwProductCaseInfo input)
{
// 【空合并运算符 ??】
// 确保 input 不为 null避免 XML 中出现问题。
return _executor.QueryListAsync<HwProductCaseInfo>(Mapper, "selectHwProductCaseInfoJoinList", input ?? new HwProductCaseInfo());
}
}