Files
EchoChat/docs/architecture/system-architecture.md
bujinyuan 6913b9e72f docs(task14): 同步 Phase 2e-2 Task 14 进度至全量项目文档
- README.md:脚本表格新增 deploy-public.sh + full 子命令说明;
  "方式二" 改为 scripts/start.sh full + 指向 docs/deployment/meeting-mvp.md
- docs/api/README.md:meeting 模块状态标注 Task 5/6/7/13 + extra 8 字段
- docs/architecture/system-architecture.md:meeting 状态升至 Task 0-14 ;
  media-server / coturn 行同步标注 Task 14 双态编排落地
- docs/plans/2026-04-20-phase2e-design.md:最后更新 + 变更记录新增 04-24 条目
- media-server/README.md:后续任务新增 Task 9  + Task 14 ,指向部署指南

保证所有项目级 README / 架构 / 路线图文档都处于 Task 0-14  最新状态。

Made-with: Cursor
2026-04-22 18:04:20 +08:00

648 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 │ │ ws │ 每个模块: │
│ │ 联系人 │ │ 通知 │ │ WebSocket│ Controller → │
│ └──────────┘ └──────────┘ └──────────┘ Service → DAO │
│ │ │ │ │
│ ┌────┴──────────────┴──────────────┴──────┐ │
│ │ 公共基础设施层 (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 管理Redis 存储 + client_type 隔离、RBAC 角色权限level 等级体系1=超管, 10=管理员, 100=用户) | ✅ Phase 1 |
| ws | WebSocket 连接管理Hub/Client/PubSub、在线状态管理Redis SET + TTL 心跳续期、好友上下线实时通知FriendIDsGetter 接口注入) | ✅ Phase 2a |
| contact | 好友关系管理(申请/接受/拒绝/删除/拉黑、好友分组CRUD + 移动)、用户搜索、好友推荐(批量查询优化) | ✅ Phase 2a |
| im | 即时消息收发(单聊)、会话管理、消息存储、撤回、搜索、离线推送 | ✅ Phase 2b |
| group | 群聊管理(建群/加入/退出/角色/禁言/@提醒/群公告/入群审批 | ✅ Phase 2c |
| file | 文件上传MinIO 对象存储 + 通用上传 API | ✅ Phase 2c |
| meeting | 会议创建/管理、WebSocket 信令转发、mediasoup 资源编排Go→Node HTTP、**会议生命周期状态机host 宽限期 + 自动转让 + 空房 TTL**、会议内聊天、主持人控制 | 🚧 Phase 2e-2 代码开发中Task 0-14 ✅12 REST + 13 WS 事件 + HTTPMediaOrchestrator 真实媒体链路 + MeetingLifecycleService + MeetingCleanupTask + Router 幂等双层防御 + 前端 mediasoup-client + 会议主页面全量 + meeting_invite 通知卡片 + docker-compose 双态部署Task 15-16 待执行);专用设计:`docs/plans/2026-04-21-phase2e-2-design.md` |
| notify | 统一通知中心、好友/群聊事件 + 系统广播推送、11 种类型枚举、跨模块 Pusher 接口注入 | ✅ Phase 2e-110 种落地 + 2 种预留 `meeting_invite` / `meeting_reminder` |
| admin | 后台管理(用户管理 + 角色权限管理 + 在线监控 + 好友关系管理 + 群聊管理、会议监控、系统配置) | ✅ Phase 1/2a/2c |
**不负责的事情:** 不处理 RTP 媒体数据、不参与音视频转发、不做 WebRTC 协议协商。
#### 3.2.1 路由架构
路由采用 **"模块自包含 + 主路由汇总"** 模式,每个模块在自己的目录内维护路由定义,主路由文件仅做注册汇总。这样设计的核心目的是**利于微服务拆分**——拆分时整个模块目录原封不动搬走即可。
**目录结构:**
```
router/
└── router.go ← 主路由入口,只做汇总注册(不含具体路由定义)
app/auth/router.go ← auth 模块的具体路由定义
app/admin/router.go ← admin 模块的具体路由定义
app/contact/router.go ← contact 模块的具体路由定义Phase 2a 已实现)
app/ws/router.go ← WebSocket 模块的路由定义Phase 2a 已实现)
app/im/router.go ← im 模块的具体路由定义Phase 2b 实现)
app/meeting/router.go ← meeting 模块的具体路由定义(后续阶段)
```
**调用关系:**
```
main.go
└── router.Setup(engine, app)
├── engine.GET("/health", ...) // 健康检查
├── auth.RegisterRoutes(engine, ...) // /api/v1/auth/*
├── admin.RegisterRoutes(engine, ...) // /api/v1/admin/*
├── contact.RegisterRoutes(engine, ...) // /api/v1/contacts/* ✅ Phase 2a
├── ws.RegisterRoutes(engine, ...) // /ws ✅ Phase 2a
├── im.RegisterRoutes(engine, ...) // /api/v1/im/* ✅ Phase 2b
└── meeting.RegisterRoutes(engine, ...) // /api/v1/meeting/* (后续)
```
**路由命名规范:**
| 端 | 路径前缀 | 中间件 | 说明 |
|---|---------|--------|------|
| 前台公开 | `/api/v1/auth/*` | 无 | 注册、登录等不需要认证 |
| 前台认证 | `/api/v1/{module}/*` | JWT 认证 | 需要登录后访问 |
| 管理端 | `/api/v1/admin/{module}/*` | JWT + admin 角色 | 需要管理员权限 |
**微服务拆分时的变化:** 每个独立服务的 main.go 直接调用自己模块的 `RegisterRoutes`,不再需要主路由汇总文件。
### 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 通信实例ws.FriendIDsGetter / im.FriendChecker / contact.OnlineChecker 均通过接口注入) |
| 模块自包含路由 | 每个模块在自己目录内维护 `router.go`,拆分时整目录搬走 |
| 主路由仅做汇总 | `router/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 后端 → :8085 ✅ 已实现(多阶段构建 Dockerfile
media-server: # mediasoup Node → :3300 + :40000-40199/udp ✅ Phase 2e-2 Task 0-2/7/14TypeScript + Fastify + mediasoup v39 REST API + observer-close 自清理HTTPMediaOrchestrator 接入 `/internal/v1/*`Task 14 纳入 docker-compose 双态编排)
coturn: # TURN 服务(可选) → :3478/:5349 ✅ Phase 2e-2 Task 14`profiles: ["public"]` + `network_mode: host`env 驱动 realm/user/credential/端口段 49152-65535
postgres: # PostgreSQL 17 → :5432 ✅ 已实现
redis: # Redis 7 → :6379 ✅ 已实现
nginx: # 反向代理 → :80/:443 📋 生产环境部署
```
> Docker Compose 配置文件:`deploy/docker-compose.dev.yml`
> Go 服务 Dockerfile`backend/go-service/Dockerfile`多阶段构建golang:1.23 → alpine:3.19
> Docker 内配置:`backend/go-service/config/config.docker.yaml`(数据库/Redis 地址使用 Docker 服务名)
### 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 17** | 强一致性、JSONB 支持、性能优异 |
| **Redis 7** | 实时状态存储、发布订阅、高速缓存 |
| **uniapp (Vue 3.4)** | 一套代码多端运行H5/App/小程序Vue 版本由 uni-app 框架锁定 |
| **Element Plus** | Vue 3 生态最成熟的 PC 端组件库,管理端使用 |
| **Docker Compose** | 轻量级容器编排,适合初期和开发环境 |
### 7.1 前端技术栈版本策略
系统包含两个独立前端项目,因框架约束,版本策略有所区别:
| 前端项目 | 目录 | 框架 | Vue 版本 | 状态管理 | 原因 |
|----------|------|------|---------|---------|------|
| 前台用户端 | `frontend/` | uni-app 3.0 | 3.4.21(框架锁定) | Pinia 2.x | uni-app 尚未适配 Vue 3.5Pinia 3.x 需要 Vue >= 3.5.11 |
| 后台管理端 | `admin/` | Vue 3 + Element Plus | 3.5+(不受限) | Pinia 3.x | 独立 Vue 3 项目,可使用最新版本 |
**npm 兼容性说明:** uni-app 的 peer dependency 链与 npm 7+ 的严格依赖解析存在冲突,`frontend/` 项目需在 `.npmrc` 中设置 `legacy-peer-deps=true`,这是 uni-app 社区的标准做法。
**版本升级策略:** 当 uni-app 正式适配 Vue 3.5+ 后,可统一将前台的 Pinia 升级至 3.xAPI 层面几乎无需改动Pinia 2.x 与 3.x 的 `defineStore` API 完全兼容)。
### 7.2 两端开发规范差异
前台用户端和后台管理端是**完全独立**的前端项目,开发规范、依赖版本、构建方式不需要强制统一:
| 维度 | 前台用户端 (`frontend/`) | 后台管理端 (`admin/`) |
|------|------------------------|---------------------|
| 框架 | uni-app 3.0Vue 3.4.21 | Vue 3.5+ + Vite |
| 状态管理 | Pinia 2.x`pinia-plugin-persistedstate@3` | Pinia 3.x最新版 |
| HTTP 客户端 | `uni.request` 封装 | Axios |
| 路由 | `pages.json` + `uni.navigateTo/switchTab` | Vue Router 4 |
| UI 组件 | 原生 uni-app 组件(`<view>`/`<text>`/`<input>` | Element Plus |
| 模块系统 | ES ModuleVite 要求) | ES ModuleVite 标准) |
| npm 配置 | 需要 `.npmrc` 设置 `legacy-peer-deps=true` | 标准 npm 配置 |
| 适配能力 | H5 / 小程序 / App / 桌面端 | 仅 PC 浏览器 |
| 设计系统 | ui-ux-pro-max 生成的设计系统 | ui-ux-pro-max + Element Plus 主题 |
---
## 八、日志系统与链路追踪设计
### 8.1 设计目标
- 每一个请求从进入系统到完成响应,全链路可追踪
- Go 服务内部的函数调用链清晰可见
- Go 服务与 mediasoup Node 服务之间的调用可关联
- WebSocket 消息的处理过程可追踪
- 日志分级明确,开发/生产环境日志策略不同
- 日志格式统一、结构化,便于后期接入 ELK/Loki 等日志平台
### 8.2 请求链路追踪Trace ID / Request ID
每个请求在进入系统时生成唯一的 `trace_id`,贯穿整个处理链路:
```
客户端请求
Nginx生成或转发 X-Request-ID
Go 中间件(提取或生成 trace_id注入 context
├── Controller 日志:[trace_id] 收到请求 POST /api/v1/auth/login
├── Service 日志: [trace_id] 开始处理登录account=zhangsan
├── DAO 日志: [trace_id] SQL查询 auth_users WHERE username=zhangsan
├── Redis 日志: [trace_id] SET echo:auth:token:1
├── 如果涉及 mediasoup 调用:
│ Go 将 trace_id 放入 HTTP Header 传给 Node 服务
│ Node 服务日志也携带同一个 trace_id
Go 中间件:[trace_id] 请求完成 200 OK 耗时 25ms
```
**实现方式:**
```go
// 中间件:为每个请求生成 trace_id 并注入 context
func TraceMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
traceID := c.GetHeader("X-Request-ID")
if traceID == "" {
traceID = generateTraceID() // UUID 或 雪花算法
}
ctx := context.WithValue(c.Request.Context(), "trace_id", traceID)
c.Request = c.Request.WithContext(ctx)
c.Header("X-Request-ID", traceID)
c.Next()
}
}
```
### 8.3 日志级别规范
| 级别 | 使用场景 | 示例 |
|------|---------|------|
| **DEBUG** | 开发调试信息,生产环境不输出 | 函数参数详情、SQL 语句、Redis 操作 |
| **INFO** | 正常业务流程的关键节点 | 用户登录成功、会议创建、消息发送 |
| **WARN** | 异常但不影响主流程 | 参数格式警告、缓存未命中、重试操作 |
| **ERROR** | 业务错误,需要关注 | 数据库查询失败、外部服务调用失败 |
| **FATAL** | 系统级致命错误 | 数据库连接失败、配置加载失败(启动时) |
**环境日志策略:**
| 环境 | 最低级别 | 输出方式 | 格式 |
|------|---------|---------|------|
| 开发 | DEBUG | 控制台(彩色文本)+ 文件JSON | 控制台 text / 文件 JSON |
| 生产 | INFO | 控制台JSON+ 文件JSON | JSON 结构化 |
**日志文件输出(基于 lumberjack 轮转):**
| 文件 | 级别 | 用途 |
|------|------|------|
| `logs/app.log` | 全量(与配置级别一致) | 持久化存储、接入 ELK/Loki |
| `logs/error.log` | 仅 WARN + ERROR | 快速定位问题 |
**日志文件轮转策略:**
| 配置项 | 开发默认值 | 生产建议值 | 说明 |
|--------|-----------|-----------|------|
| `max_size` | 50 MB | 200 MB | 单文件最大大小,超过自动切割 |
| `max_backups` | 10 | 30 | 保留的旧日志文件数量 |
| `max_age` | 30 天 | 90 天 | 旧日志保留天数 |
| `compress` | false | true | 是否压缩归档文件 |
### 8.4 日志格式规范
**结构化日志字段(每条日志必含):**
```json
{
"level": "info",
"ts": "2026-02-27 10:30:00",
"trace_id": "abc-123-def-456",
"module": "auth",
"func": "service.auth_service.Login",
"msg": "用户登录成功",
"user_id": 1,
"ip": "192.168.1.100",
"latency_ms": 25
}
```
### 8.5 函数级日志规范
每个 Service 和 DAO 层的关键函数遵循统一的日志模式:
```go
func (s *AuthService) Login(ctx context.Context, req *dto.LoginRequest) (*dto.LoginResponse, error) {
funcName := "service.auth_service.Login"
// 入口日志:记录关键入参(脱敏)
logs.Info(ctx, funcName, "开始处理登录",
zap.String("account", req.Account),
)
var err error
defer func() {
if err != nil {
// 出口日志(失败):记录错误信息
logs.Error(ctx, funcName, "登录处理失败",
zap.String("account", req.Account),
zap.Error(err),
)
} else {
// 出口日志(成功)
logs.Info(ctx, funcName, "登录处理完成",
zap.String("account", req.Account),
)
}
}()
// 业务逻辑...
return resp, err
}
```
### 8.6 请求日志中间件Access Log 层)
HTTP 请求日志中间件采用社区标准的「分层不重复」策略,只记录 HTTP 请求级元信息,不记录请求 Body。
#### 分层日志架构
| 层级 | 职责 | caller 指向 | 记录内容 |
|------|------|------------|---------|
| **中间件层Access Log** | HTTP 请求元信息 | 中间件源码 | method / path / handler / status / latency / ip / query |
| **Controller 层** | 结构化请求参数 | Controller 代码行号 | ShouldBindJSON 后的业务参数(可精准脱敏) |
| **Service 层** | 业务逻辑关键节点 | Service 代码行号 | 业务状态变更、外部调用等 |
| **DAO 层** | 数据操作 | DAO 代码行号 | SQL 操作、缓存操作等 |
> 同一请求的各层日志通过 `trace_id` 串联,可完整追踪请求的处理链路。
#### 中间件记录字段
| 字段 | 说明 | 记录时机 |
|------|------|---------|
| `method` | HTTP 方法 | 始终记录 |
| `path` | 请求路径 | 始终记录 |
| `handler` | 处理函数名(如 `controller.auth_controller.Login` | 始终记录 |
| `status` | HTTP 状态码 | 始终记录 |
| `latency` | 请求耗时 | 始终记录 |
| `ip` | 客户端 IP | 始终记录 |
| `user_agent` | 客户端 User-Agent | 始终记录 |
| `query` | URL Query 参数 | 有 Query 时记录 |
| `resp_body` | 响应 Body最大 2KB | 仅 4xx/5xx 错误响应 |
| `error` | Gin 错误信息 | 有 c.Errors 时记录 |
#### 状态码分级输出
| 情况 | 日志级别 |
|------|---------|
| 5xx 服务器错误 | ERROR |
| 4xx 客户端错误 | WARN |
| 请求耗时 > 500ms | WARN慢请求告警 |
| 正常请求 | INFO |
#### 日志输出示例
**正常 GET 请求(带 Query 参数):**
```json
{
"level": "INFO",
"ts": "2026-02-28 16:08:40",
"caller": "middleware/logger.go:108",
"trace_id": "b94a08f8-3c1e-4567-96ab-7bfe1057239a",
"func": "middleware.Logger",
"msg": "请求完成",
"method": "GET",
"path": "/health",
"handler": "main.main.func1",
"status": 200,
"latency": 0.000530,
"ip": "192.168.1.100",
"user_agent": "curl/8.7.1",
"query": "foo=bar&debug=true"
}
```
**POST 请求(中间件只记录元信息,参数由 Controller 层记录):**
```json
{
"level": "INFO",
"ts": "2026-02-28 16:08:41",
"caller": "middleware/logger.go:108",
"trace_id": "abc-123-def-456",
"func": "middleware.Logger",
"msg": "请求完成",
"method": "POST",
"path": "/api/v1/auth/login",
"handler": "controller.auth_controller.Login",
"status": 200,
"latency": 0.025
}
```
**错误响应4xx/5xx 额外记录响应 Body**
```json
{
"level": "WARN",
"ts": "2026-02-28 16:08:42",
"caller": "middleware/logger.go:99",
"trace_id": "79f3f6fb-6997-4645-9c33-23f832dc6af2",
"func": "middleware.Logger",
"msg": "客户端错误",
"method": "POST",
"path": "/api/v1/auth/register",
"handler": "controller.auth_controller.Register",
"status": 400,
"resp_body": "{\"code\":400,\"message\":\"邮箱格式不正确\"}"
}
```
**Controller 层日志caller 精确指向业务代码行号):**
```json
{
"level": "INFO",
"ts": "2026-02-28 16:08:41",
"caller": "controller/auth_controller.go:35",
"trace_id": "abc-123-def-456",
"func": "controller.auth_controller.Login",
"msg": "开始处理登录",
"account": "testuser"
}
```
> **设计理念**:中间件层关注"哪个接口被调用、结果如何",业务层关注"具体做了什么、哪里出错"。两者通过 `trace_id` 串联,既避免日志重复,又能完整追踪请求链路。
### 8.7 WebSocket 日志
WebSocket 连接和消息也纳入日志追踪体系:
```
[INFO] ws_conn=conn-789 | user_id=1 | WebSocket 连接建立
[INFO] ws_conn=conn-789 | user_id=1 | trace_id=ws-001 | 收到事件: im.message.send | conversation_id=5
[INFO] ws_conn=conn-789 | user_id=1 | trace_id=ws-001 | 消息处理完成 | msg_id=10086 | latency=8ms
[WARN] ws_conn=conn-789 | user_id=1 | 心跳超时,准备断开连接
[INFO] ws_conn=conn-789 | user_id=1 | WebSocket 连接断开 | 在线时长=3600s
```
### 8.8 跨服务链路追踪Go ↔ mediasoup Node
Go 服务调用 mediasoup Node 服务时,通过 HTTP Header 传递 trace_id
```
Go 服务:
[INFO] trace_id=abc-123 | 调用 mediasoup: POST /media/transport/create | room=123-456
mediasoup Node 服务:
[INFO] trace_id=abc-123 | 收到请求: POST /media/transport/create | room=123-456
[INFO] trace_id=abc-123 | Transport 创建成功 | transport_id=xxx | latency=5ms
Go 服务:
[INFO] trace_id=abc-123 | mediasoup 调用完成 | transport_id=xxx | latency=12ms
```
### 8.9 敏感信息脱敏
日志中的敏感信息必须脱敏处理:
| 字段 | 脱敏规则 | 示例 |
|------|---------|------|
| 密码 | 不记录 | `password=***` |
| Token | 只记录前后各 4 位 | `token=eyJh...5NiJ` |
| 邮箱 | 部分隐藏 | `email=zh***@example.com` |
| 手机号 | 中间 4 位隐藏 | `phone=138****8000` |
| IP | 完整记录(用于安全审计) | `ip=192.168.1.100` |
### 8.10 前端日志与错误上报
前端通过 API 上报关键错误和操作日志:
```
POST /api/v1/client/log # 前端错误上报接口
{
"level": "error",
"module": "mediasoup-client",
"message": "Transport connection failed",
"stack": "Error: ICE connection failed...",
"page": "/meeting/room",
"user_agent": "...",
"time": "2026-02-27 18:06:40"
}
```
前端需要记录的关键场景:
- WebSocket 连接失败/断线
- mediasoup-client Transport 连接失败
- API 请求超时/5xx 错误
- 页面 JS 异常(通过 `window.onerror` / `Vue.config.errorHandler`