1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
/**
 * 人脸识别服务模块
 * 处理人脸识别相关的业务逻辑,包括人脸注册和人脸比对
 */
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