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.

510 lines
19 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.

// ============================================================================
// 【文件说明】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;
/// <summary>
/// 关于我们明细信息服务类。
/// <para>
/// 【C# 语法知识点 - 接口实现】
/// public class HwAboutUsInfoDetailService : ITransient
///
/// ITransient 是 Furion 框架的"标记接口"Marker Interface
/// 它没有任何方法,只是用来标记服务的生命周期。
///
/// 对比 Java Spring
/// Java Spring 用 @Service + @Scope 注解来定义服务:
/// @Service
/// @Scope("prototype") // 等价于 ITransient
/// public class HwAboutUsInfoDetailService { ... }
///
/// C# Furion 用接口来标记,更符合"接口隔离原则"。
/// </para>
/// <para>
/// 【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<HwAboutUsInfoDetail>().Where(...).FirstAsync();
//
// SqlSugar 的优势:
// - 强类型,编译期检查
// - Lambda 表达式IDE 智能提示
// - 链式调用,代码更流畅
// - 性能优秀,接近原生 SQL
/// </para>
/// </summary>
public class HwAboutUsInfoDetailService : ITransient
{
/// <summary>
/// MyBatis 映射器名称。
/// <para>
/// 【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
/// </para>
/// <para>
/// 【为什么保留这个常量?】
/// 虽然当前使用 SqlSugar但保留了 XML Mapper 的回滚能力。
/// 这个常量用于指定 XML 文件中 mapper 的 namespace。
/// </para>
/// </summary>
private const string Mapper = "HwAboutUsInfoDetailMapper";
/// <summary>
/// MyBatis 执行器。
/// <para>
/// 【依赖注入】
/// 通过构造函数注入,和控制器注入服务的方式一样。
///
/// HwPortalMyBatisExecutor 是自定义的执行器,封装了 MyBatis 风格的 SQL 执行逻辑。
/// 当前代码中已注释掉使用,但保留以备回滚。
/// </para>
/// </summary>
private readonly HwPortalMyBatisExecutor _executor;
/// <summary>
/// SqlSugar 数据访问对象。
/// <para>
/// 【ISqlSugarClient 接口】
/// 这是 SqlSugar 的核心接口,提供数据库访问能力。
///
/// 对比 Java
/// Java MyBatis: SqlSession 或 Mapper 接口
/// C# SqlSugar: ISqlSugarClient
///
/// 通过依赖注入获取,由框架在启动时配置并注册到 DI 容器。
/// </para>
/// </summary>
private readonly ISqlSugarClient _db;
/// <summary>
/// 构造函数(依赖注入)。
/// <para>
/// 【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+)进一步简化
/// </para>
/// </summary>
/// <param name="executor">MyBatis 执行器(保留用于回滚)</param>
/// <param name="db">SqlSugar 数据访问对象</param>
public HwAboutUsInfoDetailService(HwPortalMyBatisExecutor executor, ISqlSugarClient db)
{
_executor = executor;
_db = db;
}
/// <summary>
/// 根据明细ID查询关于我们明细信息。
/// <para>
/// 【方法命名约定】
/// Select + 实体名 + By + 主键名:根据主键查询单条记录
///
/// 对比 Java 若依:
/// 若依通常使用selectXxxById、getXxxById
/// 这里使用SelectXxxByXxxId更符合 C# 的 PascalCase 命名规范
/// </para>
/// <para>
/// 【async/await 异步编程】
/// public async Task<HwAboutUsInfoDetail> Select...
///
/// 对比 Java
/// Java:
/// public CompletableFuture<HwAboutUsInfoDetail> select...(...) { ... }
/// 或
/// public HwAboutUsInfoDetail select...(...) { ... } // 同步
///
/// C#:
/// public async Task<HwAboutUsInfoDetail> Select...(...) { ... }
///
/// async/await 的优势:
/// - 代码看起来像同步,但底层是异步
/// - 避免回调地狱
/// - 更好的性能(不阻塞线程)
/// </para>
/// </summary>
/// <param name="usInfoDetailId">明细ID</param>
/// <returns>明细实体,不存在则返回 null</returns>
public async Task<HwAboutUsInfoDetail> SelectHwAboutUsInfoDetailByUsInfoDetailId(long usInfoDetailId)
{
// 【回滚注释说明】
// 这行注释说明如何回滚到 XML Mapper 方案:
// 只需取消注释下面这行,注释掉 SqlSugar 代码即可
// 这种设计保留了"后悔药",便于调试和问题排查
//
// 回滚到 XML 方案时可直接恢复:
// return await _executor.QuerySingleAsync<HwAboutUsInfoDetail>(Mapper, "selectHwAboutUsInfoDetailByUsInfoDetailId", new { usInfoDetailId });
// 【SqlSugar 查询语法】
// _db.Queryable<HwAboutUsInfoDetail>():创建一个针对 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<HwAboutUsInfoDetail>().eq("UsInfoDetailId", usInfoDetailId));
// C#: _db.Queryable<HwAboutUsInfoDetail>().Where(item => item.UsInfoDetailId == usInfoDetailId).FirstAsync();
//
// C# 的优势Lambda 表达式是强类型的,拼写错误在编译期就能发现
return await _db.Queryable<HwAboutUsInfoDetail>()
.Where(item => item.UsInfoDetailId == usInfoDetailId)
.FirstAsync();
}
/// <summary>
/// 查询关于我们明细列表。
/// <para>
/// 【动态查询条件构建】
/// 这个方法展示了如何根据输入参数动态构建查询条件。
/// 只有参数有值时,才添加对应的 WHERE 条件。
/// </para>
/// <para>
/// 【空合并运算符 ??】
/// input ?? new HwAboutUsInfoDetail()
///
/// 如果 input 为 null则创建一个新的默认对象。
/// 这样可以避免后面的空引用异常。
///
/// 对比 Java
/// Java: input != null ? input : new HwAboutUsInfoDetail();
/// C#: input ?? new HwAboutUsInfoDetail();
///
/// C# 更简洁!
/// </para>
/// </summary>
/// <param name="input">查询条件</param>
/// <returns>明细列表</returns>
public async Task<List<HwAboutUsInfoDetail>> SelectHwAboutUsInfoDetailList(HwAboutUsInfoDetail input)
{
// 【防御性编程】
// 确保 query 不为 null避免后续空引用异常
HwAboutUsInfoDetail query = input ?? new HwAboutUsInfoDetail();
// 回滚到 XML 方案时可直接恢复:
// return await _executor.QueryListAsync<HwAboutUsInfoDetail>(Mapper, "selectHwAboutUsInfoDetailList", query);
// 【SqlSugar WhereIF 动态条件】
// .WhereIF(condition, expression):只有当 condition 为 true 时,才添加 WHERE 条件
//
// 对比 Java MyBatis 的 XML
// <if test="aboutUsInfoId != null">
// AND AboutUsInfoId = #{aboutUsInfoId}
// </if>
//
// 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)
// 生成 SQLWHERE UsInfoDetailTitle LIKE '%xxx%'
//
// 【ToListAsync】
// 执行查询并返回列表,异步版本
return await _db.Queryable<HwAboutUsInfoDetail>()
.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();
}
/// <summary>
/// 新增关于我们明细信息。
/// <para>
/// 【插入操作返回值】
/// 返回 int 表示影响行数1 表示成功0 表示失败)。
/// 这是为了兼容原有 XML Mapper 的返回类型。
/// </para>
/// <para>
/// 【时间戳处理】
/// HwPortalContextHelper.Now() 获取当前时间。
/// 使用助手类的好处:
/// 1. 可以统一控制时间来源(如测试时 mock
/// 2. 可以统一时区处理
/// 3. 便于后续扩展(如使用 UTC 时间)
/// </para>
/// </summary>
/// <param name="input">明细数据</param>
/// <returns>影响行数1成功0失败</returns>
public async Task<int> 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;
}
/// <summary>
/// 更新关于我们明细信息。
/// <para>
/// 【更新策略】
/// 这里使用"先查询后更新"的策略:
/// 1. 先根据 ID 查询现有记录
/// 2. 如果记录不存在,返回 0
/// 3. 只更新输入对象中不为 null 的字段
/// 4. 执行更新
///
/// 这种策略的好处:
/// - 避免全字段更新(减少并发冲突)
/// - 可以处理部分字段更新的场景
/// - 保持原有数据不变
/// </para>
/// <para>
/// 【对比 Java 若依】
/// 若依通常使用 MyBatis 的 <if> 标签实现动态更新:
/// <update id="updateXxx">
/// UPDATE table
/// <set>
/// <if test="field1 != null">field1 = #{field1},</if>
/// <if test="field2 != null">field2 = #{field2},</if>
/// </set>
/// WHERE id = #{id}
/// </update>
///
/// C# 这里手动控制,更灵活但需要更多代码。
/// 也可以用 SqlSugar 的 .UpdateColumns() 实现类似效果。
/// </para>
/// </summary>
/// <param name="input">更新的数据</param>
/// <returns>影响行数</returns>
public async Task<int> 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();
}
/// <summary>
/// 批量删除关于我们明细信息。
/// <para>
/// 【批量删除】
/// 根据 ID 数组批量删除记录。
/// </para>
/// <para>
/// 【IN 语句】
// .In(usInfoDetailIds) 生成 SQLWHERE UsInfoDetailId IN (1, 2, 3)
//
// 对比 Java MyBatis
// Java XML:
// <foreach collection="array" item="id" open="(" separator="," close=")">
// #{id}
// </foreach>
//
// C# SqlSugar:
// .In(usInfoDetailIds)
//
// C# 更简洁!
/// </para>
/// </summary>
/// <param name="usInfoDetailIds">明细ID数组</param>
/// <returns>影响行数</returns>
public async Task<int> DeleteHwAboutUsInfoDetailByUsInfoDetailIds(long[] usInfoDetailIds)
{
// 回滚到 XML 方案时可直接恢复:
// return await _executor.ExecuteAsync(Mapper, "deleteHwAboutUsInfoDetailByUsInfoDetailIds", new { array = usInfoDetailIds });
// 【SqlSugar 删除语法】
// _db.Deleteable<HwAboutUsInfoDetail>():创建删除操作
// .In(usInfoDetailIds):添加 IN 条件
// .ExecuteCommandAsync():执行删除,返回影响行数
//
// 生成的 SQL
// DELETE FROM hw_about_us_info_detail WHERE UsInfoDetailId IN (@p1, @p2, @p3)
//
// 【注意】这里是物理删除,不是软删除
// 如果需要软删除,应该使用 Updateable 更新 IsDelete 字段
return await _db.Deleteable<HwAboutUsInfoDetail>()
.In(usInfoDetailIds)
.ExecuteCommandAsync();
}
}