---
group:
  title: 项目开发规范
title: 10.websocket使用说明
order: 10
---
# websocket使用说明
## 启动及关闭websocket连接
1. 统一放置到 `src/layouts/index.tsx` 内处理
2. 启动初始化的实例会传到model`common.socketClient` 和`LayoutContext`内

``` js
import React from 'react'
import { connect } from 'umi';
import { stompClient, connectSocket, disconnectSocket, initSubscribe } from '@/utils/socket';
let client
const DefaultLayout = connect(({ common }) => ({
  common
}))((props) => {
  const { common, dispatch } = props;

  // /start 启动websocket连接
  useEffect(() => {
    client = stompClient();
    // 开始连接
    connectSocket(client, { }, () => {
      // 全局存储 sockjsClient
      dispatch({
        type: 'common/updateSockjsClient',
        payload: { sockjsClient: client, sockjsClientConnected: client.connected }
      });

      // 监听订阅
      initSubscribe(client, '/topic/refresh/cache', (msg, topicUrl) => {
        console.log('推送返回数据', topicUrl);
        if (topicUrl === '/topic/refresh/cache') {
          console.log('刷新页面');
          window.location.reload();
        }
      });

    });
    return () => {
    // 断开连接
      disconnectSocket(client)
    }
  }, []);
  // /end启动websocket连接
return <RouteContext.Consumer> …… </RouteContext.Consume>
})

export DefaultLayout

``` 

## 订阅
### model获取实例
socketClient实例由model获取
```js
import React from 'react'
import { connect } from 'umi';
import { initSubscribe, unSubscribe } from '@/utils/socket';

const Page = connect(({ common }) => ({
  common
}))((props)=> {
  useEffect(() => {
    if (common.sockjsClient) {
      // 订阅监听
      initSubscribe(common.sockjsClient, '/user/topic/voice/call', (msg, topicUrl) => {
        console.log('订阅监听=>topicUrl', topicUrl)
        if (topicUrl === '/user/topic/voice/call') {
          if (msg && msg.body) {
            notification.info({
              message: `告警通知`,
              description: msg.body,
              duration: 0
            });
            audioRef.current.play()
          }
        }
      });
    }
    return ()=> {
      // 取消订阅
      common.sockjsClient && unSubscribe(common.sockjsClient)
    }
  }, [common.sockjsClient]);
  return <div>页面内容……</div>
})

export default Page

```
###  LayoutContext获取实例
socketClient实例由LayoutContext获取
> 注：在app.tsx内配置的组件无法使用`LayoutContext`(例如配置项 layout.actionsRender)，只能用model方式获取

```js
import React from 'react'
import LayoutContext from "@/components/LayoutContext";
const Page = (props)=> {
const { sockjsClient } = useContext(LayoutContext);
  useEffect(() => {
    if (sockjsClient) {
      // 订阅监听
      initSubscribe(common.sockjsClient, '/user/topic/voice/call', (msg, topicUrl) => {
        console.log('订阅监听=>topicUrl', topicUrl)
        if (topicUrl === '/user/topic/voice/call') {
          if (msg && msg.body) {
            notification.info({
              message: `告警通知`,
              description: msg.body,
              duration: 0
            });
            audioRef.current.play()
          }
        }
      });
    }
    return ()=> {
      // 取消订阅
     sockjsClient && unSubscribe(sockjsClient)
    }
  }, [sockjsClient]);
  return <div> 页面内容……</div>
}

export default Page
```

## socket.js
`src/utils/socket.js`代码
```js
/****
 *
 * @type {(function(*=, *=, *=): (SockJS|undefined))|*|{}}
 *
 * stompjs的所有方法的使用:https://www.cnblogs.com/gdluck/p/17044139.html
 * connect(headers, connectCallback): 建立websocket连接，headers可以是一个对象或者函数，connectCallback是连接成功后的回调函数
 * disconnect(disconnectCallback): 断开websocket连接，disconnectCallback是断开连接后的回调函数
 * send(destination, headers, body): 向指定目的地发送消息，destination是消息发送的目的地，headers是一个对象，body是消息体
 * subscribe(destination, callback, headers): 订阅指定目的地的消息，destination是消息发送的目的地，callback是接收到消息后的回调函数，headers是一个对象
 * unsubscribe(id): 取消订阅，id是订阅者的标识符
 * begin(transaction): 开始一个事务，transaction是事务的id
 * commit(transaction): 提交一个事务，transaction是事务的id
 * abort(transaction): 中止一个事务，transaction是事务的id
 * ack(messageID, subscription, headers): 确认收到消息，messageID是消息的id，subscription是消息的订阅者，headers是一个对象
 * nack(messageID, subscription, headers): 不确认收到消息，messageID是消息的id，subscription是消息的订阅者，headers是一个对象
 */
const SockJS = require('sockjs-client');
const Stomp = require('stompjs');
// 默认headers配置
const tokenHeaders = {
  'x-header-token': localStorage.getItem(`${process.env.siteName}-token`),
  'x-header-application': 'iot',
  'x-header-platform': 'web'
}
const stompClient = () => {
  const { NODE_ENV } = process.env;
  // const wsURL = NODE_ENV === 'development' ? `${window.location.origin}/ws` : `${window.location.origin}/spms-ibms-api/ws`;
  // http://172.16.3.118:8163/spms-ibms-api/ws // 测试服IP
  let wsURL;
  if (NODE_ENV === 'development') {
    // wsURL = 'http://172.16.9.172/city-brain-iot/websocket'
    wsURL = `${window.location.origin}${process.env.baseBuildApiPath || ''}/websocket`
  } else {
    // 如果是外网，直接使用 http://59.61.92.25:8181/spms-ibms-api/ws 会出现无法连接
    // wsURL = window.location.origin.indexOf('59.61.92.25') !== -1 ? `${window.location.origin}/spms-ibms-api/ws` : 'http://172.16.3.118:8163/spms-ibms-api/ws';
    wsURL = `${window.location.origin}${process.env.baseBuildApiPath || ''}/websocket`
  }
  const socket = new SockJS(wsURL);
  const client = Stomp.over(socket);
  if (NODE_ENV === 'production') {
    // client.debug = null // 关闭控制台打印
  }
  client.debug = null // 关闭控制台打印
  //
  // socket.onopen = function() {
  //   console.log('open');
  //   socket.send('test');
  // };
  //
  // socket.onmessage = function(e) {
  //   console.log('message', e.data);
  //   socket.close();
  // };
  //
  // socket.onclose = function() {
  //   console.log('close');
  // };

  return client;
}

const initSubscribeDone = (client, topicUrl, callback) => {
  // console.log('开始订阅', topicUrl)
  // 字符串时，仅处理一个订阅
  if (typeof topicUrl === 'string') {
    client.subscribe(topicUrl, msg => {
      if (msg && msg.headers.destination === topicUrl) {
        // 监听到变化
        callback(msg, topicUrl)
      }
    })
    // if (callgroup) callgroup(subscription)
  } else if (topicUrl.length > 0) {
    // topicUrl: [ url: 'XXX']
    // 如果是数组，则处理多个订阅
    const subscriptionGroup = []
    topicUrl.forEach((item) => {
      if (!item) return // URL未定义则不做处理
      subscriptionGroup.push(client.subscribe(item, msg => {
        if (msg && msg.headers.destination === item) {
          // 监听到变化
          callback(msg, item);
        }
      }))
    })
    // if (callgroup) callgroup(subscriptionGroup)
  }
}
// 开始启动 订阅URl监听
const initSubscribe = (client, topicUrl, callback) => {
  if (client.connected) {
    // 字符串时，仅处理一个订阅
    // console.log('已经连接')
    initSubscribeDone(client, topicUrl, callback)
  } else {
    // console.log('未连接')
    client.connect({
              ...tokenHeaders
            }, () => {
              // console.log('连接成功，开始初始化订阅', topicUrl)
              initSubscribeDone(client, topicUrl, callback)
            },
            (err) => console.error(`监听订阅${JSON.stringify(topicUrl)}出现异常`, err),
    )
  }
}

const unSubscribe = (client, subscribeId) => {
  client.unsubscribe(subscribeId)
}
// 连接推送添加订阅处理
const connectSocketAndSubscribe = (client, topicUrl, callback, callgroup) => {
  if (client) {
    if (client.connected) {
      // 字符串时，仅处理一个订阅
      initSubscribe(client, topicUrl, callback, callgroup)
    } else {
      client.connect({}, () => {
                initSubscribe(client, topicUrl, callback, callgroup)
              },
              (err) => console.error(`监听订阅${JSON.stringify(topicUrl)}出现异常`, err),
      )
    }
  }
}

const connectSocket = (client, headers, callback) => {
  // console.log('connectSocket==>', client, headers)
  // console.log('连接websocketd成功', client.connected)
  if (client) {
    if (client.connected) {
      console.log('已经连接过', client)
      callback && callback()
      // initSubscribe(client, topicUrl, callback, callgroup)
    } else {
      client.connect({
                ...tokenHeaders,
                ...headers
              }, () => {
                console.log('连接websocketd成功client.connected', client)
                callback && callback()
              },
              (err) => console.error(`连接websocket异常`, err),
      )
    }
  }
}


// send(destination, headers, body): 向指定目的地发送消息，destination是消息发送的目的地，headers是一个对象，body是消息体
const initSend = (client, destination, headers, body) => {
  if (client) {
    if (client.connected) {
      // console.log('处理send推送', headers, body)
      client.send(destination, headers, body)
    } else {
      client.connect({
                ...tokenHeaders
              }, () => {
                client.send(destination, headers, body)
              },
              (err) => console.error(`向目的地${JSON.stringify(destination)}发送消息出现异常`, err),
      )
    }
  }
}
// 关闭推送连接
const disconnectSocket = (client) => {
  if (client && client.connected) {
    client.disconnect()
  }
}

export default stompClient;

export {
  stompClient,
  connectSocket,
  disconnectSocket,
  initSubscribe,
  unSubscribe,
  initSend
}
``` 