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.
28 KiB
28 KiB
hw-portal 搜索改造方案
1. 文档目的
- 文档范围:给
Admin.NET-v2中的hw-portal插件模块设计一套新的搜索方案。 - 目标结果:使用
EF Core 10 + MySQL 普通搜索引擎(全文检索 + 兜底模糊搜索),替换当前基于UNION ALL + LIKE的关键词搜索。 - 约束来源:基于当前对话已确认,不做向量搜索,不接大模型,不接本地 embedding 模型,保持现有搜索接口不变,允许新增搜索索引表与数据库索引。
- 文档用途:供后续开发、代码评审、数据库设计、前后端联调、测试验收使用。
2. 当前理解
2.1 当前项目结构理解
- 后端主框架:
Furion + SqlSugar - 当前新增的门户模块:
Admin.NET\Plugins\Admin.NET.Plugin.HwPortal - 当前
hw-portal已经是独立插件项目,但搜索仍是“兼容 MyBatis XML”的方案,不是 EF Core 方案。 - 当前仓库里没有发现
EF Core、DbContext、Npgsql/Pomelo/MySql EF Provider等依赖,说明 EF Core 搜索子系统需要从零引入。
2.2 当前搜索实现理解
当前搜索主入口:
当前搜索核心实现:
当前搜索 SQL:
当前索引重建实现:
当前文本抽取器:
2.3 当前搜索的实际问题
- 搜索结果直接查业务表,
UNION ALL + LIKE性能一般。 - 搜索和业务表强耦合,新增来源或调整评分成本高。
- 重建接口当前是空实现,没有真正索引重建能力。
- 关键词搜索只能做字符匹配,不是“搜索引擎式”的独立索引架构。
- 当前不是 EF Core 方案,也没有独立搜索库/搜索表/全文索引。
3. 对话中的选择结论
本方案不是拍脑袋决定,而是基于当前对话确认的选择:
3.1 已确认选择
- 不做
Vector Search - 不使用大模型
- 可以做“普通搜索引擎”
- 保持原接口不变
- 允许改数据库结构
- 搜索相关改造切到
EF Core 10
3.2 因此得出的最终方案
不是:
EF Core 10 + Vector SearchElasticsearchEasy-ES- 继续使用
MyBatis XML + LIKE
而是:
EF Core 10 + MySQL 全文检索(FULLTEXT)- 辅助
LIKE兜底 - 独立搜索索引表
- 独立搜索
DbContext - 新旧搜索逻辑保留过渡接口,但默认切新方案
4. 具体需求
4.1 业务需求
- 前台搜索继续支持:
- 关键词搜索
- 分页
- 命中摘要
- 高亮
- 路由跳转
- 编辑端搜索继续支持:
- 编辑路由
editRoute
- 编辑路由
- 搜索来源继续覆盖:
- 菜单
hw_web_menu - 页面
hw_web - 页面详情
hw_web1 - 资料文档
hw_web_document - 配置类型
hw_portal_config_type
- 菜单
- 后台继续保留“重建索引”接口。
4.2 技术需求
- 新搜索查询必须使用
EF Core 10 - 新搜索必须有独立搜索索引表,而不是每次直查原业务表
- 新搜索必须兼容 MySQL
- 现有公开接口路径与返回 DTO 不变
- 要支持增量同步索引,不允许每次保存都全量扫描业务表
5. 应用场景分析
5.1 用户搜索官网内容
典型场景:
- 用户输入“工业物联网”
- 需要搜到:
- 产品中心配置类型
- 产品页面 JSON 文本
- 页面详情 JSON 文本
- 相关文档
- 菜单项
要求:
- 响应时间稳定
- 标题命中优先
- 摘要可读
- 可跳前台页面
5.2 编辑端搜索
典型场景:
- 运营同事在后台搜索某个页面或某个资料文档
- 需要直接跳到编辑页
要求:
- 复用同一套索引
- 只是在结果里补
editRoute
5.3 数据更新后的搜索可见性
典型场景:
- 新增/修改页面 JSON
- 更新资料文档
- 新增菜单
要求:
- 保存成功后索引同步更新
- 不依赖手工全量重建
6. 技术选型分析
6.1 为什么不继续用当前 SQL 搜索
当前 HwSearchMapper.xml 的特点:
- 多张表
UNION ALL - 每张表都做
%keyword% - 排序靠写死分数和更新时间
优点:
- 简单
- 快速可用
问题:
- 性能扩展性差
- 搜索能力依赖业务表结构
- 不像真正搜索引擎
- 不利于后续继续增加来源
6.2 为什么不选向量搜索
向量搜索至少需要:
- 文本 embedding 模型
- 向量字段
- 相似度检索能力
当前对话里已明确:
- 不做向量搜索
- 不用大模型
所以本次方案不能再写成 Vector Search。
6.3 为什么选 MySQL FULLTEXT + 独立搜索表
优点:
- 不依赖外部搜索引擎
- 不依赖模型
- 能明显优于当前
LIKE方案 - 可以保留原有 DTO 和接口
- 可先做成普通搜索引擎,后续再升级成 ES/向量检索
6.4 为什么引入 EF Core 10
虽然主项目主链路是 SqlSugar + Furion,但搜索子系统可以独立引入 EF Core,理由如下:
- 搜索索引表是新增子系统,不必受当前 MyBatis 兼容层约束
- EF Core 对“独立读写模型 + 独立迁移 + 查询表达式”很合适
- 后续若要演进到更多数据库特性,EF Core 子系统维护更清晰
7. 改造后的目标架构
用户请求 /portal/search
↓
HwSearchController
↓
HwSearchService(新)
↓
HwPortalSearchDbContext
↓
hw_portal_search_doc(搜索索引表)
↓
MySQL FULLTEXT / LIKE 兜底
业务数据更新流程:
业务保存成功(页面/菜单/文档/配置类型)
↓
对应业务 Service
↓
IHwSearchIndexService.UpsertAsync(...)
↓
PortalSearchDocConverter
↓
hw_portal_search_doc
8. 数据库设计方案
8.1 新增搜索索引表
建议表名:
hw_portal_search_doc
建议字段:
CREATE TABLE `hw_portal_search_doc` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`doc_id` VARCHAR(128) NOT NULL COMMENT '文档唯一标识,例如 menu:1、web:7',
`source_type` VARCHAR(32) NOT NULL COMMENT '来源类型:menu/web/web1/document/configType',
`biz_id` VARCHAR(64) NULL COMMENT '业务主键字符串',
`title` VARCHAR(500) NULL COMMENT '搜索标题',
`content` LONGTEXT NULL COMMENT '搜索正文',
`web_code` VARCHAR(64) NULL COMMENT '页面编码',
`type_id` VARCHAR(64) NULL COMMENT '类型ID',
`device_id` VARCHAR(64) NULL COMMENT '设备ID',
`menu_id` VARCHAR(64) NULL COMMENT '菜单ID',
`document_id` VARCHAR(64) NULL COMMENT '文档ID',
`base_score` INT NOT NULL DEFAULT 0 COMMENT '基础分',
`route` VARCHAR(255) NULL COMMENT '展示路由',
`route_query_json` JSON NULL COMMENT '展示路由参数',
`edit_route` VARCHAR(255) NULL COMMENT '编辑路由',
`is_delete` CHAR(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除',
`updated_at` DATETIME NULL COMMENT '业务更新时间',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '索引创建时间',
`modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '索引更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_hw_portal_search_doc_doc_id` (`doc_id`),
KEY `idx_hw_portal_search_doc_source_type` (`source_type`),
KEY `idx_hw_portal_search_doc_updated_at` (`updated_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='hw-portal 搜索索引表';
全文索引:
ALTER TABLE `hw_portal_search_doc`
ADD FULLTEXT KEY `ft_hw_portal_search_doc_title_content` (`title`, `content`);
8.2 设计理由
doc_id:保证一个业务对象只对应一条搜索文档source_type:保留来源分类title/content:统一搜索输入route/edit_route:避免查询后再做过多二次拼装updated_at:用于排序base_score:保留当前旧搜索里“不同来源不同基础权重”的思路
9. 代码改造总览
9.1 现有代码保留
保留但用途调整:
- HwSearchController.cs
- HwSearchAdminController.cs
- SearchPageDTO.cs
- SearchResultDTO.cs
- PortalSearchDocConverter.cs
9.2 现有代码废弃或降级为兼容模式
这些文件可以:
- 保留一版作为
legacy回退 - 或重命名为
LegacyHwSearchService
9.3 建议新增文件位置
推荐新增目录:
Admin.NET\Plugins\Admin.NET.Plugin.HwPortal\SearchEf
建议文件:
SearchEf/HwPortalSearchDbContext.csSearchEf/Entity/HwPortalSearchDoc.csSearchEf/Option/HwPortalSearchOptions.csSearchEf/Service/IHwSearchIndexService.csSearchEf/Service/HwSearchIndexService.csSearchEf/Service/HwSearchQueryService.csSearchEf/Service/HwSearchRebuildService.csSearchEf/Extensions/HwPortalSearchServiceCollectionExtensions.cs
如希望保持插件结构更规整,也可以落在:
Entity/SearchService/SearchEfOption
10. 具体代码设计
10.1 搜索索引实体
建议文件:
Admin.NET\Plugins\Admin.NET.Plugin.HwPortal\SearchEf\Entity\HwPortalSearchDoc.cs
建议代码:
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Admin.NET.Plugin.HwPortal.SearchEf.Entity;
/// <summary>
/// hw-portal 搜索索引实体。
/// 这不是业务原表,而是专门为搜索准备的索引表。
/// </summary>
[Table("hw_portal_search_doc")]
[Index(nameof(DocId), IsUnique = true)]
[Index(nameof(SourceType))]
[Index(nameof(UpdatedAt))]
public class HwPortalSearchDoc
{
[Key]
public long Id { get; set; }
/// <summary>
/// 文档唯一键。
/// 例如:menu:12、web:7、document:abc001。
/// 用它实现幂等更新。
/// </summary>
[Required]
[MaxLength(128)]
public string DocId { get; set; } = string.Empty;
/// <summary>
/// 来源类型。
/// 值域与当前 PortalSearchDocConverter 里的常量保持一致。
/// </summary>
[Required]
[MaxLength(32)]
public string SourceType { get; set; } = string.Empty;
[MaxLength(64)]
public string? BizId { get; set; }
[MaxLength(500)]
public string? Title { get; set; }
/// <summary>
/// 正文。
/// 这里通常会存“已抽取、已清洗”的文本,而不是原始 JSON。
/// </summary>
public string? Content { get; set; }
[MaxLength(64)]
public string? WebCode { get; set; }
[MaxLength(64)]
public string? TypeId { get; set; }
[MaxLength(64)]
public string? DeviceId { get; set; }
[MaxLength(64)]
public string? MenuId { get; set; }
[MaxLength(64)]
public string? DocumentId { get; set; }
/// <summary>
/// 基础分。
/// 保留旧系统“菜单比正文更高”的评分思想。
/// </summary>
public int BaseScore { get; set; }
[MaxLength(255)]
public string? Route { get; set; }
public string? RouteQueryJson { get; set; }
[MaxLength(255)]
public string? EditRoute { get; set; }
[MaxLength(1)]
public string IsDelete { get; set; } = "0";
public DateTime? UpdatedAt { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime ModifiedAt { get; set; }
}
10.2 搜索 DbContext
建议文件:
Admin.NET\Plugins\Admin.NET.Plugin.HwPortal\SearchEf\HwPortalSearchDbContext.cs
建议代码:
using Admin.NET.Plugin.HwPortal.SearchEf.Entity;
using Microsoft.EntityFrameworkCore;
namespace Admin.NET.Plugin.HwPortal.SearchEf;
/// <summary>
/// 搜索子系统专用 DbContext。
/// 只管理搜索相关表,不把整个 hw-portal 都切到 EF Core。
/// </summary>
public class HwPortalSearchDbContext : DbContext
{
public HwPortalSearchDbContext(DbContextOptions<HwPortalSearchDbContext> options) : base(options)
{
}
public DbSet<HwPortalSearchDoc> SearchDocs => Set<HwPortalSearchDoc>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Why:
// 这里做 EF 层的结构约束声明。
// FULLTEXT 索引建议仍通过 SQL 脚本或迁移脚本手工加,避免 provider 差异。
modelBuilder.Entity<HwPortalSearchDoc>(entity =>
{
entity.Property(x => x.Content).HasColumnType("longtext");
entity.Property(x => x.RouteQueryJson).HasColumnType("json");
});
}
}
10.3 搜索索引服务接口
建议文件:
Admin.NET\Plugins\Admin.NET.Plugin.HwPortal\SearchEf\Service\IHwSearchIndexService.cs
建议代码:
namespace Admin.NET.Plugin.HwPortal.SearchEf.Service;
/// <summary>
/// 搜索索引服务接口。
/// 负责把业务对象同步成搜索文档。
/// </summary>
public interface IHwSearchIndexService
{
Task UpsertMenuAsync(HwWebMenu menu, CancellationToken cancellationToken = default);
Task UpsertWebAsync(HwWeb web, CancellationToken cancellationToken = default);
Task UpsertWeb1Async(HwWeb1 web1, CancellationToken cancellationToken = default);
Task UpsertDocumentAsync(HwWebDocument document, CancellationToken cancellationToken = default);
Task UpsertConfigTypeAsync(HwPortalConfigType configType, CancellationToken cancellationToken = default);
Task DeleteByDocIdAsync(string docId, CancellationToken cancellationToken = default);
Task RebuildAllAsync(CancellationToken cancellationToken = default);
}
10.4 搜索查询服务
建议文件:
Admin.NET\Plugins\Admin.NET.Plugin.HwPortal\SearchEf\Service\HwSearchQueryService.cs
建议代码片段:
using Admin.NET.Plugin.HwPortal.SearchEf.Entity;
using Microsoft.EntityFrameworkCore;
namespace Admin.NET.Plugin.HwPortal.SearchEf.Service;
/// <summary>
/// 搜索查询服务。
/// 负责对索引表做查询,而不是直接查业务表。
/// </summary>
public class HwSearchQueryService
{
private readonly HwPortalSearchDbContext _db;
public HwSearchQueryService(HwPortalSearchDbContext db)
{
_db = db;
}
public async Task<List<HwPortalSearchDoc>> SearchAsync(string keyword, int take, CancellationToken cancellationToken = default)
{
keyword = keyword.Trim();
// 第一阶段:先走普通 LIKE 版本,确保功能先跑通。
// 第二阶段:再补 FromSqlRaw + MATCH AGAINST 做 FULLTEXT 优化。
IQueryable<HwPortalSearchDoc> query = _db.SearchDocs
.AsNoTracking()
.Where(x => x.IsDelete == "0")
.Where(x =>
(x.Title != null && EF.Functions.Like(x.Title, $"%{keyword}%")) ||
(x.Content != null && EF.Functions.Like(x.Content, $"%{keyword}%")))
.OrderByDescending(x => x.BaseScore)
.ThenByDescending(x => x.UpdatedAt);
return await query.Take(take).ToListAsync(cancellationToken);
}
}
说明:
- 第一阶段先保证 EF Core 路径可用。
- 第二阶段建议改成:
FromSqlInterpolatedMATCH(title, content) AGAINST (...)
- 这样能兼顾“先实现”和“后优化”。
10.5 搜索重建服务
建议文件:
Admin.NET\Plugins\Admin.NET.Plugin.HwPortal\SearchEf\Service\HwSearchRebuildService.cs
建议代码片段:
namespace Admin.NET.Plugin.HwPortal.SearchEf.Service;
/// <summary>
/// 全量重建搜索索引。
/// 当前管理后台的 /portal/search/admin/rebuild 最终会调用这里。
/// </summary>
public class HwSearchRebuildService : IHwSearchRebuildService
{
private readonly HwPortalSearchDbContext _db;
private readonly IHwSearchIndexService _indexService;
private readonly HwWebMenuService _menuService;
private readonly HwWebService _webService;
private readonly HwWeb1Service _web1Service;
private readonly HwWebDocumentService _documentService;
private readonly HwPortalConfigTypeService _configTypeService;
public HwSearchRebuildService(
HwPortalSearchDbContext db,
IHwSearchIndexService indexService,
HwWebMenuService menuService,
HwWebService webService,
HwWeb1Service web1Service,
HwWebDocumentService documentService,
HwPortalConfigTypeService configTypeService)
{
_db = db;
_indexService = indexService;
_menuService = menuService;
_webService = webService;
_web1Service = web1Service;
_documentService = documentService;
_configTypeService = configTypeService;
}
public async Task RebuildAllAsync()
{
// Why:
// 全量重建时,先清空索引表,再重新从业务表扫描一次。
await _db.Database.ExecuteSqlRawAsync("TRUNCATE TABLE hw_portal_search_doc;");
List<HwWebMenu> menus = await _menuService.SelectHwWebMenuList(new HwWebMenu());
foreach (HwWebMenu item in menus)
{
await _indexService.UpsertMenuAsync(item);
}
List<HwWeb> webs = await _webService.SelectHwWebList(new HwWeb());
foreach (HwWeb item in webs)
{
await _indexService.UpsertWebAsync(item);
}
// 其余来源同理继续补齐。
}
}
10.6 与现有控制器对接
保持现有文件位置不变:
建议改造方式:
- 控制器路径不改
- 入参不改
- 返回 DTO 不改
- 内部调用从旧
HwSearchService切到新HwSearchQueryService
11. 关键代码改造位置
11.1 插件启动注册
当前文件:
当前问题:
- 只注册了 XML 执行器
IHwSearchRebuildService绑定的是空实现
建议新增注册:
using Microsoft.EntityFrameworkCore;
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<HwPortalMapperRegistry>();
services.AddScoped<HwPortalMyBatisExecutor>();
services.AddScoped<PortalSearchDocConverter>();
// 新增:注册搜索专用 DbContext。
// Why:
// 搜索子系统是独立读写模型,不和 SqlSugar 主链路抢职责。
services.AddDbContext<HwPortalSearchDbContext>(options =>
{
options.UseMySql(
"这里读取配置中的搜索连接串",
ServerVersion.AutoDetect("这里读取配置中的搜索连接串"));
});
services.AddScoped<IHwSearchIndexService, HwSearchIndexService>();
services.AddScoped<HwSearchQueryService>();
services.AddScoped<IHwSearchRebuildService, HwSearchRebuildService>();
}
11.2 业务服务改造点
需要接入索引增量同步的文件:
- HwWebService.cs
- HwWeb1Service.cs
- HwWebMenuService.cs
- HwWebDocumentService.cs
- HwPortalConfigTypeService.cs
建议改造模式:
public class HwWebService : ITransient
{
private readonly IHwSearchIndexService _searchIndexService;
public async Task<int> InsertHwWeb(HwWeb input)
{
long identity = await _executor.InsertReturnIdentityAsync(Mapper, "insertHwWeb", input);
input.WebId = identity;
if (identity > 0)
{
// Why:
// 不再做全量重建,而是只更新这一个文档的搜索索引。
await _searchIndexService.UpsertWebAsync(input);
}
return identity > 0 ? 1 : 0;
}
}
12. 配置设计
建议新增配置文件:
Admin.NET\Admin.NET.Application\Configuration\Search.json
建议内容:
{
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
"HwPortalSearch": {
"Engine": "mysql_fulltext",
"EnableLegacyFallback": true,
"ConnectionString": "Server=localhost;Database=hw_portal;Uid=root;Pwd=123456;CharSet=utf8mb4;",
"BatchSize": 500,
"TakeLimit": 500
}
}
建议新增配置类:
Admin.NET\Plugins\Admin.NET.Plugin.HwPortal\SearchEf\Option\HwPortalSearchOptions.cs
13. 返回结构兼容要求
现有返回 DTO 不变:
必须保持:
SearchPageDTO
totalrows
SearchResultDTO
sourceTypetitlesnippetscorerouterouteQueryeditRoute
这样前端调用逻辑不用改。
14. 评分策略建议
建议沿用当前评分思路并做增强:
14.1 基础分
- menu: 110
- web1: 90
- web: 80
- document: 70
- configType: 65
14.2 二次加分
- 标题命中:+20
- 正文命中:+10
- 最近更新时间更近:排序优先
14.3 后续可扩展
- 标题完整命中额外加分
- 命中次数加权
- 前台搜索与编辑搜索使用不同的加权策略
15. 方案实施步骤
15.1 第一步:搭建 EF Core 搜索基础设施
- 引入 EF Core 10 MySQL Provider
- 新建
HwPortalSearchDbContext - 新建
HwPortalSearchDoc实体 - 新建
Search.json与配置对象 - 在插件
Startup中注册
15.2 第二步:建搜索索引表
- 建表
hw_portal_search_doc - 建唯一索引
- 建普通索引
- 建 FULLTEXT 索引
15.3 第三步:实现索引同步
- 补
IHwSearchIndexService - 实现
UpsertMenuAsync - 实现
UpsertWebAsync - 实现
UpsertWeb1Async - 实现
UpsertDocumentAsync - 实现
UpsertConfigTypeAsync
15.4 第四步:实现查询服务
- 新建
HwSearchQueryService - 先实现
LIKE版 EF 查询 - 再补
MATCH AGAINST - 复用现有摘要、高亮、路由逻辑
15.5 第五步:替换控制器调用
- 保持控制器不变
- 改内部服务依赖
- 管理端重建接口切到真正重建服务
15.6 第六步:移除空实现
- 去掉
HwNoopSearchRebuildService默认绑定 - 保留旧 XML 搜索作为
legacy回退实现
16. 测试方案
16.1 功能测试
- 搜“工业物联网”,能返回多来源结果
- 前台搜索返回
route - 编辑搜索返回
editRoute - 标题命中排在正文命中前面
- 空结果时返回空分页对象
16.2 增量同步测试
- 新增页面后立即可搜
- 修改文档标题后立即可搜到新标题
- 删除菜单后搜索不再命中
16.3 重建测试
- 执行
/portal/search/admin/rebuild - 清空索引表后重新生成
- 重建后结果数与原业务数据量匹配
16.4 性能测试
- 对比旧版
UNION ALL + LIKE - 高频关键词响应时间明显下降
- 数据量增长后查询性能仍可接受
17. 风险与注意事项
17.1 风险点
- 当前仓库还未引入 EF Core 依赖
- 当前本机没有 .NET SDK,暂时不能做真实编译验证
- MySQL FULLTEXT 对中文分词能力有限
- 如果业务文本大量是 JSON,必须先清洗文本,否则全文检索效果很差
17.2 风险应对
- 先保留旧搜索实现作为
legacy回退 - 搜索文档始终存“清洗后的文本”
- 先做功能正确,再做 MATCH AGAINST 优化
- 若后续中文搜索质量不够,再评估:
- ngram parser
- ES
- PostgreSQL + 中文分词
18. 当前不做的内容
本次方案明确不做:
- 向量搜索
- embedding
- 大模型召回
- Elasticsearch
- 把整个
hw-portal全量迁到 EF Core
19. 当前建议的实际落地文件清单
19.1 必改
- Startup.cs
- HwSearchController.cs
- HwSearchAdminController.cs
- HwWebService.cs
- HwWeb1Service.cs
- HwWebMenuService.cs
- HwWebDocumentService.cs
- HwPortalConfigTypeService.cs
19.2 建议新增
Admin.NET\Plugins\Admin.NET.Plugin.HwPortal\SearchEf\HwPortalSearchDbContext.csAdmin.NET\Plugins\Admin.NET.Plugin.HwPortal\SearchEf\Entity\HwPortalSearchDoc.csAdmin.NET\Plugins\Admin.NET.Plugin.HwPortal\SearchEf\Option\HwPortalSearchOptions.csAdmin.NET\Plugins\Admin.NET.Plugin.HwPortal\SearchEf\Service\IHwSearchIndexService.csAdmin.NET\Plugins\Admin.NET.Plugin.HwPortal\SearchEf\Service\HwSearchIndexService.csAdmin.NET\Plugins\Admin.NET.Plugin.HwPortal\SearchEf\Service\HwSearchQueryService.csAdmin.NET\Plugins\Admin.NET.Plugin.HwPortal\SearchEf\Service\HwSearchRebuildService.csAdmin.NET\Admin.NET.Application\Configuration\Search.json
20. 总结
当前 hw-portal 的搜索还不是“搜索引擎”,本质上仍是业务表上的 SQL 模糊匹配。
本次方案的核心不是“把 LIKE 换成 EF”,而是:
- 建独立搜索索引表
- 用 EF Core 10 管理搜索索引子系统
- 用 MySQL FULLTEXT + LIKE 兜底做普通搜索引擎
- 保持现有接口和返回结构不变
- 把空的索引重建能力变成真正可用的全量/增量索引能力
如果后续你要继续深化,我建议下一份文档再单独写:
- 《hw-portal 搜索表 SQL 脚本与迁移方案》
- 《hw-portal 搜索改造详细代码实施清单》
- 《hw-portal 搜索测试用例与验收清单》
最自律帅气聪明的臧辰浩