diff --git a/.cursor/rules/project-context.mdc b/.cursor/rules/project-context.mdc index a8fcf43..035b709 100644 --- a/.cursor/rules/project-context.mdc +++ b/.cursor/rules/project-context.mdc @@ -18,10 +18,10 @@ alwaysApply: true - **Phase 2a(WebSocket 实时通讯与联系人管理)**:✅ 全部完成(13 个 Task + 后期 Bug 修复 3 项) - **Phase 2b(即时通讯消息系统)**:✅ 全部完成(10 个 Task + 代码审查修复 7 项 + 用户测试修复 8 项),设计文档 `docs/plans/2026-03-03-phase2b-design.md` - 分支:`feature/phase2b-instant-messaging` -- 代码审查修复:ClearHistory 个人视图、Redis 负数保护、N+1 查询优化、全文索引搜索、撤回更新预览、推送补全 sender 信息 -- 用户测试修复:好友申请/接受唯一约束、Redis 在线状态残留清理、WS 全局初始化、管理端字段修正、好友列表在线状态初始值、聊天页布局约束 -- **Phase 2c(群聊与增强)**:待规划设计 -- **跨模块通信模式**:已建立接口注入标准(ws.FriendIDsGetter / im.FriendChecker / im.UserInfoGetter → contact.FriendshipDAO,im.OfflineMessagePusher → ws.Handler,contact.OnlineChecker → ws.OnlineService) +- **Phase 2c(群聊与已读回执)**:📋 设计完成,待实施(14 个 Task),设计文档 `docs/plans/2026-03-04-phase2c-design.md`,实施计划 `docs/plans/2026-03-04-phase2c-implementation.plan.md` +- 分支:`feature/phase2c-group-read-receipt`(待创建) +- 范围:群聊全功能(三级角色/禁言/@提醒/群公告/入群审批)+ 已读回执(单聊会话级 + 群聊消息级)+ MinIO 文件存储 + 管理端群聊管理 +- **跨模块通信模式**:已建立接口注入标准(ws.FriendIDsGetter / im.FriendChecker / im.UserInfoGetter → contact.FriendshipDAO,im.OfflineMessagePusher → ws.Handler,contact.OnlineChecker → ws.OnlineService),Phase 2c 新增 im.GroupMemberChecker / im.GroupInfoGetter → group.GroupDAO ## 项目概述 @@ -31,6 +31,7 @@ EchoChat 是一个实时音视频通讯平台,包含三个子项目: - `admin/` — 后台管理端(Vue 3.5+ + Element Plus + Pinia 3.x) 已实现模块:auth(认证)、contact(联系人)、ws(WebSocket)、admin(管理端)、im(即时通讯) +Phase 2c 待实现模块:group(群聊管理)、file(文件上传/MinIO)+ im 扩展(已读回执) ## 核心开发规则 diff --git a/docs/plans/2026-03-04-phase2c-design.md b/docs/plans/2026-03-04-phase2c-design.md new file mode 100644 index 0000000..9087087 --- /dev/null +++ b/docs/plans/2026-03-04-phase2c-design.md @@ -0,0 +1,626 @@ +# Phase 2c 设计文档:群聊与已读回执 + +> **状态:** 📋 设计完成,待实施 +> **分支:** `feature/phase2c-group-read-receipt` +> **前置依赖:** Phase 2b 全部完成(单聊即时通讯) +> **最后更新:** 2026-03-04 + +--- + +## 一、设计目标 + +基于 Phase 2b 的单聊 IM 基础设施,实现群聊核心功能和已读回执系统,同时引入 MinIO 文件存储服务作为基础设施扩展。 + +**核心交付物:** +- 群聊管理(创建/加入/退出/解散/搜索/角色管理/禁言/公告) +- 群消息收发(复用 im.message.* 事件 + @提醒 + 管理员撤回) +- 已读回执(单聊会话级 + 群聊消息级 + 实时推送 + 详情查看) +- MinIO 文件存储(Docker 容器 + Go SDK + 通用上传 API) +- 管理端群聊管理(群列表/详情/解散/移除成员) +- 前端 9 个新页面 + 群聊 Store + API 封装 + +**不包含(留待后续阶段):** +- 图片/语音/文件消息 +- 管理端消息管理 +- 消息类型扩展 + +--- + +## 二、需求决策记录 + +### 2.1 群聊功能决策 + +| 决策项 | 选定方案 | 说明 | +|--------|----------|------| +| 创建方式 | 选好友 + 搜索用户 ID 拉人 | 非好友也可拉入群 | +| 成员上限 | 200 人(默认) | im_groups.max_members 可配置 | +| 角色体系 | 三级:群主(2) + 管理员(1) + 普通成员(0) | 微信模式 | +| 管理操作 | 全功能 | 改群名/头像/公告、拉人/踢人、禁言/全体禁言、转让群主、解散、设管理员 | +| 入群方式 | 被拉入(无需审批)+ 主动申请(需审批) | 群主/管理员审批 | +| 退出机制 | 主动退出 + 显示系统消息 | "XX 退出/加入/被移出了群聊" | +| 历史消息 | 钉钉模式 | 新成员可查看所有历史消息 | +| 群搜索 | 全局搜索公开群 + 自己的群 | 默认可搜索,群主可设为不可搜索 | +| 群头像 | 默认图标 + 群主手动上传 | 使用 MinIO 存储 | +| 群昵称 | 成员可设置群内昵称 | 仅在该群内显示 | +| 免打扰 | 微信模式 | 仍计未读(灰色数字),不推送通知 | +| @提醒 | @某人 + @所有人 | 所有成员可 @所有人,输入 @ 弹出成员选择 | +| @通知 | 会话列表标记 | 显示 "[N条] @了我" | +| 正在输入 | 群聊不显示 | 仅单聊保留 | +| 消息撤回 | 成员 2 分钟,管理员无时限 | 群主/管理员可撤回任何人消息 | +| 撤回展示 | 区分操作者 | "管理员 XX 撤回了 YY 的一条消息" | +| 群公告 | 需要 | 群主/管理员发布,发布时推送系统消息通知全员 | + +### 2.2 已读回执决策 + +| 决策项 | 选定方案 | 说明 | +|--------|----------|------| +| 单聊粒度 | 会话级别 | 复用 last_read_msg_id,消息 ID ≤ 该值即已读 | +| 群聊粒度 | 消息级别 | im_message_reads 表记录每人每条消息的已读 | +| 展示方式 | 钉钉模式 | 单聊 "已读/未读",群聊 "X人已读" 可点击查看列表 | +| 存储方案 | 纯 PostgreSQL | im_message_reads 表(联合主键 message_id + user_id) | +| 推送策略 | 差异化 | 单聊实时推送已读变化,群聊仅推送已读计数变化 | +| 详情查看 | REST 按需拉取 | 点击 "X人已读" 时 GET /api/v1/im/messages/:id/reads | + +### 2.3 基础设施决策 + +| 决策项 | 选定方案 | 说明 | +|--------|----------|------| +| 文件存储 | MinIO(Docker 镜像) | 通用上传 API,后续消息类型扩展可复用 | +| 前端架构 | 群聊独立页面体系 | 不复用单聊页面,完全独立 | +| 会话列表 | 分 Tab | 单聊 Tab + 群聊 Tab | +| WS 事件 | 消息复用 + 管理独立 | im.message.* 复用,group.* 新增 | +| 消息搜索 | 统一全局搜索 | 单聊 + 群聊消息在同一搜索结果中 | +| 分支策略 | 从 phase2b 拉新分支 | feature/phase2c-group-read-receipt | +| 管理端 | 群列表 + 详情 + 解散 + 移除 | 4 个管理 API + 2 个前端页面 | + +--- + +## 三、架构设计 + +### 3.1 新增模块 + +| 模块 | 位置 | 职责 | +|------|------|------| +| group | app/group/ | 群聊管理(创建/加入/退出/角色/禁言/搜索/审批) | +| file | app/file/ | 文件上传(MinIO SDK 封装 + 通用上传 API) | +| storage | pkg/storage/ | MinIO 客户端初始化 + 配置 | + +### 3.2 模块目录结构 + +``` +backend/go-service/ +├── app/ +│ ├── group/ # [新增] 群聊管理模块 +│ │ ├── controller/ +│ │ │ └── group_controller.go # REST API 控制器(~15 个接口) +│ │ ├── service/ +│ │ │ └── group_service.go # 群创建/管理/审批/搜索业务逻辑 +│ │ ├── dao/ +│ │ │ └── group_dao.go # im_groups + 成员角色 + 入群申请 CRUD +│ │ ├── handler/ +│ │ │ └── group_handler.go # WS 群管理事件处理 +│ │ ├── model/ +│ │ │ ├── group.go # im_groups 模型 +│ │ │ └── join_request.go # im_group_join_requests 模型 +│ │ ├── router.go +│ │ └── provider.go +│ ├── file/ # [新增] 文件上传模块 +│ │ ├── controller/ +│ │ │ └── file_controller.go # POST /api/v1/upload +│ │ ├── service/ +│ │ │ └── file_service.go # MinIO 上传/删除封装 +│ │ ├── router.go +│ │ └── provider.go +│ ├── im/ # [扩展] +│ │ ├── dao/ +│ │ │ └── read_dao.go # [新增] im_message_reads 操作 +│ │ ├── model/ +│ │ │ └── message_read.go # [新增] im_message_reads 模型 +│ │ └── service/ +│ │ └── im_service.go # [扩展] 群消息/已读回执/管理员撤回 +│ └── constants/ +│ └── group.go # [新增] 群聊相关常量 +├── pkg/ +│ └── storage/ +│ └── minio.go # [新增] MinIO 客户端初始化 + +frontend/src/ +├── pages/ +│ ├── group/ # [新增] 群聊页面(独立体系) +│ │ ├── index.vue # 群聊会话列表 +│ │ ├── conversation.vue # 群聊对话页 +│ │ ├── create.vue # 创建群聊 +│ │ ├── settings.vue # 群聊设置 +│ │ ├── members.vue # 群成员列表 +│ │ ├── invite.vue # 邀请入群 +│ │ ├── join-requests.vue # 入群申请审批 +│ │ └── search.vue # 搜索公开群 +│ └── chat/ +│ └── read-detail.vue # [新增] 已读回执详情页 +├── store/ +│ └── group.js # [新增] 群聊 Pinia Store +├── api/ +│ ├── group.js # [新增] 群聊 API +│ └── file.js # [新增] 文件上传 API + +admin/src/ +├── views/ +│ └── group/ # [新增] 管理端群聊管理 +│ ├── list.vue # 群列表 +│ └── detail.vue # 群详情(含成员管理) +├── api/ +│ └── group.js # [新增] 管理端群聊 API +``` + +### 3.3 跨模块接口注入 + +延续 Phase 2a/2b 的接口注入模式,通过 Wire Bind 在 `app/provider/wire.go` 中统一绑定。 + +| 接口 | 定义模块 | 实现方 | 用途 | +|------|----------|--------|------| +| GroupMemberChecker | im/service | group/dao.GroupDAO | 检查用户是否为群成员 | +| GroupInfoGetter | im/service | group/dao.GroupDAO | 获取群信息(名称/头像/成员数) | +| GroupRoleChecker | im/service | group/dao.GroupDAO | 检查用户群角色(管理员撤回权限) | +| 已有接口 | - | - | FriendChecker / UserInfoGetter / FriendIDsGetter 等继续复用 | + +--- + +## 四、数据库设计 + +### 4.1 新增表 + +#### im_groups(群聊信息表) + +```sql +CREATE TABLE im_groups ( + id BIGSERIAL PRIMARY KEY, + conversation_id BIGINT NOT NULL UNIQUE REFERENCES im_conversations(id), + name VARCHAR(100) NOT NULL DEFAULT '', + avatar VARCHAR(500) DEFAULT '', + owner_id BIGINT NOT NULL, + notice TEXT DEFAULT '', + max_members INT NOT NULL DEFAULT 200, + is_searchable BOOLEAN NOT NULL DEFAULT TRUE, + is_all_muted BOOLEAN NOT NULL DEFAULT FALSE, + status SMALLINT NOT NULL DEFAULT 1, + created_at TIMESTAMP(0) NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP(0) NOT NULL DEFAULT NOW() +); + +COMMENT ON TABLE im_groups IS '群聊信息表'; +COMMENT ON COLUMN im_groups.id IS '群唯一标识'; +COMMENT ON COLUMN im_groups.conversation_id IS '关联 im_conversations.id'; +COMMENT ON COLUMN im_groups.name IS '群名称'; +COMMENT ON COLUMN im_groups.avatar IS '群头像 URL(MinIO)'; +COMMENT ON COLUMN im_groups.owner_id IS '群主用户 ID'; +COMMENT ON COLUMN im_groups.notice IS '群公告内容'; +COMMENT ON COLUMN im_groups.max_members IS '最大成员数,默认 200'; +COMMENT ON COLUMN im_groups.is_searchable IS '是否可被搜索发现'; +COMMENT ON COLUMN im_groups.is_all_muted IS '是否全体禁言'; +COMMENT ON COLUMN im_groups.status IS '群状态:1=正常,2=已解散'; + +CREATE INDEX idx_im_groups_owner ON im_groups(owner_id); +CREATE INDEX idx_im_groups_name ON im_groups USING gin(to_tsvector('simple', name)); +``` + +#### im_group_join_requests(入群申请表) + +```sql +CREATE TABLE im_group_join_requests ( + id BIGSERIAL PRIMARY KEY, + group_id BIGINT NOT NULL REFERENCES im_groups(id), + user_id BIGINT NOT NULL, + message TEXT DEFAULT '', + reviewer_id BIGINT DEFAULT NULL, + status SMALLINT NOT NULL DEFAULT 0, + created_at TIMESTAMP(0) NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP(0) NOT NULL DEFAULT NOW() +); + +COMMENT ON TABLE im_group_join_requests IS '入群申请表'; +COMMENT ON COLUMN im_group_join_requests.id IS '申请唯一标识'; +COMMENT ON COLUMN im_group_join_requests.group_id IS '目标群 ID'; +COMMENT ON COLUMN im_group_join_requests.user_id IS '申请人用户 ID'; +COMMENT ON COLUMN im_group_join_requests.message IS '申请附言'; +COMMENT ON COLUMN im_group_join_requests.reviewer_id IS '审批人用户 ID'; +COMMENT ON COLUMN im_group_join_requests.status IS '状态:0=待审批,1=通过,2=拒绝'; + +CREATE INDEX idx_group_join_req_group ON im_group_join_requests(group_id, status); +CREATE INDEX idx_group_join_req_user ON im_group_join_requests(user_id); +``` + +#### im_message_reads(消息已读记录表) + +```sql +CREATE TABLE im_message_reads ( + message_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + read_at TIMESTAMP(0) NOT NULL DEFAULT NOW(), + PRIMARY KEY (message_id, user_id) +); + +COMMENT ON TABLE im_message_reads IS '群聊消息已读记录表(消息级别)'; +COMMENT ON COLUMN im_message_reads.message_id IS '消息 ID'; +COMMENT ON COLUMN im_message_reads.user_id IS '已读用户 ID'; +COMMENT ON COLUMN im_message_reads.read_at IS '已读时间'; + +CREATE INDEX idx_msg_reads_user ON im_message_reads(user_id, read_at); +``` + +### 4.2 扩展现有表 + +#### im_conversation_members — 新增 5 个字段 + +```sql +ALTER TABLE im_conversation_members + ADD COLUMN role SMALLINT NOT NULL DEFAULT 0, + ADD COLUMN nickname VARCHAR(50) DEFAULT '', + ADD COLUMN is_muted BOOLEAN NOT NULL DEFAULT FALSE, + ADD COLUMN is_do_not_disturb BOOLEAN NOT NULL DEFAULT FALSE, + ADD COLUMN joined_at TIMESTAMP(0) DEFAULT NULL; + +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 '是否被禁言'; +COMMENT ON COLUMN im_conversation_members.is_do_not_disturb IS '是否消息免打扰(微信模式:仍计未读但灰色显示)'; +COMMENT ON COLUMN im_conversation_members.joined_at IS '加入群聊时间'; +``` + +#### im_messages — 新增 1 个字段 + +```sql +ALTER TABLE im_messages + ADD COLUMN at_user_ids BIGINT[] DEFAULT NULL; + +COMMENT ON COLUMN im_messages.at_user_ids IS '@提醒用户 ID 列表,NULL=无@,包含 0 表示 @所有人'; +``` + +### 4.3 数据库表关系图 + +``` +im_conversations (type=2 群聊) + │ 1:1 + ├── im_groups (群信息:名称/头像/公告/群主) + │ │ 1:N + │ └── im_group_join_requests (入群申请) + │ 1:N + ├── im_conversation_members (成员:+role/nickname/is_muted/is_do_not_disturb) + │ 1:N + └── im_messages (消息:+at_user_ids) + │ 1:N + └── im_message_reads (群聊消息已读记录) +``` + +--- + +## 五、API 设计 + +### 5.1 群聊 REST API(16 个) + +| # | 方法 | 路径 | 描述 | 权限 | +|---|------|------|------|------| +| 1 | POST | /api/v1/groups | 创建群聊 | 登录用户 | +| 2 | GET | /api/v1/groups/:id | 群详情 | 群成员 | +| 3 | PUT | /api/v1/groups/:id | 更新群信息 | 群主/管理员 | +| 4 | DELETE | /api/v1/groups/:id | 解散群聊 | 群主 | +| 5 | GET | /api/v1/groups/:id/members | 成员列表 | 群成员 | +| 6 | POST | /api/v1/groups/:id/members | 邀请/拉人入群 | 群主/管理员 | +| 7 | DELETE | /api/v1/groups/:id/members/:uid | 踢人 | 群主/管理员 | +| 8 | PUT | /api/v1/groups/:id/members/:uid/role | 设置/取消管理员 | 群主 | +| 9 | PUT | /api/v1/groups/:id/members/:uid/mute | 禁言/解除禁言 | 群主/管理员 | +| 10 | POST | /api/v1/groups/:id/leave | 退出群聊 | 群成员 | +| 11 | PUT | /api/v1/groups/:id/transfer | 转让群主 | 群主 | +| 12 | PUT | /api/v1/groups/:id/members/me/nickname | 修改群昵称 | 群成员 | +| 13 | POST | /api/v1/groups/:id/join-requests | 申请入群 | 登录用户 | +| 14 | GET | /api/v1/groups/:id/join-requests | 入群申请列表 | 群主/管理员 | +| 15 | PUT | /api/v1/groups/:id/join-requests/:rid | 审批入群申请 | 群主/管理员 | +| 16 | GET | /api/v1/groups/search | 搜索公开群 | 登录用户 | + +### 5.2 已读回执 REST API(2 个) + +| # | 方法 | 路径 | 描述 | +|---|------|------|------| +| 1 | GET | /api/v1/im/messages/:id/reads | 消息已读详情(谁读了,分页) | +| 2 | GET | /api/v1/im/messages/:id/read-count | 消息已读/未读计数 | + +### 5.3 文件上传 REST API(1 个) + +| # | 方法 | 路径 | 描述 | +|---|------|------|------| +| 1 | POST | /api/v1/upload | 通用文件上传(返回 MinIO URL) | + +### 5.4 管理端 REST API(4 个) + +| # | 方法 | 路径 | 描述 | +|---|------|------|------| +| 1 | GET | /api/v1/admin/groups | 群列表(分页 + 筛选) | +| 2 | GET | /api/v1/admin/groups/:id | 群详情(含成员列表) | +| 3 | DELETE | /api/v1/admin/groups/:id | 管理员解散群 | +| 4 | DELETE | /api/v1/admin/groups/:id/members/:uid | 管理员移除成员 | + +### 5.5 WebSocket 事件 + +#### 消息事件(复用 im.message.*,扩展群聊支持) + +| 事件 | 方向 | 变更说明 | +|------|------|----------| +| im.message.send | C→S | data 新增 at_user_ids 字段 | +| im.message.send.ack | S→C | 无变化 | +| im.message.new | S→C | data 新增 conv_type、at_user_ids 字段 | +| im.message.recall | C→S | 群聊时管理员可撤回他人消息(无时限) | +| im.message.recalled | S→C | 群聊时区分 "管理员 XX 撤回了 YY 的消息" | + +#### 已读回执事件(新增) + +| 事件 | 方向 | 说明 | +|------|------|------| +| im.message.read | C→S | 上报已读:单聊走 last_read_msg_id,群聊写 im_message_reads | +| im.message.read.ack | S→C | 单聊:实时推送已读状态给消息发送者 | +| im.message.read.count | S→C | 群聊:推送已读计数变化 {msg_id, read_count} | + +#### 群管理事件(新增 group.* 系列) + +| 事件 | 方向 | 说明 | +|------|------|------| +| group.member.join | S→C | 成员加入通知(含系统消息 "XX 加入了群聊") | +| group.member.leave | S→C | 成员退出通知(含系统消息 "XX 退出了群聊") | +| group.member.kicked | S→C | 成员被踢通知(含系统消息 "XX 被移出了群聊") | +| group.info.update | S→C | 群信息变更通知(名称/头像) | +| group.notice.update | S→C | 群公告变更通知(含系统消息) | +| group.dissolved | S→C | 群解散通知 | +| group.mute.update | S→C | 禁言状态变更通知(个人/全体) | +| group.join.request | S→C | 新入群申请(推送给群主/管理员) | +| group.join.approved | S→C | 入群申请通过(推送给申请人) | + +--- + +## 六、核心业务流程 + +### 6.1 创建群聊 + +``` +1. 用户选择好友 + 搜索用户 ID → 确定初始成员列表 +2. POST /api/v1/groups {name, member_ids[]} +3. GroupService: + a. 创建 im_conversations (type=2) + b. 创建 im_groups (关联 conversation_id) + c. 批量创建 im_conversation_members (创建者 role=2 群主, 其余 role=0) + d. 插入系统消息 "XX 创建了群聊,邀请了 A、B、C 加入" + e. PubSub 推送 group.member.join 给所有初始成员 +4. 返回群信息 + 会话 ID +``` + +### 6.2 群消息发送 + +``` +1. 发送者 WS: im.message.send {conversation_id, content, at_user_ids} +2. IMService.SendMessage: + a. 查询 im_conversations.type → 群聊分支 + b. 校验群成员身份 + 禁言检查(is_muted / is_all_muted,管理员豁免全体禁言) + c. 写入 im_messages (含 at_user_ids) + d. 更新 im_conversations.last_msg_* + e. 遍历群成员: + - 排除发送者 + - 非免打扰成员:递增 unread_count + Redis 全局未读 + - 免打扰成员:递增 unread_count,不递增 Redis 全局未读 + - 被 @的成员:记录 @计数(可存 im_conversation_members 或 Redis) + f. PubSub.PublishToUsers → 推送 im.message.new 给所有在线成员 +3. 发送者收到 im.message.send.ack +``` + +### 6.3 已读回执(单聊) + +``` +1. 用户打开单聊会话 +2. 前端 WS: im.message.read {conversation_id} +3. IMService: + a. 获取会话最新消息 ID → 更新 last_read_msg_id + b. 清零 unread_count + Redis 全局未读 + c. PubSub 推送 im.message.read.ack 给对方 + {conversation_id, reader_id, last_read_msg_id} +4. 对方前端收到后,对 ID ≤ last_read_msg_id 的自己发送的消息标记为 "已读" +``` + +### 6.4 已读回执(群聊) + +``` +1. 用户打开群聊会话,前端获取可视区域消息 ID 列表 +2. 前端 WS: im.message.read {conversation_id, message_ids[]} +3. IMService: + a. 批量写入 im_message_reads (ON CONFLICT DO NOTHING) + b. 更新 last_read_msg_id + 清零 unread_count + c. 查询受影响消息的 sender_id(去重) + d. 对每个 sender_id 推送 im.message.read.count + {msg_id, read_count, total_members} +4. 发送者前端更新 "X人已读" 显示 +5. 用户点击 "X人已读" → GET /api/v1/im/messages/:id/reads → 显示详情列表 +``` + +### 6.5 @提醒流程 + +``` +1. 发送者在输入框键入 @ → 弹出成员选择列表 +2. 选择成员(或 @所有人)→ 消息中插入 @昵称 标记 +3. 发送消息时 at_user_ids 包含被 @用户的 ID(0 表示 @所有人) +4. 接收方前端: + a. 收到 im.message.new 检查 at_user_ids + b. 如果包含自己的 ID 或 0 → 标记该会话 "被@" + c. 会话列表显示 "[N条] @了我" +``` + +--- + +## 七、前端页面规划 + +### 7.1 新增页面(9 个) + +| 页面 | 路径 | 功能描述 | +|------|------|----------| +| 群聊会话列表 | pages/group/index.vue | 群聊 Tab 页,显示群会话列表 | +| 群聊对话页 | pages/group/conversation.vue | 群消息展示 + 发送 + @选择器 | +| 创建群聊 | pages/group/create.vue | 选好友 + 搜索用户 + 填群名 | +| 群设置 | pages/group/settings.vue | 群信息/公告/成员概览/免打扰/退群/解散 | +| 群成员列表 | pages/group/members.vue | 完整成员列表 + 角色标识 + 管理操作 | +| 邀请入群 | pages/group/invite.vue | 搜索用户 ID 拉人入群 | +| 入群审批 | pages/group/join-requests.vue | 群主/管理员审批入群申请 | +| 搜索群聊 | pages/group/search.vue | 搜索公开群 + 申请加入 | +| 已读详情 | pages/chat/read-detail.vue | 已读/未读人员列表(单聊+群聊共用) | + +### 7.2 修改现有页面 + +| 页面 | 修改内容 | +|------|----------| +| pages/chat/index.vue | 改为 Tab 切换:单聊 Tab + 群聊 Tab | +| pages/chat/conversation.vue | 添加单聊已读状态展示("已读"/"未读") | +| pages/chat/search.vue | 全局搜索扩展支持群聊消息结果 | +| components/CustomTabBar.vue | TabBar 适配群聊入口 | + +### 7.3 管理端新增页面(2 个) + +| 页面 | 路径 | 功能描述 | +|------|------|----------| +| 群列表 | admin/src/views/group/list.vue | 群列表(搜索/分页/状态筛选) | +| 群详情 | admin/src/views/group/detail.vue | 群信息 + 成员列表 + 解散/移除操作 | + +--- + +## 八、Docker Compose 变更 + +在 `deploy/docker/docker-compose.dev.yml` 中新增 MinIO 服务: + +```yaml +minio: + image: minio/minio:latest + command: server /data --console-address ":9001" + ports: + - "9000:9000" + - "9001:9001" + environment: + MINIO_ROOT_USER: echochat + MINIO_ROOT_PASSWORD: echochat123456 + volumes: + - minio_data:/data + restart: unless-stopped +``` + +Go 后端 `config.yaml` 新增: + +```yaml +minio: + endpoint: "localhost:9000" + access_key: "echochat" + secret_key: "echochat123456" + bucket: "echochat" + use_ssl: false +``` + +--- + +## 九、常量定义规划 + +```go +// app/constants/group.go + +// 群成员角色 +const ( + GroupRoleMember = 0 // 普通成员 + GroupRoleAdmin = 1 // 管理员 + GroupRoleOwner = 2 // 群主 +) + +// 群状态 +const ( + GroupStatusActive = 1 // 正常 + GroupStatusDissolved = 2 // 已解散 +) + +// 入群申请状态 +const ( + JoinRequestPending = 0 // 待审批 + JoinRequestApproved = 1 // 已通过 + JoinRequestRejected = 2 // 已拒绝 +) + +// 系统消息类型(im_messages.type 扩展) +const ( + MessageTypeSystem = 10 // 系统通知消息 +) +``` + +--- + +## 十、DTO 扩展规划 + +### 10.1 群聊 DTO + +```go +// CreateGroupRequest 创建群聊请求 +type CreateGroupRequest struct { + Name string `json:"name" binding:"required"` + MemberIDs []int64 `json:"member_ids" binding:"required,min=1"` +} + +// GroupDTO 群详情 +type GroupDTO struct { + ID int64 `json:"id"` + ConversationID int64 `json:"conversation_id"` + Name string `json:"name"` + Avatar string `json:"avatar"` + OwnerID int64 `json:"owner_id"` + OwnerNickname string `json:"owner_nickname"` + Notice string `json:"notice"` + MemberCount int `json:"member_count"` + MaxMembers int `json:"max_members"` + IsSearchable bool `json:"is_searchable"` + IsAllMuted bool `json:"is_all_muted"` + Status int `json:"status"` + CreatedAt string `json:"created_at"` +} + +// GroupMemberDTO 群成员信息 +type GroupMemberDTO struct { + UserID int64 `json:"user_id"` + Username string `json:"username"` + Nickname string `json:"nickname"` // 群昵称(优先)或全局昵称 + Avatar string `json:"avatar"` + Role int `json:"role"` + IsMuted bool `json:"is_muted"` + IsOnline bool `json:"is_online"` + JoinedAt string `json:"joined_at"` +} +``` + +### 10.2 已读回执 DTO + +```go +// ReadDetailDTO 消息已读详情 +type ReadDetailDTO struct { + UserID int64 `json:"user_id"` + Nickname string `json:"nickname"` + Avatar string `json:"avatar"` + ReadAt string `json:"read_at"` +} + +// ReadCountDTO 消息已读计数 +type ReadCountDTO struct { + MessageID int64 `json:"message_id"` + ReadCount int `json:"read_count"` + UnreadCount int `json:"unread_count"` + TotalMembers int `json:"total_members"` +} +``` + +### 10.3 ConversationDTO 扩展 + +```go +// ConversationDTO 扩展:增加群聊字段 +type ConversationDTO struct { + // 原有字段... + GroupID *int64 `json:"group_id,omitempty"` // 群聊时的群 ID + GroupName string `json:"group_name,omitempty"` // 群名称 + GroupAvatar string `json:"group_avatar,omitempty"` // 群头像 + MemberCount int `json:"member_count,omitempty"` // 群成员数 + IsDoNotDisturb bool `json:"is_do_not_disturb"` // 是否免打扰 + AtMeCount int `json:"at_me_count,omitempty"` // 被@计数 +} +``` diff --git a/docs/plans/2026-03-04-phase2c-implementation.plan.md b/docs/plans/2026-03-04-phase2c-implementation.plan.md new file mode 100644 index 0000000..7bbb3c8 --- /dev/null +++ b/docs/plans/2026-03-04-phase2c-implementation.plan.md @@ -0,0 +1,562 @@ +# Phase 2c 实施计划:群聊与已读回执 + +> **状态:** 📋 待执行 +> **设计文档:** `docs/plans/2026-03-04-phase2c-design.md` +> **分支:** `feature/phase2c-group-read-receipt` +> **预计 Task 数:** 14 个 +> **最后更新:** 2026-03-04 + +--- + +## Task 总览 + +| Task | 阶段 | 描述 | 依赖 | 状态 | +|------|------|------|------|------| +| Task 0 | 基础设施 | MinIO Docker + SDK + 通用上传 API | 无 | 📋 | +| Task 1 | 基础设施 | 数据库迁移 + Model + 常量定义 | 无 | 📋 | +| Task 2 | 群聊后端 | Group DAO 层 | Task 1 | 📋 | +| Task 3 | 群聊后端 | Group Service 业务逻辑 | Task 2 | 📋 | +| Task 4 | 群聊后端 | Group Controller + Router + Wire | Task 3 | 📋 | +| Task 5 | 群聊后端 | WS 群管理事件处理器 | Task 3 | 📋 | +| Task 6 | 群聊后端 | IM Service 扩展(群消息 + @提醒 + 管理员撤回) | Task 2 | 📋 | +| Task 7 | 已读回执 | 已读回执后端(ReadDAO + Service + API + WS 推送) | Task 1 | 📋 | +| Task 8 | 已读回执 | 前端已读回执 UI(单聊标记 + 群聊计数 + 详情页) | Task 7 | 📋 | +| Task 9 | 前端 | 群聊 Store + API 封装 + WS 事件监听 | Task 4, 5 | 📋 | +| Task 10 | 前端 | 群聊核心页面(Tab 改造 + 群对话页 + 创建群页) | Task 9 | 📋 | +| Task 11 | 前端 | 群聊管理页面(群设置 + 成员 + 邀请 + @选择器) | Task 10 | 📋 | +| Task 12 | 前端 | 群聊辅助功能(审批 + 搜索 + 免打扰 + 公告 UI) | Task 11 | 📋 | +| Task 13 | 管理端 | 管理端群聊管理 + 全量文档更新 + 代码审查 | Task 12 | 📋 | + +--- + +## Task 0: MinIO Docker + SDK + 通用上传 API + +**目标:** 引入 MinIO 文件存储服务,封装通用上传能力 + +### 交付物 + +1. **Docker Compose** + - `deploy/docker/docker-compose.dev.yml` 添加 minio 服务 + - volumes 持久化 `minio_data` + - 端口:9000(API)+ 9001(Console) + +2. **Go 后端配置** + - `config/config.yaml` 添加 minio 配置节 + - `config/config.go` 添加 MinioConfig 结构体 + - `pkg/storage/minio.go` — MinIO 客户端初始化(NewMinioClient) + +3. **文件模块** + - `app/file/service/file_service.go` — Upload(ctx, file) → URL + - `app/file/controller/file_controller.go` — POST /api/v1/upload + - `app/file/router.go` — 路由注册 + - `app/file/provider.go` — Wire ProviderSet + +4. **集成** + - `app/provider/wire.go` 添加 FileSet + - `router/router.go` 注册 file 路由 + +### 验收标准 + +- `docker compose up -d minio` 正常启动 +- `curl -F "file=@test.png" http://localhost:8085/api/v1/upload` 返回 MinIO URL +- MinIO Console (localhost:9001) 可看到上传的文件 + +--- + +## Task 1: 数据库迁移 + Model + 常量定义 + +**目标:** 建立 Phase 2c 所需的数据库表和 Go 模型 + +### 交付物 + +1. **新增表 SQL(init.sql 追加)** + - `im_groups` 表(含所有注释和索引) + - `im_group_join_requests` 表 + - `im_message_reads` 表 + +2. **ALTER 现有表** + - `im_conversation_members` 新增 5 字段:role, nickname, is_muted, is_do_not_disturb, joined_at + - `im_messages` 新增 1 字段:at_user_ids (BIGINT[]) + +3. **Go Model 文件** + - `app/group/model/group.go` — Group 结构体 + - `app/group/model/join_request.go` — JoinRequest 结构体 + - `app/im/model/message_read.go` — MessageRead 结构体 + - `app/im/model/conversation_member.go` — 扩展 ConversationMember 结构体 + - `app/im/model/message.go` — 扩展 Message 结构体(at_user_ids) + +4. **常量文件** + - `app/constants/group.go` — 群角色、群状态、申请状态、系统消息类型 + +5. **DTO 文件** + - `app/dto/group_dto.go` — 群聊相关 DTO + - `app/dto/im_dto.go` — 扩展:已读回执 DTO + ConversationDTO 群聊字段 + +6. **GORM AutoMigrate** + - `app/provider/wire_gen.go` 中确保新 Model 注册 + +### 验收标准 + +- 数据库迁移成功,`\d+ im_groups` 等表存在 +- `go build` 编译通过 +- 常量和 DTO 定义完整 + +--- + +## Task 2: Group DAO 层 + +**目标:** 实现群聊数据访问层 + +### 交付物 + +`app/group/dao/group_dao.go` + +| 方法 | 功能 | +|------|------| +| CreateGroup | 创建 im_groups 记录 | +| GetByID | 根据 ID 查群信息 | +| GetByConversationID | 根据会话 ID 查群信息 | +| UpdateInfo | 更新群名/头像/公告/可搜索性/全体禁言 | +| UpdateOwner | 转让群主 | +| Dissolve | 解散群(status → 2) | +| GetMembers | 获取群成员列表(含用户信息 JOIN auth_users) | +| GetMemberRole | 查询用户在群内的角色 | +| IsMember | 检查用户是否为群成员 | +| AddMembers | 批量添加成员(im_conversation_members) | +| RemoveMember | 移除成员(DELETE im_conversation_members) | +| UpdateMemberRole | 更新成员角色 | +| UpdateMemberMute | 更新成员禁言状态 | +| UpdateMemberNickname | 更新群内昵称 | +| GetMemberCount | 获取群成员数 | +| SearchGroups | 搜索公开群(is_searchable=true,GIN 全文索引) | +| GetUserGroups | 获取用户加入的所有群 | +| CreateJoinRequest | 创建入群申请 | +| GetPendingJoinRequests | 获取待审批申请列表 | +| UpdateJoinRequest | 更新申请状态 | +| HasPendingRequest | 检查是否有待处理的申请 | + +### 验收标准 + +- 所有方法有完整的日志记录(LogFunctionEntry/Exit) +- `go build` 编译通过 + +--- + +## Task 3: Group Service 业务逻辑 + +**目标:** 实现群聊核心业务逻辑 + +### 交付物 + +`app/group/service/group_service.go` + +| 方法 | 功能 | 关键逻辑 | +|------|------|----------| +| CreateGroup | 创建群聊 | 创建 conversation(type=2) + groups + members,插入系统消息,推送通知 | +| GetGroupInfo | 获取群详情 | 包含成员数 | +| UpdateGroupInfo | 更新群信息 | 权限校验(群主/管理员),推送 group.info.update | +| UpdateNotice | 更新群公告 | 权限校验,插入系统消息,推送 group.notice.update | +| DissolveGroup | 解散群聊 | 仅群主,推送 group.dissolved | +| InviteMembers | 邀请入群 | 权限校验,检查上限,批量添加,系统消息,推送 group.member.join | +| KickMember | 踢人 | 层级权限(群主>管理员>成员),系统消息,推送 group.member.kicked | +| LeaveGroup | 退出群 | 群主不能退出(需先转让),系统消息,推送 group.member.leave | +| TransferOwner | 转让群主 | 仅群主,修改双方角色 | +| SetAdmin | 设置/取消管理员 | 仅群主 | +| MuteMember | 禁言/解除 | 群主/管理员(不能禁言同级或上级) | +| SetAllMuted | 全体禁言 | 群主/管理员 | +| UpdateNickname | 修改群昵称 | 群成员自行修改 | +| SearchGroups | 搜索公开群 | 分页 | +| ApplyJoin | 申请入群 | 创建申请,推送 group.join.request 给群主/管理员 | +| GetJoinRequests | 获取申请列表 | 权限校验 | +| ReviewJoinRequest | 审批申请 | 通过→添加成员+系统消息+推送,拒绝→更新状态 | + +**接口依赖:** +- `UserInfoGetter` — 获取用户信息(批量) +- `PubSub` — 推送通知 + +### 验收标准 + +- 所有权限校验逻辑完整(群主 > 管理员 > 成员) +- 系统消息正确写入 im_messages (type=10) +- PubSub 推送事件正确 + +--- + +## Task 4: Group Controller + Router + Wire + +**目标:** 暴露群聊 REST API 并集成到依赖注入 + +### 交付物 + +1. **Controller** + - `app/group/controller/group_controller.go` — 16 个 REST API 处理函数 + - 统一使用 `utils.Response*` 系列响应 + - `handleError` 覆盖所有已知业务错误 + +2. **Router** + - `app/group/router.go` — 路由注册 + - 路径前缀 `/api/v1/groups` + - JWT 中间件保护 + +3. **Wire 集成** + - `app/group/provider.go` — GroupSet + - `app/provider/wire.go` — 添加 GroupSet + 新接口绑定 + - `app/provider/wire_gen.go` — 重新生成 + - `router/router.go` — 注册 group 路由 + +### 验收标准 + +- `go build` 编译通过 +- 所有 16 个 API 端点可访问(需 JWT Token) +- API 响应格式统一 + +--- + +## Task 5: WS 群管理事件处理器 + +**目标:** 实现群管理相关的 WebSocket 事件推送 + +### 交付物 + +`app/group/handler/group_handler.go` + +| WS 事件 | 触发时机 | 推送目标 | +|---------|----------|----------| +| group.member.join | 成员加入 | 群所有成员 | +| group.member.leave | 成员退出 | 群所有成员 | +| group.member.kicked | 成员被踢 | 群所有成员 + 被踢者 | +| group.info.update | 群信息变更 | 群所有成员 | +| group.notice.update | 群公告变更 | 群所有成员 | +| group.dissolved | 群解散 | 群所有成员 | +| group.mute.update | 禁言变更 | 群所有成员 | +| group.join.request | 新入群申请 | 群主 + 管理员 | +| group.join.approved | 申请通过 | 申请人 | + +**注册方式:** 通过 Hub.RegisterEvent 注册到事件路由表(如有 C→S 事件),S→C 推送通过 PubSub.PublishToUser/PublishToUsers。 + +### 验收标准 + +- 群管理操作后相关成员能收到实时通知 +- 系统消息在群聊中正确显示 + +--- + +## Task 6: IM Service 扩展(群消息 + @提醒 + 管理员撤回) + +**目标:** 扩展现有 IMService 以支持群聊消息场景 + +### 交付物 + +1. **IMService 方法扩展** + +| 方法 | 变更内容 | +|------|----------| +| SendMessage | 增加群聊分支:成员校验、禁言检查、批量未读递增、免打扰跳过 Redis | +| RecallMessage | 增加群聊分支:管理员可撤回他人消息无时限,撤回展示区分操作者 | +| GetConversationList | 增加群聊会话:填充群名/群头像/成员数/免打扰/被@计数 | +| GetHistoryMessages | 群聊适配:返回发送者群昵称(优先于全局昵称) | + +2. **新增接口定义**(`app/im/service/im_service.go`) + +```go +type GroupMemberChecker interface { + IsMember(ctx context.Context, conversationID, userID int64) (bool, error) + GetMemberRole(ctx context.Context, conversationID, userID int64) (int, error) + IsMuted(ctx context.Context, conversationID, userID int64) (bool, error) + IsAllMuted(ctx context.Context, conversationID int64) (bool, error) +} + +type GroupInfoGetter interface { + GetByConversationID(ctx context.Context, conversationID int64) (*GroupBasicInfo, error) + GetMemberCount(ctx context.Context, groupID int64) (int, error) +} +``` + +3. **SendMessage 扩展** + - `app/dto/im_dto.go` — SendMessageRequest 新增 at_user_ids 字段 + - `app/im/handler/event_handler.go` — 适配群消息推送(推给所有群成员) + - im.message.new 推送增加 conv_type、at_user_ids 字段 + +4. **免打扰逻辑** + - 免打扰成员:递增会话 unread_count,不递增 Redis 全局未读 + - 会话列表中免打扰会话显示灰色未读数 + +### 验收标准 + +- 群消息发送/撤回功能正常 +- 禁言用户发消息被拒 +- @提醒字段正确存储和推送 +- 免打扰用户不增加全局未读数 + +--- + +## Task 7: 已读回执后端 + +**目标:** 实现已读回执完整后端逻辑 + +### 交付物 + +1. **ReadDAO** + - `app/im/dao/read_dao.go` + - BatchCreate(ctx, messageIDs[], userID) — 批量写入已读记录 + - GetMessageReadUsers(ctx, messageID, page, limit) — 查询已读用户列表 + - GetMessageReadCount(ctx, messageID) — 查询已读计数 + - GetBatchReadCounts(ctx, messageIDs[]) — 批量查询已读计数 + +2. **IMService 扩展** + - MarkRead — 重构:单聊走 last_read_msg_id,群聊写 im_message_reads + - GetMessageReadDetail — 已读详情列表(含用户信息) + - GetMessageReadCount — 已读/未读计数 + +3. **REST API** + - GET /api/v1/im/messages/:id/reads — 已读详情 + - GET /api/v1/im/messages/:id/read-count — 已读计数 + - Controller + Router 更新 + +4. **WS 事件** + - im.message.read — 重构:单聊推 read.ack,群聊推 read.count + - im.message.read.ack — 单聊实时推送 + - im.message.read.count — 群聊计数推送 + +### 验收标准 + +- 单聊:打开会话后对方看到 "已读" +- 群聊:打开会话后发送者看到 "X人已读" +- 点击 "X人已读" 可查看已读/未读人员列表 + +--- + +## Task 8: 前端已读回执 UI + +**目标:** 前端实现已读回执展示(使用 ui-ux-pro-max 设计) + +### 交付物 + +1. **chat Store 扩展**(`store/chat.js`) + - 新增 readStatus 状态管理 + - WS 监听 im.message.read.ack / im.message.read.count + - 打开会话时发送 im.message.read + +2. **单聊已读标记**(修改 `pages/chat/conversation.vue`) + - 自己发的消息下方显示 "已读" / "未读" + - 基于 last_read_msg_id 判断 + +3. **群聊已读计数**(后续 Task 10 的 group/conversation.vue 中实现基础展示) + - 消息下方显示 "X人已读" + - 点击跳转已读详情页 + +4. **已读详情页**(新增 `pages/chat/read-detail.vue`) + - Tab 切换:已读 / 未读 + - 用户列表(头像 + 昵称 + 已读时间) + - 调用 GET /api/v1/im/messages/:id/reads + +### 验收标准 + +- 单聊对话页正确显示 "已读"/"未读" +- 已读详情页正确展示已读/未读人员 +- 使用 ui-ux-pro-max 设计规范 + +--- + +## Task 9: 前端群聊 Store + API 封装 + +**目标:** 建立前端群聊数据管理层 + +### 交付物 + +1. **API 封装** + - `api/group.js` — 16 个群聊 REST API 封装 + - `api/file.js` — 文件上传 API 封装 + +2. **群聊 Store**(`store/group.js`) + - state: groupConversations, currentGroup, groupMessages, groupMembers + - actions: loadGroupConversations, sendGroupMessage, loadGroupHistory, ... + - WS 监听: im.message.new(conv_type=2), group.* 系列事件 + - 群消息缓存策略(同 chat.js 模式) + +3. **WS 事件注册** + - App.vue `_initGlobalWS` 中初始化 groupStore.initWsListeners() + - 处理 group.member.join/leave/kicked/info.update/notice.update/dissolved 等 + +### 验收标准 + +- API 封装完整,方法命名清晰 +- Store 能正确管理群聊状态 +- WS 群管理事件正确触发 Store 更新 + +--- + +## Task 10: 群聊核心页面(ui-ux-pro-max) + +**目标:** 实现群聊核心交互页面 + +### 交付物 + +1. **会话列表 Tab 改造**(修改 `pages/chat/index.vue`) + - 顶部增加 Tab 切换:单聊 / 群聊 + - 群聊 Tab 展示群会话列表(群名、群头像、最后消息、未读数) + - 免打扰群未读数显示为灰色 + - 被@标记:"[N条] @了我" + +2. **群聊对话页**(新增 `pages/group/conversation.vue`) + - 消息列表(显示发送者昵称 + 头像) + - @成员选择器(输入 @ 弹出成员列表) + - 消息发送(含 at_user_ids) + - 消息撤回(管理员额外权限) + - 系统消息特殊展示(居中灰色文字) + - "X人已读" 显示 + 点击跳转详情 + +3. **创建群聊页**(新增 `pages/group/create.vue`) + - 好友列表多选 + - 搜索用户 ID 添加 + - 填写群名称 + - 确认创建 + +4. **CustomTabBar 适配** + - TabBar 新增或适配群聊入口(如果需要) + +### 验收标准 + +- Tab 切换流畅,单聊/群聊列表独立 +- 群聊对话页消息展示正确 +- @选择器交互流畅(输入 @ 弹出选择列表) +- 创建群聊后自动跳转到群聊对话页 + +--- + +## Task 11: 群聊管理页面(ui-ux-pro-max) + +**目标:** 实现群聊管理相关页面 + +### 交付物 + +1. **群设置页**(新增 `pages/group/settings.vue`) + - 群信息展示(名称/头像/公告/群 ID) + - 群信息编辑(群主/管理员可修改名称/头像/公告) + - 成员概览(前 N 个头像 + "查看全部") + - 免打扰开关 + - 群昵称设置 + - 退出群聊 / 解散群聊 + +2. **群成员列表页**(新增 `pages/group/members.vue`) + - 完整成员列表 + - 角色标识(群主皇冠/管理员盾牌/成员无标识) + - 管理操作入口(踢人/禁言/设管理员)— 根据当前用户角色显示 + - 搜索成员 + +3. **邀请入群页**(新增 `pages/group/invite.vue`) + - 好友列表选择(排除已在群内的) + - 搜索用户 ID 添加 + +### 验收标准 + +- 群设置页信息展示完整 +- 角色权限控制正确(群主能看到所有管理入口,普通成员看不到) +- 邀请页排除已有成员 + +--- + +## Task 12: 群聊辅助功能(ui-ux-pro-max) + +**目标:** 实现群聊辅助功能页面 + +### 交付物 + +1. **入群申请审批页**(新增 `pages/group/join-requests.vue`) + - 待审批申请列表(申请人信息 + 附言 + 时间) + - 通过/拒绝操作 + - 已处理记录 + +2. **搜索群聊页**(新增 `pages/group/search.vue`) + - 关键词搜索公开群 + - 搜索结果:群名、头像、成员数、简介 + - 申请加入(填写申请附言) + - 已加入的群直接进入 + +3. **免打扰 UI** + - 群设置页免打扰开关 + - 会话列表免打扰标识(灰色未读数) + +4. **群公告 UI** + - 群设置页公告展示 + 编辑 + - 群公告系统消息展示 + +5. **全局消息搜索扩展**(修改 `pages/chat/search.vue`) + - 搜索结果包含群聊消息 + - 结果标识会话类型(单聊/群聊图标) + +### 验收标准 + +- 入群审批流程完整(申请→通知→审批→入群) +- 群搜索结果准确 +- 免打扰功能正常 +- 全局搜索包含群聊消息 + +--- + +## Task 13: 管理端 + 文档更新 + 代码审查 + +**目标:** 管理端群聊管理 + 全量文档同步 + 代码审查 + +### 交付物 + +1. **管理端后端** + - `app/admin/service/group_manage_service.go` — 群列表/详情/解散/移除 + - `app/admin/controller/group_manage_controller.go` — 4 个 REST API + - `app/admin/router.go` — 路由扩展 + +2. **管理端前端** + - `admin/src/views/group/list.vue` — 群列表页(搜索/分页/状态筛选) + - `admin/src/views/group/detail.vue` — 群详情页(成员管理/解散) + - `admin/src/api/group.js` — API 封装 + - 侧边栏菜单添加群聊管理入口 + +3. **文档更新** + - `docs/progress/CURRENT_STATUS.md` — 进度更新 + - `.cursor/rules/project-context.mdc` — 记忆更新 + - `docs/architecture/system-architecture.md` — 架构更新 + - `docs/plans/2026-02-27-echochat-system-design.md` — 总体设计更新 + - `docs/api/frontend/im.md` — IM API 文档更新 + - `docs/api/frontend/group.md` — 新增群聊 API 文档 + - `docs/api/websocket.md` — WS 事件文档更新 + - `docs/api/admin/group.md` — 新增管理端群聊 API 文档 + - `docs/api/README.md` — 导航更新 + +4. **代码审查** + - 使用 code-reviewer 子代理进行结构化审查 + - 修复审查发现的问题 + +### 验收标准 + +- 管理端群列表/详情页功能正常 +- 管理员可解散群/移除成员 +- 所有文档与代码保持一致 +- 代码审查通过 + +--- + +## 实施依赖关系图 + +``` +Task 0 (MinIO) ──────────────────────────────────────────────┐ +Task 1 (DB迁移) ──┬── Task 2 (Group DAO) ──┬── Task 3 (Group Service) ──┬── Task 4 (Controller+Wire) + │ │ └── Task 5 (WS Handler) + │ └── Task 6 (IM扩展) + └── Task 7 (已读回执后端) ── Task 8 (已读回执前端) + │ +Task 4 + Task 5 ── Task 9 (Store+API) ── Task 10 (核心页面) ── Task 11 (管理页面) ── Task 12 (辅助功能) + │ +Task 0 + Task 12 ── Task 13 (管理端+文档+审查) +``` + +--- + +## 开发注意事项 + +1. **代码风格一致性**:严格遵循 Phase 2b 的代码风格(日志记录、错误处理、常量命名、DTO 设计) +2. **接口注入模式**:新模块间通信必须走 interface injection,禁止直接 import +3. **批量查询优化**:群成员信息获取使用批量查询 + Map 映射,避免 N+1 +4. **前端设计规范**:所有前端页面使用 ui-ux-pro-max 技能包设计 +5. **系统消息**:群管理操作产生的系统消息统一使用 type=10(MessageTypeSystem),内容格式化 +6. **权限层级**:群主(2) > 管理员(1) > 成员(0),操作时必须校验层级 +7. **Wire 依赖**:Phase 2b 中 Wire 有过手动 patch 历史,注意检查 wire_gen.go 一致性 diff --git a/docs/progress/CURRENT_STATUS.md b/docs/progress/CURRENT_STATUS.md index ac2d3d3..1fce81c 100644 --- a/docs/progress/CURRENT_STATUS.md +++ b/docs/progress/CURRENT_STATUS.md @@ -1,10 +1,10 @@ # EchoChat 项目开发进度 -> **最后更新**:2026-03-03(Phase 2b 全部完成,含代码审查修复 + 用户测试修复) -> **当前阶段**:Phase 2b 全部完成,准备进入 Phase 2c -> **当前分支**:`feature/phase2b-instant-messaging` -> **实施计划**:`docs/plans/2026-03-03-phase2b-implementation.plan.md` -> **设计文档**:`docs/plans/2026-03-03-phase2b-design.md` +> **最后更新**:2026-03-04(Phase 2c 设计完成,待实施) +> **当前阶段**:Phase 2c 设计完成,准备开始实施 +> **当前分支**:`feature/phase2b-instant-messaging`(Phase 2c 分支待创建) +> **实施计划**:`docs/plans/2026-03-04-phase2c-implementation.plan.md` +> **设计文档**:`docs/plans/2026-03-04-phase2c-design.md` --- @@ -237,11 +237,44 @@ cd frontend && npm run dev:h5 --- -## 八、下一阶段规划 +## 八、下一阶段:Phase 2c — 群聊与已读回执 + +> **状态:** 设计完成,待实施 +> **设计文档:** `docs/plans/2026-03-04-phase2c-design.md` +> **实施计划:** `docs/plans/2026-03-04-phase2c-implementation.plan.md` +> **分支:** `feature/phase2c-group-read-receipt`(待创建,从 phase2b 拉出) + +### 功能范围 + +| 模块 | 内容 | +|------|------| +| 群聊管理 | 建群/加入/退出/解散/搜索/三级角色/禁言/全体禁言/群公告/群昵称/免打扰 | +| 群消息 | 复用 im.message.* 事件 + @某人/@所有人 + 管理员撤回(无时限)+ 系统消息 | +| 已读回执 | 单聊会话级(last_read_msg_id)+ 群聊消息级(im_message_reads 表)+ 实时推送 | +| MinIO | Docker 容器 + Go SDK + 通用上传 API(群头像) | +| 管理端 | 群列表/群详情/解散群/移除成员 | +| 前端 | 9 个新页面 + 群聊 Store + 会话列表 Tab 改造 | + +### Task 概览(14 个) + +| Task | 描述 | 状态 | +|------|------|------| +| Task 0 | MinIO Docker + SDK + 通用上传 API | 📋 | +| Task 1 | 数据库迁移 + Model + 常量 | 📋 | +| Task 2 | Group DAO 层 | 📋 | +| Task 3 | Group Service 业务逻辑 | 📋 | +| Task 4 | Group Controller + Router + Wire | 📋 | +| Task 5 | WS 群管理事件处理器 | 📋 | +| Task 6 | IM Service 扩展(群消息/@提醒/管理员撤回) | 📋 | +| Task 7 | 已读回执后端 | 📋 | +| Task 8 | 前端已读回执 UI | 📋 | +| Task 9 | 前端群聊 Store + API + WS 监听 | 📋 | +| Task 10 | 群聊核心页面(Tab + 对话 + 创建) | 📋 | +| Task 11 | 群聊管理页面(设置 + 成员 + 邀请 + @选择器) | 📋 | +| Task 12 | 群聊辅助功能(审批 + 搜索 + 免打扰 + 公告) | 📋 | +| Task 13 | 管理端 + 文档更新 + 代码审查 | 📋 | + +### 留待后续阶段 -### Phase 2c - 群聊与增强(待规划) -- 群聊会话(建群/加入/退出/管理) -- 群消息收发 -- 已读回执(单聊 + 群聊) - 消息类型扩展(图片/语音/文件) - 管理端消息管理功能