# search_server **Repository Path**: wfchat/search_server ## Basic Information - **Project Name**: search_server - **Description**: 消息搜索服务 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-26 - **Last Updated**: 2026-08-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # wf-search-server 野火IM Web 端 · 会话内消息搜索服务(Spring Boot)。 Web 客户端(vue-chat)无本地存储,本地搜索不可用。本服务**只读访问 IM 服务端数据库**,为 Web 端提供会话内消息搜索(关键字 / 消息类型 / 时间),认证复用 IM 应用体系(authCode,强制)。**支持 MySQL / PostgreSQL / 金仓 KingbaseES / 达梦 DM / MongoDB** 五种数据源(`search.db.type` 切换)。设计文档见 [docs/服务器搜索-产品交互设计.md](docs/服务器搜索-产品交互设计.md),**接口文档见 [docs/API.md](docs/API.md)**。 ## 特性 - **Spring Boot 2.7**,参考 `wf-poll-server` 的 authCode 认证(`wfc.getAuthCode('admin', 2, host, ...)` → 请求头 `authCode` → 服务端 `applicationGetUserInfo` 换取 userId),**认证为强制项,无关闭开关**; - **多数据源可插拔**:JDBC 方言层(`JdbcDialect`:MySQL / PostgreSQL 与金仓共用 / 达梦)+ MongoDB 实现,`search.db.type=mysql|pgsql|kingbase|dm|mongodb` 切换,业务层(扩散路由/归并/上下文/OutputMessageData)与数据源无关; - **OpenAPI 3 文档**(默认关闭):配置 `springdoc.swagger-ui.enabled=true` 开启 `/swagger-ui/index.html`; - **按用户限流**:滑动窗口,`search.rate-limit.per-minute`(默认 60 次/分钟),超限 429; - **仅会话内消息搜索**:关键字(`_searchable_key`)、消息类型(`_content_type`,服务端透传 int)、发送人、时间范围(`_mid` 区间)组合检索; - **扩散类型路由**:单聊/普通群(写扩散)→ `t_user_messages_{hash(uid)}` 归属表;超级群(读扩散)→ `t_group_messages_{hash(gid)%128}`(与 IM Server 一致,默认分表;内嵌 H2 部署为单表 `t_group_messages`);超级群显式成员校验; - **消息上下文**:锚点 ±N 条真实连续前后消息(不过滤,命中标记 `isHit`)+ 上一处/下一处命中; - **按月顺延查询**:按 mid 定位锚点所在月表,先查当月表,不足条数再向相邻月表顺延(凑够即停); - **关键字检索策略按数据源**:MySQL 走 ngram 全文索引(MATCH)/单字 LIKE;PostgreSQL/金仓/达梦/MongoDB 走"LIKE + 应用层内存过滤"(零全文索引依赖,部署简单); - **中文分词(jieba)**:LIKE / 内存过滤路径下,关键词先经 jieba 切分为词元,按**词元 AND 匹配**(如搜"部署文档"可命中"…部署…文档…"分离出现的消息),MySQL ngram 路径本身即分词;摘要高亮优先命中完整关键词、其次各词元; - **游标分页**(`_mid < cursorMid`,时间倒序)、digest 高亮(服务端转义防 XSS)、**64 位 mid 以字符串传输**(JS Number 无法精确表示 > 2^53 的 long)。 ## 部署 ### 1. 前置条件 - IM Server(专业版)已部署,数据库为 **MySQL / PostgreSQL / 金仓 / 达梦 / MongoDB** 之一(与 `search.db.type` 对应); - **IM Server 未对 `_searchable_key` 加密**(本服务仅支持未加密明文); - 创建只读数据库账号(可选): ```sql CREATE USER 'wf_search_ro'@'%' IDENTIFIED BY 'password'; GRANT SELECT ON wildfirechat.* TO 'wf_search_ro'@'%'; FLUSH PRIVILEGES; ``` > 上面sql语句中的 password 替换为有效的密码. ### 2. 数据库索引 **MySQL 模式(search.db.type=mysql,必须执行)**——IM 服务本身没有以下两个索引: ```bash mysql -u root -p wildfirechat < scripts/create_ft_index.sql # 36 张消息表全文索引(ngram) mysql -u root -p wildfirechat < scripts/create_indexes.sql # 128 张归属表组合索引 ``` **PostgreSQL / 金仓 / 达梦 模式(search.db.type=pgsql/kingbase/dm)**——执行归属表组合索引(关键字检索走 LIKE + 内存过滤,无需全文索引): ```bash psql -U -d wfchat < scripts/create_indexes_pg.sql ``` **MongoDB 模式(search.db.type=mongodb)**——IM 服务在 MongoDB 中的检索索引**已建立**(归属表 `{_uid,_type,_target,_line,_mid}`、消息表 `_id` 主键、群消息表 `{_gid,_line,_seq,_dt}`),**无需任何脚本**。 > ⚠️ **mongodb 模式必须同时配置 `search.db.*`(关系库)**:IM Server 开启 MongoDB(`db.type=2` / `db.save_messages_in_mongodb=true`)时,MongoDB 里**只有消息集合**(`t_messages_*` / `t_user_messages_*` / `t_group_messages_*`),而 `t_group` / `t_group_member` / `t_user` 仍在 MySQL 等关系库中(`db.type=2` 会同时置 `UseMongoDB` 与 `UseMySQL`)。本服务据此把元数据查询固定走 JDBC。 ### 3. 配置与启动 编辑 `src/main/resources/application.properties`: | 配置 | 说明 | | --- | --- | | `im.server.admin-url` | IM Server admin 地址(如 `http://localhost:18080`) | | `im.server.admin-secret` | IM Server admin secret | | `search.db.type` | 数据源:`mysql`(默认)/ `pgsql` / `kingbase` / `dm` / `mongodb` | | `search.db.url` | JDBC 地址(**所有模式必填**,含 mongodb 模式——元数据始终在关系库;驱动按 url 前缀自动探测:jdbc:mysql://、jdbc:postgresql://、jdbc:kingbase8://、jdbc:dm://) | | `search.db.username/password` | 数据库账号 | | `search.mongo.uri` / `search.mongo.database` | MongoDB 连接(mongodb 模式,仅消息;元数据仍走 `search.db.*`) | | `search.rate-limit.per-minute` | 每用户每分钟最大请求数(默认 60) | | `springdoc.api-docs.enabled` / `springdoc.swagger-ui.enabled` | Swagger 文档开关(默认 false,联调可开启) | | `search.keyword.fulltext-min-length` | 短词退化为 LIKE 的阈值(默认 2,MySQL ngram 场景) | | `search.group-message-table.sharded` | 超级群消息表是否分表(默认 `true` = `t_group_messages_{hash(gid)%128}`,与 IM Server 一致;仅内嵌 H2 部署置 `false`) | ```bash mvn -q package java -jar target/wf-search-server-1.0.0.jar ``` ### 4. 验证 ```bash # 健康检查(免鉴权) curl http://localhost:8890/api/health # {"code":0,"message":"ok","data":"ok"} # 会话内搜索(authCode 由 Web 端 wfc.getAuthCode 获取) curl -X POST http://localhost:8890/api/search/conversation/messages \ -H 'Content-Type: application/json' \ -H 'authCode: ' \ -d '{"conversation":{"type":1,"target":"gid123","line":0},"keyword":"部署","size":20}' ``` ## 接口 完整契约见 [docs/API.md](docs/API.md)。 | 方法 | 路径 | 说明 | | --- | --- | --- | | POST | `/api/search/conversation/messages` | 会话内消息搜索(关键字/类型/发送人/时间,游标分页) | | POST | `/api/search/conversation/messages/context` | 消息上下文(锚点 ±N 条 + 上一处/下一处;上下滚动分页复用本接口) | | GET | `/api/health` | 健康检查(免鉴权) | 文件搜索不在此服务实现,前端复用现有 `searchFiles` / `searchMyFiles` 接口。 ## 前端接入(vue-chat,已实现) 1. `src/config.js`:配置 `SEARCH_SERVER`(默认 `http://localhost:8083`); 2. `src/api/searchServerApi.js`:authCode 封装(`wfc.getAuthCode('admin', 2, host, ...)`); 3. 入口:单聊/群聊会话信息页 → "查找聊天记录" → 会话内搜索页(`/conversation-search`,屏幕中央弹窗); 4. 结果"定位" → 消息上下文页(`/message-context`:锚点居中、上一处/下一处、上下滚动浏览时间线、消息长按/右键菜单); 5. 搜索服务已配置 CORS(允许任意来源 + `authCode` 头),Web 端可跨域直连。 ## 目录结构 ``` search_server/ ├── docs/ 设计文档(PRD)与 API 文档 ├── scripts/ 数据库索引运维脚本 ├── src/ │ ├── lib/ 本地依赖 jar(野火 IM SDK、达梦 DmJdbcDriver8) │ └── main/java/cn/wildfirechat/search/ │ ├── config/ IM 配置 / SDK 初始化 / 只读数据源 / 搜索配置 / CORS / OpenAPI │ ├── filter/ AuthFilter(认证)/ AuthValidator(校验器)/ RateLimitFilter(限流) │ ├── util/ 分表工具 / 扩散路由(含单聊 target 归一) │ ├── dao/ 检索 DAO(JDBC 方言:MySQL/PG/金仓/达梦 + MongoDB)/ 过滤条件构建 │ ├── service/ 检索编排(扩散路由、按月顺延查询、游标、上下文) │ └── controller/ REST 接口 └── pom.xml ``` ## 说明 ### 参考实现声明 - **本服务仅供参考实现**:检索能力基于直接读取 IM 服务端数据库构建,可在此基础上**二次开发对接 Elasticsearch 等其他检索服务**(`MessageSearchDao` / `MetaDataAccessor` 接口已按数据源可插拔设计,新增 ES 实现即可平滑替换); - **如遇问题可自行调试解决**:服务提供 `[CTX]` / `[CTX-DAO]` / `[Mongo]` 前缀的 debug 日志、Swagger 开关(`springdoc.swagger-ui.enabled=true`)与冒烟自检工具(`JdbcSmoke` / `MongoSmoke`,见下)。 ### 各数据源驱动来源 | 数据源 | 驱动 | 来源与说明 | | --- | --- | --- | | MySQL | `mysql-connector-j` | Maven 中央仓库,自动引入 | | PostgreSQL | `postgresql` | Maven 中央仓库,自动引入 | | 金仓 KingbaseES | `kingbase8-8.6.1.jar` | **不在 Maven 中央仓库**,需自行安装到本地/私有仓库后构建:`mvn install:install-file -Dfile=kingbase8-8.6.1.jar -DgroupId=cn.com.kingbase -DartifactId=kingbase8 -Dversion=8.6.1 -Dpackaging=jar` | | 达梦 DM | `DmJdbcDriver8.jar`(8.1.3.162) | 取自 `im-app_server/src/lib`,已随项目放入 `src/lib/`(system scope,打包时打入 fat jar,无需额外安装) | | MongoDB | `spring-boot-starter-data-mongodb` | Maven 中央仓库,自动引入 | | 中文分词 | `jieba-analysis`(1.0.2) | Maven 中央仓库,自动引入;LIKE/内存过滤路径的关键词切词依赖 | > 驱动类与 JDBC url 前缀对应:`jdbc:mysql://`、`jdbc:postgresql://`、`jdbc:kingbase8://`(金仓兼容 PG 协议)、`jdbc:dm://`(达梦)。 ### 部署注意事项 - **已实测环境**:MySQL 8、PostgreSQL 18、金仓 KingbaseES V8R6、达梦 DM8 均已在真实库上跑通检索链路(含中文分词);MongoDB 模式在 docker mongo:6.0 上验证过(消息表 `_id` / 归属表 `_mid` 两段式查询);不同版本/部署形态仍有差异,正式部署前请用冒烟工具在目标库上自检(见下),如遇问题按日志自行排查; - **达梦标识符大小写**:无引号标识符在达梦中统一转为**大写**(PostgreSQL 转小写、MySQL 大小写不敏感)——IM 在达梦上的建表同样**无引号**(表存在探测已按大写适配 `TABLE_NAME = UPPER(?)`);达梦表存在探测走 `ALL_TABLES` / `USER_TABLES`; - **达梦 `IF NOT EXISTS`**:`scripts/create_indexes_pg.sql` 中的 `CREATE INDEX IF NOT EXISTS` 达梦(默认兼容模式)不支持,需手工去掉后执行; - **PG 非默认模式**:表不在 `public` 模式时,JDBC url 需带 `?currentSchema=<模式名>`(如 `jdbc:postgresql://host:5432/wfchat?currentSchema=wfchat`); - **关键字检索策略按数据源**:PostgreSQL/金仓/达梦/MongoDB 为"归属表索引收敛 + LIKE(jieba 分词词元 AND)+ 应用层内存过滤"(零全文索引依赖),**必须执行对应归属表组合索引脚本**(见「数据库索引」),否则候选集退化全表扫描;MySQL 走 ngram 全文索引,短词自动退化 LIKE; - **`_searchable_key` 仅支持未加密**:若 IM Server 开启搜索字段加密,本服务不支持,需关闭加密或另行解密方案; - **超级群驱动表必须与 IM Server 路由一致**:IM Server 的 `MessageShardingUtil.getGroupMessageTable(gid)` 在 MySQL/PG/金仓/达梦/MongoDB 部署下写入 `t_group_messages_{|gid.hashCode()|%128}`(本服务默认,`search.group-message-table.sharded=true`),仅内嵌 H2 写入单表 `t_group_messages`。**建表脚本会同时建出单表与 128 张分表**,所以配错时表存在但恒为空,超级群搜索会静默返回 0 条(单聊/普通群不受影响); - **IM Server 关闭分表(`disable_message_sharding=true`)不受支持**:此时 IM 写入 `t_messages_0` / `t_user_messages_0` / `t_group_messages_0`,与本服务的按月/按哈希路由均不一致。 ### 冒烟自检工具(dev 包,非测试代码) ```bash # JDBC 检索链路自检(MySQL/PG/金仓/达梦通用) # 参数:jdbc url、用户名、密码、[方言 mysql|pgsql|kingbase|dm]、[关键字] # 方言缺省按 url 前缀自动探测;自动扫描非空分表并取样一条真实会话,无需手工指定 mvn -q compile exec:java -Dexec.classpathScope=compile \ -Dexec.mainClass=cn.wildfirechat.search.dev.JdbcSmoke \ -Dexec.args="jdbc:postgresql://:/wfchat pgsql 欢迎" # 也可显式指定会话与表(跳过自动探测): # args = url user pwd [dialect] [keyword] [uid type target line userTable msgTable] mvn -q compile exec:java -Dexec.classpathScope=compile \ -Dexec.mainClass=cn.wildfirechat.search.dev.JdbcSmoke \ -Dexec.args="jdbc:dm://:5236 WFCHAT 123456 dm - uid 0 target 0 t_user_messages_78 t_messages_19" # MongoDB 检索链路自检(参数:mongo uri、数据库名) mvn -q compile exec:java -Dexec.classpathScope=compile \ -Dexec.mainClass=cn.wildfirechat.search.dev.MongoSmoke \ -Dexec.args="mongodb://:@:27017/wfchat wfchat" ``` > 注意: > - `exec:java` 默认 runtime classpath **不含 system scope**(IM SDK proto 类),**必须带 `-Dexec.classpathScope=compile`**; > - 两个工具均为**独立 main、无 Spring 上下文依赖**,直接连库调用 DAO 验证:表存在性探测、归属表收敛、浏览态、关键字(LIKE/全文)、**中文分词(jieba 词元 AND 匹配)**、锚点上下文、上一处/下一处命中; > - PG 表在非默认模式时,url 需带 `?currentSchema=`(如 `jdbc:postgresql://host:5432/wfchat?currentSchema=wfchat`);达梦表存在探测已按大写表名适配(`TABLE_NAME = UPPER(?)`)。