// ============================================================================ // 【文件说明】HwAboutUsInfoDetailService.cs - 关于我们明细信息服务类 // ============================================================================ // 这个服务类负责处理"关于我们"模块的明细数据业务逻辑,包括: // - 明细信息的 CRUD 操作 // - 与主信息的关联查询 // // 【分层架构说明】 // Controller(控制器层)-> Service(服务层)-> Repository(数据访问层) // // 控制器只负责:接收请求、调用服务、返回响应 // 服务层负责:业务逻辑、数据组装、事务控制 // 数据访问层负责:数据库 CRUD 操作 // // 【与 Java Spring Boot 的对比】 // Java Spring Boot: // @Service // public class HwAboutUsInfoDetailServiceImpl implements HwAboutUsInfoDetailService { ... } // // ASP.NET Core + Furion: // public class HwAboutUsInfoDetailService : ITransient { ... } // // ITransient 是 Furion 的"生命周期接口",表示"瞬态服务": // - 每次请求都创建新实例 // - 类似 Java Spring 的 @Scope("prototype") // // Furion 支持三种生命周期: // - ITransient:瞬态,每次请求新实例 // - IScoped:作用域,同一请求内共享实例 // - ISingleton:单例,全局共享一个实例 // ============================================================================ namespace Admin.NET.Plugin.HwPortal; /// /// 关于我们明细信息服务类。 /// /// 【C# 语法知识点 - 接口实现】 /// public class HwAboutUsInfoDetailService : ITransient /// /// ITransient 是 Furion 框架的"标记接口"(Marker Interface)。 /// 它没有任何方法,只是用来标记服务的生命周期。 /// /// 对比 Java Spring: /// Java Spring 用 @Service + @Scope 注解来定义服务: /// @Service /// @Scope("prototype") // 等价于 ITransient /// public class HwAboutUsInfoDetailService { ... } /// /// C# Furion 用接口来标记,更符合"接口隔离原则"。 /// /// /// 【SqlSugar ORM 数据访问】 /// 这个服务使用 ISqlSugarClient 进行数据库操作。 /// SqlSugar 是 .NET 生态中流行的 ORM 框架,类似于 Java 的 MyBatis-Plus。 /// /// 对比 Java MyBatis: // Java MyBatis: // @Mapper // public interface HwAboutUsInfoDetailMapper { // HwAboutUsInfoDetail selectById(@Param("id") Long id); // } // // C# SqlSugar: // _db.Queryable().Where(...).FirstAsync(); // // SqlSugar 的优势: // - 强类型,编译期检查 // - Lambda 表达式,IDE 智能提示 // - 链式调用,代码更流畅 // - 性能优秀,接近原生 SQL /// /// public class HwAboutUsInfoDetailService : ITransient { /// /// MyBatis 映射器名称。 /// /// 【C# 语法知识点 - const 常量】 /// const 是"编译期常量",值在编译时就确定了。 /// /// 对比 Java: /// Java: private static final String MAPPER = "HwAboutUsInfoDetailMapper"; /// C#: private const string Mapper = "HwAboutUsInfoDetailMapper"; /// /// C# 的命名约定: /// - const 通常用 PascalCase(首字母大写) /// - Java 的 static final 通常用 UPPER_SNAKE_CASE /// /// /// 【为什么保留这个常量?】 /// 虽然当前使用 SqlSugar,但保留了 XML Mapper 的回滚能力。 /// 这个常量用于指定 XML 文件中 mapper 的 namespace。 /// /// private const string Mapper = "HwAboutUsInfoDetailMapper"; /// /// MyBatis 执行器。 /// /// 【依赖注入】 /// 通过构造函数注入,和控制器注入服务的方式一样。 /// /// HwPortalMyBatisExecutor 是自定义的执行器,封装了 MyBatis 风格的 SQL 执行逻辑。 /// 当前代码中已注释掉使用,但保留以备回滚。 /// /// private readonly HwPortalMyBatisExecutor _executor; /// /// SqlSugar 数据访问对象。 /// /// 【ISqlSugarClient 接口】 /// 这是 SqlSugar 的核心接口,提供数据库访问能力。 /// /// 对比 Java: /// Java MyBatis: SqlSession 或 Mapper 接口 /// C# SqlSugar: ISqlSugarClient /// /// 通过依赖注入获取,由框架在启动时配置并注册到 DI 容器。 /// /// private readonly ISqlSugarClient _db; /// /// 构造函数(依赖注入)。 /// /// 【C# 语法知识点 - 构造函数】 /// public HwAboutUsInfoDetailService(HwPortalMyBatisExecutor executor, ISqlSugarClient db) /// /// 对比 Java: /// Java: /// @Autowired /// public HwAboutUsInfoDetailService(HwPortalMyBatisExecutor executor, ISqlSugarClient db) { /// this.executor = executor; /// this.db = db; /// } /// /// C#: /// public HwAboutUsInfoDetailService(HwPortalMyBatisExecutor executor, ISqlSugarClient db) /// { /// _executor = executor; /// _db = db; /// } /// /// C# 的改进: /// 1. 不需要 @Autowired 注解,框架自动识别构造函数 /// 2. 使用 _executor 命名约定表示私有字段 /// 3. 可以使用主构造函数(C# 12+)进一步简化 /// /// /// MyBatis 执行器(保留用于回滚) /// SqlSugar 数据访问对象 public HwAboutUsInfoDetailService(HwPortalMyBatisExecutor executor, ISqlSugarClient db) { _executor = executor; _db = db; } /// /// 根据明细ID查询关于我们明细信息。 /// /// 【方法命名约定】 /// Select + 实体名 + By + 主键名:根据主键查询单条记录 /// /// 对比 Java 若依: /// 若依通常使用:selectXxxById、getXxxById /// 这里使用:SelectXxxByXxxId,更符合 C# 的 PascalCase 命名规范 /// /// /// 【async/await 异步编程】 /// public async Task Select... /// /// 对比 Java: /// Java: /// public CompletableFuture select...(...) { ... } /// 或 /// public HwAboutUsInfoDetail select...(...) { ... } // 同步 /// /// C#: /// public async Task Select...(...) { ... } /// /// async/await 的优势: /// - 代码看起来像同步,但底层是异步 /// - 避免回调地狱 /// - 更好的性能(不阻塞线程) /// /// /// 明细ID /// 明细实体,不存在则返回 null public async Task SelectHwAboutUsInfoDetailByUsInfoDetailId(long usInfoDetailId) { // 【回滚注释说明】 // 这行注释说明如何回滚到 XML Mapper 方案: // 只需取消注释下面这行,注释掉 SqlSugar 代码即可 // 这种设计保留了"后悔药",便于调试和问题排查 // // 回滚到 XML 方案时可直接恢复: // return await _executor.QuerySingleAsync(Mapper, "selectHwAboutUsInfoDetailByUsInfoDetailId", new { usInfoDetailId }); // 【SqlSugar 查询语法】 // _db.Queryable():创建一个针对 HwAboutUsInfoDetail 表的查询 // .Where(item => item.UsInfoDetailId == usInfoDetailId):添加 WHERE 条件 // .FirstAsync():执行查询,返回第一条记录,如果没有则返回 null // // 生成的 SQL 类似于: // SELECT * FROM hw_about_us_info_detail WHERE UsInfoDetailId = @usInfoDetailId LIMIT 1 // // 对比 Java MyBatis: // Java: mapper.selectOne(new QueryWrapper().eq("UsInfoDetailId", usInfoDetailId)); // C#: _db.Queryable().Where(item => item.UsInfoDetailId == usInfoDetailId).FirstAsync(); // // C# 的优势:Lambda 表达式是强类型的,拼写错误在编译期就能发现 return await _db.Queryable() .Where(item => item.UsInfoDetailId == usInfoDetailId) .FirstAsync(); } /// /// 查询关于我们明细列表。 /// /// 【动态查询条件构建】 /// 这个方法展示了如何根据输入参数动态构建查询条件。 /// 只有参数有值时,才添加对应的 WHERE 条件。 /// /// /// 【空合并运算符 ??】 /// input ?? new HwAboutUsInfoDetail() /// /// 如果 input 为 null,则创建一个新的默认对象。 /// 这样可以避免后面的空引用异常。 /// /// 对比 Java: /// Java: input != null ? input : new HwAboutUsInfoDetail(); /// C#: input ?? new HwAboutUsInfoDetail(); /// /// C# 更简洁! /// /// /// 查询条件 /// 明细列表 public async Task> SelectHwAboutUsInfoDetailList(HwAboutUsInfoDetail input) { // 【防御性编程】 // 确保 query 不为 null,避免后续空引用异常 HwAboutUsInfoDetail query = input ?? new HwAboutUsInfoDetail(); // 回滚到 XML 方案时可直接恢复: // return await _executor.QueryListAsync(Mapper, "selectHwAboutUsInfoDetailList", query); // 【SqlSugar WhereIF 动态条件】 // .WhereIF(condition, expression):只有当 condition 为 true 时,才添加 WHERE 条件 // // 对比 Java MyBatis 的 XML: // // AND AboutUsInfoId = #{aboutUsInfoId} // // // C# SqlSugar 的 WhereIF 更直观,而且类型安全 // // 【HasValue 可空类型检查】 // query.AboutUsInfoId.HasValue 检查可空 long? 是否有值 // // 对比 Java: // Java: query.getAboutUsInfoId() != null // C#: query.AboutUsInfoId.HasValue // // 【string.IsNullOrWhiteSpace】 // 检查字符串是否为 null、空字符串 "" 或仅包含空白字符 // // 对比 Java: // Java: StringUtils.isBlank(str)(需要 Apache Commons Lang) // C#: string.IsNullOrWhiteSpace(str)(内置) // // 【Contains 模糊查询】 // item.UsInfoDetailTitle.Contains(query.UsInfoDetailTitle) // 生成 SQL:WHERE UsInfoDetailTitle LIKE '%xxx%' // // 【ToListAsync】 // 执行查询并返回列表,异步版本 return await _db.Queryable() .WhereIF(query.AboutUsInfoId.HasValue, item => item.AboutUsInfoId == query.AboutUsInfoId) .WhereIF(!string.IsNullOrWhiteSpace(query.UsInfoDetailTitle), item => item.UsInfoDetailTitle.Contains(query.UsInfoDetailTitle)) .WhereIF(!string.IsNullOrWhiteSpace(query.UsInfoDetailDesc), item => item.UsInfoDetailDesc.Contains(query.UsInfoDetailDesc)) .WhereIF(query.UsInfoDetailOrder.HasValue, item => item.UsInfoDetailOrder == query.UsInfoDetailOrder) .WhereIF(!string.IsNullOrWhiteSpace(query.UsInfoDetailPic), item => item.UsInfoDetailPic == query.UsInfoDetailPic) .ToListAsync(); } /// /// 新增关于我们明细信息。 /// /// 【插入操作返回值】 /// 返回 int 表示影响行数(1 表示成功,0 表示失败)。 /// 这是为了兼容原有 XML Mapper 的返回类型。 /// /// /// 【时间戳处理】 /// HwPortalContextHelper.Now() 获取当前时间。 /// 使用助手类的好处: /// 1. 可以统一控制时间来源(如测试时 mock) /// 2. 可以统一时区处理 /// 3. 便于后续扩展(如使用 UTC 时间) /// /// /// 明细数据 /// 影响行数(1成功,0失败) public async Task InsertHwAboutUsInfoDetail(HwAboutUsInfoDetail input) { // 【审计字段自动填充】 // CreateTime 由代码自动设置,不需要前端传递 // 这是常见的"审计字段"处理模式 input.CreateTime = HwPortalContextHelper.Now(); // 回滚到 XML 方案时可直接恢复: // long identity = await _executor.InsertReturnIdentityAsync(Mapper, "insertHwAboutUsInfoDetail", input); // 【SqlSugar 插入语法】 // _db.Insertable(input):创建一个插入操作 // .ExecuteReturnEntityAsync():执行插入并返回插入后的实体(包含自增主键) // // 对比 Java MyBatis: // Java: // mapper.insert(input); // Long identity = input.getUsInfoDetailId(); // 需要配置 useGeneratedKeys // // C#: // var entity = await _db.Insertable(input).ExecuteReturnEntityAsync(); // input.UsInfoDetailId = entity.UsInfoDetailId; // // C# 的优势:ExecuteReturnEntityAsync 直接返回插入后的完整实体 HwAboutUsInfoDetail entity = await _db.Insertable(input).ExecuteReturnEntityAsync(); // 【回填主键】 // 将生成的自增主键回填到输入对象 // 这样调用方可以获取到新插入记录的 ID input.UsInfoDetailId = entity.UsInfoDetailId; // 【返回影响行数】 // 为了兼容原有接口,返回 1 或 0 // 而不是返回主键值 return input.UsInfoDetailId > 0 ? 1 : 0; } /// /// 更新关于我们明细信息。 /// /// 【更新策略】 /// 这里使用"先查询后更新"的策略: /// 1. 先根据 ID 查询现有记录 /// 2. 如果记录不存在,返回 0 /// 3. 只更新输入对象中不为 null 的字段 /// 4. 执行更新 /// /// 这种策略的好处: /// - 避免全字段更新(减少并发冲突) /// - 可以处理部分字段更新的场景 /// - 保持原有数据不变 /// /// /// 【对比 Java 若依】 /// 若依通常使用 MyBatis 的 标签实现动态更新: /// /// UPDATE table /// /// field1 = #{field1}, /// field2 = #{field2}, /// /// WHERE id = #{id} /// /// /// C# 这里手动控制,更灵活但需要更多代码。 /// 也可以用 SqlSugar 的 .UpdateColumns() 实现类似效果。 /// /// /// 更新的数据 /// 影响行数 public async Task UpdateHwAboutUsInfoDetail(HwAboutUsInfoDetail input) { // 【审计字段更新】 // 自动设置更新时间 input.UpdateTime = HwPortalContextHelper.Now(); // 回滚到 XML 方案时可直接恢复: // return await _executor.ExecuteAsync(Mapper, "updateHwAboutUsInfoDetail", input); // 【查询现有记录】 // 先查出数据库中的当前记录 // 如果记录不存在,返回 0 表示更新失败 HwAboutUsInfoDetail current = await SelectHwAboutUsInfoDetailByUsInfoDetailId(input.UsInfoDetailId ?? 0); if (current == null) { return 0; } // 【字段级更新】 // 只更新输入对象中明确有值的字段 // null 值表示"不更新此字段" // // 这种设计支持部分更新(PATCH 语义) // 对比全量更新(PUT 语义),更灵活 // 更新关联主表ID if (input.AboutUsInfoId.HasValue) { current.AboutUsInfoId = input.AboutUsInfoId; } // 更新标题(null 检查避免覆盖为 null) if (input.UsInfoDetailTitle != null) { current.UsInfoDetailTitle = input.UsInfoDetailTitle; } // 更新描述 if (input.UsInfoDetailDesc != null) { current.UsInfoDetailDesc = input.UsInfoDetailDesc; } // 更新排序号 if (input.UsInfoDetailOrder.HasValue) { current.UsInfoDetailOrder = input.UsInfoDetailOrder; } // 更新图片 if (input.UsInfoDetailPic != null) { current.UsInfoDetailPic = input.UsInfoDetailPic; } // 更新创建时间(通常不应该更新,但保留兼容) if (input.CreateTime.HasValue) { current.CreateTime = input.CreateTime; } // 更新创建人 if (input.CreateBy != null) { current.CreateBy = input.CreateBy; } // 【必须更新的字段】 // UpdateTime 每次更新都必须更新 current.UpdateTime = input.UpdateTime; // 更新更新人 if (input.UpdateBy != null) { current.UpdateBy = input.UpdateBy; } // 【执行更新】 // _db.Updateable(current):创建更新操作 // .ExecuteCommandAsync():执行更新,返回影响行数 // // 生成的 SQL 类似于: // UPDATE hw_about_us_info_detail // SET AboutUsInfoId = @AboutUsInfoId, UsInfoDetailTitle = @UsInfoDetailTitle, ... // WHERE UsInfoDetailId = @UsInfoDetailId return await _db.Updateable(current).ExecuteCommandAsync(); } /// /// 批量删除关于我们明细信息。 /// /// 【批量删除】 /// 根据 ID 数组批量删除记录。 /// /// /// 【IN 语句】 // .In(usInfoDetailIds) 生成 SQL:WHERE UsInfoDetailId IN (1, 2, 3) // // 对比 Java MyBatis: // Java XML: // // #{id} // // // C# SqlSugar: // .In(usInfoDetailIds) // // C# 更简洁! /// /// /// 明细ID数组 /// 影响行数 public async Task DeleteHwAboutUsInfoDetailByUsInfoDetailIds(long[] usInfoDetailIds) { // 回滚到 XML 方案时可直接恢复: // return await _executor.ExecuteAsync(Mapper, "deleteHwAboutUsInfoDetailByUsInfoDetailIds", new { array = usInfoDetailIds }); // 【SqlSugar 删除语法】 // _db.Deleteable():创建删除操作 // .In(usInfoDetailIds):添加 IN 条件 // .ExecuteCommandAsync():执行删除,返回影响行数 // // 生成的 SQL: // DELETE FROM hw_about_us_info_detail WHERE UsInfoDetailId IN (@p1, @p2, @p3) // // 【注意】这里是物理删除,不是软删除 // 如果需要软删除,应该使用 Updateable 更新 IsDelete 字段 return await _db.Deleteable() .In(usInfoDetailIds) .ExecuteCommandAsync(); } }