using Contract.Application.Utility.Cache;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using Newtonsoft.Json;
using System.Web;
namespace Test
{
public class TencentH5FaceIDHelper
{
private HttpClient _client { get; }
#region 配置
private string _tencentAppId = "test";
private string _tencentSecret = "test";
private string _tencentCallBackUrl = "http://localhost:8081/";
#endregion
private const string _redisKey= "TencentFaceId";
public TencentH5FaceIDHelper(HttpClient client)
{
_client = client;
}
/// <summary>
/// 获取人脸识别各个Field ,这个方法是获取缓存中的数据,可以参考,也可以丢弃,具体还是会调用到腾讯接口
/// </summary>
/// <returns></returns>
private string GetFieldByCache(TencentFaceIDCache.TencentFaceIDField tencentFaceIDField) {
string value = TencentFaceIDCache.Get(tencentFaceIDField);
if (string.IsNullOrEmpty(value)) {
long seconds = 0;
string expire_time = "";
TimeSpan timeSpan;
switch (tencentFaceIDField)
{
case TencentFaceIDCache.TencentFaceIDField.AccessToken:
DtoViewAccessToken accessTokenObj = GetAccessTokenAsync().Result;
expire_time = accessTokenObj.expire_time.Insert(12, ":").Insert(10, ":").Insert(8, " ").Insert(6, "-").Insert(4, "-");
timeSpan = DateTime.Parse(expire_time).Subtract(DateTime.Now);
value = accessTokenObj.access_token;
seconds = (long)timeSpan.TotalSeconds - 10;
break;
case TencentFaceIDCache.TencentFaceIDField.SingTicket:
DtoViewSignTicket signTicketObj = GetSignTicketAsync().Result;
expire_time = signTicketObj.tickets[0].expire_time.Insert(12, ":").Insert(10, ":").Insert(8, " ").Insert(6, "-").Insert(4, "-");
timeSpan = DateTime.Parse(expire_time).Subtract(DateTime.Now);
value = signTicketObj.tickets[0].value;
seconds = (long)timeSpan.TotalSeconds - 10;
break;
default:
break;
}
TencentFaceIDCache.Set(tencentFaceIDField, value, seconds);
}
return value;
}
/// <summary>
/// 获取AccessToken
/// </summary>
/// <returns></returns>
private async Task<DtoViewAccessToken> GetAccessTokenAsync()
{
string url = "https://miniprogram-kyc.tencentcloudapi.com/api/oauth2/access_token?app_id={0}&secret={1}&grant_type={2}&version={3}";
url = string.Format(url, _tencentAppId, _tencentSecret, "client_credential", "1.0.0");
var response = await _client.GetAsync(url);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
DtoViewAccessToken result = Newtonsoft.Json.JsonConvert.DeserializeObject<DtoViewAccessToken>(content);
if (result.code == "0")
{
return result;
}
else {
LogHelper.Error("获取人脸识别AccessToken失败:"+ content);
return null;
}
}
/// <summary>
/// 获取SignTicket
/// </summary>
/// <returns></returns>
private async Task<DtoViewSignTicket> GetSignTicketAsync() {
string url = "https://miniprogram-kyc.tencentcloudapi.com/api/oauth2/api_ticket?app_id={0}&access_token={1}&type={2}&version={3}";
url = string.Format(url, _tencentAppId, GetFieldByCache(TencentFaceIDCache.TencentFaceIDField.AccessToken), "SIGN", "1.0.0");
var response = await _client.GetAsync(url);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
DtoViewSignTicket result = Newtonsoft.Json.JsonConvert.DeserializeObject<DtoViewSignTicket>(content);
if (result.code == "0")
{
return result;
}
else
{
LogHelper.Error("获取人脸识别SignTicket失败:" + content);
return null;
}
}
/// <summary>
/// 获取NonceTicket
/// </summary>
/// <returns></returns>
private async Task<DtoViewNonceTicket> GetNonceTicketAsync(int userId) {
string url = "https://miniprogram-kyc.tencentcloudapi.com/api/oauth2/api_ticket?app_id={0}&access_token={1}&type={2}&version={3}&user_id={4}";
url = string.Format(url, _tencentAppId, GetFieldByCache(TencentFaceIDCache.TencentFaceIDField.AccessToken), "NONCE", "1.0.0", userId);
var response = await _client.GetAsync(url);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
DtoViewNonceTicket result = Newtonsoft.Json.JsonConvert.DeserializeObject<DtoViewNonceTicket>(content);
if (result.code == "0")
{
return result;
}
else
{
LogHelper.Error("获取人脸识别NonceTicket失败:" + content);
throw new Ex.FailException("获取必要参数失败,"+result.msg);
}
}
/// <summary>
/// 获取SingTicket签名
/// </summary>
/// <param name="orderNo"></param>
/// <param name="name"></param>
/// <param name="idNo"></param>
/// <param name="userId"></param>
/// <returns></returns>
private string GetSignBySingTicket(string orderNo,string name,string idNo,int userId) {
List<string> lstParam = new List<string>();
lstParam.Add(_tencentAppId);
lstParam.Add(orderNo);
lstParam.Add(userId.ToString());
lstParam.Add(name);
lstParam.Add(idNo);
lstParam.Add("1.0.0");
lstParam.Add(GetFieldByCache(TencentFaceIDCache.TencentFaceIDField.SingTicket));
String[] arrayParam = lstParam.ToArray();
Array.Sort(arrayParam, String.CompareOrdinal);
string str = "";
for (int i = 0; i < arrayParam.Length; i++)
{
str += arrayParam[i];
}
string sha1 = ComputeStringSHA1(str).ToUpper();
return sha1;
}
/// <summary>
/// 获取NonceTicket签名
/// </summary>
/// <param name="orderNo"></param>
/// <param name="name"></param>
/// <param name="idNo"></param>
/// <param name="userId"></param>
/// <returns></returns>
private string GetSignByNonceTicket(string orderNo, string h5faceId, int userId,out string nonce)
{
List<string> lstParam = new List<string>();
lstParam.Add(_tencentAppId);
lstParam.Add(orderNo);
lstParam.Add(userId.ToString());
lstParam.Add(h5faceId);
lstParam.Add("1.0.0");
nonce = CreateRandomStr(32);
lstParam.Add(nonce);
lstParam.Add(GetNonceTicketAsync(userId).Result.tickets[0].value);
String[] arrayParam = lstParam.ToArray();
Array.Sort(arrayParam, String.CompareOrdinal);
string str = "";
for (int i = 0; i < arrayParam.Length; i++)
{
str += arrayParam[i];
}
string sha1 = ComputeStringSHA1(str).ToUpper();
return sha1;
}
/// <summary>
/// 获取NonceSingTicket签名
/// </summary>
/// <param name="orderNo"></param>
/// <param name="name"></param>
/// <param name="idNo"></param>
/// <param name="userId"></param>
/// <returns></returns>
private string GetSignByNonceSignTicket(string orderNo,out string nonce)
{
List<string> lstParam = new List<string>();
lstParam.Add(_tencentAppId);
lstParam.Add(orderNo);
lstParam.Add("1.0.0");
nonce = CreateRandomStr(32);
lstParam.Add(nonce);
lstParam.Add(GetFieldByCache(TencentFaceIDCache.TencentFaceIDField.SingTicket));
String[] arrayParam = lstParam.ToArray();
Array.Sort(arrayParam, String.CompareOrdinal);
string str = "";
for (int i = 0; i < arrayParam.Length; i++)
{
str += arrayParam[i];
}
string sha1 = HashHelper.ComputeStringSHA1(str).ToUpper();
return sha1;
}
/// <summary>
/// 上报人脸识别请求
/// </summary>
/// <param name="orderNo"></param>
/// <param name="name"></param>
/// <param name="idNo"></param>
/// <param name="userId"></param>
/// <returns></returns>
private async Task<DtoViewH5FaceId> GetFaceIdAsync(string orderNo, string name, string idNo, int userId) {
DtoInputH5FaceId dtoInput = new DtoInputH5FaceId();
dtoInput.webankAppId = _tencentAppId;
dtoInput.orderNo = orderNo;
dtoInput.name = name;
dtoInput.idNo = idNo;
dtoInput.userId = userId.ToString();
dtoInput.sourcePhotoStr = "";
dtoInput.sourcePhotoType = "";
dtoInput.version = "1.0.0";
dtoInput.sign = GetSignBySingTicket(orderNo, name, idNo, userId);
string url = "https://miniprogram-kyc.tencentcloudapi.com/api/server/h5/geth5faceid?orderNo=" + orderNo;
var response = await Post(dtoInput.ToJson(), url);
DtoViewH5FaceId result = Newtonsoft.Json.JsonConvert.DeserializeObject<DtoViewH5FaceId>(response);
if (result.code == "0")
{
return result;
}
else
{
LogHelper.Error("上报人脸识别请求失败:" + response);
throw new Ex.FailException("上报人脸识别请求失败,"+ result.msg);
}
}
/// <summary>
/// 获取人脸识别开启地址(主要方法,调用腾讯接口获取人脸识别地址,有业务代码,当每天同个人超过3次则退出)
/// </summary>
/// <param name="orderNo">订单号</param>
/// <param name="name">用户名</param>
/// <param name="idNo">身份证号</param>
/// <param name="userId">用户Id</param>
/// <returns></returns>
public string GetFaceIdUrl(string orderNo, string name, string idNo, int userId) {
VerificationUser user = new VerificationUser();
try
{
user = Redis.RedisClient.HGet<VerificationUser>(_redisKey, userId.ToString());
}
catch {
user = null;
}
string date = DateTime.Now.ToString("yyyy-MM-dd");
if (user != null) {
var checkOrder = user.OrderList.Find(a => a.Date == date);
if (checkOrder.OrderDetailList.Count >= 3) {
throw new ZhuoShang.Infrastructure.Core.ExceptionExtend.FailException("今日验证次数以达到上限,请明天重试");
}
}
DtoViewH5FaceId faceIdModel = GetFaceIdAsync(orderNo, name, idNo, userId).Result;
string sign = GetSignByNonceTicket(orderNo, faceIdModel.result.h5faceId, userId, out string nonce);
string enCallbackUrl = HttpUtility.UrlEncode(_tencentCallBackUrl);
string url = @$"https://{faceIdModel.result.optimalDomain}/api/web/login?webankAppId={_tencentAppId}&version=1.0.0&nonce={nonce}&orderNo={orderNo}&h5faceId={faceIdModel.result.h5faceId}&url={enCallbackUrl}&from=browser&userId={userId}&sign={sign}&redirectType=1";
#region 设置缓存
if (user == null) {
user = new VerificationUser();
user.Id = userId;
}
VerificationUser.OrderListObj order = user.OrderList.Find(a => a.Date == DateTime.Now.ToString("yyyy-MM-dd"));
if (order == null)
{
order = new VerificationUser.OrderListObj();
order.Date = date;
order.OrderDetailList.Add(new VerificationUser.OrderDetailListObj()
{
OrderNo = orderNo,
CreateTime = DateTime.Now
});
user.OrderList.Add(order);
}
else {
order.OrderDetailList.Add(new VerificationUser.OrderDetailListObj()
{
OrderNo = orderNo,
CreateTime = DateTime.Now
});
}
Redis.RedisClient.HSet(_redisKey, userId.ToString(), user);
#endregion
return url;
}
/// <summary>
/// 获取人脸识别结果
/// </summary>
/// <param name="orderNo"></param>
/// <returns></returns>
private async Task<DtoViewFaceIdVerificationResult> GetFaceIdVerificationResultAsync(string orderNo) {
string url = "https://miniprogram-kyc.tencentcloudapi.com/api/server/sync?app_id={0}&nonce={1}&order_no={2}&version={3}&sign={4}";
string sign = GetSignByNonceSignTicket(orderNo, out string nonce);
url = string.Format(url, _tencentAppId, nonce, orderNo, "1.0.0", sign);
var response = await _client.GetAsync(url);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
LogHelper.Info(content);
DtoViewFaceIdVerificationResult result = Newtonsoft.Json.JsonConvert.DeserializeObject<DtoViewFaceIdVerificationResult>(content);
if (result.code == "0")
{
return result;
}
else
{
LogHelper.Error("获取人脸识别结果FaceIdVerificationResult失败:" + content);
throw new Ex.FailException("获取人脸识别结果失败," + result.msg);
}
}
/// <summary>
/// 按用户Id获取人脸识别结果(主要方法,获取结果,这里有业务代码,可以删除)
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public DtoViewFaceIdVerificationResult GetFaceIdVerificationResultById(int userId) {
VerificationUser user = Redis.RedisClient.HGet<VerificationUser>(_redisKey, userId.ToString());
if (user == null || user.OrderList==null || user.OrderList.Count==0 || user.OrderList[0].OrderDetailList == null || user.OrderList[0].OrderDetailList.Count == 0)
throw new Ex.FailException("用户没有申请人脸验身");
VerificationUser.OrderListObj order = user.OrderList.Last();
VerificationUser.OrderDetailListObj orderDetail = order.OrderDetailList.Last();
DtoViewFaceIdVerificationResult dto = GetFaceIdVerificationResultAsync(orderDetail.OrderNo).Result;
if (dto == null)
throw new Ex.ParameterException("认证失败或还未生效");
return dto;
}
#region Model
/// <summary>
/// 获取AccessToken Model
/// </summary>
public class DtoViewAccessToken {
/// <summary>
/// 0:成功 非0:失败
/// </summary>
public string code { get; set; }
/// <summary>
/// 请求结果描述
/// </summary>
public string msg { get; set; }
/// <summary>
/// 调用接口的时间
/// </summary>
public string transactionTime { get; set; }
/// <summary>
/// access_token 的值
/// </summary>
public string access_token { get; set; }
/// <summary>
/// access_token 失效的绝对时间
/// </summary>
public string expire_time { get; set; }
/// <summary>
/// access_token 的最大生存时间
/// </summary>
public int expire_in { get; set; }
}
/// <summary>
/// 获取SignTicket Model
/// </summary>
public class DtoViewSignTicket {
/// <summary>
/// 0:成功 非0:失败
/// </summary>
public string code { get; set; }
/// <summary>
/// 请求结果描述
/// </summary>
public string msg { get; set; }
/// <summary>
/// 调用接口的时间
/// </summary>
public string transactionTime { get; set; }
/// <summary>
/// ticket 返回数组
/// </summary>
public List<TicketsListObj> tickets { get; set; }
/// <summary>
/// 数组obj
/// </summary>
public class TicketsListObj {
/// <summary>
/// ticket 的值
/// </summary>
public string value { get; set; }
/// <summary>
/// ticket 失效的绝对时间
/// </summary>
public string expire_time { get; set; }
/// <summary>
/// ticket 的最大生存时间
/// </summary>
public int expire_in { get; set; }
}
}
/// <summary>
/// 获取NonceTicket Model
/// </summary>
public class DtoViewNonceTicket {
/// <summary>
/// 0:成功 非0:失败
/// </summary>
public string code { get; set; }
/// <summary>
/// 请求结果描述
/// </summary>
public string msg { get; set; }
/// <summary>
/// 调用接口的时间
/// </summary>
public string transactionTime { get; set; }
/// <summary>
/// ticket 返回数组
/// </summary>
public List<TicketsListObj> tickets { get; set; }
/// <summary>
/// 数组obj
/// </summary>
public class TicketsListObj
{
/// <summary>
/// ticket 的值
/// </summary>
public string value { get; set; }
/// <summary>
/// ticket 失效的绝对时间
/// </summary>
public string expire_time { get; set; }
/// <summary>
/// ticket 的最大生存时间
/// </summary>
public int expire_in { get; set; }
}
}
/// <summary>
/// 获取人脸验证所需的数据 Model
/// </summary>
public class DtoViewH5FaceId {
/// <summary>
/// 0:成功,非0:失败
/// </summary>
public string code { get; set; }
/// <summary>
/// 请求结果描述
/// </summary>
public string msg { get; set; }
/// <summary>
/// 请求业务流水号
/// </summary>
public string bizSeqNo { get; set; }
/// <summary>
/// 接口请求的时间
/// </summary>
public string transactionTime { get; set; }
public resultObj result { get; set; }
public class resultObj {
/// <summary>
/// 请求业务流水号
/// </summary>
public string bizSeqNo { get; set; }
/// <summary>
/// 接口请求的时间
/// </summary>
public string transactionTime { get; set; }
/// <summary>
/// 订单编号
/// </summary>
public string orderNo { get; set; }
/// <summary>
/// 此次刷脸用户标识
/// </summary>
public string h5faceId { get; set; }
/// <summary>
/// 启动 H5 人脸核身步骤中调用 login 接口使用的域名
/// </summary>
public string optimalDomain { get; set; }
/// <summary>
///
/// </summary>
public string success { get; set; }
}
}
/// <summary>
/// 获取人脸验证所需的数据 Input.Model
/// </summary>
public class DtoInputH5FaceId {
public string webankAppId { get; set; }
public string orderNo { get; set; }
public string name { get; set; }
public string idNo { get; set; }
public string userId { get; set; }
public string sourcePhotoStr { get; set; }
public string sourcePhotoType { get; set; }
public string version { get; set; }
public string sign { get; set; }
}
/// <summary>
/// 获取人脸验证结果 Model
/// </summary>
public class DtoViewFaceIdVerificationResult {
/// <summary>
/// 0:身份验证成功且认证为同一人
/// </summary>
public string code { get; set; }
/// <summary>
/// 返回结果描述
/// </summary>
public string msg { get; set; }
/// <summary>
/// 业务流水号
/// </summary>
public string bizSeqNo { get; set; }
/// <summary>
/// 请求接口的时间
/// </summary>
public string transactionTime { get; set; }
/// <summary>
/// 订单编号
/// </summary>
public string orderNo { get; set; }
/// <summary>
/// 腾讯服务分配的 app_id
/// </summary>
public string app_id { get; set; }
/// <summary>
/// 结果
/// </summary>
public resultObj result { get; set; }
public class resultObj {
/// <summary>
/// 业务流水号
/// </summary>
public string bizSeqNo { get; set; }
/// <summary>
/// 请求接口的时间
/// </summary>
public string transactionTime { get; set; }
/// <summary>
/// 订单编号
/// </summary>
public string orderNo { get; set; }
/// <summary>
/// 证件号码
/// </summary>
public string idNo { get; set; }
/// <summary>
/// 证件类型
/// </summary>
public string idType { get; set; }
/// <summary>
/// 姓名
/// </summary>
public string name { get; set; }
/// <summary>
/// 人脸核身时的视频,Base64 位编码
/// </summary>
public string video { get; set; }
/// <summary>
/// 人脸核身时的照片,Base64 位编码
/// </summary>
public string photo { get; set; }
/// <summary>
/// 活体检测得分
/// </summary>
public string liveRate { get; set; }
/// <summary>
/// 人脸比对得分
/// </summary>
public string similarity { get; set; }
/// <summary>
/// 进行刷脸的时间
/// </summary>
public string occurredTime { get; set; }
/// <summary>
/// 是否成功
/// </summary>
public string success { get; set; }
}
}
/// <summary>
/// 用户验证缓存Model
/// </summary>
private class VerificationUser {
/// <summary>
/// 用户Id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 请求列表
/// </summary>
public List<OrderListObj> OrderList { get; set; } = new List<OrderListObj>();
public class OrderListObj {
public string Date { get; set; }
public List<OrderDetailListObj> OrderDetailList { get; set; } = new List<OrderDetailListObj>();
}
/// <summary>
/// 请求详情
/// </summary>
public class OrderDetailListObj {
public string OrderNo { get; set; }
public DateTime CreateTime { get; set; }
}
}
#endregion
#region 方法
private async Task<string> Post(string jsonData, string url)
{
var postData = new StringContent(jsonData, Encoding.UTF8, "application/json");
HttpContent str = new StringContent(jsonData);
str.Headers.Remove("Content-Type");
str.Headers.Add("Content-Type", "application/json");
string json = "";
try
{
var response = await _client.PostAsync(url, str);
response.EnsureSuccessStatusCode();
json = await response.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
LogHelper.Error(ex.ToString());
throw ex;
}
return json;
}
private async Task<string> Get(string url)
{
string json = "";
try
{
var response = await _client.GetAsync(url);
response.EnsureSuccessStatusCode();
json = await response.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
LogHelper.Error(ex.ToString());
throw ex;
}
return json;
}
#region 生成随机数
/// <summary>
/// 生成单个随机数字
/// </summary>
private int createNum()
{
Random random = new Random(Guid.NewGuid().GetHashCode());
int num = random.Next(10);
return num;
}
/// <summary>
/// 生成单个大写随机字母
/// </summary>
private string createBigAbc()
{
//A-Z的 ASCII值为65-90
Random random = new Random(Guid.NewGuid().GetHashCode());
int num = random.Next(65, 91);
string abc = Convert.ToChar(num).ToString();
return abc;
}
/// <summary>
/// 生成单个小写随机字母
/// </summary>
private string createSmallAbc()
{
//a-z的 ASCII值为97-122
Random random = new Random(Guid.NewGuid().GetHashCode());
int num = random.Next(97, 123);
string abc = Convert.ToChar(num).ToString();
return abc;
}
/// <summary>
/// 生成随机字符串
/// </summary>
/// <param name="length">字符串的长度</param>
/// <returns></returns>
private string CreateRandomStr(int length)
{
// 创建一个StringBuilder对象存储密码
StringBuilder sb = new StringBuilder();
//使用for循环把单个字符填充进StringBuilder对象里面变成14位密码字符串
for (int i = 0; i < length; i++)
{
Random random = new Random(Guid.NewGuid().GetHashCode());
//随机选择里面其中的一种字符生成
switch (random.Next(3))
{
case 0:
//调用生成生成随机数字的方法
sb.Append(createNum());
break;
case 1:
//调用生成生成随机小写字母的方法
sb.Append(createSmallAbc());
break;
case 2:
//调用生成生成随机大写字母的方法
sb.Append(createBigAbc());
break;
}
}
return sb.ToString();
}
#endregion
#endregion
/// <summary>
/// 计算指定文件的SHA1值,不安全但效率高
/// </summary>
public static String ComputeStringSHA1(string str)
{
String hashSHA1 = String.Empty;
System.Security.Cryptography.SHA1 calculator = System.Security.Cryptography.SHA1.Create();
byte[] byteArray = System.Text.Encoding.Default.GetBytes(str);
Byte[] buffer = calculator.ComputeHash(byteArray);
calculator.Clear();
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < buffer.Length; i++)
{
stringBuilder.Append(buffer[i].ToString("x2"));
}
hashSHA1 = stringBuilder.ToString();
return hashSHA1;
}
}
}
c# .net core腾讯人脸识别帮助类
于 2022-06-06 11:07:19 首次发布