Stream and validate native feature loading with fast decoding and bounded SDK batches. Reconcile authoritative department snapshots with rollback-safe metadata and file updates. Bound downloads, isolate duplicate identifiers, and defer unchanged invalid photo versions. Remove unused legacy face APIs and document device measurements. Validation: 42 Flutter tests in an isolated source snapshot; native unit and device checks; all 5,537 registered features verified byte-for-byte, plus real SDK photo searches and metadata checks.
6.4 KiB
6.4 KiB
人脸差异同步修复与验证
2026-10-22,Flutter 3.27.4 / RK3568 Android 11。数据与日志保留在本机私有临时审计目录,本文不包含人员、图片地址或特征。
已确认的问题
- 本部门只有 846 条远端人脸,本地还有全租户调试导入的其他人脸,共 5,537 条。原同步仍读取、检查全部本地档案和文件。
- 预下载尚未完成时,主流程会重复下载相同照片;预取窗口无消费背压,中断时仍会下载后续清单。
- 删除范围由远端“当前有照片的部门”反推。某个子部门完全删空时,其本地残留不会进入孤儿删除范围。
- 姓名或用户绑定只更新 SQLite,内存识别档案保留旧值。可空部门名与空字符串的比较也会产生重复写入。
- 质量接口的
ignoredNotApprovedSampleIds未落确认标记,一条历史结果在每次同步末尾反复上报。 - 全租户清单实际有 5,560 行、5,559 个唯一编号,其中一组编号冲突。旧实现任意选择先读到的一条;另有 22 张照片无法提取注册特征(无可检测人脸、口罩或 SDK 图片特征错误),每次同步都会重新下载和提取。
- V2 使用 OFFSET 分页,不能直接将并发变化造成的漏页当成本地删除;旧代码还会把任意质量请求失败拆成最多二十次请求重试。
实现
- 部门清单分页提高到服务端上限 500,独立拉取真实部门树,覆盖没有人脸的子部门。只查询范围内档案,并按编号补查新入部门的已有调试档案,防止重复插入。
- 全租户使用 V2 上限 1,000 条分页并关闭无用总数查询;按物理主键检测重复分页,隔离整组冲突编号并保留本地样本。确实需要删除时,在任何本地变更前再次拉取完整清单并核对指纹,变化则中止。
- 比对采用独立差集计划。未变化记录没有图片下载、特征提取或原生写入;照片 URL/版本变化时先对比字节摘要,相同内容复用已有特征和质量结果。缺少历史版本时先核对一次照片,避免盲信同一 URL。
- 元数据最多 100 条一个 SQLite 事务;仅姓名/用户绑定变化时批量修改原生映射,不重新注册特征。原生先校验整批再修改,失败时回滚 SQLite。人脸时间字段无损保存微秒和时区,不影响其他业务实体。
- 下载窗口限制为 3,等待同一在途任务,复用 HTTP 连接;退出后只等待已开始的请求。下载的完整响应限时 30 秒,正式文件使用唯一版本路径。
- 对原生明确判定为图片内容错误的提取失败,按照片版本暂缓 15 分钟。记录只存摘要和到期时间,原子替换文件;版本/地址变化立即重试,成功后移除,损坏缓存不会阻断同步。网络、授权和插件异常不进入此记录。
- 图片存在性改为按目录分块读取;保留小批量和异常回退。中断遗留的
.pending-delete若仍有数据库引用则恢复;SQL 删除失败恢复图片及原生特征并准确统计失败。 - 质量上报只查询未确认记录,完整处理所有确认类别。整批请求失败留到下轮,停止拆批放大重试。目录清扫按变更或一小时周期执行。
- 人脸清单日志只输出条数,不在截断前遍历、脱敏和字符串化所有人员档案;进度回调按 150 ms 合并。
部门同步继续保留范围外调试档案;全租户同步继续仅在 Debug 环境提供。复用当前服务端已有接口,本次没有修改或部署服务端,也没有修改远端冲突样本或替换无效照片。
删除的无调用生产代码
删除旧 syncFace 包装、V1 人脸清单 API、旧文件名/直接下载助手、无调用 DAO、registerFaceByDatabase Dart/原生通道及包装、旧 active/getConfig/getFace 通道与模型、静态激活标记,以及无调用的旧整库/单条注册包装。旧算法仅保留在实际执行 A/B 的 androidTest 中,不进入业务 APK。
验证
- 最终提交的独立源码副本 42 项 Flutter 测试通过,Debug / Release 均构建成功。
- 回归使用真实 SQLite/Floor 事务和本地 HTTP 图片服务;模拟原生通道以注入失败,覆盖重复同步、照片变化/相同字节、元数据和事务回滚、空部门删除、范围外转入、SQL 删除失败、分页中断、冲突隔离、V2 删除复核、中断图片恢复、质量确认及网络失败、坏图版本重试和到期。
- 真机 SDK 校验 5,537 条特征的每个字节和全部编号,四张实际照片生成 RECOGNIZE 特征检索;增加原生档案更新不改变特征和整批拒绝不部分生效的两项检查。
- 真机本部门 846 条全部跳过、无下载与提取;全量清单 5,559 个唯一编号,5,536 条正常本地人脸直接跳过,一组冲突及 22 张不可注册照片如实报告。
- 全量首轮建立失败版本记录后,跨进程重启复测得到 downloads=0、extractions=0、nativeUpserts=0、metadataUpdates=0、deferredImages=22,耗时 18,127 ms(含首次目录清扫 2,114 ms);不把异常照片当作同步成功。
- 随后完整重复全量同步耗时 11,897 ms,同样是零下载、零提取、零特征/档案写入,22 个失败版本仍被暂缓。
- 最新部门同步耗时 4,112 ms,846 条全部跳过、失败 0;其中清单 2,114 ms、本地档案读取 394 ms、文件核对 1,298 ms。
- 再次部门同步同样 846 条全部跳过、失败 0;两次任务记录耗时分别为 4,280 / 6,503 ms(包含调度记录开销与网络波动)。
- 稳定复制真机数据库后与初始备份逐行核对:5,537 条人脸的 id、sampleId、userId、faceModel 完全不变,v15 完整性检查正常。原有 32 条订单逐字段完整保留,挂单仍为 0。设备期间另外产生了 2 条 FACE/PAY 记录(15:17 / 15:22 创建),本任务没有发起人脸支付,也没有删除或改写这些新增记录;不将整个验证期间描述成订单数量不变。
- 最终版全量首轮 19,059 ms;前一轮中间版本为 38,319 ms。分页、日志及网络条件同时变化,这些同步数字用于现场诊断,不构成受控速度比。
- 静态分析无 error。Floor 新增两个
IN查询生成四条命名/字符串风格提示,其余为既有提示;没有手工改写生成代码。
启动性能 A/B、装库实现与测试命令见 face-startup-performance.md。同步耗时包含网络,单次数字不能作为受控速度比。