FaceId.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. <template>
  2. <div class="row" style="margin: 0;">
  3. <div class="col-md-12 text-center" style="padding:8px;">
  4. <div style="font-size: 30px;">
  5. <span>人脸检测</span> <span v-if="showIframe">({{ timeCount }})</span>
  6. </div>
  7. <div
  8. v-if="showIframe"
  9. class="text-center"
  10. style="color: red; font-size: 16px;"
  11. >
  12. (注意:请点击下方“开始比对”按钮并在60秒内完成人脸检测,超时将退出考试)
  13. </div>
  14. </div>
  15. <div
  16. id="faceIdDiv"
  17. style="position: relative;
  18. height:710px; background-color: #6e6f72 !important;
  19. background-image: radial-gradient(circle at 50% 0,#a9a9a9,#34363c);"
  20. >
  21. <div
  22. v-show="!showIframe"
  23. width="100%"
  24. height="200px"
  25. style="text-align: center;line-height:100px;margin-top:5px;"
  26. >
  27. <div style="color:white;font-weight: bold;font-size:20px;">
  28. {{ redoBtnMsg }}
  29. </div>
  30. <button
  31. v-if="redoBtnShow"
  32. type="button"
  33. class="qm-primary-button"
  34. :disabled="redoBtnDisabled"
  35. @click="startFaceVerify"
  36. >
  37. 重试
  38. </button>
  39. </div>
  40. <webview
  41. id="myFrame"
  42. v-show="showIframe"
  43. :preload="electronDir + 'manipulateFaceID.js'"
  44. style="position:absolute;width:100%;height:710px;"
  45. ></webview>
  46. <!-- <iframe
  47. src="https://www.baidu.com"
  48. preload="./manipulateFaceID.js"
  49. allow="camera *"
  50. src="https://www.baidu.com"
  51. allowusermedia
  52. id="myFrame"
  53. scrolling="no"
  54. width="100%"
  55. height="710px"
  56. frameborder="0"
  57. ></iframe> -->
  58. <!-- <iframe
  59. v-show="showIframe"
  60. allow="camera *"
  61. allowusermedia
  62. id="myFrame"
  63. scrolling="no"
  64. width="100%"
  65. height="710px"
  66. frameborder="0"
  67. ></iframe> -->
  68. </div>
  69. </div>
  70. </template>
  71. <script>
  72. import {
  73. FACEID_LINENESS_URL,
  74. VUE_APP_WK_SERVER_SOCKET,
  75. } from "@/constants/constants.js";
  76. export default {
  77. name: "FaceId",
  78. data() {
  79. return {
  80. showIframe: false,
  81. redoBtnShow: false,
  82. timeCount: 60,
  83. redoBtnMsg: "",
  84. electronDir: "",
  85. };
  86. },
  87. mounted() {
  88. console.debug("startFaceVerify");
  89. window._hmt.push(["_trackEvent", "活体检测弹出框", "弹出框"]);
  90. this.startFaceVerify();
  91. if (typeof nodeRequire != "undefined") {
  92. // console.log(nodeRequire("electron").remote.app);
  93. const app = window.nodeRequire("electron").remote.app;
  94. this.electronDir = "file://" + app.getAppPath() + "/";
  95. console.log(this.electronDir);
  96. }
  97. },
  98. beforeDestroy() {
  99. try {
  100. if (this.ws && this.ws.readyState === 1) this.ws.close();
  101. } catch (e) {
  102. console.log("关闭ws异常。");
  103. }
  104. clearTimeout(this.faceIdTime);
  105. clearInterval(this.timeCountInterval);
  106. clearTimeout(this.iframeDomReadyTimeout);
  107. },
  108. methods: {
  109. showRedo(redoMsg) {
  110. this.showIframe = false;
  111. this.redoBtnDisabled = false;
  112. this.redoBtnShow = true;
  113. if (redoMsg) {
  114. this.redoBtnMsg = redoMsg;
  115. } else {
  116. this.redoBtnMsg = "系统繁忙,请手动点击重试";
  117. }
  118. },
  119. updateFaceVerify(errorMsg, redoMsg) {
  120. this.showRedo(redoMsg);
  121. this.$http.get(
  122. "/api/ecs_oe_student/examFaceLivenessVerify/updateFaceLivenessVerify/" +
  123. this.$route.params.examRecordDataId,
  124. { params: { errorMsg: errorMsg } }
  125. );
  126. },
  127. // checkIframeOnload() {
  128. // var iframe = document.getElementById("myFrame");
  129. // if (!iframe) {
  130. // return null;
  131. // }
  132. // var app = iframe.contentWindow.document.getElementById("app");
  133. // if (app) {
  134. // return "success";
  135. // } else {
  136. // var preLabel = iframe.contentWindow.document.getElementsByTagName(
  137. // "pre"
  138. // )[0];
  139. // if (
  140. // preLabel &&
  141. // preLabel.innerText &&
  142. // preLabel.innerText.indexOf("error_message") > -1
  143. // ) {
  144. // return preLabel.innerText;
  145. // }
  146. // }
  147. // return null;
  148. // },
  149. iframeLoadSuccess() {
  150. window._hmt.push([
  151. "_trackEvent",
  152. "活体检测弹出框",
  153. "FaceID页面iframe加载成功",
  154. ]);
  155. // try {
  156. // var iframe = document.getElementById("myFrame");
  157. // var app = iframe.contentWindow.document.getElementById("app");
  158. // app.querySelector(".footer").remove();
  159. // } catch (err) {
  160. // console.error(err);
  161. // }
  162. const examRecordId = this.$route.params.examRecordDataId;
  163. this.timeCount = 60; //人脸检测倒计时60秒
  164. this.timeCountInterval = setInterval(() => {
  165. --this.timeCount;
  166. if (this.timeCount === 0) {
  167. clearInterval(this.timeCountInterval);
  168. }
  169. }, 1000);
  170. //定时事件,如果1分钟内未完成人脸检测,执行内部程序
  171. this.faceIdTime = setTimeout(() => {
  172. this.ws.close();
  173. this.$http
  174. .get(
  175. "/api/ecs_oe_student/examFaceLivenessVerify/faceLivenessVerifyTimeOut/" +
  176. examRecordId
  177. )
  178. .then(response => {
  179. if (response.status == 200) {
  180. var receivedMsg = response.data;
  181. this.faceTestEnd(receivedMsg);
  182. }
  183. })
  184. .finally(() => {
  185. // Chrome 63开始支持。但是vue-cli引入了p-finally,所以已经加在Promise对象中了
  186. clearInterval(this.timeCountInterval);
  187. if (!this.faceTestEndCalled) {
  188. this.$Message.error({
  189. content: "人脸检测超时,系统退出,请重新登录",
  190. duration: 30,
  191. closable: true,
  192. });
  193. }
  194. this.logout(
  195. "?LogoutReason=" +
  196. (!this.faceTestEndCalled
  197. ? "活体检测超时-可续考"
  198. : "活体检测超时")
  199. );
  200. });
  201. }, 60000); //60000
  202. /**
  203. * 人脸检测结果返回后台处理
  204. */
  205. this.faceTestEndHandle = result => {
  206. this.$http
  207. .get(
  208. "/api/ecs_oe_student/examFaceLivenessVerify/faceLivenessVerifyEnd/" +
  209. examRecordId +
  210. "?result=" +
  211. result
  212. )
  213. .then(() => {
  214. if (result != "SUCCESS") {
  215. this.logout("?LogoutReason=活体检测失败");
  216. window._hmt.push([
  217. "_trackEvent",
  218. "活体检测弹出框",
  219. "活体检测失败",
  220. ]);
  221. } else {
  222. window._hmt.push([
  223. "_trackEvent",
  224. "活体检测弹出框",
  225. "活体检测成功",
  226. ]);
  227. }
  228. })
  229. .catch(() => {
  230. this.$Message.error({
  231. content: "上传人脸检测结果出错!",
  232. duration: 15,
  233. closable: true,
  234. });
  235. this.logout("?LogoutReason=上传人脸检测结果出错!");
  236. });
  237. };
  238. /**
  239. * 人脸检测结束
  240. */
  241. this.faceTestEnd = receivedMsg => {
  242. this.faceTestEndCalled = true;
  243. if (receivedMsg.verifyCount == 1) {
  244. if (receivedMsg.verifyResult == "TIME_OUT") {
  245. this.$Message.error({
  246. content: "第一次人脸检测超时,检测失败,系统退出,请重新登录",
  247. duration: 30,
  248. closable: true,
  249. });
  250. this.logout(
  251. "?LogoutReason=第一次活体检测超时,检测失败,系统退出,请重新登录"
  252. );
  253. } else if (receivedMsg.verifyResult == "VERIFY_FAILED") {
  254. this.$Message.error({
  255. content: "第一次人脸检测失败,系统退出,请重新登录",
  256. duration: 30,
  257. closable: true,
  258. });
  259. this.logout(
  260. "?LogoutReason=第一次活体检测失败,系统退出,请重新登录"
  261. );
  262. } else if (receivedMsg.verifyResult == "NOT_ONESELF") {
  263. this.$Message.error({
  264. content: "人脸检测不合格,结束考试",
  265. duration: 30,
  266. closable: true,
  267. });
  268. this.faceTestEndHandle("FAILED");
  269. } else if (receivedMsg.verifyResult == "VERIFY_SUCCESS") {
  270. this.$Message.info({
  271. content: "人脸检测成功,请继续完成考试",
  272. duration: 15,
  273. closable: true,
  274. });
  275. this.faceTestEndHandle("SUCCESS");
  276. }
  277. } else if (receivedMsg.verifyCount >= 2) {
  278. if (receivedMsg.verifyResult == "VERIFY_SUCCESS") {
  279. this.$Message.info({
  280. content: "人脸检测成功,请继续完成考试",
  281. duration: 15,
  282. closable: true,
  283. });
  284. this.faceTestEndHandle("SUCCESS");
  285. } else {
  286. this.$Message.error({
  287. content: "人脸检测不合格,结束考试",
  288. duration: 30,
  289. closable: true,
  290. });
  291. this.faceTestEndHandle("FAILED");
  292. }
  293. }
  294. };
  295. // open websocket
  296. this.ws = new WebSocket(VUE_APP_WK_SERVER_SOCKET + examRecordId);
  297. this.ws.onopen = function() {
  298. console.log("websocket已连接");
  299. };
  300. this.ws.onmessage = response => {
  301. if (response.data.indexOf("verifyResult") > -1) {
  302. var receivedMsg = JSON.parse(response.data);
  303. this.faceTestEnd(receivedMsg);
  304. clearTimeout(this.faceIdTime);
  305. clearInterval(this.timeCountInterval);
  306. this.$emit("closeFaceId");
  307. this.ws.close();
  308. }
  309. };
  310. // 重复关闭不会报错
  311. this.ws.onclose = function() {
  312. console.log("websocket连接已关闭...");
  313. };
  314. },
  315. async startFaceVerify() {
  316. this.redoBtnDisabled = true;
  317. this.redoBtnMsg = "正在进入人脸检测...";
  318. const examRecordId = this.$route.params.examRecordDataId;
  319. let response = null;
  320. try {
  321. response = await this.$http.get(
  322. "/api/ecs_oe_student/examFaceLivenessVerify/getFaceLivenessVerifyToken/" +
  323. examRecordId
  324. );
  325. } catch (error) {
  326. this.$Message.error({
  327. content: "获取底照token失败,请重新登录!",
  328. duration: 15,
  329. closable: true,
  330. });
  331. this.logout("?LogoutReason=获取底照token失败,请重新登录!");
  332. return;
  333. }
  334. if (!response.data.success) {
  335. console.log(response.data.errorMsg);
  336. this.$Message.error({
  337. content: "您上传的底照不适合做活体检测,请联系老师!",
  338. duration: 15,
  339. closable: true,
  340. });
  341. this.logout("?LogoutReason=您上传的底照不适合做活体检测,请联系老师!");
  342. return;
  343. }
  344. this.showIframe = true;
  345. var iframe = document.getElementById("myFrame");
  346. try {
  347. iframe.src = FACEID_LINENESS_URL + response.data.faceLivenessToken;
  348. } catch (err) {
  349. console.error(err);
  350. }
  351. // var index = 0;
  352. // var iframeLoadTime = setInterval(() => {
  353. // var iframeLoadMsg = this.checkIframeOnload();
  354. // if (!iframeLoadMsg) {
  355. // index++;
  356. // if (index == 20) {
  357. // //检测达到20次
  358. // clearInterval(iframeLoadTime);
  359. // this.showRedo("网络异常,请手动点击重试");
  360. // window._hmt.push(["_trackEvent", "活体检测弹出框", "网络异常"]);
  361. // }
  362. // } else if (iframeLoadMsg.indexOf("error_message") > -1) {
  363. // clearInterval(iframeLoadTime);
  364. // this.updateFaceVerify(iframeLoadMsg, null);
  365. // } else if (iframeLoadMsg == "success") {
  366. // clearInterval(iframeLoadTime);
  367. // // this.iframeLoadSuccess();
  368. // setTimeout(() => {
  369. // this.iframeLoadSuccess();
  370. // }, 300); // 延迟确保能删除footer
  371. // }
  372. // }, 500);
  373. {
  374. // iframe 状态管理
  375. let iframeDomReady = false;
  376. iframe.addEventListener("did-start-loading", () => {
  377. console.log("loading faceid iframe");
  378. });
  379. iframe.addEventListener("did-fail-load", () => {
  380. console.log("failed loading faceid iframe");
  381. });
  382. iframe.addEventListener("dom-ready", () => {
  383. iframeDomReady = true;
  384. console.log("faceid iframe dom ready");
  385. // iframe.openDevTools();
  386. });
  387. iframe.addEventListener("ipc-message", event => {
  388. // console.log(event.channel);
  389. clearInterval(this.iframeDomReadyTimeout);
  390. const iframeLoadMsg = event.channel;
  391. if (iframeLoadMsg.indexOf("error_message") > -1) {
  392. this.updateFaceVerify(iframeLoadMsg, null);
  393. }
  394. if (iframeLoadMsg === "success") {
  395. this.iframeLoadSuccess();
  396. iframe.send("faceid-footer", ".copyright");
  397. }
  398. });
  399. // 网络异常,最后的管理
  400. this.iframeDomReadyTimeout = setTimeout(() => {
  401. if (iframeDomReady === false) {
  402. clearTimeout(this.iframeDomReadyTimeout);
  403. this.showRedo("网络异常,请手动点击重试");
  404. window._hmt.push(["_trackEvent", "活体检测弹出框", "网络异常"]);
  405. }
  406. }, 60 * 1000);
  407. }
  408. },
  409. },
  410. };
  411. </script>