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的简单聊天室功能

相关推荐
Takumilove23 分钟前
MQTT入门:在Spring Boot中建立连接及测试
java·spring boot·后端
小码快撩24 分钟前
vue应用移动端访问缓慢问题
前端·javascript·vue.js
yayaya15233 分钟前
javaScriptBOM
开发语言·javascript·ecmascript
Riesenzahn33 分钟前
使用vue如何监听元素尺寸的变化?
前端·javascript
阿征学IT38 分钟前
圣诞快乐(h5 css js(圣诞树))
前端·javascript·css
斜杠poven1 小时前
为什么加try catch 不会 block 进程?
前端·javascript·node.js
凡人的AI工具箱1 小时前
每天40分玩转Django:Django管理界面
开发语言·数据库·后端·python·django
cloud___fly1 小时前
Spring AOP入门
java·后端·spring
小奏技术1 小时前
我用github新开源的3D图生成工具生成了自己github历史贡献3D图
后端·开源
每天写点bug1 小时前
【go每日一题】:并发任务调度器
开发语言·后端·golang