feat: 人证人脸对比
This commit is contained in:
218
Src/Dlhis.Basic/Organization/Services/IAliyunClient.cs
Normal file
218
Src/Dlhis.Basic/Organization/Services/IAliyunClient.cs
Normal file
@@ -0,0 +1,218 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Dlhis.Basic.Organization.Response;
|
||||
using Kurisu.AspNetCore.CustomClass;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dlhis.Basic.Organization.Services;
|
||||
|
||||
public interface IAliyunClient : IScopeDependency
|
||||
{
|
||||
Task<AliyunId3MetaVerifyResponse> Id3MetaVerifyAsync(
|
||||
string identifyNum,
|
||||
string userName,
|
||||
string faceBase64,
|
||||
string crop = "F");
|
||||
}
|
||||
|
||||
public class AliyunClient : IAliyunClient
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly string _accessKeyId;
|
||||
private readonly string _accessKeySecret;
|
||||
|
||||
private const string Endpoint = "cloudauth.aliyuncs.com";
|
||||
private const string ApiVersion = "2019-03-07";
|
||||
|
||||
public AliyunClient(IHttpClientFactory httpClientFactory, IConfiguration configuration, ILogger<AliyunClient> logger)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public AliyunClient(string accessKeyId, string accessKeySecret)
|
||||
{
|
||||
_accessKeyId = accessKeyId ?? "LTAI5tAqyWKcBcSvzpc1ZM72";
|
||||
_accessKeySecret = accessKeySecret ?? "YZMSqsmMGlGZuHw5WU2tqt0mmIBX6h";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 Base64 图片进行三要素核验
|
||||
/// </summary>
|
||||
public async Task<AliyunId3MetaVerifyResponse> Id3MetaVerifyAsync(
|
||||
string identifyNum,
|
||||
string userName,
|
||||
string faceBase64,
|
||||
string crop = "F")
|
||||
{
|
||||
var accessKeyId = _accessKeyId ?? GetRequiredConfig("AliyunOptions:AccessKeyId", "Aliyun:AccessKeyId");
|
||||
var accessKeySecret = _accessKeySecret ?? GetRequiredConfig("AliyunOptions:AccessKeySecret", "Aliyun:AccessKeySecret");
|
||||
|
||||
if (faceBase64.Contains(","))
|
||||
{
|
||||
faceBase64 = faceBase64[(faceBase64.IndexOf(',') + 1)..];
|
||||
}
|
||||
|
||||
var parameters = new SortedDictionary<string, string>
|
||||
{
|
||||
{ "Action", "Id3MetaVerify" },
|
||||
{ "Format", "JSON" },
|
||||
{ "Version", ApiVersion },
|
||||
{ "AccessKeyId", accessKeyId },
|
||||
{ "SignatureMethod", "HMAC-SHA1" },
|
||||
{ "SignatureVersion", "1.0" },
|
||||
{ "SignatureNonce", Guid.NewGuid().ToString("N") },
|
||||
{ "Timestamp", DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ") },
|
||||
{ "ParamType", "normal" },
|
||||
{ "IdentifyNum", identifyNum },
|
||||
{ "UserName", userName },
|
||||
{ "FacePicture", faceBase64 },
|
||||
{ "Crop", crop }
|
||||
};
|
||||
|
||||
var signature = ComputeSignature(parameters, "POST", accessKeySecret);
|
||||
var url = $"https://{Endpoint}/?Signature={PercentEncode(signature)}";
|
||||
var bodyString = BuildBodyString(parameters);
|
||||
|
||||
using var fallbackClient = _httpClientFactory == null ? new HttpClient() : null;
|
||||
var client = _httpClientFactory?.CreateClient() ?? fallbackClient;
|
||||
client.DefaultRequestHeaders.Add("Host", Endpoint);
|
||||
var content = new StringContent(bodyString, Encoding.UTF8, "application/x-www-form-urlencoded");
|
||||
var response = await client.PostAsync(url, content);
|
||||
var rawResponse = await response.Content.ReadAsStringAsync();
|
||||
|
||||
return ParseResponse(rawResponse);
|
||||
}
|
||||
|
||||
private string GetRequiredConfig(params string[] keys)
|
||||
{
|
||||
foreach (var key in keys)
|
||||
{
|
||||
var value = _configuration.GetSection(key).Value;
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
throw new UserFriendlyException($"阿里云配置缺失:{string.Join("或", keys)}");
|
||||
}
|
||||
|
||||
private static AliyunId3MetaVerifyResponse ParseResponse(string rawResponse)
|
||||
{
|
||||
var result = new AliyunId3MetaVerifyResponse
|
||||
{
|
||||
RawResponse = rawResponse
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rawResponse))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = JsonConvert.DeserializeObject<AliyunId3MetaVerifyApiResponse>(rawResponse);
|
||||
result.RequestId = response?.RequestId;
|
||||
result.Code = response?.Code;
|
||||
result.Message = response?.Message;
|
||||
result.BizCode = response?.ResultObject?.BizCode;
|
||||
result.Similarity = ParseVerifyScore(response?.ResultObject?.FaceDetail);
|
||||
result.Passed = response?.ResultObject?.BizCode switch
|
||||
{
|
||||
"1" => true,
|
||||
"2" or "3" => false,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
result.Message = "阿里云响应不是合法JSON";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static double? ParseVerifyScore(string faceDetail)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(faceDetail))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var detail = JsonConvert.DeserializeObject<AliyunId3MetaVerifyFaceDetail>(faceDetail);
|
||||
return detail?.VerifyScore;
|
||||
}
|
||||
|
||||
private static string ComputeSignature(SortedDictionary<string, string> parameters, string httpMethod, string accessKeySecret)
|
||||
{
|
||||
var canonicalized = new StringBuilder();
|
||||
foreach (var kv in parameters)
|
||||
{
|
||||
if (kv.Key == "Signature")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (canonicalized.Length > 0)
|
||||
{
|
||||
canonicalized.Append('&');
|
||||
}
|
||||
|
||||
canonicalized.Append(PercentEncode(kv.Key))
|
||||
.Append('=')
|
||||
.Append(PercentEncode(kv.Value));
|
||||
}
|
||||
|
||||
var stringToSign = httpMethod + "&" + PercentEncode("/") + "&" + PercentEncode(canonicalized.ToString());
|
||||
var signKey = accessKeySecret + "&";
|
||||
using var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(signKey));
|
||||
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));
|
||||
return Convert.ToBase64String(hash);
|
||||
}
|
||||
|
||||
private static string BuildBodyString(SortedDictionary<string, string> parameters)
|
||||
{
|
||||
return string.Join("&", parameters
|
||||
.Where(x => x.Key != "Signature")
|
||||
.Select(x => PercentEncode(x.Key) + "=" + PercentEncode(x.Value)));
|
||||
}
|
||||
|
||||
private static string PercentEncode(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return Uri.EscapeDataString(value)
|
||||
.Replace("+", "%20")
|
||||
.Replace("*", "%2A")
|
||||
.Replace("%7E", "~");
|
||||
}
|
||||
|
||||
private class AliyunId3MetaVerifyApiResponse
|
||||
{
|
||||
public string RequestId { get; set; }
|
||||
|
||||
public string Message { get; set; }
|
||||
|
||||
public string Code { get; set; }
|
||||
|
||||
public AliyunId3MetaVerifyResultObject ResultObject { get; set; }
|
||||
}
|
||||
|
||||
private class AliyunId3MetaVerifyResultObject
|
||||
{
|
||||
public string BizCode { get; set; }
|
||||
|
||||
public string FaceDetail { get; set; }
|
||||
}
|
||||
|
||||
private class AliyunId3MetaVerifyFaceDetail
|
||||
{
|
||||
[JsonProperty("verifyScore")]
|
||||
public double? VerifyScore { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user