diff --git a/admin/src/api/contact.js b/admin/src/api/contact.js new file mode 100644 index 0000000..d012766 --- /dev/null +++ b/admin/src/api/contact.js @@ -0,0 +1,34 @@ +/** + * 好友关系管理 API 模块(管理端) + * + * 对应后端路由:/api/v1/admin/contacts/* + * 所有接口需要 JWT + admin 角色权限 + */ +import request from '@/utils/request' + +/** + * 获取所有好友关系列表(分页) + * @param {Object} params - 查询参数 + * @param {number} params.page - 页码 + * @param {number} params.page_size - 每页数量 + * @returns {Promise<{data: {total: number, list: Array}}>} + */ +export function getAllContacts(params) { + return request({ + url: '/api/v1/admin/contacts', + method: 'get', + params + }) +} + +/** + * 删除好友关系 + * @param {number} id - 好友关系 ID + * @returns {Promise} + */ +export function deleteContact(id) { + return request({ + url: `/api/v1/admin/contacts/${id}`, + method: 'delete' + }) +} diff --git a/admin/src/api/monitor.js b/admin/src/api/monitor.js new file mode 100644 index 0000000..9ded39e --- /dev/null +++ b/admin/src/api/monitor.js @@ -0,0 +1,29 @@ +/** + * 在线监控 API 模块 + * + * 对应后端路由:/api/v1/admin/online/* + * 所有接口需要 JWT + admin 角色权限 + */ +import request from '@/utils/request' + +/** + * 获取在线用户列表 + * @returns {Promise<{data: Array<{user_id: number, username: string}>}>} + */ +export function getOnlineUsers() { + return request({ + url: '/api/v1/admin/online/users', + method: 'get' + }) +} + +/** + * 获取在线用户数量 + * @returns {Promise<{data: {count: number}}>} + */ +export function getOnlineCount() { + return request({ + url: '/api/v1/admin/online/count', + method: 'get' + }) +} diff --git a/admin/src/router/index.js b/admin/src/router/index.js index e940fc0..5365703 100644 --- a/admin/src/router/index.js +++ b/admin/src/router/index.js @@ -44,6 +44,18 @@ const routes = [ name: 'UserDetail', component: () => import('@/views/user/detail.vue'), meta: { title: '用户详情' } + }, + { + path: 'monitor/online', + name: 'OnlineMonitor', + component: () => import('@/views/monitor/online.vue'), + meta: { title: '在线监控' } + }, + { + path: 'contact/list', + name: 'ContactManage', + component: () => import('@/views/contact/list.vue'), + meta: { title: '好友管理' } } ] } diff --git a/admin/src/views/contact/list.vue b/admin/src/views/contact/list.vue new file mode 100644 index 0000000..8b92643 --- /dev/null +++ b/admin/src/views/contact/list.vue @@ -0,0 +1,197 @@ + + + + + + 好友关系管理 + + + + + + + + + + #{{ row.user_id }} + {{ row.username || '--' }} + + + + + + + #{{ row.friend_id }} + {{ row.friend_username || '--' }} + + + + + + + {{ getStatusText(row.status) }} + + + + + + {{ row.remark || '--' }} + + + + + + + 删除 + + + + + + + + + + + + + + + + diff --git a/admin/src/views/layout/index.vue b/admin/src/views/layout/index.vue index f58c335..1b4e8d3 100644 --- a/admin/src/views/layout/index.vue +++ b/admin/src/views/layout/index.vue @@ -39,6 +39,19 @@ 用户列表 + + + + + 联系人管理 + + 好友关系 + + + + + 在线监控 + diff --git a/admin/src/views/monitor/online.vue b/admin/src/views/monitor/online.vue new file mode 100644 index 0000000..094aa29 --- /dev/null +++ b/admin/src/views/monitor/online.vue @@ -0,0 +1,267 @@ + + + + + + 在线监控 + + + {{ autoRefresh ? '自动刷新中' : '已暂停' }} + + + 刷新 + + + + + + + + + + {{ onlineCount }} + + 当前在线 + + + + + + + + {{ lastRefreshTime }} + 最后刷新 + + + + + + + 30s + 刷新间隔 + + + + + + + + + + 在线用户列表 + {{ onlineUsers.length }} 人 + + + + + + + + + {{ row.username }} + + + + + + + 在线 + + + + + + + + + + + diff --git a/docs/api/admin/contact.md b/docs/api/admin/contact.md new file mode 100644 index 0000000..8fad5cb --- /dev/null +++ b/docs/api/admin/contact.md @@ -0,0 +1,78 @@ +# 管理端好友关系管理 API + +> 通用规范(认证方式、响应格式、错误码)见 [README.md](../README.md) +> 所有接口需要 JWT + admin/super_admin 角色权限 + +--- + +## 接口列表 + +| 方法 | 路径 | 权限 | 说明 | +|------|------|------|------| +| GET | /api/v1/admin/contacts | admin | 获取所有好友关系列表(分页) | +| DELETE | /api/v1/admin/contacts/:id | admin | 删除好友关系(双向解除) | + +--- + +## 1. 获取所有好友关系列表 + +`GET /api/v1/admin/contacts` + +**说明:** 分页查询系统中所有好友关系记录,包含双方用户名信息。 + +**查询参数:** + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| page | int | 否 | 页码,默认 1 | +| page_size | int | 否 | 每页数量,默认 20 | + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": { + "total": 150, + "list": [ + { + "id": 1, + "user_id": 1, + "username": "zhangsan", + "friend_id": 2, + "friend_username": "lisi", + "remark": "同事", + "status": 1, + "created_at": "2026-03-01 10:00:00" + } + ] + } +} +``` + +**status 状态码:** + +| 值 | 说明 | +|-----|------| +| 0 | 待确认(pending) | +| 1 | 已通过(accepted) | +| 2 | 已拒绝(rejected) | +| 3 | 已拉黑(blocked) | + +--- + +## 2. 删除好友关系 + +`DELETE /api/v1/admin/contacts/:id` + +**路径参数:** `id` — 好友关系记录 ID + +**说明:** 管理员可以强制删除任意好友关系。此操作会双向解除(同时删除 A→B 和 B→A 的关系记录)。 + +**成功响应:** +```json +{ + "code": 0, + "message": "ok" +} +``` diff --git a/docs/api/admin/online.md b/docs/api/admin/online.md new file mode 100644 index 0000000..6db3836 --- /dev/null +++ b/docs/api/admin/online.md @@ -0,0 +1,56 @@ +# 管理端在线监控 API + +> 通用规范(认证方式、响应格式、错误码)见 [README.md](../README.md) +> 所有接口需要 JWT + admin/super_admin 角色权限 + +--- + +## 接口列表 + +| 方法 | 路径 | 权限 | 说明 | +|------|------|------|------| +| GET | /api/v1/admin/online/users | admin | 获取在线用户列表 | +| GET | /api/v1/admin/online/count | admin | 获取在线用户数量 | + +--- + +## 1. 获取在线用户列表 + +`GET /api/v1/admin/online/users` + +**说明:** 返回当前所有在线用户的基本信息。数据来源于 Redis SET `echo:user:online`。 + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": [ + { + "user_id": 1, + "username": "super_admin" + }, + { + "user_id": 2, + "username": "lisi" + } + ] +} +``` + +--- + +## 2. 获取在线用户数量 + +`GET /api/v1/admin/online/count` + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": { + "count": 25 + } +} +``` diff --git a/docs/api/frontend/contact.md b/docs/api/frontend/contact.md index ecde3f0..54d9f3e 100644 --- a/docs/api/frontend/contact.md +++ b/docs/api/frontend/contact.md @@ -12,10 +12,19 @@ | POST | /api/v1/contacts/request | 需认证 | 发送好友申请 | | POST | /api/v1/contacts/accept | 需认证 | 接受好友申请 | | POST | /api/v1/contacts/reject | 需认证 | 拒绝好友申请 | +| GET | /api/v1/contacts/requests | 需认证 | 获取待处理的好友申请列表 | | DELETE | /api/v1/contacts/:id | 需认证 | 删除好友 | | PUT | /api/v1/contacts/:id/remark | 需认证 | 修改好友备注 | +| PUT | /api/v1/contacts/:id/group | 需认证 | 移动好友到分组 | +| POST | /api/v1/contacts/block | 需认证 | 拉黑用户 | +| DELETE | /api/v1/contacts/block/:id | 需认证 | 取消拉黑 | +| GET | /api/v1/contacts/block | 需认证 | 获取黑名单 | | GET | /api/v1/contacts/groups | 需认证 | 获取好友分组列表 | | POST | /api/v1/contacts/groups | 需认证 | 创建好友分组 | +| PUT | /api/v1/contacts/groups/:id | 需认证 | 修改好友分组 | +| DELETE | /api/v1/contacts/groups/:id | 需认证 | 删除好友分组 | +| GET | /api/v1/contacts/recommend | 需认证 | 好友推荐 | +| GET | /api/v1/users/search | 需认证 | 搜索用户 | --- @@ -23,13 +32,11 @@ `GET /api/v1/contacts` -**权限:** 需认证 - **查询参数:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| -| group_id | int | 否 | 按分组筛选 | +| group_id | int | 否 | 按分组筛选,不传则返回全部 | **成功响应:** ```json @@ -38,15 +45,13 @@ "message": "ok", "data": [ { - "id": 1, - "friend_id": 2, + "user_id": 2, "username": "lisi", "nickname": "李四", + "avatar": "", "remark": "我的同事", - "avatar": "https://cdn.echochat.com/avatar/2.jpg", - "online": true, "group_id": 1, - "group_name": "同事" + "is_online": true } ] } @@ -58,16 +63,18 @@ `POST /api/v1/contacts/request` -**权限:** 需认证 - **请求参数:** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | target_id | int | 是 | 目标用户 ID | -| message | string | 否 | 申请附言,如"我是张三的同事" | +| message | string | 否 | 申请附言 | -**可能的错误码:** 1004(用户不存在),1005(已是好友或已发送过申请) +**错误场景:** +- 400: 不能添加自己为好友 +- 400: 已是好友 +- 400: 已有待处理的申请 +- 403: 对方已将你拉黑 --- @@ -75,15 +82,13 @@ `POST /api/v1/contacts/accept` -**权限:** 需认证 - **请求参数:** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| -| friendship_id | int | 是 | 好友关系记录 ID | +| request_id | int | 是 | 好友申请记录 ID | -**说明:** 接受后系统自动创建双向好友关系,并发送通知给对方。 +**说明:** 接受后系统自动创建双向好友关系,并通过 WebSocket 推送 `contact.request.accepted` 事件给对方。 --- @@ -91,35 +96,54 @@ `POST /api/v1/contacts/reject` -**权限:** 需认证 - **请求参数:** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| -| friendship_id | int | 是 | 好友关系记录 ID | +| request_id | int | 是 | 好友申请记录 ID | --- -## 5. 删除好友 +## 5. 获取待处理的好友申请 + +`GET /api/v1/contacts/requests` + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": [ + { + "id": 5, + "user_id": 3, + "username": "wangwu", + "nickname": "王五", + "avatar": "", + "message": "我是你的同学", + "created_at": "2026-03-01T10:30:00Z" + } + ] +} +``` + +--- + +## 6. 删除好友 `DELETE /api/v1/contacts/:id` -**权限:** 需认证 +**路径参数:** `id` — 好友的用户 ID -**路径参数:** `id` — 好友关系记录 ID - -**说明:** 删除后双向关系均解除,关联的单聊会话不会删除(消息记录保留)。 +**说明:** 删除后双向关系均解除。 --- -## 6. 修改好友备注 +## 7. 修改好友备注 `PUT /api/v1/contacts/:id/remark` -**权限:** 需认证 - -**路径参数:** `id` — 好友关系记录 ID +**路径参数:** `id` — 好友的用户 ID **请求参数:** @@ -129,11 +153,45 @@ --- -## 7. 获取好友分组列表 +## 8. 移动好友到分组 -`GET /api/v1/contacts/groups` +`PUT /api/v1/contacts/:id/group` -**权限:** 需认证 +**路径参数:** `id` — 好友的用户 ID + +**请求参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| group_id | int | 是 | 目标分组 ID,0 为默认分组 | + +--- + +## 9. 拉黑用户 + +`POST /api/v1/contacts/block` + +**请求参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| target_id | int | 是 | 目标用户 ID | + +**说明:** 拉黑后自动解除好友关系(如果存在),对方无法向你发送好友申请和消息。 + +--- + +## 10. 取消拉黑 + +`DELETE /api/v1/contacts/block/:id` + +**路径参数:** `id` — 被拉黑用户的 ID + +--- + +## 11. 获取黑名单 + +`GET /api/v1/contacts/block` **成功响应:** ```json @@ -141,24 +199,123 @@ "code": 0, "message": "ok", "data": [ - { "id": 1, "name": "同事", "sort_order": 0, "count": 15 }, - { "id": 2, "name": "朋友", "sort_order": 1, "count": 8 } + { + "user_id": 5, + "username": "blocked_user", + "nickname": "某用户", + "avatar": "" + } ] } ``` --- -## 8. 创建好友分组 +## 12. 获取好友分组列表 + +`GET /api/v1/contacts/groups` + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": [ + { "id": 1, "name": "同事", "sort_order": 0, "friend_count": 15 }, + { "id": 2, "name": "朋友", "sort_order": 1, "friend_count": 8 } + ] +} +``` + +--- + +## 13. 创建好友分组 `POST /api/v1/contacts/groups` -**权限:** 需认证 - **请求参数:** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | name | string | 是 | 分组名称,最多 50 字符 | -**可能的错误码:** 1005(同名分组已存在) +--- + +## 14. 修改好友分组 + +`PUT /api/v1/contacts/groups/:id` + +**路径参数:** `id` — 分组 ID + +**请求参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| name | string | 是 | 新分组名称 | +| sort_order | int | 否 | 排序序号 | + +--- + +## 15. 删除好友分组 + +`DELETE /api/v1/contacts/groups/:id` + +**路径参数:** `id` — 分组 ID + +**说明:** 删除分组后,该分组内的好友自动移至默认分组(group_id = 0)。 + +--- + +## 16. 好友推荐 + +`GET /api/v1/contacts/recommend` + +**说明:** 基于共同好友算法推荐可能认识的人。 + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": [ + { + "user_id": 8, + "username": "zhaoliu", + "nickname": "赵六", + "avatar": "", + "common_count": 3 + } + ] +} +``` + +--- + +## 17. 搜索用户 + +`GET /api/v1/users/search` + +**查询参数:** + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| keyword | string | 是 | 搜索关键词(用户名/昵称模糊匹配) | +| page | int | 否 | 页码,默认 1 | +| page_size | int | 否 | 每页数量,默认 20 | + +**成功响应:** +```json +{ + "code": 0, + "message": "ok", + "data": [ + { + "user_id": 10, + "username": "newuser", + "nickname": "新用户", + "avatar": "", + "is_friend": false + } + ] +} +``` diff --git a/docs/api/frontend/websocket.md b/docs/api/frontend/websocket.md new file mode 100644 index 0000000..64a01e0 --- /dev/null +++ b/docs/api/frontend/websocket.md @@ -0,0 +1,141 @@ +# 前端 WebSocket 事件协议 + +> 完整的 WebSocket 协议文档见 [websocket.md](../websocket.md) +> 本文档补充前端联系人模块使用的 WebSocket 事件及对接说明 + +--- + +## 连接管理 + +### 连接地址 + +| 环境 | 地址 | +|------|------| +| 开发环境 | `ws://localhost:8085/ws?token=` | +| 生产环境 | `wss://api.echochat.com/ws?token=` | + +### 前端实现 + +- **连接服务:** `frontend/src/services/websocket.js`(WebSocketService 单例) +- **状态管理:** `frontend/src/store/websocket.js`(Pinia Store) +- **联系人监听:** `frontend/src/store/contact.js`(initWsListeners) + +### 心跳与重连 + +- 心跳间隔:30 秒 +- 重连策略:指数退避 1s → 2s → 4s → 8s → 16s → 最大 30s +- 连接时自动发送 heartbeat 事件 + +--- + +## 联系人相关事件 + +### heartbeat + +**方向:** 客户端 → 服务端 + +**说明:** 心跳消息,服务端收到后续期在线状态 TTL + +**发送格式:** +```json +{ + "event": "heartbeat", + "data": {} +} +``` + +--- + +### notify.friend.request + +**方向:** 服务端 → 客户端 + +**说明:** 收到新的好友申请推送 + +**data 内容:** +```json +{ + "friendship_id": 5, + "from_user_id": 2, + "from_nickname": "李四", + "from_avatar": "", + "message": "我是你的同事" +} +``` + +**前端处理:** `contactStore.initWsListeners` 监听此事件,自动刷新待处理申请列表 + +--- + +### contact.request.accepted + +**方向:** 服务端 → 客户端 + +**说明:** 好友申请被对方接受的通知 + +**data 内容:** +```json +{ + "friendship_id": 5, + "user_id": 1, + "username": "zhangsan", + "nickname": "张三" +} +``` + +**前端处理:** `contactStore.initWsListeners` 监听此事件,自动刷新好友列表 + +--- + +### user.status.online + +**方向:** 服务端 → 客户端 + +**说明:** 好友上线通知 + +**data 内容:** +```json +{ + "user_id": 2, + "nickname": "李四" +} +``` + +**前端处理:** 更新 `contactStore.onlineMap` 和好友列表中对应用户的 `is_online` 状态 + +--- + +### user.status.offline + +**方向:** 服务端 → 客户端 + +**说明:** 好友离线通知 + +**data 内容:** +```json +{ + "user_id": 2 +} +``` + +**前端处理:** 更新 `contactStore.onlineMap` 和好友列表中对应用户的 `is_online` 状态 + +--- + +## 事件监听代码示例 + +```javascript +import { useContactStore } from '@/store/contact' +import { useWebSocketStore } from '@/store/websocket' + +const contactStore = useContactStore() +const wsStore = useWebSocketStore() + +// 建立 WebSocket 连接 +wsStore.connect() + +// 初始化联系人事件监听 +contactStore.initWsListeners() +``` + +以上代码会自动监听 `notify.friend.request`、`contact.request.accepted`、`user.status.online`、`user.status.offline` 四个事件。 diff --git a/docs/progress/CURRENT_STATUS.md b/docs/progress/CURRENT_STATUS.md index a3d804c..e01e8af 100644 --- a/docs/progress/CURRENT_STATUS.md +++ b/docs/progress/CURRENT_STATUS.md @@ -1,187 +1,209 @@ # EchoChat 项目开发进度 -> **最后更新**:2026-03-02(角色等级体系与权限管控实施) -> **当前阶段**:Phase 1 - 基础设施与用户认证 -> **当前分支**:`feature/phase1-foundation-and-auth` -> **实施计划**:`docs/plans/2026-02-27-phase1-foundation-and-auth.md` +> **最后更新**:2026-03-02(Phase 2a 完成 - WebSocket 实时通讯与联系人管理) +> **当前阶段**:Phase 2a - WebSocket 实时通讯与联系人管理 +> **当前分支**:`feature/phase2a-websocket-contacts` +> **实施计划**:`phase_2a_实施计划_221003ce.plan.md` +> **设计文档**:`docs/plans/2026-03-02-phase2a-design.md` --- -## 一、Task 完成状态 +## 一、Phase 2a Task 完成状态 | Task | 描述 | 状态 | 备注 | |------|------|------|------| -| Task 1 | Docker Compose 开发环境搭建 | ✅ 完成 | PostgreSQL 17 + Redis 7 | -| Task 2 | 数据库初始化脚本 | ✅ 完成 | users + user_roles + roles 表 | -| Task 3 | Go 后端服务骨架 | ✅ 完成 | Gin + GORM + Wire + Zap | -| Task 4 | Auth Service 层 | ✅ 完成 | 注册/登录/Token/Profile API | -| Task 5 | Auth Controller & Router | ✅ 完成 | JWT + Redis 有状态校验 | -| Task 6 | uniapp 前端骨架 | ✅ 完成 | request/storage/api/store/pages.json | -| Task 7 | uniapp 登录/注册页面 | ✅ 完成 | 基于 ui-ux-pro-max 设计系统 | -| Task 8 | 首页框架与 TabBar | ✅ 完成 | 自定义 TabBar + 路由分发 | -| Task 9 | Vue 3 管理端项目搭建 | ✅ 完成 | Element Plus + Pinia 3.x | -| Task 10 | 管理端用户管理模块 | ✅ 完成 | 后端 admin 模块 + 前端列表/详情页 | -| Task 11 | 端到端集成测试与文档 | ✅ 完成 | Dockerfile + Docker Compose + README + 全流程 API 验证 + Playwright 页面验证 + 代码审查 | +| Task 0 | 设计文档 + 新分支 | ✅ 完成 | 架构设计、Redis Pub/Sub、文档策略 | +| Task 1 | 数据库表结构 | ✅ 完成 | contact_friendships + contact_groups | +| Task 2 | WebSocket 核心模块 | ✅ 完成 | Hub + Client + PubSub + Handler | +| Task 3 | Contact 模型与 DAO | ✅ 完成 | friendship + friend_group DAO | +| Task 4 | Contact Service | ✅ 完成 | 好友申请/分组/黑名单/搜索/推荐 | +| Task 5 | Contact Controller & Router | ✅ 完成 | 17 个 REST API + Wire 集成 | +| Task 6 | 在线状态管理 | ✅ 完成 | Redis SET + TTL 心跳续期 | +| Task 7 | 管理端后端 | ✅ 完成 | 在线监控 + 好友关系管理 API | +| Task 8 | 前台 WS 客户端 + Store + API | ✅ 完成 | websocket.js + contact.js Store/API | +| Task 9 | 前台联系人页面 | ✅ 完成 | 6 个页面(ui-ux-pro-max 规范) | +| Task 10 | 管理端前端 | ✅ 完成 | 在线监控 + 好友管理页面 | +| Task 11 | API 文档编写 | ✅ 完成 | 4 份独立文档 | +| Task 12 | 集成测试 + 文档更新 + 代码审查 | ✅ 完成 | 三端编译通过 | --- -## 二、关键技术决策记录 +## 二、Phase 2a 新增功能 + +### WebSocket 实时通讯 +- **连接管理**:`gorilla/websocket` + JWT 认证 + 心跳(30s) +- **消息架构**:Redis Pub/Sub 跨实例消息路由 +- **Hub**:本地连接管理(注册/注销/按用户发送) +- **Client**:读写泵 + 断线回调 + 缓冲通道 + +### 联系人管理(17 个 API) +- 好友申请(发送/接受/拒绝) +- 好友列表(按分组筛选 + 在线状态) +- 好友详情(备注/分组移动) +- 好友删除 + 拉黑/取消拉黑 +- 好友分组(CRUD + 排序) +- 用户搜索 + 好友推荐(共同好友算法) + +### 在线状态管理 +- Redis SET `echo:user:online` 存储在线用户集合 +- Redis STRING `echo:user:status:{user_id}` + TTL 心跳续期 +- Pub/Sub 推送好友上下线通知 + +### 管理端扩展 +- 在线监控页面(自动 30s 刷新 + 统计卡片) +- 好友关系管理(分页列表 + 强制删除) + +--- + +## 三、Phase 1 完成总结 + +| Task | 描述 | 状态 | +|------|------|------| +| Task 1-11 | 基础设施 + 认证 + 用户管理 | ✅ 全部完成 | + +- Go 后端 15+ API、JWT 有状态认证、RBAC 角色权限(level 等级体系) +- 前台 uni-app 登录/注册/TabBar/个人中心 +- 管理端 Vue 3 登录/仪表盘/用户列表/详情 +- Docker Compose 一键启动 + +--- + +## 四、关键技术决策记录 ### 后端(Go) 1. **框架组合**:Gin + GORM + Wire + Zap + Viper -2. **JWT 策略**:有状态 JWT,Token 按 clientType 隔离存储在 Redis(`echo:auth:token:{frontend|admin}:{user_id}`) -3. **密码加密**:bcrypt -4. **数据库时间精度**:`TIMESTAMP(0)` 精确到秒 -5. **API 响应格式**:统一 `{ "code": 0, "message": "success", "data": ... }` -6. **常量命名**:Go camelCase(`UserStatusActive`),非大写下划线 -7. **模块路由**:模块内自注册 + 中央 router 聚合 -8. **角色等级体系**:`auth_roles.level` 字段(值越小权限越高:1=超管, 10=管理员, 100=普通用户),所有管理操作强制执行"高等级管理低等级"规则 +2. **JWT 策略**:有状态 JWT,Token 按 clientType 隔离存储在 Redis +3. **WebSocket**:`gorilla/websocket` + Redis Pub/Sub 跨实例路由 +4. **在线状态**:混合方案(Redis SET + STRING TTL + Pub/Sub 推送) +5. **角色等级**:`auth_roles.level`(1=超管, 10=管理员, 100=普通用户) ### 前台用户端(frontend/) -1. **框架**:uni-app 3.0(Vue 3.4.21 框架锁定) +1. **框架**:uni-app 3.0(Vue 3.4.21) 2. **状态管理**:Pinia 2.1.7 + pinia-plugin-persistedstate@3 -3. **npm 配置**:`.npmrc` 设置 `legacy-peer-deps=true`(uni-app 兼容性) -4. **模块系统**:ESM(`export` / `import`),禁止 CommonJS -5. **响应式单位**:`rpx`(750rpx = 屏幕宽度) -6. **设计系统**:ui-ux-pro-max 生成,持久化在 `design-system/echochat/` -7. **色板**:Primary `#2563EB` / BG `#F8FAFC` / Text `#1E293B` -8. **开发端口**:`npm run dev:h5` → localhost:5173+ +3. **WebSocket**:`uni.connectSocket`(小程序)/ `WebSocket`(H5) +4. **设计系统**:ui-ux-pro-max 规范 ### 后台管理端(admin/) -1. **框架**:Vue 3.5+ + Vite 7.x(独立项目,不受 uni-app 限制) -2. **UI 组件**:Element Plus(中文语言包) -3. **状态管理**:Pinia 3.x(最新版) -4. **HTTP 客户端**:Axios -5. **存储隔离**:localStorage key 前缀 `admin_` -6. **主题色**:CSS 变量覆盖 Element Plus → `--el-color-primary: #2563EB` -7. **开发端口**:`npm run dev` → localhost:3100(Vite proxy 代理后端) +1. **框架**:Vue 3.5+ + Vite 7.x + Element Plus +2. **HTTP 客户端**:Axios +3. **存储隔离**:localStorage key 前缀 `admin_` --- -## 三、目录结构概览 +## 五、目录结构概览 ``` EchoChat/ -├── backend/go-service/ # Go 后端服务 +├── backend/go-service/ │ ├── app/ -│ │ ├── admin/ # 管理端模块(controller/service/dao/router/provider) -│ │ ├── auth/ # 认证模块(controller/service/dao/model/router) -│ │ ├── constants/ # 常量(role_code/user_status) -│ │ ├── dto/ # 数据传输对象(auth_dto + admin_dto) -│ │ └── provider/ # Wire 依赖注入 -│ ├── cmd/server/main.go # 入口 -│ ├── config/ # 配置 -│ ├── pkg/ # 公共包(db/logs/middleware/utils) -│ └── router/router.go # 中央路由聚合 -├── frontend/ # 前台用户端(uni-app) +│ │ ├── admin/ # 管理端(controller/service/provider) +│ │ ├── auth/ # 认证模块 +│ │ ├── contact/ # [Phase 2a] 联系人模块 +│ │ │ ├── controller/ +│ │ │ ├── dao/ +│ │ │ ├── model/ +│ │ │ ├── service/ +│ │ │ ├── router.go +│ │ │ └── provider.go +│ │ ├── ws/ # [Phase 2a] WebSocket 模块 +│ │ │ ├── handler.go +│ │ │ ├── online_service.go +│ │ │ ├── provider.go +│ │ │ └── router.go +│ │ ├── constants/ +│ │ ├── dto/ +│ │ └── provider/ +│ ├── pkg/ +│ │ ├── ws/ # [Phase 2a] WebSocket 核心 +│ │ │ ├── hub.go +│ │ │ ├── client.go +│ │ │ ├── pubsub.go +│ │ │ └── message.go +│ │ ├── db/ logs/ middleware/ utils/ +│ └── router/router.go +├── frontend/ # 前台(uni-app) │ └── src/ -│ ├── api/auth.js -│ ├── components/CustomTabBar.vue -│ ├── pages/{auth,chat,contact,meeting,profile,index}/ -│ ├── store/user.js -│ └── utils/{request,storage}.js -├── admin/ # 后台管理端(Vue 3 + Element Plus) +│ ├── api/{auth,contact,user}.js +│ ├── services/websocket.js # [Phase 2a] +│ ├── store/{user,websocket,contact}.js +│ ├── pages/contact/ # [Phase 2a] 6 个页面 +│ │ ├── index.vue +│ │ ├── request.vue +│ │ ├── detail.vue +│ │ ├── search.vue +│ │ ├── groups.vue +│ │ └── blacklist.vue +│ └── components/CustomTabBar.vue +├── admin/ # 管理端(Vue 3 + Element Plus) │ └── src/ -│ ├── api/auth.js -│ ├── router/index.js -│ ├── store/user.js -│ ├── utils/{request,storage}.js -│ └── views/{layout,login,dashboard,user}/ +│ ├── api/{auth,user,monitor,contact}.js +│ ├── views/ +│ │ ├── monitor/online.vue # [Phase 2a] +│ │ ├── contact/list.vue # [Phase 2a] +│ │ ├── layout/ login/ dashboard/ user/ +│ └── router/index.js ├── deploy/ -│ ├── docker-compose.dev.yml -│ └── docker/postgres/init.sql -├── design-system/echochat/ # ui-ux-pro-max 生成的设计系统 -│ ├── MASTER.md -│ └── pages/{login,admin-login}.md +├── design-system/ └── docs/ - ├── api/ # API 接口文档 - ├── architecture/ # 系统架构文档 - ├── conventions/ # 开发规范文档 - │ └── frontend-backend-integration.md # 前后端集成规范 - ├── plans/ # 实施计划文档 - └── progress/ # 进度文档(本文件) + ├── api/ + │ ├── frontend/{auth,contact,websocket}.md + │ ├── admin/{auth,user,online,contact}.md + │ └── websocket.md + ├── plans/ + ├── progress/CURRENT_STATUS.md + └── conventions/ ``` --- -## 四、开发流程规范 +## 六、开发测试指南 -1. **工作流**:使用 superpowers 流程控制开发节奏 -2. **前端设计**:**必须**使用 ui-ux-pro-max 技能包,禁止手动设计 -3. **代码注释**:所有公开函数、组件、Store 必须有详细注释 -4. **文档同步**:代码变更后必须同步更新相关文档 -5. **Git 分支**:`feature/phase1-foundation-and-auth` -6. **验证方式**:Playwright MCP 进行页面自动化验证 -7. **代码审查**:每个 Task 完成后,使用 `code-reviewer` 子代理进行结构化代码审查,对照实施计划和编码标准检查 -8. **完成验证**:使用 `verification-before-completion` 技能,在声称完成前必须运行验证命令并确认输出结果 -9. **前后端联动规范**:详见 `docs/conventions/frontend-backend-integration.md`,核心要求:前端错误提示必须优先使用后端 message,禁止硬编码覆盖;后端错误处理禁止忽略 error;登录安全统一返回 401 - ---- - -## 五、开发测试指南 - -### 启动命令(开发模式) - -前提:postgres 和 redis 已通过 Docker Compose 运行。 +### 启动命令 ```bash -# 启动 Go 后端(http://localhost:8085) +# 1. 启动 PostgreSQL + Redis +cd deploy && docker compose -f docker-compose.dev.yml up -d postgres redis + +# 2. 启动 Go 后端(http://localhost:8085) cd backend/go-service && go run cmd/server/main.go -# 启动管理端(http://localhost:3100) +# 3. 启动管理端(http://localhost:3100) cd admin && npm run dev -# 启动前台 H5(http://localhost:5173+) +# 4. 启动前台 H5(http://localhost:5173+) cd frontend && npm run dev:h5 ``` -如需全容器启动(包括 Go 服务):`cd deploy && docker compose -f docker-compose.dev.yml up -d` - ### 测试账号 | 账号 | 密码 | 角色 | 用途 | |------|------|------|------| -| `admin_test` | `admin123456` | user + admin | **管理端登录推荐** | +| `super_admin` | `admin123456` | super_admin | 系统预置唯一超管 | +| `admin_test` | `admin123456` | user + admin | 管理端登录推荐 | | `testuser1` | `test123456` | user + admin | 前台登录测试 | | `testuser` | `test123456` | user | 前台登录测试 | -| `testuser3` | `test123456` | user | 前台登录测试 | -| `created_by_admin` | `pass123456` | user | 管理端创建的用户 | -| `super_admin` | `admin123456` | super_admin | **系统预置唯一超管账号** | -> 也可以通过注册接口或管理端"创建用户"功能创建新的测试账号。 +### Phase 2a 可测试功能 -### 可测试功能 - -- **管理端**:登录 → 仪表盘 → 用户列表 → 搜索/筛选 → 用户详情 → 禁用/启用(受角色等级约束) → 批量设置角色(Checkbox 多选 + 等级管控) → 创建用户 -- **前台 H5**:注册 → 登录 → 个人中心 → 修改资料 → 退出登录 -- **API**:`GET http://localhost:8085/health`(健康检查) +- **前台联系人**:好友列表 → 搜索添加 → 好友申请 → 好友详情 → 备注/分组 → 拉黑/删除 +- **前台 WebSocket**:自动连接 → 心跳 → 在线状态实时更新 → 好友申请推送 +- **管理端在线监控**:在线用户数 → 在线用户列表 → 自动刷新 +- **管理端好友管理**:好友关系列表 → 强制删除关系 --- -## 六、已知问题与注意事项 +## 七、已知问题 -1. ~~前端工具模块使用了 CommonJS,已修复为 ESM~~(已解决) -2. ~~前端登录失败时错误提示不正确(硬编码"登录已过期"覆盖后端消息)~~(已修复,详见 `docs/conventions/frontend-backend-integration.md`) -3. ~~后端登录时用户不存在返回 404 暴露用户是否注册~~(已修复为统一返回 401) -4. uni-app 的 `tabBar.custom: true` 配合自定义 TabBar 组件使用 -5. 管理端和前台的 localStorage key 通过前缀隔离(`admin_` vs `echo_`) -6. Go 依赖版本需匹配 Go 1.23.12,不要随意升级 Go 工具链 -7. `.gitignore` 已配置忽略:`.cursor/`、`.vite/`、`node_modules/`、`dist/`、`*.png`(根目录截图)、`logs/` 等 +1. uni-app 的 `tabBar.custom: true` 配合自定义 TabBar 组件使用 +2. Go 依赖版本需匹配 Go 1.23.12 +3. 管理端 Element Plus 全量导入导致打包体积较大(后续可改为按需导入) --- -## 六、Phase 1 完成总结 +## 八、下一阶段规划 -### Phase 1 阶段成果 -- **11 个 Task 全部完成**,端到端验证通过 -- **Go 后端**:15 个 API 端点,JWT 有状态认证,RBAC 角色权限 -- **前台 uni-app**:登录/注册/TabBar/个人中心 -- **管理端 Vue 3**:登录/仪表盘/用户列表/用户详情 -- **基础设施**:Docker Compose 一键启动(PostgreSQL + Redis + Go 服务) -- **代码审查**:code-reviewer 审查通过,已修复 panic 风险、错误处理等问题 - -### 下一阶段:Phase 2 - 即时通讯 -- 待制定实施计划 -- WebSocket 长连接 + 消息系统 -- 联系人/好友管理 +### Phase 2b - 即时通讯消息系统 +- 会话管理(单聊/群聊) +- 消息收发 + 离线消息 - 消息通知 +- 已读回执 diff --git a/frontend/src/pages.json b/frontend/src/pages.json index 58a2caf..2f8d7e5 100644 --- a/frontend/src/pages.json +++ b/frontend/src/pages.json @@ -28,7 +28,38 @@ { "path": "pages/contact/index", "style": { - "navigationBarTitleText": "联系人" + "navigationBarTitleText": "联系人", + "navigationStyle": "custom" + } + }, + { + "path": "pages/contact/request", + "style": { + "navigationBarTitleText": "好友申请" + } + }, + { + "path": "pages/contact/detail", + "style": { + "navigationBarTitleText": "好友详情" + } + }, + { + "path": "pages/contact/search", + "style": { + "navigationBarTitleText": "搜索好友" + } + }, + { + "path": "pages/contact/groups", + "style": { + "navigationBarTitleText": "好友分组" + } + }, + { + "path": "pages/contact/blacklist", + "style": { + "navigationBarTitleText": "黑名单" } }, { diff --git a/frontend/src/pages/contact/blacklist.vue b/frontend/src/pages/contact/blacklist.vue new file mode 100644 index 0000000..1d94d93 --- /dev/null +++ b/frontend/src/pages/contact/blacklist.vue @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + + + + + + {{ getInitial(user.nickname || user.username) }} + + + {{ user.nickname || user.username }} + @{{ user.username }} + + + 取消拉黑 + + + + + + + 黑名单为空 + 被拉黑的用户会显示在这里 + + + + + + + diff --git a/frontend/src/pages/contact/detail.vue b/frontend/src/pages/contact/detail.vue new file mode 100644 index 0000000..39bc466 --- /dev/null +++ b/frontend/src/pages/contact/detail.vue @@ -0,0 +1,405 @@ + + + + + + + + {{ getInitial(friend.remark || friend.nickname || friend.username) }} + + {{ friend.remark || friend.nickname || friend.username }} + @{{ friend.username }} + + + {{ friend.is_online ? '在线' : '离线' }} + + + + + + + 备注名 + + {{ friend.remark || '未设置' }} + › + + + + 所属分组 + + {{ currentGroupName }} + › + + + + + + + + 发消息 + + + + + + 拉黑该用户 + + + 删除好友 + + + + + + + 未找到好友信息 + + + + + + + diff --git a/frontend/src/pages/contact/groups.vue b/frontend/src/pages/contact/groups.vue new file mode 100644 index 0000000..e478ab9 --- /dev/null +++ b/frontend/src/pages/contact/groups.vue @@ -0,0 +1,336 @@ + + + + + + + + 新建分组 + + + + + + + + + + + + + + + + + + ★ + + + 默认分组 + 系统默认 + + + + + + + + + ☰ + + + {{ group.name }} + {{ group.friend_count || 0 }} 位好友 + + + + + ✎ + + + ✕ + + + + + + + 还没有自定义分组,点击上方按钮创建 + + + + + + + + diff --git a/frontend/src/pages/contact/index.vue b/frontend/src/pages/contact/index.vue index f5a7047..ac76775 100644 --- a/frontend/src/pages/contact/index.vue +++ b/frontend/src/pages/contact/index.vue @@ -1,27 +1,172 @@ - - 👥 - 联系人 - 好友管理功能开发中… + + + 联系人 + + + ☰ + + + + + + + + + + + 🔍 + + + + + + + + + + ✉ + + 好友申请 + + + + {{ contactStore.pendingCount > 99 ? '99+' : contactStore.pendingCount }} + + › + + + + + + ⊘ + + 黑名单 + + + › + + + + + + + 好友列表 + {{ filteredFriends.length }} 人 + + + + + + + + + + + + + + + + + + + {{ getInitial(friend.remark || friend.nickname || friend.username) }} + + + + + {{ friend.remark || friend.nickname || friend.username }} + {{ friend.is_online ? '在线' : '离线' }} + + + + + + + 暂无好友 + 点击右上角 "+" 搜索并添加好友 + + - diff --git a/frontend/src/pages/contact/request.vue b/frontend/src/pages/contact/request.vue new file mode 100644 index 0000000..9a6d37c --- /dev/null +++ b/frontend/src/pages/contact/request.vue @@ -0,0 +1,337 @@ + + + + + + + + + + + + + + + + + + + + + + + {{ getInitial(req.nickname || req.username) }} + + + {{ req.nickname || req.username }} + {{ req.message }} + 请求添加你为好友 + {{ formatTime(req.created_at) }} + + + + 接受 + + + 拒绝 + + + + + + + + 暂无好友申请 + 当有人向你发送好友申请时,会在这里显示 + + + + + + + diff --git a/frontend/src/pages/contact/search.vue b/frontend/src/pages/contact/search.vue new file mode 100644 index 0000000..ee6e668 --- /dev/null +++ b/frontend/src/pages/contact/search.vue @@ -0,0 +1,373 @@ + + + + + + + + + + 搜索 + + + + + + + 搜索结果 + + + + 搜索中... + + + + + + {{ getInitial(user.nickname || user.username) }} + + + {{ user.nickname || user.username }} + @{{ user.username }} + + + 已是好友 + + + {{ addingMap[user.user_id] ? '已发送' : '添加' }} + + + + + + 未找到相关用户 + + + + + + + 好友推荐 + + + + 加载中... + + + + + + {{ getInitial(user.nickname || user.username) }} + + + {{ user.nickname || user.username }} + {{ user.common_count }} 位共同好友 + + + {{ addingMap[user.user_id] ? '已发送' : '添加' }} + + + + + + 暂无推荐好友 + + + + + + + +