|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// 【文件说明】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<T> 执行查询并返回列表。
|
|
|
|
|
|
// T 是结果映射类型,SearchRawRecord 是原始搜索记录。
|
|
|
|
|
|
// 回滚到 XML 方案时可直接恢复:
|
|
|
|
|
|
// return _executor.QueryListAsync<SearchRawRecord>(Mapper, "searchByKeyword", new { keyword });
|
|
|
|
|
|
return _db.Ado.SqlQueryAsync<SearchRawRecord>(SearchByKeywordSql, new[]
|
|
|
|
|
|
{
|
|
|
|
|
|
new SugarParameter("@keyword", keyword)
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|