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

news2024/9/29 1:31:19

本文将介绍如何使用 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的简单聊天室功能

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2175412.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构与算法——Java实现 24.中缀表达式转后缀

目录 中缀表达式转后缀表达式 引言 思路 代码 正因为我有能力跨越&#xff0c;考验才会降临 —— 24.9.28 中缀表达式转后缀表达式 引言 Java中的编译器会将我们编写代码中的中缀表达式转化为后缀表达式&#xff0c;然后编译好输出程序 思路 遍历中缀表达式&#xff0c;如果遇…

电脑学习通看不到课程解决办法

电脑学习通看不到课程解决办法 查看学习通时发现没有课程 解决方法1: 更改单位 具体见:超星学习通关于PC版无法查看课程问题解决 解决方法二:添加应用 添加应用 点击账号管理 点击应用管理 添加应用、添加首页这个应用 添加完成后查看首页就能看到课程了 然后就OK啦、就可…

[JavaEE] HTTP/HTTPS

目录 一、HTTP 1.1 HTTP是什么 1.2 HTTP发展史 1.3 HTTP工作过程 1.3.1 抓包工具的原理 1.4 HTTP请求格式 1.4.1认识URL 1.5 HTTP响应格式 1.6 认识HTTP"方法"(method) 1.6.1 GET方法 1.6.2 POST方法 1.6.3 其他方法 1.7 GET 与 POST 的区别 1.8 认识…

Centos安装docker(linux安装docker)——超详细小白可操作手把手教程,包好用!!!

&#x1f9f8;本篇博客重在讲解Centos安装docker&#xff0c;经博主多次在不同服务器上测试&#xff0c;极其的稳定&#xff0c;尤其是阿里的服务器&#xff0c;一路复制命令畅通无阻 &#x1f4dc;后续会退出ububtu安装docker教程&#xff0c;敬请期待 &#x1f4dc;作者首页&…

某客户Oracle RAC无法启动故障快速解决

某日&#xff0c;9:50左右接到好友协助需求&#xff0c;某个客户Oracle RAC无法启动&#xff0c;并发过来一个报错截图&#xff0c;如下&#xff1a; 和客户维护人员对接后&#xff0c;远程登录服务端进行故障分析。 查看hosts信息&#xff0c;首先进行心跳测试&#xff0c;测…

Java爬虫:获取SKU详细信息的艺术

在电子商务的世界里&#xff0c;SKU&#xff08;Stock Keeping Unit&#xff0c;库存单位&#xff09;是每个商品的唯一标识符&#xff0c;它包含了商品的详细信息&#xff0c;如尺寸、颜色、价格等。对于商家和开发者来说&#xff0c;获取商品的SKU详细信息对于库存管理、订单…

Spring Boot 整合 Keycloak

1、概览 本文将带你了解如何设置 Keycloak 服务器&#xff0c;以及如何使用 Spring Security OAuth2.0 将 Spring Boot 应用连接到 Keycloak 服务器。 2、Keycloak 是什么&#xff1f; Keycloak 是针对现代应用和服务的开源身份和访问管理解决方案。 Keycloak 提供了诸如单…

秦巴山区SHP格式矢量范围

‌秦巴山区的shp范围包括河南、湖北、重庆、四川、陕西、甘肃六省市的80个县(市、区)。‌这一区域不仅地理范围广泛&#xff0c;而且生态多样性丰富&#xff0c;是国家重要的生物多样性和水源涵养生态功能区。秦巴山区的地貌类型以山地丘陵为主&#xff0c;间有汉中、安康、商丹…

Centos8.5.2111(1)之本地yum源搭建和docker部署与网络配置

由于后边可能要启动多个服务&#xff0c;避免服务之间相互干扰&#xff0c;本课程建议每个服务独立部署到一台主机上&#xff0c;这样做会导致资源占用过多&#xff0c;可能会影响系统的运行。服务器部署一般不采用GUI图形界面部署&#xff0c;而是采用命令行方式部署&#xff…

lDE 使用技巧与插件推荐(含案例说明)

在使用集成开发环境&#xff08;IDE&#xff09;进行编程时&#xff0c;掌握一些技巧和使用高效的插件可以显著提高开发效率。以下是一些通用的IDE使用技巧和插件推荐&#xff0c;适用于多种流行的IDE&#xff0c;如IntelliJ IDEA、Visual Studio Code、PyCharm等。每个技巧和插…

IEEE GRSL投稿历程分享

投稿期刊&#xff1a;IEEE geoscience and remote sensing 本人为本科生&#xff0c;在投这本期刊时已经经历了三次拒稿&#xff08;两次RS&#xff0c;一次GRSL&#xff09;&#xff0c;被RS拒稿意料之中&#xff0c;因为工作量并不是特别大&#xff0c;所以写得比较短&#…

【RocketMQ】RocketMQ快速入门

&#x1f3af; 导读&#xff1a;该文档介绍了Apache RocketMQ消息队列的基础应用&#xff0c;包括消息发送与接收的基本流程。首先通过创建生产者实例&#xff0c;并指定名称服务器地址&#xff0c;启动后即可发送消息至指定主题。然后创建消费者实例订阅相应主题&#xff0c;并…

js逆向——webpack实战案例(一)

今日受害者网站&#xff1a;https://www.iciba.com/translate?typetext 首先通过跟栈的方法找到加密位置 我们跟进u函数&#xff0c;发现是通过webpack加载的 向上寻找u的加载位置&#xff0c;然后打上断点&#xff0c;刷新网页&#xff0c;让程序断在加载函数的位置 u r.n…

Mamba模型初步解析 — Mamba : Linear-Time Sequence Modeling with Selective State Spaces

Mamba模型初步接触 — Mamba : Linear-Time Sequence Modeling with Selective State Spaces "Mamba"是一种序列建模架构&#xff0c;它采用了称为选择性状态空间模型&#xff08;SSMs&#xff09;的结构来优化处理长序列数据的效率和性能&#xff0c;这在语言处理、…

如果只能保留一个复制粘贴软件,那一定是它pastemate

下载地址&#xff1a;Pastemate 在日常的工作和生活中&#xff0c;使用电脑必离不开的功能中&#xff0c;一定有复制粘贴。传统的复制粘贴方式效率不那么高&#xff0c;Windows内置的剪切板功能感觉又差那么些意思。 &#x1f9d0;对于功能和颜值都有要求的你&#xff0c;一定…

端口隔离配置的实验

端口隔离配置是一种网络安全技术&#xff0c;用于在网络设备中实现不同端口之间的流量隔离和控制。以下是对端口隔离配置的详细解析&#xff1a; 基本概念&#xff1a;端口隔离技术允许用户将不同的端口加入到隔离组中&#xff0c;从而实现这些端口之间的二层数据隔离。这种技…

Linux入门2——初识Linux权限

目录 0. Linux下的用户 1.文件访问者的分类 2.文件类型和访问权限 3. 文件权限值的表示方法 4.文件访问权限的相关设置方法 4.1 修改文件的访问权限 4.2修改文件的拥有者和所属组 0. Linux下的用户 在学习Linux权限之前&#xff0c;我们要先来了解Linux下的用户&#x…

(十七)、Mac 安装k8s

文章目录 1、Enable Kubernetes2、查看k8s运行状态3、启用 kubernetes-dashboard3.1、如果启动成功&#xff0c;可以在浏览器访问3.2、如果没有跳转&#xff0c;需要单独安装 kubernetes-dashboard3.2.1、方式一&#xff1a;一步到位3.2.2、方式二&#xff1a;逐步进行 1、Enab…

杭州网站设计中的常见误区及解决方案

在杭州网站设计领域&#xff0c;随着数字经济的快速发展&#xff0c;越来越多的企业意识到互联网的重要性。然而&#xff0c;在实际的网站设计过程中&#xff0c;仍然存在一些常见的误区&#xff0c;这些误区可能会影响用户体验和网站的整体效果。以下是几种普遍存在的误区及其…

国产动漫论坛系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;动漫分类管理&#xff0c;动漫视频管理&#xff0c;动漫图片管理&#xff0c;动漫文章管理&#xff0c;交流论坛&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&a…