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.

176 lines
8.0 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.

// ============================================================================
// 【文件说明】LegacyHwSearchQueryService.cs - 旧版搜索查询服务
// ============================================================================
// 这是一个"遗留系统"Legacy的搜索服务使用原始 SQL 实现搜索。
//
// 【什么是遗留系统?】
// 遗留系统是指:
// - 旧版本的功能实现
// - 新版本保留它作为兼容或降级方案
// - 通常在特定场景下使用
//
// 【为什么保留旧版搜索?】
// 1. 兼容性:新搜索可能依赖额外的索引表,旧环境可能没有
// 2. 降级方案:新搜索失败时,可以回退到旧版
// 3. 平滑迁移:逐步切换到新搜索,而不是一刀切
//
// 【与新版搜索的区别】
// - 新版:使用 EF Core 查询索引表,支持复杂查询和排序
// - 旧版:使用 MyBatis 风格的 SQL直接查询业务表
//
// 【与 Java Spring Boot 的对比】
// Java 若依的搜索通常是直接 SQL
// @Select("SELECT * FROM hw_web WHERE title LIKE CONCAT('%', #{keyword}, '%')")
// List<HwWeb> searchByKeyword(@Param("keyword") String keyword);
//
// 这个类就是类似的实现方式。
// ============================================================================
namespace Admin.NET.Plugin.HwPortal;
/// <summary>
/// 旧版搜索查询服务。
/// <para>
/// 【设计模式 - 适配器模式】
/// 这个类把旧版 SQL 搜索适配到新的服务架构中:
/// - 对外提供统一的搜索接口
/// - 内部使用旧的 SQL 实现
///
/// 这样可以在不修改调用方的情况下,平滑切换搜索实现。
/// </para>
/// <para>
/// 【C# 语法知识点 - sealed 密封类】
/// sealed 表示不能被继承。遗留服务通常不需要扩展。
/// </para>
/// <para>
/// 【C# 语法知识点 - ITransient 瞬态服务】
/// ITransient 表示每次请求都创建新实例。
/// 搜索服务通常是无状态的,用 ITransient 或 IScoped 都可以。
/// </para>
/// </summary>
public sealed class LegacyHwSearchQueryService : ITransient
{
/// <summary>
/// MyBatis 映射器名称。
/// </summary>
private const string Mapper = "HwSearchMapper";
private const string SearchByKeywordSql =
"""
SELECT *
FROM (
SELECT CONVERT('web' USING utf8mb4) COLLATE utf8mb4_general_ci AS source_type,
CONVERT(CAST(w.web_id AS CHAR) USING utf8mb4) COLLATE utf8mb4_general_ci AS biz_id,
CONVERT(CONCAT('#', w.web_code) USING utf8mb4) COLLATE utf8mb4_general_ci AS title,
CONVERT(IFNULL(w.web_json_string, '') USING utf8mb4) COLLATE utf8mb4_general_ci AS content,
CONVERT(CAST(w.web_code AS CHAR) USING utf8mb4) COLLATE utf8mb4_general_ci AS web_code,
CAST(NULL AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_general_ci AS type_id,
CAST(NULL AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_general_ci AS device_id,
CAST(NULL AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_general_ci AS menu_id,
CAST(NULL AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_general_ci AS document_id,
80 AS score,
w.update_time AS updated_at
FROM hw_web w
WHERE w.is_delete = '0'
AND w.web_json_string LIKE CONCAT('%', @keyword, '%')
UNION ALL
SELECT CONVERT('web1' USING utf8mb4) COLLATE utf8mb4_general_ci AS source_type,
CONVERT(CAST(w1.web_id AS CHAR) USING utf8mb4) COLLATE utf8mb4_general_ci AS biz_id,
CONVERT(CONCAT('#', w1.web_code, '-', w1.typeId, '-', w1.device_id) USING utf8mb4) COLLATE utf8mb4_general_ci AS title,
CONVERT(IFNULL(w1.web_json_string, '') USING utf8mb4) COLLATE utf8mb4_general_ci AS content,
CONVERT(CAST(w1.web_code AS CHAR) USING utf8mb4) COLLATE utf8mb4_general_ci AS web_code,
CONVERT(CAST(w1.typeId AS CHAR) USING utf8mb4) COLLATE utf8mb4_general_ci AS type_id,
CONVERT(CAST(w1.device_id AS CHAR) USING utf8mb4) COLLATE utf8mb4_general_ci AS device_id,
CAST(NULL AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_general_ci AS menu_id,
CAST(NULL AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_general_ci AS document_id,
90 AS score,
w1.update_time AS updated_at
FROM hw_web1 w1
WHERE w1.is_delete = '0'
AND w1.web_json_string LIKE CONCAT('%', @keyword, '%')
UNION ALL
SELECT CONVERT('document' USING utf8mb4) COLLATE utf8mb4_general_ci AS source_type,
CONVERT(IFNULL(d.document_id, '') USING utf8mb4) COLLATE utf8mb4_general_ci AS biz_id,
CONVERT(IFNULL(NULLIF(d.json, ''), d.document_id) USING utf8mb4) COLLATE utf8mb4_general_ci AS title,
CONVERT(IFNULL(d.json, '') USING utf8mb4) COLLATE utf8mb4_general_ci AS content,
CONVERT(IFNULL(d.web_code, '') USING utf8mb4) COLLATE utf8mb4_general_ci AS web_code,
CONVERT(IFNULL(d.type, '') USING utf8mb4) COLLATE utf8mb4_general_ci AS type_id,
CAST(NULL AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_general_ci AS device_id,
CAST(NULL AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_general_ci AS menu_id,
CONVERT(IFNULL(d.document_id, '') USING utf8mb4) COLLATE utf8mb4_general_ci AS document_id,
70 AS score,
d.update_time AS updated_at
FROM hw_web_document d
WHERE d.is_delete = '0'
AND (
d.json LIKE CONCAT('%', @keyword, '%')
OR d.document_id LIKE CONCAT('%', @keyword, '%')
)
) s
ORDER BY s.score DESC, s.updated_at DESC
LIMIT 500
""";
/// <summary>
/// MyBatis 执行器。
/// <para>
/// 【依赖注入】
/// 通过构造函数注入 HwPortalMyBatisExecutor。
/// 这个执行器负责解析 XML 中的 SQL 并执行。
/// </para>
/// </summary>
private readonly HwPortalMyBatisExecutor _executor;
private readonly ISqlSugarClient _db;
/// <summary>
/// 构造函数(依赖注入)。
/// </summary>
/// <param name="executor">MyBatis 执行器</param>
public LegacyHwSearchQueryService(HwPortalMyBatisExecutor executor, ISqlSugarClient db)
{
_executor = executor;
_db = db;
}
/// <summary>
/// 根据关键词搜索。
/// <para>
/// 【搜索逻辑】
/// 调用 HwSearchMapper.xml 中定义的 searchByKeyword SQL。
/// SQL 通常是 LIKE 查询:
/// SELECT * FROM hw_web WHERE title LIKE '%keyword%'
/// UNION ALL
/// SELECT * FROM hw_product WHERE title LIKE '%keyword%'
///
/// 【性能说明】
/// LIKE '%keyword%' 无法使用索引,性能较差。
/// 这就是为什么需要新版搜索(使用索引表)。
/// </para>
/// <para>
/// 【C# 语法知识点 - 匿名对象传参】
/// new { keyword } 创建匿名对象,作为 SQL 参数。
/// SQL 中的 #{keyword} 会被替换为参数值。
///
/// 对比 Java MyBatis
/// Java: @Param("keyword") String keyword
/// C#: new { keyword }
/// </para>
/// </summary>
/// <param name="keyword">搜索关键词</param>
/// <returns>搜索结果列表</returns>
public Task<List<SearchRawRecord>> SearchAsync(string keyword)
{
// QueryListAsync&lt;T&gt; 执行查询并返回列表。
// T 是结果映射类型SearchRawRecord 是原始搜索记录。
// 回滚到 XML 方案时可直接恢复:
// return _executor.QueryListAsync<SearchRawRecord>(Mapper, "searchByKeyword", new { keyword });
return _db.Ado.SqlQueryAsync<SearchRawRecord>(SearchByKeywordSql, new[]
{
new SugarParameter("@keyword", keyword)
});
}
}