|
|
// ============================================================================
|
|
|
// 【文件说明】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<HwProductCaseInfo>(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<T>(mapperName, statementId, parameters)
|
|
|
/// - mapperName:XML 文件中 mapper 的 namespace
|
|
|
/// - statementId:SQL 语句的 id
|
|
|
/// - parameters:参数对象(匿名对象或实体)
|
|
|
/// </para>
|
|
|
/// </summary>
|
|
|
/// <param name="caseInfoId">案例ID</param>
|
|
|
/// <returns>产品案例实体</returns>
|
|
|
public Task<HwProductCaseInfo> SelectHwProductCaseInfoByCaseInfoId(long caseInfoId)
|
|
|
{
|
|
|
// 【匿名对象传参】
|
|
|
// new { caseInfoId } 是 C# 的"匿名对象"语法。
|
|
|
// 编译器会自动创建一个包含 caseInfoId 属性的临时类。
|
|
|
//
|
|
|
// 对比 Java:
|
|
|
// Java 没有匿名对象语法,通常用:
|
|
|
// - Map<String, Object> params = new HashMap<>();
|
|
|
// - params.put("caseInfoId", caseInfoId);
|
|
|
// 或者:
|
|
|
// - @Param("caseInfoId") Long caseInfoId(MyBatis 注解)
|
|
|
//
|
|
|
// C# 的匿名对象更简洁,编译器自动推断类型。
|
|
|
return _executor.QuerySingleAsync<HwProductCaseInfo>(Mapper, "selectHwProductCaseInfoByCaseInfoId", new { caseInfoId });
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 查询产品案例列表。
|
|
|
/// <para>
|
|
|
/// 【动态查询条件】
|
|
|
/// 查询条件通过 input 对象传递,XML 中使用 <if> 标签动态构建 WHERE 子句。
|
|
|
///
|
|
|
/// 对比 Java MyBatis XML:
|
|
|
/// <select id="selectHwProductCaseInfoList" resultType="HwProductCaseInfo">
|
|
|
/// SELECT * FROM hw_product_case_info
|
|
|
/// <where>
|
|
|
/// <if test="caseName != null and caseName != ''">
|
|
|
/// AND case_name LIKE CONCAT('%', #{caseName}, '%')
|
|
|
/// </if>
|
|
|
/// </where>
|
|
|
/// </select>
|
|
|
/// </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 中使用 <foreach> 标签生成 IN 语句。
|
|
|
///
|
|
|
/// 对比 Java MyBatis XML:
|
|
|
/// <delete id="deleteHwProductCaseInfoByCaseInfoIds">
|
|
|
/// DELETE FROM hw_product_case_info
|
|
|
/// WHERE case_info_id IN
|
|
|
/// <foreach collection="array" item="id" open="(" separator="," close=")">
|
|
|
/// #{id}
|
|
|
/// </foreach>
|
|
|
/// </delete>
|
|
|
/// </para>
|
|
|
/// </summary>
|
|
|
/// <param name="caseInfoIds">案例ID数组</param>
|
|
|
/// <returns>影响行数</returns>
|
|
|
public Task<int> DeleteHwProductCaseInfoByCaseInfoIds(long[] caseInfoIds)
|
|
|
{
|
|
|
// 【匿名对象传参】
|
|
|
// new { array = caseInfoIds } 将数组包装为匿名对象的属性。
|
|
|
// XML 中通过 #{array} 引用整个数组,<foreach> 遍历它。
|
|
|
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 中定义,使用 <resultMap> 映射结果。
|
|
|
///
|
|
|
/// 对比 Java MyBatis:
|
|
|
/// Java MyBatis 使用 <resultMap> + <association> 或 <collection> 处理关联。
|
|
|
/// 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());
|
|
|
}
|
|
|
}
|