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

 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DAF-Net:一种基于域自适应的双分支特征分解融合网络用于红外和可见光图像融合

论文 DAF-Net: A Dual-Branch Feature Decomposition Fusion Network with Domain Adaptive for Infrared and Visible Image Fusion 提出了一种新的红外和可见光图像融合方法。该方法旨在结合红外图像和可见光图像的互补信息&#xff0c;以提供更全面的场景理解。红外图像在低…

学习C++的第七天!

1.虚函数是在基类中用 virtual 关键字声明的函数&#xff0c;可以在派生类中被重写。纯虚函数是在虚函数的基础上&#xff0c;在基类中被初始化为 0 的函数&#xff0c;含有纯虚函数的类是抽象类&#xff0c;不能被实例化。 2.如果基类的析构函数不是虚函数&#xff0c;当通过…

现代cpp多线程与并发初探

个人博客:Sekyoro的博客小屋 个人网站:Proanimer的个人网站 在现代c(c20)中,有了jthread和协程的概念,使得我们编写并发程序更加方便. 这里作简单学习. 前言知识 多线程编程 std::thread 用于创建一个执行的线程实例,所以它是一切并发编程的基础,使用时需要包含 <thread…

Android个性名片界面的设计——约束布局的应用

节选自《Android应用开发项目式教程》&#xff0c;机械工业出版社&#xff0c;2024年7月出版 做最简单的安卓入门教程&#xff0c;手把手视频、代码、答疑全配齐 【任务目标】 使用约束布局、TextView控件实现一个个性名片界面的设计&#xff0c;界面如图1所示。 图1 个性名片…

Transformer 算法模型详解

核心点&#xff1a;完整讲解Transformer模型&#xff01; 让我们用简单的语言来解释&#xff1a;想象一下&#xff0c;你正在阅读一本书&#xff0c;书中的每个字都很重要。但如果你每次只能关注一个字&#xff0c;理解整本书就会变得很慢。而Transformer模型就像是赋予你超能…

从密码学看盲拍合约:智能合约的隐私与安全新革命!

文章目录 前言一、什么是盲拍合约&#xff1f;二、盲拍合约的优势1.时间压力的缓解2.绑定与秘密的挑战 三、盲拍合约的工作原理1.提交盲出价2.披露出价3.结束拍卖4.退款机制 四、代码示例总结 前言 随着区块链技术的发展&#xff0c;智能合约在各种场景中的应用越来越广泛。盲…

基于Hive和Hadoop的病例分析系统

本项目是一个基于大数据技术的医疗病历分析系统&#xff0c;旨在为用户提供全面的病历信息和深入的医疗数据分析。系统采用 Hadoop 平台进行大规模数据存储和处理&#xff0c;利用 MapReduce 进行数据分析和处理&#xff0c;通过 Sqoop 实现数据的导入导出&#xff0c;以 Spark…

Linux入门2——初识Linux权限

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

vue+UEditor附件上传问题

&#x1f3c6;本文收录于《全栈Bug调优(实战版)》专栏&#xff0c;主要记录项目实战过程中所遇到的Bug或因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&am…

端口隔离配置的实验

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

算法记录——链表

2.链表 2.1判断是否是回文链表 1.方法一&#xff1a;利用栈反转链表 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode…

Invalid Executable The executable contains bitcode

Invalid Executable The executable contains bitcode 升级xcode16后&#xff0c;打包上传testflight时三方库报错&#xff1a;Invalid Executable - The executable ***.app/Frameworks/xxx.framework/xxx contains bitcode. 解决方案&#xff1a; 执行一下指令删除该framew…

软件测试学习路线图

软件测试工程师是专门从事软件、系统或产品测试和评估的技术专业人士&#xff0c;确保它们符合既定标准并无任何缺陷。通过精心设计和执行测试计划&#xff0c;软件测试工程师发现 Bug、故障和需要改进的领域&#xff0c;从而提高最终产品的可靠性和性能。 软件测试工程师在软…

Awcing 799. 最长连续不重复子序列

Awcing 799. 最长连续不重复子序列 解题思路: 让我们找到一个数组中&#xff0c;最长的 不包含重复的数 的连续区间的长度。 最优解是双指针算法&#xff1a; 我们用 c n t [ i ] cnt[i] cnt[i]记录 i i i 这个整数在区间内出现的次数。(因为每个数的大小为 1 0 5 10^5 105, …

状态模式原理剖析

《状态模式原理剖析》 状态模式&#xff08;State Pattern&#xff09; 是一种行为设计模式&#xff0c;它允许对象在其内部状态改变时改变其行为。换句话说&#xff0c;当对象状态发生变化时&#xff0c;它的行为也会随之变化。 通过状态模式&#xff0c;可以消除通过 if-else…

从“可用”到“好用”,百度智能云如何做大模型的“超级工厂”?

如果说&#xff0c;过去两三年大模型处于造锤子阶段&#xff0c;那么今年&#xff0c;更多的则是考验钉钉子的能力&#xff0c;面对各类业务场景大模型是否能够有的放矢、一击必中&#xff0c;为千行百业深度赋能。 当前市场上&#xff0c;已经有200多把这样的锤子在疯狂找钉子…

【unity进阶知识1】最详细的单例模式的设计和应用,继承和不继承MonoBehaviour的单例模式,及泛型单例基类的编写

文章目录 前言一、不使用单例二、普通单例模式1、单例模式介绍实现步骤&#xff1a;单例模式分为饿汉式和懒汉式两种。 2、不继承MonoBehaviour的单例模式2.1、基本实现2.2、防止外部实例化对象2.3、最终代码 3、继承MonoBehaviour的单例模式3.1、基本实现3.2、自动创建和挂载单…

OCR 行驶证识别 离线识别

目录 正页识别 副页识别 全部识别 OCR 行驶证识别 离线识别 正页识别 副页识别 全部识别

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

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

pcs集群表决盘故障导致主机reboot

建议重建fence设备并配置 PCSOracle HA实战安装配置参考 - 墨天轮