14 KiB
Chat Demo 前端设计书
1. 文档说明
本文档依据当前仓库中的前端代码,说明系统目标、技术选型、模块边界、代码结构、主要数据流和交互设计。项目是一个 Vue 单页聊天演示前端;当前仓库不包含服务端实现,因此服务端接口及消息协议以客户端实际调用和解析的内容为准。
2. 项目概述
系统提供账号注册、登录和一对一即时聊天页面。用户登录后可以查看最近会话、搜索最近联系人、浏览联系人在线状态、查看历史消息、加载更早消息,并通过 WebSocket 收发消息。聊天页面采用三栏布局:功能导航、会话/联系人列表、当前聊天内容。
前端职责包括:
- 通过 REST API 完成注册、登录以及用户、联系人、会话和历史消息数据读取。
- 保存登录令牌,并在后续 HTTP 请求中携带令牌。
- 使用 Vue Router 限制聊天路由的访问。
- 建立 WebSocket 连接,发送聊天消息、处理服务端推送并回传消息确认 ACK。
- 管理页面内的会话、消息、未读数、在线用户和输入状态。
3. 技术选型
| 分类 | 技术/模块 | 用途 |
|---|---|---|
| UI 框架 | Vue 2.6 | 页面组件与响应式视图,采用 Options API |
| 路由 | Vue Router 3 | /login、/register、/chat 页面切换及导航守卫 |
| 状态管理 | Vuex 3 | 保存 token 和当前用户名 |
| UI 组件 | Element UI 2 | 登录/注册表单、输入框、按钮、提示消息 |
| HTTP | Axios 1 | REST 请求、认证请求头、统一响应与 401 处理 |
| 实时通信 | 浏览器原生 WebSocket | 即时消息、在线用户列表和 ACK |
| 构建工具 | Vue CLI 5、Babel 7 | 本地开发、构建、ES 转译和 Element UI 组件按需样式处理 |
项目未使用 TypeScript、独立 API 状态库或 WebSocket 第三方库;聊天页面自身维护主要业务状态。
4. 总体架构
flowchart LR
Browser[浏览器] --> Entry[src/main.js]
Entry --> App[src/App.vue]
Entry --> Router[src/router.js]
Entry --> Store[src/store/index.js]
Router --> Pages[登录 / 注册 / 聊天页面]
Pages --> API[src/utils/api.js]
API --> Request[src/utils/request.js]
Request -->|HTTP /api| Backend[后端 localhost:3000]
Pages --> WS[src/utils/websocket.js]
WS -->|WebSocket ws://localhost:3000| Backend
Request --> Store
Router --> Store
页面层负责交互、视图状态和流程编排;api.js 将业务接口映射为 HTTP 方法;request.js 统一管理 Axios 配置和拦截器;websocket.js 封装浏览器 WebSocket 的创建、发送、关闭和回调。Vuex 在入口、路由和请求层之间共享认证状态。
5. 代码结构与模块职责
.
├── public/
│ └── index.html # SPA HTML 宿主
├── src/
│ ├── main.js # Vue、路由、store、Element UI 与全局 CSS 初始化
│ ├── App.vue # 根组件,通过 router-view 渲染当前页面
│ ├── router.js # 路由表与 token 路由守卫
│ ├── store/
│ │ └── index.js # token、username 与 mutations
│ ├── pages/
│ │ ├── login/
│ │ │ ├── LoginPage.vue # 登录表单和登录后跳转
│ │ │ └── RegisterPage.vue # 注册表单和密码确认
│ │ └── index/
│ │ └── ChatPage.vue # 聊天布局、会话状态及实时消息处理
│ ├── utils/
│ │ ├── api.js # 登录、注册、用户和聊天 REST API
│ │ ├── request.js # Axios 实例、认证头和响应拦截器
│ │ └── websocket.js # WebSocket 生命周期和 JSON 消息封装
│ └── assets/css/
│ └── style_public.css # 全局重置与基础样式
├── babel.config.js # Vue CLI Babel preset 与 Element UI 按需样式
├── jsconfig.json # JavaScript 配置及 @ -> src 别名
├── vue.config.js # Vue CLI 开发服务器代理
└── package.json # 依赖和开发/构建/lint 命令
5.1 应用启动与路由
src/main.js 加载 Element UI 及其主题样式、全局公共 CSS,并将 router 和 store 注入 Vue 根实例。App.vue 只提供 router-view 容器。路由采用 history 模式:根路径跳转 /login;/login、/register 和 /chat 分别对应登录、注册和聊天页面。/chat 标记 requiresAuth,导航守卫通过 store.state.token 判断是否允许进入。
5.2 登录、注册与认证状态
登录页收集用户名和密码,调用 login();成功后从响应中读取 token,提交 SET_TOKEN、SET_USERNAME 并跳转聊天页。注册页在前端检查两次密码一致后调用 register(),成功后返回登录页。
Vuex 当前包含:
| 状态 | 初始值/持久化 | 用途 |
|---|---|---|
token |
初始化时从 localStorage 读取;SET_TOKEN 同步写入 localStorage |
路由鉴权及请求认证 |
username |
空字符串,登录时写入 Vuex,不持久化 | 当前会话中的用户标识 |
HTTP 请求拦截器从 localStorage 读取 token 并设置 Authorization 请求头。响应状态为 401 时清理 token 和用户名并跳转登录页。退出登录时关闭 WebSocket、清空 Vuex token/username 并跳转登录。
5.3 REST API 封装
src/utils/api.js 提供以下调用:
| 函数 | 方法与路径 | 当前用途 |
|---|---|---|
login(data) |
POST /login |
登录并取得 token |
register(data) |
POST /register |
创建账号 |
getUserInfo() |
GET /userInfo |
读取当前用户资料 |
getContactInfo() |
GET /contactsInfo |
读取联系人列表 |
getRecentMessages() |
GET /recentChats |
读取最近会话及预览信息 |
getContactMessages(data) |
POST /contactMessagesInfo |
分页读取指定联系人的消息 |
sendMessage(data) |
POST /sendMessage |
HTTP 发送接口;当前聊天页未使用,实际发送走 WebSocket |
src/utils/request.js 创建 Axios 实例,默认 baseURL 为 /api,超时为 10 秒。开发服务器将 /api 转发到 http://localhost:3000。响应拦截器返回 response.data;页面代码随后通过 response.data 读取业务字段,因此当前前后端约定应在 HTTP 响应外层提供 data 字段(例如 Axios 响应体为 { "data": { ... } })。由于服务端代码不在本仓库,实际响应格式仍需与后端核对。
5.4 聊天页面与状态
ChatPage.vue 以一个组件承载聊天主界面及其业务流程,关键状态如下:
| 状态 | 含义 |
|---|---|
currentUser |
当前用户资料 |
contactList |
联系人列表 |
recentChats |
最近会话列表,包含最近消息预览与未读数 |
onlineList |
服务端推送的在线用户名列表 |
currentContact |
当前正在查看的联系人 |
contactMessages |
当前会话中展示的消息数组 |
searchText、messageText |
联系人搜索关键字和编辑中的消息 |
activeMenu |
messages、contacts、settings 侧栏菜单状态 |
firstLoad、loadingMore、loadEnd |
历史消息初次加载、分页加载和加载结束标记 |
组件创建时并行触发当前用户资料、最近会话和 WebSocket 初始化。最近会话加载完成后,如果列表非空,会自动选择第一条会话。切换至联系人菜单时另行请求联系人列表。选择联系人时清空当前消息并请求首批 10 条,查看后将该联系人未读数清零。
历史消息使用 limit=10,offset 取当前消息条数。加载更早消息时将返回数据倒序后置于现有列表之前,并依据加载前后的滚动区域高度调整 scrollTop,以尽量保持用户正在阅读的位置。发送或收到当前会话消息后,视图滚动到底部。
5.5 WebSocket 实时通信
src/utils/websocket.js 持有模块级 WebSocket 实例,连接地址固定为 ws://localhost:3000。聊天页面从 Vuex 读取 token,去除可能存在的 Bearer 前缀并 URL 编码,通过查询参数 token 传递。连接建立后以 JSON 收发消息,并将解析后的数据交给页面回调。
客户端当前使用/处理的消息类型:
| 类型 | 方向 | 处理方式 |
|---|---|---|
sendMessage |
客户端 -> 服务端 | 包含 messageId、senderName、receiverName、content、createdAt;成功写入本地当前消息列表并更新最近会话 |
newMessage |
服务端 -> 客户端 | 从 message 读取消息;当前联系人消息加入聊天列表,其他联系人的消息更新最近会话和未读数 |
ack |
客户端 -> 服务端 | 收到 newMessage 后按 messageId 确认 |
sendError |
服务端 -> 客户端 | 提示发送失败 |
userOnline |
服务端 -> 客户端 | 使用 userList 更新在线列表 |
上述字段是前端代码所依赖的协议,不代表后端已实现或经过端到端验证。connectWebSocket 提供 open、close、error 回调;聊天页实现了 open 和 error 处理,未提供 close 回调。发送时要求连接处于 OPEN 状态;否则封装仅写控制台,不抛出异常,因此页面的 try/catch 不能据此判断 WebSocket 是否真正发送成功。
5.6 UI 与样式
聊天页面采用固定宽度左侧导航栏(78px)、联系人面板(300px)和弹性聊天面板。聊天面板包含会话标题、消息滚动区、输入区。消息区区分本人和对方消息气泡,并展示时间;未读数和联系人在线状态通过独立样式表达。页面 CSS 以 ChatPage.vue 内的 scoped 样式为主,公共 CSS 提供基础重置。
登录和注册页使用 Element UI 表单控件,页面样式局部定义,窄屏时缩小表单内边距。聊天容器设置 min-width: 980px,当前小屏行为是保留最小宽度并可能产生横向溢出,不属于完整的移动端适配。
6. 主要业务流程
6.1 登录进入聊天
- 用户提交用户名和密码,页面调用
POST /api/login。 - 成功响应中的 token 写入 Vuex 和 localStorage,用户名写入 Vuex。
- 路由跳转
/chat;导航守卫检测到 token 后放行。 - 聊天页读取用户资料、最近会话,并携带 token 建立 WebSocket。
- 最近会话返回后默认打开第一位联系人并拉取最近 10 条消息。
6.2 发送与接收消息
- 用户点击发送或按 Enter,页面去除首尾空白并检查消息及联系人。
- 页面组装
sendMessageJSON,通过 WebSocket 发送,并乐观地将消息加入当前视图。 - 服务端推送
newMessage时,页面按发送者是否为当前联系人分流:更新当前消息列表,或更新会话预览和未读数。 - 页面向服务端发送
ack,确认消息已接收。
6.3 认证失效或退出
HTTP 401 会清理认证状态并导航至登录页。用户主动退出会先关闭 WebSocket,再清理 Vuex 状态并返回登录页。当前认证守卫主要检查 token 是否存在,并不验证 token 有效性;有效性最终由服务端响应决定。
7. 构建与运行配置
npm install:安装依赖。npm run serve:启动 Vue CLI 开发服务器。npm run build:生成生产构建。npm run lint:执行 ESLint 检查。- 开发环境
/api代理目标为http://localhost:3000。 - WebSocket URL 在
src/utils/websocket.js中固定为ws://localhost:3000;部署到其他环境前需要配置化,并在 HTTPS 部署时切换到安全 WebSocket (wss://)。
8. 当前边界与改进建议
以下内容是根据当前代码观察到的限制,供后续迭代使用,并非已实现功能:
- 运行配置集中化:HTTP base URL 与 WebSocket 地址目前分散/硬编码,应按开发、测试、生产环境配置,减少部署时遗漏。(已解决)
- 认证状态持久性:token 会持久化,但 username 不持久化;刷新后需由用户信息接口恢复展示状态。还应评估 localStorage 对 token 的安全取舍及 token 过期刷新方案。(username已删除,token问题待解决)
- WebSocket 健壮性:当前没有断线重连、心跳、待发送队列或连接状态 UI;发送前应明确连接不可用的反馈,并评估 ACK 超时及重复消息处理。
- 会话切换与分页状态:
loadEnd等分页标志需要在联系人切换时明确重置;异步消息请求也应避免旧联系人响应覆盖新联系人视图。(已解决) - 实时会话一致性:收到当前联系人消息时会更新消息区,但当前实现没有同步刷新最近会话预览;接收新消息的未读判断及空会话创建逻辑也值得通过业务用例确认。(已解决)
- 响应契约与错误处理:页面依赖响应中存在
data字段。建议与服务端定义统一响应 DTO,并区分网络错误、认证错误、业务错误;当前错误主要由页面提示。(已解决) - UI 菜单与能力范围:侧栏存在设置入口,但当前点击后主要改变菜单状态,未形成独立设置页;工具栏的表情和附件按钮也尚未绑定完整功能。
- 移动端适配与无障碍:聊天页最小宽度为 980px;联系人条目以鼠标点击交互为主。若支持移动设备或键盘操作,需补充响应式布局、语义按钮和焦点管理。
- 自动化验证:当前仓库配置了 lint/build 脚本,但未看到专门的单元测试或端到端测试。可优先为认证守卫、消息收发分流、未读计数和历史分页补充测试。