using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Security.Cryptography; using System.Text; using System.Text.Json; namespace XiangYi.Infrastructure.RealName; /// /// 阿里云实名认证服务实现 /// 使用阿里云身份证二要素核验API(纯服务端接入) /// public class AliyunRealNameProvider : IRealNameProvider { private readonly AliyunRealNameOptions _options; private readonly ILogger _logger; private readonly HttpClient _httpClient; // 身份证二要素核验API private const string IdCardVerifyHost = "cloudauth.aliyuncs.com"; private const string ApiVersion = "2019-03-07"; public AliyunRealNameProvider( IOptions options, IHttpClientFactory httpClientFactory, ILogger logger) { _options = options.Value.Aliyun ?? new AliyunRealNameOptions(); _logger = logger; _httpClient = httpClientFactory.CreateClient("AliyunRealName"); } /// /// 身份证二要素验证(姓名+身份证号) /// public async Task VerifyIdCardAsync(string name, string idCard) { try { // 验证配置 if (string.IsNullOrEmpty(_options.AccessKeyId)) { _logger.LogError("阿里云实名认证配置错误: AccessKeyId 为空"); return RealNameResult.Fail("CONFIG_ERROR", "实名认证服务配置错误,请联系管理员"); } if (string.IsNullOrEmpty(_options.AccessKeySecret)) { _logger.LogError("阿里云实名认证配置错误: AccessKeySecret 为空"); return RealNameResult.Fail("CONFIG_ERROR", "实名认证服务配置错误,请联系管理员"); } _logger.LogInformation("阿里云实名认证配置: AccessKeyId={AccessKeyId}, SecretLength={SecretLength}", _options.AccessKeyId[..8] + "***", _options.AccessKeySecret?.Length ?? 0); var parameters = new SortedDictionary { { "Action", "Id2MetaVerify" }, { "Version", ApiVersion }, { "Format", "JSON" }, { "AccessKeyId", _options.AccessKeyId }, { "SignatureMethod", "HMAC-SHA1" }, { "Timestamp", DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ") }, { "SignatureVersion", "1.0" }, { "SignatureNonce", Guid.NewGuid().ToString("N") }, { "ParamType", "normal" }, { "IdentifyNum", idCard }, { "UserName", name } }; var signature = GenerateSignature(parameters); parameters.Add("Signature", signature); var queryString = string.Join("&", parameters.Select(p => $"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(p.Value)}")); var requestUrl = $"https://{IdCardVerifyHost}/?{queryString}"; _logger.LogInformation("阿里云实名认证请求: {Url}", requestUrl.Replace(_options.AccessKeyId, "***")); var response = await _httpClient.GetAsync(requestUrl); var content = await response.Content.ReadAsStringAsync(); _logger.LogInformation("阿里云实名认证响应: {Response}", content); var result = JsonSerializer.Deserialize(content, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); if (result?.Code == "200" && result.ResultObject?.BizCode == "1") { _logger.LogInformation("阿里云身份证二要素验证通过: {IdCard}", MaskIdCard(idCard)); return RealNameResult.Success(result.RequestId); } var errorMsg = GetErrorMessage(result?.ResultObject?.BizCode, result?.Message); _logger.LogWarning("阿里云身份证二要素验证失败: {IdCard}, BizCode: {BizCode}, Error: {Error}", MaskIdCard(idCard), result?.ResultObject?.BizCode, errorMsg); return RealNameResult.Fail( result?.ResultObject?.BizCode ?? result?.Code ?? "UNKNOWN", errorMsg, result?.RequestId); } catch (Exception ex) { _logger.LogError(ex, "阿里云身份证二要素验证异常: {IdCard}", MaskIdCard(idCard)); return RealNameResult.Fail("EXCEPTION", ex.Message); } } /// /// 身份证三要素验证(阿里云纯API模式不支持,返回失败) /// public Task VerifyWithPhotoAsync(string name, string idCard, string photoBase64) { _logger.LogWarning("阿里云纯API模式不支持身份证三要素验证(人脸比对)"); return Task.FromResult(RealNameResult.Fail("NOT_SUPPORTED", "当前配置不支持人脸比对验证,请使用二要素验证")); } /// /// 身份证OCR识别(正面)- 阿里云纯API模式不支持 /// public Task OcrIdCardFrontAsync(string imageBase64) { _logger.LogWarning("阿里云纯API模式不支持身份证OCR识别"); return Task.FromResult(IdCardOcrResult.Fail("NOT_SUPPORTED", "当前配置不支持身份证OCR识别,请手动输入身份信息")); } /// /// 身份证OCR识别(反面)- 阿里云纯API模式不支持 /// public Task OcrIdCardBackAsync(string imageBase64) { _logger.LogWarning("阿里云纯API模式不支持身份证OCR识别"); return Task.FromResult(IdCardOcrResult.Fail("NOT_SUPPORTED", "当前配置不支持身份证OCR识别,请手动输入身份信息")); } /// /// 生成阿里云API签名 /// private string GenerateSignature(SortedDictionary parameters) { var canonicalizedQueryString = string.Join("&", parameters.Select(p => $"{PercentEncode(p.Key)}={PercentEncode(p.Value)}")); var stringToSign = $"GET&%2F&{PercentEncode(canonicalizedQueryString)}"; // 确保 AccessKeySecret 不为空 var secretKey = _options.AccessKeySecret ?? string.Empty; using var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(secretKey + "&")); var hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)); return Convert.ToBase64String(hashBytes); } /// /// URL编码(阿里云特殊规则) /// private static string PercentEncode(string value) { if (string.IsNullOrEmpty(value)) return string.Empty; var encoded = Uri.EscapeDataString(value); // 阿里云特殊编码规则 encoded = encoded.Replace("+", "%20") .Replace("*", "%2A") .Replace("%7E", "~"); return encoded; } /// /// 获取错误信息 /// private static string GetErrorMessage(string? bizCode, string? message) { return bizCode switch { "1" => "验证通过", "2" => "姓名与身份证号不一致", "3" => "身份证号不存在", "4" => "身份证号格式错误", "5" => "服务异常,请稍后重试", _ => message ?? "验证失败,请检查身份信息是否正确" }; } private static string MaskIdCard(string idCard) { if (string.IsNullOrEmpty(idCard) || idCard.Length < 10) return "***"; return $"{idCard[..4]}**********{idCard[^4..]}"; } #region 响应模型 private class AliyunIdVerifyResponse { public string? Code { get; set; } public string? Message { get; set; } public string? RequestId { get; set; } public AliyunIdVerifyResultObject? ResultObject { get; set; } } private class AliyunIdVerifyResultObject { public string? BizCode { get; set; } } #endregion }