socket.service.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. import { injectable, Service } from './service'
  2. import io from 'socket.io-client'
  3. import TimerService from './timer.service'
  4. import useStore from './user.data'
  5. /**
  6. * socket io 返回数据格式
  7. */
  8. type ResponseData = {
  9. status: number;
  10. body: object | null;
  11. };
  12. /**
  13. * 网络服务返回数据格式
  14. */
  15. interface Result<T = Any> {
  16. success: boolean;
  17. data: T;
  18. }
  19. interface Request<T = Any> {
  20. code: string;
  21. timestamp: number;
  22. nonestr: string;
  23. body: T;
  24. }
  25. /**
  26. * 约束socket 请求响应参数,code声明,规范请求、返回参数
  27. */
  28. interface SendKey<T = Record<string, Any>, P = Record<string, Any>> { }
  29. /**
  30. * API网络请求服务
  31. */
  32. @injectable
  33. export default class SocketService extends Service {
  34. private URL = import.meta.env.VITE_SOCKET_URL
  35. private userData = useStore()
  36. private timerService = new TimerService()
  37. /**
  38. * 是否已经完成时间同步
  39. * 防止刷新之后因为时间同步问题导致超时
  40. */
  41. private timeSynced_ = false
  42. /**
  43. * 是否已经完成时间同步
  44. */
  45. public get timeSynced() {
  46. return this.timeSynced_
  47. }
  48. /**
  49. * 网络请求监听集合
  50. */
  51. private listeners: { [code: string]: [(response: object) => void] } = {}
  52. /**
  53. * 请求回调集合
  54. */
  55. private callbacks: { [key: string]: (response: ResponseData) => void } = {}
  56. /**
  57. * 服务器当前时间戳
  58. */
  59. private timestamp = Math.floor(Date.now() / 1000)
  60. /**
  61. * socketio对象
  62. */
  63. private socket!: SocketIOClient.Socket
  64. /**
  65. * 接口超时列表
  66. */
  67. private timeoutList: { [code: string]: number } = {
  68. 'system.common': 60 * 1000
  69. }
  70. constructor() {
  71. super()
  72. // 建立连接
  73. this.reset()
  74. // 设置定时心跳
  75. this.timerService.on('10s', this.ping.bind(this))
  76. this.timerService.on('5m', this.syncTime.bind(this))
  77. this.timerService.on('5s', () => {
  78. this.timestamp += 5
  79. })
  80. this.aysncTime()
  81. }
  82. public async aysncTime() {
  83. while (true) {
  84. await (new Promise((res) => {
  85. setTimeout(res, 500)
  86. }))
  87. if (this.timeSynced_) {
  88. break
  89. }
  90. console.log('等待时间同步')
  91. }
  92. }
  93. /**
  94. * 全局发送消息
  95. * @param code 代码
  96. * @param message 信息
  97. * @param timeout 超时 默认6秒
  98. */
  99. public async send<R = Any, Q = Any>(code: string | SendKey<Q, R>, data?: Q, timeout = 1000 * 15): Promise<Result<R | null>> {
  100. if (this.timeoutList[code as string]) {
  101. timeout = this.timeoutList[code as string]
  102. }
  103. // if (!this.timeSynced_) throw 没同步成功会阻断请求
  104. return await this.request(code, data || {}, timeout)
  105. }
  106. /**
  107. * 全局监听socketio事件
  108. * @param code 命令
  109. * @param handle 处理接口
  110. */
  111. public on(code: string, handle: (response: Any) => void, unique = false) {
  112. if (unique) {
  113. this.listeners[code] = [ handle ]
  114. } else {
  115. this.listeners[code] = this.listeners[code] || []
  116. this.listeners[code].push(handle)
  117. }
  118. return true
  119. }
  120. /**
  121. * 取消全局监听socketio事件
  122. * @param code 命令 * 代表清除所有
  123. * @param handle 处理接口
  124. */
  125. public off(code: string, handle?: (response: object) => void) {
  126. if (code === '*') {
  127. this.listeners = {}
  128. return true
  129. }
  130. this.listeners[code] = this.listeners[code] || []
  131. const index = this.listeners[code].indexOf(handle!)
  132. if (index > -1) {
  133. this.listeners[code].splice(index, 1)
  134. return true
  135. }
  136. return false
  137. }
  138. /**
  139. * 设置身份令牌
  140. * @param token 令牌
  141. */
  142. public setToken(token: string) {
  143. this.userData.token = token
  144. }
  145. /**
  146. * 发起请求
  147. * @param code 命令码
  148. * @param data 数据
  149. */
  150. private request<T, P>(code: string | SendKey<T, P>, data: T | {}, timeout = 1000 * 6): Promise<Result> {
  151. const that = this
  152. const nonestr = `${Math.floor(Math.random() * 1000000)}`
  153. // proto 加密
  154. // 发送
  155. this.socket.emit(
  156. 'request',
  157. JSON.stringify({
  158. code,
  159. body: data,
  160. timestamp: this.timestamp,
  161. nonestr,
  162. token: this.userData.token
  163. })
  164. )
  165. return new Promise((res, rej) => {
  166. const timer = setTimeout(() => {
  167. if (that.callbacks[nonestr]) {
  168. delete that.callbacks[nonestr]
  169. }
  170. throw '请求超时'
  171. }, timeout)
  172. that.callbacks[nonestr] = (response: ResponseData) => {
  173. clearTimeout(timer)
  174. delete that.callbacks[nonestr]
  175. const status = +response.status
  176. if (status !== 0) {
  177. if (status === 99) this.reset()
  178. if (status === 401) {
  179. this.userData.setToken('')
  180. throw 401
  181. }
  182. throw response.body
  183. } else {
  184. res({ success: true, data: response.body })
  185. }
  186. }
  187. })
  188. }
  189. /**
  190. * 心跳监测
  191. */
  192. private async ping() {
  193. if (!this.userData.token) {
  194. return
  195. }
  196. await this.request('user.heart', {})
  197. }
  198. /**
  199. * 时间同步
  200. */
  201. private async syncTime() {
  202. const { success, data } = await this.request('server.getNowTime', {})
  203. if (success && data) {
  204. this.timestamp = data
  205. this.timeSynced_ = true
  206. console.log('同步成功')
  207. }
  208. }
  209. /**
  210. * 接收socketio返回的消息
  211. * @param res 回复消息体
  212. */
  213. private onResponse(res: string) {
  214. let response = null
  215. try {
  216. response = JSON.parse(res)
  217. } catch (error) {
  218. throw `接口返回数据异常 ${res}`
  219. }
  220. if (response && this.callbacks[response.nonestr]) {
  221. this.callbacks[response.nonestr](response)
  222. }
  223. }
  224. /**
  225. * 接收socketio 的请求消息
  226. * @param request 回复消息体
  227. */
  228. private onRequest(request: string | Request) {
  229. let data!: Request
  230. // 解析数据
  231. if (typeof request === 'string') {
  232. const temp = JSON.parse(request)
  233. if (temp) data = temp
  234. } else {
  235. data = request
  236. }
  237. // 调用监听接口
  238. if (this.listeners[data.code]) {
  239. const errs: string[] = []
  240. for (const handle of this.listeners[data.code]) {
  241. try {
  242. handle(JSON.parse(JSON.stringify(data)))
  243. } catch (err) {
  244. errs.push((err as Any).stack)
  245. }
  246. }
  247. if (errs.length) {
  248. throw `${errs}`
  249. }
  250. }
  251. }
  252. /**
  253. * 重置连接
  254. */
  255. private reset() {
  256. if (this.socket) {
  257. this.socket.close()
  258. }
  259. try {
  260. if (process.env.NODE_ENV === 'development') {
  261. const opts = { transports: [ 'websocket' ] }
  262. this.URL.includes('https') && Object.assign(opts, { forceNew: true, path: '/wss' })
  263. this.socket = io(this.URL, opts)
  264. } else {
  265. this.socket = io({
  266. forceNew: true,
  267. transports: [ 'websocket' ],
  268. path: '/wss'
  269. })
  270. }
  271. this.socket.on('request', this.onRequest.bind(this))
  272. this.socket.on('response', this.onResponse.bind(this))
  273. setTimeout(this.ping.bind(this), 300)
  274. setTimeout(this.syncTime.bind(this), 100)
  275. } catch (err) {
  276. throw `${err}`
  277. }
  278. }
  279. }