feat:单聊页面bug修复+后台管理端好友页面bug修复+项目进度、记忆文件更新
This commit is contained in:
@@ -353,93 +353,101 @@ COMMENT ON COLUMN contact_groups.sort_order IS '排序权重,数值越小越
|
||||
COMMENT ON COLUMN contact_groups.created_at IS '创建时间';
|
||||
```
|
||||
|
||||
#### im 模块 — 即时通讯(统一会话模型)
|
||||
#### im 模块 — 即时通讯(统一会话模型)✅ Phase 2b 已实现
|
||||
|
||||
单聊和群聊统一抽象为"会话",本质都是"一组人在一个空间里收发消息"。这是微信、钉钉、Slack 等主流 IM 的标准模型。
|
||||
|
||||
> **Phase 2b 实际实现说明:** 当前已实现单聊功能,表结构已针对实际需求进行了优化(冗余 last_msg_* 字段提升查询效率,新增 clear_before_msg_id 实现个人视图清空,新增 client_msg_id 实现消息幂等)。群聊相关字段(name/avatar/owner_id/max_members/role/nickname/is_muted)保留在总体设计中,将在 Phase 2c 实现。
|
||||
|
||||
```sql
|
||||
-- ============================================================
|
||||
-- im_conversations: 会话表
|
||||
-- 统一抽象单聊和群聊,单聊时 name/avatar 为空(前端用对方信息展示)
|
||||
-- im_conversations: 会话表(Phase 2b 实际实现版本)
|
||||
-- 统一抽象单聊和群聊,含冗余 last_msg_* 字段避免 JOIN 查询
|
||||
-- ============================================================
|
||||
CREATE TABLE im_conversations (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
type SMALLINT NOT NULL,
|
||||
name VARCHAR(100) DEFAULT '',
|
||||
avatar VARCHAR(500) DEFAULT '',
|
||||
owner_id BIGINT DEFAULT NULL,
|
||||
max_members INT NOT NULL DEFAULT 200,
|
||||
status SMALLINT NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
type SMALLINT NOT NULL DEFAULT 1,
|
||||
creator_id BIGINT NOT NULL,
|
||||
last_message_id BIGINT DEFAULT NULL,
|
||||
last_msg_content TEXT DEFAULT '',
|
||||
last_msg_time TIMESTAMP WITH TIME ZONE,
|
||||
last_msg_sender_id BIGINT DEFAULT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE im_conversations IS '会话表,统一管理单聊和群聊会话';
|
||||
COMMENT ON COLUMN im_conversations.id IS '会话唯一标识';
|
||||
COMMENT ON COLUMN im_conversations.type IS '会话类型:1=单聊(两人私聊),2=群聊(多人群组)';
|
||||
COMMENT ON COLUMN im_conversations.name IS '会话名称,群聊时为群名,单聊时为空(前端取对方昵称展示)';
|
||||
COMMENT ON COLUMN im_conversations.avatar IS '会话头像 URL,群聊时为群头像,单聊时为空(前端取对方头像展示)';
|
||||
COMMENT ON COLUMN im_conversations.owner_id IS '群主用户 ID,仅群聊时有值,单聊时为 NULL';
|
||||
COMMENT ON COLUMN im_conversations.max_members IS '最大成员数,单聊固定为2,群聊默认200';
|
||||
COMMENT ON COLUMN im_conversations.status IS '会话状态:1=正常,2=已解散(仅群聊可解散)';
|
||||
COMMENT ON COLUMN im_conversations.created_at IS '会话创建时间';
|
||||
COMMENT ON COLUMN im_conversations.updated_at IS '最后更新时间';
|
||||
COMMENT ON TABLE im_conversations IS '会话表,统一管理单聊和群聊会话';
|
||||
COMMENT ON COLUMN im_conversations.id IS '会话唯一标识';
|
||||
COMMENT ON COLUMN im_conversations.type IS '会话类型:1=单聊,2=群聊(Phase 2c)';
|
||||
COMMENT ON COLUMN im_conversations.creator_id IS '会话创建者用户 ID';
|
||||
COMMENT ON COLUMN im_conversations.last_message_id IS '最后一条消息 ID(冗余字段,避免 JOIN)';
|
||||
COMMENT ON COLUMN im_conversations.last_msg_content IS '最后消息预览文本(冗余字段)';
|
||||
COMMENT ON COLUMN im_conversations.last_msg_time IS '最后消息时间(冗余字段,用于排序)';
|
||||
COMMENT ON COLUMN im_conversations.last_msg_sender_id IS '最后消息发送者 ID(冗余字段)';
|
||||
|
||||
CREATE INDEX idx_im_conversations_updated ON im_conversations(updated_at DESC);
|
||||
|
||||
-- ============================================================
|
||||
-- im_conversation_members: 会话成员表
|
||||
-- 记录每个会话中的参与成员及其个性化设置
|
||||
-- im_conversation_members: 会话成员表(Phase 2b 实际实现版本)
|
||||
-- 记录每个会话中的参与成员及其个人视图设置
|
||||
-- ============================================================
|
||||
CREATE TABLE im_conversation_members (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
conversation_id BIGINT NOT NULL REFERENCES im_conversations(id),
|
||||
user_id BIGINT NOT NULL REFERENCES auth_users(id),
|
||||
role SMALLINT NOT NULL DEFAULT 0,
|
||||
nickname VARCHAR(50) DEFAULT '',
|
||||
is_muted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
is_pinned BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
last_read_msg_id BIGINT DEFAULT 0,
|
||||
joined_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (conversation_id, user_id)
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
conversation_id BIGINT NOT NULL REFERENCES im_conversations(id),
|
||||
user_id BIGINT NOT NULL,
|
||||
is_pinned BOOLEAN DEFAULT FALSE,
|
||||
is_deleted BOOLEAN DEFAULT FALSE,
|
||||
unread_count INT DEFAULT 0,
|
||||
last_read_msg_id BIGINT DEFAULT 0,
|
||||
clear_before_msg_id BIGINT DEFAULT 0,
|
||||
created_at TIMESTAMP(0) NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP(0) NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(conversation_id, user_id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE im_conversation_members IS '会话成员表,记录成员列表及每人的个性化设置';
|
||||
COMMENT ON COLUMN im_conversation_members.id IS '记录唯一标识';
|
||||
COMMENT ON COLUMN im_conversation_members.conversation_id IS '所属会话 ID';
|
||||
COMMENT ON COLUMN im_conversation_members.user_id IS '成员用户 ID';
|
||||
COMMENT ON COLUMN im_conversation_members.role IS '成员角色:0=普通成员,1=管理员(群聊可设置),2=群主';
|
||||
COMMENT ON COLUMN im_conversation_members.nickname IS '群内昵称,仅在该群聊中生效,为空则使用用户全局昵称';
|
||||
COMMENT ON COLUMN im_conversation_members.is_muted IS '是否被禁言:false=正常发言,true=已被禁言(仅管理员/群主可操作)';
|
||||
COMMENT ON COLUMN im_conversation_members.is_pinned IS '是否置顶该会话:false=不置顶,true=置顶(个人设置,不影响他人)';
|
||||
COMMENT ON COLUMN im_conversation_members.last_read_msg_id IS '最后已读消息 ID,用于计算未读消息数';
|
||||
COMMENT ON COLUMN im_conversation_members.joined_at IS '加入会话的时间';
|
||||
COMMENT ON TABLE im_conversation_members IS '会话成员表,记录成员列表及个人视图设置';
|
||||
COMMENT ON COLUMN im_conversation_members.id IS '记录唯一标识';
|
||||
COMMENT ON COLUMN im_conversation_members.conversation_id IS '所属会话 ID';
|
||||
COMMENT ON COLUMN im_conversation_members.user_id IS '成员用户 ID';
|
||||
COMMENT ON COLUMN im_conversation_members.is_pinned IS '是否置顶(个人设置)';
|
||||
COMMENT ON COLUMN im_conversation_members.is_deleted IS '是否软删除(个人视图,不影响对方)';
|
||||
COMMENT ON COLUMN im_conversation_members.unread_count IS '未读消息数';
|
||||
COMMENT ON COLUMN im_conversation_members.last_read_msg_id IS '最后已读消息 ID';
|
||||
COMMENT ON COLUMN im_conversation_members.clear_before_msg_id IS '清空记录截止消息 ID(个人视图,查询时过滤 id <= 此值的消息)';
|
||||
|
||||
CREATE INDEX idx_im_conv_members_user ON im_conversation_members(user_id, is_deleted);
|
||||
CREATE INDEX idx_im_conv_members_conv ON im_conversation_members(conversation_id);
|
||||
|
||||
-- ============================================================
|
||||
-- im_messages: 消息表
|
||||
-- 系统数据量最大的表,存储所有聊天消息内容
|
||||
-- im_messages: 消息表(Phase 2b 实际实现版本)
|
||||
-- 系统数据量最大的表,含 client_msg_id 实现幂等 + GIN 全文搜索索引
|
||||
-- ============================================================
|
||||
CREATE TABLE im_messages (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
conversation_id BIGINT NOT NULL,
|
||||
sender_id BIGINT NOT NULL,
|
||||
conversation_id BIGINT NOT NULL REFERENCES im_conversations(id),
|
||||
sender_id BIGINT NOT NULL,
|
||||
type SMALLINT NOT NULL DEFAULT 1,
|
||||
content TEXT NOT NULL DEFAULT '',
|
||||
extra JSONB DEFAULT '{}',
|
||||
content TEXT NOT NULL,
|
||||
extra JSONB DEFAULT NULL,
|
||||
status SMALLINT NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
client_msg_id VARCHAR(64) DEFAULT '',
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE im_messages IS '聊天消息表,存储所有会话的消息记录(系统数据量最大的表)';
|
||||
COMMENT ON TABLE im_messages IS '聊天消息表,存储所有会话的消息记录';
|
||||
COMMENT ON COLUMN im_messages.id IS '消息唯一标识,全局自增';
|
||||
COMMENT ON COLUMN im_messages.conversation_id IS '所属会话 ID';
|
||||
COMMENT ON COLUMN im_messages.sender_id IS '发送者用户 ID';
|
||||
COMMENT ON COLUMN im_messages.type IS '消息类型:1=文本消息,2=图片消息,3=文件消息,4=语音消息,5=系统通知消息';
|
||||
COMMENT ON COLUMN im_messages.content IS '消息内容,文本消息为文字,其他类型为描述文字或为空';
|
||||
COMMENT ON COLUMN im_messages.extra IS '附加数据(JSON),图片消息存 {url,width,height},文件消息存 {url,name,size},语音消息存 {url,duration}';
|
||||
COMMENT ON COLUMN im_messages.status IS '消息状态:1=正常,2=已撤回(发送者撤回),3=已删除(管理员删除)';
|
||||
COMMENT ON COLUMN im_messages.type IS '消息类型:1=文本(预留 2=图片 3=语音 4=文件 5=系统通知)';
|
||||
COMMENT ON COLUMN im_messages.content IS '消息内容';
|
||||
COMMENT ON COLUMN im_messages.extra IS '附加数据(JSON),预留扩展';
|
||||
COMMENT ON COLUMN im_messages.status IS '消息状态:1=正常,2=已撤回,3=已删除';
|
||||
COMMENT ON COLUMN im_messages.client_msg_id IS '客户端消息唯一 ID,用于幂等去重';
|
||||
COMMENT ON COLUMN im_messages.created_at IS '消息发送时间';
|
||||
|
||||
CREATE INDEX idx_im_messages_conv_time ON im_messages(conversation_id, created_at DESC);
|
||||
COMMENT ON INDEX idx_im_messages_conv_time IS '会话消息时间索引,用于按时间倒序查询会话历史消息';
|
||||
CREATE INDEX idx_im_messages_conv_id ON im_messages(conversation_id, id DESC);
|
||||
CREATE INDEX idx_im_messages_content_search ON im_messages USING gin(to_tsvector('simple', content));
|
||||
```
|
||||
|
||||
#### meeting 模块 — 音视频会议
|
||||
@@ -586,9 +594,8 @@ echo:auth:refresh:{client_type}:{user_id} → Refresh Token (STRING, TTL 由
|
||||
echo:user:online → 在线用户集合 (SET)
|
||||
echo:user:status:{user_id} → 用户状态 JSON (STRING, TTL 自动过期)
|
||||
|
||||
# 即时通讯
|
||||
echo:im:unread:{user_id} → 各会话未读数 (HASH: conv_id → count)
|
||||
echo:im:typing:{conversation_id} → 正在输入的用户 (SET, TTL 5秒)
|
||||
# 即时通讯(Phase 2b 实际实现)
|
||||
echo:im:unread:{user_id} → 全局未读消息总数 (STRING,Lua 脚本原子递减,下限 0)
|
||||
|
||||
# 会议实时状态
|
||||
echo:meeting:room:{room_code} → 房间实时状态 JSON (STRING)
|
||||
@@ -702,13 +709,14 @@ GET /api/v1/contacts/recommend 好友推荐
|
||||
# 在线状态
|
||||
GET /api/v1/contacts/online 批量查询好友在线状态
|
||||
|
||||
# 即时通讯模块
|
||||
GET /api/v1/conversations
|
||||
POST /api/v1/conversations
|
||||
GET /api/v1/conversations/:id
|
||||
GET /api/v1/conversations/:id/messages
|
||||
POST /api/v1/conversations/:id/members
|
||||
DELETE /api/v1/conversations/:id/members/:uid
|
||||
# 即时通讯模块(Phase 2b 已实现)
|
||||
GET /api/v1/im/conversations 会话列表(含未读数、对方信息)
|
||||
GET /api/v1/im/messages 历史消息(游标分页 before_id + limit)
|
||||
PUT /api/v1/im/conversations/:id/pin 置顶/取消置顶
|
||||
DELETE /api/v1/im/conversations/:id 删除会话(软删除)
|
||||
DELETE /api/v1/im/conversations/:id/messages 清空聊天记录(个人视图)
|
||||
GET /api/v1/im/messages/search 全局消息搜索(GIN 全文索引)
|
||||
GET /api/v1/im/unread 全局未读消息总数
|
||||
|
||||
# 会议模块
|
||||
POST /api/v1/meetings
|
||||
@@ -811,9 +819,11 @@ pages/
|
||||
│ ├── register.vue # 注册页
|
||||
│ └── forgot-password.vue # 忘记密码
|
||||
├── chat/
|
||||
│ ├── index.vue # 会话列表
|
||||
│ ├── conversation.vue # 聊天对话页
|
||||
│ └── group-create.vue # 创建群聊
|
||||
│ ├── index.vue # 会话列表 ✅ Phase 2b
|
||||
│ ├── conversation.vue # 聊天对话页 ✅ Phase 2b
|
||||
│ ├── settings.vue # 聊天设置页 ✅ Phase 2b
|
||||
│ ├── search.vue # 消息搜索页 ✅ Phase 2b
|
||||
│ └── group-create.vue # 创建群聊 📋 Phase 2c
|
||||
├── contact/
|
||||
│ ├── index.vue # 联系人列表(含搜索/在线状态) ✅ Phase 2a
|
||||
│ ├── search.vue # 搜索添加好友 + 好友推荐 ✅ Phase 2a
|
||||
@@ -937,10 +947,25 @@ services:
|
||||
- 在线状态管理(混合推拉方案)
|
||||
- 管理端扩展(在线监控 + 好友关系管理)
|
||||
|
||||
#### Phase 2b:即时通讯消息系统 🔜 待开发
|
||||
- 即时聊天(单聊 + 群聊,文字/图片/文件)
|
||||
#### Phase 2b:即时通讯消息系统 ✅ 已完成
|
||||
- 单聊即时通讯(WebSocket 全双工消息收发、三态 ACK 确认)
|
||||
- 会话管理(自动创建、置顶、软删除、清空聊天记录 — 个人视图)
|
||||
- 消息撤回(2 分钟内)、正在输入提示
|
||||
- 离线消息推送(WS 连接后服务端主动推送未读摘要)
|
||||
- 未读消息管理(会话 badge + TabBar 总未读数,Lua 脚本原子操作)
|
||||
- 全局消息搜索(PostgreSQL GIN 全文索引)
|
||||
- WS 事件路由表机制(Hub.RegisterEvent/DispatchEvent)
|
||||
- 前台 4 个页面 + chat Store + API 封装
|
||||
- 设计文档:`docs/plans/2026-03-03-phase2b-design.md`
|
||||
|
||||
#### Phase 2c:会议与通知 📋 待规划
|
||||
#### Phase 2c:群聊与增强 📋 待规划
|
||||
- 群聊会话(建群/加入/退出/管理)
|
||||
- 群消息收发
|
||||
- 已读回执(单聊 + 群聊)
|
||||
- 消息类型扩展(图片/语音/文件)
|
||||
- 管理端消息管理功能
|
||||
|
||||
#### Phase 2d:会议与通知 📋 待规划
|
||||
- 多人音视频会议(即时会议 + 预约会议)
|
||||
- 消息通知系统
|
||||
|
||||
|
||||
Reference in New Issue
Block a user