// ============================================================================ // 【文件说明】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; /// /// 产品案例信息服务类。 /// /// 【服务职责】 /// 1. 管理产品案例的增删改查 /// 2. 提供典型案例查询功能 /// 3. 支持关联查询获取完整案例信息 /// /// /// 【典型案例标记】 /// - HomeTypicalFlag:是否在首页展示("0"否,"1"是) /// - TypicalFlag:是否典型案例("0"否,"1"是) /// /// 典型案例优先级: /// 1. 优先返回标记为 TypicalFlagYes 的案例 /// 2. 如果没有典型案例,返回第一个案例 /// 3. 如果列表为空,返回空对象 /// /// /// 【当前实现说明】 /// 这个服务目前仍使用 XML Mapper 方式访问数据。 /// 后续可以考虑迁移到 SqlSugar,以获得更好的类型安全和性能。 /// /// public class HwProductCaseInfoService : ITransient { /// /// MyBatis 映射器名称。 /// /// 【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 /// /// private const string Mapper = "HwProductCaseInfoMapper"; /// /// MyBatis 执行器。 /// /// 【依赖注入】 /// 通过构造函数注入,和控制器注入服务的方式一样。 /// /// 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,但缺少编译期检查 /// /// private readonly HwPortalMyBatisExecutor _executor; /// /// 构造函数(依赖注入)。 /// /// 【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+)进一步简化 /// /// /// MyBatis 执行器 public HwProductCaseInfoService(HwPortalMyBatisExecutor executor) { _executor = executor; } /// /// 根据案例ID查询产品案例信息。 /// /// 【方法命名约定】 /// Select + 实体名 + By + 主键名:根据主键查询单条记录 /// /// 对比 Java 若依: /// 若依通常使用:selectXxxById、getXxxById /// 这里使用:SelectXxxByCaseInfoId,更符合 C# 的 PascalCase 命名规范 /// /// /// 【XML Mapper 调用】 /// _executor.QuerySingleAsync<T>(mapperName, statementId, parameters) /// - mapperName:XML 文件中 mapper 的 namespace /// - statementId:SQL 语句的 id /// - parameters:参数对象(匿名对象或实体) /// /// /// 案例ID /// 产品案例实体 public Task 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(Mapper, "selectHwProductCaseInfoByCaseInfoId", new { caseInfoId }); } /// /// 查询产品案例列表。 /// /// 【动态查询条件】 /// 查询条件通过 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> /// /// /// 查询条件 /// 产品案例列表 public Task> 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(Mapper, "selectHwProductCaseInfoList", input ?? new HwProductCaseInfo()); } /// /// 新增产品案例信息。 /// /// 【审计字段自动填充】 /// CreateTime 由代码自动设置,不需要前端传递。 /// 这是常见的"审计字段"处理模式。 /// /// /// 【自增主键回填】 /// InsertReturnIdentityAsync 返回自增主键值, /// 然后回填到 input 对象,方便调用方获取新记录的 ID。 /// /// /// 产品案例数据 /// 影响行数(1成功,0失败) public async Task 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; } /// /// 更新产品案例信息。 /// /// 【审计字段更新】 /// UpdateTime 由代码自动设置,标记记录更新时间。 /// /// /// 产品案例数据 /// 影响行数 public Task UpdateHwProductCaseInfo(HwProductCaseInfo input) { // 【审计字段】 input.UpdateTime = HwPortalContextHelper.Now(); // 【执行更新】 // ExecuteAsync 执行 UPDATE 语句,返回影响行数。 return _executor.ExecuteAsync(Mapper, "updateHwProductCaseInfo", input); } /// /// 批量删除产品案例信息。 /// /// 【批量删除】 /// 根据 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> /// /// /// 案例ID数组 /// 影响行数 public Task DeleteHwProductCaseInfoByCaseInfoIds(long[] caseInfoIds) { // 【匿名对象传参】 // new { array = caseInfoIds } 将数组包装为匿名对象的属性。 // XML 中通过 #{array} 引用整个数组,<foreach> 遍历它。 return _executor.ExecuteAsync(Mapper, "deleteHwProductCaseInfoByCaseInfoIds", new { array = caseInfoIds }); } /// /// 获取首页典型案例信息。 /// /// 【业务逻辑】 /// 1. 设置查询条件:HomeTypicalFlag = "1"(首页展示) /// 2. 查询符合条件的案例列表 /// 3. 优先返回标记为 TypicalFlagYes 的案例 /// 4. 如果没有典型案例,返回第一个案例 /// 5. 如果列表为空,返回空对象(不是 null) /// /// 【设计模式:空对象模式】 /// 返回 new HwProductCaseInfo() 而不是 null, /// 避免调用方出现 NullReferenceException。 /// 这是一种防御性编程实践。 /// /// /// 查询条件 /// 典型案例实体 public async Task 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 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(); } /// /// 查询产品案例关联列表(JOIN查询)。 /// /// 【关联查询】 /// 这个方法执行 JOIN 查询,返回包含关联信息的完整案例数据。 /// 关联查询通常在 XML 中定义,使用 <resultMap> 映射结果。 /// /// 对比 Java MyBatis: /// Java MyBatis 使用 <resultMap> + <association> 或 <collection> 处理关联。 /// C# 这里的实现类似,也是通过 XML 配置映射。 /// /// /// 查询条件 /// 产品案例列表(含关联信息) public Task> SelectHwProductCaseInfoJoinList(HwProductCaseInfo input) { // 【空合并运算符 ??】 // 确保 input 不为 null,避免 XML 中出现问题。 return _executor.QueryListAsync(Mapper, "selectHwProductCaseInfoJoinList", input ?? new HwProductCaseInfo()); } }