/**
|
* 人脸识别服务模块
|
* 处理人脸识别相关的业务逻辑,包括人脸注册和人脸比对
|
*/
|
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
|