diff --git a/README.md b/README.md index e69de29..bdb019c 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,147 @@ +# EchoChat - 音视频会议直播系统 + +EchoChat 是一套跨端可用、可扩展、可演进的实时音视频会议直播系统。支持即时聊天、多人音视频会议、互动直播等核心功能,采用控制面与媒体面彻底分离的架构设计。 + +--- + +## 技术栈 + +| 层级 | 技术 | 说明 | +|------|------|------| +| 前台前端 | uniapp (Vue 3) + mediasoup-client | 多端适配(H5/App/小程序) | +| 后台管理端 | Vue 3 + Vite + Element Plus | PC Web 管理后台 | +| 后端服务 | Go (Gin + GORM + Wire + zap) | 业务逻辑、信令控制 | +| 媒体服务 | Node.js + mediasoup | SFU 媒体控制与转发 | +| 数据库 | PostgreSQL 16 | 持久化数据存储 | +| 缓存 | Redis 7 | 实时状态、会话缓存 | +| 部署 | Docker Compose / Nginx | 容器化部署,预留 K8s | + +--- + +## 系统架构 + +``` +客户端 (uniapp / Vue3 管理端) + │ + │ WebSocket + HTTP + │ +Go 单体服务 (模块化) + │ ├── auth 用户认证鉴权 + │ ├── im 即时通讯 + │ ├── contact 联系人管理 + │ ├── meeting 会议控制/信令 + │ ├── notify 消息通知 + │ └── admin 后台管理 + │ + ├── PostgreSQL (持久化数据) + ├── Redis (实时状态) + │ + │ HTTP + │ +mediasoup Node 服务 + │ IPC +mediasoup Worker (C++ SFU) +``` + +核心设计思想:**控制面与媒体面分离**。Go 处理所有业务逻辑和信令控制,mediasoup 专注音视频媒体转发,音视频流直连 SFU Worker,不经过 Go 服务。 + +--- + +## 项目结构 + +``` +EchoChat/ +├── frontend/ # 前台用户端 (uniapp) +├── admin/ # 后台管理端 (Vue 3 + Element Plus) +├── backend/ +│ └── go-service/ # Go 后端服务 +├── media-server/ # mediasoup Node 媒体服务 +├── deploy/ # 部署配置 (Docker / Nginx / K8s) +├── docs/ # 项目文档 +│ ├── plans/ # 设计方案与实施计划 +│ ├── api/ # API 接口文档 +│ └── architecture/ # 架构设计文档 +├── scripts/ # 脚本工具 +└── README.md +``` + +--- + +## 快速开始 + +### 环境要求 + +- Go 1.22+ +- Node.js 18+ +- Docker & Docker Compose +- PostgreSQL 16 (通过 Docker) +- Redis 7 (通过 Docker) + +### 1. 启动基础设施 + +```bash +cd deploy +docker compose -f docker-compose.dev.yml up -d +``` + +### 2. 启动 Go 后端 + +```bash +cd backend/go-service +go mod tidy +go run cmd/server/main.go +``` + +### 3. 启动前台前端 + +```bash +cd frontend +npm install +npm run dev:h5 +``` + +### 4. 启动管理端 + +```bash +cd admin +npm install +npm run dev +``` + +--- + +## MVP 功能规划(第一期) + +- [x] 设计方案与架构文档 +- [ ] 用户注册/登录(邮箱+密码、用户名+密码) +- [ ] 即时聊天(单聊 + 群聊,文字/图片/文件) +- [ ] 联系人/好友管理 +- [ ] 多人音视频会议(即时会议 + 预约会议) +- [ ] 消息通知系统 +- [ ] 后台管理端(用户管理、会议监控、系统配置) + +## 后续规划 + +- 屏幕共享 +- 微信授权登录 +- 互动直播(主播/观众/弹幕) +- 会议录制与回放 +- 微服务拆分 + K8s 部署 +- AI 辅助功能(语音转文字、会议纪要) + +--- + +## 文档导航 + +| 文档 | 路径 | 说明 | +|------|------|------| +| 整体设计方案 | [docs/plans/2026-02-27-echochat-system-design.md](docs/plans/2026-02-27-echochat-system-design.md) | 系统完整设计方案 | +| 第一阶段实施计划 | [docs/plans/2026-02-27-phase1-foundation-and-auth.md](docs/plans/2026-02-27-phase1-foundation-and-auth.md) | 基础设施+用户体系实施步骤 | +| 系统架构文档 | [docs/architecture/system-architecture.md](docs/architecture/system-architecture.md) | 架构分层与技术选型 | +| API 接口文档 | [docs/api/](docs/api/) | 按模块拆分的 REST API + WebSocket 事件定义 | + +--- + +## 开源协议 + +MIT License diff --git a/docs/api/README.md b/docs/api/README.md new file mode 100644 index 0000000..593ad1a --- /dev/null +++ b/docs/api/README.md @@ -0,0 +1,138 @@ +# EchoChat API 接口文档 + +> 本目录包含 EchoChat 系统所有接口定义,按功能模块拆分为独立文档,便于维护和查阅。 +> 架构设计见 `docs/architecture/system-architecture.md` +> 完整设计方案见 `docs/plans/2026-02-27-echochat-system-design.md` + +--- + +## 文档导航 + +| 文档 | 模块 | 说明 | +|------|------|------| +| [auth.md](auth.md) | 认证模块 | 用户注册、登录、Token 管理、个人信息 | +| [contact.md](contact.md) | 联系人模块 | 好友管理、好友分组 | +| [im.md](im.md) | 即时通讯模块 | 会话管理、消息历史、群聊管理 | +| [meeting.md](meeting.md) | 会议模块 | 即时会议、预约会议、加入/离开会议 | +| [notify.md](notify.md) | 通知模块 | 通知列表、已读管理 | +| [admin.md](admin.md) | 后台管理模块 | 用户管理、会议监控、系统配置、操作日志 | +| [websocket.md](websocket.md) | WebSocket 协议 | 实时消息、会议信令、在线状态事件 | + +--- + +## 通用规范 + +### 基础 URL + +| 环境 | 地址 | +|------|------| +| 开发环境 | `http://localhost:8080` | +| 生产环境 | `https://api.echochat.com`(待定) | + +### 认证方式 + +除登录/注册等公开接口外,所有接口均需在请求头中携带 JWT Token: + +``` +Authorization: Bearer +``` + +### 统一响应格式 + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": { ... }, + "timestamp": 1740700000 +} +``` + +**错误响应:** +```json +{ + "code": 1001, + "message": "参数错误:邮箱格式不正确", + "data": null, + "timestamp": 1740700000 +} +``` + +### 错误码定义 + +#### 通用错误码(1000-1099) + +| 错误码 | 含义 | 说明 | +|--------|------|------| +| 0 | 成功 | 请求处理成功 | +| 1001 | 参数错误 | 请求参数缺失或格式不正确 | +| 1002 | 未认证 | Token 缺失、过期或无效 | +| 1003 | 权限不足 | 当前用户角色无权执行此操作 | +| 1004 | 资源不存在 | 请求的目标资源不存在 | +| 1005 | 操作重复 | 如重复注册、重复添加好友等 | + +#### 认证模块错误码(2000-2099) + +| 错误码 | 含义 | 说明 | +|--------|------|------| +| 2001 | 用户名已存在 | 注册时用户名重复 | +| 2002 | 邮箱已注册 | 注册时邮箱重复 | +| 2003 | 账号或密码错误 | 登录失败 | +| 2004 | 账号已被禁用 | 用户状态为禁用 | + +#### IM 模块错误码(3000-3099) + +| 错误码 | 含义 | 说明 | +|--------|------|------| +| 3001 | 会话不存在 | IM 会话 ID 无效 | +| 3002 | 非会话成员 | 用户不在该会话中 | +| 3003 | 已被禁言 | 用户在该群聊中被禁言 | + +#### 会议模块错误码(4000-4099) + +| 错误码 | 含义 | 说明 | +|--------|------|------| +| 4001 | 会议不存在 | 会议号无效 | +| 4002 | 会议密码错误 | 加入会议时密码不匹配 | +| 4003 | 会议已满 | 参会人数已达上限 | +| 4004 | 会议已结束 | 尝试加入已结束的会议 | + +#### 系统错误码(5000-5099) + +| 错误码 | 含义 | 说明 | +|--------|------|------| +| 5001 | 系统内部错误 | 服务端未知错误 | +| 5002 | 服务暂不可用 | 依赖服务不可用(数据库、Redis 等) | + +### 分页规范 + +支持分页的接口统一使用以下查询参数: + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| page | int | 1 | 页码,从 1 开始 | +| page_size | int | 20 | 每页数量,最大 100 | + +分页响应格式: +```json +{ + "code": 0, + "message": "ok", + "data": { + "list": [ ... ], + "total": 150, + "page": 1, + "page_size": 20 + } +} +``` + +--- + +## 文档维护规则 + +1. **新增接口时**:在对应模块文档中追加,保持格式一致 +2. **接口变更时**:同步更新文档,必要时在接口描述中标注版本信息 +3. **新增模块时**:创建新的模块文档,在本 README 导航表中添加链接 +4. **错误码新增时**:在本文档的错误码定义中追加,保持各模块错误码区间不重叠 diff --git a/docs/api/admin.md b/docs/api/admin.md new file mode 100644 index 0000000..88f57d8 --- /dev/null +++ b/docs/api/admin.md @@ -0,0 +1,280 @@ +# 后台管理模块 API (Admin) + +> 通用规范(认证方式、响应格式、错误码)见 [README.md](README.md) +> 以下所有接口(除管理员登录外)均需要 **JWT 认证 + admin/super_admin 角色** + +--- + +## 接口列表 + +### 认证 + +| 方法 | 路径 | 权限 | 说明 | +|------|------|------|------| +| POST | /api/v1/admin/auth/login | 公开 | 管理员登录 | + +### 用户管理 + +| 方法 | 路径 | 权限 | 说明 | +|------|------|------|------| +| GET | /api/v1/admin/users | admin | 获取用户列表 | +| GET | /api/v1/admin/users/:id | admin | 获取用户详情 | +| PUT | /api/v1/admin/users/:id/status | admin | 更新用户状态 | +| PUT | /api/v1/admin/users/:id/role | super_admin | 分配用户角色 | +| POST | /api/v1/admin/users | admin | 管理员创建用户 | +| GET | /api/v1/admin/users/:id/meetings | admin | 获取用户会议记录 | + +### 会议管理 + +| 方法 | 路径 | 权限 | 说明 | +|------|------|------|------| +| GET | /api/v1/admin/meetings | admin | 获取会议列表 | +| GET | /api/v1/admin/meetings/:id | admin | 获取会议详情 | +| PUT | /api/v1/admin/meetings/:id/close | admin | 强制结束会议 | +| GET | /api/v1/admin/meetings/stats | admin | 获取会议统计 | + +### 系统管理 + +| 方法 | 路径 | 权限 | 说明 | +|------|------|------|------| +| GET | /api/v1/admin/dashboard | admin | 获取仪表盘数据 | +| GET | /api/v1/admin/logs | admin | 获取操作日志 | +| GET | /api/v1/admin/system/config | super_admin | 获取系统配置 | +| PUT | /api/v1/admin/system/config | super_admin | 更新系统配置 | + +--- + +## 认证 + +### 1. 管理员登录 + +`POST /api/v1/admin/auth/login` + +**权限:** 公开 + +**请求参数:** 与用户登录相同 + +**说明:** 登录后会额外验证用户是否拥有 admin 或 super_admin 角色,如果没有对应角色则返回 1003(权限不足)。 + +--- + +## 用户管理 + +### 2. 获取用户列表 + +`GET /api/v1/admin/users` + +**权限:** admin + +**查询参数:** + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| keyword | string | 无 | 搜索关键词(匹配用户名/邮箱/昵称) | +| status | int | 无 | 按状态筛选:1=正常,2=禁用,3=注销 | +| role | string | 无 | 按角色筛选:user / admin / super_admin | +| page | int | 1 | 页码 | +| page_size | int | 20 | 每页数量 | + +--- + +### 3. 获取用户详情 + +`GET /api/v1/admin/users/:id` + +**权限:** admin + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": { + "id": 1, + "username": "zhangsan", + "email": "zhangsan@example.com", + "nickname": "张三", + "avatar": "https://...", + "gender": 1, + "phone": "13800138000", + "status": 1, + "roles": ["user"], + "last_login_at": "2026-02-27T10:00:00Z", + "last_login_ip": "192.168.1.100", + "created_at": "2026-02-20T08:00:00Z" + } +} +``` + +--- + +### 4. 更新用户状态 + +`PUT /api/v1/admin/users/:id/status` + +**权限:** admin + +**请求参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| status | int | 是 | 目标状态:1=正常(启用),2=禁用 | + +**说明:** 禁用用户后,该用户的所有活跃 Token 将被清除,正在进行的 WebSocket 连接将被断开。 + +--- + +### 5. 分配用户角色 + +`PUT /api/v1/admin/users/:id/role` + +**权限:** super_admin + +**请求参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| role_code | string | 是 | 角色代码:user / admin / super_admin | + +--- + +### 6. 管理员创建用户 + +`POST /api/v1/admin/users` + +**权限:** admin + +**请求参数:** 同用户注册接口,额外支持: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| role_code | string | 否 | 指定角色,默认为 user | + +--- + +### 7. 获取用户会议记录 + +`GET /api/v1/admin/users/:id/meetings` + +**权限:** admin + +**查询参数:** + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| status | string | 无 | ongoing=进行中,upcoming=即将开始,ended=已结束 | +| page | int | 1 | 页码 | +| page_size | int | 20 | 每页数量 | + +--- + +## 会议管理 + +### 8. 获取会议列表 + +`GET /api/v1/admin/meetings` + +**权限:** admin + +**查询参数:** 支持按 status、type、keyword(会议标题)筛选,支持分页 + +--- + +### 9. 获取会议详情 + +`GET /api/v1/admin/meetings/:id` + +**权限:** admin + +--- + +### 10. 强制结束会议 + +`PUT /api/v1/admin/meetings/:id/close` + +**权限:** admin + +**说明:** 强制结束后,所有参会者将收到会议结束通知,所有媒体资源将被回收。操作将记录到管理日志。 + +--- + +### 11. 获取会议统计 + +`GET /api/v1/admin/meetings/stats` + +**权限:** admin + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": { + "total_meetings": 1500, + "ongoing_meetings": 5, + "today_meetings": 23, + "total_participants": 8500, + "avg_duration": 1800 + } +} +``` + +--- + +## 系统管理 + +### 12. 获取仪表盘数据 + +`GET /api/v1/admin/dashboard` + +**权限:** admin + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": { + "total_users": 1200, + "online_users": 85, + "today_new_users": 12, + "ongoing_meetings": 5, + "today_meetings": 23, + "today_messages": 5600 + } +} +``` + +--- + +### 13. 获取操作日志 + +`GET /api/v1/admin/logs` + +**权限:** admin + +**查询参数:** + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| module | string | 无 | 按模块筛选 | +| action | string | 无 | 按操作类型筛选 | +| admin_id | int | 无 | 按操作管理员筛选 | +| page | int | 1 | 页码 | +| page_size | int | 20 | 每页数量 | + +--- + +### 14. 获取系统配置 + +`GET /api/v1/admin/system/config` + +**权限:** super_admin + +--- + +### 15. 更新系统配置 + +`PUT /api/v1/admin/system/config` + +**权限:** super_admin diff --git a/docs/api/auth.md b/docs/api/auth.md new file mode 100644 index 0000000..c8c52a7 --- /dev/null +++ b/docs/api/auth.md @@ -0,0 +1,203 @@ +# 认证模块 API (Auth) + +> 通用规范(认证方式、响应格式、错误码)见 [README.md](README.md) + +--- + +## 接口列表 + +| 方法 | 路径 | 权限 | 说明 | +|------|------|------|------| +| POST | /api/v1/auth/register | 公开 | 用户注册 | +| POST | /api/v1/auth/login | 公开 | 用户登录 | +| POST | /api/v1/auth/logout | 需认证 | 退出登录 | +| POST | /api/v1/auth/refresh-token | 公开 | 刷新 Token | +| GET | /api/v1/auth/profile | 需认证 | 获取个人信息 | +| PUT | /api/v1/auth/profile | 需认证 | 更新个人信息 | +| PUT | /api/v1/auth/password | 需认证 | 修改密码 | + +--- + +## 1. 用户注册 + +`POST /api/v1/auth/register` + +**权限:** 公开 + +**请求参数:** + +| 字段 | 类型 | 必填 | 规则 | 说明 | +|------|------|------|------|------| +| username | string | 是 | 3-50 字符,字母数字下划线 | 用户名 | +| email | string | 是 | 合法邮箱格式 | 邮箱地址 | +| password | string | 是 | 6-50 字符 | 登录密码 | +| nickname | string | 否 | 最多 50 字符 | 昵称,默认与用户名相同 | + +**请求示例:** +```json +{ + "username": "zhangsan", + "email": "zhangsan@example.com", + "password": "123456", + "nickname": "张三" +} +``` + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": { + "token": "eyJhbGciOiJIUzI1NiIs...", + "refresh_token": "eyJhbGciOiJIUzI1NiIs...", + "expires_in": 604800, + "user": { + "id": 1, + "username": "zhangsan", + "email": "zhangsan@example.com", + "nickname": "张三", + "avatar": "", + "roles": ["user"] + } + } +} +``` + +**可能的错误码:** 1001, 2001, 2002 + +--- + +## 2. 用户登录 + +`POST /api/v1/auth/login` + +**权限:** 公开 + +**请求参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| account | string | 是 | 用户名或邮箱(自动识别格式) | +| password | string | 是 | 登录密码 | + +**请求示例:** +```json +{ + "account": "zhangsan", + "password": "123456" +} +``` + +**成功响应:** 与注册接口返回格式一致 + +**可能的错误码:** 1001, 2003, 2004 + +--- + +## 3. 退出登录 + +`POST /api/v1/auth/logout` + +**权限:** 需认证 + +**说明:** 服务端清除 Redis 中的 Token 记录,客户端需同步清除本地存储的 Token。 + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": null +} +``` + +--- + +## 4. 刷新 Token + +`POST /api/v1/auth/refresh-token` + +**权限:** 公开(携带 refresh_token) + +**请求参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| refresh_token | string | 是 | 刷新令牌 | + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": { + "token": "eyJhbG...(新 Access Token)", + "refresh_token": "eyJhbG...(新 Refresh Token)", + "expires_in": 604800 + } +} +``` + +**可能的错误码:** 1002 + +--- + +## 5. 获取个人信息 + +`GET /api/v1/auth/profile` + +**权限:** 需认证 + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": { + "id": 1, + "username": "zhangsan", + "email": "zhangsan@example.com", + "nickname": "张三", + "avatar": "https://cdn.echochat.com/avatar/1.jpg", + "gender": 1, + "phone": "13800138000", + "roles": ["user"], + "created_at": "2026-02-27T10:00:00Z" + } +} +``` + +--- + +## 6. 更新个人信息 + +`PUT /api/v1/auth/profile` + +**权限:** 需认证 + +**请求参数(均为可选,只传需要修改的字段):** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| nickname | string | 否 | 新昵称 | +| avatar | string | 否 | 新头像 URL | +| gender | int | 否 | 性别:0=未知,1=男,2=女 | +| phone | string | 否 | 手机号 | + +--- + +## 7. 修改密码 + +`PUT /api/v1/auth/password` + +**权限:** 需认证 + +**请求参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| old_password | string | 是 | 原密码 | +| new_password | string | 是 | 新密码(6-50 字符) | + +**可能的错误码:** 1001, 2003(原密码错误) diff --git a/docs/api/contact.md b/docs/api/contact.md new file mode 100644 index 0000000..5db93e9 --- /dev/null +++ b/docs/api/contact.md @@ -0,0 +1,164 @@ +# 联系人模块 API (Contact) + +> 通用规范(认证方式、响应格式、错误码)见 [README.md](README.md) + +--- + +## 接口列表 + +| 方法 | 路径 | 权限 | 说明 | +|------|------|------|------| +| GET | /api/v1/contacts | 需认证 | 获取好友列表 | +| POST | /api/v1/contacts/request | 需认证 | 发送好友申请 | +| POST | /api/v1/contacts/accept | 需认证 | 接受好友申请 | +| POST | /api/v1/contacts/reject | 需认证 | 拒绝好友申请 | +| DELETE | /api/v1/contacts/:id | 需认证 | 删除好友 | +| PUT | /api/v1/contacts/:id/remark | 需认证 | 修改好友备注 | +| GET | /api/v1/contacts/groups | 需认证 | 获取好友分组列表 | +| POST | /api/v1/contacts/groups | 需认证 | 创建好友分组 | + +--- + +## 1. 获取好友列表 + +`GET /api/v1/contacts` + +**权限:** 需认证 + +**查询参数:** + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| group_id | int | 否 | 按分组筛选 | + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": [ + { + "id": 1, + "friend_id": 2, + "username": "lisi", + "nickname": "李四", + "remark": "我的同事", + "avatar": "https://cdn.echochat.com/avatar/2.jpg", + "online": true, + "group_id": 1, + "group_name": "同事" + } + ] +} +``` + +--- + +## 2. 发送好友申请 + +`POST /api/v1/contacts/request` + +**权限:** 需认证 + +**请求参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| target_id | int | 是 | 目标用户 ID | +| message | string | 否 | 申请附言,如"我是张三的同事" | + +**可能的错误码:** 1004(用户不存在),1005(已是好友或已发送过申请) + +--- + +## 3. 接受好友申请 + +`POST /api/v1/contacts/accept` + +**权限:** 需认证 + +**请求参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| friendship_id | int | 是 | 好友关系记录 ID | + +**说明:** 接受后系统自动创建双向好友关系,并发送通知给对方。 + +--- + +## 4. 拒绝好友申请 + +`POST /api/v1/contacts/reject` + +**权限:** 需认证 + +**请求参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| friendship_id | int | 是 | 好友关系记录 ID | + +--- + +## 5. 删除好友 + +`DELETE /api/v1/contacts/:id` + +**权限:** 需认证 + +**路径参数:** `id` — 好友关系记录 ID + +**说明:** 删除后双向关系均解除,关联的单聊会话不会删除(消息记录保留)。 + +--- + +## 6. 修改好友备注 + +`PUT /api/v1/contacts/:id/remark` + +**权限:** 需认证 + +**路径参数:** `id` — 好友关系记录 ID + +**请求参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| remark | string | 是 | 新备注名,最多 50 字符 | + +--- + +## 7. 获取好友分组列表 + +`GET /api/v1/contacts/groups` + +**权限:** 需认证 + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": [ + { "id": 1, "name": "同事", "sort_order": 0, "count": 15 }, + { "id": 2, "name": "朋友", "sort_order": 1, "count": 8 } + ] +} +``` + +--- + +## 8. 创建好友分组 + +`POST /api/v1/contacts/groups` + +**权限:** 需认证 + +**请求参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| name | string | 是 | 分组名称,最多 50 字符 | + +**可能的错误码:** 1005(同名分组已存在) diff --git a/docs/api/im.md b/docs/api/im.md new file mode 100644 index 0000000..0aa4c48 --- /dev/null +++ b/docs/api/im.md @@ -0,0 +1,208 @@ +# 即时通讯模块 API (IM) + +> 通用规范(认证方式、响应格式、错误码)见 [README.md](README.md) +> 消息的实时收发通过 WebSocket 完成,见 [websocket.md](websocket.md) +> 本文档中的接口用于会话管理和消息历史查询等非实时操作。 + +--- + +## 接口列表 + +| 方法 | 路径 | 权限 | 说明 | +|------|------|------|------| +| 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 | 需认证 | 移除群聊成员 | + +--- + +## 1. 获取会话列表 + +`GET /api/v1/conversations` + +**权限:** 需认证 + +**说明:** 返回当前用户的所有会话,按最后消息时间倒序排列。单聊会话的 `name`/`avatar` 为空,前端应使用 `target_user` 的信息展示。 + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": [ + { + "id": 1, + "type": 1, + "name": "", + "avatar": "", + "target_user": { + "id": 2, + "nickname": "李四", + "avatar": "https://cdn.echochat.com/avatar/2.jpg", + "online": true + }, + "last_message": { + "id": 100, + "type": 1, + "content": "你好", + "sender_id": 2, + "created_at": "2026-02-27T10:30:00Z" + }, + "unread_count": 3, + "is_pinned": false + }, + { + "id": 5, + "type": 2, + "name": "产品讨论组", + "avatar": "https://cdn.echochat.com/group/5.jpg", + "target_user": null, + "last_message": { + "id": 205, + "type": 1, + "content": "明天开会", + "sender_id": 3, + "created_at": "2026-02-27T11:00:00Z" + }, + "unread_count": 0, + "is_pinned": true, + "member_count": 8 + } + ] +} +``` + +--- + +## 2. 创建群聊 + +`POST /api/v1/conversations` + +**权限:** 需认证 + +**请求参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| name | string | 是 | 群聊名称 | +| member_ids | int[] | 是 | 初始成员用户 ID 列表(不含自己,至少 2 人) | + +**说明:** 创建者自动成为群主(role=2),被邀请的成员为普通成员(role=0)。 + +--- + +## 3. 获取会话详情 + +`GET /api/v1/conversations/:id` + +**权限:** 需认证,且为该会话成员 + +**成功响应(群聊示例):** +```json +{ + "code": 0, + "message": "ok", + "data": { + "id": 5, + "type": 2, + "name": "产品讨论组", + "avatar": "https://cdn.echochat.com/group/5.jpg", + "owner_id": 1, + "max_members": 200, + "member_count": 8, + "members": [ + { "user_id": 1, "nickname": "张三", "role": 2, "online": true }, + { "user_id": 2, "nickname": "李四", "role": 0, "online": false } + ], + "created_at": "2026-02-20T09:00:00Z" + } +} +``` + +--- + +## 4. 获取消息历史 + +`GET /api/v1/conversations/:id/messages` + +**权限:** 需认证,且为该会话成员 + +**查询参数:** + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| before_id | int | 无 | 获取此消息 ID 之前的消息(用于向上翻页加载历史) | +| limit | int | 30 | 每次获取数量,最大 50 | + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": { + "messages": [ + { + "id": 98, + "sender_id": 1, + "sender_name": "张三", + "sender_avatar": "https://...", + "type": 1, + "content": "明天几点开会?", + "extra": {}, + "status": 1, + "created_at": "2026-02-27T10:28:00Z" + }, + { + "id": 99, + "sender_id": 2, + "sender_name": "李四", + "sender_avatar": "https://...", + "type": 2, + "content": "", + "extra": { + "url": "https://cdn.echochat.com/img/xxx.jpg", + "width": 800, + "height": 600, + "thumbnail": "https://cdn.echochat.com/img/xxx_thumb.jpg" + }, + "status": 1, + "created_at": "2026-02-27T10:29:00Z" + } + ], + "has_more": true + } +} +``` + +--- + +## 5. 邀请成员加入群聊 + +`POST /api/v1/conversations/:id/members` + +**权限:** 需认证,且为该群聊成员 + +**请求参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| user_ids | int[] | 是 | 要邀请的用户 ID 列表 | + +**可能的错误码:** 3001(会话不存在),3002(非会话成员),4003(超出人数上限) + +--- + +## 6. 移除群聊成员 + +`DELETE /api/v1/conversations/:id/members/:uid` + +**权限:** 需认证,且为群主或管理员 + +**路径参数:** +- `id` — 会话 ID +- `uid` — 被移除的用户 ID + +**可能的错误码:** 3001, 3002, 1003(非群主/管理员无权操作) diff --git a/docs/api/meeting.md b/docs/api/meeting.md new file mode 100644 index 0000000..8907248 --- /dev/null +++ b/docs/api/meeting.md @@ -0,0 +1,180 @@ +# 会议模块 API (Meeting) + +> 通用规范(认证方式、响应格式、错误码)见 [README.md](README.md) +> 会议中的实时信令(Transport/Producer/Consumer)通过 WebSocket 完成,见 [websocket.md](websocket.md) + +--- + +## 接口列表 + +| 方法 | 路径 | 权限 | 说明 | +|------|------|------|------| +| POST | /api/v1/meetings | 需认证 | 创建即时会议 | +| POST | /api/v1/meetings/schedule | 需认证 | 预约会议 | +| GET | /api/v1/meetings/:code | 需认证 | 获取会议信息 | +| POST | /api/v1/meetings/:code/join | 需认证 | 加入会议 | +| POST | /api/v1/meetings/:code/leave | 需认证 | 离开会议 | +| GET | /api/v1/meetings/upcoming | 需认证 | 获取即将开始的会议 | +| GET | /api/v1/meetings/ongoing | 需认证 | 获取进行中的会议 | +| GET | /api/v1/meetings/history | 需认证 | 获取历史会议 | + +--- + +## 1. 创建即时会议 + +`POST /api/v1/meetings` + +**权限:** 需认证 + +**请求参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| title | string | 是 | 会议标题 | +| password | string | 否 | 会议密码,不设则任何人可加入 | +| max_members | int | 否 | 最大人数,默认 50 | +| settings | object | 否 | 会议设置 | + +**settings 可选字段:** + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| mute_on_join | bool | false | 入会时自动静音 | +| allow_recording | bool | false | 是否允许录制(预留) | + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": { + "id": 1, + "room_code": "123-456-789", + "title": "产品需求讨论", + "type": 1, + "status": 1, + "host_id": 1, + "max_members": 50, + "created_at": "2026-02-27T10:00:00Z" + } +} +``` + +--- + +## 2. 预约会议 + +`POST /api/v1/meetings/schedule` + +**权限:** 需认证 + +**请求参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| title | string | 是 | 会议标题 | +| scheduled_at | string | 是 | 预约时间(ISO 8601,如 "2026-03-01T14:00:00Z") | +| password | string | 否 | 会议密码 | +| max_members | int | 否 | 最大人数 | +| invite_user_ids | int[] | 否 | 预先邀请的用户 ID 列表 | +| settings | object | 否 | 会议设置 | + +**说明:** 预约会议创建后 status=0(未开始),被邀请的用户会收到通知。系统在预约时间前 15 分钟和 5 分钟各推送一次提醒。 + +--- + +## 3. 获取会议信息 + +`GET /api/v1/meetings/:code` + +**权限:** 需认证 + +**路径参数:** `code` — 会议号 + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": { + "id": 1, + "room_code": "123-456-789", + "title": "产品需求讨论", + "type": 1, + "status": 1, + "host": { + "id": 1, + "nickname": "张三", + "avatar": "https://..." + }, + "has_password": true, + "max_members": 50, + "current_members": 5, + "started_at": "2026-02-27T10:00:00Z", + "participants": [ + { "user_id": 1, "nickname": "张三", "role": 1, "joined_at": "2026-02-27T10:00:00Z" }, + { "user_id": 2, "nickname": "李四", "role": 0, "joined_at": "2026-02-27T10:01:00Z" } + ] + } +} +``` + +--- + +## 4. 加入会议 + +`POST /api/v1/meetings/:code/join` + +**权限:** 需认证 + +**请求参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| password | string | 否 | 会议密码(如果会议设有密码) | + +**成功响应包含加入会议所需的信令参数。** + +**可能的错误码:** 4001, 4002, 4003, 4004 + +--- + +## 5. 离开会议 + +`POST /api/v1/meetings/:code/leave` + +**权限:** 需认证 + +**说明:** 离开后系统自动计算参会时长。如果主持人离开且没有联合主持人,会议将自动结束。 + +--- + +## 6. 获取即将开始的会议 + +`GET /api/v1/meetings/upcoming` + +**权限:** 需认证 + +**说明:** 返回当前用户被邀请的、尚未开始的预约会议列表,按预约时间升序排列。 + +--- + +## 7. 获取进行中的会议 + +`GET /api/v1/meetings/ongoing` + +**权限:** 需认证 + +**说明:** 返回当前用户正在参与的或被邀请的进行中会议。 + +--- + +## 8. 获取历史会议 + +`GET /api/v1/meetings/history` + +**权限:** 需认证 + +**查询参数:** 支持分页(page, page_size) + +**说明:** 返回当前用户参与过的已结束会议,按结束时间倒序排列。 diff --git a/docs/api/notify.md b/docs/api/notify.md new file mode 100644 index 0000000..c45e183 --- /dev/null +++ b/docs/api/notify.md @@ -0,0 +1,93 @@ +# 通知模块 API (Notify) + +> 通用规范(认证方式、响应格式、错误码)见 [README.md](README.md) +> 新通知的实时推送通过 WebSocket 完成,见 [websocket.md](websocket.md) + +--- + +## 接口列表 + +| 方法 | 路径 | 权限 | 说明 | +|------|------|------|------| +| GET | /api/v1/notifications | 需认证 | 获取通知列表 | +| PUT | /api/v1/notifications/:id/read | 需认证 | 标记通知已读 | +| PUT | /api/v1/notifications/read-all | 需认证 | 全部标记已读 | + +--- + +## 1. 获取通知列表 + +`GET /api/v1/notifications` + +**权限:** 需认证 + +**查询参数:** + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| is_read | bool | 无 | 筛选已读/未读,不传则返回全部 | +| type | string | 无 | 筛选通知类型(meeting_invite / friend_request / friend_accepted / meeting_reminder / system) | +| page | int | 1 | 页码 | +| page_size | int | 20 | 每页数量 | + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": { + "list": [ + { + "id": 1, + "type": "meeting_invite", + "title": "会议邀请", + "content": "张三邀请你参加会议「产品需求讨论」", + "extra": { + "room_code": "123-456-789", + "room_title": "产品需求讨论", + "from_user_id": 1, + "from_username": "zhangsan" + }, + "is_read": false, + "created_at": "2026-02-27T10:00:00Z" + }, + { + "id": 2, + "type": "friend_request", + "title": "好友申请", + "content": "李四请求添加你为好友", + "extra": { + "from_user_id": 2, + "from_username": "lisi", + "message": "我是你的同事" + }, + "is_read": false, + "created_at": "2026-02-27T09:30:00Z" + } + ], + "total": 15, + "page": 1, + "page_size": 20 + } +} +``` + +--- + +## 2. 标记通知已读 + +`PUT /api/v1/notifications/:id/read` + +**权限:** 需认证 + +**路径参数:** `id` — 通知 ID + +--- + +## 3. 全部标记已读 + +`PUT /api/v1/notifications/read-all` + +**权限:** 需认证 + +**说明:** 将当前用户的所有未读通知标记为已读。 diff --git a/docs/api/websocket.md b/docs/api/websocket.md new file mode 100644 index 0000000..4917ae1 --- /dev/null +++ b/docs/api/websocket.md @@ -0,0 +1,397 @@ +# WebSocket 事件协议 + +> 通用规范(认证方式、响应格式、错误码)见 [README.md](README.md) +> 本文档定义 EchoChat 系统中所有 WebSocket 实时通信事件。 + +--- + +## 连接说明 + +### 连接地址 + +| 环境 | 地址 | +|------|------| +| 开发环境 | `ws://localhost:8080/ws?token=` | +| 生产环境 | `wss://api.echochat.com/ws?token=` | + +### 连接认证 + +通过 URL 查询参数 `token` 携带 JWT Access Token,服务端验证通过后建立连接。 + +### 心跳机制 + +- 客户端每 **30 秒** 发送一次 ping 帧 +- 服务端响应 pong 帧 +- 如果 **90 秒** 内未收到客户端心跳,服务端主动断开连接 + +### 断线重连 + +- 客户端检测到连接断开后自动重连 +- 重连间隔采用指数退避:1s → 2s → 4s → 8s → 16s → 最大 30s +- 重连成功后拉取离线消息 + +--- + +## 消息格式 + +### 客户端发送格式 + +```json +{ + "event": "im.message.send", + "seq": 1001, + "data": { ... }, + "timestamp": 1740700000 +} +``` + +| 字段 | 类型 | 说明 | +|------|------|------| +| event | string | 事件名称,格式:`{模块}.{对象}.{动作}` | +| seq | int | 消息序列号,客户端自增,用于匹配请求和响应 | +| data | object | 事件数据 | +| timestamp | int | 发送时间戳(秒) | + +### 服务端响应格式(ACK) + +```json +{ + "event": "im.message.send.ack", + "seq": 1001, + "code": 0, + "message": "ok", + "data": { "msg_id": 10086 } +} +``` + +### 服务端推送格式 + +```json +{ + "event": "im.message.new", + "data": { ... }, + "timestamp": 1740700000 +} +``` + +推送类消息没有 seq 字段(不需要客户端确认)。 + +--- + +## 即时通讯事件 + +### im.message.send + +**方向:** 客户端 → 服务端 + +**说明:** 发送消息到会话 + +**data 参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| conversation_id | int | 是 | 目标会话 ID | +| type | int | 是 | 消息类型:1=文本,2=图片,3=文件,4=语音 | +| content | string | 否 | 文本内容 | +| extra | object | 否 | 附加数据(图片/文件信息) | + +**ACK 响应 data:** `{ "msg_id": 10086 }` + +--- + +### im.message.new + +**方向:** 服务端 → 客户端 + +**说明:** 收到新消息推送 + +**data 内容:** + +```json +{ + "id": 10086, + "conversation_id": 1, + "sender_id": 2, + "sender_name": "李四", + "sender_avatar": "https://...", + "type": 1, + "content": "你好", + "extra": {}, + "created_at": "2026-02-27T10:30:00Z" +} +``` + +--- + +### im.message.revoke + +**方向:** 客户端 → 服务端 + +**说明:** 撤回消息(发送后 2 分钟内) + +**data 参数:** `{ "message_id": 10086 }` + +--- + +### im.message.read + +**方向:** 客户端 → 服务端 + +**说明:** 消息已读回执 + +**data 参数:** `{ "conversation_id": 1, "message_id": 10086 }` + +--- + +### im.typing.start + +**方向:** 客户端 → 服务端 + +**说明:** 通知对方"正在输入" + +**data 参数:** `{ "conversation_id": 1 }` + +--- + +### im.typing.stop + +**方向:** 客户端 → 服务端 + +**说明:** 停止输入 + +**data 参数:** `{ "conversation_id": 1 }` + +--- + +## 会议信令事件 + +### meeting.room.join + +**方向:** 客户端 → 服务端 + +**说明:** 加入会议房间 + +**data 参数:** `{ "room_code": "123-456-789" }` + +**ACK 响应 data:** 房间信息、参与者列表、RTP Capabilities + +--- + +### meeting.room.leave + +**方向:** 客户端 → 服务端 + +**说明:** 离开会议房间 + +**data 参数:** `{ "room_code": "123-456-789" }` + +--- + +### meeting.room.info + +**方向:** 服务端 → 客户端 + +**说明:** 房间信息同步(成员变更、设置变更时推送) + +--- + +### meeting.member.join + +**方向:** 服务端 → 客户端(广播) + +**说明:** 有新成员加入会议 + +**data 内容:** +```json +{ + "room_code": "123-456-789", + "user_id": 3, + "nickname": "王五", + "avatar": "https://...", + "role": 0 +} +``` + +--- + +### meeting.member.leave + +**方向:** 服务端 → 客户端(广播) + +**说明:** 有成员离开会议 + +**data 内容:** `{ "room_code": "...", "user_id": 3 }` + +--- + +### meeting.member.mute + +**方向:** 双向 + +**说明:** 静音/解除静音 + +**data 内容:** `{ "room_code": "...", "user_id": 1, "muted": true }` + +--- + +### meeting.member.video + +**方向:** 双向 + +**说明:** 开关摄像头 + +**data 内容:** `{ "room_code": "...", "user_id": 1, "video_enabled": false }` + +--- + +## mediasoup 信令事件 + +### meeting.transport.create + +**方向:** 客户端 → 服务端 + +**说明:** 请求创建 WebRTC Transport(发送端或接收端) + +**data 参数:** `{ "room_code": "...", "direction": "send" }` 或 `"recv"` + +**ACK 响应 data:** Transport 参数(id, iceParameters, iceCandidates, dtlsParameters) + +--- + +### meeting.transport.connect + +**方向:** 客户端 → 服务端 + +**说明:** 完成 Transport DTLS 握手 + +**data 参数:** `{ "transport_id": "...", "dtls_parameters": { ... } }` + +--- + +### meeting.produce.start + +**方向:** 客户端 → 服务端 + +**说明:** 开始推流(音频或视频) + +**data 参数:** +```json +{ + "transport_id": "...", + "kind": "video", + "rtp_parameters": { ... } +} +``` + +**ACK 响应 data:** `{ "producer_id": "..." }` + +--- + +### meeting.produce.stop + +**方向:** 客户端 → 服务端 + +**说明:** 停止推流 + +**data 参数:** `{ "producer_id": "..." }` + +--- + +### meeting.consume.start + +**方向:** 服务端 → 客户端 + +**说明:** 通知客户端可以开始接收某个参与者的流 + +**data 内容:** +```json +{ + "consumer_id": "...", + "producer_id": "...", + "kind": "video", + "rtp_parameters": { ... }, + "user_id": 3, + "nickname": "王五" +} +``` + +--- + +### meeting.consume.resume + +**方向:** 客户端 → 服务端 + +**说明:** 恢复被暂停的 Consumer + +**data 参数:** `{ "consumer_id": "..." }` + +--- + +## 用户状态事件 + +### user.status.online + +**方向:** 服务端 → 客户端 + +**说明:** 好友上线通知 + +**data 内容:** `{ "user_id": 2, "nickname": "李四" }` + +--- + +### user.status.offline + +**方向:** 服务端 → 客户端 + +**说明:** 好友离线通知 + +**data 内容:** `{ "user_id": 2 }` + +--- + +## 通知事件 + +### notify.new + +**方向:** 服务端 → 客户端 + +**说明:** 新通知推送(通用) + +**data 内容:** 与 Notify API 获取通知列表中的单条通知格式一致 + +--- + +### notify.meeting.invite + +**方向:** 服务端 → 客户端 + +**说明:** 会议邀请推送 + +**data 内容:** +```json +{ + "room_code": "123-456-789", + "title": "产品需求讨论", + "from_user_id": 1, + "from_nickname": "张三" +} +``` + +--- + +### notify.friend.request + +**方向:** 服务端 → 客户端 + +**说明:** 好友申请推送 + +**data 内容:** +```json +{ + "friendship_id": 5, + "from_user_id": 2, + "from_nickname": "李四", + "from_avatar": "https://...", + "message": "我是你的同事" +} +``` diff --git a/docs/architecture/system-architecture.md b/docs/architecture/system-architecture.md new file mode 100644 index 0000000..908f480 --- /dev/null +++ b/docs/architecture/system-architecture.md @@ -0,0 +1,247 @@ +# EchoChat 系统架构设计 + +> 本文档从整体设计方案中提取并深化架构设计部分,便于独立查阅。 +> 完整设计方案见 `docs/plans/2026-02-27-echochat-system-design.md` + +--- + +## 一、架构概述 + +EchoChat 采用 **「精简单体 + 媒体微服务」** 架构,核心思想是 **控制面与媒体面彻底分离、业务系统与实时系统解耦**。 + +- **Go 单体服务**:处理所有业务逻辑(认证、IM、会议控制、好友、通知、后台管理),内部按模块化组织,保留后期拆分为微服务的能力 +- **mediasoup Node 服务**:独立的媒体控制微服务,管理 SFU Worker,不涉及任何业务逻辑 +- **mediasoup Worker**:C++ SFU 引擎,负责 RTP 转发、拥塞控制、带宽自适应 + +--- + +## 二、架构分层图 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 接入层 (Nginx) │ +│ SSL 终止 · 反向代理 · WebSocket 升级 · 静态资源 · 负载均衡 │ +└─────┬───────────────────────┬───────────────────────────────┘ + │ │ + │ HTTPS / WSS │ HTTPS + │ │ +┌─────┴──────────┐ ┌──────┴──────────┐ +│ 前台用户端 │ │ 后台管理端 │ +│ uniapp │ │ Vue3+Element │ +│ (H5/App/小程序)│ │ Plus (PC Web) │ +└─────┬──────────┘ └──────┬──────────┘ + │ │ + │ WebSocket + HTTP │ HTTP (RESTful) + │ │ +┌─────┴───────────────────────┴──────────────────────────────┐ +│ Go 单体服务(模块化) │ +│ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ +│ │ auth │ │ im │ │ meeting │ │ admin │ │ +│ │ 认证鉴权 │ │ 即时通讯 │ │ 会议控制 │ │ 后台管理 │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │ +│ ┌──────────┐ ┌──────────┐ │ +│ │ contact │ │ notify │ 每个模块: Controller → │ +│ │ 联系人 │ │ 通知 │ Service → DAO → Model │ +│ └──────────┘ └──────────┘ │ +│ │ │ │ │ +│ ┌────┴──────────────┴──────────────┴──────┐ │ +│ │ 公共基础设施层 (pkg/) │ │ +│ │ db · redis · ws · middleware · utils │ │ +│ └─────────────────────────────────────────┘ │ +└────────┬──────────────────────┬─────────────────────────────┘ + │ │ + PostgreSQL Redis HTTP + (持久化数据) (实时状态) │ + ┌─────┴─────────────┐ + │ mediasoup Node 服务 │ + │ Router 管理 │ + │ Transport 管理 │ + │ Producer/Consumer │ + └─────┬─────────────┘ + │ IPC + ┌─────┴─────────────┐ + │ mediasoup Worker │ + │ (C++ SFU 引擎) │ + │ RTP 转发 │ + │ 拥塞控制 │ + │ 带宽自适应 │ + └───────────────────┘ +``` + +--- + +## 三、各层职责说明 + +### 3.1 接入层 (Nginx) + +| 职责 | 说明 | +|------|------| +| SSL 终止 | 处理 HTTPS/WSS 加密,内部服务间通信使用 HTTP | +| 反向代理 | 将请求分发到 Go 服务或前端静态资源 | +| WebSocket 升级 | 处理 WebSocket 协议升级 | +| 负载均衡 | 后期多实例部署时进行请求分发 | + +### 3.2 Go 单体服务 + +系统的 **"大脑"**,处理所有业务逻辑。 + +| 模块 | 职责 | +|------|------| +| auth | 用户注册/登录、JWT Token 管理、RBAC 角色权限 | +| im | 即时消息收发、会话管理、消息存储 | +| contact | 好友关系管理、好友分组 | +| meeting | 会议创建/管理、信令转发、mediasoup 资源编排 | +| notify | 通知推送、会议邀请、好友申请通知 | +| admin | 后台管理(用户管理、会议监控、系统配置) | + +**不负责的事情:** 不处理 RTP 媒体数据、不参与音视频转发、不做 WebRTC 协议协商。 + +### 3.3 mediasoup Node 服务 + +mediasoup C++ SFU 的 **"遥控器"**,不懂业务、不懂用户、只懂媒体对象。 + +| 职责 | 说明 | +|------|------| +| Worker 管理 | 创建和管理 mediasoup C++ Worker 进程 | +| Router 管理 | 每个会议房间对应一个 Router | +| Transport 管理 | 为每个参与者创建 WebRTC Transport | +| Producer/Consumer | 管理音视频流的推送和消费 | + +### 3.4 mediasoup Worker + +真正的 **"发动机"**,纯 C++ 实现的 SFU 引擎。 + +| 职责 | 说明 | +|------|------| +| RTP 转发 | 接收发送者的 RTP 包,转发给所有接收者 | +| 拥塞控制 | 根据网络状况动态调整 | +| 带宽自适应 | Simulcast/SVC 支持 | + +--- + +## 四、数据流说明 + +### 4.1 即时消息流 + +``` +客户端A Go 服务 客户端B + │ │ │ + │── WS: 发送消息 ──→ │ │ + │ │── 写入 PostgreSQL │ + │ │── 更新 Redis 未读数 │ + │ ←── WS: 发送确认 ──│ │ + │ │── WS: 推送新消息 ────────→ │ + │ │ │ +``` + +### 4.2 音视频会议流 + +``` +客户端 Go 服务 mediasoup Node Worker + │ │ │ │ + │── HTTP: 加入会议 → │ │ │ + │ │── HTTP: 创建Router→│ │ + │ │ ←── RTP能力 ──────│ │ + │ ←── WS: 房间信息 ──│ │ │ + │ │ │ │ + │── WS: 创建Transport→│ │ │ + │ │── HTTP: 创建 ────→ │── IPC ──────→ │ + │ ←── WS: Transport参数│ │ │ + │ │ │ │ + │── WS: 开始推流 ──→ │ │ │ + │ │── HTTP: Producer → │── IPC ──────→ │ + │ │ │ │ + │════════════════ RTP/DTLS 媒体流直连 ═══════════════════→│ + │ (音视频数据不经过 Go 服务,直连 Worker) │ +``` + +--- + +## 五、微服务演进路径 + +当前架构从第一天起就为微服务拆分做了准备: + +### 5.1 代码层面的预留 + +| 规则 | 说明 | +|------|------| +| 模块间零直接引用 | `auth` 不会 import `im` 内部代码,通过 interface 通信 | +| 独立路由注册 | 每个模块有自己的 `router.go`,注册独立的路由组 | +| 数据库表按模块前缀 | `auth_users`、`im_messages`、`meeting_rooms`,后期可分库 | +| Redis key 按命名空间 | `echo:auth:*`、`echo:im:*`、`echo:meeting:*` | + +### 5.2 演进路径 + +``` +第一阶段(当前) 第二阶段 第三阶段 +┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐ +│ Go 单体服务 │ → │ Go 实时服务 │ → │ auth-service │ +│ (模块化) │ │ (信令+会议+IM) │ │ im-service │ +│ │ │ │ │ meeting-service │ +│ │ │ Go 业务服务 │ │ contact-service │ +│ │ │ (用户+好友+管理) │ │ admin-service │ +└─────────────┘ └──────────────────┘ └─────────────────┘ + + API Gateway + + 服务发现 + + 链路追踪 +``` + +--- + +## 六、部署架构 + +### 6.1 开发环境 (Docker Compose) + +```yaml +services: + go-service: # Go 后端 → :8080 + media-server: # mediasoup Node → :3000 + :40000-40100/udp + postgres: # PostgreSQL 16 → :5432 + redis: # Redis 7 → :6379 + nginx: # 反向代理 → :80/:443 +``` + +### 6.2 生产环境 (预留 K8s) + +``` +┌─────────────────────────────────────────┐ +│ Kubernetes 集群 │ +│ │ +│ ┌─────────┐ ┌─────────┐ │ +│ │ Go Pod │ │ Go Pod │ (水平扩展) │ +│ │ (副本1) │ │ (副本N) │ │ +│ └────┬────┘ └────┬────┘ │ +│ └──────┬─────┘ │ +│ │ │ +│ ┌───────────┴───────────┐ │ +│ │ Service (LB) │ │ +│ └───────────────────────┘ │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ │ +│ │ mediasoup │ │ mediasoup │ │ +│ │ Pod (副本1) │ │ Pod (副本N) │ │ +│ └──────────────┘ └──────────────┘ │ +│ │ +│ PostgreSQL (StatefulSet / 外部 RDS) │ +│ Redis (StatefulSet / 外部 ElastiCache) │ +└─────────────────────────────────────────┘ +``` + +--- + +## 七、技术选型依据 + +| 技术 | 选型理由 | +|------|---------| +| **Go (Gin)** | 高并发、静态编译、内存占用小,适合实时系统 | +| **GORM** | Go 生态最成熟的 ORM,社区活跃 | +| **Wire** | 编译时依赖注入,零运行时开销 | +| **zap** | 高性能结构化日志,Uber 出品 | +| **Viper** | 配置管理标准库,支持 YAML + 环境变量覆盖 | +| **mediasoup** | 最高性能的开源 SFU,C++ 实现 | +| **PostgreSQL 16** | 强一致性、JSONB 支持、性能优异 | +| **Redis 7** | 实时状态存储、发布订阅、高速缓存 | +| **uniapp (Vue 3)** | 一套代码多端运行(H5/App/小程序) | +| **Element Plus** | Vue 3 生态最成熟的 PC 端组件库 | +| **Docker Compose** | 轻量级容器编排,适合初期和开发环境 | diff --git a/docs/plans/2026-02-27-echochat-system-design.md b/docs/plans/2026-02-27-echochat-system-design.md index 409c25c..f607f65 100644 --- a/docs/plans/2026-02-27-echochat-system-design.md +++ b/docs/plans/2026-02-27-echochat-system-design.md @@ -187,9 +187,15 @@ EchoChat/ ### 4.1 PostgreSQL 核心表 +> 所有表和字段均添加 `COMMENT` 注释,枚举类字段详细标注各值含义。 + #### auth 模块 — 用户与权限 ```sql +-- ============================================================ +-- auth_users: 用户主表 +-- 存储系统所有用户(包括普通用户和管理员),通过角色表区分权限 +-- ============================================================ CREATE TABLE auth_users ( id BIGSERIAL PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, @@ -197,46 +203,105 @@ CREATE TABLE auth_users ( password_hash VARCHAR(255) NOT NULL, nickname VARCHAR(50) NOT NULL DEFAULT '', avatar VARCHAR(500) NOT NULL DEFAULT '', - gender SMALLINT NOT NULL DEFAULT 0, -- 0:未知 1:男 2:女 + gender SMALLINT NOT NULL DEFAULT 0, phone VARCHAR(20) DEFAULT NULL, - status SMALLINT NOT NULL DEFAULT 1, -- 1:正常 2:禁用 3:注销 + status SMALLINT NOT NULL DEFAULT 1, last_login_at TIMESTAMPTZ DEFAULT NULL, last_login_ip VARCHAR(50) DEFAULT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); +COMMENT ON TABLE auth_users IS '用户主表,存储所有用户信息(普通用户与管理员共用)'; +COMMENT ON COLUMN auth_users.id IS '用户唯一标识,自增主键'; +COMMENT ON COLUMN auth_users.username IS '用户名,全局唯一,用于登录'; +COMMENT ON COLUMN auth_users.email IS '邮箱地址,全局唯一,用于登录和通知'; +COMMENT ON COLUMN auth_users.password_hash IS '密码哈希值,使用 bcrypt 加密存储'; +COMMENT ON COLUMN auth_users.nickname IS '用户昵称,用于前端显示'; +COMMENT ON COLUMN auth_users.avatar IS '头像 URL 地址'; +COMMENT ON COLUMN auth_users.gender IS '性别:0=未知,1=男,2=女'; +COMMENT ON COLUMN auth_users.phone IS '手机号码,可选'; +COMMENT ON COLUMN auth_users.status IS '账号状态:1=正常,2=禁用(管理员封禁),3=注销(用户主动注销)'; +COMMENT ON COLUMN auth_users.last_login_at IS '最后一次登录时间'; +COMMENT ON COLUMN auth_users.last_login_ip IS '最后一次登录 IP 地址'; +COMMENT ON COLUMN auth_users.created_at IS '账号创建时间'; +COMMENT ON COLUMN auth_users.updated_at IS '信息最后更新时间'; + +-- ============================================================ +-- auth_roles: 角色表 +-- 系统预置角色,用于 RBAC 权限控制 +-- ============================================================ CREATE TABLE auth_roles ( id SERIAL PRIMARY KEY, - code VARCHAR(50) UNIQUE NOT NULL, -- user, admin, super_admin + code VARCHAR(50) UNIQUE NOT NULL, name VARCHAR(50) NOT NULL, description VARCHAR(200) DEFAULT '', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); +COMMENT ON TABLE auth_roles IS '角色表,定义系统中所有角色类型'; +COMMENT ON COLUMN auth_roles.id IS '角色唯一标识,自增主键'; +COMMENT ON COLUMN auth_roles.code IS '角色代码,唯一标识:user=普通用户,admin=管理员,super_admin=超级管理员'; +COMMENT ON COLUMN auth_roles.name IS '角色显示名称,如"普通用户""管理员""超级管理员"'; +COMMENT ON COLUMN auth_roles.description IS '角色描述说明'; +COMMENT ON COLUMN auth_roles.created_at IS '创建时间'; + +-- 预置角色数据 +INSERT INTO auth_roles (code, name, description) VALUES + ('user', '普通用户', '系统普通用户,可以使用聊天、会议等功能'), + ('admin', '管理员', '后台管理员,可以管理用户、监控会议等'), + ('super_admin', '超级管理员', '最高权限管理员,可以管理角色和系统配置'); + +-- ============================================================ +-- auth_user_roles: 用户角色关联表 +-- 多对多关系,一个用户可拥有多个角色 +-- ============================================================ CREATE TABLE auth_user_roles ( user_id BIGINT NOT NULL REFERENCES auth_users(id), role_id INT NOT NULL REFERENCES auth_roles(id), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (user_id, role_id) ); + +COMMENT ON TABLE auth_user_roles IS '用户角色关联表,建立用户与角色的多对多关系'; +COMMENT ON COLUMN auth_user_roles.user_id IS '关联的用户 ID'; +COMMENT ON COLUMN auth_user_roles.role_id IS '关联的角色 ID'; +COMMENT ON COLUMN auth_user_roles.created_at IS '角色分配时间'; ``` #### contact 模块 — 联系人与好友 ```sql +-- ============================================================ +-- contact_friendships: 好友关系表 +-- 双向存储:A→B 和 B→A 各一条记录,便于查询"我的好友列表" +-- ============================================================ CREATE TABLE contact_friendships ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL REFERENCES auth_users(id), friend_id BIGINT NOT NULL REFERENCES auth_users(id), remark VARCHAR(50) DEFAULT '', group_id BIGINT DEFAULT NULL, - status SMALLINT NOT NULL DEFAULT 0, -- 0:待确认 1:已接受 2:已拒绝 3:已拉黑 + status SMALLINT NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (user_id, friend_id) ); +COMMENT ON TABLE contact_friendships IS '好友关系表,双向存储(A→B和B→A各一条记录)'; +COMMENT ON COLUMN contact_friendships.id IS '记录唯一标识'; +COMMENT ON COLUMN contact_friendships.user_id IS '发起方用户 ID'; +COMMENT ON COLUMN contact_friendships.friend_id IS '好友用户 ID'; +COMMENT ON COLUMN contact_friendships.remark IS '好友备注名,仅对当前用户可见'; +COMMENT ON COLUMN contact_friendships.group_id IS '所属好友分组 ID,关联 contact_groups 表'; +COMMENT ON COLUMN contact_friendships.status IS '好友关系状态:0=待确认(已发送申请),1=已接受(互为好友),2=已拒绝,3=已拉黑'; +COMMENT ON COLUMN contact_friendships.created_at IS '记录创建时间(申请发送时间)'; +COMMENT ON COLUMN contact_friendships.updated_at IS '最后更新时间(状态变更时间)'; + +-- ============================================================ +-- contact_groups: 好友分组表 +-- 每个用户可自定义好友分组 +-- ============================================================ CREATE TABLE contact_groups ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL REFERENCES auth_users(id), @@ -244,6 +309,13 @@ CREATE TABLE contact_groups ( sort_order INT NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); + +COMMENT ON TABLE contact_groups IS '好友分组表,用户可自定义分组管理好友'; +COMMENT ON COLUMN contact_groups.id IS '分组唯一标识'; +COMMENT ON COLUMN contact_groups.user_id IS '所属用户 ID'; +COMMENT ON COLUMN contact_groups.name IS '分组名称,如"同事""家人""朋友"等'; +COMMENT ON COLUMN contact_groups.sort_order IS '排序权重,数值越小越靠前'; +COMMENT ON COLUMN contact_groups.created_at IS '创建时间'; ``` #### im 模块 — 即时通讯(统一会话模型) @@ -251,23 +323,42 @@ CREATE TABLE contact_groups ( 单聊和群聊统一抽象为"会话",本质都是"一组人在一个空间里收发消息"。这是微信、钉钉、Slack 等主流 IM 的标准模型。 ```sql +-- ============================================================ +-- im_conversations: 会话表 +-- 统一抽象单聊和群聊,单聊时 name/avatar 为空(前端用对方信息展示) +-- ============================================================ CREATE TABLE im_conversations ( id BIGSERIAL PRIMARY KEY, - type SMALLINT NOT NULL, -- 1:单聊 2:群聊 + 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, -- 1:正常 2:已解散 + status SMALLINT NOT NULL DEFAULT 1, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL 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 '最后更新时间'; + +-- ============================================================ +-- im_conversation_members: 会话成员表 +-- 记录每个会话中的参与成员及其个性化设置 +-- ============================================================ 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, -- 0:普通成员 1:管理员 2:群主 + role SMALLINT NOT NULL DEFAULT 0, nickname VARCHAR(50) DEFAULT '', is_muted BOOLEAN NOT NULL DEFAULT FALSE, is_pinned BOOLEAN NOT NULL DEFAULT FALSE, @@ -276,74 +367,154 @@ CREATE TABLE im_conversation_members ( 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 '加入会话的时间'; + +-- ============================================================ +-- im_messages: 消息表 +-- 系统数据量最大的表,存储所有聊天消息内容 +-- ============================================================ CREATE TABLE im_messages ( id BIGSERIAL PRIMARY KEY, conversation_id BIGINT NOT NULL, sender_id BIGINT NOT NULL, - type SMALLINT NOT NULL DEFAULT 1, -- 1:文本 2:图片 3:文件 4:语音 5:系统消息 + type SMALLINT NOT NULL DEFAULT 1, content TEXT NOT NULL DEFAULT '', extra JSONB DEFAULT '{}', - status SMALLINT NOT NULL DEFAULT 1, -- 1:正常 2:已撤回 3:已删除 + status SMALLINT NOT NULL DEFAULT 1, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); + +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.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 '会话消息时间索引,用于按时间倒序查询会话历史消息'; ``` #### meeting 模块 — 音视频会议 ```sql +-- ============================================================ +-- meeting_rooms: 会议房间表 +-- 存储所有会议信息,支持即时会议和预约会议两种类型 +-- ============================================================ CREATE TABLE meeting_rooms ( id BIGSERIAL PRIMARY KEY, - room_code VARCHAR(20) UNIQUE NOT NULL, -- 会议号 + room_code VARCHAR(20) UNIQUE NOT NULL, title VARCHAR(200) NOT NULL, host_id BIGINT NOT NULL REFERENCES auth_users(id), - type SMALLINT NOT NULL DEFAULT 1, -- 1:即时会议 2:预约会议 + type SMALLINT NOT NULL DEFAULT 1, password VARCHAR(50) DEFAULT NULL, max_members INT NOT NULL DEFAULT 50, - status SMALLINT NOT NULL DEFAULT 0, -- 0:未开始 1:进行中 2:已结束 - scheduled_at TIMESTAMPTZ DEFAULT NULL, -- 预约时间(即时会议为NULL) + status SMALLINT NOT NULL DEFAULT 0, + scheduled_at TIMESTAMPTZ DEFAULT NULL, started_at TIMESTAMPTZ DEFAULT NULL, ended_at TIMESTAMPTZ DEFAULT NULL, - settings JSONB DEFAULT '{}', -- 会议设置 + settings JSONB DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); +COMMENT ON TABLE meeting_rooms IS '会议房间表,存储所有会议的基本信息和状态'; +COMMENT ON COLUMN meeting_rooms.id IS '会议唯一标识,自增主键'; +COMMENT ON COLUMN meeting_rooms.room_code IS '会议号(用户可见),如"123-456-789",用于分享和加入会议'; +COMMENT ON COLUMN meeting_rooms.title IS '会议标题'; +COMMENT ON COLUMN meeting_rooms.host_id IS '会议创建者/主持人用户 ID'; +COMMENT ON COLUMN meeting_rooms.type IS '会议类型:1=即时会议(立即创建立即开始),2=预约会议(设定未来时间)'; +COMMENT ON COLUMN meeting_rooms.password IS '会议密码,NULL 表示无密码,任何人可直接加入'; +COMMENT ON COLUMN meeting_rooms.max_members IS '最大参会人数,默认50人'; +COMMENT ON COLUMN meeting_rooms.status IS '会议状态:0=未开始(仅预约会议),1=进行中,2=已结束'; +COMMENT ON COLUMN meeting_rooms.scheduled_at IS '预约时间,仅预约会议有值,即时会议为 NULL'; +COMMENT ON COLUMN meeting_rooms.started_at IS '实际开始时间'; +COMMENT ON COLUMN meeting_rooms.ended_at IS '实际结束时间'; +COMMENT ON COLUMN meeting_rooms.settings IS '会议设置(JSON),如 {mute_on_join: true, allow_recording: false, auto_start: true}'; +COMMENT ON COLUMN meeting_rooms.created_at IS '会议创建时间'; +COMMENT ON COLUMN meeting_rooms.updated_at IS '信息最后更新时间'; + +-- ============================================================ +-- meeting_participants: 会议参与者表 +-- 记录每场会议的参与者及其参会信息 +-- ============================================================ CREATE TABLE meeting_participants ( id BIGSERIAL PRIMARY KEY, room_id BIGINT NOT NULL REFERENCES meeting_rooms(id), user_id BIGINT NOT NULL REFERENCES auth_users(id), - role SMALLINT NOT NULL DEFAULT 0, -- 0:参与者 1:主持人 2:联合主持人 + role SMALLINT NOT NULL DEFAULT 0, joined_at TIMESTAMPTZ DEFAULT NULL, left_at TIMESTAMPTZ DEFAULT NULL, duration INT DEFAULT 0, UNIQUE (room_id, user_id) ); + +COMMENT ON TABLE meeting_participants IS '会议参与者表,记录每场会议的所有参与者信息'; +COMMENT ON COLUMN meeting_participants.id IS '记录唯一标识'; +COMMENT ON COLUMN meeting_participants.room_id IS '所属会议 ID'; +COMMENT ON COLUMN meeting_participants.user_id IS '参与者用户 ID'; +COMMENT ON COLUMN meeting_participants.role IS '参会角色:0=普通参与者,1=主持人(会议创建者),2=联合主持人(主持人指定)'; +COMMENT ON COLUMN meeting_participants.joined_at IS '加入会议的时间'; +COMMENT ON COLUMN meeting_participants.left_at IS '离开会议的时间,NULL 表示仍在会议中'; +COMMENT ON COLUMN meeting_participants.duration IS '累计参会时长(秒),离开时自动计算'; ``` 会议类型状态流转: -- 即时会议:创建 → 进行中(1) → 已结束(2) -- 预约会议:创建 → 未开始(0) → 进行中(1) → 已结束(2) +- **即时会议**(type=1):创建 → 进行中(status=1) → 已结束(status=2) +- **预约会议**(type=2):创建 → 未开始(status=0) → 进行中(status=1) → 已结束(status=2) #### notify 模块 — 消息通知 ```sql +-- ============================================================ +-- notify_notifications: 通知表 +-- 存储所有推送给用户的通知消息 +-- ============================================================ CREATE TABLE notify_notifications ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL REFERENCES auth_users(id), - type VARCHAR(50) NOT NULL, -- meeting_invite, friend_request, system + type VARCHAR(50) NOT NULL, title VARCHAR(200) NOT NULL, content TEXT DEFAULT '', extra JSONB DEFAULT '{}', is_read BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); + +COMMENT ON TABLE notify_notifications IS '通知消息表,存储推送给用户的所有类型通知'; +COMMENT ON COLUMN notify_notifications.id IS '通知唯一标识'; +COMMENT ON COLUMN notify_notifications.user_id IS '接收通知的用户 ID'; +COMMENT ON COLUMN notify_notifications.type IS '通知类型:meeting_invite=会议邀请,friend_request=好友申请,friend_accepted=好友已接受,meeting_reminder=会议提醒,system=系统通知'; +COMMENT ON COLUMN notify_notifications.title IS '通知标题'; +COMMENT ON COLUMN notify_notifications.content IS '通知正文内容'; +COMMENT ON COLUMN notify_notifications.extra IS '附加数据(JSON),如会议邀请存 {room_code, room_title},好友申请存 {from_user_id, from_username}'; +COMMENT ON COLUMN notify_notifications.is_read IS '是否已读:false=未读,true=已读'; +COMMENT ON COLUMN notify_notifications.created_at IS '通知创建时间'; + CREATE INDEX idx_notify_user_read ON notify_notifications(user_id, is_read, created_at DESC); +COMMENT ON INDEX idx_notify_user_read IS '用户未读通知索引,优化"获取未读通知列表"查询'; ``` #### admin 模块 — 管理操作日志 ```sql +-- ============================================================ +-- admin_operation_logs: 管理操作日志表 +-- 记录后台管理员的所有操作行为,用于审计和追踪 +-- ============================================================ CREATE TABLE admin_operation_logs ( id BIGSERIAL PRIMARY KEY, admin_id BIGINT NOT NULL REFERENCES auth_users(id), @@ -355,6 +526,17 @@ CREATE TABLE admin_operation_logs ( ip VARCHAR(50) DEFAULT '', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); + +COMMENT ON TABLE admin_operation_logs IS '管理操作日志表,记录所有后台管理员的操作行为'; +COMMENT ON COLUMN admin_operation_logs.id IS '日志唯一标识'; +COMMENT ON COLUMN admin_operation_logs.admin_id IS '操作管理员的用户 ID'; +COMMENT ON COLUMN admin_operation_logs.module IS '操作所属模块:user=用户管理,meeting=会议管理,permission=权限管理,system=系统配置'; +COMMENT ON COLUMN admin_operation_logs.action IS '操作类型:create=创建,update=修改,delete=删除,disable=禁用,enable=启用,close=关闭'; +COMMENT ON COLUMN admin_operation_logs.target_type IS '操作目标类型:user=用户,meeting=会议,role=角色,config=配置'; +COMMENT ON COLUMN admin_operation_logs.target_id IS '操作目标 ID,关联对应表的主键'; +COMMENT ON COLUMN admin_operation_logs.detail IS '操作详情(JSON),如 {before: {...}, after: {...}} 记录变更前后数据'; +COMMENT ON COLUMN admin_operation_logs.ip IS '操作者 IP 地址'; +COMMENT ON COLUMN admin_operation_logs.created_at IS '操作时间'; ``` ### 4.2 Redis 数据结构