trainWs.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. import io from 'socket.io-client';
  2. import { ref, onMounted, onUnmounted, onBeforeUnmount } from 'vue';
  3. export function useWs() {
  4. const address: any = import.meta.env.VITE_APP_BASE_API;
  5. const token: any = localStorage.getItem('token');
  6. const myInfo: any = localStorage.getItem('userInfo');
  7. let socket: any = null; //ws实例对象
  8. let timerManager: any = {}; //计时器管理
  9. let parameter: any = {}; //参数
  10. let testTime: number = 0; //默认时长
  11. let userInfo: any = JSON.parse(myInfo); //用户信息
  12. let beatTime: number = 10000; //心跳频率
  13. let loading: any = null; //遮罩层
  14. let version: string = ''; //ws接口版本v2表示单ws多项目
  15. let examStateList: any = []; //当前状态码
  16. let testList: any = []; //区列表
  17. let wkList: any = []; //工作站列表
  18. let loadingTime: any = null; //记录等待时间
  19. socket = io(address + '/midexam', {
  20. transports: ['websocket', 'polling'],
  21. query: {
  22. type: 'train',
  23. Authorization: 'JWT ' + token,
  24. sysuuid: 'JWT ' + token
  25. }
  26. });
  27. async function initWs(data: any, callback: any) {
  28. examStateList = []; //当前状态码
  29. testList = []; //区列表
  30. wkList = []; //工作站列表
  31. loading = ElLoading.service({ text: '正在初始化,请稍候', background: 'rgba(0, 0, 0, 0.8)', customClass: `sports ${data.parameter.project}` });
  32. parameter = data.parameter;
  33. testTime = data.testTime;
  34. version = data.version || '';
  35. testList = data.parameter.area.split(',');
  36. examStateList = testList.map((item: any) => {
  37. let examId = `${parameter.project}_${item}`;
  38. let obj = {
  39. examState: 0,
  40. examId: examId,
  41. beatNumber: 0,
  42. wsStatus: 0
  43. };
  44. return obj;
  45. });
  46. loadingTime = setTimeout(() => {
  47. //20秒还在0状态就算超时
  48. let list = examStateList.filter((item: any) => {
  49. return item.examState == 0;
  50. });
  51. //考虑到多开只有一个在线也有效的
  52. if (list.length == testList.length) {
  53. clearTimeout(loadingTime);
  54. for (const item of testList) {
  55. let examId = `${parameter.project}_${item}`;
  56. callback({ cmd: 'disconnect_request', exam_id: examId, data: { message: 'WS连接超时' } });
  57. }
  58. }
  59. }, 20000);
  60. socket.on('connect', async (e: any) => {
  61. callback({ cmd: 'mySid', data: { sid: socket.id.replace('/midexam#', '') } });
  62. if (testList.length > 1) {
  63. //单WS多区直接执行
  64. for (const item of testList) {
  65. await delay(100); // 延时
  66. let examId = `${parameter.project}_${item}`;
  67. if (parameter.taskId) {
  68. getTaskStarts(examId);
  69. } else {
  70. getExamStarts(examId);
  71. }
  72. getNetWork(examId, (e: any) => {
  73. if (!e.status) {
  74. callback({ cmd: 'disconnect_request', exam_id: examId, data: { message: '工作站未响应' } });
  75. }
  76. });
  77. }
  78. //分组延迟执行
  79. // let num = 10; //每次执行N个
  80. // let time = 200; //间隔时间毫秒
  81. // let group = Math.ceil(testList.length / num);
  82. // for (let i = 0; i < group; i++) {
  83. // for (let j = i * group; j < (i + 1) * num; j++) {
  84. // setTimeout(() => {
  85. // let item = testList[j];
  86. // let examId = `${parameter.project}_${item}`;
  87. // if (parameter.taskId) {
  88. // getTaskStarts(examId);
  89. // } else {
  90. // getExamStarts(examId);
  91. // }
  92. // getNetWork(examId, (e: any) => {
  93. // if (!e.status) {
  94. // callback({ cmd: 'disconnect_request', exam_id: examId, data: { message: '工作站未响应' } });
  95. // }
  96. // });
  97. // }, time * i);
  98. // }
  99. // }
  100. } else {
  101. //单WS单区
  102. let examId = parameter.examId;
  103. if (parameter.taskId) {
  104. getTaskStarts(examId);
  105. } else {
  106. getExamStarts(examId);
  107. }
  108. getNetWork(examId, (e: any) => {
  109. if (!e.status) {
  110. callback({ cmd: 'disconnect_request', exam_id: examId, data: { message: '工作站未响应' } });
  111. }
  112. });
  113. }
  114. });
  115. socket.on('msg2frontend', (e: any) => {
  116. callback(e);
  117. //实时状态
  118. if (e.cmd === 'exam_status') {
  119. let index = examStateList.findIndex((item: any) => {
  120. return item.examId == e.exam_id;
  121. });
  122. if (index != -1) {
  123. examStateList[index].examState = e.data;
  124. examStateList[index].beatNumber = examStateList[index].beatNumber + 1;
  125. examStateList[index].wsStatus = e.status;
  126. }
  127. }
  128. //工作站状态
  129. if (e.cmd === 'init_result') {
  130. if (e.status == 666) {
  131. let data = {
  132. wk_id: e.data.wk_id,
  133. examId: e.exam_id
  134. };
  135. wkList.push(data);
  136. }
  137. }
  138. //测试违规
  139. if (e.cmd === 'warning_result') {
  140. }
  141. //后端播报语音
  142. if (e.cmd === 'return_audio_msg') {
  143. }
  144. //错误提示
  145. if (e.cmd === 'info_result') {
  146. }
  147. //错误提示
  148. if (e.cmd === 'error_result') {
  149. }
  150. //测试中违规提示
  151. if (e.cmd === 'warning_notify') {
  152. if ([233].includes(e.status)) {
  153. callback({ cmd: 'disconnect_request', exam_id: e.exam_id, data: { message: e.data.message } });
  154. }
  155. }
  156. //断线状态
  157. if (e.cmd === 'disconnect_request') {
  158. if (loadingTime) {
  159. clearTimeout(loadingTime);
  160. }
  161. if (testList.length > 1) {
  162. //如果全部的WS状态都为213、220就退出
  163. let list = examStateList.filter((item: any) => {
  164. return [213, 220, 302].includes(item.wsStatus);
  165. });
  166. if (list.length == testList.length) {
  167. getExit();
  168. }
  169. }
  170. if (testList.length == 1) {
  171. getExit();
  172. //let examId = e?.exam_id || '';
  173. //getExit(examId);
  174. }
  175. }
  176. //状态变更
  177. if (e.cmd === 'set_exam_state') {
  178. let index = examStateList.findIndex((item: any) => {
  179. return item.examId == e.exam_id;
  180. });
  181. if (index != -1) {
  182. examStateList[index].examState = e.data;
  183. examStateList[index].wsStatus = e.status;
  184. }
  185. if (e.data == 3) {
  186. //关闭遮罩层
  187. loading?.close();
  188. clearTimeout(loadingTime);
  189. }
  190. }
  191. //新建测试后返回信息,获取result_id
  192. if (e.cmd === 'open_one_test_ack') {
  193. }
  194. //人脸识别状态
  195. if (e.cmd === 'face_check_result') {
  196. }
  197. //测试结束结果
  198. if (e.cmd === 'oneresult') {
  199. }
  200. //结果生成完成(视频图片)
  201. if (e.cmd === 'static_urls_finished') {
  202. }
  203. //选择学生或测试结束后返回的数据
  204. if (e.cmd === 'result_info') {
  205. }
  206. });
  207. socket.on('disconnect', (e: any) => {
  208. callback(e);
  209. getExit();
  210. });
  211. }
  212. /**
  213. * 发送命令
  214. * @param type发送类型:v2版是单WS多人的项目
  215. * msgfrom_frontend:测试中命令交互,
  216. * get_exam_status:心跳,
  217. * exam_ends:结束测试,
  218. * fe_reconnect:重连,
  219. * task_starts:课程开启测试,
  220. * exam_starts:开启测试,
  221. * join_exam_room:加入房间,
  222. * @param data发送数据:
  223. * msgfrom_frontend发送cmd的参:
  224. * open_one_test:创建测试将由3转40,
  225. * start_face_recognition:开始人脸识别将由40转41,
  226. * stop_face_recognition:停止人脸识别将由41转43,
  227. * face_confirm_only:人脸确认即将测试,
  228. * start_one_test:开始测试将由43转42,
  229. * finish_one_test:正在测试中结束并下一次将由42转6转3,
  230. * close_one_test:中断任何阶段将由42、43、41、40转3,
  231. * suspend_face_recognition_channels:工作站识别的短跑,某道识别到了人就停止某道的识别,
  232. * resume_face_recognition_channels:工作站识别的短跑,重新开启某一道的识别,
  233. * next_test:,
  234. * result_info:,
  235. * @param callback回调函数
  236. */
  237. function sendMessage(type: string, data: any, callback?: () => void) {
  238. if (socket?.connected) {
  239. callback = callback || function () {};
  240. //版本2就拼接进去
  241. if (version == 'v2') {
  242. type = type + '_' + version;
  243. }
  244. socket.emit(type, JSON.stringify(data), callback);
  245. }
  246. }
  247. /**
  248. * 课程连接成功
  249. */
  250. function getTaskStarts(data?: any) {
  251. let examId = data ? data : parameter.examId;
  252. sendMessage(
  253. 'task_starts',
  254. {
  255. data: 'start_' + examId,
  256. class_id: parameter.classes,
  257. exam_type: parameter.standard,
  258. task_cate: parameter.taskCate,
  259. gesture: parameter.gesture,
  260. demo: parameter.demo,
  261. test_time: testTime
  262. },
  263. () => {}
  264. );
  265. }
  266. /**
  267. * 连接成功
  268. */
  269. function getExamStarts(data?: any) {
  270. let examId = data ? data : parameter.examId;
  271. sendMessage(
  272. 'exam_starts',
  273. {
  274. data: 'start_' + examId,
  275. class_id: parameter.classes,
  276. exam_type: parameter.standard,
  277. gesture: parameter.gesture,
  278. demo: parameter.demo,
  279. test_time: testTime
  280. },
  281. () => {}
  282. );
  283. }
  284. /**
  285. * 创建测试
  286. */
  287. function openOneTest(data?: any) {
  288. return new Promise((resolve, reject) => {
  289. let examId = data ? data : parameter.examId;
  290. let index = examStateList.findIndex((item: any) => {
  291. return item.examId == examId;
  292. });
  293. sendMessage('msgfrom_frontend', {
  294. data: {
  295. cmd: 'open_one_test',
  296. exam_id: examId
  297. }
  298. });
  299. let timer1 = setInterval(() => {
  300. if (examStateList[index].examState == 40) {
  301. clearInterval(timer1);
  302. clearTimeout(timer2);
  303. resolve({ data: examStateList[index].examState });
  304. }
  305. }, 250);
  306. let timer2 = setTimeout(() => {
  307. if (examStateList[index].examState == 3) {
  308. clearInterval(timer1);
  309. clearTimeout(timer2);
  310. reject({ cmd: 'disconnect_request', exam_id: examId, data: { message: '超时:open_one_test' } });
  311. }
  312. }, 30000);
  313. });
  314. }
  315. /**
  316. * 开始人脸识别
  317. */
  318. function startFace(data?: any) {
  319. return new Promise((resolve, reject) => {
  320. let examId = data ? data : parameter.examId;
  321. let index = examStateList.findIndex((item: any) => {
  322. return item.examId == examId;
  323. });
  324. sendMessage('msgfrom_frontend', {
  325. data: {
  326. cmd: 'start_face_recognition',
  327. exam_id: examId
  328. }
  329. });
  330. let timer1 = setInterval(() => {
  331. if (examStateList[index].examState == 41) {
  332. clearInterval(timer1);
  333. clearTimeout(timer2);
  334. resolve({ data: examStateList[index].examState });
  335. }
  336. }, 250);
  337. let timer2 = setTimeout(() => {
  338. if (examStateList[index].examState == 40) {
  339. clearInterval(timer1);
  340. clearTimeout(timer2);
  341. reject({ cmd: 'disconnect_request', exam_id: examId, data: { message: '超时:start_face_recognition' } });
  342. }
  343. }, 30000);
  344. });
  345. }
  346. /**
  347. * 停止人脸识别
  348. */
  349. function stopFace(data?: any) {
  350. return new Promise((resolve, reject) => {
  351. let examId = data ? data : parameter.examId;
  352. let index = examStateList.findIndex((item: any) => {
  353. return item.examId == examId;
  354. });
  355. sendMessage('msgfrom_frontend', {
  356. data: {
  357. cmd: 'stop_face_recognition',
  358. exam_id: examId
  359. }
  360. });
  361. let timer1 = setInterval(() => {
  362. if (examStateList[index].examState == 43) {
  363. clearInterval(timer1);
  364. clearTimeout(timer2);
  365. resolve({ data: examStateList[index].examState });
  366. }
  367. }, 250);
  368. let timer2 = setTimeout(() => {
  369. if (examStateList[index].examState == 41) {
  370. clearInterval(timer1);
  371. clearTimeout(timer2);
  372. reject({ cmd: 'disconnect_request', exam_id: examId, data: { message: '超时:stop_face_recognition' } });
  373. }
  374. }, 30000);
  375. });
  376. }
  377. /**
  378. * 确认并提交人脸
  379. */
  380. function faceConfirmOnly(data: any, callback?: any) {
  381. let examId = data.exam_id ? data.exam_id : parameter.examId;
  382. let myData = null;
  383. if (Array.isArray(data)) {
  384. //数组类型
  385. myData = {
  386. cmd: 'face_confirm_only',
  387. data
  388. };
  389. } else {
  390. //对象类型
  391. myData = {
  392. cmd: 'face_confirm_only',
  393. exam_id: examId,
  394. result_id: data.result_id,
  395. student_id: data.student_id,
  396. gender: data.gender
  397. };
  398. }
  399. sendMessage(
  400. 'msgfrom_frontend',
  401. {
  402. data: myData
  403. },
  404. () => {
  405. callback();
  406. }
  407. );
  408. }
  409. /**
  410. * 开始测试
  411. */
  412. function startOneTest(data?: any, callback?: any) {
  413. let examId = data ? data : parameter.examId;
  414. sendMessage(
  415. 'msgfrom_frontend',
  416. {
  417. data: {
  418. cmd: 'start_one_test',
  419. exam_id: examId
  420. }
  421. },
  422. () => {
  423. callback();
  424. }
  425. );
  426. }
  427. /**
  428. * 停止测试
  429. */
  430. function finishOneTest(data?: any) {
  431. return new Promise((resolve, reject) => {
  432. let examId = data ? data : parameter.examId;
  433. let index = examStateList.findIndex((item: any) => {
  434. return item.examId == examId;
  435. });
  436. sendMessage('msgfrom_frontend', {
  437. data: {
  438. cmd: 'finish_one_test',
  439. exam_id: examId
  440. }
  441. });
  442. let timer1 = setInterval(() => {
  443. if (examStateList[index].examState == 3) {
  444. clearInterval(timer1);
  445. clearTimeout(timer2);
  446. resolve({ data: examStateList[index].examState });
  447. }
  448. }, 250);
  449. let timer2 = setTimeout(() => {
  450. if (examStateList[index].examState == 42) {
  451. clearInterval(timer1);
  452. clearTimeout(timer2);
  453. reject({ cmd: 'disconnect_request', exam_id: examId, data: { message: '超时:finish_one_test' } });
  454. }
  455. }, 60000);
  456. });
  457. }
  458. /**
  459. * 关闭测试测试
  460. */
  461. function closeOneTest(data?: any) {
  462. return new Promise((resolve, reject) => {
  463. let examId = data ? data : parameter.examId;
  464. let index = examStateList.findIndex((item: any) => {
  465. return item.examId == examId;
  466. });
  467. sendMessage('msgfrom_frontend', {
  468. data: {
  469. cmd: 'close_one_test',
  470. exam_id: examId
  471. }
  472. });
  473. let timer1 = setInterval(() => {
  474. if (examStateList[index].examState == 3) {
  475. clearInterval(timer1);
  476. clearTimeout(timer2);
  477. resolve({ data: examStateList[index].examState });
  478. }
  479. }, 250);
  480. let timer2 = setTimeout(() => {
  481. if (examStateList[index].examState != 3) {
  482. clearInterval(timer1);
  483. clearTimeout(timer2);
  484. reject({ cmd: 'disconnect_request', exam_id: examId, data: { message: '超时:close_one_test' } });
  485. }
  486. }, 30000);
  487. });
  488. }
  489. /**
  490. * 某道识别到了人就停止某道的识别
  491. */
  492. function suspendFaceRecognitionChannels(data: any) {
  493. let track = data;
  494. sendMessage('msgfrom_frontend', {
  495. data: {
  496. cmd: 'suspend_face_recognition_channels',
  497. track: track
  498. }
  499. });
  500. }
  501. /**
  502. * 某道重新开始识别
  503. */
  504. function resumeFaceRecognitionChannels(data: any) {
  505. let track = data;
  506. sendMessage('msgfrom_frontend', {
  507. data: {
  508. cmd: 'resume_face_recognition_channels',
  509. track: track
  510. }
  511. });
  512. }
  513. /**
  514. * 心跳
  515. */
  516. function getNetWork(data: any, callback?: any) {
  517. timerManager[data] = setInterval(() => {
  518. let obj = wkList.find((item: any) => {
  519. return item.examId == data;
  520. });
  521. if (obj == undefined) {
  522. return false;
  523. }
  524. let wk_id = obj.wk_id;
  525. let examId = data ? data : parameter.examId;
  526. sendMessage(
  527. 'get_exam_status',
  528. {
  529. exam_id: examId,
  530. wk_id: wk_id,
  531. school_id: userInfo.school_id || null
  532. },
  533. () => {
  534. //如果心跳停止了就退出去
  535. let index = examStateList.findIndex((item: any) => {
  536. return item.examId == examId;
  537. });
  538. if (index == -1) {
  539. return false;
  540. }
  541. let beforBeatNumber = JSON.parse(JSON.stringify(examStateList[index].beatNumber));
  542. setTimeout(() => {
  543. //5秒后验证是否有变
  544. if (beforBeatNumber == examStateList[index].beatNumber) {
  545. //异常
  546. callback({ status: false });
  547. } else {
  548. //正常
  549. callback({ status: true });
  550. }
  551. }, 5000);
  552. }
  553. );
  554. }, beatTime);
  555. }
  556. /**
  557. * 关闭项目
  558. */
  559. function examEnds() {
  560. getExit();
  561. }
  562. /**
  563. * 退出
  564. */
  565. function getExit(data?: any) {
  566. //关闭遮罩层
  567. loading?.close();
  568. //通知工作站关闭
  569. if (testList.length > 1 && !data) {
  570. //单WS多区
  571. examStateList.forEach((item: any) => {
  572. let examId = item.examId;
  573. sendMessage('exam_ends', {
  574. data: 'end_' + examId,
  575. class_id: parameter.classes
  576. });
  577. });
  578. //清除计时器
  579. getClearTimer();
  580. //如果正在连接就关闭
  581. if (socket?.connected) {
  582. socket?.close();
  583. }
  584. } else {
  585. //单WS单区
  586. let examId = data ? data : parameter.examId;
  587. sendMessage('exam_ends', {
  588. data: 'end_' + examId,
  589. class_id: parameter.classes
  590. });
  591. if (!data) {
  592. //清除计时器
  593. getClearTimer();
  594. //如果正在连接就关闭
  595. if (socket?.connected) {
  596. socket?.close();
  597. }
  598. }
  599. }
  600. }
  601. /**
  602. * 清空定时任务
  603. */
  604. function getClearTimer() {
  605. for (let key in timerManager) {
  606. if (timerManager.hasOwnProperty(key)) {
  607. clearInterval(timerManager[key]);
  608. timerManager[key] = null;
  609. }
  610. }
  611. }
  612. /**
  613. * 控制延时
  614. */
  615. function delay(ms: number): Promise<void> {
  616. return new Promise((resolve) => setTimeout(resolve, ms));
  617. }
  618. onBeforeUnmount(() => {
  619. getClearTimer();
  620. if (socket) {
  621. socket.close();
  622. socket = null;
  623. }
  624. });
  625. return {
  626. initWs,
  627. sendMessage,
  628. openOneTest,
  629. startFace,
  630. stopFace,
  631. faceConfirmOnly,
  632. startOneTest,
  633. finishOneTest,
  634. closeOneTest,
  635. suspendFaceRecognitionChannels,
  636. resumeFaceRecognitionChannels,
  637. getNetWork,
  638. examEnds
  639. };
  640. }