import 'package:dio/dio.dart' as dio; import 'package:pj_supermarket_cloud/common/index.dart'; abstract class UserApi { /// 硬件管理登录 static Future login(dynamic pa) async { var password = pa["password"]; var pwdForWire = LoginVersion.v1 == Constants.loginVersion ? password : EncryptionTool.encryptLoginPassword(password); final res = await HttpManage().dio.post( "/auth/oauth2/token", queryParameters: { "username": pa["username"], "randomStr": "", "code": "", "grant_type": pa["grant_type"], "scope": pa["scope"], }, data: { "password": pwdForWire, }, options: dio.Options( headers: { "Authorization": EncryptionTool.getLoginAuthorization(), "skipToken": true, "content-type": "application/x-www-form-urlencoded", }, ), ); return res.data; } /// 刷新token static Future> refreshToken(dynamic pa) async { final res = await HttpManage().dio.post( "/refresh-token", queryParameters: pa, ); if (res.data?.data == null) { return res.data; } final map = res.data!.data as Map; final typeData = ServiceAuth.fromJson(map); res.data?.data = typeData; return res.data; } /// 获取人脸v2 static Future?> getFaceListV2( dynamic formData, ) async { final res = await HttpManage().dio.get( "/admin/user-face/ad/v2/find-face", queryParameters: formData, options: dio.Options(extra: { HttpManage.rejectBusinessErrorKey: true, HttpManage.responseSummaryKey: true, }), ); if (res.data == null) return null; return res.data ?.map((e) => ServiceAsyncFace.fromJson(e)) .toList(); } /// 按固定主键上界游标读取本部门及子部门的 V3 人脸权威快照。 static Future getFaceSyncSnapshot( Map requestBody, ) async { final res = await HttpManage().dio.post( "/admin/user-face/ad/v3/sync-snapshot", data: requestBody, options: dio.Options(extra: { HttpManage.rejectBusinessErrorKey: true, HttpManage.responseSummaryKey: true, }), ); if (res.data is! Map) { throw const FormatException('服务端未返回有效的人脸快照'); } return ServiceFaceSyncSnapshot.fromJson( Map.from(res.data as Map), ); } /// 使用服务端真实部门树确定删除范围,包含当前已无人脸的空部门。 static Future> getFaceDepartmentScope( String merchantDeptId) async { final res = await HttpManage().dio.get( '/admin/dept/getDescendantList/$merchantDeptId', options: dio.Options(extra: { HttpManage.rejectBusinessErrorKey: true, HttpManage.responseSummaryKey: true }), ); if (res.data is! List) throw const FaceSyncSnapshotException('部门范围响应无效'); final ids = {}; for (final item in res.data as List) { if (item is! Map || item['deptId'] == null) { throw const FaceSyncSnapshotException('部门范围包含无效记录'); } final id = item['deptId'].toString(); if (int.tryParse(id) == null || !ids.add(id)) { throw const FaceSyncSnapshotException('部门范围包含无效或重复编号'); } } if (!ids.contains(merchantDeptId)) { throw const FaceSyncSnapshotException('绑定部门不存在,已停止人脸同步'); } return ids; } /// 由当前设备请求删除服务端部门范围内的人脸,单批最多 500 条。 /// /// 服务端会以 DEVICE-ID 解析真实绑定部门;业务错误必须抛给调用方,确保终端 /// 在未得到 deleted 或 alreadyAbsent 确认前不清理本地数据。 static Future deleteTerminalFaces( List sampleIds, ) async { final setting = await AppSettingTool.getSysSetting(); final hardId = setting.serviceId; if (hardId == null) throw StateError('终端尚未绑定设备'); final res = await HttpManage().dio.post( '/hard/hard-api/ad/face/delete', data: {'sampleIds': sampleIds}, options: dio.Options( headers: {'DEVICE-ID': hardId}, extra: {HttpManage.rejectBusinessErrorKey: true}, ), ); if (res.data is! Map) { throw const FormatException('服务端未返回有效的人脸删除结果'); } return ServiceTerminalFaceDeleteResult.fromJson( Map.from(res.data as Map), ); } /// 由当前设备批量上报在库人脸静态图片质量,单批最多 500 条。 /// /// 服务端通过 DEVICE-ID 解析可信设备和部门;业务失败必须抛出,但调用方应将 /// 质量上报与既有人脸同步、注册和识别结果隔离,网络失败不得回滚本地质量。 static Future reportTerminalFaceQuality( List items, ) async { if (items.isEmpty) throw ArgumentError.value(items, 'items', '质量结果不能为空'); if (items.length > 500) { throw ArgumentError.value(items.length, 'items', '单批最多上报500条质量结果'); } final setting = await AppSettingTool.getSysSetting(); final hardId = setting.serviceId; if (hardId == null) throw StateError('终端尚未绑定设备'); final res = await HttpManage().dio.post( '/hard/hard-api/ad/face/quality/report', data: { 'items': items.map((item) => item.toJson()).toList(growable: false), }, options: dio.Options( headers: {'DEVICE-ID': hardId}, extra: {HttpManage.rejectBusinessErrorKey: true}, ), ); if (res.data is! Map) { throw const FormatException('服务端未返回有效的人脸质量上报结果'); } return ServiceFaceQualityReportResult.fromJson( Map.from(res.data as Map), ); } /// 竞争当前商户部门的人脸质量上报租约。 /// /// Hardware 仍会在实际上报时二次验证租约;未取得租约不是业务失败,调用方应 /// 直接跳过本轮质量批次,避免同一单位的多台终端重复发送相同在库结果。 static Future acquireTerminalFaceQualityLease() async { final setting = await AppSettingTool.getSysSetting(); final hardId = setting.serviceId; if (hardId == null) throw StateError('终端尚未绑定设备'); final res = await HttpManage().dio.post( '/hard/hard-api/ad/face/quality/lease', options: dio.Options( headers: {'DEVICE-ID': hardId}, extra: {HttpManage.rejectBusinessErrorKey: true}, ), ); if (res.data is! Map) { throw const FormatException('服务端未返回有效的人脸质量租约'); } return ServiceFaceQualityLease.fromJson( Map.from(res.data as Map), ); } /// 获取租户列表 static Future?> getTenantList() async { final res = await HttpManage().dio.get("/admin/tenant/list"); if (res.data == null) return null; return res.data ?.map((e) => ServiceTenant.fromJson(e)) .toList(); } }