本文将介绍如何使用 Nest.js
和 Uni-app
实现一个简单的实时聊天应用。后端使用 @nestjs/websockets
和 socket.io
,前端使用 uni-app
并集成 socket.io-client
。这个项目允许多个用户同时加入聊天并实时交换消息。
效果图:
一、准备工作
-
安装 Node.js 和 npm
-
全局安装 Nest.js CLI
npm install -g @nestjs/cli
二、后端:Nest.js 实现 WebSocket 服务
1. 创建 Nest.js 项目
首先使用 CLI 创建一个新的 Nest.js 项目:
nest new nest-chat
选择使用 npm
或 yarn
进行依赖管理。
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项目
下面给大家提供代码地址,可以与你的同事们私密聊天