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

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 核心表
> 所有表和字段均添加 `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 数据结构