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#

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.

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