簡介
本文服務端以 Nest 官方模板,客戶端以 Vue3 + Vite 官方模板為例,簡單介紹如何在 Nest 項目中使用 socket.io 與 Vue3 的客戶端進行即時通訊。
初始化項目
服務端
# 安裝Nest腳手架
$ npm i -g @nestjs/cli
# 創建一個nest后端項目
$ nest new project-name
# 啟動項目
$ yarn start:dev
創建完畢后:
# 服務端初始目錄結構:
src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
└── main.ts
客戶端
# 安裝vite腳手架
$ npm init vite@latest
# 創建一個vue前端端項目
$ npm init vite@latest my-vue-app -- --template vue
# 啟動項目
$ yarn dev
創建完畢后:
# 客戶端初始目錄結構:
src
├── assets
├── components
├── App.vue
└── main.ts
安裝所需依賴
服務端
# 安裝官方提供的socket.io包
$ yarn add @nestjs/websockets @nestjs/platform-socket.io
客戶端
# 安裝官方提供的socket.io包
$ yarn add socket.io-client
注意:
Nest v7
及以下版本依賴於socket.io v2
,Nest v8
依賴於socket.io v4
,請注意查看版本的兼容性。- 服務端和客戶端
socket.io
依賴包版本必須保持一致,否則將有可能無法連接或者報跨域等錯誤。
配置 websocket
服務端
# 使用官方cli工具在項目中生成一個websocket模塊。
$ nest g res socketTest
自動生成模塊之后,項目目錄是這樣的:
src
├── socket-test
├── dto
├── entities
├── socket-test.gateway.spec.ts
├── socket-test.gateway.ts
├── socket-test.module.ts
├── socket-test.service.spec.ts
├── socket-test.service.ts
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
└── main.ts
打開socket-test.gateway.ts
文件,內容如下:
import {
WebSocketGateway,
SubscribeMessage,
MessageBody,
} from "@nestjs/websockets";
import { SocketTestService } from "./socket-test.service";
import { CreateSocketTestDto } from "./dto/create-socket-test.dto";
import { UpdateSocketTestDto } from "./dto/update-socket-test.dto";
/** @WebSocketGateway裝飾器可傳入一些配置選項,如下面的示例:
* @WebSocketGateway(80, {
* namespace: 'events',
* transports: ['websocket']
* cors: {
* origin: '*'
* },
* ...
* })
**/
@WebSocketGateway({ cors: true })
export class SocketTestGateway {
constructor(private readonly socketTestService: SocketTestService) {}
@SubscribeMessage("createSocketTest")
create(@MessageBody() createSocketTestDto: CreateSocketTestDto) {
return this.socketTestService.create(createSocketTestDto);
}
@SubscribeMessage("findAllSocketTest")
findAll() {
return this.socketTestService.findAll();
}
@SubscribeMessage("findOneSocketTest")
findOne(@MessageBody() id: number) {
return this.socketTestService.findOne(id);
}
@SubscribeMessage("updateSocketTest")
update(@MessageBody() updateSocketTestDto: UpdateSocketTestDto) {
return this.socketTestService.update(
updateSocketTestDto.id,
updateSocketTestDto
);
}
@SubscribeMessage("removeSocketTest")
remove(@MessageBody() id: number) {
return this.socketTestService.remove(id);
}
}
客戶端
在 src 目錄下創建 plugins 文件夾,於其中新建一個 Socket.io.ts 插件,
src
├── assets
├── components
├── plugins
├── Socket.io.ts
├── App.vue
└── main.ts
在 Socket.io.ts 文件中寫入下面的內容,
// Socket.io.ts
import { io } from "socket.io-client";
export default {
install: (app, { connection, options }) => {
const socket = io(connection, options);
app.config.globalProperties.$socket = socket;
app.provide("socket", socket);
},
};
然后在 main.ts 文件中引入進來,掛載到 app 上,
// main.ts
import { createApp } from "vue";
import App from "./App.vue";
import Socketio from "/@/plugins/Socket.io";
const app = createApp(App);
app.use(Socketio, {
connection: "/* 這里填寫服務端地址,如 http://localhost:3000 */",
options: {
autoConnect: false, //關閉自動連接
// ...其它選項
},
});
app.mount("#app");
然后在需要的時候使用socket.connect()
手動連接 socket。
實際運用
Nest 在使用@SubscribeMessage
裝飾的方法中,會return
一個確認信息,我們可以直接返回 JSON 格式或是字符串格式數據,比如我們添加一個測試事件:
// 服務端 socket-test.gateway.ts
@SubscribeMessage('socketTest')
socketTest(@MessageBody() data: any) {
Logger.log(data) // {test: '測試數據'}
return {
msg1: '測試1',
msg2: '測試2',
}
}
----------------------------------
// 客戶端 HelloWorld.vue
<script lang="ts" setup>
import { ref, onMounted, inject } from 'vue';
import { Socket } from 'socket.io-client';
const socket = inject('socket') as Socket;
socket.emit('socketTest', {test: '測試數據'}, (data) => {
console.log(data) // { msg1: '測試1', msg2: '測試2' }
});
onMounted(() => {
socket.connect(); //連接socket服務器
});
</script>
有時候可能需要在客戶端發送消息后,讓服務端把消息再轉發給客戶端的另一個事件中,我們可以在 return 的時候增加一個指定事件 event,然后在客戶端進行監聽,比如:
// 服務端 socket-test.gateway.ts
@SubscribeMessage('socketTest')
socketTest(@MessageBody() data: any) {
return {
event: 'socketTest2',
data
}
}
----------------------------------
// 客戶端 HelloWorld.vue
<script lang="ts" setup>
import { ref, onMounted, inject } from 'vue';
import { Socket } from 'socket.io-client';
const socket = inject('socket') as Socket;
socket.emit('socketTest',{ msg1: '測試1', msg2: '測試2' })
socket.on('socketTest2', (data) => {
console.log(data) // { msg1: '測試1', msg2: '測試2' }
});
onMounted(() => {
socket.connect(); //連接socket服務器
});
</script>
上面雖然解決了客戶端與服務端相互通信的問題,但實際上我們的項目可能不會這么簡單,有可能是socket.id
不同的多個客戶端,這種情況下我們就需要使用@nestjs/websockets
包導出的@ConnectedSocket()
裝飾器,獲取到 socket.io 的實例,使用官方提供的一些 Api 來定義事件,以廣播事件為例:
// 服務端 socket-test.gateway.ts
@SubscribeMessage('socketTest')
socketTest(@MessageBody() data: any, @ConnectedSocket() clinet: Socket) {
clinet.broadcast.emit('socketTest2', data);
}
----------------------------------
// 客戶端-1 HelloWorld.vue
<script lang="ts" setup>
import { ref, onMounted, inject } from 'vue';
import { Socket } from 'socket.io-client';
const socket = inject('socket') as Socket;
socket.emit('socketTest',{ msg1: '測試1', msg2: '測試2' })
socket.on('socketTest2', (data) => {
console.log(data) // { msg1: '測試1', msg2: '測試2' }
});
onMounted(() => {
socket.connect(); //連接socket服務器
});
</script>
----------------------------------
// 客戶端-2 Layout.vue
<script lang="ts" setup>
import { ref, onMounted, inject } from 'vue';
import { Socket } from 'socket.io-client';
const socket = inject('socket') as Socket;
socket.on('socketTest2', (data) => {
console.log(data) // { msg1: '測試1', msg2: '測試2' }
});
onMounted(() => {
socket.connect(); //連接socket服務器
});
</script>