526 lines
19 KiB
C#
526 lines
19 KiB
C#
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<GainfoFaceService> _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<GainfoFaceService> 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<GainfoFacePageSummaryResponse> GetPageAsync(GainfoFacePageRequest request)
|
|
{
|
|
var tenantIds = _currentUser.GetUserClaim("tenants").Split(",").ToList();
|
|
if (tenantIds.IsEmpty())
|
|
{
|
|
return new GainfoFacePageSummaryResponse
|
|
{
|
|
Total = 0,
|
|
Exists = 0,
|
|
Page = new Pagination<GainfoFacePageResponse>
|
|
{
|
|
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<GainfoEntity>().Where(x => factories.Contains(x.Factory)).Select(x => x.Code).ToListAsync();
|
|
var existsFaceCodes = await _faceDbContext.Queryable<GainfoFaceEntity>().Where(x => userCodes.Contains(x.UserCode)).Select(x => x.UserCode).ToListAsync();
|
|
|
|
var list = await _dbContext.Queryable<GainfoEntity>()
|
|
.LeftJoin<DepartmentEntity>((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<GainfoFaceEntity>().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<BasicTypeEntity>().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<double>();
|
|
|
|
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<string> 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<GainfoEntity>().Where(x => x.IdNo == idno).Select(x => x.Code).FirstAsync();
|
|
code ??= idno;
|
|
|
|
var face = await _faceDbContext.Queryable<GainfoFaceEntity>()
|
|
.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<int>();
|
|
|
|
var key = $"face:{idno}";
|
|
var token = timeLimitedDataProtector.Protect(key, TimeSpan.FromSeconds(limit));
|
|
|
|
return faceUrl + token;
|
|
}
|
|
|
|
public async Task<IActionResult> GetFaceAsync(string token)
|
|
{
|
|
var timeLimitedDataProtector = _dataProtectionProvider.CreateProtector("down:face").ToTimeLimitedDataProtector();
|
|
try
|
|
{
|
|
var password = timeLimitedDataProtector.Unprotect(token);
|
|
var userCode = password.Split(':')[1];
|
|
|
|
var face = await _faceDbContext.Queryable<GainfoFaceEntity>().Where(x => x.UserCode == userCode).SingleAsync();
|
|
|
|
if (face == null)
|
|
{
|
|
return new NotFoundResult();
|
|
}
|
|
|
|
return new FileContentResult(Convert.FromBase64String(face.Face), "image/png");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "获取人脸失败:{message}", ex.Message);
|
|
return new NotFoundResult();
|
|
}
|
|
}
|
|
|
|
public async Task<DefaultApiResult<object>> 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<GainfoFaceEntity>().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<BasicTypeEntity>().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<double>();
|
|
|
|
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<GainfoFaceEntity>()
|
|
.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<GainfoUploadFaceRequest> 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<GainfoFaceEntity>().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<string> GetFaceByUserCodeAsync(string userCode)
|
|
{
|
|
var face = await _faceDbContext.Queryable<GainfoFaceEntity>()
|
|
.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<int>();
|
|
|
|
var key = $"face:{userCode}";
|
|
var token = timeLimitedDataProtector.Protect(key, TimeSpan.FromSeconds(limit));
|
|
|
|
return faceUrl + token;
|
|
}
|
|
|
|
public async Task<Dictionary<string, string>> GetFaceByUserCodesAsync(GetFaceByUserCodesRequest request)
|
|
{
|
|
var faceDictionary = new Dictionary<string, string>();
|
|
|
|
foreach (var userCode in request.UserCodes)
|
|
{
|
|
try
|
|
{
|
|
var faceUrl = await GetFaceByUserCodeAsync(userCode);
|
|
faceDictionary.Add(userCode, faceUrl);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
//ignore
|
|
}
|
|
}
|
|
|
|
return faceDictionary;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<Pagination<FaceValidationResponse>> GetUploadPageRecordsAsync(FaceValidationRequest request)
|
|
{
|
|
return await _dbContext.Queryable<HikValidateResultEntity>()
|
|
.LeftJoin<GainfoEntity>((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);
|
|
}
|
|
|
|
//*********************************************************************************************************************************************
|
|
|
|
/// <summary>
|
|
/// 人脸质量检测
|
|
/// </summary>
|
|
/// <param name="base64Image"></param>
|
|
/// <param name="factory"></param>
|
|
/// <exception cref="UserFriendlyException"></exception>
|
|
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<string> 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")}");
|
|
}
|
|
}
|
|
} |