feat: enforce youth mode policy and improve search
This commit is contained in:
@@ -2,11 +2,12 @@
|
||||
|
||||
## 1. 适用范围
|
||||
|
||||
本文档用于 iOS、Android 客户端检查新版本,以及执行推荐更新和强制更新。
|
||||
本文档用于 iOS、Android、Web 客户端检查新版本,以及执行推荐更新和强制更新。
|
||||
|
||||
- 版本策略为全局配置,存储在 `hotgo.im_app_releases`,不区分租户。
|
||||
- iOS 安装包由 App Store 托管,客户端通过 `store_url` 跳转。
|
||||
- Android APK 由发布人员上传到专用 OSS 发布目录,客户端通过 `package_url` 下载。
|
||||
- Web 端没有安装包时,`store_url` 和 `package_url` 可以为空,使用 `update_type`、`min_supported_version` 和 `release_notes` 控制强刷、兼容提醒或维护提示。
|
||||
- 客户端不直接访问数据库,也不自行拼接 OSS 地址。
|
||||
|
||||
## 2. 检查版本接口
|
||||
@@ -22,14 +23,14 @@ GET https://api.118im.com/v1/im/app/releases/latest
|
||||
| Header | 必填 | 示例 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `X-IM-Schema` | 是 | `1` | MeetingIM HTTP 协议版本 |
|
||||
| `X-Client-Platform` | 建议 | `ios` / `android` | 客户端平台 |
|
||||
| `X-Client-Platform` | 建议 | `ios` / `android` / `web` | 客户端平台 |
|
||||
|
||||
### 2.2 Query 参数
|
||||
|
||||
| 参数 | 必填 | 示例 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `apikey` | 是 | `AQAAAA...` | 应用 API Key |
|
||||
| `platform` | 是 | `android` | 仅支持 `ios`、`android` |
|
||||
| `platform` | 是 | `android` | 仅支持 `ios`、`android`、`web` |
|
||||
| `current_version` | 是 | `1.2.0` | 当前版本,使用数字点分格式 |
|
||||
| `channel` | 否 | `stable` | 发布渠道,默认 `stable` |
|
||||
|
||||
@@ -190,7 +191,7 @@ shasum -a 256 meetingim-1.3.0.apk
|
||||
| --- | --- |
|
||||
| 400 | 缺少 `X-IM-Schema: 1` |
|
||||
| 400 | 缺少或错误的 `current_version` |
|
||||
| 400 | `platform` 不是 `ios` 或 `android` |
|
||||
| 400 | `platform` 不是 `ios`、`android` 或 `web` |
|
||||
| 500 | Admin 版本配置缺失、格式错误或内部配置接口不可用 |
|
||||
|
||||
## 9. 当前测试环境
|
||||
@@ -203,4 +204,4 @@ API Key: AQAAAAABAAAoeOI7tA3HsYvdzDhYhZJy
|
||||
默认渠道: stable
|
||||
```
|
||||
|
||||
当前测试库已配置 iOS、Android `1.0.0` 默认策略。正式验证 Android 更新前,需要先上传测试 APK,并在 Admin 填写对应 `package_url` 与 SHA-256。
|
||||
当前测试库已配置 iOS、Android、Web `1.0.0` 默认策略。正式验证 Android 更新前,需要先上传测试 APK,并在 Admin 填写对应 `package_url` 与 SHA-256。
|
||||
|
||||
+7
-6
@@ -998,7 +998,7 @@ X-MeetingIM-APIKey: <预置的普通 API Key>
|
||||
| `POST /v1/im/auth/password-reset/confirm` | `{"reset_token":"reset_token_123","new_password":"NewPass123"}` | `{"status":"ok"}` |
|
||||
| `POST /admin/v1/tenant/users/{user_id}/reset-password` 管理后台 | `{"new_password":"AdminReset123","reason":"用户无手机号无法自助找回"}` | `{"user_id":"usr...","status":"reset","auth_version":2}` |
|
||||
| `POST /v1/im/auth/logout` | `{}` | `{"status":"ok"}` |
|
||||
| `GET /v1/im/app/releases/latest?platform=ios\|android¤t_version=1.0.0&channel=stable` | Query only | `{"current_version":"1.0.0","has_update":true,"force_update":false,"platform":"ios","channel":"stable","latest_version":"1.1.0","min_supported_version":"1.0.0","update_type":"recommended","package_url":"","store_url":"https://apps.apple.com/app/id...","release_notes":"修复已知问题。","remind_after_seconds":86400}` |
|
||||
| `GET /v1/im/app/releases/latest?platform=ios\|android\|web¤t_version=1.0.0&channel=stable` | Query only | `{"current_version":"1.0.0","has_update":true,"force_update":false,"platform":"web","channel":"stable","latest_version":"1.1.0","min_supported_version":"1.0.0","update_type":"recommended","package_url":"","store_url":"","release_notes":"修复已知问题。","remind_after_seconds":86400}` |
|
||||
|
||||
## 2. 消息首页、全局导航与系统通知
|
||||
|
||||
@@ -2331,6 +2331,7 @@ GET /v1/im/topics/{topic}/message-mutations?since_version={localMutationVersion}
|
||||
- `GET /v1/im/topics/{topic}/messages/search?q=&type=text&cursor=` 返回 `seq,from,ts,snippet,matches[]`。
|
||||
- 点击结果后 `GET /v1/im/topics/{topic}/messages/{seq}/context?before=20&after=20`,返回锚点及上下文;客户端定位 `seq` 并高亮。
|
||||
- 服务端只搜索当前用户可见且未硬删除的消息,关键词做长度/频率限制,搜索日志不得记录原文。
|
||||
- 搜索匹配字段包括文本正文、`content.text/txt/content/title/summary/name`、文件名、链接标题/摘要、卡片/转发快照等可展示文本;排序按 `seq desc`,`next_cursor` 继续向更早消息翻页。
|
||||
|
||||
### 7.13 查找聊天记录-文件.png
|
||||
|
||||
@@ -3002,7 +3003,7 @@ GET /v1/im/topics/{topic}/message-mutations?since_version={localMutationVersion}
|
||||
|
||||
完整接口与 Android OSS APK 对接流程见:[IM-App版本更新前后端对接文档](./IM-App版本更新前后端对接文档.md)。
|
||||
|
||||
- `GET /v1/im/app/releases/latest?platform=ios|android¤t_version=1.0.0&channel=stable`,全局公共接口,不依赖租户解析。`current_version` 必传,使用数字点分版本号(如 `1.2.0`)。
|
||||
- `GET /v1/im/app/releases/latest?platform=ios|android|web¤t_version=1.0.0&channel=stable`,全局公共接口,不依赖租户解析。`current_version` 必传,使用数字点分版本号(如 `1.2.0`)。
|
||||
- 服务端返回 `has_update`、`force_update` 和 `update_type=none|recommended|force`;iOS 客户端使用 `store_url` 跳转 App Store。
|
||||
- Android 客户端使用 `package_url` 下载 OSS/CDN 上的 APK,并在安装前使用 `checksum` 完成 SHA-256 校验;强制更新时不得绕过进入主业务。
|
||||
- 版本策略由 Admin 在 `hotgo.im_app_releases` 统一管理,IM Server 通过 HMAC 内部配置接口读取,不从租户库查询。
|
||||
@@ -3044,7 +3045,7 @@ GET /v1/im/topics/{topic}/message-mutations?since_version={localMutationVersion}
|
||||
|
||||
**状态:新增。**
|
||||
|
||||
- `GET /v1/im/settings/youth-mode` 返回 `enabled,policy_version,restrictions,restriction_items,password_set,can_enable`。当前默认策略项为 `night_quiet`、`content_filter`、`payment_guard` 三条;后端现阶段完成状态持久化、开启/关闭校验和策略返回,业务接口层的深夜免打扰、内容过滤、支付守护尚未实际强制执行,`restriction_items[].enforced=false` 用于明确表达这一点。
|
||||
- `GET /v1/im/settings/youth-mode` 返回 `enabled,policy_version,restrictions,restriction_items,password_set,can_enable`。当前默认策略项为 `night_quiet`、`content_filter`、`payment_guard` 三条;后端现阶段完成状态持久化、开启/关闭校验和策略返回;`night_quiet` 已接入音视频呼叫发起限制,`content_filter` 已接入消息发送前内容安全审核,二者返回 `enforced=true`;`payment_guard` 仍为预留项,返回 `enforced=false`。
|
||||
- 点击开启先展示 10.15,再按 1.7 完成身份验证和 1.11 设置独立密码;最后调用 `POST /v1/im/settings/youth-mode/enable`,Body `{"policy_version":n,"action_token":"..."}`,其中 `action_token` 使用设置独立密码响应里的 `enable_action_token`。
|
||||
- 开启链路中,短信校验返回的 `set_independent_password` token 只用于设置独立密码;设置密码成功响应中的 `enable_action_token` 才用于 `youth-mode/enable`。
|
||||
- 服务端持久化到用户租户范围并推送所有设备,不能仅设置本地标志。
|
||||
@@ -3065,7 +3066,7 @@ GET /v1/im/topics/{topic}/message-mutations?since_version={localMutationVersion}
|
||||
**状态:新增。**
|
||||
|
||||
- 页面调用 10.14,`enabled=true` 时展示 `enabled_at,restrictions`。`bootstrap` 同时返回受限模块和动作。
|
||||
- 后续某条规则真正接入业务拦截后,受限接口服务端返回 `YOUTH_MODE_RESTRICTED`,并把对应 `restriction_items[].enforced` 改为 `true`;前端隐藏只是体验优化,不能作为安全控制。
|
||||
- 受限接口服务端返回 `YOUTH_MODE_RESTRICTED`,当前已覆盖夜间音视频呼叫发起;前端隐藏只是体验优化,不能作为安全控制。
|
||||
- 登录、App 重启和另一设备均从服务端恢复状态;收到 `youth_mode.updated` 即刷新。
|
||||
|
||||
### 10.17 关闭青少年模式(安卓).png
|
||||
@@ -3134,10 +3135,10 @@ GET /v1/im/topics/{topic}/message-mutations?since_version={localMutationVersion}
|
||||
| `PATCH /v1/im/me/profile` | `{"nickname":"新昵称","signature":"新签名","version":1}` | `{"avatar_file_id":"","nickname":"新昵称","signature":"新签名","version":2,"updated_at":"2026-08-20T09:00:00Z"}` |
|
||||
| `GET /v1/im/me/privacy` | Query only | `{"add_me_methods":{"account":true,"phone":true,"qr":true,"group":true},"profile_visibility":"friends","last_seen_visibility":"friends","read_receipts":true,"friend_request_verification":true,"group_invite_verification":false,"version":1,"editable":{"add_me_methods":true,"profile_visibility":true,"last_seen_visibility":true,"read_receipts":true,"friend_request_verification":true,"group_invite_verification":true}}` |
|
||||
| `PATCH /v1/im/me/privacy` | `{"add_me_methods":{"phone":false},"read_receipts":false,"friend_request_verification":false,"group_invite_verification":true,"version":1}` | `{"add_me_methods":{"account":true,"phone":false,"qr":true,"group":true},"read_receipts":false,"friend_request_verification":false,"group_invite_verification":true,"version":2,"updated_at":"2026-08-20T09:00:00Z"}` |
|
||||
| `GET /v1/im/settings/youth-mode` | Query only | `{"enabled":false,"policy_version":1,"restrictions":["night_quiet","content_filter","payment_guard"],"restriction_items":[{"code":"night_quiet","title":"深夜免打扰","description":"夜间减少消息提醒打扰,帮助用户保持健康作息。","enforced":false},{"code":"content_filter","title":"内容过滤","description":"对不适合青少年的内容进行识别和限制展示。","enforced":false},{"code":"payment_guard","title":"支付守护","description":"限制或确认支付、充值等敏感消费行为。","enforced":false}],"password_set":false,"can_enable":false,"updated_at":"2026-08-20T09:00:00Z"}` |
|
||||
| `GET /v1/im/settings/youth-mode` | Query only | `{"enabled":false,"policy_version":1,"restrictions":["night_quiet","content_filter","payment_guard"],"restriction_items":[{"code":"night_quiet","title":"深夜免打扰","description":"夜间减少消息提醒打扰,帮助用户保持健康作息。","enforced":true},{"code":"content_filter","title":"内容过滤","description":"对不适合青少年的内容进行识别和限制展示。","enforced":true},{"code":"payment_guard","title":"支付守护","description":"限制或确认支付、充值等敏感消费行为。","enforced":false}],"password_set":false,"can_enable":false,"updated_at":"2026-08-20T09:00:00Z"}` |
|
||||
| `POST /v1/im/settings/independent-password` | `{"password":"1234","purpose":"youth_mode","action_token":"action_token"}` | `{"status":"ok","password_set":true,"purpose":"youth_mode","enable_action_token":"enable_token","enable_action_expires_at":"2026-08-20T09:05:00Z","next_action":"enable_youth_mode"}` |
|
||||
| `POST /v1/im/settings/independent-password/verify` | `{"password":"1234","purpose":"close_youth_mode"}` | `{"action_token":"action_token","action":"close_youth_mode","expires_at":"2026-08-20T09:05:00Z"}` |
|
||||
| `POST /v1/im/settings/youth-mode/enable` | `{"policy_version":1,"action_token":"enable_action_token"}` | `{"enabled":true,"policy_version":1,"password_set":true,"restrictions":["night_quiet","content_filter","payment_guard"],"restriction_items":[{"code":"night_quiet","title":"深夜免打扰","description":"夜间减少消息提醒打扰,帮助用户保持健康作息。","enforced":false},{"code":"content_filter","title":"内容过滤","description":"对不适合青少年的内容进行识别和限制展示。","enforced":false},{"code":"payment_guard","title":"支付守护","description":"限制或确认支付、充值等敏感消费行为。","enforced":false}],"updated_at":"2026-08-20T09:00:00Z"}` |
|
||||
| `POST /v1/im/settings/youth-mode/enable` | `{"policy_version":1,"action_token":"enable_action_token"}` | `{"enabled":true,"policy_version":1,"password_set":true,"restrictions":["night_quiet","content_filter","payment_guard"],"restriction_items":[{"code":"night_quiet","title":"深夜免打扰","description":"夜间减少消息提醒打扰,帮助用户保持健康作息。","enforced":true},{"code":"content_filter","title":"内容过滤","description":"对不适合青少年的内容进行识别和限制展示。","enforced":true},{"code":"payment_guard","title":"支付守护","description":"限制或确认支付、充值等敏感消费行为。","enforced":false}],"updated_at":"2026-08-20T09:00:00Z"}` |
|
||||
| `POST /v1/im/settings/youth-mode/disable` | Header `X-Action-Token: action_token`;Body `{}` | `{"enabled":false,"policy_version":1,"password_set":true,"updated_at":"2026-08-20T09:00:00Z"}` |
|
||||
| `POST /v1/im/security/challenges` 开启青少年模式 | Header `Idempotency-Key`;Body `{"scene":"set_independent_password","channel":"sms"}` | `{"challenge_id":"sec_chg_1","phone_mask":"+861****000","expires_in_seconds":300,"resend_after_seconds":60}` |
|
||||
| `POST /v1/im/security/challenges` 关闭青少年模式 | Header `Idempotency-Key`;Body `{"scene":"close_youth_mode","channel":"sms"}` | `{"challenge_id":"sec_chg_2","phone_mask":"+861****000","expires_in_seconds":300,"resend_after_seconds":60}` |
|
||||
|
||||
@@ -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¤t_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¤t_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 | 青少年模式业务限制 | 涉及多个入口和策略口径,需要独立设计和联调,不能只改一个接口。 |
|
||||
@@ -23,7 +23,7 @@ Idempotency-Key: <uuid>
|
||||
|
||||
测试环境验证码固定为 `123456`。`action_token` 为 5 分钟有效、单次消费,只能用于指定动作,不能在多个接口之间重复使用。
|
||||
|
||||
当前默认策略项为 `night_quiet`、`content_filter`、`payment_guard` 三条。现阶段后端已完成状态持久化、开启/关闭校验和策略返回;业务接口层的深夜消息免打扰、内容过滤、支付拦截尚未实际强制执行,响应中的 `restriction_items[].enforced=false` 用于明确表达这一点。后续某条规则真正接入后台策略和业务拦截后,应把对应项改为 `true`。
|
||||
当前默认策略项为 `night_quiet`、`content_filter`、`payment_guard` 三条。现阶段后端已完成状态持久化、开启/关闭校验和策略返回;`night_quiet` 已接入音视频呼叫发起限制,`content_filter` 已接入消息发送前内容安全审核,响应中的这两项 `restriction_items[].enforced=true`。`payment_guard` 当前仍为预留项,`enforced=false`。
|
||||
|
||||
## 2. 开启青少年模式
|
||||
|
||||
@@ -47,13 +47,13 @@ GET /v1/im/settings/youth-mode
|
||||
"code": "night_quiet",
|
||||
"title": "深夜免打扰",
|
||||
"description": "夜间减少消息提醒打扰,帮助用户保持健康作息。",
|
||||
"enforced": false
|
||||
"enforced": true
|
||||
},
|
||||
{
|
||||
"code": "content_filter",
|
||||
"title": "内容过滤",
|
||||
"description": "对不适合青少年的内容进行识别和限制展示。",
|
||||
"enforced": false
|
||||
"enforced": true
|
||||
},
|
||||
{
|
||||
"code": "payment_guard",
|
||||
@@ -202,13 +202,13 @@ POST /v1/im/settings/youth-mode/enable
|
||||
"code": "night_quiet",
|
||||
"title": "深夜免打扰",
|
||||
"description": "夜间减少消息提醒打扰,帮助用户保持健康作息。",
|
||||
"enforced": false
|
||||
"enforced": true
|
||||
},
|
||||
{
|
||||
"code": "content_filter",
|
||||
"title": "内容过滤",
|
||||
"description": "对不适合青少年的内容进行识别和限制展示。",
|
||||
"enforced": false
|
||||
"enforced": true
|
||||
},
|
||||
{
|
||||
"code": "payment_guard",
|
||||
|
||||
@@ -966,7 +966,7 @@ X-MeetingIM-APIKey: <预置的普通 API Key>
|
||||
| `POST /v1/im/auth/password-reset/confirm` | `{"reset_token":"reset_token_123","new_password":"NewPass123"}` | `{"status":"ok"}` |
|
||||
| `POST /admin/v1/tenant/users/{user_id}/reset-password` 管理后台 | `{"new_password":"AdminReset123","reason":"用户无手机号无法自助找回"}` | `{"user_id":"usr...","status":"reset","auth_version":2}` |
|
||||
| `POST /v1/im/auth/logout` 待实现 | `{}` | `{"status":"ok"}` |
|
||||
| `GET /v1/im/app/releases/latest?platform=ios\|android¤t_version=1.0.0&channel=stable` | Query only | `{"platform":"ios","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}` |
|
||||
| `GET /v1/im/app/releases/latest?platform=ios\|android\|web¤t_version=1.0.0&channel=stable` | Query only | `{"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}` |
|
||||
|
||||
## 2. 消息首页、全局导航与系统通知
|
||||
|
||||
@@ -2271,6 +2271,7 @@ GET /v1/im/topics/{topic}/message-mutations?since_version={localMutationVersion}
|
||||
- `GET /v1/im/topics/{topic}/messages/search?q=&type=text&cursor=` 返回 `seq,from,ts,snippet,matches[]`。
|
||||
- 点击结果后 `GET /v1/im/topics/{topic}/messages/{seq}/context?before=20&after=20`,返回锚点及上下文;客户端定位 `seq` 并高亮。
|
||||
- 服务端只搜索当前用户可见且未硬删除的消息,关键词做长度/频率限制,搜索日志不得记录原文。
|
||||
- 搜索匹配字段包括文本正文、`content.text/txt/content/title/summary/name`、文件名、链接标题/摘要、卡片/转发快照等可展示文本;排序按 `seq desc`,`next_cursor` 继续向更早消息翻页。
|
||||
|
||||
### 7.13 查找聊天记录-文件.png
|
||||
|
||||
@@ -2924,7 +2925,7 @@ GET /v1/im/topics/{topic}/message-mutations?since_version={localMutationVersion}
|
||||
|
||||
**状态:新增 App 配置接口。**
|
||||
|
||||
- `GET /v1/im/app/releases/latest?platform=ios|android¤t_version=1.0.0&channel=stable`,未登录可在企业解析后调用。
|
||||
- `GET /v1/im/app/releases/latest?platform=ios|android|web¤t_version=1.0.0&channel=stable`,未登录可在企业解析后调用。
|
||||
- 返回 `latest_version,min_supported_version,update_type=none|optional|required,release_notes,store_url,package_url,checksum,published_at`。
|
||||
- 客户端按平台只打开受信任商店/签名下载地址;Android 包必须校验签名和 checksum,服务端 URL 使用允许域名。
|
||||
|
||||
@@ -2963,7 +2964,7 @@ GET /v1/im/topics/{topic}/message-mutations?since_version={localMutationVersion}
|
||||
|
||||
**状态:新增。**
|
||||
|
||||
- `GET /v1/im/settings/youth-mode` 返回 `enabled,policy_version,restrictions,restriction_items,password_set,can_enable`。当前默认策略项为 `night_quiet`、`content_filter`、`payment_guard` 三条;后端现阶段完成状态持久化、开启/关闭校验和策略返回,业务接口层的深夜免打扰、内容过滤、支付守护尚未实际强制执行,`restriction_items[].enforced=false` 用于明确表达这一点。
|
||||
- `GET /v1/im/settings/youth-mode` 返回 `enabled,policy_version,restrictions,restriction_items,password_set,can_enable`。当前默认策略项为 `night_quiet`、`content_filter`、`payment_guard` 三条;后端现阶段完成状态持久化、开启/关闭校验和策略返回;`night_quiet` 已接入音视频呼叫发起限制,`content_filter` 已接入消息发送前内容安全审核,二者返回 `enforced=true`;`payment_guard` 仍为预留项,返回 `enforced=false`。
|
||||
- 点击开启先展示 10.15,再按 1.7 完成身份验证和 1.11 设置独立密码;最后调用 `POST /v1/im/settings/youth-mode/enable`,Body `{"policy_version":n,"action_token":"..."}`,其中 `action_token` 使用设置独立密码响应里的 `enable_action_token`。
|
||||
- 开启链路中,短信校验返回的 `set_independent_password` token 只用于设置独立密码;设置密码成功响应中的 `enable_action_token` 才用于 `youth-mode/enable`。
|
||||
- 服务端持久化到用户租户范围并推送所有设备,不能仅设置本地标志。
|
||||
@@ -2984,7 +2985,7 @@ GET /v1/im/topics/{topic}/message-mutations?since_version={localMutationVersion}
|
||||
**状态:新增。**
|
||||
|
||||
- 页面调用 10.14,`enabled=true` 时展示 `enabled_at,restrictions`。`bootstrap` 同时返回受限模块和动作。
|
||||
- 后续某条规则真正接入业务拦截后,受限接口服务端返回 `YOUTH_MODE_RESTRICTED`,并把对应 `restriction_items[].enforced` 改为 `true`;前端隐藏只是体验优化,不能作为安全控制。
|
||||
- 受限接口服务端返回 `YOUTH_MODE_RESTRICTED`,当前已覆盖夜间音视频呼叫发起;前端隐藏只是体验优化,不能作为安全控制。
|
||||
- 登录、App 重启和另一设备均从服务端恢复状态;收到 `youth_mode.updated` 即刷新。
|
||||
|
||||
### 10.17 关闭青少年模式(安卓).png
|
||||
@@ -3053,10 +3054,10 @@ GET /v1/im/topics/{topic}/message-mutations?since_version={localMutationVersion}
|
||||
| `PATCH /v1/im/me/profile` | `{"nickname":"新昵称","signature":"新签名","version":1}` | `{"avatar_file_id":"","nickname":"新昵称","signature":"新签名","version":2,"updated_at":"2026-08-20T09:00:00Z"}` |
|
||||
| `GET /v1/im/me/privacy` | Query only | `{"add_me_methods":{"account":true,"phone":true,"qr":true,"group":true},"profile_visibility":"friends","last_seen_visibility":"friends","read_receipts":true,"friend_request_verification":true,"group_invite_verification":false,"version":1,"editable":{"add_me_methods":true,"profile_visibility":true,"last_seen_visibility":true,"read_receipts":true,"friend_request_verification":true,"group_invite_verification":true}}` |
|
||||
| `PATCH /v1/im/me/privacy` | `{"add_me_methods":{"phone":false},"read_receipts":false,"friend_request_verification":false,"group_invite_verification":true,"version":1}` | `{"add_me_methods":{"account":true,"phone":false,"qr":true,"group":true},"read_receipts":false,"friend_request_verification":false,"group_invite_verification":true,"version":2,"updated_at":"2026-08-20T09:00:00Z"}` |
|
||||
| `GET /v1/im/settings/youth-mode` | Query only | `{"enabled":false,"policy_version":1,"restrictions":["night_quiet","content_filter","payment_guard"],"restriction_items":[{"code":"night_quiet","title":"深夜免打扰","description":"夜间减少消息提醒打扰,帮助用户保持健康作息。","enforced":false},{"code":"content_filter","title":"内容过滤","description":"对不适合青少年的内容进行识别和限制展示。","enforced":false},{"code":"payment_guard","title":"支付守护","description":"限制或确认支付、充值等敏感消费行为。","enforced":false}],"password_set":false,"can_enable":false,"updated_at":"2026-08-20T09:00:00Z"}` |
|
||||
| `GET /v1/im/settings/youth-mode` | Query only | `{"enabled":false,"policy_version":1,"restrictions":["night_quiet","content_filter","payment_guard"],"restriction_items":[{"code":"night_quiet","title":"深夜免打扰","description":"夜间减少消息提醒打扰,帮助用户保持健康作息。","enforced":true},{"code":"content_filter","title":"内容过滤","description":"对不适合青少年的内容进行识别和限制展示。","enforced":true},{"code":"payment_guard","title":"支付守护","description":"限制或确认支付、充值等敏感消费行为。","enforced":false}],"password_set":false,"can_enable":false,"updated_at":"2026-08-20T09:00:00Z"}` |
|
||||
| `POST /v1/im/settings/independent-password` | `{"password":"1234","purpose":"youth_mode","action_token":"action_token"}` | `{"status":"ok","password_set":true,"purpose":"youth_mode","enable_action_token":"enable_token","enable_action_expires_at":"2026-08-20T09:05:00Z","next_action":"enable_youth_mode"}` |
|
||||
| `POST /v1/im/settings/independent-password/verify` | `{"password":"1234","purpose":"close_youth_mode"}` | `{"action_token":"action_token","action":"close_youth_mode","expires_at":"2026-08-20T09:05:00Z"}` |
|
||||
| `POST /v1/im/settings/youth-mode/enable` | `{"policy_version":1,"action_token":"enable_action_token"}` | `{"enabled":true,"policy_version":1,"password_set":true,"restrictions":["night_quiet","content_filter","payment_guard"],"restriction_items":[{"code":"night_quiet","title":"深夜免打扰","description":"夜间减少消息提醒打扰,帮助用户保持健康作息。","enforced":false},{"code":"content_filter","title":"内容过滤","description":"对不适合青少年的内容进行识别和限制展示。","enforced":false},{"code":"payment_guard","title":"支付守护","description":"限制或确认支付、充值等敏感消费行为。","enforced":false}],"updated_at":"2026-08-20T09:00:00Z"}` |
|
||||
| `POST /v1/im/settings/youth-mode/enable` | `{"policy_version":1,"action_token":"enable_action_token"}` | `{"enabled":true,"policy_version":1,"password_set":true,"restrictions":["night_quiet","content_filter","payment_guard"],"restriction_items":[{"code":"night_quiet","title":"深夜免打扰","description":"夜间减少消息提醒打扰,帮助用户保持健康作息。","enforced":true},{"code":"content_filter","title":"内容过滤","description":"对不适合青少年的内容进行识别和限制展示。","enforced":true},{"code":"payment_guard","title":"支付守护","description":"限制或确认支付、充值等敏感消费行为。","enforced":false}],"updated_at":"2026-08-20T09:00:00Z"}` |
|
||||
| `POST /v1/im/settings/youth-mode/disable` | Header `X-Action-Token: action_token`;Body `{}` | `{"enabled":false,"policy_version":1,"password_set":true,"updated_at":"2026-08-20T09:00:00Z"}` |
|
||||
| `POST /v1/im/security/challenges` 开启青少年模式 | Header `Idempotency-Key`;Body `{"scene":"set_independent_password","channel":"sms"}` | `{"challenge_id":"sec_chg_1","phone_mask":"+861****000","expires_in_seconds":300,"resend_after_seconds":60}` |
|
||||
| `POST /v1/im/security/challenges` 关闭青少年模式 | Header `Idempotency-Key`;Body `{"scene":"close_youth_mode","channel":"sms"}` | `{"challenge_id":"sec_chg_2","phone_mask":"+861****000","expires_in_seconds":300,"resend_after_seconds":60}` |
|
||||
|
||||
Reference in New Issue
Block a user