Midway.js打通WebSocket前后端监听通道

您好, 如果喜欢我的文章或者想上岸大厂,可以关注公众号「量子前端」,将不定期关注推送前端好文、分享就业资料秘籍,也希望有机会一对一帮助你实现梦想

前言

WebSocket协议允许客户端和服务端持久化连接,这种可以持续连接的特性使得WebScoket特别适用于游戏或者聊天室的场景,同样也适用于订单提交完以后监听状态变化进行页面刷新的场景。

Midway中提供了对于ws模块的支持和封装,能够快速创建WebSocket服务。

服务端实现

我们采用Midway.js来实现服务端能力,首先创建一个项目:

kotlin 复制代码
npm init midway@latest -y

然后在项目中安装WebSocket的依赖包:

sql 复制代码
npm i @midwayjs/ws@3 --save
npm i @types/ws --save-dev

/src/configuration.ts中开启WebSocket组件:

typescript 复制代码
// src/configuration.ts
import { Configuration } from '@midwayjs/core';
import * as ws from '@midwayjs/ws';

@Configuration({
  imports: [ws],
  // ...
})
export class MainConfiguration {
  async onReady() {
        // ...
  }
}

然后我们开始写接口,首先在项目目录创建socket目录:

bash 复制代码
├── package.json
├── src
│   ├── configuration.ts          ## 入口配置文件
│   ├── interface.ts
│   └── socket                    ## ws 服务的文件
│       └── hello.controller.ts
├── test
├── bootstrap.js                  ## 服务启动入口
└── tsconfig.json

通过 @WSController 装饰器定义 WebSocket 服务:

typescript 复制代码
import { WSController } from '@midwayjs/core';

@WSController()
export class HelloSocketController {
  // ...
}

当有客户端连接时,会触发 connection 事件,我们在代码中可以使用 @OnWSConnection() 装饰器来修饰一个方法,当每个客户端第一次连接服务时,将自动调用该方法。

typescript 复制代码
import { WSController, OnWSConnection, Inject } from '@midwayjs/core';
import { Context } from '@midwayjs/ws';
import * as http from 'http';

@WSController()
export class HelloSocketController {

  @Inject()
  ctx: Context;

  @OnWSConnection()
  async onConnectionMethod(socket: Context, request: http.IncomingMessage) {
    console.log(`namespace / got a connection ${this.ctx.readyState}`);
  }
}

WebSocket 是通过事件的监听方式来获取数据。Midway 提供了 @OnWSMessage() 装饰器来格式化接收到的事件,每次客户端发送事件,被修饰的方法都将被执行。

typescript 复制代码
import { WSController, OnWSMessage, Inject } from '@midwayjs/core';
import { Context } from '@midwayjs/ws';

@WSController()
export class HelloSocketController {

  @Inject()
  ctx: Context;

  @OnWSMessage('message')
  async gotMessage(data) {
    return { name: 'harry', result: parseInt(data) + 5 };
  }
}

我们可以通过 @WSBroadCast 装饰器将消息发送到所有连接的客户端上。

typescript 复制代码
import { WSController, OnWSConnection, Inject } from '@midwayjs/core';
import { Context } from '@midwayjs/ws';

@WSController()
export class HelloSocketController {

  @Inject()
  ctx: Context;

  @OnWSMessage('message')
  @WSBroadCast()
  async gotMyMessage(data) {
    return { name: 'harry', result: parseInt(data) + 5 };
  }

  @OnWSDisConnection()
  async disconnect(id: number) {
    console.log('disconnect ' + id);
  }
}

至此,基础的WebSocket接口就开发好了。

  • 当客户端连接时,我们会打印namespace / got a connection
  • 当客户端发送消息时,我们会返回给客户端基于入参的编号附加5的结果;
  • 当客户端断开连接时,我们会打印disconnect

最后我们在服务端开启一个WebSocket服务:

/src/config/config.default.ts

typescript 复制代码
// src/config/config.default
export default {
  // ...
  webSocket: {
    port: 3000,
  },
}

接下来我们前端来调用测试下。

前端实现

前端使用react,我们建一个新页面,在useEffect中开启ws服务:

typescript 复制代码
  useEffect(async () => {
    const ws = new WebSocket(`ws://localhost:9999`);

    ws.onopen = () => {
      console.log('连接成功');
      ws.send(1);
    };
    ws.onmessage = (e) => {
      console.log('服务端响应:', e);
    };
    ws.onclose = (e) => {
      console.log('关闭连接,服务端响应:', e)
    }
  }, []);

这样我们在前端就可以打印出服务端返回的结果,同时服务端获取到了日志信息,前后端的WebSocket链路打通了。

前端:

开启了webSocket服务:

后端:

订单刷新

接下来我们模拟一个场景:当前端提交完订单后到了订单详情页,需要实时获取订单状态,如果订单申请通过,则展示成功信息,这里就不连接数据库了,直接在服务端维护一个对象来模拟:

typescript 复制代码
import {
  WSController,
  Inject,
  OnWSConnection,
  OnWSMessage,
  OnWSDisConnection,
} from '@midwayjs/core';
import { Context } from '@midwayjs/ws';
import * as http from 'http';

const orderInfo = {
  status: 'pending',
  id: 1,
  // ...orderInfo
};

@WSController()
export class HelloSocketController {
  @Inject()
  ctx: Context;

  // 客户端第一次连接
  @OnWSConnection()
  async onConnectionMethod(socket: Context, request: http.IncomingMessage) {
    this.ctx.logger.info(`namespace / got a connection ${this.ctx.readyState}`);
    setTimeout(() => {
      orderInfo['status'] = 'success';
    }, 3000);
  }

  // 收到客户端消息
  @OnWSMessage('message')
  async gotMessage() {
    if (orderInfo.status === 'success') {
      return { result: true };
    }
    return { result: false };
  }

  // 客户端断开连接
  @OnWSDisConnection()
  async disconnect(id: number) {
    console.log('disconnect ' + id);
  }
}
  • 在后端mock一个orderInfo
  • WebSocket连接后,延迟三秒改变orderInfo的状态,模拟一个算法审核的时间;
  • 三秒后响应给前端新状态;

前端配合改造:

typescript 复制代码
useEffect(async () => {
    const ws = new WebSocket(`ws://localhost:9999`);

    ws.onopen = () => {
      console.log('连接成功');
      timer.current = setInterval(() => {
        ws.send(1);
      }, 1000);
    };
    ws.onmessage = (e) => {
      console.log('服务端响应:', e);
      if (JSON.parse(e.data).result) {
        setOrderPass(true);
      }
    };
    ws.onclose = (e) => {
      console.log('关闭连接,服务端响应:', e);
    };
  }, []);

  return <>订单状态:{orderPass ? '通过' : '申请中'}</>;

改造后,前端页面刷新,三秒后订单状态异步更改为通过,达到了页面监听实时刷新的效果,这样的效果是不是比传统Http定时轮询性能更优呢?

结尾

本文通过Midway0~1打通前后端ws链路,并在最后通过实际业务场景展示了ws的优势以及与传统轮询方案的差异。

如果喜欢我的文章或者想上岸大厂,可以关注公众号「量子前端」,将不定期关注推送前端好文、分享就业资料秘籍,也希望有机会一对一帮助你实现梦想。

相关推荐
长栎17 分钟前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
user205855615181319 分钟前
Windows 项目安装时报 `node-sass` 错误,如何快速处理
前端
LiaCode20 分钟前
Redis 在生产项目的使用
前端·后端
用户5598224812225 分钟前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode26 分钟前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战27 分钟前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
米丘39 分钟前
vite8 vite preview 命令做了什么?
node.js·vite
风骏时光牛马39 分钟前
# Ruby基于Rails框架实现多角色权限管理与数据分页查询完整实战代码案例
前端
weedsfly41 分钟前
迭代器、生成器与异步迭代——让数据“按需流动”的艺术
前端·javascript
xiaodaoluanzha1 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端