vue3+nestjs搭建一个聊天室

34 阅读8分钟

在线体验

体验地址

代码地址

前端代码

后端代码

当 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)          │
│   ┌──────────────────────────────┐     │
│   │  用户消息历史 | 房间消息历史   │     │
│   └──────────────────────────────┘     │
└─────────────────────────────────────────┘

前端的核心亮点:

  1. 🎯 模块化设计

    • 每个功能模块都有独立的 store(user、friend、room、socket...)
    • API 自动注册机制,告别手动导入的烦恼
    • 动态路由加载,权限控制更灵活
  2. ⚡ 实时通信魔法

    // Socket.IO 连接 - 就像给后端安装了心灵感应器
    this.socket = io(API_URL, {
      path: '/websocket',
      auth: { token: userStore.token }
    })
    
    // 监听好友消息 - 实时接收,从不等待
    this.socket.on('user', (data) => {
      // 消息自动推送到对应的好友聊天窗口
    })
    
  3. 💾 智能存储策略

    • 使用 LocalForage 替代 localStorage,容量更大,性能更好
    • 防抖机制:消息不会频繁写入,1秒内只写一次
    • 断线重连后,消息历史自动恢复,仿佛什么都没发生过
  4. 🎨 路由守卫的智能门卫

    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  │
└─────────────────────────────────────────┘

后端的核心亮点:

  1. 🏛️ 标准的 NestJS 分层架构

    • Controller:接收请求的迎宾员
    • Service:处理业务的核心引擎
    • Guard:检查身份的门卫(JWT 认证)
    • Interceptor:包装礼品的包装工(数据转换)
    • Filter:处理意外的清洁工(异常处理)
  2. 🔄 双重实时通信系统

    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`)
      }
    }
    
  3. 🛡️ 灵活的 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 '这是受保护的内容'
    }
    
  4. 🧹 自动清理的定时任务

    @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 })
        }
      }
    }
    
  5. 📝 全局日志记录官

    // 记录每一个请求的详细信息
    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()
  })
}

🌈 项目亮点:让人眼前一开的创新

前端亮点:

  1. 🤖 自动 API 注册

    • 使用 import.meta.glob 自动导入所有 API 模块
    • 告别手动 import,让代码更简洁
    const modules = import.meta.glob('./**/*.ts', { eager: true })
    // 递归注册所有 API 模块
    
  2. 📦 LocalForage 智能存储

    • 支持 IndexedDB,存储容量比 localStorage 大很多
    • 异步操作,不会阻塞主线程
    • 自动恢复消息历史,断线重连无缝衔接
  3. 🎭 动态路由加载

    • 根据用户权限动态加载路由
    • 使用 import.meta.glob 动态导入组件
    • 支持菜单配置化,灵活扩展
  4. ⚡ Vite 超快构建

    • 开发服务器瞬间启动
    • HMR(热模块替换)毫秒级响应
    • 生产环境代码分割优化

后端亮点:

  1. 🔄 WebSocket + SSE 双重实时通信

    • WebSocket:双向实时通信,适合聊天
    • SSE:单向服务器推送,适合通知
    • 根据场景选择最合适的通信方式
  2. 🛡️ 多 Token 传递方式

    • REST API 使用 Header 传递
    • SSE 使用 Query 传递
    • WebSocket 使用 Auth 对象传递
    • 灵活适配不同场景
  3. 🧹 定时任务自动清理

    • 每天凌晨自动清理上传的临时文件
    • 释放磁盘空间,保持服务器整洁
    • 可扩展为更多自动化任务
  4. 📝 完整的日志系统

    • 使用 Winston 记录所有请求和错误
    • 日志文件按天分割,自动清理
    • 控制台彩色输出,开发体验友好
  5. 🎯 统一的响应格式

    // 成功响应
    { 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 会自动重启、日志管理、负载均衡

🎓 学习价值:为什么这两个项目值得关注?

对于前端开发者:

  1. Vue 3 + TypeScript 最佳实践

    • Composition API 的优雅使用
    • TypeScript 类型定义的规范
    • Pinia 状态管理的模块化设计
  2. 实时通信实战

    • Socket.IO 的前端集成
    • 消息的接收和处理机制
    • 离线消息的恢复策略
  3. 性能优化技巧

    • Vite 构建优化
    • 代码分割和懒加载
    • 本地存储的性能优化

对于后端开发者:

  1. NestJS 企业级应用架构

    • 分层架构的实践
    • 依赖注入的使用
    • 装饰器的优雅运用
  2. 实时通信实现

    • WebSocket 网关的开发
    • SSE 服务端推送
    • 认证和安全机制
  3. 生产环境最佳实践

    • 全局异常处理
    • 日志记录和管理
    • 定时任务的实现

🎊 结语

DiscussServer-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

作者寄语:

代码不仅是为了运行,更是为了传递思想。希望这两个项目能为你打开一扇新的窗户,让你看到前后端开发的无限可能。