/** * 人脸识别服务模块 * 处理人脸识别相关的业务逻辑,包括人脸注册和人脸比对 */ import logger from "../../dxmodules/dxLogger.js"; import dxCommon from "../../dxmodules/dxCommon.js"; import bus from "../../dxmodules/dxEventBus.js"; import dxMap from "../../dxmodules/dxMap.js"; import driver from "../driver.js"; import config from "../../dxmodules/dxConfig.js"; import sqliteService from "./sqliteService.js"; import std from "../../dxmodules/dxStd.js"; import pool from "../../dxmodules/dxWorkerPool.js"; let map = dxMap.get("LOGIN") const faceService = {} // 语音播放防抖控制 let lastPlayTime = 0 const PLAY_DELAY = 2000 // 2秒内不重复播放相同语音 /** * 语音播放防抖函数 * @param {function} playFunction - 播放函数 * @returns {boolean} 是否执行了播放 */ function debouncePlay(playFunction) { const now = Date.now() if (now - lastPlayTime > PLAY_DELAY) { lastPlayTime = now playFunction() return true } return false } // 双人认证状态管理 let dualAuthState = { firstUserId: null, // 第一个用户ID firstUserName: null, // 第一个用户姓名 firstUserFileName: null, // 第一个用户人脸图片文件路径 firstUserIdCard: null, // 第一个用户身份证号 firstUserType: null, // 第一个用户身份类型 secondUserId: null, // 第二个用户ID secondUserName: null, // 第二个用户姓名 secondUserIdCard: null, // 第二个用户身份证号 secondUserType: null, // 第二个用户身份类型 startTime: 0, // 开始时间 timeout: null, // 定时器 authComplete: false // 认证是否完成 } /** * 接收人脸识别消息并处理 * @param {object} data - 人脸识别数据 */ faceService.receiveMsg = function (data) { logger.info('[faceService] receiveMsg :' + JSON.stringify(data)) // 触发退出空闲状态事件(锁屏和息屏) bus.fire("exitIdle") switch (data.type) { case "register": // 注册人脸处理 for (let i = 0; i < data.faces.length; i++) { const element = data.faces[i]; bus.fire("beginAddFace", element) } break; case "compare": // 人脸比对处理 // 显示姓名,代表有注册过人脸,但是权限不一定有效,需要进一步认证 for (let i = 0; i < data.faces.length; i++) { const element = data.faces[i]; // 只有在认证未完成时才触发trackResult事件 if (!dualAuthState.authComplete) { // 判断当前是第几个用户(用于双人认证) let userIndex = 1 // 0表示未知,1表示第一个用户,2表示第二个用户 if (!dualAuthState.firstUserId) { // 第一个用户还未设置,当前用户就是第一个用户 userIndex = 1 } else if (element.userId === dualAuthState.firstUserId) { userIndex = 1 } else if (element.userId !== dualAuthState.firstUserId) { userIndex = 2 } logger.info('[faceService]: 准备触发trackResult事件, userIndex=' + userIndex + ', result=' + element.result + ', userId=' + element.userId + ', authComplete=' + dualAuthState.authComplete + ', fileName=' + element.fileName) // 直接使用bus.fire触发trackResult事件 bus.fire("trackResult", { id: element.id, result: element.result, userId: element.userId, userIndex: userIndex, fileName: element.fileName }) } if (element.result) { // 人脸相似度验证通过 let ret = sqliteService.d1_person.find({ userId: element.userId }) if (dxMap.get("UI").get("faceAuthStart") == "Y") { // 正在人脸登录 if (JSON.parse(ret[0].extra).type != 0) { bus.fire("faceAuthResult", true) } else { bus.fire("faceAuthResult", false) } return } // 检查用户类型(管理员级别) let userType = 0 try { userType = JSON.parse(ret[0].extra).type || 0 } catch (error) { logger.error("解析用户类型失败") } // 管理员级别用户(type != 0)直接认证通过 if (userType != 0) { // 清除双人认证状态 clearDualAuthState() // 根据语音模式播放相应的语音 switch (config.get("face.voiceMode")) { case 0: // 无语音 break; case 1: // 播放名字 driver.alsa.ttsPlay(ret[0].name) break; case 2: // 播放问候语 driver.alsa.ttsPlay(config.get("face.voiceModeDate") ? config.get("face.voiceModeDate") : "欢迎光临") break; default: break; } // 通行认证处理 bus.fire("access", { data: { type: "300", code: element.userId }, fileName: element.fileName }) } else { // 非管理员级别用户,需要双人认证 handleDualAuth(element.userId, ret[0].name, element.fileName) } } else { // 人脸相似度验证失败 if (dxMap.get("UI").get("faceAuthStart") == "Y") { // 人脸登录失败 bus.fire("faceAuthResult", false) } else { // 陌生人处理 switch (config.get("face.stranger")) { case 0: // 无语音 break; case 1: // 播放请先注册(防抖控制) debouncePlay(() => { driver.alsa.play(`/app/code/resource/${config.get("base.language") == "CN" ? "CN" : "EN"}/wav/register.wav`) }) break; case 2: // 播放陌生人你好(防抖控制) debouncePlay(() => { driver.alsa.play(`/app/code/resource/${config.get("base.language") == "CN" ? "CN" : "EN"}/wav/stranger.wav`) }) break; default: break; } // 通行认证处理(相似度验证失败) bus.fire("access", { data: { type: "300", code: element.userId }, fileName: element.fileName, similarity: false }) } } } break; default: break; } } // 监听通行解锁完成事件,重置双人认证状态 bus.on('accessUnlockComplete', () => { logger.info('[faceService]: accessUnlockComplete事件触发,重置双人认证状态') clearDualAuthState() }) /** * 清除双人认证状态 */ function clearDualAuthState() { dualAuthState.firstUserId = null dualAuthState.firstUserName = null dualAuthState.firstUserFileName = null dualAuthState.firstUserIdCard = null dualAuthState.firstUserType = null dualAuthState.secondUserId = null dualAuthState.secondUserName = null dualAuthState.secondUserIdCard = null dualAuthState.secondUserType = null dualAuthState.startTime = 0 dualAuthState.authComplete = false // 重置认证完成标志 if (dualAuthState.timeout) { std.clearTimeout(dualAuthState.timeout) dualAuthState.timeout = null } } /** * 处理双人认证超时 */ function handleDualAuthTimeout() { try { // 保存认证记录 if (dualAuthState.firstUserId && !dualAuthState.authComplete) { // 查询第一用户的详细信息,获取身份类型 let firstUserType = dualAuthState.firstUserType || 0 if (!firstUserType) { let res = sqliteService.d1_person.findByUserId(dualAuthState.firstUserId) if (res.length > 0) { try { firstUserType = JSON.parse(res[0].extra).type || 0 } catch (error) { logger.error("无第一用户类型") } } } // 查询第二用户的详细信息,获取身份类型 let secondUserType = dualAuthState.secondUserType || 0 if (dualAuthState.secondUserId && !secondUserType) { let res = sqliteService.d1_person.findByUserId(dualAuthState.secondUserId) if (res.length > 0) { try { secondUserType = JSON.parse(res[0].extra).type || 0 } catch (error) { logger.error("无第二用户类型") } } } // 创建认证记录 const record = { id: std.genRandomStr(16), userId: dualAuthState.firstUserId, userId2: dualAuthState.secondUserId || "", type: "300", // 人脸认证(字符串格式) code: dualAuthState.firstUserFileName, // 人脸抓拍图片路径 result: 1, // 认证失败(0成功,1失败) time: Math.floor(Date.now() / 1000), // 时间戳(秒) message: "双人认证超时", extra: JSON.stringify({ name: dualAuthState.firstUserName, idCard: dualAuthState.firstUserIdCard, type: firstUserType }), extra2: dualAuthState.secondUserId ? JSON.stringify({ name: dualAuthState.secondUserName || "", idCard: dualAuthState.secondUserIdCard || "", type: secondUserType }) : "", permissionId: firstUserType.toString(), permissionId2: dualAuthState.secondUserId ? secondUserType.toString() : "" } // 保存到数据库 sqliteService.d1_pass_record.save(record) logger.info(`[faceService]: 保存双人认证超时记录: ${JSON.stringify(record)}`) } // 清除认证状态 clearDualAuthState() // 播放失败提示音 driver.alsa.play(`/app/code/resource/${config.get("base.language") == "CN" ? "CN" : "EN"}/wav/verify_300_f.wav`) // 语音提示认证失败 // driver.alsa.ttsPlay("双人认证超时,认证失败") // 触发认证失败事件 bus.fire("showAccessResult", { faceAuth: false, gasConcentration: true, accessAllowed: false, message: "*双人认证超时,认证失败*" }) // 触发通行失败事件,通知UI重置 bus.fire("accessRes", false) } catch (error) { logger.error(`[faceService]: 处理双人认证超时错误: ${error.message}`) } } /** * 处理双人认证逻辑 * @param {string} userId - 当前用户ID * @param {string} userName - 当前用户姓名 * @param {string} fileName - 人脸图片文件路径 */ function handleDualAuth(userId, userName, fileName) { try { if (!dualAuthState.firstUserId) { // 第一次认证 dualAuthState.firstUserId = userId dualAuthState.firstUserName = userName dualAuthState.firstUserFileName = fileName // 查询第一用户的详细信息,获取身份证号和身份类型 let res = sqliteService.d1_person.findByUserId(userId) if (res.length > 0) { let idCard let userType = 0 try { idCard = JSON.parse(res[0].extra).idCard userType = JSON.parse(res[0].extra).type || 0 } catch (error) { logger.error("无第一用户身份证号或类型") } dualAuthState.firstUserIdCard = idCard dualAuthState.firstUserType = userType } dualAuthState.startTime = Date.now() // 清除之前的定时器 if (dualAuthState.timeout) { std.clearTimeout(dualAuthState.timeout) } // 设置1分钟超时 dualAuthState.timeout = std.setTimeout(() => { // 超时处理,视为认证失败 handleDualAuthTimeout() }, 60000) // 语音提示-请第二用户进行认证 driver.alsa.play(`/app/code/resource/${config.get("base.language") == "CN" ? "CN" : "EN"}/wav/user2.wav`) } else { // 第二次认证 if (userId !== dualAuthState.firstUserId) { // 不同用户,认证通过 // 查询第二用户的详细信息,获取身份证号和身份类型 let secondUserIdCard = "" let secondUserType = 0 let res = sqliteService.d1_person.findByUserId(userId) if (res.length > 0) { try { secondUserIdCard = JSON.parse(res[0].extra).idCard secondUserType = JSON.parse(res[0].extra).type || 0 } catch (error) { logger.error("无第二用户身份证号或类型") } } // 保存双人认证状态信息,用于UI更新 let dualAuthInfo = { firstUserId: dualAuthState.firstUserId, firstUserName: dualAuthState.firstUserName, secondUserId: userId, secondUserName: userName, secondUserIdCard: secondUserIdCard, secondUserType: secondUserType } // 保存第一用户的人脸图片路径 let firstUserFileName = dualAuthState.firstUserFileName // 设置认证完成标志 dualAuthState.authComplete = true clearDualAuthState() // 语音提示-双人认证通过 driver.alsa.play(`/app/code/resource/${config.get("base.language") == "CN" ? "CN" : "EN"}/wav/user2_s.wav`) // 通行认证处理,传递双人认证信息 bus.fire("access", { data: { type: "300", code: userId, dualAuthInfo: dualAuthInfo, authComplete: true, firstUserFileName: firstUserFileName }, fileName: fileName }) } else { // 同一用户,重新开始 dualAuthState.firstUserId = userId dualAuthState.firstUserName = userName // 查询用户的详细信息,获取身份证号和身份类型 let idCard let userType = 0 let res = sqliteService.d1_person.findByUserId(userId) if (res.length > 0) { try { idCard = JSON.parse(res[0].extra).idCard userType = JSON.parse(res[0].extra).type || 0 } catch (error) { logger.error("无用户身份证号或类型") } dualAuthState.firstUserIdCard = idCard dualAuthState.firstUserType = userType } dualAuthState.startTime = Date.now() // 清除之前的定时器 if (dualAuthState.timeout) { std.clearTimeout(dualAuthState.timeout) } // 重新设置1分钟超时 dualAuthState.timeout = std.setTimeout(() => { // 超时处理,视为认证失败 handleDualAuthTimeout() }, 60000) // 语音提示-请第二用户进行认证 driver.alsa.play(`/app/code/resource/${config.get("base.language") == "CN" ? "CN" : "EN"}/wav/user2.wav`) } } } catch (error) { logger.error(`[faceService]: 处理双人认证错误: ${error.message}`) // 清除认证状态 clearDualAuthState() } } /** * 人脸注册错误枚举 * 包含各种注册失败的错误码和对应的错误信息 */ faceService.regErrorEnum = { "callback": { title: "注册回调状态枚举", "-1": "faceService.contrastFailure", // 对比失败 "-2": "faceService.scalingFailure", // 缩放失败 "-3": "faceService.failedToSavePicture", // 保存图片失败 "-4": "faceService.convertToBase64Fail", // 转换为base64失败 }, "feature": { title: "特征值注册状态枚举", "-1": "faceService.base64DecodingFail", // base64解码失败 "-10": "faceService.contrastFailure", // 对比失败 "-11": "faceService.similarityOverheight", // 相似度过高 }, "picture": { title: "图片注册状态枚举", "-1": "faceService.fileDoesNotExist", // 文件不存在 "-2": "faceService.theImageFormatIsNotSupported", // 图片格式不支持 "-3": "faceService.pictureReadFailure", // 图片读取失败 "-4": "faceService.thePictureSizeDoesNotMatch", // 图片尺寸不匹配 "-5": "faceService.imageParsingFailure", // 图片解析失败 "-6": "faceService.imageYUVProcessingFailed", // 图片YUV处理失败 "-7": "faceService.failedToConvertJpegToImage", // JPEG转换为图片失败 "-8": "faceService.faceInformationExtractionFailed", // 人脸信息提取失败 "-9": "faceService.theFaceIsNotUnique", // 人脸不唯一 "-10": "faceService.contrastFailure", // 对比失败 "-11": "faceService.similarityOverheight", // 相似度过高 } } export default faceService