This commit is contained in:
dzc committed 2026-08-31 15:09:38 +08:00
1 parent f6b37348c4
commit 917f03bc58
61 files changed
+4233 -367

No files matched your search

+154 -5
View File
@@ -322,12 +322,12 @@ abstract class SyncFaceTool {
var databaseChanged = false;
try {
temporaryFile = await _downloadTemporaryFace(record, storagePath);
final featureData = AppRuntimeTool.useManualHardwareInput
? <int>[]
: (await FaceDiscernPlugs.extractFaceFeature(
final extraction = AppRuntimeTool.useManualHardwareInput
? null
: await FaceDiscernPlugs.extractFaceFeature(
filePath: temporaryFile.path,
))
.featureData;
);
final featureData = extraction?.featureData ?? <int>[];
if (!AppRuntimeTool.useManualHardwareInput && featureData.isEmpty) {
throw const FaceSyncSnapshotException('图片未提取到可注册人脸');
}
@@ -341,6 +341,8 @@ abstract class SyncFaceTool {
existing: existing,
path: finalFile.path,
faceModel: featureData,
extraction: extraction,
imageReplaced: true,
);
if (existing == null) {
final id = await faceDao.save(entity);
@@ -439,6 +441,128 @@ abstract class SyncFaceTool {
}
}
/// 通过与同步孤儿删除相同的可回滚流程清理一条已获服务端确认的人脸。
///
/// 调用方必须先完成服务端删除或取得“服务端已不存在”确认;失败时本方法恢复
/// 图片、SQLite 和虹软内存特征,并返回 false 供页面保留重试入口。
static Future<bool> deleteLocalFace(FaceEntity face) =>
_deleteLocalFace(face, Get.find<AppControl>().dataBase.getFaceDao);
/// 手动检测存量在库照片质量并将结果批量回写 SQLite。
///
/// [failedOnly] 为 true 时仅重试上次失败记录;不存在或不可读的本地文件会直接
/// 标记为 failed,其他图片交给 Android 原生单线程质量任务处理。
static Future<FaceQualityAuditResult> auditLibraryFaceQuality({
bool failedOnly = false,
}) async {
final faceDao = Get.find<AppControl>().dataBase.getFaceDao;
final allCandidates = await faceDao.findQualityAuditCandidates();
final candidates = failedOnly
? allCandidates
.where(
(face) => face.qualityStatus == FaceQualityStatus.failed.code,
)
.toList(growable: false)
: allCandidates;
if (candidates.isEmpty) {
return const FaceQualityAuditResult(
results: [],
message: '没有需要检测的人脸',
);
}
final invalidResults = <FaceImageQualityResult>[];
final validFaces = <FaceEntity>[];
for (final face in candidates) {
final path = face.path;
if (path == null || path.isEmpty || !await File(path).exists()) {
invalidResults.add(FaceImageQualityResult(
sampleId: face.sampleId,
status: FaceQualityStatus.failed,
message: '本地人脸图片不存在',
errorCode: -2,
));
} else {
validFaces.add(face);
}
}
FaceQualityAuditResult nativeResult = const FaceQualityAuditResult(
results: [],
message: '在库人脸质量检测完成',
);
if (validFaces.isNotEmpty) {
try {
nativeResult = await FaceDiscernPlugs.auditFaceQuality(
items: validFaces
.map(
(face) => FaceQualityAuditItem(
sampleId: face.sampleId,
filePath: face.path!,
),
)
.toList(growable: false),
);
} catch (error, stack) {
logger.w('|$_tag|原生质量检测通道失败,将样本记录为检测失败|error:$error|stack:$stack|');
nativeResult = FaceQualityAuditResult(
results: [
for (final face in validFaces)
FaceImageQualityResult(
sampleId: face.sampleId,
status: FaceQualityStatus.failed,
message: '原生质量检测不可用',
errorCode: -99,
),
],
message: '原生质量检测不可用,样本已标记为检测失败',
);
}
}
final results = <FaceImageQualityResult>[
...invalidResults,
...nativeResult.results,
];
final bySampleId = {
for (final result in results) result.sampleId: result,
};
final checkedAt = DateTime.now();
final updates = <FaceEntity>[];
for (final face in candidates) {
final result = bySampleId[face.sampleId] ??
FaceImageQualityResult(
sampleId: face.sampleId,
status: FaceQualityStatus.failed,
message: '原生未返回该样本的质量结果',
errorCode: -99,
);
face
..qualityStatus = result.status.code
..qualityScore =
result.status == FaceQualityStatus.detected ? result.score : null
..qualityCheckedAt = checkedAt
..qualityErrorCode = result.status == FaceQualityStatus.detected
? null
: result.errorCode;
updates.add(face);
}
if (updates.isNotEmpty) await faceDao.batchEdit(updates);
return FaceQualityAuditResult(
results: [
for (final face in candidates)
bySampleId[face.sampleId] ??
FaceImageQualityResult(
sampleId: face.sampleId,
status: FaceQualityStatus.failed,
message: '原生未返回该样本的质量结果',
errorCode: -99,
),
],
message: nativeResult.message,
);
}
/// 删除回滚时恢复旧内存特征;历史记录无特征时按用户从仍存在的 SQLite 重新装载。
static Future<void> _restoreNativeFace(FaceEntity face) async {
if (AppRuntimeTool.useManualHardwareInput) return;
@@ -494,6 +618,8 @@ abstract class SyncFaceTool {
required FaceEntity? existing,
String? path,
List<int>? faceModel,
FaceFeatureExtractionResult? extraction,
bool imageReplaced = false,
}) =>
FaceEntity(
id: existing?.id,
@@ -507,6 +633,29 @@ abstract class SyncFaceTool {
faceModel: faceModel ?? existing?.faceModel,
createTime: existing?.createTime ?? DateTime.now(),
updateTime: record.sourceUpdatedAt ?? existing?.updateTime,
qualityScore: extraction == null
? imageReplaced
? null
: existing?.qualityScore
: extraction.qualityStatus == FaceQualityStatus.detected
? extraction.qualityScore
: null,
qualityStatus: extraction?.qualityStatus.code ??
(imageReplaced ? FaceQualityStatus.pending.code : null) ??
existing?.qualityStatus ??
FaceQualityStatus.pending.code,
qualityCheckedAt: extraction == null
? imageReplaced
? null
: existing?.qualityCheckedAt
: DateTime.now(),
qualityErrorCode: extraction == null
? imageReplaced
? null
: existing?.qualityErrorCode
: extraction.qualityStatus == FaceQualityStatus.detected
? null
: extraction.qualityErrorCode,
);
/// 删除指定本地文件;失败只记录文件路径以外的异常类型,避免同步主流程被清理动作中断。