xiangyixiangqin/server/src/XiangYi.Application/Services/RealNameService.cs
2026-01-24 20:20:09 +08:00

602 lines
20 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.Extensions.Logging;
using XiangYi.Application.DTOs.Requests;
using XiangYi.Application.DTOs.Responses;
using XiangYi.Application.Interfaces;
using XiangYi.Core.Constants;
using XiangYi.Core.Entities.Biz;
using XiangYi.Core.Enums;
using XiangYi.Core.Exceptions;
using XiangYi.Core.Interfaces;
using XiangYi.Infrastructure.RealName;
using XiangYi.Infrastructure.WeChat;
using XiangYi.Infrastructure.Cache;
namespace XiangYi.Application.Services;
/// <summary>
/// 实名认证服务实现
/// </summary>
public class RealNameService : IRealNameService
{
private readonly IRepository<User> _userRepository;
private readonly IRepository<Order> _orderRepository;
private readonly IRepository<RealNameAuth> _realNameAuthRepository;
private readonly IWeChatService _weChatService;
private readonly IRealNameProvider _realNameProvider;
private readonly ICacheService _cacheService;
private readonly ILogger<RealNameService> _logger;
/// <summary>
/// 实名认证价格(单位:元)
/// </summary>
private const decimal RealNamePrice = 88m;
/// <summary>
/// 临时OCR数据缓存前缀
/// </summary>
private const string OcrCachePrefix = "realname:ocr:";
/// <summary>
/// 临时OCR数据缓存时间分钟
/// </summary>
private const int OcrCacheMinutes = 30;
/// <summary>
/// 认证状态名称
/// </summary>
private static readonly Dictionary<int, string> StatusNames = new()
{
{ 0, "未认证" },
{ 1, "待审核" },
{ 2, "已通过" },
{ 3, "已拒绝" }
};
public RealNameService(
IRepository<User> userRepository,
IRepository<Order> orderRepository,
IRepository<RealNameAuth> realNameAuthRepository,
IWeChatService weChatService,
IRealNameProvider realNameProvider,
ICacheService cacheService,
ILogger<RealNameService> logger)
{
_userRepository = userRepository;
_orderRepository = orderRepository;
_realNameAuthRepository = realNameAuthRepository;
_weChatService = weChatService;
_realNameProvider = realNameProvider;
_cacheService = cacheService;
_logger = logger;
}
/// <inheritdoc />
public async Task<RealNameOrderResponse?> CreateRealNameOrderAsync(long userId)
{
var user = await _userRepository.GetByIdAsync(userId);
if (user == null)
{
throw new BusinessException(ErrorCodes.UserNotFound, "用户不存在");
}
// 已实名用户不能重复认证
if (user.IsRealName)
{
throw new BusinessException(ErrorCodes.RealNameAlreadyVerified, "您已完成实名认证");
}
// 检查是否有待处理的认证记录
var existingAuth = (await _realNameAuthRepository.GetListAsync(
a => a.UserId == userId && a.Status == (int)RealNameAuthStatus.Pending))
.FirstOrDefault();
if (existingAuth != null)
{
throw new BusinessException(ErrorCodes.InvalidParameter, "您有待处理的实名认证申请");
}
// 会员用户免费认证
if (IRealNameService.ShouldBeFreeForMember(user.IsMember, user.IsRealName))
{
_logger.LogInformation("会员用户免费实名认证: UserId={UserId}", userId);
return new RealNameOrderResponse
{
IsFree = true,
Amount = 0
};
}
// 非会员用户创建付费订单
var orderNo = GenerateOrderNo();
var order = new Order
{
OrderNo = orderNo,
UserId = userId,
OrderType = (int)OrderType.RealName,
ProductName = "实名认证",
Amount = RealNamePrice,
PayAmount = RealNamePrice,
Status = (int)OrderStatus.Pending,
ExpireTime = DateTime.Now.AddMinutes(30),
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now
};
order = await _orderRepository.AddAsync(order);
_logger.LogInformation("创建实名认证订单: OrderId={OrderId}, OrderNo={OrderNo}, UserId={UserId}",
order.Id, orderNo, userId);
// 调用微信支付创建预支付订单
var payRequest = new CreatePaymentRequest
{
OutTradeNo = orderNo,
Amount = (int)(RealNamePrice * 100),
Description = "实名认证",
OpenId = user.OpenId,
Attach = "realname"
};
var payResult = await _weChatService.CreatePaymentAsync(payRequest);
if (!payResult.Success)
{
_logger.LogWarning("创建微信支付订单失败: OrderNo={OrderNo}, Error={Error}",
orderNo, payResult.ErrorMessage);
throw new BusinessException(ErrorCodes.PaymentServiceError,
payResult.ErrorMessage ?? "创建支付订单失败");
}
return new RealNameOrderResponse
{
OrderId = order.Id,
OrderNo = orderNo,
Amount = RealNamePrice,
IsFree = false,
PayParams = payResult.PayParams != null ? new DTOs.Responses.WeChatPayParams
{
TimeStamp = payResult.PayParams.TimeStamp,
NonceStr = payResult.PayParams.NonceStr,
Package = payResult.PayParams.Package,
SignType = payResult.PayParams.SignType,
PaySign = payResult.PayParams.PaySign
} : null
};
}
/// <inheritdoc />
public async Task<RealNameSubmitResponse> SubmitRealNameAsync(long userId, RealNameSubmitRequest request)
{
var user = await _userRepository.GetByIdAsync(userId);
if (user == null)
{
throw new BusinessException(ErrorCodes.UserNotFound, "用户不存在");
}
// 已实名用户不能重复认证
if (user.IsRealName)
{
throw new BusinessException(ErrorCodes.RealNameAlreadyVerified, "您已完成实名认证");
}
// 验证参数
if (string.IsNullOrWhiteSpace(request.RealName))
{
throw new BusinessException(ErrorCodes.ValidationFailed, "请输入真实姓名");
}
if (string.IsNullOrWhiteSpace(request.IdCard))
{
throw new BusinessException(ErrorCodes.ValidationFailed, "请输入身份证号");
}
// 非会员用户需要检查是否已支付
if (!user.IsMember)
{
var paidOrder = (await _orderRepository.GetListAsync(
o => o.UserId == userId &&
o.OrderType == (int)OrderType.RealName &&
o.Status == (int)OrderStatus.Paid))
.FirstOrDefault();
if (paidOrder == null)
{
throw new BusinessException(ErrorCodes.InvalidParameter, "请先完成实名认证费用支付");
}
}
// 调用第三方实名认证接口验证
var verifyResult = await _realNameProvider.VerifyIdCardAsync(request.RealName, request.IdCard);
if (!verifyResult.IsVerified)
{
_logger.LogWarning("实名认证验证失败: UserId={UserId}, Error={Error}",
userId, verifyResult.ErrorMessage);
return new RealNameSubmitResponse
{
Success = false,
Message = verifyResult.ErrorMessage ?? "身份信息验证失败",
Status = (int)RealNameAuthStatus.Rejected
};
}
// 脱敏存储身份信息
var maskedName = IRealNameService.MaskName(request.RealName);
var maskedIdCard = IRealNameService.MaskIdCard(request.IdCard);
// 查找关联订单
long orderId = 0;
if (!user.IsMember)
{
var order = (await _orderRepository.GetListAsync(
o => o.UserId == userId &&
o.OrderType == (int)OrderType.RealName &&
o.Status == (int)OrderStatus.Paid))
.FirstOrDefault();
orderId = order?.Id ?? 0;
}
// 创建实名认证记录
var realNameAuth = new RealNameAuth
{
UserId = userId,
OrderId = orderId,
RealName = maskedName,
IdCard = maskedIdCard,
Status = (int)RealNameAuthStatus.Approved,
VerifyTime = DateTime.Now,
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now
};
await _realNameAuthRepository.AddAsync(realNameAuth);
// 更新用户实名状态
user.IsRealName = true;
user.UpdateTime = DateTime.Now;
await _userRepository.UpdateAsync(user);
_logger.LogInformation("实名认证成功: UserId={UserId}", userId);
return new RealNameSubmitResponse
{
Success = true,
Message = "实名认证成功",
Status = (int)RealNameAuthStatus.Approved,
MaskedName = maskedName,
MaskedIdCard = maskedIdCard
};
}
/// <inheritdoc />
public async Task<RealNameStatusResponse> GetRealNameStatusAsync(long userId)
{
var user = await _userRepository.GetByIdAsync(userId);
if (user == null)
{
throw new BusinessException(ErrorCodes.UserNotFound, "用户不存在");
}
// 检查是否已支付实名认证费用
var hasPaidOrder = false;
if (!user.IsMember)
{
var paidOrder = (await _orderRepository.GetListAsync(
o => o.UserId == userId &&
o.OrderType == (int)OrderType.RealName &&
o.Status == (int)OrderStatus.Paid))
.FirstOrDefault();
hasPaidOrder = paidOrder != null;
}
// 查找最新的实名认证记录
var authRecords = await _realNameAuthRepository.GetListAsync(a => a.UserId == userId);
var latestAuth = authRecords.OrderByDescending(a => a.CreateTime).FirstOrDefault();
if (latestAuth == null)
{
// 未认证
return new RealNameStatusResponse
{
IsRealName = false,
IsPaid = user.IsMember || hasPaidOrder,
Status = 0,
StatusName = GetStatusName(0),
NeedPayment = !user.IsMember && !hasPaidOrder,
PaymentAmount = user.IsMember ? null : RealNamePrice
};
}
return new RealNameStatusResponse
{
IsRealName = user.IsRealName,
IsPaid = user.IsMember || hasPaidOrder,
Status = latestAuth.Status,
StatusName = GetStatusName(latestAuth.Status),
MaskedName = latestAuth.RealName,
MaskedIdCard = latestAuth.IdCard,
Name = latestAuth.RealName,
IdNumber = latestAuth.IdCard,
RejectReason = latestAuth.RejectReason,
VerifyTime = latestAuth.VerifyTime,
NeedPayment = !user.IsMember && latestAuth.Status != (int)RealNameAuthStatus.Approved,
PaymentAmount = user.IsMember ? null : RealNamePrice
};
}
/// <inheritdoc />
public async Task<RealNameVerifyResponse> VerifyWithImagesAsync(long userId, string frontImageBase64, string? backImageBase64)
{
var user = await _userRepository.GetByIdAsync(userId);
if (user == null)
{
throw new BusinessException(ErrorCodes.UserNotFound, "用户不存在");
}
// 已实名用户不能重复认证
if (user.IsRealName)
{
return new RealNameVerifyResponse
{
Success = false,
Message = "您已完成实名认证"
};
}
// 非会员用户需要检查是否已支付
if (!user.IsMember)
{
var paidOrder = (await _orderRepository.GetListAsync(
o => o.UserId == userId &&
o.OrderType == (int)OrderType.RealName &&
o.Status == (int)OrderStatus.Paid))
.FirstOrDefault();
if (paidOrder == null)
{
return new RealNameVerifyResponse
{
Success = false,
Message = "请先完成实名认证费用支付"
};
}
}
// OCR识别身份证正面
var frontOcrResult = await _realNameProvider.OcrIdCardFrontAsync(frontImageBase64);
if (!frontOcrResult.Success)
{
_logger.LogWarning("身份证正面OCR识别失败: UserId={UserId}, Error={Error}", userId, frontOcrResult.ErrorMessage);
return new RealNameVerifyResponse
{
Success = false,
Message = frontOcrResult.ErrorMessage ?? "身份证正面识别失败,请重新拍照"
};
}
var name = frontOcrResult.Name!;
var idNumber = frontOcrResult.IdNumber!;
// 如果没有反面图片,需要分步上传
if (string.IsNullOrEmpty(backImageBase64))
{
// 缓存正面OCR结果等待反面上传
var cacheKey = $"{OcrCachePrefix}{userId}";
await _cacheService.SetAsync(cacheKey, new OcrCacheData
{
Name = name,
IdNumber = idNumber
}, OcrCacheMinutes * 60);
return new RealNameVerifyResponse
{
Success = true,
Message = "身份证正面识别成功,请上传反面",
NeedBackImage = true
};
}
// OCR识别身份证反面
var backOcrResult = await _realNameProvider.OcrIdCardBackAsync(backImageBase64);
if (!backOcrResult.Success)
{
_logger.LogWarning("身份证反面OCR识别失败: UserId={UserId}, Error={Error}", userId, backOcrResult.ErrorMessage);
return new RealNameVerifyResponse
{
Success = false,
Message = backOcrResult.ErrorMessage ?? "身份证反面识别失败,请重新拍照"
};
}
// 调用实名认证接口验证
return await CompleteVerificationAsync(userId, user, name, idNumber);
}
/// <inheritdoc />
public async Task<RealNameVerifyResponse> UploadBackImageAsync(long userId, string backImageBase64)
{
var user = await _userRepository.GetByIdAsync(userId);
if (user == null)
{
throw new BusinessException(ErrorCodes.UserNotFound, "用户不存在");
}
// 已实名用户不能重复认证
if (user.IsRealName)
{
return new RealNameVerifyResponse
{
Success = false,
Message = "您已完成实名认证"
};
}
// 获取缓存的正面OCR数据
var cacheKey = $"{OcrCachePrefix}{userId}";
var cachedData = await _cacheService.GetAsync<OcrCacheData>(cacheKey);
if (cachedData == null)
{
return new RealNameVerifyResponse
{
Success = false,
Message = "请先上传身份证正面"
};
}
// OCR识别身份证反面
var backOcrResult = await _realNameProvider.OcrIdCardBackAsync(backImageBase64);
if (!backOcrResult.Success)
{
_logger.LogWarning("身份证反面OCR识别失败: UserId={UserId}, Error={Error}", userId, backOcrResult.ErrorMessage);
return new RealNameVerifyResponse
{
Success = false,
Message = backOcrResult.ErrorMessage ?? "身份证反面识别失败,请重新拍照"
};
}
// 清除缓存
await _cacheService.RemoveAsync(cacheKey);
// 调用实名认证接口验证
return await CompleteVerificationAsync(userId, user, cachedData.Name, cachedData.IdNumber);
}
/// <summary>
/// 完成实名认证验证
/// </summary>
private async Task<RealNameVerifyResponse> CompleteVerificationAsync(long userId, User user, string name, string idNumber)
{
// 调用第三方实名认证接口验证
var verifyResult = await _realNameProvider.VerifyIdCardAsync(name, idNumber);
if (!verifyResult.IsVerified)
{
_logger.LogWarning("实名认证验证失败: UserId={UserId}, Error={Error}", userId, verifyResult.ErrorMessage);
return new RealNameVerifyResponse
{
Success = false,
Message = verifyResult.ErrorMessage ?? "身份信息验证失败,请确认身份证信息正确"
};
}
// 脱敏存储身份信息
var maskedName = IRealNameService.MaskName(name);
var maskedIdCard = IRealNameService.MaskIdCard(idNumber);
// 查找关联订单
long orderId = 0;
if (!user.IsMember)
{
var order = (await _orderRepository.GetListAsync(
o => o.UserId == userId &&
o.OrderType == (int)OrderType.RealName &&
o.Status == (int)OrderStatus.Paid))
.FirstOrDefault();
orderId = order?.Id ?? 0;
}
// 创建实名认证记录
var realNameAuth = new RealNameAuth
{
UserId = userId,
OrderId = orderId,
RealName = maskedName,
IdCard = maskedIdCard,
Status = (int)RealNameAuthStatus.Approved,
VerifyTime = DateTime.Now,
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now
};
await _realNameAuthRepository.AddAsync(realNameAuth);
// 更新用户实名状态
user.IsRealName = true;
user.UpdateTime = DateTime.Now;
await _userRepository.UpdateAsync(user);
_logger.LogInformation("实名认证成功: UserId={UserId}", userId);
return new RealNameVerifyResponse
{
Success = true,
Message = "实名认证成功",
NeedBackImage = false,
Data = new RealNameVerifyData
{
Name = maskedName,
IdNumber = maskedIdCard
}
};
}
/// <summary>
/// OCR缓存数据
/// </summary>
private class OcrCacheData
{
public string Name { get; set; } = string.Empty;
public string IdNumber { get; set; } = string.Empty;
}
#region
/// <summary>
/// 生成订单号
/// </summary>
private static string GenerateOrderNo()
{
var randomSuffix = Random.Shared.Next(1000, 9999);
return $"R{DateTime.Now:yyyyMMddHHmmss}{randomSuffix:D4}";
}
/// <summary>
/// 获取状态名称
/// </summary>
private static string GetStatusName(int status)
{
return StatusNames.TryGetValue(status, out var name) ? name : "未知";
}
#endregion
#region
/// <summary>
/// 脱敏姓名
/// </summary>
public static string MaskName(string name)
{
return IRealNameService.MaskName(name);
}
/// <summary>
/// 脱敏身份证号
/// </summary>
public static string MaskIdCard(string idCard)
{
return IRealNameService.MaskIdCard(idCard);
}
/// <summary>
/// 验证脱敏是否正确
/// </summary>
public static bool ValidateMasking(string originalName, string maskedName, string originalIdCard, string maskedIdCard)
{
return IRealNameService.ValidateMasking(originalName, maskedName, originalIdCard, maskedIdCard);
}
/// <summary>
/// 检查会员是否应该免费实名认证
/// </summary>
public static bool ShouldBeFreeForMember(bool isMember, bool isAlreadyRealName)
{
return IRealNameService.ShouldBeFreeForMember(isMember, isAlreadyRealName);
}
#endregion
}