Nest.js实现一个简单的聊天室

本文将介绍如何使用 Nest.jsUni-app 实现一个简单的实时聊天应用。后端使用 @nestjs/websocketssocket.io,前端使用 uni-app 并集成 socket.io-client。这个项目允许多个用户同时加入聊天并实时交换消息。

效果图:

一、准备工作
  1. 安装 Node.js 和 npm

  2. 全局安装 Nest.js CLI

    npm install -g @nestjs/cli

二、后端:Nest.js 实现 WebSocket 服务
1. 创建 Nest.js 项目

首先使用 CLI 创建一个新的 Nest.js 项目:

复制代码
nest new nest-chat

选择使用 npmyarn 进行依赖管理。

2. 安装 WebSocket 和 Socket.io 相关依赖

在项目中,安装 WebSocket 和 socket.io 相关的依赖包:

复制代码
npm install @nestjs/websockets socket.io
3. 创建 WebSocket 网关

src 目录下创建一个 chat 模块,并在该模块中创建 WebSocket 网关:

复制代码
nest g module chat
nest g gateway chat/chat

这将生成一个 WebSocket 网关类。修改 chat.gateway.ts 文件,添加基本的 WebSocket 聊天功能:

复制代码
import {
    SubscribeMessage,
    WebSocketGateway,
    OnGatewayInit,
    WebSocketServer,
    OnGatewayConnection,
    OnGatewayDisconnect,
  } from '@nestjs/websockets';
  import { Logger } from '@nestjs/common';
  import { Socket, Server } from 'socket.io';
  
  @WebSocketGateway({
    namespace: 'chat',
    cors: {
      origin: '*',
    },
  })
  export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
    @WebSocketServer() server: Server;
    private logger: Logger = new Logger('ChatGateway');
    users = 0;
  
    @SubscribeMessage('msgToServer')
    handleMessage(client: Socket, payload: string): void {
      // 获取当前时间并格式化为"YYYY-MM-DD HH:mm:ss"
      const currentTime = new Date().toLocaleString('zh-CN', {
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit',
        hour12: false, // 使用24小时制
      }).replace(/\//g, '-').replace(/,/, ' '); // 替换分隔符以符合所需格式
  
      // 创建一个新的消息对象,包含时间和消息内容
      const messageWithTime = {
        time: currentTime, // 当前时间
        data: payload,
      };
      
      this.server.emit('msgToClient', messageWithTime); // 发送包含时间的消息对象
    }
  
    afterInit(server: Server) {
      this.logger.log('Init');
    }
  
    handleDisconnect(client: Socket) {
      this.logger.log(`Client disconnected: ${client.id}`);
      this.users--;
      // 通知连接的客户端当前用户数量
      this.server.emit('users', this.users);
    }
  
    handleConnection(client: Socket, ...args: any[]) {
      this.logger.log(`Client connected: ${client.id}`);
      this.users++;
      // 通知连接的客户端当前用户数量
      this.server.emit('users', this.users);
    }
  }
4. 将 WebSocket 网关注册到模块中

chat.module.ts 中,将 ChatGateway 加入到模块的 providers 中:

复制代码
import { Module } from '@nestjs/common';
import { ChatGateway } from './chat.gateway';
//cli: nest g module chat
@Module({
  providers: [ChatGateway],
})
export class ChatModule {}
5. 在主模块中导入 ChatModule

打开 app.module.ts,并导入 ChatModule

复制代码
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ChatModule } from './chat/chat.module';
@Module({
  imports: [ChatModule ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

至此,后端部分已经完成,接下来启动 Nest.js 服务:

复制代码
npm run start

三、前端:Uni-App 实现客户端

1. 安装 socket.io-client

在 Uni-App 项目中,使用 npm 安装 socket.io-client

复制代码
npm install socket.io-client

2 在pages下新建两个文件页面

pages/index/index

复制代码
<template>
  <view class="container" :style="gradientStyle">
    <view class="header">
      <text class="title">加入聊天室</text>
    </view>
    
    <view class="input-container">
      <input class="nickname-input" type="text" v-model="nickname" @confirm="joinChatroom" placeholder="请输入真实昵称" />
      <input class="code-input" type="text" v-model="code" @confirm="joinChatroom" placeholder="请输入验证码" />
    </view>
    
    <view class="button-container">
      <button class="join-button" @click="joinChatroom">点击加入</button>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      nickname: '', // 用户输入的昵称
      code: '', // 验证码输入
      colorIndex: 0, // 用于记录当前颜色索引
      gradientInterval: null // 定时器引用
    };
  },
  computed: {
    gradientStyle() {
      return {
        background: this.getDynamicGradient()
      };
    }
  },
  methods: {
    getDynamicGradient() {
      const colors = [
        '#ff7e5f',
        '#feb47b',
        '#ff6a6a',
        '#ffba6a',
        '#fffb6a',
        '#6aff6a',
        '#6afffb',
        '#6a6aff',
        '#ba6aff',
        '#ff6aff'
      ];
      // 计算背景颜色
      return `linear-gradient(135deg, ${colors[this.colorIndex]}, ${colors[(this.colorIndex + 1) % colors.length]})`;
    },
    joinChatroom() {
      if (this.nickname.trim() === '') {
        uni.showToast({
          title: '你必须给老子输入你的昵称',
          icon: 'error'
        });
        return;
      }
      if (this.code.trim() !== '1210') {
        uni.showToast({
          title: '验证码错误!请输入',
          icon: 'error'
        });
        return;
      }
      // 将用户的昵称保存到全局或跳转到聊天页面
      uni.navigateTo({
        url: `/pages/list/list?nickname=${this.nickname}&password=${this.code}`
      });
    }
  },
  created() {
    // 创建定时器以更新背景颜色
    this.gradientInterval = setInterval(() => {
      this.colorIndex = (this.colorIndex + 1) % 10; // 每秒改变颜色索引
      this.$forceUpdate(); // 强制更新以应用新的背景色
    }, 1000);
  },
  beforeDestroy() {
    clearInterval(this.gradientInterval); // 清除定时器
  }
}
</script>

<style scoped>
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
  transition: background 1s ease; /* 背景渐变的过渡效果 */
}

.header {
  margin-bottom: 20px;
}

.title {
  font-size: 36rpx;
  font-weight: bold;
  color: #fff; /* 修改标题颜色为白色 */
}

.input-container {
  margin-bottom: 20px;
  width: 80%;
}

.nickname-input, .code-input {
  width: 100%;
  height: 80rpx;
  padding: 0 20rpx;
  border: 1px solid #ccc;
  border-radius: 10rpx;
  font-size: 32rpx;
  background-color: #fff;
  margin-bottom: 10px; /* 为验证码输入框增加底部间距 */
}

.button-container {
  width: 80%;
}

.join-button {
  width: 100%;
  height: 80rpx;
  background-color: #007aff;
  color: #fff;
  font-size: 32rpx;
  border: none;
  border-radius: 10rpx;
  text-align: center;
  line-height: 80rpx;
}

.join-button:active {
  background-color: #005bb5;
}
</style>

pages/list/list:

复制代码
<template>
  <view class="container" :style="gradientStyle">
    <view class="header">
      <text class="user-count">当前用户数量: {{ userCount }}</text>
    </view>

    <view class="message-container">
      <view v-for="(message, index) in messages" :key="index"
        :class="{'my-message': message.data.name === name, 'other-message': message.data.name !== name}">
        <text>{{ message.data.name }}: {{ message.data.text }}</text>
        <text style="margin-left: 100px;font-weight: normal;font-size: 12px;">{{message.time}}</text>
      </view>
    </view>

    <view class="input-container">
      <input v-model="text" placeholder="输入您的消息" class="input" @confirm="sendMessage"/>
      <button @click="sendMessage" class="send-button">发送</button>
    </view>
  </view>
</template>

<script>
import io from 'socket.io-client';

export default {
  data() {
    return {
      name: '',
      text: '',
	  code:'',
      userCount: 0,
      messages: [],
      socket: null,
      colorIndex: 0, // 用于记录当前颜色索引
      gradientInterval: null // 定时器引用
    };
  },
  onLoad(e) {
    this.name = e.nickname; // 从传递的参数中获取昵称
	this.code=e.password
  },
  onShow() {
  	if (this.code !== '1210') {
  	      // 如果不符合条件,返回上一页
  	     uni.navigateTo({
  	     	url: '/pages/index/index'
  	     })
  	    }
  },
  computed: {
    gradientStyle() {
      return {
        background: this.getDynamicGradient()
      };
    }
  },
  methods: {
    getDynamicGradient() {
      const colors = [
        '#ff7e5f',
        '#feb47b',
        '#ff6a6a',
        '#ffba6a',
        '#fffb6a',
        '#6aff6a',
        '#6afffb',
        '#6a6aff',
        '#ba6aff',
        '#ff6aff'
      ];
      // 计算背景颜色
      return `linear-gradient(135deg, ${colors[this.colorIndex]}, ${colors[(this.colorIndex + 1) % colors.length]})`;
    },
    sendMessage() {
      if (this.validateInput()) {
        const message = {
          name: this.name,
          text: this.text,
        };
        this.socket.emit('msgToServer', message);
        this.text = '';
      }
    },
    receivedUsers(message) {
      this.userCount = message;
    },
    receivedMessage(message) {
      this.messages.push(message);
    },
    validateInput() {
      return this.name.length > 0 && this.text.length > 0;
    },
    disconnectSocket() {
      if (this.socket) {
        this.socket.disconnect(); // 断开 WebSocket 连接
        this.socket = null; // 清空 socket 对象
      }
    },
  },
  created() {
    this.socket = io('http://192.168.31.76:3000/chat');
    this.socket.on('msgToClient', (message) => {
      this.receivedMessage(message);
    });
    this.socket.on('users', (message) => {
      this.receivedUsers(message);
    });

    // 创建定时器
    this.gradientInterval = setInterval(() => {
      this.colorIndex = (this.colorIndex + 1) % 10; // 每秒改变颜色索引
      this.$forceUpdate(); // 强制更新以应用新的背景色
    }, 1000);
  },
  beforeDestroy() {
    this.disconnectSocket(); // 在组件销毁前断开 WebSocket 连接
    clearInterval(this.gradientInterval); // 清除定时器
  },
};
</script>

<style scoped>
.container {
  display: flex;
  flex-direction: column;
  height: 100vh;
}

.header {
  display: flex;
  justify-content: flex-end;
  padding: 10px;
}

.user-count {
  margin-right: 20px;
  color: #fff;
  font-weight: 700;
}

.message-container {
  flex: 1;
  overflow-y: auto;
  padding: 10px;
}

.my-message {
  text-align: right;
  background-color: #f0f0f0;
  margin: 10px 0;
  padding: 10px;
  border-radius: 5px;
  width: fit-content;
  max-width: 80%;
  align-self: flex-end;
  margin-left: auto;
  font-weight: 700;
}

.other-message {
  text-align: left;
  background-color: orange;
  margin: 5px 0;
  padding: 10px;
  border-radius: 5px;
  width: fit-content;
  max-width: 80%;
  align-self: flex-start;
  margin-right: auto;
  color: #fff;
  font-weight: 700;
}

.input-container {
  display: flex;
  position: fixed;
  bottom: 0;
  width: 100%;
  padding: 10px;
  background-color: rgba(255, 255, 255, 0.9); /* 半透明背景 */
}

.input {
  flex: .98;
  margin-right: 5px;
  background-color: #f0f0f0;
  padding: 10px;
  border-radius: 5px;
}

.send-button {
  width: 100px;
  background-color: #007aff;
  color: white;
  border: none;
  border-radius: 5px;
  height: 45px;
}
</style>

3 然后运行uni项目

下面给大家提供代码地址,可以与你的同事们私密聊天

github:GitHub - dashen-lvweijie/chatRoom: nest.js的简单聊天室功能

相关推荐
㳺三才人子4 小时前
初探 Flask
后端·python·flask·html
星栈独行4 小时前
我在 Rust 全栈项目里用 JWT 做无状态认证
开发语言·后端·rust·前端框架·开源·github·web
Java爱好狂.4 小时前
Java程序员体系化学习路线(2026最新版)
java·后端·java面试·java架构师·java程序员·java八股文·java学习路线
陈随易4 小时前
Redis 8.8发布,一定要更新
前端·后端·程序员
装不满的克莱因瓶5 小时前
SpringBoot 如何将 lib 目录中jar包打包进最终的jar包里面
spring boot·后端·maven·jar·mvn
晓说前端5 小时前
第一篇:为什么学TypeScript?—— 优势、场景与环境搭建
javascript·ubuntu·typescript
ltl5 小时前
Transformer 原论文实验结果:为什么 28.4 BLEU 足以改写路线图
后端
excel6 小时前
为什么我推荐使用 Termius:现代 SSH 工具的完整体验
前端·后端
ZC跨境爬虫6 小时前
模块化烹饪小程序开发日记 Day7:(菜谱详情接口开发与JSON数据读取全流程)
前端·javascript·css·ui·微信小程序·json
এ慕ོ冬℘゜6 小时前
JS 前端基础面试题
开发语言·前端·javascript