|
@@ -5,14 +5,14 @@ import { getMediaStream } from "@/utils/camera";
|
|
|
import { httpApp } from "@/plugins/axiosApp";
|
|
|
import { showLogout } from "@/utils/utils";
|
|
|
import { getCapturePhotoYunSign, saveCapturePhoto } from "@/api/login";
|
|
|
+import { execLocal, fileExists } from "@/utils/nativeMethods";
|
|
|
|
|
|
-// FIXME: 开启异步抓拍
|
|
|
/**
|
|
|
* 上层通过showRecognizeButton来控制是否是同步比对
|
|
|
*
|
|
|
* 同步比对通过onRecognizeResult得到人脸比对结果
|
|
|
*
|
|
|
- * 异步比对通过snapNow来控制是否该进行比对,什么时候进行,以什么频率频率进行,均由上层控制
|
|
|
+ * 异步比对通过snapId来控制是否该进行比对,什么时候进行,以什么频率频率进行,错误处理,均由上层控制
|
|
|
* 异步比对同时传递一个snapId(time),供上层识别和计数
|
|
|
* 可能存在多个异步比对的任务同时进行
|
|
|
*/
|
|
@@ -22,14 +22,14 @@ import { getCapturePhotoYunSign, saveCapturePhoto } from "@/api/login";
|
|
|
const {
|
|
|
width = 400,
|
|
|
height = 300,
|
|
|
- snapNow = false,
|
|
|
- // snapId = 0,
|
|
|
+ snapId = 0,
|
|
|
+ examRecordDataId = -1,
|
|
|
} = defineProps<{
|
|
|
width: string;
|
|
|
height: string;
|
|
|
showRecognizeButton: boolean;
|
|
|
- snapNow?: boolean;
|
|
|
- // snapId: number;
|
|
|
+ snapId?: number;
|
|
|
+ examRecordDataId?: number;
|
|
|
}>();
|
|
|
|
|
|
const emit = defineEmits<{
|
|
@@ -37,14 +37,18 @@ const emit = defineEmits<{
|
|
|
e: "on-recognize-result",
|
|
|
v: { isPassed: boolean; isStranger: boolean }
|
|
|
): void;
|
|
|
+ (
|
|
|
+ e: "on-async-recognize-result",
|
|
|
+ v: { hasError: boolean; fileName: string }
|
|
|
+ ): void;
|
|
|
}>();
|
|
|
|
|
|
let snapBtnDisabled = $ref(true);
|
|
|
let btnText = $ref("开始识别");
|
|
|
|
|
|
watchEffect(() => {
|
|
|
- if (snapNow) {
|
|
|
- // snapAsync(snapId)
|
|
|
+ if (snapId) {
|
|
|
+ void snapAsync();
|
|
|
}
|
|
|
});
|
|
|
|
|
@@ -61,7 +65,6 @@ async function openCamera() {
|
|
|
try {
|
|
|
await video.play();
|
|
|
} catch (error) {
|
|
|
- console.log(error);
|
|
|
if (error instanceof Error) {
|
|
|
if (error.name == "AbortError") {
|
|
|
logger({
|
|
@@ -108,90 +111,20 @@ async function openCamera() {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
-// async function snapAsync() {
|
|
|
-// try {
|
|
|
-// logger({ cnl: ["server"], act: "定时抓拍开始" });
|
|
|
-// const examRecordDataId = this.$route.params.examRecordDataId;
|
|
|
-// const captureBlob = await getSnapShot({ compareSync: false });
|
|
|
-// logger({ cnl: ["server"], act: "抓拍照片的大小:" + captureBlob.size });
|
|
|
-// void videoStartPlay();
|
|
|
-// console.log("抓拍照片的大小:" + captureBlob.size);
|
|
|
-// if (captureBlob.size < 48 * 48 || captureBlob.size >= 2 * 1024 * 1024) {
|
|
|
-// // 经查以前记录,不完整图片均为8192大小。此处设置小于10KB的图片为未抓拍成功
|
|
|
-// // 检查百度统计的记录后,这里的图片大小可能小于8192,也可能是有效的数据,所以降低图片大小的要求为face++的要求
|
|
|
-// logger({
|
|
|
-// cnl: ["server"],
|
|
|
-// act: "摄像头异常",
|
|
|
-// dtl: "定时抓拍照片大小异常",
|
|
|
-// ext: { blobSize: captureBlob.size },
|
|
|
-// });
|
|
|
-// throw new Error("定时抓拍照片大小异常");
|
|
|
-// }
|
|
|
-// const startTime = Date.now();
|
|
|
-// const [captureFilePath, signIdentifier] = await this.uploadToServer(
|
|
|
-// captureBlob
|
|
|
-// );
|
|
|
-// const endTime = Date.now();
|
|
|
-// logger({
|
|
|
-// cnl: ["server"],
|
|
|
-// act: "定时抓拍上传",
|
|
|
-// ext: { cost: endTime - startTime },
|
|
|
-// });
|
|
|
-// await this.faceCompare(captureFilePath, signIdentifier, examRecordDataId);
|
|
|
-// logger({
|
|
|
-// cnl: ["server"],
|
|
|
-// act: "定时抓拍比对",
|
|
|
-// dtl: "定时抓拍流程成功",
|
|
|
-// ext: { cost: Date.now() - endTime, signIdentifier },
|
|
|
-// });
|
|
|
-// } catch (error) {
|
|
|
-// if (!(error instanceof Error)) {
|
|
|
-// logger({
|
|
|
-// cnl: ["server"],
|
|
|
-// act: "snapAsync",
|
|
|
-// dtl: "not an Error",
|
|
|
-// stk: error + "",
|
|
|
-// });
|
|
|
-// return;
|
|
|
-// }
|
|
|
-// logger({
|
|
|
-// cnl: ["server"],
|
|
|
-// act: "定时抓拍流程失败",
|
|
|
-// ejn: JSON.stringify(error),
|
|
|
-// stk: error.stack,
|
|
|
-// ext: {
|
|
|
-// errorName: error.name,
|
|
|
-// errorMessage: error.message,
|
|
|
-// firstSnap: (this.lastSnapTime ? "(非初次抓拍)" : "") + "将再次抓拍",
|
|
|
-// },
|
|
|
-// });
|
|
|
-// this.retrySnapTimeout = setTimeout(() => {
|
|
|
-// this.logger({
|
|
|
-// action: "答题页面",
|
|
|
-// detail: "定时抓拍流程失败后重试",
|
|
|
-// });
|
|
|
-// this.toggleSnapNow();
|
|
|
-// }, 60 * 1000);
|
|
|
-// } finally {
|
|
|
-// this.videoStartPlay();
|
|
|
-// this.decreaseSnapCount();
|
|
|
-// }
|
|
|
-// }
|
|
|
-
|
|
|
async function videoStartPlay() {
|
|
|
if (video && video.paused) {
|
|
|
await video.play().catch((e) => {
|
|
|
if (!(e instanceof Error)) {
|
|
|
logger({
|
|
|
cnl: ["server"],
|
|
|
- act: "videoStartPlay",
|
|
|
+ act: "restart video play error",
|
|
|
dtl: "not an Error",
|
|
|
stk: e + "",
|
|
|
});
|
|
|
} else {
|
|
|
logger({
|
|
|
cnl: ["server"],
|
|
|
- act: "restart video play",
|
|
|
+ act: "restart video play error",
|
|
|
stk: e.stack,
|
|
|
ejn: JSON.stringify(e),
|
|
|
});
|
|
@@ -201,7 +134,8 @@ async function videoStartPlay() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-async function snap() {
|
|
|
+//#region 同步人脸比对
|
|
|
+async function snapSync() {
|
|
|
logger({
|
|
|
cnl: ["server"],
|
|
|
act: "同步人脸比对",
|
|
@@ -212,11 +146,18 @@ async function snap() {
|
|
|
try {
|
|
|
snapBtnDisabled = true;
|
|
|
btnText = "拍照中...";
|
|
|
+ logger({ cnl: ["server"], lvl: "debug", act: btnText });
|
|
|
const captureBlob = await getSnapShot(true);
|
|
|
- console.log("抓拍照片大小", captureBlob.size);
|
|
|
+ if (!(captureBlob instanceof Blob)) return;
|
|
|
+
|
|
|
+ logger({
|
|
|
+ cnl: ["server"],
|
|
|
+ lvl: "debug",
|
|
|
+ act: "getSnapShot",
|
|
|
+ ext: { blobSize: captureBlob.size },
|
|
|
+ });
|
|
|
if (captureBlob.size < 48 * 48 || captureBlob.size >= 2 * 1024 * 1024) {
|
|
|
$message.error("抓拍照片太小!");
|
|
|
-
|
|
|
logger({
|
|
|
cnl: ["server"],
|
|
|
act: "摄像头异常",
|
|
@@ -225,8 +166,8 @@ async function snap() {
|
|
|
});
|
|
|
throw new Error("抓拍照片大小异常");
|
|
|
}
|
|
|
- void videoStartPlay();
|
|
|
btnText = "上传照片中...";
|
|
|
+ logger({ cnl: ["server"], lvl: "debug", act: btnText });
|
|
|
const [captureFilePath, signIdentifier] = await uploadToServer(captureBlob);
|
|
|
btnText = "人脸比对中...";
|
|
|
await faceCompareSync(captureFilePath, signIdentifier);
|
|
@@ -242,14 +183,13 @@ async function snap() {
|
|
|
console.log("同步照片比对流程失败");
|
|
|
throw error;
|
|
|
} finally {
|
|
|
- void videoStartPlay();
|
|
|
btnText = "开始识别";
|
|
|
// 避免人脸识别功能被大量重复点击
|
|
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
|
snapBtnDisabled = false;
|
|
|
}
|
|
|
}
|
|
|
-async function getSnapShot(compareSync: boolean): Promise<Blob> {
|
|
|
+async function getSnapShot(compareSync: boolean): Promise<Blob | unknown> {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
if (video.readyState !== 4 || !(video.srcObject as MediaStream).active) {
|
|
|
$message.error("摄像头没有正常启用");
|
|
@@ -258,7 +198,6 @@ async function getSnapShot(compareSync: boolean): Promise<Blob> {
|
|
|
pgu: "AUTO",
|
|
|
act: "getSnapShot",
|
|
|
dtl: "摄像头没有正常启用",
|
|
|
- // (!compareSync && this.lastSnapTime ? "-退出(非初次抓拍)" : ""),
|
|
|
});
|
|
|
reject("摄像头没有正常启用");
|
|
|
if (!compareSync) {
|
|
@@ -275,8 +214,9 @@ async function getSnapShot(compareSync: boolean): Promise<Blob> {
|
|
|
context?.drawImage(video, 0, 0, 220, 165);
|
|
|
|
|
|
canvas.toBlob((blob) => resolve(blob!), "image/png", 0.95);
|
|
|
- });
|
|
|
+ }).finally(() => void videoStartPlay()); // TODO: finally 此处的错误捕捉还需验证
|
|
|
}
|
|
|
+
|
|
|
// 用来比对两次抓拍照片的md5是否一样
|
|
|
let __previousPhotoMD5 = "";
|
|
|
async function uploadToServer(captureBlob: Blob): Promise<[string, string]> {
|
|
@@ -328,7 +268,6 @@ async function uploadToServer(captureBlob: Blob): Promise<[string, string]> {
|
|
|
});
|
|
|
throw new Error("图片校验失败");
|
|
|
}
|
|
|
- __previousPhotoMD5 = fileMd5Base64;
|
|
|
} catch (error) {
|
|
|
logger({
|
|
|
cnl: ["server"],
|
|
@@ -387,8 +326,6 @@ async function faceCompareSync(
|
|
|
isStranger: res.data.isStranger,
|
|
|
});
|
|
|
} catch (e) {
|
|
|
- console.log(e);
|
|
|
- // this.$Message.error(e.message);
|
|
|
logger({
|
|
|
cnl: ["server"],
|
|
|
act: "同步比对失败",
|
|
@@ -397,238 +334,220 @@ async function faceCompareSync(
|
|
|
throw new Error("同步照片比较失败!");
|
|
|
}
|
|
|
}
|
|
|
-// async function faceCompare(
|
|
|
-// captureFilePath: string,
|
|
|
-// signIdentifier: string,
|
|
|
-// examRecordDataId: number
|
|
|
-// ) {
|
|
|
-// try {
|
|
|
-// let cameraInfos;
|
|
|
-// let hasVirtualCamera = false;
|
|
|
-// if (typeof nodeRequire != "undefined") {
|
|
|
-// try {
|
|
|
-// var fs = window.nodeRequire("fs");
|
|
|
-// if (fs.existsSync("multiCamera.exe")) {
|
|
|
-// await new Promise((resolve, reject) => {
|
|
|
-// window.nodeRequire("node-cmd").get("multiCamera.exe", () => {
|
|
|
-// try {
|
|
|
-// cameraInfos = fs.readFileSync("CameraInfo.txt", "utf-8");
|
|
|
-// // cameraInfos =
|
|
|
-// // '[{"detail":"@device:pnp:?display#int3470#4&300121c4&0&uid13424#{65e8773d-8f56-11d0-a3b9-00a0c9223196}{9c5f415a-02cd-4e28-aeb7-811cb317dd64}","name":"HP Truevision 5MP Front","pid":"13424","vid":"3470"},{"detail":"@device:pnp:?display#int3470#4&300121c4&0&uid13424#{65e8773d-8f56-11d0-a3b9-00a0c9223196}{a6c1c503-01f1-4767-a229-00a0b223162f}","name":"HP Truevision 8MP Rear","pid":"13424","vid":"3470"},{"detail":"@device:pnp:?usb#vid_8086&pid_0a80&mi_04#6&28913c47&0&0004#{65e8773d-8f56-11d0-a3b9-00a0c9223196}global","name":"Intel(R) RealSense(TM) 3D Camera (R200) RGB","pid":"0a80","vid":"8086"},{"detail":"@device:pnp:?usb#vid_8086&pid_0a80&mi_00#6&28913c47&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}global","name":"Intel(R) RealSense(TM) 3D Camera (R200) Left-Right","pid":"0a80","vid":"8086"},{"detail":"@device:pnp:?usb#vid_8086&pid_0a80&mi_02#6&28913c47&0&0002#{65e8773d-8f56-11d0-a3b9-00a0c9223196}global","name":"Intel(R) RealSense(TM) 3D Camera (R200) Depth","pid":"0a80","vid":"8086"}]';
|
|
|
-// if (cameraInfos && cameraInfos.trim()) {
|
|
|
-// cameraInfos = cameraInfos.trim();
|
|
|
-// cameraInfos = cameraInfos.replace(/\r\n/g, "");
|
|
|
-// cameraInfos = cameraInfos.replace(/\n/g, "");
|
|
|
-// console.log(cameraInfos);
|
|
|
-// this.logger({
|
|
|
-// page: "摄像头框",
|
|
|
-// cameraInfos,
|
|
|
-// });
|
|
|
-// }
|
|
|
-// if (cameraInfos.includes('""')) {
|
|
|
-// hasVirtualCamera = true;
|
|
|
-// }
|
|
|
-// // multiCamera.exe 1.0.1
|
|
|
-// if (cameraInfos.includes("cameraInfo")) {
|
|
|
-// cameraInfos = JSON.stringify(
|
|
|
-// JSON.parse(cameraInfos).cameraInfo
|
|
|
-// );
|
|
|
-// }
|
|
|
-// if (cameraInfos.length >= 800) {
|
|
|
-// this.logger({
|
|
|
-// page: "摄像头框",
|
|
|
-// type: "虚拟摄像头-cameraInfos超长",
|
|
|
-// cameraInfos: cameraInfos,
|
|
|
-// });
|
|
|
-// let ary = JSON.parse(cameraInfos);
|
|
|
-// // 相同pid&vid仅保留一个
|
|
|
-// const pidAndVidCollector = [];
|
|
|
-// ary = ary.filter((c) => {
|
|
|
-// const pv = c.pid + "|" + c.vid;
|
|
|
-// const res = pidAndVidCollector.includes(pv);
|
|
|
-// pidAndVidCollector.push(pv);
|
|
|
-// return !res;
|
|
|
-// });
|
|
|
-// cameraInfos = JSON.stringify(ary);
|
|
|
-// console.log("摄像头检测超长:", "去除重复pid&vid");
|
|
|
-// console.log(cameraInfos);
|
|
|
-// if (cameraInfos.length >= 800) {
|
|
|
-// cameraInfos = JSON.stringify(
|
|
|
-// JSON.parse(cameraInfos).map((v) => {
|
|
|
-// return {
|
|
|
-// pid: v.pid,
|
|
|
-// vid: v.pid,
|
|
|
-// detail: "omitted",
|
|
|
-// name: v.name,
|
|
|
-// };
|
|
|
-// })
|
|
|
-// );
|
|
|
-// console.log("摄像头检测超长:", "去除detail");
|
|
|
-// console.log(cameraInfos);
|
|
|
-// }
|
|
|
-// if (cameraInfos.length >= 800) {
|
|
|
-// console.log("摄像头检测超长:", "精简后还是超长");
|
|
|
-// this.logger({
|
|
|
-// page: "摄像头框",
|
|
|
-// type: "虚拟摄像头-精简后还是超长",
|
|
|
-// cameraInfos: cameraInfos,
|
|
|
-// });
|
|
|
-// console.log(cameraInfos);
|
|
|
-// }
|
|
|
-// }
|
|
|
-// resolve();
|
|
|
-// } catch (error) {
|
|
|
-// this.logger({
|
|
|
-// page: "摄像头框",
|
|
|
-// type: "虚拟摄像头-读取摄像头列表失败",
|
|
|
-// errorJSON: JSON.stringify(error, (key, value) =>
|
|
|
-// key === "token" ? "" : value
|
|
|
-// ),
|
|
|
-// errorName: error.name,
|
|
|
-// errorMessage: error.message,
|
|
|
-// errorStack: error.stack,
|
|
|
-// });
|
|
|
-// window._hmt.push([
|
|
|
-// "_trackEvent",
|
|
|
-// "摄像头框",
|
|
|
-// "虚拟摄像头-读取摄像头列表失败",
|
|
|
-// ]);
|
|
|
-// reject("读取摄像头列表失败");
|
|
|
-// }
|
|
|
-// });
|
|
|
-// });
|
|
|
-// }
|
|
|
-// } catch (error) {
|
|
|
-// console.log(error);
|
|
|
-// }
|
|
|
-// }
|
|
|
-
|
|
|
-// let body = {
|
|
|
-// fileUrl: captureFilePath,
|
|
|
-// signIdentifier,
|
|
|
-// examRecordDataId,
|
|
|
-// };
|
|
|
-
|
|
|
-// if (cameraInfos) {
|
|
|
-// body.cameraInfos = cameraInfos;
|
|
|
-// body.hasVirtualCamera = hasVirtualCamera;
|
|
|
-
|
|
|
-// this.logger({
|
|
|
-// action: "抓拍照片详细日志",
|
|
|
-// fileUrl: captureFilePath,
|
|
|
-// signIdentifier,
|
|
|
-// examRecordDataId,
|
|
|
-// cameraInfos,
|
|
|
-// hasVirtualCamera,
|
|
|
-// duplicateMD5: this.__duplicateMD5,
|
|
|
-// });
|
|
|
-// }
|
|
|
-// const res = await this.$http.post(
|
|
|
-// "/api/ecs_oe_student_face/examCaptureQueue/uploadExamCapture",
|
|
|
-// body
|
|
|
-// );
|
|
|
-// const fileName = res.data;
|
|
|
-// try {
|
|
|
-// await this.showSnapResult(fileName, examRecordDataId);
|
|
|
-// } catch (error) {
|
|
|
-// this.logger({
|
|
|
-// page: "摄像头框",
|
|
|
-// action: "设置获取抓拍结果失败!",
|
|
|
-// errorJSON: JSON.stringify(error, (key, value) =>
|
|
|
-// key === "token" ? "" : value
|
|
|
-// ),
|
|
|
-// errorName: error.name,
|
|
|
-// errorMessage: error.message,
|
|
|
-// errorStack: error.stack,
|
|
|
-// });
|
|
|
-// this.$Message.error({
|
|
|
-// content: "设置获取抓拍结果失败!",
|
|
|
-// duration: 15,
|
|
|
-// closable: true,
|
|
|
-// });
|
|
|
-// }
|
|
|
-// } catch (e) {
|
|
|
-// console.log(e);
|
|
|
-// this.logger({
|
|
|
-// page: "摄像头框",
|
|
|
-// action: "faceCompare失败",
|
|
|
-// error: e.response ? e.response.data.desc : e,
|
|
|
-// });
|
|
|
-// window._hmt.push([
|
|
|
-// "_trackEvent",
|
|
|
-// "摄像头框",
|
|
|
-// "faceCompare失败",
|
|
|
-// e.response ? e.response.data.desc : e,
|
|
|
-// ]);
|
|
|
-// // this.$Message.error(e.message);
|
|
|
-// throw new Error("异步比较抓拍照片失败");
|
|
|
-// }
|
|
|
-// }
|
|
|
-// async function showSnapResult(fileName, examRecordDataId) {
|
|
|
-// if (!fileName) return; // 交卷后提交照片会得不到照片名称
|
|
|
-// if (this.$route.name !== "OnlineExamingHome") {
|
|
|
-// // 非考试页,不显示结果,也不继续查询
|
|
|
-// return;
|
|
|
-// }
|
|
|
-
|
|
|
-// try {
|
|
|
-// // 获取抓拍结果
|
|
|
-// const snapRes =
|
|
|
-// (
|
|
|
-// await this.$http.get(
|
|
|
-// "/api/ecs_oe_student_face/examCaptureQueue/getExamCaptureResult?fileName=" +
|
|
|
-// fileName +
|
|
|
-// "&examRecordDataId=" +
|
|
|
-// examRecordDataId
|
|
|
-// )
|
|
|
-// ).data || {};
|
|
|
-// if (snapRes.isCompleted) {
|
|
|
-// if (snapRes.isStranger) {
|
|
|
-// this.$Message.error({
|
|
|
-// content: "请独立完成考试",
|
|
|
-// duration: 5,
|
|
|
-// closable: true,
|
|
|
-// });
|
|
|
-// } else if (!snapRes.isPass) {
|
|
|
-// this.$Message.error({
|
|
|
-// content: "请调整坐姿,诚信考试",
|
|
|
-// duration: 5,
|
|
|
-// closable: true,
|
|
|
-// });
|
|
|
-// }
|
|
|
-// } else {
|
|
|
-// this.showSnapResultTimeout = setTimeout(
|
|
|
-// this.showSnapResult.bind(this, fileName, examRecordDataId),
|
|
|
-// 30 * 1000
|
|
|
-// );
|
|
|
-// }
|
|
|
-// } catch (e) {
|
|
|
-// console.log(e);
|
|
|
-// if (this.$route.name !== "OnlineExamingHome") {
|
|
|
-// // 非考试页,不显示结果,也不继续查询
|
|
|
-// return;
|
|
|
-// }
|
|
|
-// this.$Message.error(e.message);
|
|
|
-// throw e.message;
|
|
|
-// }
|
|
|
-// }
|
|
|
+//#endregion 同步人脸比对
|
|
|
+
|
|
|
+//#region 异步人脸比对
|
|
|
+async function snapAsync() {
|
|
|
+ try {
|
|
|
+ logger({ cnl: ["server"], act: "定时抓拍开始" });
|
|
|
+ const captureBlob = await getSnapShot(false);
|
|
|
+ if (!(captureBlob instanceof Blob)) return;
|
|
|
+
|
|
|
+ logger({ cnl: ["server"], act: "抓拍照片的大小:" + captureBlob.size });
|
|
|
+ if (captureBlob.size < 48 * 48 || captureBlob.size >= 2 * 1024 * 1024) {
|
|
|
+ // 经查以前记录,不完整图片均为8192大小。此处设置小于10KB的图片为未抓拍成功
|
|
|
+ // 检查百度统计的记录后,这里的图片大小可能小于8192,也可能是有效的数据,所以降低图片大小的要求为face++的要求
|
|
|
+ logger({
|
|
|
+ cnl: ["server"],
|
|
|
+ act: "摄像头异常",
|
|
|
+ dtl: "定时抓拍照片大小异常",
|
|
|
+ ext: { blobSize: captureBlob.size },
|
|
|
+ });
|
|
|
+ throw new Error("定时抓拍照片大小异常");
|
|
|
+ }
|
|
|
+ const startTime = Date.now();
|
|
|
+ const [captureFilePath, signIdentifier] = await uploadToServer(captureBlob);
|
|
|
+ const endTime = Date.now();
|
|
|
+ logger({
|
|
|
+ cnl: ["server"],
|
|
|
+ act: "定时抓拍上传",
|
|
|
+ ext: { cost: endTime - startTime },
|
|
|
+ });
|
|
|
+ await faceCompare(captureFilePath, signIdentifier, examRecordDataId);
|
|
|
+ logger({
|
|
|
+ cnl: ["server"],
|
|
|
+ act: "定时抓拍比对",
|
|
|
+ dtl: "定时抓拍流程成功",
|
|
|
+ ext: { cost: Date.now() - endTime, signIdentifier },
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ if (!(error instanceof Error)) {
|
|
|
+ logger({
|
|
|
+ cnl: ["server"],
|
|
|
+ act: "snapAsync",
|
|
|
+ dtl: "not an Error",
|
|
|
+ stk: error + "",
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ logger({
|
|
|
+ cnl: ["server"],
|
|
|
+ act: "定时抓拍流程失败",
|
|
|
+ ejn: JSON.stringify(error),
|
|
|
+ stk: error.stack,
|
|
|
+ possibleError: error,
|
|
|
+ });
|
|
|
+ emit("on-async-recognize-result", {
|
|
|
+ hasError: true,
|
|
|
+ fileName: "",
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+type CameraInfo = {
|
|
|
+ detail: string;
|
|
|
+ pid: string;
|
|
|
+ vid: string;
|
|
|
+ name: string;
|
|
|
+};
|
|
|
+async function faceCompare(
|
|
|
+ captureFilePath: string,
|
|
|
+ signIdentifier: string,
|
|
|
+ examRecordDataId: number
|
|
|
+) {
|
|
|
+ try {
|
|
|
+ let cameraInfos;
|
|
|
+ let hasVirtualCamera = false;
|
|
|
+ if (typeof window.nodeRequire != "undefined") {
|
|
|
+ const fs: typeof import("fs") = window.nodeRequire("fs");
|
|
|
+ if (fileExists("multiCamera.exe")) {
|
|
|
+ try {
|
|
|
+ await execLocal("multiCamera.exe");
|
|
|
+ cameraInfos = fs.readFileSync("CameraInfo.txt", "utf-8");
|
|
|
+ // cameraInfos =
|
|
|
+ // '[{"detail":"@device:pnp:?display#int3470#4&300121c4&0&uid13424#{65e8773d-8f56-11d0-a3b9-00a0c9223196}{9c5f415a-02cd-4e28-aeb7-811cb317dd64}","name":"HP Truevision 5MP Front","pid":"13424","vid":"3470"},{"detail":"@device:pnp:?display#int3470#4&300121c4&0&uid13424#{65e8773d-8f56-11d0-a3b9-00a0c9223196}{a6c1c503-01f1-4767-a229-00a0b223162f}","name":"HP Truevision 8MP Rear","pid":"13424","vid":"3470"},{"detail":"@device:pnp:?usb#vid_8086&pid_0a80&mi_04#6&28913c47&0&0004#{65e8773d-8f56-11d0-a3b9-00a0c9223196}global","name":"Intel(R) RealSense(TM) 3D Camera (R200) RGB","pid":"0a80","vid":"8086"},{"detail":"@device:pnp:?usb#vid_8086&pid_0a80&mi_00#6&28913c47&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}global","name":"Intel(R) RealSense(TM) 3D Camera (R200) Left-Right","pid":"0a80","vid":"8086"},{"detail":"@device:pnp:?usb#vid_8086&pid_0a80&mi_02#6&28913c47&0&0002#{65e8773d-8f56-11d0-a3b9-00a0c9223196}global","name":"Intel(R) RealSense(TM) 3D Camera (R200) Depth","pid":"0a80","vid":"8086"}]';
|
|
|
+ if (cameraInfos && cameraInfos.trim()) {
|
|
|
+ cameraInfos = cameraInfos.trim();
|
|
|
+ cameraInfos = cameraInfos.replace(/\r\n/g, "");
|
|
|
+ cameraInfos = cameraInfos.replace(/\n/g, "");
|
|
|
+ logger({
|
|
|
+ cnl: ["server"],
|
|
|
+ act: "multiCamera.exe",
|
|
|
+ ext: { cameraInfos },
|
|
|
+ });
|
|
|
+ }
|
|
|
+ if (cameraInfos.includes('""')) {
|
|
|
+ hasVirtualCamera = true;
|
|
|
+ }
|
|
|
+ // multiCamera.exe 1.0.1
|
|
|
+ if (cameraInfos.includes("cameraInfo")) {
|
|
|
+ cameraInfos = JSON.stringify(JSON.parse(cameraInfos).cameraInfo);
|
|
|
+ }
|
|
|
+ if (cameraInfos.length >= 800) {
|
|
|
+ logger({
|
|
|
+ cnl: ["server"],
|
|
|
+ act: "multiCamera.exe",
|
|
|
+ stk: "虚拟摄像头-cameraInfos超长",
|
|
|
+ ext: { cameraInfos },
|
|
|
+ });
|
|
|
+ let ary: CameraInfo[] = JSON.parse(cameraInfos);
|
|
|
+ // 相同pid&vid仅保留一个
|
|
|
+ const pidAndVidCollector: string[] = [];
|
|
|
+ ary = ary.filter((c) => {
|
|
|
+ const pv = c.pid + "|" + c.vid;
|
|
|
+ const res = pidAndVidCollector.includes(pv);
|
|
|
+ pidAndVidCollector.push(pv);
|
|
|
+ return !res;
|
|
|
+ });
|
|
|
+ cameraInfos = JSON.stringify(ary);
|
|
|
+ logger({
|
|
|
+ cnl: ["server"],
|
|
|
+ act: "multiCamera.exe",
|
|
|
+ stk: "除重复pid&vid",
|
|
|
+ });
|
|
|
+ if (cameraInfos.length >= 800) {
|
|
|
+ cameraInfos = JSON.stringify(
|
|
|
+ (<CameraInfo[]>JSON.parse(cameraInfos)).map((v) => {
|
|
|
+ return {
|
|
|
+ pid: v.pid,
|
|
|
+ vid: v.pid,
|
|
|
+ detail: "omitted",
|
|
|
+ name: v.name,
|
|
|
+ };
|
|
|
+ })
|
|
|
+ );
|
|
|
+ console.log("摄像头检测超长:", "去除detail");
|
|
|
+ console.log(cameraInfos);
|
|
|
+ }
|
|
|
+ if (cameraInfos.length >= 800) {
|
|
|
+ logger({
|
|
|
+ cnl: ["server"],
|
|
|
+ act: "multiCamera.exe",
|
|
|
+ stk: "精简后还是超长",
|
|
|
+ ext: { cameraInfos },
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ logger({
|
|
|
+ cnl: ["server"],
|
|
|
+ act: "multiCamera.exe",
|
|
|
+ stk: "虚拟摄像头-读取摄像头列表失败",
|
|
|
+ possibleError: error,
|
|
|
+ });
|
|
|
+ // throw new Error("读取摄像头列表失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let body: any = {
|
|
|
+ fileUrl: captureFilePath,
|
|
|
+ signIdentifier,
|
|
|
+ examRecordDataId,
|
|
|
+ };
|
|
|
+
|
|
|
+ if (cameraInfos) {
|
|
|
+ body.cameraInfos = cameraInfos;
|
|
|
+ body.hasVirtualCamera = hasVirtualCamera;
|
|
|
+ }
|
|
|
+ logger({
|
|
|
+ cnl: ["server"],
|
|
|
+ act: "抓拍照片详细日志",
|
|
|
+ ext: {
|
|
|
+ fileUrl: captureFilePath,
|
|
|
+ signIdentifier,
|
|
|
+ examRecordDataId,
|
|
|
+ cameraInfos,
|
|
|
+ hasVirtualCamera,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ const res = await httpApp.post(
|
|
|
+ "/api/ecs_oe_student_face/examCaptureQueue/uploadExamCapture",
|
|
|
+ body
|
|
|
+ );
|
|
|
+
|
|
|
+ emit("on-async-recognize-result", {
|
|
|
+ hasError: false,
|
|
|
+ fileName: res.data,
|
|
|
+ });
|
|
|
+ } catch (e) {
|
|
|
+ logger({
|
|
|
+ cnl: ["server"],
|
|
|
+ act: "定时抓拍",
|
|
|
+ dtl: "抓拍失败",
|
|
|
+ possibleError: e,
|
|
|
+ });
|
|
|
+ emit("on-async-recognize-result", {
|
|
|
+ hasError: true,
|
|
|
+ fileName: "",
|
|
|
+ });
|
|
|
+
|
|
|
+ throw new Error("异步比较抓拍照片失败");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+//#endregion 异步人脸比对
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
<div>
|
|
|
- <video
|
|
|
- id="video"
|
|
|
- ref="video"
|
|
|
- :width="width"
|
|
|
- :height="height"
|
|
|
- autoplay
|
|
|
- ></video>
|
|
|
+ <video id="video" ref="video" :width="width" :height="height" autoplay />
|
|
|
<div v-if="showRecognizeButton" class="btn-container">
|
|
|
<button
|
|
|
class="verify-button"
|
|
|
:class="[snapBtnDisabled && 'disable-verify-button']"
|
|
|
:disabled="snapBtnDisabled"
|
|
|
- @click="snap"
|
|
|
+ @click="snapSync"
|
|
|
>
|
|
|
{{ btnText }}
|
|
|
</button>
|