在 Nestjs 和 Vue3 中使用 socket.io

本文首次发布在https://www.cnblogs.com/China-Dream/p/15827701.html

简介

本文服务端以 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 v2Nest 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

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服务器
});

有时候可能需要在客户端发送消息后,让服务端把消息再转发给客户端的另一个事件中,我们可以在 return 的时候增加一个指定事件 event,然后在客户端进行监听,比如:

// 服务端 socket-test.gateway.ts

@SubscribeMessage('socketTest')
socketTest(@MessageBody() data: any) {
  return {
    event: 'socketTest2',
    data
  }
}

----------------------------------
// 客户端 HelloWorld.vue

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服务器
});

上面虽然解决了客户端与服务端相互通信的问题,但实际上我们的项目可能不会这么简单,有可能是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

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服务器
});

----------------------------------
// 客户端-2 Layout.vue

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服务器
});
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容