53 lines
5.6 KiB
Markdown
53 lines
5.6 KiB
Markdown
# 聊天归档后端只读复核
|
||
|
||
检查范围:DiagnosisLogic 的回调、消息键、持久化、患者历史读取、分页与批量同步,DiagnosisController 的读取/触发接口,ImChatSyncSession、ImRoamMessagePager、SyncImChatArchive、定时 SQL 与实际 Crontab 调度器,以及 IM callback 的鉴权入口。
|
||
|
||
以下为检查时仍存在的三个实质问题。未修改业务代码。
|
||
|
||
## 1. [P1] 多元素消息只保存第一个元素,形成不可恢复的内容缺失
|
||
|
||
位置:[DiagnosisLogic.php](/D:/web/zyt/server/app/adminapi/logic/tcm/DiagnosisLogic.php:1381),`parseTimMsgBody`;同文件 `persistImChatArchiveRows` 与 `insertIgnoreImChatBatch`。
|
||
|
||
`parseTimMsgBody` 遇到首个文本、图片或其他已识别元素就 `return $out`。因此合法的 `MsgBody=[text1,text2]` 只归档 text1,`[text,image]` 则丢失图片。数据库行也没有保存原始 MsgBody。新回调和历史补拉都复用这条解析路径;稍后即使再次取回完整消息,相同 msg_id 的 `ON DUPLICATE KEY UPDATE msg_id=msg_id` 也不会补齐内容。
|
||
|
||
用真实 `normalizeTimMessage` 的 Reflection 调用作了无数据库复现:两段 TIMTextElem 分别为 `first fragment` 与 `second fragment`,输出 `text=first fragment`,没有原始消息体字段。官方明确单条消息可包含多个消息元素。[腾讯消息格式说明](https://cloud.tencent.cn/document/product/269/2720)
|
||
|
||
建议保留全部消息元素或原始 JSON,再由展示层处理;并处理已有不完整归档的补齐,不能只靠重复键忽略。此问题源于已有解析函数,但新增即时归档仍沿用它,未实现完整内容留存。
|
||
|
||
## 2. [P2] 定时补拉失败被调度器清成正常状态
|
||
|
||
位置:[SyncImChatArchive.php](/D:/web/zyt/server/app/common/command/SyncImChatArchive.php:49),[Crontab.php](/D:/web/zyt/server/app/common/command/Crontab.php:75)。
|
||
|
||
同步返回错误时,命令写日志后 `return 1`。但 SQL 所注册任务实际由 `Crontab::start` 调用 `Console::call`:本项目 ThinkPHP `Console::call` 执行 `Command::run` 后没有读取退出码,只返回 Output;`Crontab::start` 也没有读取输出,紧接着无条件把数据库任务 `error` 清为空字符串。只要没有抛出异常,任务列表就不会反映腾讯请求或归档写库失败。
|
||
|
||
证据:[Console.php](/D:/web/zyt/server/vendor/topthink/framework/src/think/Console.php:217) 明确丢弃 `run` 返回值;[Crontab.php](/D:/web/zyt/server/app/common/command/Crontab.php:79) 仅以异常作为错误分支。实际结果是 CLI 单独执行可见非零退出码,已部署的数据库定时任务却仍显示正常,历史缺口容易持续被忽略。
|
||
|
||
建议让该命令与调度器明确传递失败状态并持久化错误,同时保持所需的后续重试安排;仅返回 1 不足以修复这条调度链。
|
||
|
||
## 3. [P2] 医生单侧漫游结果被当作整个会话的完整归档
|
||
|
||
位置:[DiagnosisLogic.php](/D:/web/zyt/server/app/adminapi/logic/tcm/DiagnosisLogic.php:1084),`advanceImChatSync` 的 `nextPage` 调用与完整检查点写入。
|
||
|
||
所有补拉固定使用 doctor_* 作为 `Operator_Account`、patient_* 作为 `Peer_Account`。医生侧返回 Complete=1 后即建立该患者/医生的完整检查点,后续仅从检查点前两分钟重叠补拉,整个流程没有切换患者侧查询。
|
||
|
||
腾讯官方说明:任一侧清空或删除会话、删除部分消息,以及 REST 发送时 SyncOtherMachine=2,都会使双方能拉到的历史不同。因此,在首次补历史时,医生侧已经删除但患者侧仍保留的消息会被完全遗漏;医生侧 Complete=1 只证明该侧记录拉完,不能证明患者会话全部归档。新回调不能追溯部署前已发生的消息。[腾讯历史消息接口说明](https://cloud.tencent.cn/document/product/269/42794)
|
||
|
||
建议分别完成双方视角的补拉后再认定完整,使用现有 msg_id 去重;或至少按视角分别记录检查点,并对只完成一侧的结果保留准确语义。
|
||
|
||
## 已发现并在复核过程中修复的项目
|
||
|
||
`SendMsgResult` 非零消息原先同样落入普通聊天历史。官方明确发送失败仍触发发送后回调,0 表示成功、非 0 表示失败。[腾讯发送后回调说明](https://intl.cloud.tencent.com/zh/document/product/1047/34365)
|
||
|
||
复读当前代码已看到 `archiveImCallbackMessage` 校验整数 SendMsgResult、非零返回 0;此项已解决,不计入上述未解决问题。
|
||
|
||
## 未发现确证问题的检查项与验证边界
|
||
|
||
- 回调与漫游消息使用相同 from/to/MsgKey 时,实际 normalize 结果 msg_id 一致;旧 seq_random_from 键复用前会核对患者、双向账号与消息时间,没有发现可证实的跨患者键复用。
|
||
- 读取/触发控制器先检查诊单可读权限;读取以真实 patient_id 汇总并再次核对双方 IM 账号;同步 token 绑定诊单、患者及管理员。
|
||
- 同步失败会话不写完整检查点;归档抛异常时页游标不推进。没有发现当前状态机可确定复现的无限续页。
|
||
- 批量查询按 patient_id 分组选择 MAX(id),对静态患者集合可按游标遍历并回绕。检查了已安装 ThinkORM 的 column/字段解析实现,未发现此处可确证的生产 SQL/ORM 语法错误;未连接 MySQL 执行。
|
||
- `php server/tests/ImRoamMessagePagerTest.php` 通过;`php server/tests/ImCallbackTest.php` 的 81 项断言通过。两者均使用虚构配置和替身,无业务数据库或腾讯 API 调用。
|
||
- 附加执行了真实消息归一化函数的无数据库复现,确认多元素内容丢失及 callback/roam 消息键相同。
|
||
|
||
本报告是只读复核;唯一新增文件为本报告。
|