scanCommon.ts 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. import { ElMessage } from 'element-plus';
  2. // 定义扫描动作类型
  3. type ScanAction = 'connectMessage' | 'getScannerList' | 'scanResult' | string;
  4. // 定义接收到的消息结构
  5. interface ScanMessage {
  6. code: number;
  7. action?: ScanAction;
  8. data?: any;
  9. message?: string;
  10. param?: any; // 扩展参数,用于传递额外信息
  11. }
  12. // 定义回调函数类型
  13. type ScanCallback = (data: ScanMessage) => void;
  14. type ConnectionCallback = (isOnline: boolean,clientVersion: string) => void;
  15. class ScanCommonService {
  16. // 扫描客户端WebSocket 服务器地址
  17. private url: string = 'ws://127.0.0.1:9999'; // 扫描端口9999 图片服务端口9998
  18. // WebSocket 实例
  19. private ws: WebSocket | null = null;
  20. // 当前客户端连接状态
  21. public isOnline: boolean = false;
  22. public isScanned: boolean = false; // 是否有扫描仪 是否可以扫描
  23. public clientVersion: string = '1.0.0'; // 客户端版本号
  24. // 心跳定时器
  25. private timerPing: number | null = null;
  26. private timerClose: number | null = null;
  27. private connectionInterval: number | null = null; // 监听连接状态变化的定时器
  28. // 心跳间隔时间(单位:毫秒)
  29. private intervalPing: number = 10000; // 每隔10秒发送一次ping
  30. private intervalClose: number = 5000; // 等待5秒未收到pong则判定断开
  31. private isManuallyStopped: boolean = false; // 是否手动停止,防止重复连接
  32. private scanCallback: ScanCallback | null = null;//传入的回调函数
  33. /**
  34. * 初始化 WebSocket 连接
  35. * @param callback - 接收扫描结果的回调函数
  36. */
  37. init(callback?: ScanCallback): void {
  38. if (callback) {
  39. this.scanCallback = callback;
  40. }
  41. // 如果已经连接且未手动停止,不重复初始化
  42. if (this.ws && this.ws.readyState === WebSocket.OPEN && !this.isManuallyStopped) {
  43. return;
  44. }
  45. // 重置手动停止标志,允许重连
  46. this.isManuallyStopped = false;
  47. try {
  48. this.ws = new WebSocket(this.url);
  49. } catch (error) {
  50. console.error('WebSocket 初始化失败:', error);
  51. ElMessage({
  52. type: 'error',
  53. message: '无法初始化 WebSocket 连接,请检查网络设置',
  54. duration: 5000
  55. });
  56. return;
  57. }
  58. /**
  59. * WebSocket 连接建立成功时触发
  60. */
  61. this.ws.onopen = () => {
  62. this.isOnline = true;
  63. // 发送连接成功消息
  64. const sendConnectMsg = () => {
  65. if (this.ws && this.ws.readyState === WebSocket.OPEN) {
  66. this.ws.send("client connect success");
  67. }
  68. };
  69. sendConnectMsg();
  70. // 兜底延迟发送,防止某些极端情况下的就绪延迟
  71. setTimeout(sendConnectMsg, 500);
  72. this.startHeartbeat(); // 启动心跳检测
  73. };
  74. /**
  75. * 接收到服务端消息时触发
  76. */
  77. this.ws.onmessage = (evt: MessageEvent) => {
  78. if (evt.data != null) {
  79. if (evt.data === "ping") {
  80. this.send("pong"); // 回复 pong
  81. } else if (evt.data === "pong") {
  82. // 收到 pong 不做处理,仅用于重置心跳
  83. } else {
  84. try {
  85. const objectData: ScanMessage = JSON.parse(evt.data);
  86. console.log("处理扫描结果的objectData:", objectData);
  87. if (objectData.code === 200) {
  88. if (objectData.action === 'connectMessage') {
  89. this.clientVersion = objectData.param.appVersion || '';
  90. const version = objectData.param.appVersion || '未知版本';
  91. ElMessage({
  92. type: 'success',
  93. message: `客户端连接成功……版本号:${version}`
  94. });
  95. } else if (objectData.action === 'getScannerList') {
  96. // 获取扫描仪结果
  97. if (objectData.data && Array.isArray(objectData.data) && objectData.data.length > 0) {
  98. // 有扫描仪连接
  99. this.isScanned = true;
  100. ElMessage({
  101. type: 'success',
  102. message: '扫描仪连接成功……'
  103. });
  104. } else {
  105. // 没有扫描仪连接
  106. this.isScanned = false;
  107. }
  108. } else {
  109. // 其他业务数据回调
  110. if (this.scanCallback) this.scanCallback(objectData);
  111. }
  112. } else {
  113. // 未检测到纸张或卡纸
  114. console.log("未检测到纸张或者卡纸:", callback);
  115. if (this.scanCallback)
  116. {
  117. this.scanCallback(objectData);
  118. }
  119. else
  120. {
  121. ElMessage({
  122. type: 'error',
  123. message: '未检测到纸张或者卡纸'
  124. });
  125. }
  126. }
  127. } catch (error) {
  128. console.error("解析消息失败:", error);
  129. }
  130. }
  131. }
  132. this.resetHeartbeat(); // 重置心跳
  133. };
  134. /**
  135. * WebSocket 连接关闭时触发
  136. */
  137. this.ws.onclose = () => {
  138. this.handleOffline();
  139. };
  140. /**
  141. * WebSocket 发生错误时触发
  142. */
  143. this.ws.onerror = () => {
  144. this.isOnline = false;
  145. if (this.ws) {
  146. this.ws.close();
  147. }
  148. };
  149. }
  150. /**
  151. * 发送消息给 WebSocket 服务端
  152. * @param data - 要发送的消息内容
  153. */
  154. send(data: string): void {
  155. if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
  156. // 静默失败或根据需要提示
  157. return;
  158. }
  159. try {
  160. this.ws.send(data);
  161. } catch (e) {
  162. console.error("发送数据失败:", e);
  163. ElMessage({
  164. type: 'error',
  165. message: '发送数据失败,请检查连接状态',
  166. duration: 5000
  167. });
  168. }
  169. }
  170. /**
  171. * 启动心跳机制
  172. */
  173. private startHeartbeat(): void {
  174. this.clearHeartbeatTimers();
  175. this.timerPing = window.setTimeout(() => {
  176. if (this.ws && this.ws.readyState === WebSocket.OPEN) {
  177. this.send("ping"); // 发送心跳 ping
  178. this.timerClose = window.setTimeout(() => {
  179. if (this.ws) {
  180. this.ws.close(); // 若长时间无响应,主动断开连接
  181. }
  182. this.isOnline = false;
  183. this.handleOffline(); // 处理离线逻辑
  184. }, this.intervalClose);
  185. }
  186. }, this.intervalPing);
  187. }
  188. /**
  189. * 清除心跳定时器
  190. */
  191. private clearHeartbeatTimers(): void {
  192. if (this.timerPing) {
  193. clearTimeout(this.timerPing);
  194. this.timerPing = null;
  195. }
  196. if (this.timerClose) {
  197. clearTimeout(this.timerClose);
  198. this.timerClose = null;
  199. }
  200. }
  201. /**
  202. * 重置心跳定时器
  203. */
  204. private resetHeartbeat(): void {
  205. this.clearHeartbeatTimers();
  206. this.startHeartbeat(); // 重新启动心跳
  207. }
  208. /**
  209. * 停止 WebSocket 连接并清除所有定时器
  210. */
  211. stop(): void {
  212. // 标记为手动停止,防止后续自动重连
  213. this.isManuallyStopped = true;
  214. // 清除心跳定时器
  215. this.clearHeartbeatTimers();
  216. // 清除连接状态监听定时器
  217. this.stopWatchingConnection();
  218. // 关闭 WebSocket 连接
  219. if (this.ws) {
  220. // 移除事件监听,防止触发 handleOffline 导致重连
  221. this.ws.onclose = null;
  222. this.ws.onerror = null;
  223. this.ws.onmessage = null;
  224. this.ws.onopen = null;
  225. if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
  226. this.ws.close();
  227. }
  228. this.ws = null;
  229. }
  230. // 重置连接状态
  231. this.isOnline = false;
  232. this.isScanned = false;
  233. }
  234. /**
  235. * 处理设备离线逻辑,尝试自动重连
  236. */
  237. private handleOffline(): void {
  238. this.isOnline = false;
  239. // 如果是手动停止,则不再尝试重连
  240. if (this.isManuallyStopped) {
  241. return;
  242. }
  243. // 延迟尝试重连
  244. setTimeout(() => {
  245. if (!this.isOnline && !this.isManuallyStopped) {
  246. this.init(); // 重新初始化连接
  247. }
  248. }, this.intervalClose * 2);
  249. }
  250. /**
  251. * 检查客户端是否已连接
  252. * @returns {boolean}
  253. */
  254. isClientConnected(): boolean {
  255. return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
  256. }
  257. /**
  258. * 监听连接状态变化
  259. * @param callback - 回调函数,接收 isOnline 布尔值参数
  260. */
  261. watchConnection(callback: ConnectionCallback): void {
  262. if (this.connectionInterval) {
  263. clearInterval(this.connectionInterval); // 防止重复启动
  264. }
  265. const checkStatus = () => {
  266. const isOnline = this.isClientConnected();
  267. // 调用检测扫描仪指令
  268. if (isOnline) {
  269. if (!this.isScanned) {
  270. // 没有扫描仪连接 实时发送指令获取状态
  271. const json = {
  272. action: 'getScannerList', // 获取扫描仪信息
  273. };
  274. this.ws?.send(JSON.stringify(json));
  275. }
  276. callback(true,this.clientVersion); // 告诉网页 客户端打开状态
  277. } else {
  278. callback(false,this.clientVersion); // 告诉网页 连接已经断开
  279. }
  280. };
  281. checkStatus(); // 立即执行一次
  282. this.connectionInterval = window.setInterval(checkStatus, 3000); // 每3秒检测一次
  283. }
  284. /**
  285. * 停止监听连接状态
  286. */
  287. stopWatchingConnection(): void {
  288. if (this.connectionInterval) {
  289. clearInterval(this.connectionInterval);
  290. this.connectionInterval = null;
  291. }
  292. }
  293. }
  294. // 导出单例
  295. const scanCommon = new ScanCommonService();
  296. export default scanCommon;