import 'dart:io'; import 'package:common_utils/common_utils.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pj_supermarket_cloud/common/index.dart'; class SyncFaceTool { static const _tag = "sync人脸工具"; // 删除数据库中为空的人脸 static Future delEmptyFace() async { final app = Get.find(); final faceDao = app.dataBase.getFaceDao; final faceList = await faceDao.getEmptyFaceList(); if (ObjectUtil.isEmptyList(faceList)) return; for (final v in faceList!) { if (ObjectUtil.isNotEmpty(v.path)) { await delFaceByStorage(filePath: v.path!); } await faceDao.delById(v.id!); } } // 同步人脸 static Future syncFace({ bool isAll = false, VoidCallback? onSyncGroup, }) async { final appSetting = await AppSettingTool.getSysSetting(); final merchantDeptId = appSetting.merchantDeptId; // 人脸存储路径 final storagePath = await AppFileTool.getFacePath(); if (ObjectUtil.isEmpty(storagePath)) { logger.e("|$_tag|同步人脸错误,创建人脸存储路径错误"); return; } // 同步没有的人脸 int pageNo = 1; bool isNext = true; while (isNext) { final bool = await syncGroupFace( pageNo: pageNo, merchantDeptId: isAll ? "" : merchantDeptId!, storagePath: storagePath!, ); if (isNext != bool) isNext = bool; if (!bool) break; pageNo += 1; onSyncGroup?.call(); } } static Future syncGroupFace({ required int pageNo, required String merchantDeptId, required String storagePath, String? userId, bool? isGlobalFace, }) async { final app = Get.find(); final faceDao = app.dataBase.getFaceDao; const size = 100; var pa = { "current": pageNo, "size": size, "userId": userId, "merchantDeptId": merchantDeptId, }; if (isGlobalFace != null && isGlobalFace) pa["merchantDeptId"] = null; logger.i("|$_tag|同步人脸api参数:|$pa|"); try { final data = await UserApi.getFaceListV2(pa); if (data == null) throw Error(); if (ObjectUtil.isEmptyList(data)) { if (ObjectUtil.isEmpty(userId)) return false; // 删除人脸图片 final faceList = await faceDao.findByUserId(userId!); if (!ObjectUtil.isEmptyList(faceList)) { for (final v in faceList!) { if (ObjectUtil.isNotEmpty(v.path)) { await delFaceByStorage(filePath: v.path!); await AppFaceTool.removeFace(sampleId: v.sampleId); } } } // 删除数据库 await faceDao.delByUserId(userId); return false; } if (isGlobalFace != null && !isGlobalFace) return false; if (userId != null) { final dbUserList = await faceDao.findByUserId(userId); if (!ObjectUtil.isEmptyList(dbUserList)) { for (final v in dbUserList!) { final target = data.firstWhereOrNull((i) => i.id == v.sampleId); if (target == null) { if (ObjectUtil.isNotEmpty(v.path)) { await delFaceByStorage(filePath: v.path!); await AppFaceTool.removeFace(sampleId: v.sampleId); } await faceDao.delById(v.id!); } } } } for (final v in data) { if (ObjectUtil.isEmpty(v.autoCode) || ObjectUtil.isEmpty(v.userId)) { logger.e( "|$_tag|同步人脸跳过无效数据:|autoCode=${v.autoCode}|userId=${v.userId}|deptId=${v.deptId}|username=${v.username}|", ); continue; } final sampleId = v.autoCode!; final userId = v.userId!; // 删除人脸 if (v.delFlag == true) { // 先查询数据库是否存在 final detail = await faceDao.findBySampleId(sampleId); if (detail == null) { continue; } if (ObjectUtil.isNotEmpty(detail.path)) { await delFaceByStorage(filePath: detail.path!); } await AppFaceTool.removeFace(sampleId: sampleId); await faceDao.delBySampleId(sampleId); continue; } final faceEntity = await faceDao.findBySampleId(sampleId); if (faceEntity == null) { final add = FaceEntity( userId: userId, sampleId: sampleId, userName: v.username ?? "", deptId: v.deptId, deptName: v.deptName ?? "", imagePath: v.faceImg ?? "", createTime: DateTime.now(), ); final addId = await faceDao.save(add); if (addId != null) { String? fileName = generateFaceFileName( userId: add.userId, sampleId: add.sampleId, username: add.userName, facePath: add.imagePath, ); final filePath = await writeFaceByStorage( storagePath: storagePath, fileName: fileName, facePath: add.imagePath, ); if (ObjectUtil.isNotEmpty(filePath)) { add.id = addId; add.updateTime = DateTime.now(); add.path = filePath; await faceDao.edit(add); } } await AppFaceTool.removeFace(sampleId: add.sampleId); await AppFaceTool.faceRegister( userId: add.userId.toString(), forceRegister: true, ); continue; } final edit = FaceEntity( id: faceEntity.id!, userId: userId, sampleId: sampleId, userName: v.username ?? "", deptId: v.deptId, deptName: v.deptName ?? "", imagePath: v.faceImg ?? "", createTime: DateTime.now(), ); if (edit == faceEntity) { continue; } String? fileName = generateFaceFileName( userId: edit.userId, sampleId: edit.sampleId, username: edit.userName, facePath: edit.imagePath, ); final filePath = await writeFaceByStorage( storagePath: storagePath, facePath: edit.imagePath, fileName: fileName, ); if (ObjectUtil.isNotEmpty(filePath)) { if (ObjectUtil.isNotEmpty(edit.path)) { await delFaceByStorage(filePath: edit.path!); } edit.path = filePath; } await faceDao.edit(edit); // 重新生成人脸模型 await AppFaceTool.removeFace(sampleId: edit.sampleId); await AppFaceTool.faceRegister( userId: edit.userId.toString(), forceRegister: true, ); } if (data.length >= size) return true; } catch (e, stack) { logger.e("|$_tag|同步人脸意外的错误error:|$e|stack:|$stack|"); } return false; } /// 删除人脸图片 static Future delFaceByStorage({ required String filePath, }) async { try { final file = File(filePath); if (await file.exists()) { await file.delete(); } } catch (e) { logger.e("|$_tag|删除人脸图片意外的错误error:|$e|"); } } static String? generateFaceFileName({ required String userId, required String sampleId, required String username, required String facePath, }) { if (ObjectUtil.isEmpty(facePath)) return null; return "${userId}_${sampleId}_$username${AppFileTool.getFileExtension(facePath)}"; } /// 写入人脸头像到本地磁盘 static Future writeFaceByStorage({ required String storagePath, required String? fileName, required String facePath, }) async { if (ObjectUtil.isEmpty(fileName)) return null; final storageDirectory = Directory(storagePath); final file = File('${storageDirectory.path}/$fileName'); if (await file.exists()) { await file.delete(); } final bytesImg = await AppNetworkTool.downloadImage(facePath); if (bytesImg == null) return null; final writeFile = await file.writeAsBytes(bytesImg); if (await writeFile.exists()) { return file.path; } return null; } }