docs: 完善项目文档体系

- 数据库 SQL 所有字段添加 COMMENT 注释,枚举字段详细标注各值含义
- 新增 docs/architecture/ 系统架构文档(分层架构、数据流、演进路径)
- API 文档按模块拆分为 8 个独立文档(auth/contact/im/meeting/notify/admin/websocket)
- 补充 README.md 项目说明(技术栈、架构、快速开始、功能规划、文档导航)

Made-with: Cursor
This commit is contained in:
bujinyuan
2026-02-27 16:27:07 +08:00
parent bed50841dc
commit 4458f40025
11 changed files with 2257 additions and 18 deletions

147
README.md
View File

@@ -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

138
docs/api/README.md Normal file
View File

@@ -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 <access_token>
```
### 统一响应格式
**成功响应:**
```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. **错误码新增时**:在本文档的错误码定义中追加,保持各模块错误码区间不重叠

280
docs/api/admin.md Normal file
View File

@@ -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

203
docs/api/auth.md Normal file
View File

@@ -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原密码错误

164
docs/api/contact.md Normal file
View File

@@ -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同名分组已存在

208
docs/api/im.md Normal file
View File

@@ -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非群主/管理员无权操作)

180
docs/api/meeting.md Normal file
View File

@@ -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
**说明:** 返回当前用户参与过的已结束会议,按结束时间倒序排列。

93
docs/api/notify.md Normal file
View File

@@ -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`
**权限:** 需认证
**说明:** 将当前用户的所有未读通知标记为已读。

397
docs/api/websocket.md Normal file
View File

@@ -0,0 +1,397 @@
# WebSocket 事件协议
> 通用规范(认证方式、响应格式、错误码)见 [README.md](README.md)
> 本文档定义 EchoChat 系统中所有 WebSocket 实时通信事件。
---
## 连接说明
### 连接地址
| 环境 | 地址 |
|------|------|
| 开发环境 | `ws://localhost:8080/ws?token=<access_token>` |
| 生产环境 | `wss://api.echochat.com/ws?token=<access_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": "我是你的同事"
}
```

View File

@@ -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** | 最高性能的开源 SFUC++ 实现 |
| **PostgreSQL 16** | 强一致性、JSONB 支持、性能优异 |
| **Redis 7** | 实时状态存储、发布订阅、高速缓存 |
| **uniapp (Vue 3)** | 一套代码多端运行H5/App/小程序) |
| **Element Plus** | Vue 3 生态最成熟的 PC 端组件库 |
| **Docker Compose** | 轻量级容器编排,适合初期和开发环境 |

View File

@@ -187,9 +187,15 @@ EchoChat/
### 4.1 PostgreSQL 核心表 ### 4.1 PostgreSQL 核心表
> 所有表和字段均添加 `COMMENT` 注释,枚举类字段详细标注各值含义。
#### auth 模块 — 用户与权限 #### auth 模块 — 用户与权限
```sql ```sql
-- ============================================================
-- auth_users: 用户主表
-- 存储系统所有用户(包括普通用户和管理员),通过角色表区分权限
-- ============================================================
CREATE TABLE auth_users ( CREATE TABLE auth_users (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL, username VARCHAR(50) UNIQUE NOT NULL,
@@ -197,46 +203,105 @@ CREATE TABLE auth_users (
password_hash VARCHAR(255) NOT NULL, password_hash VARCHAR(255) NOT NULL,
nickname VARCHAR(50) NOT NULL DEFAULT '', nickname VARCHAR(50) NOT NULL DEFAULT '',
avatar VARCHAR(500) 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, 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_at TIMESTAMPTZ DEFAULT NULL,
last_login_ip VARCHAR(50) DEFAULT NULL, last_login_ip VARCHAR(50) DEFAULT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_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 ( CREATE TABLE auth_roles (
id SERIAL PRIMARY KEY, 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, name VARCHAR(50) NOT NULL,
description VARCHAR(200) DEFAULT '', description VARCHAR(200) DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() 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 ( CREATE TABLE auth_user_roles (
user_id BIGINT NOT NULL REFERENCES auth_users(id), user_id BIGINT NOT NULL REFERENCES auth_users(id),
role_id INT NOT NULL REFERENCES auth_roles(id), role_id INT NOT NULL REFERENCES auth_roles(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (user_id, role_id) 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 模块 — 联系人与好友 #### contact 模块 — 联系人与好友
```sql ```sql
-- ============================================================
-- contact_friendships: 好友关系表
-- 双向存储A→B 和 B→A 各一条记录,便于查询"我的好友列表"
-- ============================================================
CREATE TABLE contact_friendships ( CREATE TABLE contact_friendships (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES auth_users(id), user_id BIGINT NOT NULL REFERENCES auth_users(id),
friend_id BIGINT NOT NULL REFERENCES auth_users(id), friend_id BIGINT NOT NULL REFERENCES auth_users(id),
remark VARCHAR(50) DEFAULT '', remark VARCHAR(50) DEFAULT '',
group_id BIGINT DEFAULT NULL, 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(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (user_id, friend_id) 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 ( CREATE TABLE contact_groups (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES auth_users(id), user_id BIGINT NOT NULL REFERENCES auth_users(id),
@@ -244,6 +309,13 @@ CREATE TABLE contact_groups (
sort_order INT NOT NULL DEFAULT 0, sort_order INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() 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 模块 — 即时通讯(统一会话模型) #### im 模块 — 即时通讯(统一会话模型)
@@ -251,23 +323,42 @@ CREATE TABLE contact_groups (
单聊和群聊统一抽象为"会话",本质都是"一组人在一个空间里收发消息"。这是微信、钉钉、Slack 等主流 IM 的标准模型。 单聊和群聊统一抽象为"会话",本质都是"一组人在一个空间里收发消息"。这是微信、钉钉、Slack 等主流 IM 的标准模型。
```sql ```sql
-- ============================================================
-- im_conversations: 会话表
-- 统一抽象单聊和群聊,单聊时 name/avatar 为空(前端用对方信息展示)
-- ============================================================
CREATE TABLE im_conversations ( CREATE TABLE im_conversations (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
type SMALLINT NOT NULL, -- 1:单聊 2:群聊 type SMALLINT NOT NULL,
name VARCHAR(100) DEFAULT '', name VARCHAR(100) DEFAULT '',
avatar VARCHAR(500) DEFAULT '', avatar VARCHAR(500) DEFAULT '',
owner_id BIGINT DEFAULT NULL, owner_id BIGINT DEFAULT NULL,
max_members INT NOT NULL DEFAULT 200, 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(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_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 ( CREATE TABLE im_conversation_members (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
conversation_id BIGINT NOT NULL REFERENCES im_conversations(id), conversation_id BIGINT NOT NULL REFERENCES im_conversations(id),
user_id BIGINT NOT NULL REFERENCES auth_users(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 '', nickname VARCHAR(50) DEFAULT '',
is_muted BOOLEAN NOT NULL DEFAULT FALSE, is_muted BOOLEAN NOT NULL DEFAULT FALSE,
is_pinned 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) 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 ( CREATE TABLE im_messages (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
conversation_id BIGINT NOT NULL, conversation_id BIGINT NOT NULL,
sender_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 '', content TEXT NOT NULL DEFAULT '',
extra JSONB 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() 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); 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 模块 — 音视频会议 #### meeting 模块 — 音视频会议
```sql ```sql
-- ============================================================
-- meeting_rooms: 会议房间表
-- 存储所有会议信息,支持即时会议和预约会议两种类型
-- ============================================================
CREATE TABLE meeting_rooms ( CREATE TABLE meeting_rooms (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
room_code VARCHAR(20) UNIQUE NOT NULL, -- 会议号 room_code VARCHAR(20) UNIQUE NOT NULL,
title VARCHAR(200) NOT NULL, title VARCHAR(200) NOT NULL,
host_id BIGINT NOT NULL REFERENCES auth_users(id), 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, password VARCHAR(50) DEFAULT NULL,
max_members INT NOT NULL DEFAULT 50, max_members INT NOT NULL DEFAULT 50,
status SMALLINT NOT NULL DEFAULT 0, -- 0:未开始 1:进行中 2:已结束 status SMALLINT NOT NULL DEFAULT 0,
scheduled_at TIMESTAMPTZ DEFAULT NULL, -- 预约时间即时会议为NULL scheduled_at TIMESTAMPTZ DEFAULT NULL,
started_at TIMESTAMPTZ DEFAULT NULL, started_at TIMESTAMPTZ DEFAULT NULL,
ended_at TIMESTAMPTZ DEFAULT NULL, ended_at TIMESTAMPTZ DEFAULT NULL,
settings JSONB DEFAULT '{}', -- 会议设置 settings JSONB DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_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 ( CREATE TABLE meeting_participants (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
room_id BIGINT NOT NULL REFERENCES meeting_rooms(id), room_id BIGINT NOT NULL REFERENCES meeting_rooms(id),
user_id BIGINT NOT NULL REFERENCES auth_users(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, joined_at TIMESTAMPTZ DEFAULT NULL,
left_at TIMESTAMPTZ DEFAULT NULL, left_at TIMESTAMPTZ DEFAULT NULL,
duration INT DEFAULT 0, duration INT DEFAULT 0,
UNIQUE (room_id, user_id) 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) - **即时会议**type=1:创建 → 进行中(status=1) → 已结束(status=2)
- 预约会议:创建 → 未开始(0) → 进行中(1) → 已结束(2) - **预约会议**type=2:创建 → 未开始(status=0) → 进行中(status=1) → 已结束(status=2)
#### notify 模块 — 消息通知 #### notify 模块 — 消息通知
```sql ```sql
-- ============================================================
-- notify_notifications: 通知表
-- 存储所有推送给用户的通知消息
-- ============================================================
CREATE TABLE notify_notifications ( CREATE TABLE notify_notifications (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES auth_users(id), 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, title VARCHAR(200) NOT NULL,
content TEXT DEFAULT '', content TEXT DEFAULT '',
extra JSONB DEFAULT '{}', extra JSONB DEFAULT '{}',
is_read BOOLEAN NOT NULL DEFAULT FALSE, is_read BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() 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); 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 模块 — 管理操作日志 #### admin 模块 — 管理操作日志
```sql ```sql
-- ============================================================
-- admin_operation_logs: 管理操作日志表
-- 记录后台管理员的所有操作行为,用于审计和追踪
-- ============================================================
CREATE TABLE admin_operation_logs ( CREATE TABLE admin_operation_logs (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
admin_id BIGINT NOT NULL REFERENCES auth_users(id), admin_id BIGINT NOT NULL REFERENCES auth_users(id),
@@ -355,6 +526,17 @@ CREATE TABLE admin_operation_logs (
ip VARCHAR(50) DEFAULT '', ip VARCHAR(50) DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() 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 数据结构 ### 4.2 Redis 数据结构