ws.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import { isDevMode } from '@/utils/env'
  2. import { message } from 'antd'
  3. import type { MessageType } from '@/types/typing'
  4. import wsNotice from '@/components/RightContent/Book/components/wsNotice'
  5. import { isNullOrUnDef } from './is'
  6. import { tryChangeWorkStatus } from '@/components/RightContent/Book'
  7. enum ReadyState {
  8. Connecting = 0,
  9. Open = 1,
  10. Closing = 2,
  11. Closed = 3
  12. }
  13. export enum CmdType {
  14. /** @name 心跳 */
  15. HEART_BEAT = 0,
  16. /** @name 初始化接收的消息 */
  17. FirstMsg = 1,
  18. /** @name 通知、消息 */
  19. NoticeOrMessage = 13,
  20. /** @name 在线员工列表 */
  21. OnlineBook = 12,
  22. /** @name 员工工作状态 */
  23. WorkStatus = 14,
  24. /** @name 强制退出 */
  25. OutLine = 15
  26. }
  27. interface Options {
  28. reconnectLimit?: number
  29. reconnectInterval?: number // ♥跳间隔时长
  30. onOpen?: (event: WebSocketEventMap['open']) => void
  31. onClose?: (event: WebSocketEventMap['close']) => void
  32. onMessage?: (message: WebSocketEventMap['message']) => void
  33. onError?: (event: WebSocketEventMap['error']) => void
  34. }
  35. class Ws {
  36. private uid: string // 用户id
  37. private cid: string // 副本号
  38. public uri: string // websocket 链接地址
  39. public opts?: Options // websocket 链接相关配置
  40. private isLock: boolean // 断开锁
  41. private reconnectLimit: number // 重连限制锁
  42. private timer: NodeJS.Timeout | undefined // 重连 延时函数
  43. private sTimer: NodeJS.Timeout | undefined // ♥跳 延时函数
  44. private sinTimer: NodeJS.Timeout | undefined // ♥跳
  45. public wsIns: WebSocket | undefined // websocket实例
  46. // 初始化socket,一般在应用启动时初始化一次就好了,或者需要更换wsUrl
  47. public init(uid: string, uri: string, opts?: Options) {
  48. this.uid = uid
  49. this.uri = uri
  50. this.opts = opts
  51. this.reconnectLimit = 0
  52. if (isNullOrUnDef(this.uri)) {
  53. throw new Error('websocket连接地址不能为空')
  54. return
  55. }
  56. if (!this.wsIns || this.wsIns?.readyState === ReadyState.Closed) {
  57. this.connectWs()
  58. }
  59. }
  60. // 利用promise封装,解决ws可能没有实例化
  61. public getInstance(): Promise<WebSocket> {
  62. return new Promise((resolve, reject) => {
  63. if (this.wsIns?.readyState === ReadyState.Open) {
  64. resolve(this.wsIns)
  65. } else if (this.wsIns) {
  66. this.connectWs()
  67. resolve(this.wsIns)
  68. } else {
  69. reject()
  70. }
  71. })
  72. }
  73. private connectWs() {
  74. try {
  75. this.wsIns = new WebSocket(this.uri)
  76. this.wsIns.onmessage = msg => {
  77. this.heartCheck()
  78. // 心跳检测事件, 什么事情都不用干
  79. if (msg.data === 'pong') {
  80. return
  81. }
  82. const data = JSON.parse(msg.data)
  83. if (data.cmd === CmdType.FirstMsg) {
  84. // 保存当前socket副本号
  85. this.cid = data.CopyCount
  86. }
  87. this.opts?.onMessage?.(data)
  88. }
  89. this.wsIns.onopen = event => {
  90. // this.heartCheck()
  91. tryChangeWorkStatus(this.uid, window.location.pathname)
  92. this.opts?.onOpen?.(event)
  93. }
  94. this.wsIns.onclose = event => {
  95. this.reconnect()
  96. this.opts?.onClose?.(event)
  97. }
  98. this.wsIns.onerror = err => {
  99. this.reconnect()
  100. this.opts?.onError?.(err)
  101. }
  102. // 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
  103. window.addEventListener('beforeunload', () => {
  104. this.disconnect()
  105. })
  106. } catch (error) {
  107. if (isDevMode()) {
  108. message.error(error.toString())
  109. }
  110. this.wsIns = undefined
  111. }
  112. }
  113. /** 发送消息 */
  114. public sendMessage(map: MessageType) {
  115. const cMap = { ...map }
  116. const keys = Object.keys(map)
  117. keys.forEach(item => {
  118. if (/(userid)|(dstid)/.test(item)) {
  119. cMap[item] += `_${this.cid}`
  120. }
  121. })
  122. if (this.wsIns?.readyState === ReadyState.Open) {
  123. this.wsIns.send(JSON.stringify(cMap))
  124. }
  125. }
  126. /** 断开连接 */
  127. public disconnect() {
  128. if (this.wsIns?.readyState === ReadyState.Open) {
  129. this.isLock = true
  130. // this.wsIns.close()
  131. this.sendMessage({ cmd: CmdType.OutLine, userid: this.uid })
  132. }
  133. }
  134. /** ♥跳检测 */
  135. private heartCheck() {
  136. this.sTimer && clearTimeout(this.sTimer)
  137. this.sinTimer && clearInterval(this.sinTimer)
  138. this.sTimer = setTimeout(() => {
  139. // 发送♥跳
  140. this.sendMessage({ cmd: CmdType.HEART_BEAT, dstid: this.uid })
  141. this.sinTimer = setTimeout(() => {
  142. if (this.wsIns.readyState !== ReadyState.Open) {
  143. this.wsIns.close()
  144. }
  145. }, this.opts.reconnectInterval)
  146. }, this.opts.reconnectInterval)
  147. }
  148. // 重连
  149. private reconnect(): void {
  150. if (
  151. !this.isLock &&
  152. !(this.opts?.reconnectLimit && this.reconnectLimit > this.opts?.reconnectLimit)
  153. ) {
  154. this.reconnectLimit += 1
  155. this.timer && clearTimeout(this.timer)
  156. this.timer = setTimeout(() => {
  157. this.connectWs()
  158. this.isLock = false
  159. }, 4000)
  160. }
  161. }
  162. }
  163. export function onMessage(msg: MessageType) {
  164. // 这里执行所有的message监听事件
  165. switch (msg.cmd) {
  166. // 通知、消息
  167. case CmdType.NoticeOrMessage:
  168. wsNotice(msg)
  169. break
  170. // 在线员工列表
  171. case CmdType.OnlineBook:
  172. break
  173. default:
  174. break
  175. }
  176. }
  177. export default new Ws()