# 前后端集成开发规范

> **适用范围**：EchoChat 项目全端（Go 后端 + admin 管理端 + frontend 用户端）
> **创建日期**：2026-03-02
> **最后更新**：2026-03-04（Phase 2c 全部完成：群聊模块 + MinIO 文件存储 + 已读回执 + 代码审查修复 14 项 + 浏览器测试修复 21 项）

---

## 一、API 响应格式规范

### 1.1 后端统一响应结构

所有后端 API 必须使用 `pkg/utils/response.go` 中的 `Response*` 系列函数返回响应，保证结构一致：

```json
{
  "code": 0,
  "message": "success",
  "data": {},
  "trace_id": "uuid-v4",
  "time": "2026-03-02 14:00:00"
}
```

| 字段 | 类型 | 说明 |
|------|------|------|
| `code` | int | 业务状态码：0=成功，非 0=HTTP 状态码 |
| `message` | string | 人类可读的描述信息，**前端直接展示给用户** |
| `data` | any | 业务数据，失败时为 null |
| `trace_id` | string | 请求追踪 ID，用于日志排查 |
| `time` | string | 服务器响应时间 |

### 1.2 后端 Response 函数对照

| 函数 | HTTP Status | code | 使用场景 |
|------|-------------|------|----------|
| `ResponseOK` | 200 | 0 | 请求成功 |
| `ResponseCreated` | 201 | 0 | 资源创建成功 |
| `ResponseBadRequest` | 400 | 400 | 参数错误、业务规则校验失败 |
| `ResponseUnauthorized` | 401 | 401 | 认证失败（密码错误、Token 无效/过期） |
| `ResponseForbidden` | 403 | 403 | 权限不足、账号被禁用 |
| `ResponseNotFound` | 404 | 404 | 资源不存在 |
| `ResponseError` | 500 | 500 | 服务器内部错误 |

### 1.3 message 字段要求

- **必须是中文**，面向最终用户
- **必须精准描述错误原因**，不能笼统（如 "操作失败"）
- **安全场景除外**：登录时"用户不存在"和"密码错误"统一为 "账号或密码错误"

---

## 二、HTTP 状态码使用规范

### 2.1 状态码语义

| 状态码 | 语义 | 后端使用场景 |
|--------|------|-------------|
| 200 | OK | 查询成功、操作成功 |
| 201 | Created | 资源创建成功（注册、新建用户） |
| 400 | Bad Request | 参数校验失败、业务规则不满足 |
| 401 | Unauthorized | 认证失败：密码错误、Token 过期/无效/已注销 |
| 403 | Forbidden | 权限不足：角色不匹配、账号被禁用/注销 |
| 404 | Not Found | 请求的资源不存在 |
| 500 | Internal Server Error | 服务器内部错误 |

### 2.2 安全约束

**登录接口的 401 响应**：

```
✅ 正确：用户不存在 → 401 "账号或密码错误"
✅ 正确：密码错误 → 401 "账号或密码错误"
❌ 错误：用户不存在 → 404 "用户不存在"（暴露用户是否注册）
```

防止用户枚举攻击，登录场景下所有认证失败统一返回相同状态码和信息。

---

## 三、前端错误处理规范

### 3.1 核心原则

1. **后端 message 优先**：所有 HTTP 错误提示必须优先使用 `data.message`
2. **禁止硬编码覆盖**：不能用前端硬编码的文案替换后端返回的错误信息
3. **Fallback 仅兜底**：`|| '默认文案'` 仅在后端无响应体或无 message 字段时触发
4. **按状态码分类处理**：不同 HTTP 状态码执行不同的副作用（跳转、清 Token 等）

### 3.2 管理端（admin/）错误处理

文件：`admin/src/utils/request.js`

```
HTTP 响应成功（2xx）:
  ├─ code === 0 → 返回数据
  └─ code !== 0 → ElMessage.error(data.message || '请求失败')

HTTP 响应错误：
  ├─ 401
  │   ├─ 在登录页 → ElMessage.error(data.message || '登录已过期')
  │   └─ 非登录页 → ElMessage.error(data.message || '登录已过期') + 清 Token + 跳转 /login
  ├─ 403 → ElMessage.error(data.message || '没有访问权限')
  ├─ 其他 → ElMessage.error(data.message || '请求错误(N)')
  └─ 网络异常 → ElMessage.error('网络异常，请检查网络连接')
```

**401 场景区分的关键**：通过 `router.currentRoute.value.path === '/login'` 判断当前是否在登录页。登录页不清 Token 不跳转，仅显示错误。

### 3.3 前台用户端（frontend/）错误处理

文件：`frontend/src/utils/request.js`

```
HTTP 响应成功（2xx）:
  ├─ code === 0 → resolve(data)
  └─ code !== 0 → showToast(data.message || '请求失败')

HTTP 响应错误：
  ├─ 401
  │   ├─ 登录/注册请求 → showToast(data.message || '登录已过期')
  │   └─ 其他请求 → showToast(data.message || '登录已过期') + removeToken() + 跳转登录页
  ├─ 403 → showToast(data.message || '没有访问权限')
  ├─ 其他 → showToast(data.message || '请求错误(N)')
  └─ 网络异常 → showToast('网络异常，请检查网络连接')
```

**401 场景区分的关键**：通过正则 `/\/auth\/(login|register)$/` 匹配请求 URL。登录/注册请求不清 Token 不跳转。

**reject 对象结构（重要）**：`request.js` 的 `reject(data)` 传出的是 `{ code, message, ... }` 对象（即后端响应体本身），不是包装的 `{ data: { message } }`。因此在页面 catch 中获取错误信息时，必须使用 `e?.message` 而非 `e?.data?.message`。

### 3.4 错误处理检查清单

新增 API 接口时，必须确认以下各项：

- [ ] 后端 Controller 使用正确的 `Response*` 函数
- [ ] 后端 Controller 的 error switch 覆盖所有已知业务错误
- [ ] 后端不使用 `_` 忽略 error，至少 log warning
- [ ] 前端调用方的 catch 不自行覆盖 message（交给拦截器统一处理）
- [ ] 前端页面级 catch 只做状态清理（如 loading = false），不重复弹提示
- [ ] 前端 catch 中使用 `e?.message`（非 `e?.data?.message`）获取错误信息

---

## 四、后端错误处理规范

### 4.1 Controller 层

- 使用 switch/case 或 if/else 匹配所有已知业务错误
- 每种错误映射到正确的 HTTP 状态码
- message 参数必须是用户友好的中文描述
- default 分支处理未知错误，返回 500

```go
if err := svc.DoSomething(ctx, req); err != nil {
    switch err {
    case service.ErrNotFound:
        utils.ResponseNotFound(c, "资源不存在")
    case service.ErrInvalidParam:
        utils.ResponseBadRequest(c, "参数无效")
    default:
        logs.Error(ctx, funcName, "操作失败", zap.Error(err))
        utils.ResponseError(c, "操作失败")
    }
    return
}
```

### 4.2 Service 层

- 定义明确的业务错误变量（`var ErrXxx = errors.New("...")`）
- 不在 Service 层直接返回 HTTP 状态码，由 Controller 映射
- DAO 层的 `gorm.ErrRecordNotFound` 必须转换为业务错误

### 4.3 禁止忽略错误

```go
// ❌ 错误：忽略 error
roles, _ := s.roleDAO.GetUserRoleCodes(ctx, user.ID)

// ✅ 正确：处理 error
roles, err := s.roleDAO.GetUserRoleCodes(ctx, user.ID)
if err != nil {
    logs.Warn(ctx, funcName, "获取角色失败", zap.Error(err))
    roles = []string{}
}
```

---

## 五、前后端错误码对照表

| 后端场景 | HTTP | message 示例 | 前端行为 |
|---------|------|-------------|---------|
| 登录-密码错误 | 401 | 账号或密码错误 | 显示 message |
| 登录-用户不存在 | 401 | 账号或密码错误 | 显示 message |
| 登录-账号被禁用 | 403 | 账号已被禁用 | 显示 message |
| Token 过期 | 401 | 认证已过期或无效，请重新登录 | 显示 message + 清 Token + 跳转 |
| Token 已注销 | 401 | 认证已失效，请重新登录 | 显示 message + 清 Token + 跳转 |
| 缺少认证信息 | 401 | 缺少认证信息 | 显示 message + 清 Token + 跳转 |
| 权限不足 | 403 | 权限不足，需要角色: admin 或 super_admin | 显示 message |
| 参数校验失败 | 400 | 参数校验失败: ... | 显示 message |
| 用户名已注册 | 400 | 用户名或邮箱已被注册 | 显示 message |
| 不能禁用自己 | 400 | 不能禁用自己的账号 | 显示 message |
| 用户不存在 | 404 | 用户不存在 | 显示 message |
| 服务器内部错误 | 500 | 获取用户列表失败 | 显示 message |
| 网络异常 | - | （无响应体） | 显示前端 fallback 文案 |

---

## 六、前后台 Token 隔离规范

### 6.1 核心原则

前台用户端（frontend）和后台管理端（admin）的 Token **必须在 Redis 中完全隔离**，同一用户可以同时在两端保持登录状态，互不影响。

### 6.2 Redis Key 格式

```
echo:auth:token:{client_type}:{user_id}     → Access Token
echo:auth:refresh:{client_type}:{user_id}    → Refresh Token
```

| client_type | 说明 | 示例 Key |
|------------|------|----------|
| `frontend` | 前台用户端 | `echo:auth:token:frontend:1` |
| `admin` | 后台管理端 | `echo:auth:token:admin:1` |

### 6.3 JWT Claims 中的 client_type

JWT Token 的 Claims 中包含 `client_type` 字段，用于：
- 中间件校验时定位正确的 Redis key
- 登出时只删除当前端的 Token
- Refresh Token 刷新时保持 client_type 不变

```json
{
  "user_id": 1,
  "username": "admin_test",
  "roles": ["user", "admin"],
  "client_type": "admin",
  "sub": "access",
  "exp": 1709400000,
  "iss": "echochat"
}
```

### 6.4 API 路由区分

| 端 | 登录 API | 说明 |
|----|---------|------|
| 前台 | `POST /api/v1/auth/login` | clientType=frontend |
| 管理端 | `POST /api/v1/admin/auth/login` | clientType=admin，额外检查管理员角色 |

### 6.5 登出行为

- 前台登出：只删除 `echo:auth:token:frontend:{user_id}`，不影响管理端
- 管理端登出：只删除 `echo:auth:token:admin:{user_id}`，不影响前台
- 管理端禁用用户：应删除该用户两端的所有 Token（由管理模块负责）

### 6.6 检查清单

新增认证相关功能时，必须确认：

- [ ] Login 方法传递了正确的 clientType
- [ ] 前端调用了正确的登录 API 端点
- [ ] Redis key 包含 clientType 前缀
- [ ] JWT Claims 中包含 client_type 字段
- [ ] 登出时只删除对应 clientType 的 Token
- [ ] Token 刷新时保持原 clientType 不变

---

## 7. 角色等级与权限管控规范

### 7.1 角色等级设计

`auth_roles` 表 `level` 字段，值越小权限越高，预留间隔：

| 角色 | Code | Level | 说明 |
|------|------|-------|------|
| 超级管理员 | super_admin | 1 | 最高权限 |
| 管理员 | admin | 10 | 后台管理 |
| 普通用户 | user | 100 | 基础权限 |

用户可拥有多个角色，取**最小 level 值**作为有效权限等级。

### 7.2 权限管控规则

**核心原则：操作者的 level 必须严格小于目标用户的 level，才能执行管理操作。**

1. **更新用户状态（禁用/启用）**：操作者 level < 目标用户 level
2. **设置用户角色**：操作者 level < 目标用户 level，且不能分配 level <= 自身的角色
3. **前端管控**：禁用/启用按钮对高等级用户隐藏，高等级角色 checkbox 禁用

### 7.3 API 响应变更

`AdminUserInfo` 的 `roles` 字段从 `[]string` 改为 `[]RoleInfo`：

```json
{
    "roles": [
        { "code": "admin", "name": "管理员", "level": 10 },
        { "code": "user", "name": "普通用户", "level": 100 }
    ],
    "max_level": 10
}
```

### 7.4 检查清单

涉及用户管理操作时，必须确认：

- [ ] 后端 Service 层调用 `checkPermissionLevel` 进行等级校验
- [ ] 权限不足时返回 403（`ErrInsufficientPermission`）
- [ ] 前端通过比较 `adminMaxLevel` 和 `targetMaxLevel` 控制 UI 可见性
- [ ] 角色分配使用全量覆盖模式（`SetUserRoles`），非追加模式

---

## 8. Go 后端架构与编码规范

> 详细规范已拆分为独立文档，见 `docs/conventions/backend-module-architecture.md`
>
> 包含：模块分层架构、层间调用规则、跨模块接口注入模式、Wire 依赖注入、日志标准、Controller 错误处理、批量查询优化、系统消息规范、WS 事件推送模式、前端 Store/API 封装规范

---

## 9. WebSocket 事件联动规范

### 9.1 适用范围

仅前台用户端（frontend）使用 WebSocket 实时通讯，管理端（admin）使用 REST 轮询。

### 9.2 连接管理

- WebSocket 地址：`ws(s)://host/ws?token=xxx`
- 认证方式：URL Query 参数传递 JWT Token（前台 frontend Token）
- 心跳间隔：30 秒 ping/pong
- 断线重连：指数退避（1s → 2s → 4s → 8s → 30s max）

### 9.3 事件命名规范

格式：`{模块}.{对象}.{动作}`

| 事件 | 方向 | 说明 |
|------|------|------|
| `heartbeat` | 双向 | 心跳保活 |
| `notify.friend.request` | 服务端 → 客户端 | 收到好友申请 |
| `contact.request.accepted` | 服务端 → 客户端 | 好友申请被接受 |
| `user.status.online` | 服务端 → 客户端 | 好友上线 |
| `user.status.offline` | 服务端 → 客户端 | 好友下线 |

### 9.4 前端事件处理原则

1. **WebSocket Store 统一管理**：连接状态、事件监听、消息发送由 `store/websocket.js` 管理
2. **业务 Store 订阅事件**：各模块 Store（如 `contact.js`）通过 WebSocket Store 注册事件回调
3. **避免页面直接操作 WebSocket**：页面组件通过 Store 间接与 WebSocket 交互

### 9.5 WebSocket 详细协议

> 完整事件协议见 `docs/api/frontend/websocket.md`
> WS 事件推送模式和代码示例见 `docs/conventions/backend-module-architecture.md` 第九章
