ws.ts 15 KB

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