在线体验
代码地址
当 Vue 3 遇上 NestJS,当 Socket.IO 搭建起实时通信的鹊桥,一场关于聊天的技术盛宴就此展开...
🌟 前言
在这个信息爆炸的时代,我们每天都在和各种聊天应用打交道。但是,你有没有想过,一个现代化的聊天系统是如何诞生的?今天,就让我们揭开这对"前后端双星"——**Discuss(前端)和Server-NestJS(后端)**的神秘面纱,看看它们是如何携手打造一个令人惊叹的实时聊天应用的。
🎭 角色介绍
前端主角:Discuss —— 优雅的 UI 舞者
Discuss 就像是一位优雅的舞者,在浏览器这个舞台上翩翩起舞。她不仅长得漂亮(现代化 UI),而且身手敏捷(高性能),更重要的是,她懂得如何与后端这位音乐家完美配合。
她的核心装备:
- 🎨 Vue 3.5.22 - 现代化框架,让她舞姿更加优雅流畅
- 🎭 TypeScript 5.9.3 - 类型安全的服装,让她的每个动作都精准无误
- ⚡ Vite - 超级引擎,让她瞬间起舞,开发体验丝滑如丝
- 🔗 Socket.IO Client - 神奇的传音符,让她能实时与后端心灵感应
- 💾 LocalForage - 记忆宫殿,让她能记住所有的重要对话
- 🎭 Pinia - 状态管理师,让她的情绪(数据)稳定可控
后台大佬:Server-NestJS —— 稳重的架构大师
Server-NestJS 则像是一位稳重的架构大师,在服务器这片土地上筑起坚固的城堡。他用 TypeORM 搭建数据桥梁,用 JWT 守护安全大门,用 WebSocket 和 SSE 双管齐下,确保每一条消息都能准时送达。
他的核心装备:
- 🏰 NestJS 11.0.1 - 企业级框架,让他的城堡坚不可摧
- 🗃️ TypeORM 0.3.27 - 数据库魔术师,让数据管理变得轻松自如
- 🔐 JWT 11.0.1 - 安全守护者,确保只有合法用户才能进入
- 📡 Socket.IO 4.8.1 - 双向通信桥梁,让消息自由穿梭
- 📢 SSE - 单向广播塔,向所有用户推送重要通知
- 📝 Winston 3.18.3 - 历史记录官,详细记录每一个重要时刻
🏗️ 架构解析:一场完美的双人舞
前端架构:优雅的舞步
┌─────────────────────────────────────────┐
│ 用户界面 (Views) │
│ ┌──────────┐ ┌──────────┐ │
│ │ 登录页 │ │ 聊天页 │ ... │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 状态管理 (Pinia Store) │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ User │ │Friend│ │ Room │ │Socket│ │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 通信层 (API + Socket) │
│ ┌──────────┐ ┌──────────┐ │
│ │ Axios │ │Socket.IO │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 本地存储 (LocalForage) │
│ ┌──────────────────────────────┐ │
│ │ 用户消息历史 | 房间消息历史 │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────────┘
前端的核心亮点:
-
🎯 模块化设计
- 每个功能模块都有独立的 store(user、friend、room、socket...)
- API 自动注册机制,告别手动导入的烦恼
- 动态路由加载,权限控制更灵活
-
⚡ 实时通信魔法
// Socket.IO 连接 - 就像给后端安装了心灵感应器 this.socket = io(API_URL, { path: '/websocket', auth: { token: userStore.token } }) // 监听好友消息 - 实时接收,从不等待 this.socket.on('user', (data) => { // 消息自动推送到对应的好友聊天窗口 }) -
💾 智能存储策略
- 使用 LocalForage 替代 localStorage,容量更大,性能更好
- 防抖机制:消息不会频繁写入,1秒内只写一次
- 断线重连后,消息历史自动恢复,仿佛什么都没发生过
-
🎨 路由守卫的智能门卫
router.beforeEach((to, from, next) => { // 没有登录?请先去登录页! if (!localStorage.getItem('token')) { next({ name: 'login' }) } // 已登录?动态加载菜单和路由 else if (!menuStore.isLoad) { let asyncRoutes = await menuStore.GenerateRoutes() router.addRoute(layout) } })
后端架构:坚固的城堡
┌─────────────────────────────────────────┐
│ 客户层 (Client Layer) │
│ Web Browser | Mobile App | ... │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 应用层 (Application Layer) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Controller│ │ Gateway │ │ Filter │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Guard │ │Intercept│ │ Pipe │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 业务层 (Business Layer) │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ User │ │ Room │ │Friend│ │Apply │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │Member│ │ Auth │ │ SSE │ │ Cron │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 数据层 (Data Layer) │
│ ┌──────────────────────────────┐ │
│ │ TypeORM Repository │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 数据库 (MySQL Database) │
│ user | room | member | friend | apply │
└─────────────────────────────────────────┘
后端的核心亮点:
-
🏛️ 标准的 NestJS 分层架构
- Controller:接收请求的迎宾员
- Service:处理业务的核心引擎
- Guard:检查身份的门卫(JWT 认证)
- Interceptor:包装礼品的包装工(数据转换)
- Filter:处理意外的清洁工(异常处理)
-
🔄 双重实时通信系统
WebSocket - 双向交流的鹊桥
@WebSocketGateway({ path: '/websocket' }) export class SocketGateway { @UseGuards(WsJwtAuthGuard) handleConnection(client: Socket) { // 用户连接成功,发送欢迎消息 client.emit('online', '欢迎回来!') } @SubscribeMessage('user') async handleUserMessage(client: Socket, payload: any) { // 转发私聊消息给目标用户 this.server.to(payload.target).emit('user', payload) } @SubscribeMessage('room') async handleRoomMessage(client: Socket, payload: any) { // 广播群聊消息给房间内所有成员 this.server.to(payload.roomId).emit('room', payload) } }SSE - 单向广播的传声筒
@Sse('sse') sse(@Query('token') token: string): Observable<MessageEvent> { return interval(1000).pipe( map(() => ({ data: { message: '这是一条服务器推送消息' } })) ) } // 向特定用户推送消息 async sendToUser(userId: string, message: string) { const client = this.clients.get(userId) if (client) { client.res.write(`data: ${JSON.stringify({ message })}\n\n`) } } -
🛡️ 灵活的 JWT 认证方案
// 支持三种 Token 传递方式: // 1. Header: Authorization: Bearer <token> // 2. Query: ?token=<token> // 3. WebSocket Auth: { token: <token> } @UseGuards(JwtAuthGuard) @Get('protected') getProtected(@Request() req) { // 只有携带有效 Token 的请求才能通过 return '这是受保护的内容' } -
🧹 自动清理的定时任务
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT) async cleanPublicDirectory() { // 每天凌晨自动清理上传的文件 // 就像一位勤劳的清洁工,每天深夜默默打扫 const items = fs.readdirSync(this.publicDir) for (const item of items) { if (fs.statSync(itemPath).isDirectory()) { fs.rmSync(itemPath, { recursive: true }) } } } -
📝 全局日志记录官
// 记录每一个请求的详细信息 this.logger.log('请求信息:', { url, method, headers, body, query, params, responseTime: `${Date.now() - startTime}ms` })
🎯 核心功能:聊天应用的完整生态
1. 用户系统:身份的守护者
前端:
// 用户登录 - 简单优雅
const login = async (credentials) => {
const data = await serverApi.Login(credentials)
userStore.setToken(data.token)
userStore.setUserInfo(data.user)
// 自动跳转到首页
router.push('/')
}
后端:
// 密码加密存储 - bcrypt 保镖
const hashedPassword = await bcrypt.hash(password, 10)
// JWT 令牌生成 - 身份通行证
const token = this.jwtService.sign({
id: user.id,
username: user.username
}, {
secret: this.configService.get('JWT_SECRET'),
expiresIn: '24h'
})
2. 好友系统:社交的桥梁
前端:
// 搜索添加好友 - 一键连接
const searchUser = async (username) => {
const users = await serverApi.SearchUser(username)
displaySearchResults(users)
}
// 发送好友申请 - 友好的邀请
const sendFriendRequest = async (userId) => {
await serverApi.apply({ type: 'friend', targetId: userId })
}
后端:
// 处理好友申请 - 智能的中间人
async handleApply(id: string, status: 'accept' | 'reject') {
const apply = await this.applyRepository.findOne(id)
apply.status = status
await this.applyRepository.save(apply)
if (status === 'accept') {
// 创建好友关系
await this.friendRepository.save({
creator: apply.userId,
friendId: apply.applyUserId
})
}
}
3. 房间系统:聚会的广场
前端:
// 创建房间 - 搭建自己的小天地
const createRoom = async (roomInfo) => {
await serverApi.CreateRoom(roomInfo)
router.push(`/room/${roomId}`)
}
// 发送群聊消息 - 广播给所有人
const sendRoomMessage = () => {
socketStore.socket.emit('room', {
room: route.params.id,
content: message.value
})
}
后端:
// 房间成员管理 - 严谨的名单登记
async addMember(roomId: string, userId: string) {
const member = this.memberRepository.create({
roomId,
userId
})
await this.memberRepository.save(member)
}
4. 实时通信:心跳的共鸣
前端 - 接收消息:
// 监听好友消息 - 实时接收,从不等待
socketStore.socket.on('user', (data) => {
const messages = socketStore.userMessageMap.get(data.sender) || []
messages.push(data)
socketStore.userMessageMap.set(data.sender, messages)
})
// 监听房间消息 - 群聊动态,一目了然
socketStore.socket.on('room', (data) => {
const messages = socketStore.roomMessageMap.get(data.room) || []
messages.push(data)
socketStore.roomMessageMap.set(data.room, messages)
})
后端 - 转发消息:
// WebSocket 消息转发 - 快速的快递员
@SubscribeMessage('user')
async handleUserMessage(client: Socket, payload: any) {
// 找到目标用户的 Socket 连接
const targetClient = this.clients.get(payload.target)
if (targetClient) {
// 立即转发消息
targetClient.emit('user', {
sender: client.data.user.id,
content: payload.content,
timestamp: new Date()
})
}
}
@SubscribeMessage('room')
async handleRoomMessage(client: Socket, payload: any) {
// 广播消息给房间内所有成员
this.server.to(payload.roomId).emit('room', {
sender: client.data.user.id,
content: payload.content,
timestamp: new Date()
})
}
🌈 项目亮点:让人眼前一开的创新
前端亮点:
-
🤖 自动 API 注册
- 使用
import.meta.glob自动导入所有 API 模块 - 告别手动 import,让代码更简洁
const modules = import.meta.glob('./**/*.ts', { eager: true }) // 递归注册所有 API 模块 - 使用
-
📦 LocalForage 智能存储
- 支持 IndexedDB,存储容量比 localStorage 大很多
- 异步操作,不会阻塞主线程
- 自动恢复消息历史,断线重连无缝衔接
-
🎭 动态路由加载
- 根据用户权限动态加载路由
- 使用
import.meta.glob动态导入组件 - 支持菜单配置化,灵活扩展
-
⚡ Vite 超快构建
- 开发服务器瞬间启动
- HMR(热模块替换)毫秒级响应
- 生产环境代码分割优化
后端亮点:
-
🔄 WebSocket + SSE 双重实时通信
- WebSocket:双向实时通信,适合聊天
- SSE:单向服务器推送,适合通知
- 根据场景选择最合适的通信方式
-
🛡️ 多 Token 传递方式
- REST API 使用 Header 传递
- SSE 使用 Query 传递
- WebSocket 使用 Auth 对象传递
- 灵活适配不同场景
-
🧹 定时任务自动清理
- 每天凌晨自动清理上传的临时文件
- 释放磁盘空间,保持服务器整洁
- 可扩展为更多自动化任务
-
📝 完整的日志系统
- 使用 Winston 记录所有请求和错误
- 日志文件按天分割,自动清理
- 控制台彩色输出,开发体验友好
-
🎯 统一的响应格式
// 成功响应 { code: 200, msg: 'success', data: {...} } // 错误响应 { code: -1, data: null, msg: '错误信息' }
🚀 部署流程:从开发到生产
前端部署:
# 开发环境
npm run dev
# 访问: http://localhost:8000
# 构建生产版本
npm run build
# 部署到 GitHub Pages(自动)
# 提交到 master 分支,GitHub Actions 自动部署
# 访问: https://eug620.github.io/discuss/
后端部署:
# 开发环境
npm run start:dev
# 访问: http://localhost:3000
# 构建生产版本
npm run build
# 使用 PM2 部署
pm2 start ecosystem.config.js --env prod
# PM2 会自动重启、日志管理、负载均衡
🎓 学习价值:为什么这两个项目值得关注?
对于前端开发者:
-
Vue 3 + TypeScript 最佳实践
- Composition API 的优雅使用
- TypeScript 类型定义的规范
- Pinia 状态管理的模块化设计
-
实时通信实战
- Socket.IO 的前端集成
- 消息的接收和处理机制
- 离线消息的恢复策略
-
性能优化技巧
- Vite 构建优化
- 代码分割和懒加载
- 本地存储的性能优化
对于后端开发者:
-
NestJS 企业级应用架构
- 分层架构的实践
- 依赖注入的使用
- 装饰器的优雅运用
-
实时通信实现
- WebSocket 网关的开发
- SSE 服务端推送
- 认证和安全机制
-
生产环境最佳实践
- 全局异常处理
- 日志记录和管理
- 定时任务的实现
🎊 结语
Discuss 和 Server-NestJS 这对前后端双星,用现代化的技术栈,构建了一个功能完整、性能卓越的实时聊天应用。
前端优雅地展示界面,智能地管理状态,实时地接收消息;后端稳重地处理业务,安全地守护数据,快速地传递信息。它们就像是天作之合,在技术的舞台上演绎着完美的双人舞。
如果你正在寻找一个学习前后端分离、实时通信、企业级架构的实战项目,那么这对双星绝对值得你深入了解。
项目信息:
- 前端仓库:eug620.github.io/discuss/
- 前端技术栈:Vue 3 + TypeScript + Vite + Socket.IO + Pinia + LocalForage
- 后端技术栈:NestJS + TypeORM + MySQL + WebSocket + SSE + JWT
- 实时通信:Socket.IO(双向)+ SSE(单向)
- 部署方式:GitHub Pages + PM2
作者寄语:
代码不仅是为了运行,更是为了传递思想。希望这两个项目能为你打开一扇新的窗户,让你看到前后端开发的无限可能。