1.更新vf107代码,更新自动上报通行记录接口、人员查询接口、人员添加接口
已修改26个文件
已添加1个文件
3753 ■■■■ 文件已修改
vf107/dxmodules/dxDriver.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/dxmodules/dxOta.js 189 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/resource/langPack.js 49 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/config.json 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/controller.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/driver.js 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/main.js 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/screen.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/service/accessService.js 272 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/service/api.js 444 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/service/configService.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/service/demo.js 1832 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/service/grainService.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/service/httpService.js 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/service/mqttService.js 434 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/service/sqliteService.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/view/config/menu/doorControlView.js 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/view/config/menu/localUser/localUserAddView.js 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/view/config/menu/localUserView.js 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/view/config/menu/networkSettingView.js 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/view/config/menu/recordQuery/recordQueryDetailView.js 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/view/config/menu/systemSetting/displaySettingView.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/view/config/menu/systemSetting/passwordOpenDoorSettingView.js 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/view/mainView.js 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/worker/mqttWorker.js 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/worker/netWorker.js 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/src/worker/passRecordWorker.js 76 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vf107/dxmodules/dxDriver.js
@@ -7,7 +7,7 @@
 */
dxDriver.DRIVER = {
    // Driver model
    MODEL:         "vf105"
    MODEL:         "vf107"
}
/**
vf107/dxmodules/dxOta.js
@@ -1,20 +1,20 @@
/**
 * OTA Module
 * Features:
 * - HTTP online and local file upgrades
 * - Automatic MD5 integrity verification
 * - Pre-upgrade disk space validation
 * OTA æ¨¡å—
 * åŠŸèƒ½ï¼š
 * - HTTP åœ¨çº¿å‡çº§å’Œæœ¬åœ°æ–‡ä»¶å‡çº§
 * - è‡ªåЍ MD5 å®Œæ•´æ€§éªŒè¯
 * - å‡çº§å‰ç£ç›˜ç©ºé—´éªŒè¯
 
 * Usage:
  1. Build code into an app package. Click `Package` in VSCode DejaOS Plugin to generate a .dpk file in .temp folder.
  2. Upload the .dpk file (zip format) to a web server and get the download URL.
  3. Send the download URL and MD5 checksum to the device app.
   - Encode URL and MD5 as QR code for device scanning,
   - Or use other methods (Bluetooth, MQTT, RS485, etc.).
  4. Device downloads and verifies package integrity using MD5.
  5. Extract package to stable directory and reboot device.
  6. After reboot, DejaOS extracts package and overwrites existing code.
 * Doc/Demo: https://github.com/DejaOS/DejaOS
 * ä½¿ç”¨æ–¹æ³•:
 * 1. å°†ä»£ç æž„建为应用包。在 VSCode DejaOS æ’件中点击 `Package` ç”Ÿæˆ .temp æ–‡ä»¶å¤¹ä¸­çš„ .dpk æ–‡ä»¶ã€‚
 * 2. å°† .dpk æ–‡ä»¶ï¼ˆzip æ ¼å¼ï¼‰ä¸Šä¼ åˆ° web æœåŠ¡å™¨å¹¶èŽ·å–ä¸‹è½½ URL。
 * 3. å°†ä¸‹è½½ URL å’Œ MD5 æ ¡éªŒå’Œå‘送到设备应用。
 *   - å°† URL å’Œ MD5 ç¼–码为二维码供设备扫描,
 *   - æˆ–使用其他方法(蓝牙、MQTT、RS485 ç­‰ï¼‰ã€‚
 * 4. è®¾å¤‡ä¸‹è½½å¹¶ä½¿ç”¨ MD5 éªŒè¯åŒ…完整性。
 * 5. å°†åŒ…提取到稳定目录并重启设备。
 * 6. é‡å¯åŽï¼ŒDejaOS æå–包并覆盖现有代码。
 * æ–‡æ¡£/示例:https://github.com/DejaOS/DejaOS
 */
import log from './dxLogger.js'
import com from './dxCommon.js'
@@ -26,24 +26,24 @@
ota.UPGRADE_TEMP = '/upgrades.temp'
ota.DF_CMD = `df -k / | awk 'NR==2 {print $4}'`
/**
 * Download upgrade package via HTTP
 * @param {string} url Required. HTTP URL for downloading the upgrade package
 * @param {string} md5 Required. MD5 hash for integrity verification (32-char lowercase hex)
 * @param {number} timeout Optional. Download timeout in seconds (default: 60)
 * @param {number} size Optional. Package size in KB for disk space validation
 * @param {Object} [httpOpts] Additional request opts
 * é€šè¿‡ HTTP ä¸‹è½½å‡çº§åŒ…
 * @param {string} url å¿…填。下载升级包的 HTTP URL
 * @param {string} md5 å¿…填。用于完整性验证的 MD5 å“ˆå¸Œå€¼ï¼ˆ32 ä½å°å†™åå…­è¿›åˆ¶ï¼‰
 * @param {number} timeout å¯é€‰ã€‚下载超时时间(秒),默认:60
 * @param {number} size å¯é€‰ã€‚用于磁盘空间验证的包大小(KB)
 * @param {Object} [httpOpts] é¢å¤–的请求选项
 */
ota.updateHttp = function (url, md5, timeout = 60, size, httpOpts) {
    if (!url || !md5) {
        throw new Error("'url' and 'md5' parameters are required")
        throw new Error("'url' å’Œ 'md5' å‚数是必填的")
    }
    if (size && (typeof size != 'number')) {
        throw new Error("'size' parameter must be a number")
        throw new Error("'size' å‚数必须是数字")
    }
    // 1. Check available disk space
    // 1. æ£€æŸ¥å¯ç”¨ç£ç›˜ç©ºé—´
    checkDiskSpace(size)
    // 2. Download file to temporary directory
    com.systemBrief(`rm -rf ${ota.UPGRADE_TARGET} && rm -rf ${ota.UPGRADE_TEMP} `) // Clean up existing files
    // 2. ä¸‹è½½æ–‡ä»¶åˆ°ä¸´æ—¶ç›®å½•
    com.systemBrief(`rm -rf ${ota.UPGRADE_TARGET} && rm -rf ${ota.UPGRADE_TEMP} `) // æ¸…理现有文件
    log.info("download url:" + url)
    let downloadRet = http.download(url, ota.UPGRADE_TEMP, timeout * 1000, httpOpts)
    log.info("download result:" + JSON.stringify(downloadRet))
@@ -51,46 +51,50 @@
    let fileExist = (os.stat(ota.UPGRADE_TEMP)[1] === 0)
    if (!fileExist) {
        com.systemBrief(`rm -rf ${ota.UPGRADE_TARGET} && rm -rf ${ota.UPGRADE_TEMP} `)
        throw new Error('Download failed. Please check the URL: ' + url)
        log.info("下载失败。 url:" + url)
        throw new Error('下载失败。请检查 URL: ' + url)
    }
    // 3. Verify MD5 checksum
    log.info("verify md5:" + md5)
    log.info("verify md5 result:" + verifyMD5(ota.UPGRADE_TEMP, md5))
    // 3. éªŒè¯ MD5 æ ¡éªŒå’Œ
    if (!verifyMD5(ota.UPGRADE_TEMP, md5)) {
        com.systemBrief(`rm -rf ${ota.UPGRADE_TARGET} && rm -rf ${ota.UPGRADE_TEMP} `)
        throw new Error('MD5 verification failed')
        throw new Error('MD5 éªŒè¯å¤±è´¥')
    }
    // 4. Move verified package to upgrade directory
    // 4. å°†éªŒè¯é€šè¿‡çš„包移动到升级目录
    com.systemBrief(`mv ${ota.UPGRADE_TEMP} ${ota.UPGRADE_TARGET} `)
    com.systemBrief(`sync`)
}
/**
 * Upgrade from local file
 * Use this when you've already downloaded the package via custom methods.
 * @param {string} path Required. Path to the upgrade package
 * @param {string} md5 Required. MD5 hash for integrity verification (32-char lowercase hex)
 * @param {number} size Optional. Package size in KB for disk space validation
 * ä»Žæœ¬åœ°æ–‡ä»¶å‡çº§
 * å½“你已经通过自定义方法下载了包时使用此方法。
 * @param {string} path å¿…填。升级包的路径
 * @param {string} md5 å¿…填。用于完整性验证的 MD5 å“ˆå¸Œå€¼ï¼ˆ32 ä½å°å†™åå…­è¿›åˆ¶ï¼‰
 * @param {number} size å¯é€‰ã€‚用于磁盘空间验证的包大小(KB)
 */
ota.updateFile = function (path, md5, size) {
    if (!path || !md5) {
        throw new Error("'path' and 'md5' parameters are required")
        throw new Error("'path' å’Œ 'md5' å‚数是必填的")
    }
    if (size && (typeof size != 'number')) {
        throw new Error("'size' parameter must be a number")
        throw new Error("'size' å‚数必须是数字")
    }
    let fileExist = (os.stat(path)[1] === 0)
    if (!fileExist) {
        throw new Error('File not found: ' + path)
        throw new Error('文件未找到: ' + path)
    }
    // 1. Check available disk space
    // 1. æ£€æŸ¥å¯ç”¨ç£ç›˜ç©ºé—´
    checkDiskSpace(size)
    // 2. Verify MD5 checksum
    if (!verifyMD5(path, md5)) {
        throw new Error('MD5 verification failed')
    }
    // 2. éªŒè¯ MD5 æ ¡éªŒå’Œ
    // if (!verifyMD5(path, md5)) {
    //     throw new Error('MD5 éªŒè¯å¤±è´¥')
    // }
    // 3. Move package to upgrade directory
    // 3. å°†åŒ…移动到升级目录
    com.systemBrief(`mv ${path} ${ota.UPGRADE_TARGET} `)
    com.systemBrief(`sync`)
}
@@ -99,7 +103,7 @@
    if (requiredKb) {
        const df = parseInt(com.systemWithRes(ota.DF_CMD, 1000))
        if (df < 3 * requiredKb) {
            throw new Error('Insufficient disk space for upgrade')
            throw new Error('升级磁盘空间不足')
        }
    }
}
@@ -107,47 +111,48 @@
function verifyMD5(filePath, expectedMD5) {
    const hash = com.md5HashFile(filePath)
    const actualMD5 = hash.map(v => v.toString(16).padStart(2, '0')).join('')
    log.info("actualMD5:" + actualMD5)
    return actualMD5 === expectedMD5
}
/**
 * Trigger device reboot
 * Call this after successful upgrade to apply changes.
 * è§¦å‘设备重启
 * åœ¨æˆåŠŸå‡çº§åŽè°ƒç”¨æ­¤æ–¹æ³•ä»¥åº”ç”¨æ›´æ”¹ã€‚
 */
ota.reboot = function () {
    com.asyncReboot(2)
}
//-------------------------DEPRECATED-------------------
//-------------------------已废弃-------------------
ota.OTA_ROOT = '/ota'
ota.OTA_RUN = ota.OTA_ROOT + '/run.sh'
/**
 * @deprecated Use updateHttp() instead
 * Legacy upgrade method with custom script support.
 * Downloads, extracts, and executes custom upgrade scripts.
 * @param {string} url Required. HTTP URL for downloading the upgrade package
 * @param {string} md5 Required. MD5 hash for integrity verification (32-char lowercase hex)
 * @param {number} size Optional. Package size in KB for disk space validation
 * @param {string} shell Optional. Custom upgrade script content
 * @param {number} timeout Optional. Connection timeout in seconds (default: 3)
 * @deprecated ä½¿ç”¨ updateHttp() ä»£æ›¿
 * æ”¯æŒè‡ªå®šä¹‰è„šæœ¬çš„æ—§å‡çº§æ–¹æ³•ã€‚
 * ä¸‹è½½ã€æå–并执行自定义升级脚本。
 * @param {string} url å¿…填。下载升级包的 HTTP URL
 * @param {string} md5 å¿…填。用于完整性验证的 MD5 å“ˆå¸Œå€¼ï¼ˆ32 ä½å°å†™åå…­è¿›åˆ¶ï¼‰
 * @param {number} size å¯é€‰ã€‚用于磁盘空间验证的包大小(KB)
 * @param {string} shell å¯é€‰ã€‚自定义升级脚本内容
 * @param {number} timeout å¯é€‰ã€‚连接超时时间(秒),默认:3
 */
ota.update = function (url, md5, size, shell, timeout = 3) {
    if (!url || !md5) {
        throw new Error("'url' and 'md5' parameters are required")
        throw new Error("'url' å’Œ 'md5' å‚数是必填的")
    }
    if (size && (typeof size != 'number')) {
        throw new Error("'size' parameter must be a number")
        throw new Error("'size' å‚数必须是数字")
    }
    // 1. Check available disk space
    // 1. æ£€æŸ¥å¯ç”¨ç£ç›˜ç©ºé—´
    let df = parseInt(com.systemWithRes(ota.DF_CMD, 1000))
    if (size) {
        if (df < (3 * size)) { // Require 3x package size for extraction
            throw new Error('Insufficient disk space for upgrade')
        if (df < (3 * size)) { // éœ€è¦ 3 å€åŒ…大小用于提取
            throw new Error('升级磁盘空间不足')
        }
    }
    // 2. Download to specific directory
    // 2. ä¸‹è½½åˆ°ç‰¹å®šç›®å½•
    const firmware = ota.OTA_ROOT + '/download.zip'
    const temp = ota.OTA_ROOT + '/temp'
    com.systemBrief(`rm -rf ${ota.OTA_ROOT} && mkdir ${ota.OTA_ROOT} `) // Clean and create directory
    com.systemBrief(`rm -rf ${ota.OTA_ROOT} && mkdir ${ota.OTA_ROOT} `) // æ¸…理并创建目录
    let download = `wget --no-check-certificate --timeout=${timeout} -c "${url}" -O ${firmware} 2>&1`
    com.systemBrief(download, 1000)
    let fileExist = (os.stat(firmware)[1] === 0)
@@ -158,65 +163,65 @@
    fileExist = (os.stat(firmware)[1] === 0)
    if (!fileExist) {
        log.error("download result" + downloadRet)
        throw new Error('Download failed. Please check the URL: ' + url)
        throw new Error('下载失败。请检查 URL: ' + url)
    }
    // 3. Verify MD5 checksum
    // 3. éªŒè¯ MD5 æ ¡éªŒå’Œ
    let md5Hash = com.md5HashFile(firmware)
    md5Hash = md5Hash.map(v => v.toString(16).padStart(2, 0)).join('')
    if (md5Hash != md5) {
        log.error("download result" + downloadRet)
        throw new Error('MD5 verification failed')
        throw new Error('MD5 éªŒè¯å¤±è´¥')
    }
    // 4. Extract package
    // 4. æå–包
    com.systemBrief(`mkdir ${temp} && unzip -o ${firmware} -d ${temp}`)
    // 5. Execute custom upgrade script if present
    // 5. å¦‚果存在,执行自定义升级脚本
    const custom_update = temp + '/custom_update.sh'
    if (os.stat(custom_update)[1] === 0) {
        com.systemBrief(`chmod +x ${custom_update}`)
        com.systemWithRes(`${custom_update}`)
    }
    // 6. Create upgrade script
    // 6. åˆ›å»ºå‡çº§è„šæœ¬
    if (!shell) {
        // Default: copy files and clean up
        // é»˜è®¤ï¼šå¤åˆ¶æ–‡ä»¶å¹¶æ¸…理
        shell = `cp -r ${temp}/* /app/code \n rm -rf ${ota.OTA_ROOT}`
    }
    com.systemBrief(`echo "${shell}" > ${ota.OTA_RUN} && chmod +x ${ota.OTA_RUN}`)
    fileExist = (os.stat(ota.OTA_RUN)[1] === 0)
    if (!fileExist) {
        throw new Error('Failed to create upgrade script')
        throw new Error('创建升级脚本失败')
    }
    com.systemWithRes(`${ota.OTA_RUN}`)
}
/**
 * @deprecated Use updateHttp() instead
 * Legacy resource upgrade for tar.xz packages.
 * Specialized for upgrading resource files only.
 * @param {string} url Required. HTTP URL for downloading the upgrade package
 * @param {string} md5 Required. MD5 hash for integrity verification (32-char lowercase hex)
 * @param {number} size Optional. Package size in KB for disk space validation
 * @param {string} shell Optional. Custom upgrade script content
 * @param {number} timeout Optional. Connection timeout in seconds (default: 3)
 * @deprecated ä½¿ç”¨ updateHttp() ä»£æ›¿
 * ç”¨äºŽ tar.xz åŒ…的旧资源升级。
 * ä¸“门用于仅升级资源文件。
 * @param {string} url å¿…填。下载升级包的 HTTP URL
 * @param {string} md5 å¿…填。用于完整性验证的 MD5 å“ˆå¸Œå€¼ï¼ˆ32 ä½å°å†™åå…­è¿›åˆ¶ï¼‰
 * @param {number} size å¯é€‰ã€‚用于磁盘空间验证的包大小(KB)
 * @param {string} shell å¯é€‰ã€‚自定义升级脚本内容
 * @param {number} timeout å¯é€‰ã€‚连接超时时间(秒),默认:3
 */
ota.updateResource = function (url, md5, size, shell, timeout = 3) {
    if (!url || !md5) {
        throw new Error("'url' and 'md5' parameters are required")
        throw new Error("'url' å’Œ 'md5' å‚数是必填的")
    }
    if (size && (typeof size != 'number')) {
        throw new Error("'size' parameter must be a number")
        throw new Error("'size' å‚数必须是数字")
    }
    // 1. Check available disk space
    // 1. æ£€æŸ¥å¯ç”¨ç£ç›˜ç©ºé—´
    let df = parseInt(com.systemWithRes(ota.DF_CMD, 1000))
    if (size) {
        if (df < (3 * size)) { // Require 3x package size for extraction
            throw new Error('Insufficient disk space for upgrade')
        if (df < (3 * size)) { // éœ€è¦ 3 å€åŒ…大小用于提取
            throw new Error('升级磁盘空间不足')
        }
    }
    // 2. Download to specific directory
    // 2. ä¸‹è½½åˆ°ç‰¹å®šç›®å½•
    const firmware = ota.OTA_ROOT + '/download.tar.xz'
    const temp = ota.OTA_ROOT + '/temp'
    com.systemBrief(`rm -rf ${ota.OTA_ROOT} && mkdir ${ota.OTA_ROOT} `) // Clean and create directory
    com.systemBrief(`rm -rf ${ota.OTA_ROOT} && mkdir ${ota.OTA_ROOT} `) // æ¸…理并创建目录
    let download = `wget --no-check-certificate --timeout=${timeout} -c "${url}" -O ${firmware} 2>&1`
    com.systemBrief(download, 1000)
    let fileExist = (os.stat(firmware)[1] === 0)
@@ -225,18 +230,18 @@
    }
    fileExist = (os.stat(firmware)[1] === 0)
    if (!fileExist) {
        throw new Error('Download failed. Please check the URL: ' + url)
        throw new Error('下载失败。请检查 URL: ' + url)
    }
    // 3. Verify MD5 checksum
    // 3. éªŒè¯ MD5 æ ¡éªŒå’Œ
    let md5Hash = com.md5HashFile(firmware)
    md5Hash = md5Hash.map(v => v.toString(16).padStart(2, 0)).join('')
    if (md5Hash != md5) {
        throw new Error('MD5 verification failed')
        throw new Error('MD5 éªŒè¯å¤±è´¥')
    }
    // 4. Extract tar.xz package
    // 4. æå– tar.xz åŒ…
    com.systemBrief(`mkdir ${temp} && tar -xJvf ${firmware} -C ${temp}`)
    // 5. Create resource upgrade script
    // 5. åˆ›å»ºèµ„源升级脚本
    if (!shell) {
        shell = `
        source=${temp}/vgapp/res/image/bk.png
@@ -264,7 +269,7 @@
    com.systemBrief(`echo "${shell}" > ${ota.OTA_RUN} && chmod +x ${ota.OTA_RUN}`)
    fileExist = (os.stat(ota.OTA_RUN)[1] === 0)
    if (!fileExist) {
        throw new Error('Failed to create upgrade script')
        throw new Error('创建升级脚本失败')
    }
    com.systemWithRes(`${ota.OTA_RUN}`)
}
vf107/resource/langPack.js
@@ -195,6 +195,7 @@
      face: "人脸",
      swipeCardRecognition: "刷卡核验",
      passwordOpenDoor: "密码开门",
      emergencyOpenDoorPassword: "应急开门密码",
      inputOriginalPassword: "请输入原登录密码",
      inputNewPassword: "请输入新密码",
      inputRepeatNewPassword: "请重复新密码",
@@ -265,10 +266,10 @@
      title: "通行记录详情",
      id: "人员1编号",
      name: "人员1姓名",
      idCard: "人员1身份证号",
      card: "人员1卡号",
      userId2: "人员2编号(第二用户)",
      name2: "人员2姓名",
      idCard2: "人员2身份证号",
      card2: "人员2卡号",
      time: "通行时间",
      result: "通行结果",
      face: "人员1人脸抓拍",
@@ -338,7 +339,7 @@
      failSimilarity: "失败,人脸相似度过高",
      failCardRepeat: "失败,卡片重复",
      failPwdRepeat: "失败,密码重复",
      typeOptions: ["普通用户", "管理员"],
      typeOptions: ["保管员", "科长"],
      finger: "指纹凭证",
      confirmFinger: "确认删除指纹凭证吗?",
      failFingerRepeat: "失败,指纹重复",
@@ -602,6 +603,7 @@
      face: "Face Only",
      swipeCardRecognition: "Card Verification",
      passwordOpenDoor: "Password Access",
      emergencyOpenDoorPassword: "Emergency Open Door Password",
      inputOriginalPassword: "Enter Current Password",
      inputNewPassword: "Enter New Password",
      inputRepeatNewPassword: "Confirm New Password",
@@ -673,10 +675,13 @@
      title: "Access Log Details",
      id: "User ID",
      name: "Name",
      idCard: "ID Number",
      card: "Card Number",
      userId2: "User ID 2",
      name2: "Name 2",
      time: "Access Time",
      result: "Result",
      face: "Face Photo",
      face2: "Face Photo 2",
    },
    voiceBroadcastView: {
      title: "Voice Settings",
@@ -1000,6 +1005,7 @@
      face: "Solo rostro",
      swipeCardRecognition: "Verificación tarjeta",
      passwordOpenDoor: "Acceso con clave",
      emergencyOpenDoorPassword: "Contraseña de emergencia",
      inputOriginalPassword: "Contraseña actual",
      inputNewPassword: "Nueva contraseña",
      inputRepeatNewPassword: "Repite contraseña",
@@ -1067,10 +1073,13 @@
      title: "Detalle registro",
      id: "ID usuario",
      name: "Nombre",
      idCard: "Documento",
      card: "Número tarjeta",
      userId2: "ID usuario 2",
      name2: "Nombre 2",
      time: "Hora",
      result: "Resultado",
      face: "Foto rostro",
      face2: "Foto rostro 2",
    },
    voiceBroadcastView: {
      title: "Voz",
@@ -1462,10 +1471,13 @@
      title: "Détail journal",
      id: "ID utilisateur",
      name: "Nom",
      idCard: "Document",
      card: "Numéro carte",
      userId2: "ID utilisateur 2",
      name2: "Nom 2",
      time: "Heure",
      result: "Résultat",
      face: "Photo visage",
      face2: "Photo visage 2",
    },
    voiceBroadcastView: {
      title: "Voix",
@@ -1857,10 +1869,13 @@
      title: "Protokoll-Detail",
      id: "Nutzer-ID",
      name: "Name",
      idCard: "Ausweis",
      card: "Kartennummer",
      userId2: "Nutzer-ID 2",
      name2: "Name 2",
      time: "Zeit",
      result: "Ergebnis",
      face: "Gesichtsfoto",
      face2: "Gesichtsfoto 2",
    },
    voiceBroadcastView: {
      title: "Stimme",
@@ -2252,10 +2267,13 @@
      title: "Деталь Ð»Ð¾Ð³Ð°",
      id: "ID",
      name: "Имя",
      idCard: "Документ",
      card: "Номер ÐºÐ°Ñ€Ñ‚Ñ‹",
      userId2: "ID 2",
      name2: "Имя 2",
      time: "Время",
      result: "Результат",
      face: "Фото Ð»Ð¸Ñ†Ð°",
      face2: "Фото Ð»Ð¸Ñ†Ð° 2",
    },
    voiceBroadcastView: {
      title: "Голос",
@@ -2647,10 +2665,13 @@
      title: "تفاصيل Ø³Ø¬Ù„",
      id: "ID",
      name: "اسم",
      idCard: "وثيقة",
      card: "رقم Ø§Ù„بطاقة",
      userId2: "ID 2",
      name2: "اسم 2",
      time: "وقت",
      result: "نتيجة",
      face: "صورة ÙˆØ¬Ù‡",
      face2: "صورة ÙˆØ¬Ù‡ 2",
    },
    voiceBroadcastView: {
      title: "صوت",
@@ -3045,10 +3066,13 @@
      title: "Detalhe registro",
      id: "ID usuário",
      name: "Nome",
      idCard: "Documento",
      card: "Número cartão",
      userId2: "ID usuário 2",
      name2: "Nome 2",
      time: "Hora",
      result: "Resultado",
      face: "Foto face",
      face2: "Foto face 2",
    },
    voiceBroadcastView: {
      title: "Voz",
@@ -3440,10 +3464,13 @@
      title: "기록 ìƒì„¸",
      id: "ID",
      name: "이름",
      idCard: "신분증",
      card: "카드 ë²ˆí˜¸",
      userId2: "ID 2",
      name2: "이름 2",
      time: "시간",
      result: "ê²°ê³¼",
      face: "얼굴 ì‚¬ì§„",
      face2: "얼굴 ì‚¬ì§„ 2",
    },
    voiceBroadcastView: {
      title: "음성",
vf107/src/config.json
@@ -65,9 +65,9 @@
    //是否第一次登录后台 0 æœªç™»å½• 1 å·²ç™»å½• // TODO è¿™ä¸ªé…ç½®é¡¹åº”该属于运行时状态,不赢该写入config.json或者写入单独分组,查询配置时做过滤
    "base.firstLogin": 0,
    // ç†„屏时间,单位分钟,0从不
    "base.screenOff": 0,
    "base.screenOff": 2,
    // å±å¹•保护,单位分钟,0从不
    "base.screensaver": 0,
    "base.screensaver": 2,
    //屏幕背光
    "base.backlight": 70,
    //白色补光灯
@@ -137,5 +137,7 @@
    // åº“区名称
    "GranaryName": "中央储备粮某某直属库",
    // HTTP接口路径
    "http.safeInputAccess": "http://192.168.1.227:80/cgi-bin/safeInputAccess"
    "http.safeInputAccess": "http://192.168.1.227:80/cgi-bin/safeInputAccess",
    // æ˜¯å¦å¼€å¯æ°”体浓度验证 1:是 0:否
    "gas.verification": 1
}
vf107/src/controller.js
@@ -31,7 +31,7 @@
    driver.nfc.loop()
    driver.gpiokey.loop()
    driver.face.loop()
    if (!driver.device.finger && (dxDriver.DRIVER.MODEL == "vf105" || dxDriver.DRIVER.MODEL == "vf114")) {
    if (!driver.device.finger && (dxDriver.DRIVER.MODEL == "vf105" || dxDriver.DRIVER.MODEL == "vf107" || dxDriver.DRIVER.MODEL == "vf114")) {
        driver.uartCode.loop()
    }
    if (dxDriver.DRIVER.MODEL == "vf202") {
@@ -74,7 +74,7 @@
            bus.fire(driver.gpiokey.RECEIVE_MSG, event)
        }
    });
    if (!driver.device.finger && (dxDriver.DRIVER.MODEL == "vf105" || dxDriver.DRIVER.MODEL == "vf114")) {
    if (!driver.device.finger && (dxDriver.DRIVER.MODEL == "vf105" || dxDriver.DRIVER.MODEL == "vf107" || dxDriver.DRIVER.MODEL == "vf114")) {
        driver.uartCode.setCallbacks({
            onMessage: (event) => {
                bus.fire(driver.uartCode.RECEIVE_MSG, event)
vf107/src/driver.js
@@ -48,7 +48,7 @@
            config.set('mqtt.clientId', uuid)
        }
        if (driver.device.finger) {
            if (dxDriver.DRIVER.MODEL == "vf105") {
            if (dxDriver.DRIVER.MODEL == "vf105" || dxDriver.DRIVER.MODEL == "vf107") {
                config.set('sys.model', "vf107")
            } else if (dxDriver.DRIVER.MODEL == "vf114") {
                config.set('sys.model', "vf124")
@@ -129,7 +129,7 @@
        if (dxDriver.DRIVER.MODEL == "vf203") {
            pwm.init(dxDriver.PWM.NIR_SUPPLEMENT_CHANNEL);
            pwm.setPower(nirLuminance, dxDriver.PWM.NIR_SUPPLEMENT_CHANNEL);
        } else if (dxDriver.DRIVER.MODEL == "vf202" || dxDriver.DRIVER.MODEL == "vf114" || dxDriver.DRIVER.MODEL == "vf105") {
        } else if (dxDriver.DRIVER.MODEL == "vf202" || dxDriver.DRIVER.MODEL == "vf114" || dxDriver.DRIVER.MODEL == "vf105" || dxDriver.DRIVER.MODEL == "vf107") {
            pwm.init(dxDriver.PWM.WHITE_SUPPLEMENT_CHANNEL);
            pwm.init(dxDriver.PWM.NIR_SUPPLEMENT_CHANNEL);
            pwm.setPower(whiteLuminance, dxDriver.PWM.WHITE_SUPPLEMENT_CHANNEL);
@@ -362,7 +362,7 @@
driver.uartCode = {
    RECEIVE_MSG: '__UART_RECEIVE_MSG__',
    init: function () {
        if (dxDriver.DRIVER.MODEL == 'vf105') {
        if (dxDriver.DRIVER.MODEL == 'vf105' || dxDriver.DRIVER.MODEL == 'vf107') {
            dxVgCode.init('/dev/ttySLB1', '115200-8-N-1')
        } else if (dxDriver.DRIVER.MODEL == 'vf114') {
            dxVgCode.init('/dev/ttySLB3', '115200-8-N-1')
vf107/src/main.js
@@ -205,31 +205,9 @@
    // æ¸…理未绑定的凭证信息
    cleanupUnboundVouchers()
    let appVersion
    let releaseTime
    if (dxDriver.DRIVER.MODEL == "vf202") {
        appVersion = 'vf202_v12_access_2.0.2'
        releaseTime = '2026-01-09 13:00:00'
    } else if (dxDriver.DRIVER.MODEL == "vf203") {
        appVersion = 'vf203_v14_access_2.0.2'
        releaseTime = '2026-02-04 14:30:00'
    } else if (dxDriver.DRIVER.MODEL == "vf114") {
        if(driver.device.finger) {
            appVersion = 'vf124_v12_access_2.0.2'
            releaseTime = '2026-03-19 13:00:00'
        } else {
            appVersion = 'vf114_v12_access_2.0.2'
            releaseTime = '2026-01-09 13:00:00'
        }
    } else if (dxDriver.DRIVER.MODEL == "vf105") {
        if(driver.device.finger) {
            appVersion = 'vf107_v12_access_2.0.2.1'
            releaseTime = '2026-03-19 13:00:00'
        } else {
            appVersion = 'vf105_v12_access_2.0.2'
            releaseTime = '2026-01-09 13:00:00'
        }
    }
    let appVersion = 'vf107_access_2.0.2.1'
    let releaseTime = '2026-03-19 13:00:00'
    config.setAndSave('sys.version', appVersion)
    config.setAndSave('sys.appVersion', appVersion)
    config.setAndSave('sys.releaseTime', releaseTime)
vf107/src/screen.js
@@ -65,7 +65,7 @@
screen.model = dxDriver.DRIVER.MODEL
screen.resourcePath = {
    imagePath: `/app/code/resource/image/${dxDriver.DRIVER.MODEL}/`
    imagePath: `/app/code/resource/image/vf105/`
}
screen.dropdownSymbol = screen.resourcePath.imagePath + '/down.png'
@@ -179,26 +179,10 @@
function getClickPoint() {
    const indev = NativeObject.APP.NativeComponents.NativeIndev
    std.setInterval(() => {
        if (dxDriver.DRIVER.MODEL == "vf203") {
            clickPoint = {
                x: Math.abs(600 - indev.lvIndevGetPointVg().y),
                y: indev.lvIndevGetPointVg().x
            }
        } else if (dxDriver.DRIVER.MODEL == "vf202") {
            clickPoint = {
                x: indev.lvIndevGetPointVg().x,
                y: indev.lvIndevGetPointVg().y
            }
        } else if (dxDriver.DRIVER.MODEL == "vf114") {
            clickPoint = {
                x: indev.lvIndevGetPointVg().x,
                y: indev.lvIndevGetPointVg().y
            }
        } else if (dxDriver.DRIVER.MODEL == "vf105") {
            clickPoint = {
                x: indev.lvIndevGetPointVg().x,
                y: indev.lvIndevGetPointVg().y
            }
        }
        if (lastClickPoint.x != clickPoint.x || lastClickPoint.y != clickPoint.y) {
@@ -1035,11 +1019,14 @@
    let param = driver.net.getNetParam()
    if (data == "connected" && param) {
        // åªæœ‰åœ¨ç½‘络配置页面不可见且用户不在编辑配置时才自动保存网络参数
        if (!networkSettingView.isVisible && !networkSettingView.isEditing) {
        config.setAndSave("net.ip", param.ip)
        config.setAndSave("net.gateway", param.gateway)
        config.setAndSave("net.mask", param.netmask)
        config.setAndSave('net.dns', param.dns)
        config.setAndSave('net.mac', screen.getNetMac())
        }
        topView.ethConnectState(true, type)
        networkSettingView.netInfo[10].label.dataI18n = "networkSettingView.networkConnected"
        if (mainView.ipInfoLbl) mainView.ipInfoLbl.text("IP:" + param.ip)
@@ -1054,8 +1041,11 @@
    }
    i18n.refreshObj(networkSettingView.netInfo[10].label)
    networkSettingView.refresh()
    // åªæœ‰åœ¨ç”¨æˆ·ä¸åœ¨ç¼–辑配置时才执行网络类型切换
    if (!networkSettingView.isEditing) {
    networkSettingView.changeNetType(type)
}
}
screen.fireNetStatus = function () {
    bus.fire(driver.mqtt.CONNECTED_CHANGED, "disconnected")
vf107/src/service/accessService.js
@@ -169,7 +169,16 @@
                    } catch (error) {
                        logger.error("无身份证号或类型")
                    }
                    data.extra = { name: res[0].name, idCard: idCard, type: userType }
                    // æ ¹æ®data.type设置正确的认证类型
                    let accessType = 0
                    if (data.type == "200") {
                        accessType = 200 // åˆ·å¡
                    } else if (data.type == "300") {
                        accessType = 300 // äººè„¸
                    } else if (data.type == "500") {
                        accessType = 500 // æŒ‡çº¹
                    }
                    data.extra = { name: res[0].name, idCard: idCard, card: data.code, type: userType, accessType: accessType }
                    data.permissionIds = res[0].permissionIds
                }
@@ -183,21 +192,39 @@
                // æŸ¥è¯¢ç¬¬ä¸€ç”¨æˆ·çš„详细信息
                let res1 = sqliteService.d1_person.findByUserId(firstUserId)
                if (res1.length > 0) {
                    // èŽ·å–ç¬¬ä¸€ç”¨æˆ·çš„å§“åã€èº«ä»½è¯å·å’Œèº«ä»½ç±»åž‹
                    // èŽ·å–ç¬¬ä¸€ç”¨æˆ·çš„å§“åã€èº«ä»½è¯å·
                    let idCard1
                    let firstUserType = 0
                    let userType1 = 0
                    try {
                        idCard1 = JSON.parse(res1[0].extra).idCard
                        firstUserType = JSON.parse(res1[0].extra).type || 0
                        userType1 = JSON.parse(res1[0].extra).type || 0
                    } catch (error) {
                        logger.error("无第一用户身份证号或类型")
                        logger.error("无第一用户身份证号")
                    }
                    // æ ¹æ®data.type设置正确的认证类型
                    let accessType1 = 0
                    if (data.type == "200") {
                        accessType1 = 200 // åˆ·å¡
                    } else if (data.type == "300") {
                        accessType1 = 300 // äººè„¸
                    } else if (data.type == "500") {
                        accessType1 = 500 // æŒ‡çº¹
                    }
                    data.userId = firstUserId
                    data.extra = { name: res1[0].name, idCard: idCard1, type: firstUserType }
                    data.extra = { name: res1[0].name, idCard: idCard1, card: data.code, type: userType1, accessType: accessType1 }
                } else {
                    // å¦‚果没有查询到第一用户信息,使用默认值
                    data.userId = firstUserId
                    data.extra = { name: data.dualAuthInfo.firstUserName, idCard: "", type: 0 }
                    // æ ¹æ®data.type设置正确的认证类型
                    let accessType1 = 0
                    if (data.type == "200") {
                        accessType1 = 200 // åˆ·å¡
                    } else if (data.type == "300") {
                        accessType1 = 300 // äººè„¸
                    } else if (data.type == "500") {
                        accessType1 = 500 // æŒ‡çº¹
                    }
                    data.extra = { name: data.dualAuthInfo.firstUserName, idCard: "", type: 0, accessType: accessType1 }
                }
                // å­˜å‚¨ç¬¬äºŒç”¨æˆ·ä¿¡æ¯
                data.userId2 = data.dualAuthInfo.secondUserId
@@ -206,19 +233,37 @@
                if (res2.length > 0) {
                    // èŽ·å–ç¬¬äºŒç”¨æˆ·çš„å§“åå’Œèº«ä»½è¯å·
                    let idCard2
                    let secondUserType = 0
                    let userType2 = 0
                    try {
                        idCard2 = JSON.parse(res2[0].extra).idCard
                        secondUserType = JSON.parse(res2[0].extra).type || 0
                        userType2 = JSON.parse(res2[0].extra).type || 0
                    } catch (error) {
                        logger.error("无第二用户身份证号或类型")
                        logger.error("无第二用户身份证号")
                    }
                    data.extra2 = { name: res2[0].name, idCard: idCard2 }
                    // æ ¹æ®data.type设置正确的认证类型
                    let accessType2 = 0
                    if (data.type == "200") {
                        accessType2 = 200 // åˆ·å¡
                    } else if (data.type == "300") {
                        accessType2 = 300 // äººè„¸
                    } else if (data.type == "500") {
                        accessType2 = 500 // æŒ‡çº¹
                    }
                    data.extra2 = { name: res2[0].name, idCard: idCard2, card: data.code2, type: userType2, accessType: accessType2 }
                    // å­˜å‚¨ç¬¬äºŒç”¨æˆ·çš„æƒé™ID(身份类型)
                    data.permissionId2 = secondUserType.toString()
                    data.permissionId2 = userType2.toString()
                } else {
                    // å¦‚果没有查询到第二用户信息,使用默认值
                    data.extra2 = { name: data.dualAuthInfo.secondUserName, idCard: "" }
                    // æ ¹æ®data.type设置正确的认证类型
                    let accessType2 = 0
                    if (data.type == "200") {
                        accessType2 = 200 // åˆ·å¡
                    } else if (data.type == "300") {
                        accessType2 = 300 // äººè„¸
                    } else if (data.type == "500") {
                        accessType2 = 500 // æŒ‡çº¹
                    }
                    data.extra2 = { name: data.dualAuthInfo.secondUserName, idCard: "", type: 0, accessType: accessType2 }
                    data.permissionId2 = ""
                }
                // å¤„理第一用户的人脸图片
@@ -323,6 +368,19 @@
                            if (authQueue.length === 1) {
                                // ç¬¬ä¸€ç”¨æˆ·è®¤è¯
                                logger.info("[access]: ä¿ç®¡å‘˜æƒé™ï¼Œéœ€è¦åŒäººè®¤è¯")
                                // ç¡®ä¿data.extra包含accessType字段
                                if (!data.extra.accessType) {
                                    // æ ¹æ®data.type设置正确的认证类型
                                    let accessType = 0
                                    if (data.type == "200") {
                                        accessType = 200 // åˆ·å¡
                                    } else if (data.type == "300") {
                                        accessType = 300 // äººè„¸
                                    } else if (data.type == "500") {
                                        accessType = 500 // æŒ‡çº¹
                                    }
                                    data.extra.accessType = accessType
                                }
                                // è§¦å‘第一用户认证成功事件,更新UI
                                bus.fire("accessSuccess", { 
                                    data: { 
@@ -413,13 +471,19 @@
                                        } catch (error) {
                                            logger.error("无第一用户身份证号或类型")
                                        }
                                        data.extra = { name: userRes[0].name, idCard: idCard1, type: firstUserType }
                                        // ç¡®ä¿ç¬¬ä¸€ç”¨æˆ·çš„信息包含accessType字段
                                        let accessType1 = firstUser.data.extra.accessType || 0
                                        data.extra = { name: userRes[0].name, idCard: idCard1, type: firstUserType, accessType: accessType1 }
                                    } else {
                                        data.extra = { name: firstUser.name, idCard: '', type: firstUser.type }
                                        // ç¡®ä¿ç¬¬ä¸€ç”¨æˆ·çš„信息包含accessType字段
                                        let accessType1 = firstUser.data.extra.accessType || 0
                                        data.extra = { name: firstUser.name, idCard: '', type: firstUser.type, accessType: accessType1 }
                                    }
                                } catch (error) {
                                    logger.error("解析第一用户信息失败")
                                    data.extra = { name: firstUser.name, idCard: '', type: firstUser.type }
                                    // ç¡®ä¿ç¬¬ä¸€ç”¨æˆ·çš„信息包含accessType字段
                                    let accessType1 = firstUser.data.extra.accessType || 0
                                    data.extra = { name: firstUser.name, idCard: '', type: firstUser.type, accessType: accessType1 }
                                }
                                // å­˜å‚¨ç¬¬äºŒç”¨æˆ·çš„人脸图片
                                if (secondUser.fileName) {
@@ -481,7 +545,7 @@
            if (!ret && config.get('mqtt.onlinecheck') == 1 && mqtt_map.get("MQTT_STATUS") == "connected") {
                logger.info("[access]: æ— æƒé™ï¼Œèµ°åœ¨çº¿éªŒè¯")
                let serialNo = std.genRandomStr(10)
                driver.mqtt.send("access_device/v2/event/access_online", JSON.stringify(mqttService.mqttReply(serialNo, data, mqttService.CODE.S_000)))
                driver.mqtt.send(`access_device/v2/event/${sn}/access_online`, JSON.stringify(mqttService.mqttReply(serialNo, data, mqttService.CODE.S_000)))
                driver.audio.play(`/app/code/resource/${language}/wav/recg.wav`)
               
                // ç­‰å¾…在线验证结果
@@ -504,6 +568,10 @@
                bleReply(data, true)
            }
            
            // æ£€æŸ¥æ˜¯å¦å¼€å¯æ°”体浓度验证
            const gasVerificationEnabled = config.get('gas.verification')
            if (gasVerificationEnabled) {
            // éªŒè¯æ°”体浓度
            grainService.checkGasConcentration(function() {
                // ä»Žå­˜å‚¨çš„æ°”体数据中获取验证结果
@@ -511,56 +579,17 @@
                
                if(gasData && gasData.data && gasData.data.status === "0") {
                    logger.info("[access]: æ°”体浓度验证合格")
                    // é€šè¡ŒæˆåŠŸå¤„ç†
                    driver.screen.accessSuccess()
                    logger.info("[access]: é€šè¡ŒæˆåŠŸ")
                    // æ˜¾ç¤ºé€šè¡ŒæˆåŠŸç»“æžœ
                    bus.fire("showAccessResult", {
                        faceAuth: true,
                        gasConcentration: true,
                        accessAllowed: true,
                        message: "*仓内气体浓度合格,允许通行*"
                    })
                    // è§¦å‘通行成功事件,通知UI更新
                    bus.fire("accessSuccess", { data: data, fileName: fileName })
                    driver.audio.play(`/app/code/resource/${language}/wav/access_s.wav`) // æ’­æ”¾è¯­éŸ³
                    driver.gpio.open() // å¼€é—¨
                    savePassPic(data, fileName) // ä¿å­˜é€šè¡Œå›¾ç‰‡
                    reply(data, true) // ä¸ŠæŠ¥é€šè¡Œè®°å½•
                    // 60秒后重置用户UI
                    std.setTimeout(() => {
                        bus.fire("accessUnlockComplete")
                    }, 60000)
                        handleAccessSuccess(data, fileName, similarity, "*仓内气体浓度合格,允许通行*")
                } else {
                    logger.info("[access]: æ°”体浓度验证不合格")
                    // é€šè¡Œå¤±è´¥å¤„理
                    driver.screen.accessFail()
                    logger.error("[access]: é€šè¡Œå¤±è´¥")
                    // è§¦å‘失败弹窗
                    bus.fire("showAccessResult", {
                        faceAuth: true,
                        gasConcentration: false,
                        accessAllowed: false,
                        message: "*仓内气体浓度不合格,禁止通行*"
                    })
                    // è§¦å‘通行成功事件,更新用户UI
                    bus.fire("accessSuccess", { data: data, fileName: fileName })
                    if (utils.isEmpty(similarity)) {
                        driver.audio.play(`/app/code/resource/${language}/wav/access_f.wav`)
                    }
                    // 60秒后重置用户UI
                    std.setTimeout(() => {
                        bus.fire("accessUnlockComplete")
                    }, 60000)
                    savePassPic(data, fileName)
                    // æ·»åŠ æ°”ä½“æµ“åº¦å¤±è´¥ä¿¡æ¯
                    data.message = "气体浓度不合格"
                    reply(data, false) // ä¸ŠæŠ¥é€šè¡Œè®°å½•
                        handleAccessFail(data, fileName, similarity, "*仓内气体浓度不合格,禁止通行*", "气体浓度不合格")
                }
            })
            } else {
                // è·³è¿‡æ°”体浓度验证,直接通行成功
                logger.info("[access]: æ°”体浓度验证已关闭,跳过验证")
                handleAccessSuccess(data, fileName, similarity, "*允许通行*")
            }
        } else {
            if (data.type == 500) {
@@ -771,24 +800,55 @@
        sqliteService.d1_pass_record.save(record)
    }
    let accessRecord = {
        userId: record.userId,
        type: record.type,
        result: record.result,
        timeStamp: record.timeStamp || 0,
        result: record.result || 0,
        error: record.message || "",
        permissionId: record.permissionId || "",
        door: record.door || "",
        users: [
            {
                userId: record.userId || "",
        name: data.extra && data.extra.name ? data.extra.name : "",
        timeStamp: record.timeStamp,
        extra: {},
        error: record.message
                keyId: record.keyId || "",
                userType: data.extra && data.extra.type ? data.extra.type : 0,
                accessType: data.extra && data.extra.accessType || ""
    }
        ]
    }
    // å¦‚果是双人认证,添加第二个用户信息
    if (record.userId2) {
        let extra2 = record.extra2 ? JSON.parse(record.extra2) : ""
        let secondUser = {
            userId: record.userId2 || "",
            name: extra2 && extra2.name ? extra2.name : "",
            userType: extra2 && extra2.type ? extra2.type : 0,
            accessType: extra2.accessType || ""
        }
        // å¦‚果有卡号信息,添加card字段
        if (extra2 && extra2.card) {
            secondUser.card = extra2.card
        }
        accessRecord.users.push(secondUser)
    }
    let serialNo = record.id
    if (record.type == 300) {
        if (config.get('sys.strangerImage') && config.get('access.uploadToCloud')) {
            accessRecord.code = dxCommonUtils.fs.fileToBase64(record.code)
            accessRecord.users[0].code = dxCommonUtils.fs.fileToBase64(record.code)
        } else {
            accessRecord.code = ""
            accessRecord.users[0].code = ""
        }
        // å¦‚果有第二个用户的二维码
        if (record.code2) {
            accessRecord.users[1] = accessRecord.users[1] || {}
            accessRecord.users[1].code = record.code2
        }
    }
    let payload = mqttService.mqttReply(serialNo, [accessRecord], mqttService.CODE.S_000)
    driver.mqtt.send("access_device/v2/event/access", JSON.stringify(payload))
    // ä¸å†ç›´æŽ¥å‘送MQTT消息,由passRecordWorker.js统一处理上报
    // let payload = mqttService.mqttReply(serialNo, [accessRecord], mqttService.CODE.S_000)
    // driver.mqtt.send("access_device/v2/event/access", JSON.stringify(payload))
}
// è“ç‰™å›žå¤
@@ -801,5 +861,71 @@
    driver.uartBle.send("0101" + dxCommonUtils.codec.strToUtf8Hex(replyData))
}
function handleAccessSuccess(data, fileName, similarity, message) {
    let language = config.get("base.language") || "CN";
    driver.screen.accessSuccess()
    logger.info("[access]: é€šè¡ŒæˆåŠŸ")
    // ç¡®ä¿ç¬¬ä¸€ç”¨æˆ·çš„extra包含accessType字段
    if (!data.extra.accessType) {
        // æ ¹æ®data.type设置正确的认证类型
        let accessType = 0
        if (data.type == "200" || data.type == 200) {
            accessType = 200 // åˆ·å¡
        } else if (data.type == "300" || data.type == 300) {
            accessType = 300 // äººè„¸
        } else if (data.type == "500" || data.type == 500) {
            accessType = 500 // æŒ‡çº¹
        }
        data.extra.accessType = accessType
    }
    // æ˜¾ç¤ºé€šè¡ŒæˆåŠŸç»“æžœ
    bus.fire("showAccessResult", {
        faceAuth: true,
        gasConcentration: true,
        accessAllowed: true,
        message: message
    })
    // è§¦å‘通行成功事件,通知UI更新
    bus.fire("accessSuccess", { data: data, fileName: fileName })
    driver.audio.play(`/app/code/resource/${language}/wav/access_s.wav`) // æ’­æ”¾è¯­éŸ³
    driver.gpio.open() // å¼€é—¨
    savePassPic(data, fileName) // ä¿å­˜é€šè¡Œå›¾ç‰‡
    reply(data, true) // ä¸ŠæŠ¥é€šè¡Œè®°å½•
    // 60秒后重置用户UI
    std.setTimeout(() => {
        bus.fire("accessUnlockComplete")
    }, 60000)
}
function handleAccessFail(data, fileName, similarity, message, errorMessage) {
    let language = config.get("base.language") || "CN";
    driver.screen.accessFail()
    logger.error("[access]: é€šè¡Œå¤±è´¥")
    // è§¦å‘失败弹窗
    bus.fire("showAccessResult", {
        faceAuth: true,
        gasConcentration: false,
        accessAllowed: false,
        message: message
    })
    // è§¦å‘通行成功事件,更新用户UI
    bus.fire("accessSuccess", { data: data, fileName: fileName })
    if (utils.isEmpty(similarity)) {
        driver.audio.play(`/app/code/resource/${language}/wav/access_f.wav`)
    }
    // 60秒后重置用户UI
    std.setTimeout(() => {
        bus.fire("accessUnlockComplete")
    }, 60000)
    savePassPic(data, fileName)
    // æ·»åŠ å¤±è´¥ä¿¡æ¯
    data.message = errorMessage
    reply(data, false) // ä¸ŠæŠ¥é€šè¡Œè®°å½•
}
export default accessService
vf107/src/service/api.js
@@ -202,7 +202,12 @@
    if (data.type == 0) {
        try {
            driver.screen.upgrade({ title: "confirm.upgrade", content: "confirm.upgrading" })
            ota.updateHttp(data.url, data.md5,  100)
            // ç¡®ä¿URL包含协议前缀
            let url = data.url
            if (!url.startsWith('http://') && !url.startsWith('https://')) {
                url = 'http://' + url
            }
            ota.updateHttp(url, data.md5,  300)
            driver.screen.upgrade({ title: "confirm.upgrade", content: "confirm.upgradeSuccess" })
        } catch (error) {
            driver.screen.upgrade({ title: "confirm.upgrade", content: "confirm.upgradeFail" })
@@ -315,7 +320,18 @@
                let person = sqliteService.d1_person.findByUserId(record.userId)
                if (person.length) {
                    record.name = person[0].name
                    // å°†extra字段从JSON字符串解析为JSON对象
                    if (person[0].extra && typeof person[0].extra === 'string') {
                        try {
                            record.extra = JSON.parse(person[0].extra)
                        } catch (error) {
                            // å¦‚果解析失败,保持原样
                            console.error('解析extra字段失败:', error)
                    record.extra = person[0].extra
                        }
                    } else {
                        record.extra = person[0].extra
                    }
                }
            }
        })
@@ -396,6 +412,7 @@
            userId: person.userId || 'unknown',
            errmsg: ''
        }
        // å¢žå¼ºæ•°æ®éªŒè¯
        if (!person.userId || !person.name) {
            errorItem.errmsg = "userId or name cannot be empty"
            errors.push(errorItem)
@@ -406,11 +423,15 @@
            errors.push(errorItem)
            continue
        }
        // æž„建人员记录
        let record = {}
        record.userId = person.userId
        record.name = person.name
        record.extra = isEmpty(person.extra) ? JSON.stringify({}) : JSON.stringify(person.extra)
        record.permissionIds = person.permissionIds ? person.permissionIds.join(",") : ""
        // ä¿å­˜äººå‘˜ä¿¡æ¯
        let ret = sqliteService.d1_person.save(record)
        if (ret != 0) {
            sqliteService.d1_person.deleteByUserId(record.userId)
@@ -420,6 +441,197 @@
                errors.push(errorItem)
                continue
            }
        }
        // å¤„理人脸信息
        if (person.face) {
            try {
                logger.info('[api] å¼€å§‹å¤„理人脸信息:', person.userId)
                let faceFilePath = person.face
                // æ£€æŸ¥æ˜¯å¦æ˜¯base64编码的图片数据
                if (person.face.startsWith('data:image/')) {
                    logger.info('[api] æ£€æµ‹åˆ°base64编码的图片数据')
                    // æå–base64数据
                    let base64Data = person.face.split(',')[1]
                    // åˆ›å»ºä¸´æ—¶æ–‡ä»¶
                    faceFilePath = '/data/user/temp_face_' + person.userId + '.jpg'
                    std.ensurePathExists(faceFilePath)
                    // å°†base64数据转换为文件
                    dxCommonUtils.fs.base64ToFile(faceFilePath, base64Data)
                    logger.info('[api] å·²å°†base64数据保存为文件:', faceFilePath)
                } else {
                    errorItem.errmsg = "数据格式错误,face字段必须是base64编码的图片数据"
                    errors.push(errorItem)
                    continue
                }
                // æ³¨å†Œäººè„¸
                logger.info('[api] å¼€å§‹æ³¨å†Œäººè„¸:', person.userId)
                let featureFile = driver.face.getFeaByFile(faceFilePath)
                let addFeaRes = driver.face.addFea(person.userId, featureFile.feature)
                if (addFeaRes == 0) {
                    // æ³¨å†ŒæˆåŠŸåŽç§»åŠ¨å›¾ç‰‡åˆ°ç”¨æˆ·ç›®å½•
                    let src = "/data/user/" + person.userId + "/register.jpg"
                    std.ensurePathExists(src)
                    logger.info('[api] ç§»åŠ¨äººè„¸å›¾ç‰‡åˆ°ç”¨æˆ·ç›®å½•:', faceFilePath, '->', src)
                    dxos.systemBrief('mv ' + faceFilePath + " " + src)
                    // ä¿å­˜äººè„¸å‡­è¯
                    logger.info('[api] ä¿å­˜äººè„¸å‡­è¯:', person.userId)
                    let voucherRet = sqliteService.d1_voucher.save({
                        keyId: std.genRandomStr(32),
                        type: "300",
                        code: src,
                        userId: person.userId,
                        extra: JSON.stringify({ faceType: 0 })
                    });
                    logger.info('[api] ä¿å­˜äººè„¸å‡­è¯ç»“æžœ:', voucherRet)
                } else {
                    logger.error('[api] æ³¨å†Œäººè„¸å¤±è´¥ï¼Œè¿”回码:', addFeaRes)
                    errorItem.errmsg = "注册人脸失败,返回码:" + addFeaRes
                    errors.push(errorItem)
                    continue
                }
            } catch (error) {
                logger.error('[api] å¤„理人脸信息错误:', error)
                errorItem.errmsg = "处理人脸信息错误: " + error.message
                errors.push(errorItem)
                continue
            } finally {
                logger.info('[api] äººè„¸ä¿¡æ¯å¤„理完成:', person.userId)
            }
        }
        // å¤„理指纹信息
        if (person.fingerprint) {
            try {
                logger.info('[api] å¼€å§‹å¤„理指纹信息:', person.userId)
                // æ£€æŸ¥ä¹‹å‰æ˜¯å¦æœ‰æŒ‡çº¹å‡­è¯
                let oldVoucher = sqliteService.d1_voucher.findByuserIdAndType(person.userId, "500");
                if (oldVoucher.length > 0) {
                    logger.info('[api] åˆ é™¤æ—§æŒ‡çº¹å‡­è¯:', JSON.stringify(oldVoucher[0]))
                    try {
                        let ret = driver.finger.delete(parseInt(oldVoucher[0].code))
                        if (ret != 0) {
                            errorItem.errmsg = "finger delete error ret:" + ret
                            errors.push(errorItem)
                            continue
                        }
                    } catch (error) {
                        logger.error('[api] åˆ é™¤æ—§æŒ‡çº¹å¤±è´¥:', error)
                    }
                }
                // å½•入新指纹
                logger.info('[api] å½•入新指纹:', person.userId)
                let index = driver.finger.insert(person.fingerprint)
                if(index < 0){
                    errorItem.errmsg = "insertKey finger insert error ret:" + index
                    errors.push(errorItem)
                    continue
                }
                // ä¿å­˜æŒ‡çº¹å‡­è¯
                logger.info('[api] ä¿å­˜æŒ‡çº¹å‡­è¯:', person.userId)
                let voucherRet = sqliteService.d1_voucher.save({
                    keyId: std.genRandomStr(32),
                    type: "500",
                    code: index.toString(),
                    userId: person.userId,
                    extra: JSON.stringify({ type: 0 })
                });
                logger.info('[api] ä¿å­˜æŒ‡çº¹å‡­è¯ç»“æžœ:', voucherRet)
                logger.info('[api] æŒ‡çº¹ä¿¡æ¯å¤„理完成:', person.userId)
            } catch (error) {
                logger.error('[api] å¤„理指纹信息错误:', error)
                errorItem.errmsg = "处理指纹信息错误: " + error.message
                errors.push(errorItem)
                continue
            }
        }
        // å¤„理NFC卡信息
        if (person.nfcCard) {
            try {
                logger.info('[api] å¼€å§‹å¤„理NFC卡信息:', person.userId)
                // æ£€æŸ¥NFC卡是否重复
                let existingVoucher = sqliteService.d1_voucher.findByCodeAndType(person.nfcCard, "200")
                if (existingVoucher.length > 0 && existingVoucher[0].userId != person.userId) {
                    errorItem.errmsg = "NFC卡已被其他用户使用"
                    errors.push(errorItem)
                    continue
                }
                // ä¿å­˜NFC卡凭证
                logger.info('[api] ä¿å­˜NFC卡凭证:', person.userId)
                let voucherRet = sqliteService.d1_voucher.save({
                    keyId: std.genRandomStr(32),
                    type: "200",
                    code: person.nfcCard.toUpperCase(),
                    userId: person.userId,
                    extra: JSON.stringify({ type: 0 })
                });
                logger.info('[api] ä¿å­˜NFC卡凭证结果:', voucherRet)
                logger.info('[api] NFC卡信息处理完成:', person.userId)
            } catch (error) {
                logger.error('[api] å¤„理NFC卡信息错误:', error)
                errorItem.errmsg = "处理NFC卡信息错误: " + error.message
                errors.push(errorItem)
                continue
            }
        }
        // ä¸ºç”¨æˆ·æ·»åŠ æƒé™
        try {
            // èŽ·å–ç”¨æˆ·ç±»åž‹
            let userType = 0
            if (person.extra) {
                try {
                    userType = person.extra.type || 0
                } catch (error) {
                    logger.error('[api] è§£æžç”¨æˆ·ç±»åž‹å¤±è´¥:', error)
                }
            }
            // åªæœ‰ä¿ç®¡å‘˜ï¼ˆ0)和科长(1)需要添加权限
            if (userType == 0 || userType == 1) {
                // æ£€æŸ¥æ˜¯å¦å·²å­˜åœ¨æƒé™è®°å½•
                let existingPermissions = sqliteService.d1_permission.findByUserId(person.userId)
                if (existingPermissions && existingPermissions.length == 0) {
                    // æ·»åŠ æ°¸ä¹…æƒé™
                    let permissionId = std.genRandomStr(32)
                    let permissionRet = sqliteService.d1_permission.save({
                        permissionId: permissionId,
                        userId: person.userId,
                        door: "", // ç©ºå­—符串表示所有门
                        timeType: 0, // æ°¸ä¹…权限
                        beginTime: 0,
                        endTime: 0,
                        period: ""
                    });
                    logger.info('[api] ä¸ºç”¨æˆ·æ·»åŠ æƒé™ç»“æžœ:', permissionRet)
                    // æ›´æ–°äººå‘˜è¡¨ä¸­çš„permissionIds字段
                    if (permissionRet == 0) {
                        // æž„建更新记录
                        let updateRecord = { permissionIds: permissionId }
                        // ä½¿ç”¨updateAllByUserId方法更新
                        let updateRet = sqliteService.d1_person.updateAllByUserId(updateRecord, person.userId)
                        logger.info('[api] æ›´æ–°äººå‘˜æƒé™ID结果:', updateRet)
                    }
                } else {
                    logger.info('[api] ç”¨æˆ·å·²å­˜åœ¨æƒé™è®°å½•,跳过权限添加:', person.userId)
                }
            } else {
                logger.info('[api] ç”¨æˆ·ç±»åž‹ä¸éœ€è¦æ·»åŠ æƒé™ï¼Œè·³è¿‡æƒé™æ·»åŠ :', person.userId)
            }
        } catch (error) {
            logger.error('[api] æ·»åŠ æƒé™æ—¶å‡ºé”™:', error)
        }
    }
    if (errors.length > 0) {
@@ -438,6 +650,14 @@
                userId: userId || 'unknown',
                errmsg: ''
            }
            try {
                // åˆ é™¤äººè„¸æ•°æ®
                driver.face.deleteFea(userId)
                logger.info('[api] åˆ é™¤äººè„¸æ•°æ®æˆåŠŸ:', userId)
            } catch (error) {
                logger.error(`Failed to delete face feature for user ${userId}:`, error)
            }
            // åˆ é™¤æŒ‡çº¹å‡­è¯ä¹‹å‰éœ€è¦å…ˆåˆ é™¤æŒ‡çº¹åº“中的指纹
            let fingerVoucher = sqliteService.d1_voucher.findByuserIdAndType(userId, "500")
            if (fingerVoucher.length > 0) {
@@ -448,19 +668,18 @@
                    errors.push(errorItem)
                    continue
                }
                logger.info('[api] åˆ é™¤æŒ‡çº¹æ•°æ®æˆåŠŸ:', userId)
            }
            let ret1 = sqliteService.d1_person.deleteByUserId(userId)
            let ret3 = sqliteService.d1_voucher.deleteByUserId(userId)
            if (ret1 != 0 || ret3 != 0) {
                errorItem.errmsg = `sql error: person(${ret1}), voucher(${ret3})`
            let ret4 = sqliteService.d1_permission.deleteByUserId(userId)
            if (ret1 != 0 || ret3 != 0 || ret4 != 0) {
                errorItem.errmsg = `sql error: person(${ret1}), voucher(${ret3}), permission(${ret4})`
                errors.push(errorItem)
                continue
            }
            try {
                driver.face.deleteFea(userId)
            } catch (error) {
                logger.error(`Failed to delete face feature for user ${userId}:`, error)
            }
            logger.info('[api] åˆ é™¤äººå‘˜æˆåŠŸ:', userId)
        }
    }
    if (errors.length > 0) {
@@ -479,6 +698,19 @@
    }
    let totalCount = sqliteService.d1_person.count(data)
    let persons = sqliteService.d1_person.findAll(data)
    // å°†extra字段从JSON字符串解析为JSON对象
    persons.forEach(person => {
        if (person.extra && typeof person.extra === 'string') {
            try {
                person.extra = JSON.parse(person.extra)
            } catch (error) {
                // å¦‚果解析失败,保持原样
                console.error('解析extra字段失败:', error)
            }
        }
    })
    return {
        content: persons,
        page: data.page,
@@ -573,10 +805,18 @@
        if (voucher.type == "200" || voucher.type == "201" || voucher.type == "202") {
            voucher.code = voucher.code.toUpperCase()
        }
        if (voucher.type == "300" && voucher.extra.faceType != 0 && voucher.extra.faceType != 1) {
        if (voucher.type == "300") {
            if (voucher.extra) {
                if (voucher.extra.faceType != 0 && voucher.extra.faceType != 1) {
            errorItem.errmsg = "faceType Incorrect format"
            errors.push(errorItem)
            continue
                }
            } else {
                errorItem.errmsg = "faceType is required"
                errors.push(errorItem)
                continue
            }
        }
        if (voucher.type == "400") {
            if (voucher.code.length != 6) {
@@ -585,17 +825,6 @@
                continue
            }
        }
        // // åˆ¤æ–­æŒ‡çº¹å‡­è¯æ˜¯å¦é‡å¤ï¼Œå¦‚果没有重复就录入指纹
        // if (voucher.type == "500") {
        //     voucher.code = parseInt(voucher.code)
        //     let index = driver.finger.insert(voucher.code)
        //     if(index < 0){
        //         errorItem.errmsg = "finger insert error ret:" + index
        //         errors.push(errorItem)
        //         continue
        //     }
        //     voucher.code = index
        // }
        let record = {}
        record.keyId = voucher.keyId
        record.type = voucher.type
@@ -755,8 +984,17 @@
    let totalCount = sqliteService.d1_voucher.count(data)
    let vouchers = sqliteService.d1_voucher.findAll(data)
    vouchers.forEach(element => {
        if (element.type == 300 && element.extra && JSON.parse(element.extra).faceType == 0) {
        // å°†extra字段从JSON字符串解析为JSON对象
        if (element.extra && typeof element.extra === 'string') {
            try {
                element.extra = JSON.parse(element.extra)
            } catch (error) {
                // å¦‚果解析失败,保持原样
                console.error('解析extra字段失败:', error)
            }
        }
            //人脸特殊处理一下
        if (element.type == 300 && element.extra && element.extra.faceType == 0) {
            element.code = dxCommonUtils.fs.fileToBase64(element.code)
        }
    });
@@ -1227,28 +1465,6 @@
    }
}
// åˆ¤ç©º
function isEmpty(value) {
    return value === undefined || value === null || value === ""
}
// æ¿€æ´»äº‘证
api.eidActive = function (data) {
    if (data.code && data.code.startsWith("___VBAR_ID_ACTIVE_V")) {
        try {
            let activeResute = driver.nfc.eidActive(data.code);
            if (activeResute != 0) {
                return 'Activation failed'
            }
        } catch (error) {
            return error.message
        }
    } else {
        return 'The key format is incorrect'
    }
    return true
}
// æ–°å¢žå¯†é’¥
api.insertSecurity = function (data) {
    let errors = []
@@ -1299,9 +1515,9 @@
// åˆ é™¤å¯†é’¥
api.delSecurity = function (data) {
    let errors = []
    if (data.length > 0) {
        for (let i = 0; i < data.length; i++) {
            const securityId = data[i];
    if (data.securityIds && data.securityIds.length > 0) {
        for (let i = 0; i < data.securityIds.length; i++) {
            const securityId = data.securityIds[i];
            let errorItem = {
                securityId: securityId || 'unknown',
                errmsg: ''
@@ -1326,8 +1542,142 @@
    if (ret == 0) {
        return true
    } else {
        return "sql error "
        return "sql error ret:" + ret
    }
}
// æ·»åŠ åº”æ€¥å¼€ä»“å¯†ç 
api.insertEmergencyPassword = function (data) {
    // åº”急开仓密码在设备中仅有唯一的1个,所以先清空表
    let deleteRet = sqliteService.d1_emergency_password.deleteAll()
    if (deleteRet != 0) {
        return "清空旧密码失败: " + deleteRet
    }
    // æ£€æŸ¥å¯†ç æ˜¯å¦æœ‰æ•ˆ
    if (!data.password) {
        return "password cannot be empty"
    }
    // æ£€æŸ¥å¯†ç é•¿åº¦æ˜¯å¦å¤§äºŽç­‰äºŽ8位
    if (data.password.length < 8) {
        return "Password length must be at least 8 digits"
    }
    // æž„建密码记录
    let record = {}
    record.id = data.id || 'emergency_' + Date.now() // å¦‚果没有id,自动生成
    record.password = data.password
    record.description = data.description || "云端下发" // å¦‚果没有传入description,默认为"云端下发"(MQTT和HTTP接口设置的情况)
    record.createTime = Date.now()
    record.updateTime = Date.now()
    record.status = data.status || 1
    // ä¿å­˜å¯†ç 
    let ret = sqliteService.d1_emergency_password.save(record)
    if (ret == 0) {
        return true
    } else {
        return "sql error ret:" + ret
    }
}
// æ¸…空密钥
api.clearSecurity = function () {
    let ret = sqliteService.d1_security.deleteAll()
    if (ret == 0) {
        return true
    } else {
        return "sql error ret:" + ret
    }
}
// æ·»åŠ åº”æ€¥å¼€ä»“å¯†ç 
api.insertEmergencyPassword = function (data) {
    // åº”急开仓密码在设备中仅有唯一的1个,所以先清空表
    let deleteRet = sqliteService.d1_emergency_password.deleteAll()
    if (deleteRet != 0) {
        return "清空旧密码失败: " + deleteRet
    }
    // æ£€æŸ¥å¯†ç æ˜¯å¦æœ‰æ•ˆ
    if (!data.password) {
        return "password cannot be empty"
    }
    // æ£€æŸ¥å¯†ç é•¿åº¦æ˜¯å¦å¤§äºŽç­‰äºŽ8位
    if (data.password.length < 8) {
        return "Password length must be at least 8 digits"
    }
    // æž„建密码记录
    let record = {}
    record.id = data.id || 'emergency_' + Date.now() // å¦‚果没有id,自动生成
    record.password = data.password
    record.description = data.description || "云端下发" // å¦‚果没有传入description,默认为"云端下发"(MQTT和HTTP接口设置的情况)
    record.createTime = Date.now()
    record.updateTime = Date.now()
    record.status = data.status || 1
    // ä¿å­˜å¯†ç 
    let ret = sqliteService.d1_emergency_password.save(record)
    if (ret == 0) {
        return true
    } else {
        return "sql error ret:" + ret
    }
}
// æŸ¥è¯¢åº”急开仓密码
api.getEmergencyPassword = function () {
    let passwords = sqliteService.d1_emergency_password.findAll()
    if (passwords && passwords.length > 0) {
        let password = passwords[0];
        // è½¬æ¢æ—¶é—´æˆ³ä¸ºå­—符串格式
        if (password.createTime) {
            // å°è¯•å°†createTime转换为数字
            const createTimeNum = Number(password.createTime);
            if (!isNaN(createTimeNum)) {
                password.createTime = timestampToDateString(createTimeNum);
            }
        }
        if (password.updateTime) {
            // å°è¯•å°†updateTime转换为数字
            const updateTimeNum = Number(password.updateTime);
            if (!isNaN(updateTimeNum)) {
                password.updateTime = timestampToDateString(updateTimeNum);
            }
        }
        return password;
    }
    return {};
}
// æ¸…空应急开仓密码
api.clearEmergencyPassword = function () {
    let ret = sqliteService.d1_emergency_password.deleteAll()
    if (ret == 0) {
        return true
    } else {
        return "sql error ret:" + ret
    }
}
// æ—¶é—´æˆ³è½¬æ—¥æœŸå­—符串
function timestampToDateString(timestamp) {
    const date = new Date(timestamp);
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    const hours = String(date.getHours()).padStart(2, '0');
    const minutes = String(date.getMinutes()).padStart(2, '0');
    const seconds = String(date.getSeconds()).padStart(2, '0');
    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// åˆ¤ç©º
function isEmpty(value) {
    return value === undefined || value === null || value === ""
}
export default api
vf107/src/service/configService.js
@@ -212,6 +212,12 @@
        tamper: validators.switch,
        uploadToCloud: validators.switch
    },
    gas: {
        verification: validators.switch
    },
    http: {
        safeInputAccess: validators.string
    },
    base: {
        firstLogin: validators.switch,
        backlight: validators.percentage,
@@ -315,6 +321,13 @@
            // éªŒè¯å¹¶æ”¶é›†é…ç½®é¡¹
            for (const [section, sectionData] of Object.entries(data)) {
                // å¤„理顶级配置项(非分组配置)
                if (typeof sectionData !== 'object' || sectionData === null) {
                    // ç›´æŽ¥ä¿å­˜é¡¶çº§é…ç½®é¡¹
                    configsToSave.push({ section: '', key: section, value: sectionData })
                    continue
                }
                for (let [key, value] of Object.entries(sectionData)) {
                    // éªŒè¯é…ç½®é¡¹
                    validateConfig(section, key, value)
@@ -341,7 +354,8 @@
            // æ‰¹é‡ä¿å­˜é…ç½®
            configsToSave.forEach(({ section, key, value }) => {
                config.set(`${section}.${key}`, value)
                const configKey = section ? `${section}.${key}` : key
                config.set(configKey, value)
            })
            config.save()
vf107/src/service/demo.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1832 @@
/**
 * MQTT服务模块
 * å¤„理MQTT消息的接收和发送,包括设备管理、人员管理、权限管理等功能
 */
import common from "../../dxmodules/dxCommon.js";
import config from "../../dxmodules/dxConfig.js";
import logger from "../../dxmodules/dxLogger.js";
import ota from "../../dxmodules/dxOta.js";
import std from "../../dxmodules/dxStd.js";
import dxMap from '../../dxmodules/dxMap.js'
import driver from "../driver.js";
import configService from "./configService.js";
import sqliteService from "./sqliteService.js";
import sqlite from "../../dxmodules/dxSqlite.js";
import utils from '../common/utils/utils.js'
const mqttService = {}
let map = dxMap.get("faceAccesss")
/**
 * æŽ¥æ”¶MQTT消息并处理
 * @param {object} data - MQTT消息数据
 * @param {string} data.topic - æ¶ˆæ¯ä¸»é¢˜
 * @param {string} data.payload - æ¶ˆæ¯è½½è·
 */
mqttService.receiveMsg = function (data) {
    // {"topic":"ddddd","payload":"{\n  \"msg\": \"world\"\n}"}
    logger.info('[mqttService] receiveMsg :' + JSON.stringify(data.topic))
    // æå–主题的最后部分作为函数名
    if (typeof mqttService[data.topic.match(/[^/]+$/)[0]] == 'function') {
        mqttService[data.topic.match(/[^/]+$/)[0]](data)
    } else {
        logger.error("未实现的topic", data.topic)
    }
}
// =================================权限增删改查=================================
/**
 * æ·»åŠ æƒé™
 * @param {object} event - MQTT事件对象
 */
mqttService.insertPermission = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°insertPermission命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : {}
        logger.info('[mqttService] è§£æžåŽçš„参数:', JSON.stringify(data))
        let res = this.insertPermissionAgreement(data)
        if (typeof res == 'string') {
            logger.error('[mqttService] insertPermission失败:', res)
            return reply(event, res, CODE.E_100)
        }
        logger.info('[mqttService] insertPermission成功')
        return reply(event)
    } catch (error) {
        logger.error('[mqttService] insertPermission error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * æ·»åŠ æƒé™é€šç”¨åè®®æ ¼å¼
 * @param {array} data - æƒé™æ•°æ®æ•°ç»„
 * @returns {boolean|string} true表示成功,string表示错误信息
 */
mqttService.insertPermissionAgreement = function (data) {
    let permissions = []
    for (let i = 0; i < data.length; i++) {
        const permission = data[i];
        if (!permission.permissionId || !permission.userId) {
            return "id or userId cannot be empty"
        }
        if (!permission.extra) {
            permission.extra = ""
        }
        if (!permission.time) {
            return "time and type cannot be empty"
        }
        if (permission.time.type != 0 && permission.time.type != 1 && permission.time.type != 2 && permission.time.type != 3) {
            return "time type is not supported"
        }
        let record = {}
        record.permissionId = permission.permissionId
        record.userId = permission.userId
        record.door = isEmpty(permission.index) ? 0 : permission.index
        record.extra = isEmpty(permission.extra) ? JSON.stringify({}) : JSON.stringify(permission.extra)
        record.timeType = permission.time.type
        record.beginTime = permission.time.type == 0 ? 0 : permission.time.range.beginTime
        record.endTime = permission.time.type == 0 ? 0 : permission.time.range.endTime
        record.repeatBeginTime = permission.time.type != 2 ? 0 : permission.time.beginTime
        record.repeatEndTime = permission.time.type != 2 ? 0 : permission.time.endTime
        record.period = permission.time.type != 3 ? 0 : JSON.stringify(permission.time.weekPeriodTime)
        let ret = sqliteService.d1_permission.save(record)
        if (ret != 0) {
            // å¦‚果保存失败,尝试删除后重新保存
            sqliteService.d1_permission.deleteByPermissionId(record.permissionId)
            ret = sqliteService.d1_permission.save(record)
            if (ret != 0) {
                return "sql error ret:" + ret
            } else {
                continue
            }
        }
    }
    return true
}
/**
 * æŸ¥è¯¢æƒé™
 * @param {object} event - MQTT事件对象
 */
mqttService.getPermission = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°getPermission命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : {}
        logger.info('[mqttService] è§£æžåŽçš„æŸ¥è¯¢å‚æ•°:', JSON.stringify(data))
        let res = this.getPermissionAgreement(data)
        logger.info('[mqttService] æŸ¥è¯¢ç»“æžœ:', JSON.stringify(res))
        return reply(event, res)
    } catch (error) {
        logger.error('[mqttService] getPermission error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * æŸ¥è¯¢æƒé™é€šç”¨åè®®æ ¼å¼
 * @param {object} data - æŸ¥è¯¢å‚æ•°
 * @returns {object} æŸ¥è¯¢ç»“æžœ
 */
mqttService.getPermissionAgreement = function (data) {
    try {
        // ç¡®ä¿data参数不为undefined
        data = data || {}
        data.page = isEmpty(data.page) ? 0 : data.page
        data.size = isEmpty(data.size) ? 10 : data.size
        let totalCount = sqliteService.d1_permission.count(data)
        let permissions = sqliteService.d1_permission.findAll(data)
        // æž„建返回结果
        let content = permissions.map(permission => ({
            permissionId: permission.permissionId,
            userId: permission.userId,
            extra: JSON.parse(permission.extra ? permission.extra : "{}"),
            time: {
                type: permission.timeType,
                beginTime: permission.timeType != 2 ? undefined : permission.repeatBeginTime,
                endTime: permission.timeType != 2 ? undefined : permission.repeatEndTime,
                range: permission.timeType === 0 ? undefined : { beginTime: permission.beginTime, endTime: permission.endTime },
                weekPeriodTime: permission.timeType != 3 ? undefined : JSON.parse(permission.period)
            }
        }))
        return {
            content: content,
            page: data.page,
            size: data.size,
            total: totalCount,
            totalPage: Math.ceil(totalCount / data.size),
            count: content.length
        }
    } catch (error) {
        logger.error('[mqttService] getPermissionAgreement error:', error)
        throw error
    }
}
/**
 * åˆ é™¤æƒé™
 * @param {object} event - MQTT事件对象
 */
mqttService.delPermission = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°delPermission命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : {}
        logger.info('[mqttService] è§£æžåŽçš„参数:', JSON.stringify(data))
        let res = this.delPermissionAgreement(data)
        if (typeof res == 'string') {
            logger.error('[mqttService] delPermission失败:', res)
            return reply(event, res, CODE.E_100)
        }
        logger.info('[mqttService] delPermission成功')
        return reply(event)
    } catch (error) {
        logger.error('[mqttService] delPermission error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * åˆ é™¤æƒé™é€šç”¨åè®®æ ¼å¼
 * @param {object} data - åˆ é™¤å‚æ•°
 * @returns {boolean|string} true表示成功,string表示错误信息
 */
mqttService.delPermissionAgreement = function (data) {
    if (data.permissionIds && data.permissionIds.length > 0) {
        let ret = sqliteService.d1_permission.deleteByPermissionIdInBatch(data.permissionIds)
        if (ret != 0) {
            return "sql error ret:" + ret
        }
    }
    if (data.userIds && data.userIds.length > 0) {
        let ret = sqliteService.d1_permission.deleteByUserIdInBatch(data.userIds)
        if (ret != 0) {
            return "sql error ret:" + ret
        }
    }
    return true
}
/**
 * æ¸…空权限
 * @param {object} event - MQTT事件对象
 */
mqttService.clearPermission = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°clearPermission命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let ret = sqliteService.d1_permission.deleteAll()
        if (ret == 0) {
            logger.info('[mqttService] clearPermission成功')
            return reply(event)
        } else {
            logger.error('[mqttService] clearPermission失败:', "sql error ret:" + ret)
            return reply(event, "sql error ret:" + ret, CODE.E_100)
        }
    } catch (error) {
        logger.error('[mqttService] clearPermission error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
// =================================人员增删改查=================================
/**
 * æ·»åŠ äººå‘˜
 * @param {object} event - MQTT事件对象
 */
mqttService.insertUser = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°insertUser命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : []
        logger.info('[mqttService] è§£æžåŽçš„参数:', JSON.stringify(data))
        let res = this.insertUserAgreement(data)
        if (typeof res == 'string') {
            logger.error('[mqttService] insertUser失败:', res)
            return reply(event, res, CODE.E_100)
        }
        logger.info('[mqttService] insertUser成功')
        return reply(event)
    } catch (error) {
        logger.error('[mqttService] insertUser error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * æ·»åŠ äººå‘˜é€šç”¨åè®®æ ¼å¼
 * @param {array} data - äººå‘˜æ•°æ®æ•°ç»„
 * @returns {boolean|string} true表示成功,string表示错误信息
 */
mqttService.insertUserAgreement = function (data) {
    let persons = []
    for (let i = 0; i < data.length; i++) {
        const person = data[i];
        // ä¸¥æ ¼æ£€æŸ¥æ•°æ®æ ¼å¼
        if (!person.userId || !person.name || person.type === undefined || !person.idCard) {
            return "数据格式错误,缺少必要字段(userId、name、type、idCard)"
        }
        // æ£€æŸ¥type字段类型
        if (typeof person.type !== 'number') {
            return "数据格式错误,type字段必须是数字"
        }
        // æ£€æŸ¥type字段值范围
        if (person.type < 0 || person.type > 1) {
            return "数据格式错误,type字段值必须在0-1之间"
        }
        let record = {}
        record.userId = person.userId
        record.name = person.name
        // å¤„理人员类型字段和身份证号
        let extra = {}
        extra.type = person.type
        extra.idCard = person.idCard
        record.extra = JSON.stringify(extra)
        persons.push(record)
        // å¤„理人脸信息
        if (person.face) {
            try {
                logger.info('[mqttService] å¼€å§‹å¤„理人脸信息:', person.userId)
                let faceFilePath = person.face
                // æ£€æŸ¥æ˜¯å¦æ˜¯base64编码的图片数据
                if (person.face.startsWith('data:image/')) {
                    logger.info('[mqttService] æ£€æµ‹åˆ°base64编码的图片数据')
                    // æå–base64数据
                    let base64Data = person.face.split(',')[1]
                    // åˆ›å»ºä¸´æ—¶æ–‡ä»¶
                    faceFilePath = '/app/data/user/temp_face_' + person.userId + '.jpg'
                    std.ensurePathExists(faceFilePath)
                    // å°†base64数据转换为文件
                    common.base64_2binfile(faceFilePath, base64Data)
                    logger.info('[mqttService] å·²å°†base64数据保存为文件:', faceFilePath)
                } else {
                    return "数据格式错误,face字段必须是base64编码的图片数据"
                }
                // æ£€æŸ¥æ–‡ä»¶æ˜¯å¦å­˜åœ¨
                let fileExists = common.systemWithRes(`test -e "${faceFilePath}" && echo "OK" || echo "NO"`, 2)
                logger.info('[mqttService] äººè„¸å›¾ç‰‡æ–‡ä»¶å­˜åœ¨:', fileExists.includes('OK'))
                if (fileExists.includes('OK')) {
                    // æ³¨å†Œäººè„¸
                    logger.info('[mqttService] å¼€å§‹æ³¨å†Œäººè„¸:', person.userId)
                    let ret = driver.face.registerFaceByPicFile(person.userId, faceFilePath)
                    logger.info('[mqttService] æ³¨å†Œäººè„¸ç»“æžœ:', ret)
                    if (ret == 0) {
                        // æ³¨å†ŒæˆåŠŸåŽç§»åŠ¨å›¾ç‰‡åˆ°ç”¨æˆ·ç›®å½•
                        let src = "/app/data/user/" + person.userId + "/register.jpg"
                        std.ensurePathExists(src)
                        logger.info('[mqttService] ç§»åŠ¨äººè„¸å›¾ç‰‡åˆ°ç”¨æˆ·ç›®å½•:', faceFilePath, '->', src)
                        common.systemBrief('mv ' + faceFilePath + " " + src)
                        // ä¿å­˜äººè„¸å‡­è¯
                        logger.info('[mqttService] ä¿å­˜äººè„¸å‡­è¯:', person.userId)
                        let voucherRet = sqliteService.d1_voucher.save({
                            keyId: std.genRandomStr(32),
                            type: "300",
                            code: src,
                            userId: person.userId,
                            extra: JSON.stringify({ faceType: 0 })
                        });
                        logger.info('[mqttService] ä¿å­˜äººè„¸å‡­è¯ç»“æžœ:', voucherRet)
                    } else {
                        logger.error('[mqttService] æ³¨å†Œäººè„¸å¤±è´¥ï¼Œè¿”回码:', ret)
                    }
                } else {
                    logger.error('[mqttService] äººè„¸å›¾ç‰‡æ–‡ä»¶ä¸å­˜åœ¨:', faceFilePath)
                }
            } catch (error) {
                logger.error('[mqttService] å¤„理人脸信息错误:', error)
                return "处理人脸信息错误: " + error.message
            } finally {
                logger.info('[mqttService] äººè„¸ä¿¡æ¯å¤„理完成:', person.userId)
            }
        }
    }
    let ret = sqliteService.d1_person.saveAll(persons)
    if (ret != 0) {
        //失败了 æŠŠè¿™äº›äººå…¨éƒ½åˆ é™¤åŽåœ¨æ–°å¢žä¸€ä¸‹
        let userIds = persons.map(obj => obj.userId);
        sqliteService.d1_person.deleteByUserIdInBatch(userIds)
        //重新新增
        let ret = sqliteService.d1_person.saveAll(persons)
        if (ret != 0) {
            return "sql error ret:" + ret
        }
    }
    // ä¸ºç”¨æˆ·æ·»åŠ å¯¹åº”æƒé™
    for (let i = 0; i < data.length; i++) {
        const person = data[i];
        let userId = person.userId
        let userType = person.type
        // åªæœ‰ä¿ç®¡å‘˜ï¼ˆ0)和科长(1)需要添加权限
        if (userType == 0 || userType == 1) {
            try {
                // æ£€æŸ¥æ˜¯å¦å·²å­˜åœ¨æƒé™è®°å½•
                let existingPermissions = sqliteService.d1_permission.findByUserId(userId)
                if (existingPermissions && existingPermissions.length == 0) {
                    // æ·»åŠ æ°¸ä¹…æƒé™
                    let permissionRet = sqliteService.d1_permission.save({
                        permissionId: std.genRandomStr(32),
                        userId: userId,
                        timeType: 0, // æ°¸ä¹…权限
                        beginTime: 0,
                        endTime: 0,
                        repeatBeginTime: 0,
                        repeatEndTime: 0,
                        period: ""
                    });
                    logger.info('[mqttService] ä¸ºç”¨æˆ·æ·»åŠ æƒé™ç»“æžœ:', permissionRet)
                } else {
                    logger.info('[mqttService] ç”¨æˆ·å·²å­˜åœ¨æƒé™è®°å½•,跳过权限添加:', userId)
                }
            } catch (error) {
                logger.error('[mqttService] æ·»åŠ æƒé™æ—¶å‡ºé”™:', error)
            }
        }
    }
    return true
}
/**
 * æŸ¥è¯¢äººå‘˜
 * @param {object} event - MQTT事件对象
 */
mqttService.getUser = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°getUser命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : {}
        let res = this.getUserAgreement(data)
        logger.info('[mqttService] æŸ¥è¯¢ç»“æžœ:', JSON.stringify(res))
        return reply(event, res)
    } catch (error) {
        logger.error('[mqttService] getUser error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * æŸ¥è¯¢äººå‘˜é€šç”¨åè®®æ ¼å¼
 * @param {object} data - æŸ¥è¯¢å‚æ•°
 * @returns {object} æŸ¥è¯¢ç»“æžœ
 */
mqttService.getUserAgreement = function (data) {
    try {
        data.page = isEmpty(data.page) ? 0 : data.page
        data.size = isEmpty(data.size) ? 10 : data.size
        let totalCount = sqliteService.d1_person.count(data)
        let persons = sqliteService.d1_person.findAll(data)
        // è§£æž extra å­—段,JSON字符串转化为JSON对象,消除转义字符
        persons.forEach(person => {
            try {
                if (person.extra) {
                    person.extra = JSON.parse(person.extra)
                }
            } catch (error) {
                logger.error('[mqttService] è§£æž extra å­—段错误:', error)
            }
        })
        let result = {
            content: persons,
            page: data.page,
            size: data.size,
            total: totalCount,
            totalPage: Math.ceil(totalCount / data.size),
            count: persons.length
        }
        return result
    } catch (error) {
        logger.error('[mqttService] getUserAgreement error:', error)
        throw error
    }
}
/**
 * åˆ é™¤äººå‘˜
 * @param {object} event - MQTT事件对象
 */
mqttService.delUser = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°delUser命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : []
        logger.info('[mqttService] è§£æžåŽçš„参数:', JSON.stringify(data))
        let res = this.delUserAgreement(data)
        if (typeof res == 'string') {
            logger.error('[mqttService] delUser失败:', res)
            return reply(event, res, CODE.E_100)
        }
        logger.info('[mqttService] delUser成功')
        return reply(event)
    } catch (error) {
        logger.error('[mqttService] delUser error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * åˆ é™¤äººå‘˜é€šç”¨åè®®æ ¼å¼
 * @param {array} data - äººå‘˜ID数组
 * @returns {boolean|string} true表示成功,string表示错误信息
 */
mqttService.delUserAgreement = function (data) {
    if (data && data.length > 0) {
        sqliteService.transaction()
        let ret1 = sqliteService.d1_person.deleteByUserIdInBatch(data)
        let ret2 = sqliteService.d1_permission.deleteByUserIdInBatch(data)
        let ret3 = sqliteService.d1_voucher.deleteByUserIdInBatch(data)
        if (ret1 != 0 || ret2 != 0 || ret3 != 0) {
            sqliteService.rollback()
            return "sql error"
        }
        sqliteService.commit()
        // åˆ é™¤äººå‘˜çš„人脸数据
        data.forEach(element => {
            driver.face.delete(element)
        });
    }
    return true
}
/**
 * æ¸…空人员
 * @param {object} event - MQTT事件对象
 */
mqttService.clearUser = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°clearUser命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let persons = sqliteService.d1_person.findAll()
        // åˆ é™¤æ‰€æœ‰äººå‘˜çš„人脸数据
        logger.info('[mqttService] å¼€å§‹åˆ é™¤äººè„¸æ•°æ®ï¼Œå…±', persons.length, '条')
        persons.forEach(element => {
            driver.face.delete(element.userId)
        });
        let ret1 = sqliteService.d1_person.deleteAll()
        let ret2 = sqliteService.d1_permission.deleteAll()
        let ret3 = sqliteService.d1_voucher.deleteAll()
        if (ret1 == 0 && ret2 == 0 && ret3 == 0) {
            logger.info('[mqttService] clearUser成功')
            return reply(event)
        } else {
            let errorMsg = "sql error ret: " + ret1 + ", " + ret2 + ", " + ret3
            logger.error('[mqttService] clearUser失败:', errorMsg)
            return reply(event, errorMsg, CODE.E_100)
        }
    } catch (error) {
        logger.error('[mqttService] clearUser error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
// =================================凭证增删改查=================================
/**
 * æ·»åŠ å‡­è¯
 * @param {object} event - MQTT事件对象
 */
mqttService.insertKey = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°insertKey命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : {}
        logger.info('[mqttService] è§£æžåŽçš„参数:', JSON.stringify(data))
        let res = this.insertKeyAgreement(data)
        if (typeof res == 'string') {
            logger.error('[mqttService] insertKey失败:', res)
            return reply(event, res, CODE.E_100)
        }
        logger.info('[mqttService] insertKey成功')
        return reply(event)
    } catch (error) {
        logger.error('[mqttService] insertKey error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * æ·»åŠ å‡­è¯é€šç”¨åè®®æ ¼å¼
 * @param {array} data - å‡­è¯æ•°æ®æ•°ç»„
 * @returns {boolean|string} true表示成功,string表示错误信息
 */
mqttService.insertKeyAgreement = function (data) {
    let vouchers = []
    for (let i = 0; i < data.length; i++) {
        const voucher = data[i];
        if (!voucher.keyId || !voucher.type || !voucher.code || !voucher.userId) {
            return "keyId or type or code  or userId cannot be empty"
        }
        // å‡­è¯é‡å¤
        let ret = sqliteService.d1_voucher.findAllBycode(voucher.code)
        if (ret.length != 0) {
            return "Duplicate vouchers"
        }
        if (voucher.type == 300) {
            if (voucher.extra) {
                if (voucher.extra.faceType != 0 && voucher.extra.faceType != 1) {
                    return "faceType Incorrect format"
                }
            } else {
                return "faceType is required"
            }
        }
        let record = {}
        record.keyId = voucher.keyId
        record.type = voucher.type
        if (voucher.type == "400") {
            if (voucher.code.length > 6) {
                return "Password length cannot exceed 6 digits"
            }
        }
        if (voucher.type == "300") {
            if (voucher.extra.faceType == 0) {
                record.code = `/app/data/user/${voucher.userId}/register.jpg`
                // ä¿å­˜base64图片
                std.ensurePathExists(record.code)
                common.base64_2binfile(record.code, voucher.code)
                // æ³¨å†Œäººè„¸
                let weq = driver.face.registerFaceByPicFile(voucher.userId, record.code)
                if (weq == 0) {
                    logger.info("注册人脸成功")
                } else {
                    logger.info("第一次人脸注册失败")
                    //删除重新注册
                    driver.face.delete(voucher.userId)
                    let res = driver.face.registerFaceByPicFile(voucher.userId, record.code)
                    if (res == 0) {
                        logger.info("第二次注册人脸成功")
                        sqliteService.d1_voucher.deleteByKeyId(record.keyId)
                    } else {
                        return "Face registration failed"
                    }
                }
            } else {
                record.code = voucher.code
                //特征值注册
                let res = driver.face.reg(voucher.userId, voucher.code)
                if (res != 0) {
                    return "Face registration failed"
                }
            }
        } else {
            record.code = voucher.code
            let ret = sqliteService.d1_voucher.findAllByCodeAndType(voucher.code, voucher.type)
            if (ret.length != 0) {
                return "Duplicate vouchers"
            }
        }
        record.userId = voucher.userId
        record.extra = isEmpty(voucher.extra) ? JSON.stringify({ type: 0 }) : JSON.stringify(voucher.extra)
        vouchers.push(record)
    }
    let ret = sqliteService.d1_voucher.saveAll(vouchers)
    if (ret == 0) {
        return true
    } else {
        return "sql error ret:" + ret
    }
}
/**
 * æŸ¥è¯¢å‡­è¯
 * @param {object} event - MQTT事件对象
 */
mqttService.getKey = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°getKey命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : {}
        logger.info('[mqttService] è§£æžåŽçš„æŸ¥è¯¢å‚æ•°:', JSON.stringify(data))
        let res = this.getKeyAgreement(data)
        if (typeof res == 'string') {
            logger.error('[mqttService] getKey失败:', res)
            return reply(event, res, CODE.E_100)
        }
        logger.info('[mqttService] æŸ¥è¯¢ç»“æžœ:', JSON.stringify(res))
        return reply(event, res)
    } catch (error) {
        logger.error('[mqttService] getKey error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * æŸ¥è¯¢å‡­è¯é€šç”¨åè®®æ ¼å¼
 * @param {object} data - æŸ¥è¯¢å‚æ•°
 * @returns {object|string} æŸ¥è¯¢ç»“果或错误信息
 */
mqttService.getKeyAgreement = function (data) {
    if (!data.type) {
        return "type is required"
    }
    if (data.type == 300) {
        data.size = 1
    } else {
        data.page = isEmpty(data.page) ? 0 : data.page
        data.size = isEmpty(data.size) ? 10 : data.size
    }
    let totalCount = sqliteService.d1_voucher.count(data)
    let vouchers = sqliteService.d1_voucher.findAll(data)
    vouchers.forEach(element => {
        if (element.type == 300 && element.extra && JSON.parse(element.extra).faceType == 0) {
            //人脸特殊处理一下
            element.code = driver.face.fileToBase64(element.code)
        }
    });
    return {
        content: vouchers,
        page: data.page,
        size: data.size,
        total: totalCount,
        totalPage: Math.ceil(totalCount / data.size),
        count: vouchers.length
    }
}
/**
 * åˆ é™¤å‡­è¯
 * @param {object} event - MQTT事件对象
 */
mqttService.delKey = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°delKey命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : {}
        logger.info('[mqttService] è§£æžåŽçš„参数:', JSON.stringify(data))
        let res = this.delKeyAgreement(data)
        if (typeof res == 'string') {
            logger.error('[mqttService] delKey失败:', res)
            return reply(event, res, CODE.E_100)
        }
        logger.info('[mqttService] delKey成功')
        return reply(event)
    } catch (error) {
        logger.error('[mqttService] delKey error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * åˆ é™¤å‡­è¯é€šç”¨åè®®æ ¼å¼
 * @param {object} data - åˆ é™¤å‚æ•°
 * @returns {boolean|string} true表示成功,string表示错误信息
 */
mqttService.delKeyAgreement = function (data) {
    if (data.keyIds && data.keyIds.length > 0) {
        let userIds = []
        for (let i = 0; i < data.keyIds.length; i++) {
            const element = data.keyIds[i];
            let res = sqliteService.d1_voucher.findAllByKeyId(element)
            if (res.length <= 0) {
                continue
            }
            if (res[0].type == 300) {
                userIds.push(res[0].userId)
            }
        }
        let ret = sqliteService.d1_voucher.deleteByKeyIdInBatch(data.keyIds)
        if (ret != 0) {
            return "sql error ret:" + ret
        }
        // åˆ é™¤äººè„¸æ•°æ®
        userIds.forEach(element => {
            driver.face.delete(element)
        });
    }
    if (data.userIds && data.userIds.length > 0) {
        let ret = sqliteService.d1_voucher.deleteByUserIdInBatch(data.userIds)
        if (ret != 0) {
            return "sql error ret:" + ret
        }
        // åˆ é™¤äººè„¸æ•°æ®
        data.userIds.forEach(element => {
            driver.face.delete(element)
        });
    }
    return true
}
/**
 * æ¸…空凭证
 * @param {object} event - MQTT事件对象
 */
mqttService.clearKey = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°clearKey命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let res = sqliteService.d1_voucher.findAll()
        let userIds = []
        res.forEach(element => {
            if (element.type == 300) {
                userIds.push(element.userId)
            }
        });
        logger.info('[mqttService] æ‰¾åˆ°éœ€è¦åˆ é™¤çš„人脸数据,共', userIds.length, '条')
        let ret = sqliteService.d1_voucher.deleteAll()
        if (ret == 0) {
            // åˆ é™¤äººè„¸æ•°æ®
            logger.info('[mqttService] å¼€å§‹åˆ é™¤äººè„¸æ•°æ®')
            userIds.forEach((element, index) => {
                driver.face.delete(element)
            });
            logger.info('[mqttService] clearKey成功')
            reply(event)
        } else {
            logger.error('[mqttService] clearKey失败:', "sql error ret:" + ret)
            reply(event, "sql error ret:" + ret, CODE.E_100)
        }
    } catch (error) {
        logger.error('[mqttService] clearKey error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
// =================================应急开仓密码增删改查=================================
/**
 * æ·»åŠ åº”æ€¥å¼€ä»“å¯†ç 
 * @param {object} event - MQTT事件对象
 */
mqttService.insertEmergencyPassword = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°insertEmergencyPassword命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : {}
        logger.info('[mqttService] è§£æžåŽçš„参数:', JSON.stringify(data))
        let res = this.insertEmergencyPasswordAgreement(data)
        if (typeof res == 'string') {
            logger.error('[mqttService] insertEmergencyPassword失败:', res)
            return reply(event, res, CODE.E_100)
        }
        logger.info('[mqttService] insertEmergencyPassword成功')
        return reply(event)
    } catch (error) {
        logger.error('[mqttService] insertEmergencyPassword error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * æ·»åŠ åº”æ€¥å¼€ä»“å¯†ç é€šç”¨åè®®æ ¼å¼
 * @param {object} data - åº”急开仓密码数据对象
 * @returns {boolean|string} true表示成功,string表示错误信息
 */
mqttService.insertEmergencyPasswordAgreement = function (data) {
    // åº”急开仓密码在设备中仅有唯一的1个,所以先清空表
    let deleteRet = sqliteService.d1_emergency_password.deleteAll()
    if (deleteRet != 0) {
        return "清空旧密码失败: " + deleteRet
    }
    // æ£€æŸ¥å¯†ç æ˜¯å¦æœ‰æ•ˆ
    if (!data.password) {
        return "password cannot be empty"
    }
    // æ£€æŸ¥å¯†ç é•¿åº¦æ˜¯å¦å¤§äºŽç­‰äºŽ8位
    if (data.password.length < 8) {
        return "Password length must be at least 8 digits"
    }
    // æž„建密码记录
    let record = {}
    record.id = data.id || 'emergency_' + Date.now() // å¦‚果没有id,自动生成
    record.password = data.password
    record.description = data.description || ""
    record.createTime = Date.now()
    record.updateTime = Date.now()
    record.status = data.status || 1
    // ä¿å­˜å¯†ç 
    let ret = sqliteService.d1_emergency_password.save(record)
    if (ret == 0) {
        return true
    } else {
        return "sql error ret:" + ret
    }
}
/**
 * æŸ¥è¯¢åº”急开仓密码
 * @param {object} event - MQTT事件对象
 */
mqttService.getEmergencyPassword = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°getEmergencyPassword命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let res = this.getEmergencyPasswordAgreement()
        logger.info('[mqttService] æŸ¥è¯¢ç»“æžœ:', JSON.stringify(res))
        return reply(event, res)
    } catch (error) {
        logger.error('[mqttService] getEmergencyPassword error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * æ—¶é—´æˆ³è½¬æ—¥æœŸå­—符串
 * @param {number} timestamp - æ—¶é—´æˆ³
 * @returns {string} æ—¥æœŸå­—符串,格式:YYYY-MM-DD HH:MM:SS
 */
function timestampToDateString(timestamp) {
    const date = new Date(timestamp);
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    const hours = String(date.getHours()).padStart(2, '0');
    const minutes = String(date.getMinutes()).padStart(2, '0');
    const seconds = String(date.getSeconds()).padStart(2, '0');
    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
/**
 * æŸ¥è¯¢åº”急开仓密码通用协议格式
 * @returns {object} æŸ¥è¯¢ç»“æžœ
 */
mqttService.getEmergencyPasswordAgreement = function () {
    let passwords = sqliteService.d1_emergency_password.findAll()
    if (passwords && passwords.length > 0) {
        let password = passwords[0];
        // è½¬æ¢æ—¶é—´æˆ³ä¸ºå­—符串格式
        password.createTime = timestampToDateString(password.createTime);
        password.updateTime = timestampToDateString(password.updateTime);
        return password;
    }
    return {};
}
/**
 * æ¸…空应急开仓密码
 * @param {object} event - MQTT事件对象
 */
mqttService.clearEmergencyPassword = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°clearEmergencyPassword命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let ret = sqliteService.d1_emergency_password.deleteAll()
        if (ret == 0) {
            logger.info('[mqttService] clearEmergencyPassword成功')
            return reply(event)
        } else {
            logger.error('[mqttService] clearEmergencyPassword失败:', "sql error ret:" + ret)
            return reply(event, "sql error ret:" + ret, CODE.E_100)
        }
    } catch (error) {
        logger.error('[mqttService] clearEmergencyPassword error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
// =================================密钥增删改查=================================
/**
 * æ·»åР坆钥
 * @param {object} event - MQTT事件对象
 */
mqttService.insertSecurity = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°insertSecurity命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : {}
        logger.info('[mqttService] è§£æžåŽçš„参数:', JSON.stringify(data))
        let res = this.insertSecurityAgreement(data)
        if (typeof res == 'string') {
            logger.error('[mqttService] insertSecurity失败:', res)
            return reply(event, res, CODE.E_100)
        }
        logger.info('[mqttService] insertSecurity成功')
        return reply(event)
    } catch (error) {
        logger.error('[mqttService] insertSecurity error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * æ·»åŠ å¯†é’¥é€šç”¨åè®®æ ¼å¼
 * @param {array} data - å¯†é’¥æ•°æ®æ•°ç»„
 * @returns {boolean|string} true表示成功,string表示错误信息
 */
mqttService.insertSecurityAgreement = function (data) {
    let securities = []
    for (let i = 0; i < data.length; i++) {
        const security = data[i];
        let record = []
        record.securityId = security.securityId
        record.type = security.type
        record.key = security.key
        record.value = security.value
        record.startTime = security.startTime
        record.endTime = security.endTime
        securities.push(record)
    }
    let ret = sqliteService.d1_security.saveAll(securities)
    if (ret == 0) {
        return true
    } else {
        return "sql error ret:" + ret
    }
}
/**
 * æŸ¥è¯¢å¯†é’¥
 * @param {object} event - MQTT事件对象
 */
mqttService.getKey = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°getKey命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : {}
        logger.info('[mqttService] è§£æžåŽçš„æŸ¥è¯¢å‚æ•°:', JSON.stringify(data))
        let res = this.getKeyAgreement(data)
        logger.info('[mqttService] æŸ¥è¯¢ç»“æžœ:', JSON.stringify(res))
        return reply(event, res)
    } catch (error) {
        logger.error('[mqttService] getKey error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * æŸ¥è¯¢å¯†é’¥é€šç”¨åè®®æ ¼å¼
 * @param {object} data - æŸ¥è¯¢å‚æ•°
 * @returns {object} æŸ¥è¯¢ç»“æžœ
 */
mqttService.getSecurityAgreement = function (data) {
    data.page = isEmpty(data.page) ? 0 : data.page
    data.size = isEmpty(data.size) ? 10 : data.size
    let totalCount = sqliteService.d1_security.count(data)
    let securities = sqliteService.d1_security.findAll(data)
    return {
        content: securities,
        page: data.page,
        size: data.size,
        total: totalCount,
        totalPage: Math.ceil(totalCount / data.size),
        count: securities.length
    }
}
/**
 * åˆ é™¤å¯†é’¥
 * @param {object} event - MQTT事件对象
 */
mqttService.delSecurity = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°delSecurity命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : {}
        logger.info('[mqttService] è§£æžåŽçš„参数:', JSON.stringify(data))
        let res = this.delSecurityAgreement(data)
        if (typeof res == 'string') {
            logger.error('[mqttService] delSecurity失败:', res)
            return reply(event, res, CODE.E_100)
        }
        logger.info('[mqttService] delSecurity成功')
        return reply(event)
    } catch (error) {
        logger.error('[mqttService] delSecurity error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * åˆ é™¤å¯†é’¥é€šç”¨åè®®æ ¼å¼
 * @param {array} data - å¯†é’¥ID数组
 * @returns {boolean|string} true表示成功,string表示错误信息
 */
mqttService.delSecurityAgreement = function (data) {
    if (data.length > 0) {
        let ret = sqliteService.d1_security.deleteBySecurityIdInBatch(data)
        if (ret != 0) {
            return "sql error ret:" + ret
        }
    }
    return true
}
/**
 * æ¸…空密钥
 * @param {object} event - MQTT事件对象
 */
mqttService.clearSecurity = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°clearSecurity命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let ret = sqliteService.d1_key.deleteAll()
        if (ret == 0) {
            logger.info('[mqttService] clearSecurity成功')
            return reply(event)
        } else {
            logger.error('[mqttService] clearSecurity失败:', "sql error ret:" + ret)
            return reply(event, "sql error ret:" + ret, CODE.E_100)
        }
    } catch (error) {
        logger.error('[mqttService] clearSecurity error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * è¿œç¨‹æŽ§åˆ¶
 * @param {object} event - MQTT事件对象
 */
mqttService.control = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°control命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload.data || {}
        switch (data.command) {
            case 0:
                //重启
                logger.info('[mqttService] æ‰§è¡Œé‡å¯å‘½ä»¤')
                reply(event)
                common.asyncReboot(2)
                return
            case 1:
                //远程开门
                logger.info('[mqttService] æ‰§è¡Œè¿œç¨‹å¼€é—¨å‘½ä»¤')
                driver.gpio.open()
                break
            case 4:
                //重置
                logger.info('[mqttService] æ‰§è¡Œé‡ç½®å‘½ä»¤')
                common.systemBrief("rm -rf /app/data/config/*")
                common.systemBrief("rm -rf /app/data/db/*")
                common.systemBrief("rm -rf /app/data/user/*")
                common.systemBrief("rm -rf /app/data/user/*")
                common.systemBrief("rm -rf /vgmj.db")
                reply(event)
                common.asyncReboot(2)
                return
            case 5:
                //播放语音
                logger.info('[mqttService] æ‰§è¡Œæ’­æ”¾è¯­éŸ³å‘½ä»¤')
                if (data.extra) {
                    let res = common.systemWithRes(`test -e "/app/code/resource/wav/${data.extra.wav}.wav" && echo "OK" || echo "NO"`, 2)
                    if (res.includes('OK')) {
                        driver.alsa.play(`/app/code/resource/wav/${data.extra.wav}.wav`)
                    }
                }
                break
            case 6:
                // 6:屏幕展示图片
                // TODO
                logger.info('[mqttService] æ‰§è¡Œå±å¹•展示图片命令')
                break
            case 7:
                // 7:屏幕展示文字
                // TODO
                logger.info('[mqttService] æ‰§è¡Œå±å¹•展示文字命令')
                break
            case 10:
                logger.info('[mqttService] æ‰§è¡ŒäºŒç»´ç å±•示命令')
                if (!isEmpty(data.extra.qrCodeBase64) && typeof data.extra.qrCodeBase64 == 'string') {
                    //base64转图片保存
                    let src = `/app/code/resource/image/app_qrcode.png`
                    std.ensurePathExists(src)
                    common.base64_2binfile(src, data.extra.qrCodeBase64)
                    logger.info('[mqttService] äºŒç»´ç ä¿å­˜æˆåŠŸ')
                    return reply(event)
                }
                break
            default:
                logger.info('[mqttService] æœªçŸ¥å‘½ä»¤:', data.command)
                break
        }
        logger.info('[mqttService] control命令执行完成')
        return reply(event)
    } catch (error) {
        logger.error('[mqttService] control error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * æŸ¥è¯¢é…ç½®
 * @param {object} event - MQTT事件对象
 */
mqttService.getConfig = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°getConfig命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : {}
        logger.info('[mqttService] è§£æžåŽçš„参数:', JSON.stringify(data))
        let configAll = config.getAll()
        let res = {}
        // é…ç½®åˆ†ç»„
        for (const key in configAll) {
            const value = configAll[key];
            const keys = key.split(".")
            if (keys.length == 2) {
                if (!res[keys[0]]) {
                    res[keys[0]] = {}
                }
                res[keys[0]][keys[1]] = value
            } else {
                res[keys[0]] = value
            }
        }
        res.sys = {
            // ä¿ç•™åŽŸæœ‰çš„ sysInfo ä¸­çš„其他值
            ...res.sys,
            totalmem: common.getTotalmem(),
            freemem: common.getFreemem(),
            totaldisk: common.getTotaldisk(),
            freedisk: common.getFreedisk(),
            freecpu: common.getFreecpu()
        };
        if (isEmpty(data) || typeof data != "string" || data == "") {
            // æŸ¥è¯¢å…¨éƒ¨
            logger.info('[mqttService] getConfig成功,返回全部配置,配置数量:', Object.keys(res).length)
            return reply(event, res)
        }
        // å•条件查询"data": "mqttInfo.clientId"
        let keys = data.split(".")
        let search = {}
        if (keys.length == 2) {
            if (res[keys[0]]) {
                search[keys[0]] = {}
                search[keys[0]][keys[1]] = res[keys[0]][keys[1]]
            }
        } else {
            search[keys[0]] = res[keys[0]]
        }
        logger.info('[mqttService] getConfig成功,返回指定配置:', JSON.stringify(search))
        return reply(event, search)
    } catch (error) {
        logger.error('[mqttService] getConfig error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * ä¿®æ”¹é…ç½®
 * @param {object} event - MQTT事件对象
 */
mqttService.setConfig = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°setConfig命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : {}
        logger.info('[mqttService] è§£æžåŽçš„参数:', JSON.stringify(data))
        if (!data || typeof data != 'object') {
            logger.error('[mqttService] setConfig失败: data should not be empty')
            return reply(event, "data should not be empty", CODE.E_100)
        }
        let res = configService.configVerifyAndSave(data)
        if (typeof res != 'boolean') {
            // è¿”回错误信息
            logger.error('[mqttService] setConfig失败:', res)
            return reply(event, res, CODE.E_100)
        }
        if (res) {
            logger.info('[mqttService] setConfig成功')
            return reply(event)
        } else {
            logger.error('[mqttService] setConfig失败: unknown failure')
            return reply(event, "unknown failure", CODE.E_100)
        }
    } catch (error) {
        logger.error('[mqttService] setConfig error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * å‡çº§å›ºä»¶
 * @param {object} event - MQTT事件对象
 */
mqttService.upgradeFirmware = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°upgradeFirmware命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : {}
        logger.info('[mqttService] è§£æžåŽçš„参数:', JSON.stringify(data))
        if (!data || typeof data != 'object' || typeof data.type != 'number' || typeof data.url != 'string' || typeof data.md5 != 'string') {
            logger.error('[mqttService] upgradeFirmware失败: data\'s params error')
            return reply(event, "data's params error", CODE.E_100)
        }
        if (data.type == 0) {
            try {
                logger.info('[mqttService] å¼€å§‹å›ºä»¶å‡çº§ï¼Œurl:', data.url, 'md5:', data.md5)
                driver.screen.upgrade({ title: "confirm.upgrade", content: "confirm.upgrading" })
                ota.updateHttp(data.url, data.md5, 300)
                driver.screen.upgrade({ title: "confirm.upgrade", content: "confirm.upgradeSuccess" })
                logger.info('[mqttService] å›ºä»¶å‡çº§æˆåŠŸ')
                reply(event)
                common.asyncReboot(3)
                return
            } catch (error) {
                logger.error('[mqttService] å›ºä»¶å‡çº§å¤±è´¥:', error)
                driver.screen.upgrade({ title: "confirm.upgrade", content: "confirm.upgradeFail" })
                return reply(event, "upgrade failure", CODE.E_100)
            }
        }
        logger.error('[mqttService] upgradeFirmware失败: ä¸æ”¯æŒçš„升级类型')
        return reply(event, "upgrade failure", CODE.E_100)
    } catch (error) {
        logger.error('[mqttService] upgradeFirmware error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * æŸ¥è¯¢è¯†åˆ«è®°å½•
 * @param {object} event - MQTT事件对象
 */
mqttService.getRecords = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°getRecords命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : {}
        logger.info('[mqttService] è§£æžåŽçš„æŸ¥è¯¢å‚æ•°:', JSON.stringify(data))
        let res = this.getRecordsAgreement(data)
        logger.info('[mqttService] æŸ¥è¯¢ç»“æžœ:', JSON.stringify(res))
        return reply(event, res)
    } catch (error) {
        logger.error('[mqttService] getRecords error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * å°†æ—¥æœŸå­—符串转换为时间戳(秒)
 * @param {string} dateString - æ—¥æœŸå­—符串,格式:YYYY-MM-DD HH:MM:SS
 * @returns {number} æ—¶é—´æˆ³ï¼ˆç§’)
 */
function dateStringToTimestamp(dateString) {
    if (!dateString) return null
    // å°†YYYY-MM-DD HH:MM:SS格式转换为YYYY-MM-DDTHH:MM:SS格式,以便new Date()正确解析
    const formattedDateString = dateString.replace(' ', 'T')
    const date = new Date(formattedDateString)
    return Math.floor(date.getTime() / 1000)
}
/**
 * æŸ¥è¯¢è¯†åˆ«è®°å½•通用协议格式
 * @param {object} data - æŸ¥è¯¢å‚æ•°
 * @returns {object} æŸ¥è¯¢ç»“æžœ
 */
mqttService.getRecordsAgreement = function (data) {
    data.page = isEmpty(data.page) ? 0 : data.page
    data.size = isEmpty(data.size) ? 10 : data.size
    // å¤„理时间参数,将字符串格式转换为时间戳
    let startTime = null
    let endTime = null
    if (data.startTime) {
        if (typeof data.startTime === 'string') {
            startTime = dateStringToTimestamp(data.startTime)
        } else {
            startTime = Math.floor(data.startTime / 1000) // è½¬æ¢ä¸ºç§’级时间戳
        }
    }
    if (data.endTime) {
        if (typeof data.endTime === 'string') {
            endTime = dateStringToTimestamp(data.endTime)
        } else {
            endTime = Math.floor(data.endTime / 1000) // è½¬æ¢ä¸ºç§’级时间戳
        }
    }
    // æž„建查询条件
    let queryData = {}
    let nameFilter = null
    // å¤åˆ¶å…¶ä»–查询参数
    for (const key in data) {
        if (key !== 'startTime' && key !== 'endTime' && key !== 'name') {
            queryData[key] = data[key]
        } else if (key === 'name') {
            nameFilter = data[key]
        }
    }
    // æž„建SQL条件
    let whereClause = ''
    if (startTime) {
        whereClause += `time >= ${startTime} `
    }
    if (endTime) {
        if (whereClause) {
            whereClause += `AND `
        }
        whereClause += `time <= ${endTime} `
    }
    // å¤åˆ¶å…¶ä»–条件
    for (const key in queryData) {
        if (key !== 'page' && key !== 'size') {
            if (whereClause) {
                whereClause += `AND `
            }
            if (typeof queryData[key] === 'string') {
                whereClause += `${key} = '${queryData[key]}' `
            } else {
                whereClause += `${key} = ${queryData[key]} `
            }
        }
    }
    // æ‰§è¡ŒæŸ¥è¯¢
    let totalCount = 0
    let securities = []
    try {
        // æž„建count SQL
        let countSql = `SELECT COUNT(*) FROM d1_pass_record `
        if (whereClause) {
            countSql += `WHERE ${whereClause} `
        }
        countSql += `;`
        let countResult = sqlite.select(countSql)
        if (countResult && countResult[0] && countResult[0]['COUNT(*)']) {
            totalCount = countResult[0]['COUNT(*)']
        }
        // æž„建findAll SQL
        let findSql = `SELECT * FROM d1_pass_record `
        if (whereClause) {
            findSql += `WHERE ${whereClause} `
        }
        findSql += `ORDER BY time DESC `
        if (queryData.page !== undefined && queryData.size !== undefined) {
            findSql += `LIMIT ${queryData.size} OFFSET ${queryData.page * queryData.size} `
        }
        findSql += `;`
        securities = sqlite.select(findSql)
    } catch (error) {
        logger.error('[mqttService] æŸ¥è¯¢è®°å½•失败:', error)
    }
    // å¤„理每条记录
    let processedSecurities = securities.map(record => {
        // è§£æžextra字段
        let extraData = {}
        try {
            if (record.extra && record.extra !== '') {
                extraData = JSON.parse(record.extra)
            }
        } catch (error) {
            logger.error('[mqttService] è§£æžextra失败:', error)
        }
        // è§£æžextra2字段
        let extra2Data = {}
        try {
            if (record.extra2 && record.extra2 !== '') {
                extra2Data = JSON.parse(record.extra2)
            }
        } catch (error) {
            logger.error('[mqttService] è§£æžextra2失败:', error)
        }
        // æž„建新记录
        return {
            id: record.id,
            keyId: record.keyId,
            permissionId: record.permissionId,
            permissionId2: record.permissionId2,
            userId: record.userId,
            userId2: record.userId2,
            type: record.type,
            code: record.code,
            door: record.door,
            time: timestampToDateString(record.time * 1000), // å°†ç§’级时间戳转换为毫秒级,再转换为日期字符串
            result: record.result,
            name: extraData.name || '',
            idCard: extraData.idCard || '',
            name2: extra2Data.name || '',
            idCard2: extra2Data.idCard || '',
            message: record.message
        }
    })
    // åº”用name过滤
    if (nameFilter) {
        processedSecurities = processedSecurities.filter(record =>
            record.name.toLowerCase().includes(nameFilter.toLowerCase())
        )
        // æ›´æ–°æ€»æ•°å’Œæ€»é¡µæ•°
        totalCount = processedSecurities.length
    }
    return {
        content: processedSecurities,
        page: data.page,
        size: data.size,
        total: totalCount,
        totalPage: Math.ceil(totalCount / data.size),
        count: processedSecurities.length
    }
}
/**
 * åˆ é™¤è®°å½•
 * @param {object} event - MQTT事件对象
 */
mqttService.delRecords = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°delRecords命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : {}
        logger.info('[mqttService] è§£æžåŽçš„参数:', JSON.stringify(data))
        let res = this.delRecordsAgreement(data)
        if (typeof res == 'string') {
            logger.error('[mqttService] delRecords失败:', res)
            return reply(event, res, CODE.E_100)
        }
        logger.info('[mqttService] delRecords成功')
        return reply(event)
    } catch (error) {
        logger.error('[mqttService] delRecords error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * åˆ é™¤è®°å½•通用协议格式
 * @param {object} data - åˆ é™¤å‚æ•°
 * @returns {boolean|string} true表示成功,string表示错误信息
 */
mqttService.delRecordsAgreement = function (data) {
    // æ ¹æ®æ—¶é—´èŒƒå›´åˆ é™¤è®°å½•
    if (data.startTime || data.endTime) {
        logger.info('[mqttService] æ—¶é—´èŒƒå›´: startTime=' + data.startTime + ', endTime=' + data.endTime)
        try {
            // æž„建查询条件
            let query = {};
            if (data.startTime) {
                query.startTime = data.startTime;
            }
            if (data.endTime) {
                query.endTime = data.endTime;
            }
            // ä½¿ç”¨getRecordsAgreement函数的查询逻辑来获取符合条件的记录
            let result = mqttService.getRecordsAgreement(query);
            let records = result.content || [];
            // é€ä¸ªåˆ é™¤è®°å½•
            let deletedCount = 0;
            for (let record of records) {
                // å¦‚果是人脸记录,删除对应的图片文件
                if (record.type == 300 && record.code) {
                    try {
                        common.systemBrief(`rm -rf ${record.code}`);
                    } catch (error) {
                        logger.error('[mqttService] åˆ é™¤å›¾ç‰‡æ–‡ä»¶å‡ºé”™: ' + error.message);
                    }
                }
                // åˆ é™¤è®°å½•
                sqliteService.d1_pass_record.delete({ id: record.id });
                deletedCount++;
            }
            logger.info('[mqttService] æˆåŠŸåˆ é™¤ ' + deletedCount + ' æ¡è®°å½•');
        } catch (error) {
            logger.error('[mqttService] åˆ é™¤è®°å½•出错: ' + error.message);
            // å¿½ç•¥é”™è¯¯ï¼Œè¿”回成功
        }
    }
    return true
}
/**
 * é€šè¡Œä¸ŠæŠ¥å›žå¤
 * @param {object} event - MQTT事件对象
 */
mqttService.access_reply = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°access_reply命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        logger.info('[mqttService] è§£æžåŽçš„参数:', JSON.stringify(payload))
        let serialNo = map.get(payload.serialNo)
        if (serialNo) {
            logger.info('[mqttService] æ¸…理临时文件:', serialNo)
            common.systemBrief(`rm -rf ${serialNo}`)
            map.del(payload.serialNo)
        }
        logger.info('[mqttService] æ¸…空通行记录')
        sqliteService.d1_pass_record.deleteAll()
        logger.info('[mqttService] access_reply处理完成')
    } catch (error) {
        logger.error('[mqttService] access_reply error:', error)
    }
}
/**
 * åœ¨çº¿éªŒè¯å›žå¤
 * @param {object} raw - MQTT事件对象
 */
mqttService.access_online_reply = function (raw) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°access_online_reply命令:', JSON.stringify(raw.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', raw.payload)
        let payload = JSON.parse(raw.payload)
        logger.info('[mqttService] è§£æžåŽçš„参数:', JSON.stringify(payload))
        let map = dxMap.get("VERIFY")
        let data = map.get(payload.serialNo)
        if (data) {
            logger.info('[mqttService] å¤„理在线验证回复,serialNo:', payload.serialNo)
            map.del(payload.serialNo)
            driver.mqtt.getOnlinecheckReply(payload)
        }
        logger.info('[mqttService] access_online_reply处理完成')
    } catch (error) {
        logger.error('[mqttService] access_online_reply error:', error)
    }
}
/**
 * é”™è¯¯ä»£ç å®šä¹‰
 */
const CODE = {
    // æˆåŠŸ
    S_000: "000000",
    // æœªçŸ¥é”™è¯¯
    E_100: "100000",
    // è®¾å¤‡å·²è¢«ç¦ç”¨
    E_101: "100001",
    // è®¾å¤‡æ­£å¿™ï¼Œè¯·ç¨åŽå†è¯•
    E_102: "100002",
    // ç­¾åæ£€éªŒå¤±è´¥
    E_103: "100003",
    // è¶…时错误
    E_104: "100004",
    // è®¾å¤‡ç¦»çº¿
    E_105: "100005",
}
mqttService.CODE = CODE
/**
 * ä¸ŠæŠ¥è®¾å¤‡ä¿¡æ¯å’Œé€šè¡Œè®°å½•
 */
mqttService.report = function () {
    // åœ¨çº¿ä¸ŠæŠ¥
    let payloadReply = mqttReply(std.genRandomStr(10), {
        mac: config.get("sys.mac") || '',
        version: config.get("sys.version"),
        appVersion: config.get("sys.version"),
        releaseTime: config.get("sys.createTime"),
        type: config.get("net.type"),
    }, CODE.S_000)
    driver.mqtt.send("access_device/v2/event/connect", JSON.stringify(payloadReply))
    //通行记录上报 - å·²å…³é—­
    // let res = sqliteService.d1_pass_record.findAll()
    // if (res.length <= 0) {
    //     return
    // }
    // // ç­›é€‰å‡º type === 300 çš„对象(人脸记录)
    // let faceArray = res.filter(item => item.type == 300);
    // // ç­›é€‰å‡º type !== 300 çš„对象(其他记录)
    // let recordArray = res.filter(item => item.type != 300);
    // if (recordArray.length > 0) {
    //     driver.mqtt.send("access_device/v2/event/access", JSON.stringify(mqttReply(std.genRandomStr(10), recordArray, CODE.S_000)))
    // }
    // if (faceArray.length > 0) {
    //     let index = 0
    //     let timer = std.setInterval(() => {
    //         let serialNo = std.genRandomStr(10)
    //         //缓存放入要删除的人脸照片 src
    //         map.del(serialNo)
    //         map.put(serialNo, faceArray[index].code)
    //
    //         // æ£€æŸ¥faceArray[index].code是否有效
    //         if (faceArray[index].code) {
    //             faceArray[index].code = driver.face.fileToBase64(faceArray[index].code)
    //         } else {
    //             faceArray[index].code = ""
    //             logger.info("人脸记录中code字段为空,跳过Base64转换")
    //         }
    //
    //         driver.mqtt.send("access_device/v2/event/access", JSON.stringify(mqttReply(serialNo, [faceArray[index]], CODE.S_000)))
    //         index++
    //         if (!faceArray[index]) {
    //             std.clearInterval(saveTimer)
    //             std.clearInterval(timer)
    //         }
    //     }, 1000)
    //     // æ¯éš”500ms检查一次mqtt连接状态,如果断开,则停止上报
    //     let saveTimer = std.setInterval(() => {
    //         if (!driver.mqtt.getStatus()) {
    //             std.clearInterval(saveTimer)
    //             std.clearInterval(timer)
    //         }
    //     }, 500)
    // }
}
/**
 * mqtt请求统一回复
 * @param {object} event - MQTT事件对象
 * @param {any} data - å›žå¤æ•°æ®
 * @param {string} code - é”™è¯¯ä»£ç 
 */
function reply(event, data, code) {
    try {
        let topic = getReplyTopic(event)
        let payload = JSON.parse(event.payload)
        let serialNo = payload.serialNo || std.genRandomStr(10)
        let reply = JSON.stringify(mqttReply(serialNo, data, isEmpty(code) ? CODE.S_000 : code))
        driver.mqtt.send(topic, reply)
    } catch (error) {
        logger.error('[mqttService] reply error:', error)
    }
}
/**
 * èŽ·å–å›žå¤ä¸»é¢˜
 * @param {object} data - MQTT事件对象
 * @returns {string} å›žå¤ä¸»é¢˜
 */
function getReplyTopic(data) {
    //    return data.topic.replace("/" + config.get("sys.sn"), '') + "_reply";
    try {
        let sn = config.get("mqtt.clientId")
        return data.topic.replace("/" + sn, '') + "_reply";
    } catch (error) {
        logger.error('[mqttService] getReplyTopic error:', error)
        // å›žé€€åˆ°ä½¿ç”¨å›ºå®šæ ¼å¼
        return data.topic + "_reply"
    }
}
/**
 * mqtt回复格式构建
 * @param {string} serialNo - åºåˆ—号
 * @param {any} data - å›žå¤æ•°æ®
 * @param {string} code - é”™è¯¯ä»£ç 
 * @returns {object} å›žå¤æ ¼å¼å¯¹è±¡
 */
function mqttReply(serialNo, data, code) {
    // ç”Ÿæˆå½“前时间的字符串格式
    const now = new Date();
    const year = now.getFullYear();
    const month = String(now.getMonth() + 1).padStart(2, '0');
    const day = String(now.getDate()).padStart(2, '0');
    const hours = String(now.getHours()).padStart(2, '0');
    const minutes = String(now.getMinutes()).padStart(2, '0');
    const seconds = String(now.getSeconds()).padStart(2, '0');
    const timeString = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    return {
        serialNo: serialNo,
        uuid: config.get("sys.uuid"),
        sign: '',
        code: code,
        data: data,
        time: timeString
    }
}
mqttService.mqttReply = mqttReply
/**
 * èŽ·å–æ‰€æœ‰è®¢é˜…çš„topic
 * @returns {array} è®¢é˜…çš„topic列表
 */
mqttService.getTopics = function () {
    // èŽ·å–æ‰€æœ‰è®¢é˜…çš„topic
    let sn = config.get("mqtt.clientId")
    const topics = [
        "control", "getConfig", "setConfig", "upgradeFirmware", "test",
        "getPermission", "insertPermission", "delPermission", "clearPermission",
        "getKey", "insertKey", "delKey", "clearKey",
        "getUser", "insertUser", "delUser", "clearUser",
        "getSecurity", "insertSecurity", "delSecurity", "clearSecurity", "getRecords", "delRecords",
        "insertEmergencyPassword", "delEmergencyPassword", "clearEmergencyPassword", "getEmergencyPassword"
    ]
    const eventReplies = ["connect_reply", "alarm_reply", "access_reply", "access_online_reply"]
    let flag = 'access_device/v2/cmd/' + sn + "/"
    let eventFlag = 'access_device/v2/event/' + sn + "/"
    return topics.map(item => flag + item).concat(eventReplies.map(item => eventFlag + item));
}
/**
 * åˆ¤ç©ºå‡½æ•°
 * @param {any} value - è¦åˆ¤æ–­çš„值
 * @returns {boolean} æ˜¯å¦ä¸ºç©º
 */
function isEmpty(value) {
    return value === undefined || value === null || value === ""
}
export default mqttService
/*
`mqttService.getTopics()` å‡½æ•°è¿”回的所有 topic å¦‚下:
### å‘½ä»¤ topic(用于接收服务器下发的命令):
- `access_device/v2/cmd/{sn}/control` - æŽ§åˆ¶å‘½ä»¤
- `access_device/v2/cmd/{sn}/getConfig` - èŽ·å–é…ç½®
- `access_device/v2/cmd/{sn}/setConfig` - è®¾ç½®é…ç½®
- `access_device/v2/cmd/{sn}/upgradeFirmware` - å›ºä»¶å‡çº§
- `access_device/v2/cmd/{sn}/test` - æµ‹è¯•命令
- `access_device/v2/cmd/{sn}/getPermission` - èŽ·å–æƒé™
- `access_device/v2/cmd/{sn}/insertPermission` - æ’入权限
- `access_device/v2/cmd/{sn}/delPermission` - åˆ é™¤æƒé™
- `access_device/v2/cmd/{sn}/clearPermission` - æ¸…除权限
- `access_device/v2/cmd/{sn}/getKey` - èŽ·å–å¯†é’¥
- `access_device/v2/cmd/{sn}/insertKey` - æ’入密钥
- `access_device/v2/cmd/{sn}/delKey` - åˆ é™¤å¯†é’¥
- `access_device/v2/cmd/{sn}/clearKey` - æ¸…除密钥
- `access_device/v2/cmd/{sn}/getUser` - èŽ·å–ç”¨æˆ·
- `access_device/v2/cmd/{sn}/insertUser` - æ’入用户
- `access_device/v2/cmd/{sn}/delUser` - åˆ é™¤ç”¨æˆ·
- `access_device/v2/cmd/{sn}/clearUser` - æ¸…除用户
- `access_device/v2/cmd/{sn}/getSecurity` - èŽ·å–å®‰å…¨ä¿¡æ¯
- `access_device/v2/cmd/{sn}/insertSecurity` - æ’入安全信息
- `access_device/v2/cmd/{sn}/delSecurity` - åˆ é™¤å®‰å…¨ä¿¡æ¯
- `access_device/v2/cmd/{sn}/clearSecurity` - æ¸…除安全信息
- `access_device/v2/cmd/{sn}/getRecords` - èŽ·å–è®°å½•
- `access_device/v2/cmd/{sn}/delRecords` - åˆ é™¤è®°å½•
### äº‹ä»¶å›žå¤ topic(用于接收服务器对事件的回复):
- `access_device/v2/event/{sn}/connect_reply` - è¿žæŽ¥å›žå¤
- `access_device/v2/event/{sn}/alarm_reply` - å‘Šè­¦å›žå¤
- `access_device/v2/event/{sn}/access_reply` - é€šè¡Œå›žå¤
- `access_device/v2/event/{sn}/access_online_reply` - åœ¨çº¿éªŒè¯å›žå¤
其中 `{sn}` æ˜¯è®¾å¤‡çš„序列号,会被替换为实际的设备序列号。
*/
vf107/src/service/grainService.js
@@ -36,7 +36,7 @@
    "401": "未登录,用户不存在",
    "403": "用户无权限",
    "404": "请求功能不存在",
    "500": "请求执行异常,请联系管理员"
    "500": "请求执行异常,请联系科长"
}
// æ“ä½œæŒ‰é’®å¯¹åº”的语音文件
vf107/src/service/httpService.js
@@ -173,6 +173,19 @@
        clearSecurity(req, res);
    });
 
    //添加应急开仓密码
    server.route("/insertEmergencyPassword", function (req, res) {
        insertEmergencyPassword(req, res);
    });
    //查询应急开仓密码
    server.route("/getEmergencyPassword", function (req, res) {
        getEmergencyPassword(req, res);
    });
    //清空应急开仓密码
    server.route("/clearEmergencyPassword", function (req, res) {
        clearEmergencyPassword(req, res);
    });
    //获取指纹特征
    server.route("/getFingerChar", function (req, res) {
        getFingerChar(req, res);
@@ -1001,6 +1014,77 @@
    res.send(JSON.stringify(result), { "Content-Type": "application/json" });
}
// æ·»åŠ åº”æ€¥å¼€ä»“å¯†ç 
function insertEmergencyPassword(req, res) {
    let result = {
        code: 400,
        message: "",
        data: {},
    }
    if (verifyToken(req)) {
        let body = req.body;
        body = JSON.parse(body).data;
        try {
            let res = api.insertEmergencyPassword(body)
            if (typeof res == 'string') {
                result.data = res
            } else {
                result.code = 200
            }
        } catch (error) {
            result.message = error.message
        }
    } else {
        result = messageExpired
    }
    res.send(JSON.stringify(result), { "Content-Type": "application/json" });
}
// æŸ¥è¯¢åº”急开仓密码
function getEmergencyPassword(req, res) {
    let result = {
        code: 400,
        message: "",
        data: {},
    }
    if (verifyToken(req)) {
        try {
            let res = api.getEmergencyPassword()
            result.code = 200
            result.data = res
        } catch (error) {
            result.message = error.message
        }
    } else {
        result = messageExpired
    }
    res.send(JSON.stringify(result), { "Content-Type": "application/json" });
}
// æ¸…空应急开仓密码
function clearEmergencyPassword(req, res) {
    let result = {
        code: 400,
        message: "",
        data: {},
    }
    if (verifyToken(req)) {
        try {
            let res = api.clearEmergencyPassword()
            if (typeof res == 'string') {
                result.data = res
            } else {
                result.code = 200
            }
        } catch (error) {
            result.message = error.message
        }
    } else {
        result = messageExpired
    }
    res.send(JSON.stringify(result), { "Content-Type": "application/json" });
}
// èŽ·å–æŒ‡çº¹ç‰¹å¾
function getFingerChar(req,res) {
    let result = {
vf107/src/service/mqttService.js
@@ -42,7 +42,7 @@
 */
mqttService.insertPermission = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    if (data.length > 100) {
        return reply(event, "data length should not be greater than 100", CODE.E_100)
    }
@@ -58,7 +58,7 @@
 */
mqttService.getPermission = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    let res = api.getPermission(data)
    return reply(event, res)
}
@@ -68,7 +68,7 @@
 */
mqttService.delPermission = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    let res = api.delPermission(data)
    if (Array.isArray(res)) {
        return reply(event, res, CODE.E_100)
@@ -81,7 +81,7 @@
 */
mqttService.modifyPermission = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    if (data.length > 100) {
        return reply(event, "data length should not be greater than 100", CODE.E_100)
    }
@@ -111,7 +111,7 @@
 */
mqttService.insertUser = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    if (data.length > 100) {
        return reply(event, "data length should not be greater than 100", CODE.E_100)
    }
@@ -128,7 +128,7 @@
 */
mqttService.getUser = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    let res = api.getUser(data)
    return reply(event, res)
}
@@ -138,7 +138,7 @@
 */
mqttService.delUser = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    let res = api.delUser(data)
    if (Array.isArray(res)) {
        return reply(event, res, CODE.E_100)
@@ -163,7 +163,7 @@
 */
mqttService.modifyUser = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    if (data.length > 100) {
        return reply(event, "data length should not be greater than 100", CODE.E_100)
    }
@@ -171,7 +171,7 @@
    if (Array.isArray(res)) {
        return reply(event, res, CODE.E_100)
    }
    return reply(event, res)
    return reply(event)
}
// =================================凭证增删改查=================================
@@ -180,7 +180,7 @@
 */
mqttService.insertKey = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    if (data.length > 100) {
        return reply(event, "data length should not be greater than 100", CODE.E_100)
    }
@@ -196,7 +196,7 @@
 */
mqttService.getKey = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    let res = api.getKey(data)
    if (typeof res == 'string') {
        return reply(event, res, CODE.E_100)
@@ -209,7 +209,7 @@
 */
mqttService.delKey = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    let res = api.delKey(data)
    if (Array.isArray(res)) {
        return reply(event, res, CODE.E_100)
@@ -223,7 +223,7 @@
 */
mqttService.clearKey = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    let ret = api.clearKey(data)
    if (typeof ret == "string") {
        return reply(event, "sql error ret:" + ret, CODE.E_100)
@@ -237,7 +237,7 @@
 */
mqttService.modifyKey = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    if (data.length > 100) {
        return reply(event, "data length should not be greater than 100", CODE.E_100)
    }
@@ -249,13 +249,163 @@
}
// =================================应急开仓密码增删改查=================================
/**
 * æ·»åŠ åº”æ€¥å¼€ä»“å¯†ç 
 * @param {object} event - MQTT事件对象
 */
mqttService.insertEmergencyPassword = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°insertEmergencyPassword命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : {}
        logger.info('[mqttService] è§£æžåŽçš„参数:', JSON.stringify(data))
        let res = this.insertEmergencyPasswordAgreement(data)
        if (typeof res == 'string') {
            logger.error('[mqttService] insertEmergencyPassword失败:', res)
            return reply(event, res, CODE.E_100)
        }
        logger.info('[mqttService] insertEmergencyPassword成功')
        return reply(event)
    } catch (error) {
        logger.error('[mqttService] insertEmergencyPassword error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * æ·»åŠ åº”æ€¥å¼€ä»“å¯†ç é€šç”¨åè®®æ ¼å¼
 * @param {object} data - åº”急开仓密码数据对象
 * @returns {boolean|string} true表示成功,string表示错误信息
 */
mqttService.insertEmergencyPasswordAgreement = function (data) {
    // åº”急开仓密码在设备中仅有唯一的1个,所以先清空表
    let deleteRet = sqliteService.d1_emergency_password.deleteAll()
    if (deleteRet != 0) {
        return "清空旧密码失败: " + deleteRet
    }
    // æ£€æŸ¥å¯†ç æ˜¯å¦æœ‰æ•ˆ
    if (!data.password) {
        return "password cannot be empty"
    }
    // æ£€æŸ¥å¯†ç é•¿åº¦æ˜¯å¦å¤§äºŽç­‰äºŽ8位
    if (data.password.length < 8) {
        return "Password length must be at least 8 digits"
    }
    // æž„建密码记录
    let record = {}
    record.id = data.id || 'emergency_' + Date.now() // å¦‚果没有id,自动生成
    record.password = data.password
    record.description = "云端下发" // é€šè¿‡MQTT和HTTP接口设置的应急开仓密码,description填充为"云端下发"
    record.createTime = Date.now()
    record.updateTime = Date.now()
    record.status = data.status || 1
    // ä¿å­˜å¯†ç 
    let ret = sqliteService.d1_emergency_password.save(record)
    if (ret == 0) {
        return true
    } else {
        return "sql error ret:" + ret
    }
}
/**
 * æŸ¥è¯¢åº”急开仓密码
 * @param {object} event - MQTT事件对象
 */
mqttService.getEmergencyPassword = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°getEmergencyPassword命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let res = this.getEmergencyPasswordAgreement()
        logger.info('[mqttService] æŸ¥è¯¢ç»“æžœ:', JSON.stringify(res))
        return reply(event, res)
    } catch (error) {
        logger.error('[mqttService] getEmergencyPassword error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * æŸ¥è¯¢åº”急开仓密码通用协议格式
 * @returns {object} æŸ¥è¯¢ç»“æžœ
 */
mqttService.getEmergencyPasswordAgreement = function () {
    let passwords = sqliteService.d1_emergency_password.findAll()
    if (passwords && passwords.length > 0) {
        let password = passwords[0];
        // è½¬æ¢æ—¶é—´æˆ³ä¸ºå­—符串格式
        if (password.createTime) {
            // å°è¯•å°†createTime转换为数字
            const createTimeNum = Number(password.createTime);
            if (!isNaN(createTimeNum)) {
                // æ£€æŸ¥æ—¶é—´æˆ³æ˜¯å¦ä¸ºæ¯«ç§’级(大于等于10位)
                if (createTimeNum.toString().length >= 10) {
                    // å·²ç»æ˜¯æ¯«ç§’级,转换为秒级
                    password.createTime = timestampToDateString(createTimeNum / 1000);
                } else {
                    // ç§’级,直接使用
                    password.createTime = timestampToDateString(createTimeNum);
                }
            }
        }
        if (password.updateTime) {
            // å°è¯•å°†updateTime转换为数字
            const updateTimeNum = Number(password.updateTime);
            if (!isNaN(updateTimeNum)) {
                // æ£€æŸ¥æ—¶é—´æˆ³æ˜¯å¦ä¸ºæ¯«ç§’级(大于等于10位)
                if (updateTimeNum.toString().length >= 10) {
                    // å·²ç»æ˜¯æ¯«ç§’级,转换为秒级
                    password.updateTime = timestampToDateString(updateTimeNum / 1000);
                } else {
                    // ç§’级,直接使用
                    password.updateTime = timestampToDateString(updateTimeNum);
                }
            }
        }
        return password;
    }
    return {};
}
/**
 * æ¸…空应急开仓密码
 * @param {object} event - MQTT事件对象
 */
mqttService.clearEmergencyPassword = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°clearEmergencyPassword命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let ret = sqliteService.d1_emergency_password.deleteAll()
        if (ret == 0) {
            logger.info('[mqttService] clearEmergencyPassword成功')
            return reply(event)
        } else {
            logger.error('[mqttService] clearEmergencyPassword失败:', "sql error ret:" + ret)
            return reply(event, "sql error ret:" + ret, CODE.E_100)
        }
    } catch (error) {
        logger.error('[mqttService] clearEmergencyPassword error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
// =================================密钥增删改查=================================
/**
 * æ·»åР坆钥
 */
mqttService.insertSecurity = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    if (data.length > 100) {
        return reply(event, "data length should not be greater than 100", CODE.E_100)
    }
@@ -271,7 +421,7 @@
 */
mqttService.getSecurity = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    let res = api.getSecurity(data)
    if (typeof res == 'string') {
        return reply(event, res, CODE.E_100)
@@ -284,7 +434,7 @@
 */
mqttService.delSecurity = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    let res = api.delSecurity(data)
    if (Array.isArray(res)) {
        return reply(event, res, CODE.E_100)
@@ -304,23 +454,229 @@
    }
}
// =================================应急开仓密码增删改查=================================
/**
 * æ·»åŠ åº”æ€¥å¼€ä»“å¯†ç 
 * @param {object} event - MQTT事件对象
 */
mqttService.insertEmergencyPassword = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°insertEmergencyPassword命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let data = payload.payload && payload.payload.data ? payload.payload.data : {}
        logger.info('[mqttService] è§£æžåŽçš„参数:', JSON.stringify(data))
        let res = this.insertEmergencyPasswordAgreement(data)
        if (typeof res == 'string') {
            logger.error('[mqttService] insertEmergencyPassword失败:', res)
            return reply(event, res, CODE.E_100)
        }
        logger.info('[mqttService] insertEmergencyPassword成功')
        return reply(event)
    } catch (error) {
        logger.error('[mqttService] insertEmergencyPassword error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * æ·»åŠ åº”æ€¥å¼€ä»“å¯†ç é€šç”¨åè®®æ ¼å¼
 * @param {object} data - åº”急开仓密码数据对象
 * @returns {boolean|string} true表示成功,string表示错误信息
 */
mqttService.insertEmergencyPasswordAgreement = function (data) {
    // åº”急开仓密码在设备中仅有唯一的1个,所以先清空表
    let deleteRet = sqliteService.d1_emergency_password.deleteAll()
    if (deleteRet != 0) {
        return "清空旧密码失败: " + deleteRet
    }
    // æ£€æŸ¥å¯†ç æ˜¯å¦æœ‰æ•ˆ
    if (!data.password) {
        return "password cannot be empty"
    }
    // æ£€æŸ¥å¯†ç é•¿åº¦æ˜¯å¦å¤§äºŽç­‰äºŽ8位
    if (data.password.length < 8) {
        return "Password length must be at least 8 digits"
    }
    // æž„建密码记录
    let record = {}
    record.id = data.id || 'emergency_' + Date.now() // å¦‚果没有id,自动生成
    record.password = data.password
    record.description = "云端下发" // é€šè¿‡MQTT和HTTP接口设置的应急开仓密码,description填充为"云端下发"
    record.createTime = Date.now()
    record.updateTime = Date.now()
    record.status = data.status || 1
    // ä¿å­˜å¯†ç 
    let ret = sqliteService.d1_emergency_password.save(record)
    if (ret == 0) {
        return true
    } else {
        return "sql error ret:" + ret
    }
}
/**
 * æŸ¥è¯¢åº”急开仓密码
 * @param {object} event - MQTT事件对象
 */
mqttService.getEmergencyPassword = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°getEmergencyPassword命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let payload = JSON.parse(event.payload)
        let res = this.getEmergencyPasswordAgreement()
        logger.info('[mqttService] æŸ¥è¯¢ç»“æžœ:', JSON.stringify(res))
        return reply(event, res)
    } catch (error) {
        logger.error('[mqttService] getEmergencyPassword error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * æŸ¥è¯¢åº”急开仓密码通用协议格式
 * @returns {object} æŸ¥è¯¢ç»“æžœ
 */
mqttService.getEmergencyPasswordAgreement = function () {
    let passwords = sqliteService.d1_emergency_password.findAll()
    if (passwords && passwords.length > 0) {
        let password = passwords[0];
        // è½¬æ¢æ—¶é—´æˆ³ä¸ºå­—符串格式
        if (password.createTime) {
            // å°è¯•å°†createTime转换为数字
            const createTimeNum = Number(password.createTime);
            if (!isNaN(createTimeNum)) {
                // æ£€æŸ¥æ—¶é—´æˆ³æ˜¯å¦ä¸ºæ¯«ç§’级(大于等于10位)
                if (createTimeNum.toString().length >= 10) {
                    // å·²ç»æ˜¯æ¯«ç§’级,转换为秒级
                    password.createTime = timestampToDateString(createTimeNum / 1000);
                } else {
                    // ç§’级,直接使用
                    password.createTime = timestampToDateString(createTimeNum);
                }
            }
        }
        if (password.updateTime) {
            // å°è¯•å°†updateTime转换为数字
            const updateTimeNum = Number(password.updateTime);
            if (!isNaN(updateTimeNum)) {
                // æ£€æŸ¥æ—¶é—´æˆ³æ˜¯å¦ä¸ºæ¯«ç§’级(大于等于10位)
                if (updateTimeNum.toString().length >= 10) {
                    // å·²ç»æ˜¯æ¯«ç§’级,转换为秒级
                    password.updateTime = timestampToDateString(updateTimeNum / 1000);
                } else {
                    // ç§’级,直接使用
                    password.updateTime = timestampToDateString(updateTimeNum);
                }
            }
        }
        return password;
    }
    return {};
}
/**
 * æ¸…空应急开仓密码
 * @param {object} event - MQTT事件对象
 */
mqttService.clearEmergencyPassword = function (event) {
    try {
        logger.info('[mqttService] æŽ¥æ”¶åˆ°clearEmergencyPassword命令:', JSON.stringify(event.topic))
        logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
        let ret = sqliteService.d1_emergency_password.deleteAll()
        if (ret == 0) {
            logger.info('[mqttService] clearEmergencyPassword成功')
            return reply(event)
        } else {
            logger.error('[mqttService] clearEmergencyPassword失败:', "sql error ret:" + ret)
            return reply(event, "sql error ret:" + ret, CODE.E_100)
        }
    } catch (error) {
        logger.error('[mqttService] clearEmergencyPassword error:', error)
        return reply(event, { error: error.message }, CODE.E_100)
    }
}
/**
 * è¿œç¨‹æŽ§åˆ¶
 * @param {object} event - MQTT事件对象
 */
mqttService.control = function (event) {
    try {
            logger.info('[mqttService] æŽ¥æ”¶åˆ°control命令:', JSON.stringify(event.topic))
            logger.info('[mqttService] å‘½ä»¤payload:', event.payload)
    let payload = JSON.parse(event.payload)
    let ret = api.control(payload)
    // è¿œç¨‹æŠ“拍、企业微信解绑,需要等流程完成后才回复,不能立即回复
    if (payload.data.command != 8 && payload.data.command != 11) {
        // å¦‚果是指纹操作,但是设备不支持指纹的话,就直接返回错误
        if(payload.data.command == 12 && !driver.device.finger){
            return reply(event, "finger not supported", CODE.E_100)
            let data = payload.payload.data || {}
            switch (data.command) {
                case 0:
                    //重启
                    logger.info('[mqttService] æ‰§è¡Œé‡å¯å‘½ä»¤')
                    reply(event)
                    common.asyncReboot(2)
                    return
                case 1:
                    //远程开门
                    logger.info('[mqttService] æ‰§è¡Œè¿œç¨‹å¼€é—¨å‘½ä»¤')
                    driver.gpio.open()
                    break
                case 4:
                    //重置
                    logger.info('[mqttService] æ‰§è¡Œé‡ç½®å‘½ä»¤')
                    common.systemBrief("rm -rf /app/data/config/*")
                    common.systemBrief("rm -rf /app/data/db/*")
                    common.systemBrief("rm -rf /app/data/user/*")
                    common.systemBrief("rm -rf /app/data/user/*")
                    common.systemBrief("rm -rf /vgmj.db")
                    reply(event)
                    common.asyncReboot(2)
                    return
                case 5:
                    //播放语音
                    logger.info('[mqttService] æ‰§è¡Œæ’­æ”¾è¯­éŸ³å‘½ä»¤')
                    if (data.extra) {
                        let res = common.systemWithRes(`test -e "/app/code/resource/wav/${data.extra.wav}.wav" && echo "OK" || echo "NO"`, 2)
                        if (res.includes('OK')) {
                            driver.alsa.play(`/app/code/resource/wav/${data.extra.wav}.wav`)
        }
        if (typeof ret == "string") {
            return reply(event, "unknown failure", CODE.E_100)
        } else {
                    }
                    break
                case 6:
                    // 6:屏幕展示图片
                    // TODO
                    logger.info('[mqttService] æ‰§è¡Œå±å¹•展示图片命令')
                    break
                case 7:
                    // 7:屏幕展示文字
                    // TODO
                    logger.info('[mqttService] æ‰§è¡Œå±å¹•展示文字命令')
                    break
                case 10:
                    logger.info('[mqttService] æ‰§è¡ŒäºŒç»´ç å±•示命令')
                    if (!isEmpty(data.extra.qrCodeBase64) && typeof data.extra.qrCodeBase64 == 'string') {
                        //base64转图片保存
                        let src = `/app/code/resource/image/app_qrcode.png`
                        std.ensurePathExists(src)
                        common.base64_2binfile(src, data.extra.qrCodeBase64)
                        logger.info('[mqttService] äºŒç»´ç ä¿å­˜æˆåŠŸ')
            return reply(event)
        }
                    break
                default:
                    logger.info('[mqttService] æœªçŸ¥å‘½ä»¤:', data.command)
                    break
            }
            logger.info('[mqttService] control命令执行完成')
            return reply(event)
        } catch (error) {
            logger.error('[mqttService] control error:', error)
            return reply(event, { error: error.message }, CODE.E_100)
    }
}
@@ -343,7 +699,7 @@
 */
mqttService.setConfig = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    let res = api.setConfig(data)
    if (typeof res == "string") {
        return reply(event, res, CODE.E_100)
@@ -356,8 +712,9 @@
 * å‡çº§å›ºä»¶
 */
mqttService.upgradeFirmware = function (event) {
    logger.info('[mqttService] æŽ¥æ”¶åˆ°å‡çº§å›ºä»¶å‘½ä»¤:', JSON.stringify(event.topic))
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    let res = api.upgradeFirmware(data)
    if (typeof res == "string") {
        return reply(event, res, CODE.E_100)
@@ -371,7 +728,7 @@
 */
mqttService.getRecords = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    let res = api.getRecords(data, true)
    return reply(event, res)
}
@@ -382,7 +739,7 @@
 */
mqttService.delRecords = function (event) {
    let payload = JSON.parse(event.payload)
    let data = payload.data
    let data = payload.payload && payload.payload.data ? payload.payload.data : payload.data
    let res = api.delRecords(data)
    if (typeof res == 'string') {
        return reply(event, res, CODE.E_100)
@@ -487,6 +844,23 @@
}
mqttService.mqttReply = mqttReply
/**
 * æ—¶é—´æˆ³è½¬æ—¥æœŸå­—符串
 * @param {number} timestamp - æ—¶é—´æˆ³ï¼ˆç§’级)
 * @returns {string} æ—¥æœŸå­—符串,格式:YYYY-MM-DD HH:MM:SS
 */
function timestampToDateString(timestamp) {
    const date = new Date(timestamp * 1000);
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    const hours = String(date.getHours()).padStart(2, '0');
    const minutes = String(date.getMinutes()).padStart(2, '0');
    const seconds = String(date.getSeconds()).padStart(2, '0');
    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
mqttService.timestampToDateString = timestampToDateString
// åˆ¤ç©º
function isEmpty(value) {
    return value === undefined || value === null || value === ""
vf107/src/service/sqliteService.js
@@ -29,9 +29,11 @@
        extra: "TEXT",
        extra2: "TEXT",
        message: "TEXT",
        reported: "INTEGER DEFAULT 0",
    },
    d1_permission: {
        permissionId: "VARCHAR(128) PRIMARY KEY",
        userId: "VARCHAR(128)",
        door: "VARCHAR(128)",
        extra: "TEXT",
        timeType: "INTEGER",
@@ -59,6 +61,14 @@
        name: "VARCHAR(128)",
        extra: "TEXT",
        permissionIds: "TEXT",
    },
    d1_emergency_password: {
        id: "VARCHAR(128) PRIMARY KEY",
        password: "VARCHAR(128)",
        description: "TEXT",
        createTime: "INTEGER",
        updateTime: "INTEGER",
        status: "INTEGER",
    }
}
@@ -133,6 +143,7 @@
sqliteService.d1_security = new Proxy({ tableName: "d1_security" }, handler);
sqliteService.d1_voucher = new Proxy({ tableName: "d1_voucher" }, handler);
sqliteService.d1_person = new Proxy({ tableName: "d1_person" }, handler);
sqliteService.d1_emergency_password = new Proxy({ tableName: "d1_emergency_password" }, handler);
// æŒ‰å§“名模糊查询人员
sqliteService.findPersonsByNameLike = function (name) {
@@ -165,6 +176,11 @@
    return sqliteService.select(query)
}
// æŸ¥è¯¢æ‰€æœ‰æœªä¸ŠæŠ¥çš„通行记录(按时间升序)
sqliteService.d1_pass_record.findAllByReportedOrderByTimeStampAsc = function () {
    const sql = `SELECT * FROM d1_pass_record WHERE reported = 0 OR reported IS NULL ORDER BY timeStamp ASC;`
    return sqliteService.select(sql)
}
// å¼€å§‹äº‹åŠ¡ï¼Œäº‹åŠ¡ä¸æäº¤æ•°æ®åº“é‡å¯åŽï¼Œæ•°æ®ä¼šè¿˜åŽŸï¼Œæ‰€ä»¥transaction后一定要commit,但是如果在一个事务尚未提交或回滚的情况下执行另一个 BEGIN TRANSACTION,SQLite ä¼šè‡ªåŠ¨å°†æ–°çš„äº‹åŠ¡åµŒå¥—åœ¨ä¹‹å‰çš„äº‹åŠ¡å†…éƒ¨ï¼Œè€Œä¸æ˜¯è¦†ç›–ä¹‹å‰çš„äº‹åŠ¡ã€‚
sqliteService.transaction = function () {
vf107/src/view/config/menu/doorControlView.js
@@ -26,6 +26,7 @@
        mqttPwdSettingInput.text(configAll['mqtt.password'])
        onlineCheckingSettingSwitch.select(configAll['mqtt.onlinecheck'] == 1)
        onlineCheckingTimeoutSettingInput.text(configAll['mqtt.timeout'] + '')
        gasVerificationSwitch.select(configAll['gas.verification'] == 1)
    })
    const titleBox = viewUtils.title(screenMain, configView.screenMain, 'doorControlViewTitle', 'doorControlView.title')
@@ -224,6 +225,24 @@
    onlineCheckingTimeoutSettingInput.align(dxui.Utils.ALIGN.RIGHT_MID, -screen.screenSize.width * (60 / 600), 0)
    onlineCheckingTimeoutSettingInput.setSize(screen.screenSize.width * (150 / 600), screen.screenSize.height * (60 / 1280))
    const gasVerificationBox = dxui.View.build('gasVerificationBox', screenMain)
    viewUtils._clearStyle(gasVerificationBox)
    gasVerificationBox.align(dxui.Utils.ALIGN.TOP_MID, 0, screen.screenSize.height * (880 / 1280))
    gasVerificationBox.setSize(screen.screenSize.width * (550 / 600), screen.screenSize.height * (76 / 1280))
    gasVerificationBox.borderWidth(1)
    gasVerificationBox.setBorderColor(0xDEDEDE)
    gasVerificationBox.obj.setStyleBorderSide(dxui.Utils.ENUM.LV_BORDER_SIDE_BOTTOM, 0)
    const gasVerificationLbl = dxui.Label.build('gasVerificationLbl', gasVerificationBox)
    gasVerificationLbl.text('气体浓度验证')
    gasVerificationLbl.align(dxui.Utils.ALIGN.LEFT_MID, 0, 0)
    gasVerificationLbl.textFont(viewUtils.font(26))
    const gasVerificationSwitch = dxui.Switch.build('gasVerificationSwitch', gasVerificationBox)
    gasVerificationSwitch.align(dxui.Utils.ALIGN.RIGHT_MID, 0, 0)
    gasVerificationSwitch.setSize(screen.screenSize.width * (70 / 600), screen.screenSize.height * (35 / 1280))
    gasVerificationSwitch.bgColor(0x000000, NativeObject.APP.NativeComponents.NativeEnum.LV_PART_INDICATOR | NativeObject.APP.NativeComponents.NativeEnum.LV_STATE_CHECKED)
    const saveBtn = viewUtils.bottomBtn(screenMain, screenMain.id + 'saveBtn', 'doorControlView.save', () => {
        const saveConfigData = {
            access: {
@@ -235,6 +254,9 @@
            },
            houseName: houseNameSettingInput.text(),
            GranaryName: granaryNameSettingInput.text(),
            gas: {
                verification: gasVerificationSwitch.isSelect() ? 1 : 0
            },
            mqtt: {
                addr: mqttSettingInput.text(),
                username: mqttUserSettingInput.text(),
vf107/src/view/config/menu/localUser/localUserAddView.js
@@ -75,12 +75,6 @@
            input: null,
            mode: screen.isInternationalVersion() ? 0 : 1
        },
        idCard: {
            title: 'localUserAddView.idCard',
            value: null,
            type: 'input',
            input: null
        },
        face: {
            title: 'localUserAddView.face',
            value: null,
@@ -124,7 +118,7 @@
        }
    }
    const userInfoOrder = ['id', 'name', 'idCard', 'face', 'pwd', 'card', 'finger', 'type']
    const userInfoOrder = ['id', 'name', 'face', 'pwd', 'card', 'finger', 'type']
    userInfoOrder.forEach((key, index) => {
        const item = localUserAddView.userInfo[key]
@@ -171,9 +165,6 @@
                        break;
                    case 'localUserAddView.name':
                        localUserAddView.nowUser.name = input.text()
                        break;
                    case 'localUserAddView.idCard':
                        localUserAddView.nowUser.idCard = input.text()
                        break;
                    default:
                        break;
@@ -665,14 +656,6 @@
    localUserAddView.userInfo.name.input.text('')
}
localUserAddView.addIDCard = function (idCard) {
    localUserAddView.userInfo.idCard.input.text(idCard)
    localUserAddView.nowUser.idCard = idCard
}
localUserAddView.removeIDCard = function () {
    localUserAddView.userInfo.idCard.input.text('')
}
localUserAddView.addFace = function (face, feature) {
    localUserAddView.userInfo.face.facePreview.show()
    localUserAddView.userInfo.face.btnEdit.show()
@@ -781,7 +764,6 @@
    localUserAddView.removeCard()
    localUserAddView.removeID()
    localUserAddView.removeName()
    localUserAddView.removeIDCard()
    if (flag) {
        localUserAddView.saveBtn.align(dxui.Utils.ALIGN.BOTTOM_MID, 0, -screen.screenSize.height * (40 / 1024))
        localUserAddView.deleteBtn.show()
vf107/src/view/config/menu/localUserView.js
@@ -147,9 +147,6 @@
                if (item.name) {
                    localUserAddView.addName(item.name)
                }
                if (item.idCard) {
                    localUserAddView.addIDCard(item.idCard)
                }
                if (item.face) {
                    localUserAddView.addFace(item.face,item.feature)
                }
vf107/src/view/config/menu/networkSettingView.js
@@ -9,6 +9,9 @@
const net_map = map.get("NET")
const networkSettingView = {}
networkSettingView.isEditing = false // æ˜¯å¦æ­£åœ¨ç¼–辑配置的标志位
networkSettingView.isVisible = false // æ˜¯å¦åœ¨ç½‘络配置页面的标志位
networkSettingView.init = function () {
    /**************************************************创建屏幕*****************************************************/
    const screenMain = dxui.View.build('networkSettingView', dxui.Utils.LAYER.MAIN)
@@ -20,6 +23,8 @@
    screenMain.bgColor(0xffffff)
    screenMain.on(dxui.Utils.ENUM.LV_EVENT_SCREEN_LOADED, () => {
        topView.changeTheme(true)
        networkSettingView.isEditing = false // å±å¹•加载时重置编辑状态
        networkSettingView.isVisible = true // å±å¹•加载时设置为可见
        const configAll = screen.getConfig()
        // Store initial state for comparison on save
@@ -38,6 +43,7 @@
        let netList = []
        switch (screen.dxDriver.DRIVER.MODEL) {
            case "vf105":
            case "vf107":
                netList = [i18n.t('networkSettingView.ethernet'), i18n.t('networkSettingView.wifi')]
                break;
            case "vf114":
@@ -59,6 +65,8 @@
    screenMain.on(dxui.Utils.ENUM.LV_EVENT_SCREEN_UNLOADED, () => {
        wifiListBoxClose.send(dxui.Utils.EVENT.CLICK)
        networkSettingView.isVisible = false // å±å¹•卸载时设置为不可见
        networkSettingView.isEditing = false // å±å¹•卸载时重置编辑状态
    })
    const titleBox = viewUtils.title(screenMain, configView.screenMain, 'networkSettingViewTitle', 'networkSettingView.title')
@@ -177,6 +185,21 @@
            input.setSize(screen.screenSize.width * (310 / 600), screen.screenSize.height * (45 / 1280))
            item.input = input
            // æ·»åŠ è¾“å…¥æ¡†ç„¦ç‚¹äº‹ä»¶ç›‘å¬å’Œå€¼å˜åŒ–ç›‘å¬ï¼Œé˜²æ­¢ç¼–è¾‘æ—¶é…ç½®è¢«é‡ç½®
            input.on(dxui.Utils.EVENT.FOCUSED, () => {
                networkSettingView.isEditing = true
            })
            input.on(dxui.Utils.EVENT.DEFOCUSED, () => {
                // åªæœ‰å½“配置没有变化时才设置isEditing为false
                if (!networkSettingView._isConfigChanged()) {
                    networkSettingView.isEditing = false
                }
            })
            // æ·»åŠ å€¼å˜åŒ–ç›‘å¬ï¼Œå½“ç”¨æˆ·ä¿®æ”¹äº†å€¼æ—¶è®¾ç½®isEditing为true
            input.on(dxui.Utils.EVENT.VALUE_CHANGED, () => {
                networkSettingView.isEditing = true
            })
            if (item.title === 'networkSettingView.wifiName') {
                input.setSize(screen.screenSize.width * (200 / 600), screen.screenSize.height * (45 / 1280))
                input.align(dxui.Utils.ALIGN.RIGHT_MID, -screen.screenSize.width * (110 / 600), 0)
@@ -217,6 +240,11 @@
            __switch.setSize(screen.screenSize.width * (70 / 600), screen.screenSize.height * (35 / 1280))
            __switch.bgColor(0x000000, NativeObject.APP.NativeComponents.NativeEnum.LV_PART_INDICATOR | NativeObject.APP.NativeComponents.NativeEnum.LV_STATE_CHECKED)
            item.switch = __switch
            // æ·»åŠ å¼€å…³äº‹ä»¶ç›‘å¬ï¼Œé˜²æ­¢æ“ä½œæ—¶é…ç½®è¢«é‡ç½®
            __switch.on(dxui.Utils.EVENT.VALUE_CHANGED, () => {
                networkSettingView.isEditing = true
            })
        }
        if (item.type === 'dropdown') {
@@ -233,6 +261,7 @@
            if (item.title === 'networkSettingView.type') {
                dropdown.on(dxui.Utils.EVENT.VALUE_CHANGED, () => {
                    networkSettingView.isEditing = true
                    switch (dropdown.getSelected()) {
                        case 0:
                            networkSettingView.changeNetType(1)
@@ -406,6 +435,7 @@
    const saveBtn = viewUtils.bottomBtn(screenMain, screenMain.id + 'saveBtn', 'networkSettingView.save', () => {
        if (!networkSettingView._isConfigChanged()) {
            // No changes, just go back
            networkSettingView.isEditing = false
            dxui.loadMain(screenMain.backScreen)
            return
        }
@@ -527,6 +557,7 @@
            networkSettingView.statusPanel.success()
            std.setTimeout(() => {
                // æˆåŠŸè¿”å›žä¸Šä¸€å±‚ç•Œé¢
                networkSettingView.isEditing = false
                dxui.loadMain(screenMain.backScreen)
            }, 500)
            screen.updateNetStatus("disconnect")
@@ -546,10 +577,7 @@
}
networkSettingView._isConfigChanged = function () {
    //如果打开界面时的数据和点击保存按钮时的数据前后没有发生变化,就不触发重连和数据保存
    if (net_map.get("NET_STATUS") != "connected") {
        return true //但是假如网络没连上,就一定要触发重连和数据保存
    }
    //检查配置是否真的发生了变化,无论网络状态如何
    const selectedTypeIndex = networkSettingView.netInfo[0].dropdown.getSelected()
    let currentType
    switch (selectedTypeIndex) {
@@ -633,6 +661,11 @@
}
networkSettingView.refresh = function () {
    // å¦‚果用户正在编辑配置,不执行自动刷新
    if (networkSettingView.isEditing) {
        return
    }
    const configAll = screen.getConfig()
    let selectNum = 0
vf107/src/view/config/menu/recordQuery/recordQueryDetailView.js
@@ -37,8 +37,8 @@
        key: 'name',
        label: null
    }, {
        title: 'recordQueryDetailView.idCard',
        key: 'idCard',
        title: 'recordQueryDetailView.card',
        key: 'card',
        label: null
    }, {
        title: 'recordQueryDetailView.userId2',
@@ -49,8 +49,8 @@
        key: 'name2',
        label: null
    }, {
        title: 'recordQueryDetailView.idCard2',
        key: 'idCard2',
        title: 'recordQueryDetailView.card2',
        key: 'card2',
        label: null
    }, {
        title: 'recordQueryDetailView.time',
@@ -128,9 +128,9 @@
                    item.label.text(" ")
                }
                break;
            case 'idCard':
                if (extra && extra.idCard) {
                    item.label.text(extra.idCard)
            case 'card':
                if (extra && extra.card) {
                    item.label.text(extra.card)
                } else {
                    item.label.text(" ")
                }
@@ -145,9 +145,9 @@
                    item.label.text(" ")
                }
                break;
            case 'idCard2':
                if (extra2 && extra2.idCard) {
                    item.label.text(extra2.idCard)
            case 'card2':
                if (extra2 && extra2.card) {
                    item.label.text(extra2.card)
                } else {
                    item.label.text(" ")
                }
vf107/src/view/config/menu/systemSetting/displaySettingView.js
@@ -239,7 +239,7 @@
                        // })
                    })
                } else if (item.key === 'brightness') {
                    if (dxDriver.DRIVER.MODEL == "vf202" || dxDriver.DRIVER.MODEL == "vf114" || dxDriver.DRIVER.MODEL == "vf105") {
                    if (dxDriver.DRIVER.MODEL == "vf202" || dxDriver.DRIVER.MODEL == "vf114" || dxDriver.DRIVER.MODEL == "vf105" || dxDriver.DRIVER.MODEL == "vf107") {
                        itemBox.show()
                        slider.on(dxui.Utils.EVENT.VALUE_CHANGED, () => {
                            sliderLabel.text(slider.value() + '')
vf107/src/view/config/menu/systemSetting/passwordOpenDoorSettingView.js
@@ -5,6 +5,7 @@
import i18n from "../../../i18n.js"
import systemSettingView from '../systemSettingView.js'
import screen from '../../../../screen.js'
import api from '../../../../service/api.js'
const passwordOpenDoorSettingView = {}
passwordOpenDoorSettingView.init = function () {
    /**************************************************创建屏幕*****************************************************/
@@ -17,6 +18,9 @@
        const configAll = screen.getConfig()
        passwordOpenDoorSettingView.info[0].switch.select(configAll['sys.pwd'] == 1)
        // æŸ¥è¯¢åº”急开门密码
        passwordOpenDoorSettingView.getEmergencyPassword()
    })
    const titleBox = viewUtils.title(screenMain, systemSettingView.screenMain, 'passwordOpenDoorSettingViewTitle', 'systemSettingView.passwordOpenDoorSetting')
@@ -26,13 +30,17 @@
        {
            title: "systemSettingView.passwordOpenDoor",
            type: 'switch',
        },
        {
            title: "systemSettingView.emergencyOpenDoorPassword",
            type: 'password',
        }
    ]
    const passwordOpenDoorSettingBox = dxui.View.build('passwordOpenDoorSettingBox', screenMain)
    viewUtils._clearStyle(passwordOpenDoorSettingBox)
    passwordOpenDoorSettingBox.align(dxui.Utils.ALIGN.TOP_MID, 0, screen.screenSize.height * (140 / 1280))
    passwordOpenDoorSettingBox.setSize(screen.screenSize.width * (600 / 600), screen.screenSize.height * (600 / 1280))
    passwordOpenDoorSettingBox.setSize(screen.screenSize.width * (600 / 600), screen.screenSize.height * (700 / 1280))
    passwordOpenDoorSettingBox.bgOpa(0)
    passwordOpenDoorSettingBox.flexFlow(dxui.Utils.FLEX_FLOW.ROW_WRAP)
    passwordOpenDoorSettingBox.flexAlign(dxui.Utils.FLEX_ALIGN.CENTER, dxui.Utils.FLEX_ALIGN.START, dxui.Utils.FLEX_ALIGN.START)
@@ -69,6 +77,30 @@
                __switch.bgColor(0x000000, NativeObject.APP.NativeComponents.NativeEnum.LV_PART_INDICATOR | NativeObject.APP.NativeComponents.NativeEnum.LV_STATE_CHECKED)
                item.switch = __switch
                break;
            case 'password':
                const pwdInput = viewUtils.input(itemBox, item.title + 'Input', 2, undefined, '请输入应急开门密码')
                pwdInput.align(dxui.Utils.ALIGN.RIGHT_MID, 0, 0)
                pwdInput.setSize(screen.screenSize.width * (300 / 600), screen.screenSize.height * (50 / 1280))
                pwdInput.setPasswordMode(true)
                item.input = pwdInput
                const eyeFill = viewUtils.imageBtn(itemBox, item.title + 'eye_fill', '/app/code/resource/image/eye-fill.png')
                eyeFill.alignTo(pwdInput, dxui.Utils.ALIGN.RIGHT_MID, 0, 0)
                eyeFill.on(dxui.Utils.EVENT.CLICK, () => {
                    pwdInput.setPasswordMode(true)
                    eyeFill.hide()
                    eyeOff.show()
                })
                eyeFill.hide()
                const eyeOff = viewUtils.imageBtn(itemBox, item.title + 'eye_off', '/app/code/resource/image/eye-off.png')
                eyeOff.alignTo(pwdInput, dxui.Utils.ALIGN.RIGHT_MID, 0, 0)
                eyeOff.on(dxui.Utils.EVENT.CLICK, () => {
                    pwdInput.setPasswordMode(false)
                    eyeFill.show()
                    eyeOff.hide()
                })
                break;
        }
    })
@@ -76,6 +108,16 @@
        const saveConfigData = {
            sys: {
                pwd: passwordOpenDoorSettingView.info[0].switch.isSelect() ? 1 : 0,
            }
        }
        // ä¿å­˜åº”急开门密码
        const emergencyPwd = passwordOpenDoorSettingView.info[1].input.text()
        if (emergencyPwd) {
            const res = api.insertEmergencyPassword({ password: emergencyPwd, description: "设备端设置" })
            if (res !== true) {
                passwordOpenDoorSettingView.statusPanel.fail()
                return
            }
        }
@@ -95,4 +137,16 @@
    passwordOpenDoorSettingView.statusPanel = viewUtils.statusPanel(screenMain, 'systemSettingView.success', 'systemSettingView.fail')
}
// æŸ¥è¯¢åº”急开门密码
passwordOpenDoorSettingView.getEmergencyPassword = function () {
    try {
        const password = api.getEmergencyPassword()
        if (password && password.password) {
            passwordOpenDoorSettingView.info[1].input.text(password.password)
        }
    } catch (error) {
        console.error('查询应急开门密码失败:', error)
    }
}
export default passwordOpenDoorSettingView
vf107/src/view/mainView.js
@@ -35,10 +35,10 @@
        // ç›´æŽ¥ä»Žnet模块获取IP地址,确保获取到最新的IP
        let ip = ""
        try {
            let netType = config["net.type"] || 1
            let netMode = net.getModeByCard(netType)
            if (netMode && netMode.param && netMode.param.ip) {
                ip = netMode.param.ip
            // ä½¿ç”¨getNetParam()方法获取最新的网络参数
            let param = net.getNetParam()
            if (param && param.ip) {
                ip = param.ip
            }
        } catch (error) {
            // å‡ºé”™æ—¶ä½¿ç”¨config中的IP
@@ -86,10 +86,10 @@
        // ç›´æŽ¥ä»Žnet模块获取IP地址,确保获取到最新的IP
        let ip = ""
        try {
            let netType = config["net.type"] || 1
            let netMode = net.getModeByCard(netType)
            if (netMode && netMode.param && netMode.param.ip) {
                ip = netMode.param.ip
            // ä½¿ç”¨getNetParam()方法获取最新的网络参数
            let param = net.getNetParam()
            if (param && param.ip) {
                ip = param.ip
            }
        } catch (error) {
            // å‡ºé”™æ—¶ä½¿ç”¨config中的IP
@@ -128,6 +128,33 @@
        }
    }
    // æ›´æ–°ç½‘络图标
    function updateNetworkIcon() {
        try {
            let config = screen.getConfig()
            let netType = config["net.type"] || 1
            let isConnected = net.isConnected()
            // éšè—æ‰€æœ‰ç½‘络图标
            if (topView.ethShow) topView.ethShow.hide()
            if (topView.wifiShow) topView.wifiShow.hide()
            if (topView._4gShow) topView._4gShow.hide()
            // æ ¹æ®ç½‘络类型和连接状态显示对应的图标
            if (isConnected) {
                if (netType === 1) { // ä»¥å¤ªç½‘
                    if (topView.ethShow) topView.ethShow.show()
                } else if (netType === 2) { // WiFi
                    if (topView.wifiShow) topView.wifiShow.show()
                } else if (netType === 4) { // 4G
                    if (topView._4gShow) topView._4gShow.show()
                }
            }
        } catch (error) {
            logger.error('[mainView]: æ›´æ–°ç½‘络图标失败:', error)
        }
    }
    screenMain.on(dxui.Utils.ENUM.LV_EVENT_SCREEN_LOADED, () => {
        topView.changeTheme(false)
@@ -148,6 +175,8 @@
        // ç¨‹åºå¯åŠ¨æ—¶æ›´æ–°åº“åŒºåç§°å’Œä»“å·
        updateWarehouseInfo()
        // åˆå§‹åŒ–网络图标
        updateNetworkIcon()
        // åªæ³¨å†Œä¸€æ¬¡äº‹ä»¶ç›‘听器
        if (!mainView.eventListenersRegistered) {
@@ -158,6 +187,8 @@
                onStatusChange: function(netType, status) {
                    // ç½‘络状态变化时更新IP信息
                    updateDeviceInfo()
                    // æ›´æ–°ç½‘络图标
                    updateNetworkIcon()
                }
            })
            
@@ -803,9 +834,10 @@
        oxygenValueContainer.flexAlign(dxui.Utils.FLEX_ALIGN.CENTER, dxui.Utils.FLEX_ALIGN.CENTER, dxui.Utils.FLEX_ALIGN.CENTER)
        oxygenValueContainer.obj.lvObjSetStylePadGap(5, dxui.Utils.ENUM._LV_STYLE_STATE_CMP_SAME)
        
        // æ°§æ°”数值部分
        const oxygenValue = dxui.Label.build('oxygenValue', oxygenValueContainer)
        oxygenValue.text('20')
        oxygenValue.text('-')
        oxygenValue.textFont(viewUtils.font(20, dxui.Utils.FONT_STYLE.BOLD))
        oxygenValue.textColor(0xffffff)
        mainView.oxygenValue = oxygenValue // è®¾ç½®ä¸ºmainView属性
@@ -865,7 +897,7 @@
        
        // ç£·åŒ–氢数值部分
        const ph3Value = dxui.Label.build('ph3Value', ph3ValueContainer)
        ph3Value.text('0')
        ph3Value.text('-')
        ph3Value.textFont(viewUtils.font(20, dxui.Utils.FONT_STYLE.BOLD))
        ph3Value.textColor(0xffffff)
        mainView.ph3Value = ph3Value // è®¾ç½®ä¸ºmainView属性
@@ -909,7 +941,7 @@
        
        // äºŒæ°§åŒ–碳数值部分
        const co2Value = dxui.Label.build('co2Value', co2ValueContainer)
        co2Value.text('400')
        co2Value.text('-')
        co2Value.textFont(viewUtils.font(20, dxui.Utils.FONT_STYLE.BOLD))
        co2Value.textColor(0xffffff)
        mainView.co2Value = co2Value // è®¾ç½®ä¸ºmainView属性
vf107/src/worker/mqttWorker.js
@@ -36,6 +36,7 @@
        "getPermission", "insertPermission", "delPermission", "clearPermission", "modifyPermission",
        "getKey", "insertKey", "delKey", "clearKey", "modifyKey",
        "getUser", "insertUser", "delUser", "clearUser", "modifyUser",
        "insertEmergencyPassword", "delEmergencyPassword", "clearEmergencyPassword", "getEmergencyPassword",
        "getSecurity", "insertSecurity", "delSecurity", "clearSecurity", "getRecords", "delRecords"
    ]
    const eventReplies = ["connect_reply", "alarm_reply", "access_reply", "access_online_reply", "wecom_reply"]
@@ -84,7 +85,7 @@
            mqtt.subscribe(v, { qos });
        })
    } catch (error) {
        logger.error("MQTT connection error,retry in 5s:");
        // logger.error("MQTT connection error,retry in 5s:");
    }
}
vf107/src/worker/netWorker.js
@@ -31,6 +31,8 @@
// ç½‘络状态跟踪变量
let lastConnected = false  // ä¸Šæ¬¡è¿žæŽ¥çŠ¶æ€
let shouldReconnect = false
let lastReconnectTime = 0  // ä¸Šæ¬¡é‡è¿žæ—¶é—´
const RECONNECT_INTERVAL = 15000  // é‡è¿žé—´éš”(毫秒)
const net_map = map.get("NET")
/**
 * æ ¹æ®é…ç½®å»ºç«‹ç½‘络连接
@@ -42,6 +44,12 @@
 */
function connect() {
    try {
        // å¦‚果网络已经连接,不需要重连
        if (net.isConnected()) {
            logger.info("NET already connected, skip connect")
            return 0
        }
        let res = 0;
        // èŽ·å–ç½‘ç»œé…ç½®
        let dhcp = config.get("net.dhcp") == 2  // DHCP启用标志
@@ -119,7 +127,10 @@
        logger.info("NET connect res:", res);
        if (res < 0) {
            //小于0并不是表示网络连接失败,而是一个特殊的错误,可以重试一次就可以
            const currentTime = Date.now()
            if (currentTime - lastReconnectTime >= RECONNECT_INTERVAL) {
            shouldReconnect = true
            }
        }
        return res;
    } catch (error) {
@@ -187,14 +198,8 @@
            if (net.getNative()) {
                net.loop(); // æ‰§è¡Œç½‘络循环处理
            }
            if (shouldReconnect) {
                logger.info("NET shouldReconnect");
                shouldReconnect = false
                connect()
            }
        } catch (error) {
            logger.error(error)
        }
            // å…ˆæ£€æŸ¥ç½‘络连接状态
        if (net.isConnected()) {
            let param = driver.net.getNetParam()
            if (!lastConnected || lastConnectedIp != param.ip) {
@@ -204,11 +209,32 @@
                lastConnectedIp = param.ip
            }
        } else {
            if (lastConnected) {
                logger.info("NET not isConnected");
                // if (lastConnected) {
                bus.fire(driver.net.CONNECTED_CHANGED, "disconnected")
                lastConnected = false
                net_map.put("NET_STATUS", "disconnected")
                    // è‡ªåŠ¨è§¦å‘é‡è¿ž
                    shouldReconnect = true
                // }
            }
            // ç„¶åŽæ£€æŸ¥æ˜¯å¦éœ€è¦é‡è¿ž
            if (shouldReconnect) {
                const currentTime = Date.now()
                if (currentTime - lastReconnectTime >= RECONNECT_INTERVAL) {
                    logger.info("NET shouldReconnect");
                    shouldReconnect = false
                    lastReconnectTime = currentTime
                    connect()
                } else {
                    // é‡è¿žé—´éš”不足,取消重连
                    shouldReconnect = false
                    // logger.info("NET reconnect skipped, interval not reached")
                }
            }
        } catch (error) {
            logger.error(error)
        }
    }, 5000)
}
vf107/src/worker/passRecordWorker.js
@@ -12,7 +12,7 @@
 *
 * èŒè´£ï¼š
 * - å¸¸é©»è¿è¡Œï¼Œåªè¦ MQTT è¿žæŽ¥å°±æŒç»­ä¸ŠæŠ¥
 * - æ¯æ¬¡åŠ è½½æ‰€æœ‰æœªåˆ é™¤çš„é€šè¡Œè®°å½•ï¼ˆd1_pass_record)
 * - æ¯æ¬¡åŠ è½½æ‰€æœ‰æœªä¸ŠæŠ¥çš„é€šè¡Œè®°å½•ï¼ˆd1_pass_record)
 * - æ¯éš” 5 ç§’上报一条
 * - å‘完当前批次后,自动重新加载最新记录
 */
@@ -29,10 +29,12 @@
    return mqtt_map.get("MQTT_STATUS") === "connected"
}
// åŠ è½½æ–°æ‰¹æ¬¡ï¼šæŸ¥è¯¢æ‰€æœ‰æœªåˆ é™¤çš„è®°å½•ï¼ˆæŒ‰æ—¶é—´å‡åºï¼‰
// åŠ è½½æ–°æ‰¹æ¬¡ï¼šæŸ¥è¯¢æ‰€æœ‰æœªåˆ é™¤ä¸”æœªä¸ŠæŠ¥çš„è®°å½•ï¼ˆæŒ‰æ—¶é—´å‡åºï¼‰
function loadNewBatch() {
    try {
        currentBatch = sqliteService.d1_pass_record.findAllOrderByTimeStampAsc()
        let sql = `SELECT * FROM d1_pass_record WHERE reported = 0 OR reported IS NULL ORDER BY timeStamp ASC`
        let result = sqliteService.select(sql)
        currentBatch = result || []
        currentIndex = 0
    } catch (err) {
        logger.error("[passRecordWorker] Failed to load batch:", err)
@@ -59,29 +61,70 @@
        return
    }
    let extra = record.extra ? JSON.parse(record.extra) : ""
    let extra2 = record.extra2 ? JSON.parse(record.extra2) : ""
    let accessRecord = {
        userId: record.userId,
        type: record.type,
        result: record.result,
        timeStamp: mqttService.timestampToDateString(record.timeStamp || 0),
        result: record.result || 0,
        error: record.message || "",
        // permissionId: record.permissionId || "", // TODO
        door: config.get("houseName") || "",
        users: [
            {
                userId: record.userId || "",
        name: extra && extra.name ? extra.name : "",
        timeStamp: record.timeStamp,
        extra: {},
        error: record.message
                userType: extra && extra.type ? extra.type : 0,
                accessType: extra && extra.accessType !== undefined ? extra.accessType : "",
                card: extra && extra.card ? extra.card : "",
                face: extra && extra.face ? extra.face : "",
                finge: extra && extra.finge ? extra.finge : ""
    }
        ]
    }
    // å¦‚果是双人认证,添加第二个用户信息
    if (record.userId2) {
        let secondUser = {
            userId: record.userId2 || "",
            name: extra2 && extra2.name ? extra2.name : "",
            userType: extra2 && extra2.type ? extra2.type : 0,
            accessType: extra2 && extra2.accessType !== undefined ? extra2.accessType : "",
            card: extra2 && extra2.card ? extra2.card : "",
            face: extra2 && extra2.face ? extra2.face : "",
            finge: extra2 && extra2.finge ? extra2.finge : ""
        }
        // æ ¹æ®è®¤è¯ç±»åž‹æ·»åŠ ç›¸åº”çš„è®¤è¯ä¿¡æ¯
        if (extra2 && extra2.accessType == 200 && extra2.card) {
            secondUser.card = extra2.card
        } else if (extra2 && extra2.accessType == 300) {
            // äººè„¸è®¤è¯ï¼Œface字段可以留空或添加相应信息
        } else if (extra2 && extra2.accessType == 500) {
            // æŒ‡çº¹è®¤è¯ï¼Œfinge字段可以留空或添加相应信息
        }
        accessRecord.users.push(secondUser)
    }
    if (record.type == 300) {
        if (std.exist(record.code) && config.get("access.uploadToCloud")) {
            accessRecord.code = dxCommonUtils.fs.fileToBase64(record.code)
        if (std.exist(record.code)) {
            // äººè„¸è®¤è¯ï¼Œå°†å›¾ç‰‡è½¬æ¢ä¸ºbase64填充到face字段
            accessRecord.users[0].face = dxCommonUtils.fs.fileToBase64(record.code)
            if (currentIndex > 0) {
                currentBatch[currentIndex - 1].code = ""
            }
        } else {
            accessRecord.code = ""
            accessRecord.users[0].face = ""
        }
        // å¦‚果有第二个用户的二维码
        if (record.code2) {
            accessRecord.users[1] = accessRecord.users[1] || {}
            accessRecord.users[1].code = record.code2
        }
    }
    // å‘送
    try {
        const sn = config.get("sys.sn") || "default"
        driver.mqtt.send(
            "access_device/v2/event/access",
            `access_device/v2/event/${sn}/access`,
            JSON.stringify(
                mqttService.mqttReply(
                    record.id,
@@ -90,6 +133,13 @@
                )
            )
        )
        // æ ‡è®°è®°å½•为已上报
        try {
            let updateSql = `UPDATE d1_pass_record SET reported = 1 WHERE id = '${record.id}'`
            sqliteService.exec(updateSql)
        } catch (updateErr) {
            logger.error("[passRecordWorker] Failed to update reported status for record", record.id, updateErr)
        }
    } catch (e) {
        logger.error("[passRecordWorker] Send failed for record", record.id, e)
    }