123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513 |
- <script setup lang="ts">
- import {
- FACEID_LINENESS_URL,
- WEBSOCKET_FOR_FACE_ID,
- } from "@/constants/constants";
- import { httpApp } from "@/plugins/axiosApp";
- import { useTimers } from "@/setups/useTimers";
- import { useWebSocket } from "@/setups/useWebSocket";
- import { store } from "@/store/store";
- import { showLogout } from "@/utils/utils";
- import { onUnmounted } from "vue";
- const emit = defineEmits<{ (e: "close-faceid"): void }>();
- const { addTimeout, addInterval } = useTimers();
- let electronDir = "";
- if (typeof window.nodeRequire != "undefined") {
- const remote: import("electron").Remote =
- window.nodeRequire("electron").remote;
- electronDir = "file://" + remote.app.getAppPath() + "/";
- logger({ cnl: ["server"], pgn: "活体检测弹出框", ext: { electronDir } });
- }
- logger({ cnl: ["server", "local"], pgn: "活体检测弹出框", act: "弹出框" });
- const { startWS } = useWebSocket();
- startWS(
- WEBSOCKET_FOR_FACE_ID + `?key=${store.user.key}&token=${store.user.token}`,
- onFaceIdMessage,
- "活体检测socket"
- );
- // 退出faceid的办法只能二选一。下面是socket返回结果退出。
- let processedBySocketMsg = $ref(false);
- function onFaceIdMessage(event: MessageEvent<string>) {
- logger({ cnl: ["server"], act: "活体检测FaceId response", dtl: event.data });
- if (processedByTimeout) {
- logger({ cnl: ["server"], act: "活体检测websocket消息来迟了,拒绝处理" });
- return;
- }
- if (event.data.indexOf("verifyResult") > -1) {
- logger({
- cnl: ["server"],
- pgn: "活体检测弹出框",
- dtl: "websocket得到verifyResult消息",
- });
- processedBySocketMsg = true;
- try {
- const receivedMsgData: { content: { returnMsgJson: string } } =
- JSON.parse(event.data);
- const receivedMsg: FaceIDMessage = JSON.parse(
- receivedMsgData.content.returnMsgJson
- );
- // 两个结束点。第二个结束点:从websocket得到消息。
- void faceTestEnd(receivedMsg);
- } catch (error) {
- logger({
- cnl: ["server"],
- pgn: "活体检测弹出框",
- act: "处理websocket消息时",
- possibleError: error,
- });
- }
- emit("close-faceid");
- }
- }
- store.exam.isDoingFaceLiveness = true;
- onUnmounted(() => {
- logger({ cnl: ["server"], pgn: "关闭活体检测弹出框" });
- clearTimeout(iframeDomReadyTimeout);
- store.exam.isDoingFaceLiveness = false;
- });
- let showIframe = $ref(false);
- let redoBtnDisabled = $ref(true);
- let redoBtnShow = $ref(false);
- let redoBtnMsg = $ref("");
- /** 设置重做按钮的文本 */
- function showRedo(redoMsg: string) {
- showIframe = false;
- redoBtnDisabled = false;
- redoBtnShow = true;
- redoBtnMsg = redoMsg || "系统繁忙,请手动点击重试";
- logger({ cnl: ["server"], pgn: "活体检测弹出框", act: redoBtnMsg });
- }
- async function updateFaceVerifyMsg(errorMsg: string) {
- logger({
- cnl: ["server"],
- pgn: "活体检测弹出框",
- act: "faceid页面报错",
- dtl: errorMsg,
- });
- await httpApp.get(
- "/api/ecs_oe_student/examFaceLivenessVerify/updateFaceLivenessVerify/" +
- store.exam.examRecordDataId,
- { params: { errorMsg: errorMsg } }
- );
- }
- // 退出faceid的办法只能二选一。下面是timeout退出。
- let processedByTimeout = false;
- async function faceidLoadedButTimeouted() {
- if (processedBySocketMsg) {
- logger({
- cnl: ["server"],
- act: "faceidLoadedButTimeouted",
- dtl: "已被websocket处理了",
- });
- return;
- }
- processedByTimeout = true;
- logger({
- cnl: ["server"],
- act: "faceidLoadedButTimeouted",
- dtl: "进入超时处理",
- });
- await httpApp
- .get(
- "/api/ecs_oe_student/examFaceLivenessVerify/getFaceVerifyResult/" +
- faceVerifyId,
- { "axios-retry": { retries: 4 } }
- )
- .then((response) => {
- logger({
- cnl: ["server"],
- pgn: "活体检测弹出框",
- act: "60秒超时非websocket处理结果",
- });
- const receivedMsg: FaceIDMessage = response.data;
- void faceTestEnd(receivedMsg);
- emit("close-faceid");
- })
- .catch((error) => {
- logger({
- cnl: ["server"],
- pgn: "活体检测弹出框",
- act: "60秒超时非websocket处理结果--api失败",
- possibleError: error,
- });
- });
- }
- type FaceIDMessage = {
- verifyCount: number;
- verifyResult: string;
- };
- /** 指定动作检测结束 */
- async function faceTestEnd(receivedMsg: FaceIDMessage) {
- logger({
- cnl: ["server"],
- pgn: "活体检测弹出框",
- act: "指定动作检测结束",
- ext: {
- verifyCount: receivedMsg.verifyCount,
- verifyResult: receivedMsg.verifyResult,
- },
- });
- if (receivedMsg.verifyCount == 1) {
- if (receivedMsg.verifyResult == "TIME_OUT") {
- if (store.QECSConfig.FACE_VERIFY_FORCE_EXIT === "false") {
- logger({
- cnl: ["server"],
- act: "第一次指定动作检测超时,但是因为配置不强制,于是允许继续考试",
- });
- return;
- }
- logger({
- cnl: ["server"],
- act: "第一次指定动作检测超时,检测失败,系统退出,请重新登录",
- });
- showLogout("第一次指定动作检测超时,检测失败,系统退出,请重新登录");
- } else if (receivedMsg.verifyResult == "VERIFY_FAILED") {
- if (store.QECSConfig.FACE_VERIFY_FORCE_EXIT === "false") {
- logger({
- cnl: ["server"],
- act: "第一次指定动作检测失败,但是因为配置不强制,于是允许继续考试",
- });
- return;
- }
- logger({
- cnl: ["server"],
- act: "第一次指定动作检测失败,系统退出,请重新登录",
- });
- showLogout("第一次指定动作检测失败,系统退出,请重新登录");
- } else if (receivedMsg.verifyResult == "NOT_ONESELF") {
- if (store.QECSConfig.FACE_VERIFY_FORCE_EXIT === "false") {
- logger({
- cnl: ["server"],
- act: "指定动作检测不合格,但是因为配置不强制,于是允许继续考试",
- });
- return;
- }
- logger({ cnl: ["server"], act: "指定动作检测不合格,结束考试" });
- $message.error("指定动作检测不合格,结束考试");
- return faceTestUploadResult("FAILED");
- } else if (receivedMsg.verifyResult == "VERIFY_SUCCESS") {
- logger({ cnl: ["server"], act: "指定动作检测成功,请继续完成考试" });
- $message.info("指定动作检测成功,请继续完成考试");
- return faceTestUploadResult("SUCCESS");
- } else if (receivedMsg.verifyResult == "UNKNOWN") {
- if (store.QECSConfig.FACE_VERIFY_FORCE_EXIT === "false") {
- logger({
- cnl: ["server"],
- act: "第一次指定动作检测异常(fid),但是因为配置不强制,于是允许继续考试",
- });
- return;
- }
- showLogout("第一次指定动作检测异常(fid),系统退出,请重新登录");
- }
- } else if (receivedMsg.verifyCount >= 2) {
- if (receivedMsg.verifyResult == "VERIFY_SUCCESS") {
- logger({ cnl: ["server"], act: "指定动作检测成功,请继续完成考试" });
- $message.info("指定动作检测成功,请继续完成考试");
- return faceTestUploadResult("SUCCESS");
- } else {
- if (store.QECSConfig.FACE_VERIFY_FORCE_EXIT === "false") {
- logger({
- cnl: ["server"],
- act: "指定动作检测不合格,但是因为配置不强制,于是允许继续考试",
- });
- return;
- }
- logger({ cnl: ["server"], act: "指定动作检测不合格,结束考试" });
- $message.error("指定动作检测不合格,结束考试");
- return faceTestUploadResult("FAILED");
- }
- }
- }
- /** 指定动作检测结果返回后台处理 */
- async function faceTestUploadResult(result: string) {
- logger({
- cnl: ["server"],
- pgn: "活体检测弹出框",
- act: "上传活体检测结果",
- ext: { result },
- });
- return httpApp
- .get(
- "/api/ecs_oe_student/examFaceLivenessVerify/faceLivenessVerifyEnd/" +
- store.exam.examRecordDataId +
- "?result=" +
- result,
- { "axios-retry": { retries: 4 } }
- )
- .then(() => {
- if (result != "SUCCESS") {
- logger({
- cnl: ["server"],
- pgn: "活体检测弹出框",
- act: "活体检测失败-退出登录",
- });
- showLogout("活体检测失败");
- } else {
- logger({ cnl: ["server"], pgn: "活体检测弹出框", act: "活体检测成功" });
- }
- })
- .catch(() => {
- logger({
- cnl: ["server"],
- pgn: "活体检测弹出框",
- act: "上传指定动作检测结果出错!--退出登录",
- });
- showLogout("上传指定动作检测结果出错!");
- });
- }
- let timeCount = $ref(60); //指定动作检测倒计时60秒
- function iframeLoadSuccess() {
- logger({
- cnl: ["server"],
- pgn: "活体检测弹出框",
- act: "FaceID页面iframe加载成功",
- });
- // @ts-expect-error
- if (!iframeLoadSuccess.loaded) {
- // @ts-expect-error
- iframeLoadSuccess.loaded = true;
- } else {
- logger({
- cnl: ["server"],
- key: "不可能的事情发生了",
- dtl: "iframeLoadSuccess.loaded",
- });
- return;
- }
- const timeCountInterval = addInterval(() => {
- timeCount--;
- if (timeCount === 0) {
- clearInterval(timeCountInterval);
- }
- }, 1000);
- // 两个结束点。第一个结束点:超时。先传后台,再根据后台信息进行处理。可能ws没有收到处理结果,会通过http接收一遍。
- //定时事件,如果1分钟内未完成指定动作检测,执行内部程序
- addTimeout(faceidLoadedButTimeouted, 60 * 1000); //60000
- }
- async function startFaceVerifyClicked() {
- logger({
- cnl: ["server"],
- pgn: "活体检测弹出框",
- act: "点击重试按钮",
- });
- await startFaceVerify();
- }
- let faceVerifyId: string | null = null;
- let iframeDomReadyTimeout = -1;
- async function startFaceVerify() {
- redoBtnDisabled = true;
- redoBtnMsg = "正在进入指定动作检测...";
- let response = null;
- if (faceVerifyId) {
- try {
- response = await httpApp.get(
- "/api/ecs_oe_student/examFaceLivenessVerify/getFaceVerifyToken/" +
- faceVerifyId,
- { "axios-retry": { retries: 4 } }
- );
- } catch (error) {
- showRedo("网络异常,请手动点击重试");
- logger({
- cnl: ["server"],
- pgn: "活体检测弹出框",
- act: "网络异常-getFaceVerifyToken",
- possibleError: error,
- });
- return;
- }
- } else {
- try {
- response = await httpApp.get(
- "/api/ecs_oe_student/examFaceLivenessVerify/startFaceVerify/" +
- store.exam.examRecordDataId,
- { "axios-retry": { retries: 4 } }
- );
- faceVerifyId = response.data.faceVerifyId;
- } catch (error) {
- logger({
- cnl: ["server"],
- pgn: "活体检测弹出框",
- act: "获取底照token失败,请重新登录!",
- possibleError: error,
- });
- showLogout("获取底照token失败,请重新登录!");
- return;
- }
- }
- if (!response.data.success) {
- logger({
- cnl: ["server"],
- pgn: "活体检测弹出框",
- act: "您上传的底照不适合做活体检测,请联系老师!",
- dtl: JSON.stringify(response.data),
- });
- showLogout("您上传的底照不适合做活体检测,请联系老师!");
- return;
- }
- showIframe = true;
- const iframe = <HTMLIFrameElement>document.getElementById("myFrame");
- try {
- iframe.src = FACEID_LINENESS_URL + response.data.faceLivenessToken;
- } catch (err) {
- logger({
- cnl: ["server"],
- pgn: "活体检测弹出框",
- act: "set iframe.src",
- key: "不可能的事情发生了",
- possibleError: err,
- });
- showLogout("网络错误,请重试!");
- }
- if (!iframe) return;
- {
- // iframe 状态管理
- clearTimeout(iframeDomReadyTimeout);
- // 网络异常,最后的管理
- iframeDomReadyTimeout = window.setTimeout(() => {
- if (iframeDomReady === false) {
- clearTimeout(iframeDomReadyTimeout);
- showRedo("网络异常,请手动点击重试");
- logger({ cnl: ["server"], pgn: "活体检测弹出框", act: "网络异常" });
- }
- }, 30 * 1000);
- let iframeDomReady = false;
- iframe.addEventListener("did-start-loading", () => {
- logger({
- cnl: ["server", "local", "console"],
- pgn: "活体检测弹出框",
- act: "loading faceid iframe",
- });
- console.log(iframe);
- });
- iframe.addEventListener("did-fail-load", () => {
- logger({
- cnl: ["server", "local", "console"],
- pgn: "活体检测弹出框",
- act: "failed loading faceid iframe",
- });
- clearTimeout(iframeDomReadyTimeout);
- if (iframe.src.includes(FACEID_LINENESS_URL)) {
- showRedo("网络异常,请手动点击重试");
- logger({
- cnl: ["server"],
- pgn: "活体检测弹出框",
- act: "网络异常-加载失败",
- });
- }
- });
- iframe.addEventListener("dom-ready", () => {
- iframeDomReady = true;
- logger({ cnl: ["server", "console"], act: "faceid iframe dom ready" });
- // TODO: need verify
- // iframe.insertCSS(".copyright { display: none !important;}");
- // iframe.openDevTools();
- });
- iframe.addEventListener("ipc-message", (event: any) => {
- logger({
- cnl: ["server", "console"],
- act: "got ipc-message",
- dtl: event.channel,
- });
- clearTimeout(iframeDomReadyTimeout);
- const iframeLoadMsg: string = event.channel;
- if (iframeLoadMsg.indexOf("error_message") > -1) {
- showRedo("");
- void updateFaceVerifyMsg(iframeLoadMsg);
- }
- if (iframeLoadMsg === "success") {
- iframeLoadSuccess();
- }
- });
- }
- }
- // 页面渲染后自动进入流程
- void startFaceVerify();
- </script>
- <template>
- <n-modal
- :show="true"
- :closable="false"
- :maskClosable="false"
- preset="card"
- style="width: 800px"
- >
- <div class="col-md-12 text-center" style="padding: 8px">
- <div style="font-size: 30px">
- <span>指定动作检测</span>
- <span v-if="showIframe">({{ timeCount }})</span>
- </div>
- <div
- v-if="showIframe"
- class="text-center"
- style="color: red; font-size: 16px"
- >
- (注意:请点击下方“开始比对”按钮并在60秒内完成指定动作检测,超时将退出考试)
- </div>
- </div>
- <div
- id="faceIdDiv"
- style="
- position: relative;
- height: 710px;
- background-color: #6e6f72 !important;
- background-image: radial-gradient(circle at 50% 0, #a9a9a9, #34363c);
- "
- >
- <div
- v-show="!showIframe"
- width="100%"
- height="200px"
- style="text-align: center; line-height: 100px; margin-top: 5px"
- >
- <div style="color: white; font-weight: bold; font-size: 20px">
- {{ redoBtnMsg }}
- </div>
- <n-button
- v-if="redoBtnShow"
- size="large"
- type="info"
- :disabled="redoBtnDisabled"
- @click="startFaceVerifyClicked"
- >
- 重试
- </n-button>
- </div>
- <webview
- v-show="showIframe"
- id="myFrame"
- :preload="electronDir + 'manipulateFaceID.js'"
- style="position: absolute; width: 100%; height: 710px"
- />
- </div>
- </n-modal>
- </template>
|