using Dlhis.Basic.Device.Services; using Dlhis.Basic.Organization.Api; using Dlhis.Basic.Organization.Api.Request; using Dlhis.Basic.Organization.Request; using Dlhis.Basic.Organization.Response; using Dlhis.Entity.Basic.Entity; using Dlhis.Entity.Basic.Enums; using Dlhis.Entity.Device.Enums; using Dlhis.InfoCheck.Api; using Dlhis.InfoCheck.Api.Requests; using Dlhis.Jip.Api; using Dlhis.Jip.Api.Requests; using Dlhis.Sys.Api; using Kurisu.AspNetCore.Authentication.Abstractions; using Kurisu.AspNetCore.CustomClass; using Kurisu.AspNetCore.DataAccess.Dto; using Kurisu.AspNetCore.DataAccess.Extensions; using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; using Kurisu.AspNetCore.UnifyResultAndValidation; using Kurisu.AspNetCore.Utils.Extensions; using Mapster; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Mvc; using SqlSugar; namespace Dlhis.Basic.Organization.Services.Implements; public class GainfoFaceService : IGainfoFaceService { private readonly IGainfoStore _gainfoStore; private readonly IDbContext _dbContext; private readonly IDbContext _faceDbContext; private readonly ILogger _logger; private readonly IConfiguration _configuration; private readonly ICurrentUser _currentUser; private readonly IDevicePermissionService _devicePermissionService; private readonly IFactorySettingService _factorySettingService; private readonly IHainingApi _hainingApi; private readonly IInfoCheckApi _infoCheckApi; private readonly IJinkoJipApi _jinkoJipApi; private readonly ITenantApi _tenantApi; private readonly IHikService _hikService; private readonly IDataProtectionProvider _dataProtectionProvider; public GainfoFaceService(IGainfoStore gainfoStore, IDbContext dbContext, ILogger logger, IConfiguration configuration, ICurrentUser currentUser, IDevicePermissionService devicePermissionService, IFactorySettingService factorySettingService, IHainingApi hainingApi, IInfoCheckApi infoCheckApi, IJinkoJipApi jinkoJipApi, ITenantApi tenantApi, IHikService hikService, IDataProtectionProvider dataProtectionProvider) { _logger = logger; _gainfoStore = gainfoStore; _dbContext = dbContext; _faceDbContext = dbContext.ChangeDb("face"); _configuration = configuration; _currentUser = currentUser; _devicePermissionService = devicePermissionService; _factorySettingService = factorySettingService; _hainingApi = hainingApi; _infoCheckApi = infoCheckApi; _jinkoJipApi = jinkoJipApi; _tenantApi = tenantApi; _hikService = hikService; _dataProtectionProvider = dataProtectionProvider; } public async Task GetPageAsync(GainfoFacePageRequest request) { var tenantIds = _currentUser.GetUserClaim("tenants").Split(",").ToList(); if (tenantIds.IsEmpty()) { return new GainfoFacePageSummaryResponse { Total = 0, Exists = 0, Page = new Pagination { PageIndex = request.PageIndex, PageSize = request.PageSize, Total = 0, Data = null } }; } var factories = request.Factories; factories = factories.Where(x => tenantIds.Contains(x)).ToList(); var userCodes = await _dbContext.Queryable().Where(x => factories.Contains(x.Factory)).Select(x => x.Code).ToListAsync(); var existsFaceCodes = await _faceDbContext.Queryable().Where(x => userCodes.Contains(x.UserCode)).Select(x => x.UserCode).ToListAsync(); var list = await _dbContext.Queryable() .LeftJoin((x, d) => x.DepartmentNo == d.No) .Where(x => factories.Contains(x.Factory)) .WhereIF(request.Exists.HasValue && request.Exists.Value, x => existsFaceCodes.Contains(x.Code)) .WhereIF(request.Exists.HasValue && !request.Exists.Value, x => !existsFaceCodes.Contains(x.Code)) .WhereIF(request.Search.IsPresent(), x => x.Code == request.Search) .OrderBy(x => x.CreateTime, OrderByType.Desc) .Select((x, d) => new GainfoFacePageResponse { UserCode = x.Code, UserName = x.Name, DepartmentName = d.Name, Exists = false, Factory = x.Factory }) .ToListAsync(); var page = list.ToPage(request); page.Data.ForEach(x => { x.Exists = existsFaceCodes.Contains(x.UserCode); }); try { var tenants = await _tenantApi.GetTenantsInternalAsync(); page.Data.ForEach(x => { x.FactoryName = tenants.FirstOrDefault(p => p.Code == x.Factory)?.Name; }); } catch (Exception e) { _logger.LogError(e, "处理tenant错误:{msg}", e.Message); } return new GainfoFacePageSummaryResponse { Total = userCodes.Count, Exists = existsFaceCodes.Count, Page = page }; } public async Task UploadExternalFaceAsync(GainfoUploadFaceRequest request) { var protector = _dataProtectionProvider.CreateProtector("gainfo:external:idno"); try { request.UserNo = protector.Unprotect(request.UserNo); } catch (Exception) { //ignore } var gainfo = await _gainfoStore.Queryable(GainfoStatusEnumType.Working) .Where(x => x.IdNo == request.UserNo) .SingleAsync(); gainfo.ThrowIfNull("人员信息不存在"); var face = await _faceDbContext.Queryable().Where(x => x.UserCode == gainfo.Code).SingleAsync(); var taskType = face == null ? DeviceTaskEnumType.Add : DeviceTaskEnumType.Update; if (face == null) { face = new GainfoFaceEntity { UserCode = gainfo.Code, Face = request.Face, CreatedBy = 1, CreateTime = DateTime.Now, ModifiedBy = 1, ModifiedTime = DateTime.Now, }; await _faceDbContext.InsertAsync(face); } else { if (gainfo.Type == GainfoUserTypeEnumType.Outsource) { var faceSimilaritySetting = await _dbContext.Queryable().Where(x => x.Type == "faceSimilarity").SingleAsync(); if (faceSimilaritySetting != null) { var faceSimilarity = await _hikService.GetSimilarityAsync(new GainfoFaceValidateRequest { SrcFacePicBinaryData = face.Face, DistFacePicBinaryData = request.Face }); var acceptSimilarity = faceSimilaritySetting.Code.Adapt(); var isError = faceSimilarity < acceptSimilarity; await _dbContext.InsertAsync(new HikValidateResultEntity { UserCode = face.UserCode, Similarity = faceSimilarity, AcceptSimilarity = acceptSimilarity, CurrentFace = face.Face, UploadFace = request.Face, TenantId = gainfo.Factory, }); if (isError) { throw new UserFriendlyException($"系统检测非本人更新,请重试(相似度{faceSimilarity})"); } } } face.Face = request.Face; face.ModifiedBy = 1; face.ModifiedTime = DateTime.Now; await _faceDbContext.UpdateAsync(face); } await _devicePermissionService.GenerateDeviceTasksAsync(gainfo.No, taskType, "照片上传生成"); } public async Task GetExternalFaceAsync(string idno) { var protector = _dataProtectionProvider.CreateProtector("gainfo:external:idno"); try { idno = protector.Unprotect(idno); } catch (Exception) { throw new UserFriendlyException("凭证有误,无法获取人脸数"); } var code = await _dbContext.Queryable().Where(x => x.IdNo == idno).Select(x => x.Code).FirstAsync(); code ??= idno; var face = await _faceDbContext.Queryable() .Where(x => x.UserCode == code) .SingleAsync(); face.ThrowIfNull("人脸照片尚未录入"); var timeLimitedDataProtector = _dataProtectionProvider.CreateProtector("down:face").ToTimeLimitedDataProtector(); var faceUrl = _configuration.GetSection("FaceUrlOptions:Url").Value; var limit = _configuration.GetSection("FaceUrlOptions:Limit").Get(); var key = $"face:{idno}"; var token = timeLimitedDataProtector.Protect(key, TimeSpan.FromSeconds(limit)); return faceUrl + token; } public async Task GetFaceAsync(string token) { var timeLimitedDataProtector = _dataProtectionProvider.CreateProtector("down:face").ToTimeLimitedDataProtector(); try { //解密token,如果非法和过期会抛异常 var password = timeLimitedDataProtector.Unprotect(token); var userCode = password.Split(':')[1]; var face = await _faceDbContext.Queryable().Where(x => x.UserCode == userCode).SingleAsync(); if (face == null) { return new NotFoundResult(); } return new FileContentResult(Convert.FromBase64String(face.Face), "image/png"); } catch { //已经验证没有问题,后续不需要输出多余日志 //_logger.LogError(ex, "获取人脸失败:{message}", ex.Message); return new NotFoundResult(); } } public async Task> UploadFaceAsync(GainfoUploadFaceRequest request) { var gainfo = await _gainfoStore.Queryable().Where(x => x.No == request.UserNo || x.Code == request.UserNo).SingleAsync(); gainfo.ThrowIfNull("人员档案信息不存在"); var face = await _faceDbContext.Queryable().Where(x => x.UserCode == gainfo.Code).SingleAsync(); if (face != null) { ((DateTime.Now - face.ModifiedTime).TotalSeconds > 300).ThrowIfFalse("请勿频繁更新照片,请稍后重试"); } var userId = _currentUser.GetIntSubjectId(); await DetectFaceImageQualityAsync(request.Face, gainfo.Factory); var taskType = face == null ? DeviceTaskEnumType.Add : DeviceTaskEnumType.Update; if (face == null) { face = new GainfoFaceEntity { UserCode = gainfo.Code, Face = request.Face, CreateTime = DateTime.Now, ModifiedTime = DateTime.Now, CreatedBy = userId, ModifiedBy = userId }; await _faceDbContext.InsertAsync(face); } else { if (face.Face == request.Face) { _logger.LogWarning("相同的人脸照片更新忽略"); return DefaultApiResult.Success("操作成功"); } var faceSimilaritySetting = await _dbContext.Queryable().Where(x => x.Type == "faceSimilarity").SingleAsync(); if (faceSimilaritySetting != null) { var faceSimilarity = await _hikService.GetSimilarityAsync(new GainfoFaceValidateRequest { SrcFacePicBinaryData = face.Face, DistFacePicBinaryData = request.Face }); var acceptSimilarity = faceSimilaritySetting.Code.Adapt(); var isError = faceSimilarity < acceptSimilarity; await _dbContext.InsertAsync(new HikValidateResultEntity { UserCode = face.UserCode, Similarity = faceSimilarity, AcceptSimilarity = acceptSimilarity, CurrentFace = face.Face, UploadFace = request.Face, TenantId = gainfo.Factory, }); if (isError) { return DefaultApiResult.Error($"系统检测非本人更新,请重试(相似度{faceSimilarity})"); } } face.Face = request.Face; face.ModifiedTime = DateTime.Now; face.ModifiedBy = userId; await _faceDbContext.UpdateAsync(face); } try { var r = await _jinkoJipApi.SyncFaceToSapAsync(new SyncFaceRequest { UserCode = face.UserCode, Base64 = face.Face }); if (r.OLX == "S") { await _faceDbContext.Updateable() .SetColumns(x => x.IsSync == true) .Where(x => x.UserCode == face.UserCode) .ExecuteCommandAsync(); } } catch (Exception e) { _logger.LogError(e, "上传face数据到sap失败:{message}", e.Message); } try { await _hainingApi.UploadAsync(new HainingApiUploadRequest() { Code = gainfo.Code, Base64 = request.Face }); } catch (Exception e) { _logger.LogError(e, "上传face数据到老系统失败:{message}", e.Message); } //生成任务 await _devicePermissionService.GenerateDeviceTasksAsync(gainfo.No, taskType, "照片上传生成"); return DefaultApiResult.Success("操作成功"); } public async Task UploadFacesAsync(List requests) { foreach (var request in requests) { var gainfo = await _gainfoStore.Queryable(GainfoStatusEnumType.Working).Where(x => x.Code == request.UserNo).FirstAsync(); gainfo.ThrowIfNull("人员档案信息不存在"); var face = await _faceDbContext.Queryable().Where(x => x.UserCode == gainfo.Code).SingleAsync(); var taskType = face == null ? DeviceTaskEnumType.Add : DeviceTaskEnumType.Update; if (face == null) { face = new GainfoFaceEntity { UserCode = gainfo.Code, Face = request.Face, CreatedBy = _currentUser.GetIntSubjectId(), CreateTime = DateTime.Now, ModifiedBy = _currentUser.GetIntSubjectId(), ModifiedTime = DateTime.Now }; await _faceDbContext.InsertAsync(face); } else { if (face.Face == request.Face) { _logger.LogWarning("相同的人脸照片更新忽略"); return; } face.ModifiedBy = _currentUser.GetIntSubjectId(); face.ModifiedTime = DateTime.Now; face.Face = request.Face; await _faceDbContext.UpdateAsync(face); } //生成任务 await _devicePermissionService.GenerateDeviceTasksAsync(gainfo.No, taskType, "照片上传生成"); } } public async Task GetFaceByUserCodeAsync(string userCode) { var face = await _faceDbContext.Queryable() .Where(x => x.UserCode == userCode) .SingleAsync(); face.ThrowIfNull("人脸照片尚未录入"); var timeLimitedDataProtector = _dataProtectionProvider.CreateProtector("down:face").ToTimeLimitedDataProtector(); var faceUrl = _configuration.GetSection("FaceUrlOptions:Url").Value; var limit = _configuration.GetSection("FaceUrlOptions:Limit").Get(); var key = $"face:{userCode}"; var token = timeLimitedDataProtector.Protect(key, TimeSpan.FromSeconds(limit)); return faceUrl + token; } public async Task> GetFaceByUserCodesAsync(GetFaceByUserCodesRequest request) { var faceDictionary = new Dictionary(); foreach (var userCode in request.UserCodes) { try { var faceUrl = await GetFaceByUserCodeAsync(userCode); faceDictionary.Add(userCode, faceUrl); } catch (Exception) { //ignore } } return faceDictionary; } /// public async Task> GetUploadPageRecordsAsync(FaceValidationRequest request) { return await _dbContext.Queryable() .LeftJoin((x, ga) => x.UserCode == ga.Code) .WhereIF(request.IsSuccess.HasValue && request.IsSuccess.Value, x => x.AcceptSimilarity >= x.Similarity) .WhereIF(request.IsSuccess is false, x => x.AcceptSimilarity < x.Similarity) .WhereIF(request.Search.IsPresent(), (x, ga) => x.UserCode == request.Search || ga.Name.Contains(request.Search)) .Select((x, ga) => new FaceValidationResponse { UserCode = x.UserCode, UserName = ga.Name, Similarity = x.Similarity, AcceptSimilarity = x.AcceptSimilarity, CurrentFace = x.CurrentFace, UploadFace = x.UploadFace, CreateTime = x.CreateTime }) .ToPageListAsync(request); } //********************************************************************************************************************************************* /// /// 人脸质量检测 /// /// /// /// private async Task DetectFaceImageQualityAsync(string base64Image, string factory = null) { var currentFactory = factory ?? _currentUser.GetStringTenantId(); var factorySetting = await _factorySettingService.GetFactorySettingAsync(currentFactory, false); if (factorySetting.FaceImageDetection.Enable) { List r; try { var result = await _infoCheckApi.FaceDetectAsync(new FaceDetectRequest { Base64 = base64Image, CheckList = factorySetting.FaceImageDetection.Detections.Select(x => new FaceDetectCheckRequest { Type = (int)x.Type, Level = (int)x.Level }).ToList() }); r = result; } catch (Exception ex) { _logger.LogError(ex, "人脸质量检测失败:{message}", ex.Message); return; } r.IsPresent().ThrowIfTrue($"{r.Join("\r\n")}"); } } }