feat: enforce youth mode policy and improve search

This commit is contained in:
richjohnrambo
2026-09-18 14:51:40 +08:00
parent 9482bb19f6
commit 8b20118570
13 changed files with 567 additions and 70 deletions
+227
View File
@@ -0,0 +1,227 @@
# IM 剩余问题修复方案
整理时间:2026-09-18
本文记录当前暂未完成的三个后端问题:青少年模式业务限制、Web 平台版本检查、消息搜索召回稳定性。
更新记录:
- 2026-09-18:P1 第一阶段已处理 Web 平台版本检查与消息搜索召回兜底;青少年模式已完成第一阶段策略层、夜间通话限制和消息内容过滤接入,剩余支付守护、好友/群邀请、Push 等全链路限制继续按 P2 增强。
## 1. 青少年模式业务限制未强制执行
### 现状
当前后端已经具备青少年模式的基础状态能力:
- `GET /v1/im/settings/youth-mode` 可返回 `enabled`、`policy_version`、`restrictions`、`restriction_items`。
- `POST /v1/im/settings/youth-mode/enable` 和 `disable` 已完成开启、关闭和独立密码校验链路。
- `im_youth_mode_settings` 已保存用户维度的启用状态、策略版本、限制项和密码状态。
第一阶段已将 `night_quiet` 接入音视频呼叫发起限制,将 `content_filter` 接入消息发送前内容安全审核,并把这两项 `restriction_items[].enforced` 改为 `true`。`payment_guard` 以及好友申请、群邀请、系统通知/Push 等更大范围入口仍未完整接入。
### 问题影响
如果只保存开关、不做服务端限制,客户端隐藏入口不能形成安全边界。用户可以通过旧客户端、重新安装、直接调用接口或构造 WebSocket 消息绕过限制。
### 建议修复方案
建议将青少年模式拆成一个统一策略检查层,例如 `imYouthPolicy` 或 `impolicy/youth`,所有关键入口调用同一套判断逻辑。
第一阶段建议先落这几个限制:
| 限制项 | 建议拦截点 | 修复方式 |
| --- | --- | --- |
| `night_quiet` 深夜免打扰 | 发消息、音视频呼叫、系统通知/Push | 服务端读取用户青少年状态和当前时间段,限制指定时段内发起呼叫或降低通知等级。 |
| `content_filter` 内容过滤 | 发文本、发链接、上传后发布文件、资料/群资料修改 | 复用现有内容安全/风险审核能力;命中高风险时拒绝或进入审核状态。 |
| `payment_guard` 支付守护 | 充值、购买、付费功能入口 | 当前 IM 暂无支付主链路,先预留统一错误码和策略接口,后续支付接口接入时强制调用。 |
建议新增统一返回:
```json
{
"code": 30005,
"reason": "YOUTH_MODE_RESTRICTED",
"message": "restricted by youth mode",
"details": {
"restriction": "night_quiet",
"policy_version": 1
}
}
```
### 建议改造点
- `server/im_http.go`
- HTTP 发消息、转发、收藏转发、好友申请、群邀请、音视频相关接口前置检查。
- `youthModeRestrictionItems` 中已真正接入的规则将 `enforced` 改为 `true`。
- `server/topic.go`
- 原生 WebSocket `{pub}` 保存消息前执行相同策略,避免绕过 HTTP。
- `server/store/store.go`
- 增加按用户读取青少年模式的轻量缓存,短 TTL 即可。
- `docs/IM-青少年模式前后端对接文档.md`
- 补充哪些接口会返回 `YOUTH_MODE_RESTRICTED`。
### 验收建议
- 开启青少年模式后,旧客户端直接发消息不能绕过限制。
- HTTP 和 WebSocket 两条链路限制结果一致。
- 关闭青少年模式后限制即时解除。
- `restriction_items[].enforced` 只对已经服务端强制执行的规则返回 `true`。
### 当前处理状态
已新增统一策略判断 `imYouthModePolicyDecision`。当前行为:
- `night_quiet`:青少年模式开启且带该限制时,22:00-06:00 发起音视频呼叫返回 `YOUTH_MODE_RESTRICTED`。
- `content_filter`:普通 WS 消息发送前进入内容安全审核,文本、文件名、链接 URL 等可展示内容会参与提取。
- `payment_guard`:仍为预留项,返回 `enforced=false`,后续支付或付费功能接入时再强制调用。
## 2. Web 平台版本检查接口缺失
### 现状
当前版本检查接口为:
```http
GET /v1/im/app/releases/latest?platform=ios|android&current_version=1.0.0&channel=stable
```
当前实现和文档主要覆盖 `ios`、`android`。Web 客户端如果调用 `platform=web`,会因为平台枚举未支持而无法得到可用版本配置。
### 问题影响
Web 端无法统一读取版本策略、升级提示、最低支持版本和发布说明。后续如果 Web 端需要灰度、强刷、维护提示或版本兼容控制,只能靠前端硬编码。
### 建议修复方案
将 `web` 加入 App Release 平台枚举。平台建议统一为:
```text
ios, android, web
```
Web 响应建议保持与移动端同结构:
```json
{
"current_version": "1.0.0",
"has_update": false,
"force_update": false,
"platform": "web",
"channel": "stable",
"latest_version": "1.0.0",
"min_supported_version": "1.0.0",
"update_type": "none",
"package_url": "",
"store_url": "",
"release_notes": "当前已是最新版本。",
"remind_after_seconds": 0
}
```
Web 没有安装包时,`package_url` 和 `store_url` 可以为空;如果需要强刷或维护提示,可通过 `update_type=required` 和 `release_notes` 传达。
### 建议改造点
- `server/im_http.go`
- 放开 `platform=web` 参数校验。
- 保持 `current_version` 必传。
- `server/db/mysql/adapter.go` 或 App Release 相关存储实现
- 确认 `im_app_releases.platform` 没有限制 `web`。
- 如数据库通过字典/配置初始化版本记录,需要补默认 Web 记录。
- `docs/IM-App版本更新前后端对接文档.md`
- 补充 Web 平台示例。
- `docs/IM-前后端对接文档.md`
- 将 `platform=ios|android` 更新为 `ios|android|web`。
### 验收建议
- `platform=web&current_version=1.0.0&channel=stable` 返回 200。
- `platform=invalid` 仍返回参数错误。
- Web 无更新、推荐更新、强制更新三类数据都能按同一结构返回。
### 当前处理状态
已在 `serveIMAppReleaseLatest` 放开 `platform=web`,并补充 Web 平台单元测试。对接文档已同步更新为 `ios|android|web`。
## 3. 消息搜索召回稳定性待复核
### 现状
当前接口为:
```http
GET /v1/im/topics/{topic}/messages/search?q=&type=text&cursor=
GET /v1/im/topics/{topic}/messages/search?type=file&q=&cursor=
```
现有后端已经能返回 `seq`、`from`、`ts`、`snippet`、`matches`、`snapshot`、`availability` 等字段。但问题清单中仍记录了“部分明显应该命中的关键词搜索返回空结果”的现象。该问题目前属于待复核项,可能来源包括:
- 搜索范围只覆盖部分消息内容字段,没有覆盖所有文本来源。
- 结构化消息中正文可能存在于 `content.text`、`content.txt`、`content.title`、`content.summary`、Drafty 或文件快照字段中,索引/过滤口径不一致。
- 中文分词、大小写、空格、标点归一化不一致。
- 分页游标与过滤顺序不一致,导致命中项被跳过。
- 热消息、分表消息和历史消息的查询路径不一致。
### 问题影响
搜索结果不稳定会直接影响聊天记录检索、文件检索、收藏定位和问题复盘。用户明知道某条消息存在,但搜索不到,会被认为是数据丢失。
### 建议修复方案
第一阶段先做“确定性兜底”,不急于引入复杂搜索引擎:
1. 明确搜索字段规范。
- 文本消息:统一提取 `content.text`、`content.txt`、`content.content`、`content.title`、`content.summary`、字符串 content。
- 文件消息:统一提取文件名、mime、文件说明、消息文本。
- 链接/名片/合并转发:提取标题、摘要、展示文本。
2. 增加 `message_search_text` 生成函数。
- 写入消息时生成可搜索文本。
- 老数据可查询时动态 fallback。
3. 查询顺序固定。
- 先按用户权限和 topic 过滤可见消息。
- 再按搜索文本匹配。
- 最后按 `seq desc` 或 `created_at desc` 分页。
4. 中文和符号归一化。
- 至少做大小写归一、前后空格、连续空白压缩、全角半角基础处理。
5. 增加回归测试。
- 普通文本、中文关键词、文件名、链接标题、转发快照、撤回/删除消息、分页边界都要覆盖。
第二阶段如果数据量和体验要求更高,再考虑引入专用搜索索引:
- MySQL FULLTEXT/ngram。
- OpenSearch/Elasticsearch。
- 独立 `im_message_search_index` 表,按 topic、seq、search_text 存储轻量索引。
### 建议改造点
- `server/im_http.go`
- `messageText` 和 `messageSearchPayload` 的提取逻辑继续收口,保证响应与查询使用同一文本口径。
- `server/db/mysql/adapter.go` 或消息查询实现
- 检查 `TenantMessageSearch` 的 SQL 过滤条件和分页顺序。
- 确认群消息分表 `im_group_messages_YYYYMM` 与私聊消息表搜索路径一致。
- `server/store/types`
- 如新增搜索索引表,补结构体和迁移 SQL。
- `docs/IM-前后端对接文档.md`
- 明确搜索匹配字段、排序规则和 `availability` 行为。
### 验收建议
- 同一 topic 下创建 20 条混合消息,逐条用关键词可搜到。
- 搜索中文短词、英文大小写、文件名、链接标题均有稳定结果。
- 翻页时不丢边界记录、不重复。
- 被删除/撤回消息按 `availability` 返回或按产品规则隐藏,行为一致。
- 私聊和群聊搜索结果字段一致。
### 当前处理状态
已完成第一阶段确定性兜底:私聊和群聊搜索不再只依赖 SQL `LIKE` 命中消息 JSON 原文,改为先按权限、Topic、类型和游标读取固定窗口,再对结构化文本做统一提取和归一化匹配。当前覆盖文本、文件名、标题、摘要、链接、卡片和转发快照等字段,并补充结构化内容召回测试。
## 建议排期
| 优先级 | 项目 | 原因 |
| --- | --- | --- |
| P1 | Web 平台版本检查 | 改动小,边界清楚,适合快速补齐。 |
| P1 | 消息搜索召回复核 | 影响用户感知,需要先补测试锁定问题。 |
| P2 | 青少年模式业务限制 | 涉及多个入口和策略口径,需要独立设计和联调,不能只改一个接口。 |