
在医疗类APP中,即时通讯模块承载着医患沟通、团队协作、预约提醒等关键功能。消息的已读未回执机制不仅是用户体验的组成部分,更直接关系到医疗信息传递的确认性与责任界定。例如,医生向患者发送检查结果解读或用药指导后,系统需要明确记录对方是否已阅读,以便后续诊疗决策参考或作为医疗过程追溯的依据。同时,合规性要求所有通信行为具备可审计、不可篡改的特性。
基于上述需求,本数据库设计的核心目标包括:
准确记录每条消息在每个接收端用户的阅读状态。
支持群聊场景下区分不同成员的已读/未读状态。
提供高性能的已读状态批量查询与更新能力。
满足医疗数据留存周期与安全审计要求。
设计可扩展的架构以适应未来跨终端同步(手机、平板、网页端)对回执状态的多设备管理需求。
消息已读未回执机制涉及三个主要实体:用户、消息、会话。其中:
用户指参与通信的个体,包括医生、护士、患者及其授权家属等角色。
消息是会话中产生的单条通信内容,可能包含文本、图片、处方、报告附件等。
会话是用户之间或用户组之间进行消息交换的逻辑容器,可以是双人私聊或多人群组。
在医疗场景中,一个特殊的约束是:已读状态必须基于“会话-消息-用户”三维度唯一确定。对于私聊,每条消息有两个参与方:发送方和接收方。发送方通常默认对本人发送的消息处于“已发送”状态而非“已读”状态,只有接收方阅读后产生“已读”事件。对于群聊,每条消息需要记录群内除发送者外所有成员的独立阅读状态。
此外,考虑到消息可能在多个终端阅读(如医生先在手机上看一眼,后在电脑上详细阅读),应支持追踪每个用户在不同设备上的阅读行为,但最终以“用户是否至少在一个终端阅读过消息”作为业务层面的已读判定。
基于上述分析,设计以下核心数据表:
此表存储会话中的每条消息主体内容,是回执机制的基础。
| 字段名 | 数据类型 | 约束 | 说明 |
|---|---|---|---|
| message_id | bigint | PRIMARY KEY, AUTO_INCREMENT | 消息唯一标识 |
| session_id | bigint | NOT NULL | 所属会话ID |
| sender_user_id | bigint | NOT NULL | 发送者用户ID |
| message_type | tinyint | NOT NULL | 消息类型:0-文本,1-图片,2-语音,3-处方,4-报告等 |
| content | text | 消息内容(加密存储) | |
| send_time | datetime | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 消息发送时间戳 |
| recall_time | datetime | 撤回时间,NULL表示未撤回 | |
| recall_reason | varchar(255) | 撤回原因(用于医疗合规记录) | |
| is_deleted | tinyint | NOT NULL, DEFAULT 0 | 逻辑删除标记 |
索引设计:对(session_id, send_time)建立复合索引,支持按会话拉取历史消息并按时间排序;对(sender_user_id)建立索引,用于查询某个用户发出的所有消息。
此表是回执机制的核心,每条消息对每个接收用户产生一条记录(若用户是消息发送者,则不产生或特殊标记)。
| 字段名 | 数据类型 | 约束 | 说明 |
|---|---|---|---|
| status_id | bigint | PRIMARY KEY, AUTO_INCREMENT | 状态记录唯一标识 |
| message_id | bigint | NOT NULL | 关联的消息ID |
| user_id | bigint | NOT NULL | 接收方用户ID |
| read_status | tinyint | NOT NULL, DEFAULT 0 | 0-未读,1-已读 |
| first_read_time | datetime | 首次阅读时间,仅当状态变1时记录 | |
| last_read_time | datetime | 最后阅读时间(用于多终端场景的最终确认) | |
| read_duration_sec | int | 从消息发送到首次阅读的间隔秒数(可用于分析响应时效) | |
| device_id | varchar(128) | 首次阅读使用的设备标识(可选,用于审计) | |
| status_update_time | datetime | NOT NULL, DEFAULT CURRENT_TIMESTAMP ON UPDATE | 状态最近更新时间 |
唯一性约束:对(message_id, user_id)建立唯一索引,确保同一用户对同一消息只有一条阅读状态记录。
索引设计:对(user_id, read_status, first_read_time)建立复合索引,用于快速查询某用户的未读消息列表;对(message_id)建立索引,用于按消息批量查询所有接收者的阅读状态。
为优化群聊场景下频繁查询“某群聊中哪些成员已读某条消息”的性能,可设计此冗余汇总表(或物化视图)。医疗群聊中常涉及多学科会诊,需快速确认参会专家是否已阅。
| 字段名 | 数据类型 | 约束 | 说明 |
|---|---|---|---|
| summary_id | bigint | PRIMARY KEY, AUTO_INCREMENT | 汇总标识 |
| message_id | bigint | NOT NULL | 关联消息ID |
| session_id | bigint | NOT NULL | 会话ID(冗余便于查询) |
| total_members | int | NOT NULL | 消息发送时该群有效成员总数(不含发送者) |
| read_count | int | NOT NULL, DEFAULT 0 | 当前已读成员数量 |
| unread_count | int | NOT NULL, DEFAULT 0 | 未读成员数量 |
| last_update_time | datetime | NOT NULL | 最后一次任何成员阅读状态变更的时间 |
此表通过触发器或应用层逻辑维护,避免每次查询时实时聚合大量message_read_status记录,尤其在拥有数百成员的医疗大群中显著提升性能。
医疗消息的撤回行为需严格审计,防止关键诊疗信息被随意删除而不留痕迹。
| 字段名 | 数据类型 | 约束 | 说明 |
|---|---|---|---|
| recall_log_id | bigint | PRIMARY KEY, AUTO_INCREMENT | 日志唯一标识 |
| message_id | bigint | NOT NULL | 原始消息ID |
| session_id | bigint | NOT NULL | 所属会话ID |
| operator_user_id | bigint | NOT NULL | 执行撤回的用户ID(通常为发送者或管理员) |
| recall_time | datetime | NOT NULL | 撤回操作时间 |
| original_content_hash | char(64) | 原始消息内容的哈希值(用于审计比对) | |
| recall_reason_code | tinyint | 撤回原因代码:1-发送错误,2-内容需修正,3-隐私问题等 |
当用户A向私聊会话或群聊会话发送消息时,系统需同时执行以下操作:
向message表插入消息记录,获取message_id。
查询该会话的所有有效成员(排除发送者本人)。
向message_read_status表批量插入记录,每条记录的read_status默认0,first_read_time和last_read_time为NULL。
若为群聊,更新group_read_summary表对应消息的total_members和unread_count。
批量插入可使用多条值语句一次性完成,避免循环插入带来的性能损耗。
当用户B打开会话窗口并阅读消息时,通常触发“已读上报”。操作逻辑:
获取会话中用户B尚未标记为已读的所有消息ID列表(即message_read_status中user_id=B且read_status=0)。
批量更新这些记录的read_status为1,设置first_read_time和last_read_time为当前时间,同时计算read_duration_sec = 当前时间 - 对应消息的send_time。
对于每条被更新的消息,若属于群聊,则原子性地增加group_read_summary中的read_count并减少unread_count(使用UPDATE ... SET read_count = read_count + 1, unread_count = unread_count - 1 WHERE message_id = ...)。
可选:向消息发送者推送“用户B已读”的回执通知(该通知可通过独立的消息推送服务实现,不直接由数据库触发)。
典型需求:用户登录APP后,首页需要显示总未读消息数,或在会话列表中每个会话旁标注未读数。
高效查询方式:
对于私聊会话:通过子查询或连接查询message_read_status,条件为user_id=当前用户且read_status=0,按会话分组计数。
对于群聊会话:直接查询group_read_summary表,关联用户所在群组,筛选unread_count > 0的记录,但需注意只有该用户自身状态为未读时才应计入。更精确的做法是维护一个“用户-会话-最新已读时间戳”的辅助表,但标准方案下仍建议按需从message_read_status中统计,仅对高频场景使用缓存。
医疗场景下,未读消息通常包含检验结果、病情变化等重要信息,建议设计定时任务在服务端预先计算每个用户的未读总数并存入缓存(如结构化存储或内存数据库),降低实时查询压力。
用于发送方查看“谁还没看”的典型场景。操作:
SELECT u.user_id, u.user_display_name, mrs.read_status, mrs.first_read_timeFROM message_read_status mrsJOIN user u ON mrs.user_id = u.user_idWHERE mrs.message_id = ?ORDER BY mrs.read_status ASC, mrs.first_read_time DESC;
该查询应利用message_id上的索引高效执行。对于群聊中成员数量较大的场景,可考虑分页返回。
在高频消息场景下,可能出现多个设备同时上报同一用户对同一批消息的已读状态。为避免重复更新或计数错误,需采用以下策略:
在应用层使用幂等设计:每次已读上报携带消息ID列表的哈希值或最后一条消息ID,服务端基于“用户-会话”的最近已读位置进行幂等检查。
数据库更新语句使用条件更新:例如UPDATE message_read_status SET read_status = 1, first_read_time = NOW() WHERE user_id = ? AND message_id = ? AND read_status = 0,并检查受影响行数。仅当实际发生更新时再去修改group_read_summary。
对group_read_summary的read_count和unread_count计数字段,使用UPDATE ... SET read_count = read_count + 1形式的原子操作,避免先读后写造成的覆盖。
分区策略:message表和message_read_status表均按时间分区(如按月分区)。医疗数据通常有明确的留存期限(如10年),分区便于数据归档和删除。同时,大部分查询聚焦于近期的消息(如最近3个月),分区裁剪可显著提升查询性能。
读写分离:消息已读状态的写入操作(用户阅读时的大量更新)与查询操作(拉取未读消息)可以分离到不同的数据库实例。主库处理消息发送和状态更新,从库处理历史消息查询与统计分析。
冷热数据分离:超过3个月的老消息及对应的阅读状态迁移至历史表或列式存储中。APP端通常不要求对超旧消息展示精确的已读回执,仅作为归档审计使用。
缓存设计:对于“用户总未读计数”这类高频查询,采用缓存预聚合。当用户阅读消息导致状态变更时,同步更新缓存中的计数值。
医疗通信数据受严格法规约束,需满足以下要求:
加密存储:message表中的content字段必须使用强加密算法(如AES-256)进行加密,密钥与数据分离管理。即使数据库被非法访问,消息正文无法直接读取。已读状态本身不涉及健康信息,可明文存储。
完整性保护:对message_read_status表的关键更新操作(如read_status由0变1)应记录审计日志,包括操作时间、操作者IP、设备指纹等。可使用数据库触发器或统一拦截层实现。
数据最小化留存:定期清理超出法定留存期限的已读状态记录,但原始消息内容仍需按法规保留。状态记录清理后,用户界面仍显示“已读”但无法追溯具体阅读时间。
访问控制:已读状态只能由会话参与者查询。例如,患者不能查看另一位患者的已读情况,医生仅能查看其负责患者及协作团队成员的状态。需在应用层通过行级安全策略或服务端逻辑严格校验权限。
用户可能同时在手机、平板、网页Web端登录。逻辑设计可扩展为:
将message_read_status表拆分为两级:用户级阅读状态(user_read_status)和设备级阅读状态(device_read_status)。用户级状态为“至少一个设备已读”,设备级存储每台设备的精确阅读时间。
当用户在任一设备上阅读消息时,更新该设备的device_read_status并尝试更新user_read_status。若user_read_status已为1则忽略,避免重复触发回执通知。
此方案增加复杂度,但对医疗场景中跨设备协同诊疗(如医生先在手机浏览值班消息、后在电脑上回复)提供了准确的阅读追溯能力。
若用户误操作将消息标记为已读(例如折叠屏设备误触),应支持“撤销已读”功能吗?从医疗严谨性角度,阅读行为不可逆,但可增加“备注”或“再次阅读”记录。设计上可扩展一个message_read_correction表,记录对已读状态的争议或修正申请,仅供高级权限角色(如科室主任、医疗质量管理员)审核后调整。
消息已读未回执数据库设计在医疗APP中承担着信息确认与责任追溯的双重使命。本文提出的方案以消息表、消息阅读状态表为核心,辅以群聊汇总表、撤回日志表,覆盖了私聊与群聊场景下的状态记录与查询需求。通过分区、读写分离、冷热分离等手段平衡性能与成本;通过加密、审计日志、访问控制满足医疗合规要求;同时预留了多设备支持与状态更正的可扩展接口。
实际落地时,还需结合具体的业务量级、并发模型以及所选用的数据库管理系统特性进行适度调整。例如,在极高并发场景下,可将已读状态的写入操作从关系型数据库迁移至高性能键值存储,再通过异步任务同步至分析型数据库用于审计。但无论如何变化,准确记录“谁、什么时候、读了哪条消息”这一核心事实永不改变。