using FreeSql; using LiveForum.IService.Others; using LiveForum.IService.RealName; using LiveForum.Model; using LiveForum.Model.Dto.RealName; using Microsoft.Extensions.Logging; using System.Security.Cryptography; using System.Text; namespace LiveForum.Service.RealName { /// /// 实名认证业务服务 /// public class RealNameService : IRealNameService { private readonly IBaseRepository _userRepository; private readonly IBaseRepository _logRepository; private readonly ISystemSettingsService _settings; private readonly ILogger _logger; private readonly IEnumerable _providers; private const string REAL_NAME_ENABLED_KEY = "real_name_enabled"; private const string REAL_NAME_PROVIDER_KEY = "real_name_provider"; private const string DAILY_LIMIT_KEY = "real_name_daily_limit"; private const string INTERVAL_KEY = "real_name_interval_seconds"; // AES加密密钥(实际项目应从配置读取) private const string AES_KEY = "LiveForum@RealName2024!AesKey32B"; public RealNameService( IBaseRepository userRepository, IBaseRepository logRepository, ISystemSettingsService settings, ILogger logger, IEnumerable providers) { _userRepository = userRepository; _logRepository = logRepository; _settings = settings; _logger = logger; _providers = providers; } public async Task CheckStatusAsync(long userId) { var enabled = await IsEnabledAsync(); var user = await _userRepository.Select.Where(x => x.Id == userId).FirstAsync(); return new RealNameStatusDto { RealNameEnabled = enabled, IsVerified = user?.IsRealNameVerified ?? false }; } public async Task RequiresRealNameAsync(long userId) { var enabled = await IsEnabledAsync(); if (!enabled) return false; var user = await _userRepository.Select.Where(x => x.Id == userId).FirstAsync(); return !(user?.IsRealNameVerified ?? false); } public async Task VerifyAsync(long userId, string realName, string idCardNumber, string? clientIp) { // 1. 限流检查 var rateLimitMsg = await CheckRateLimitAsync(userId); if (rateLimitMsg != null) return rateLimitMsg; // 2. 获取当前服务商 var provider = await GetProviderAsync(); if (provider == null) return "实名认证服务未配置,请联系管理员"; // 3. 调用服务商验证 var result = await provider.VerifyAsync(realName, idCardNumber); // 4. 记录日志 await _logRepository.InsertAsync(new T_RealNameVerifyLogs { UserId = userId, RealName = MaskName(realName), IdCardNumberMasked = MaskIdCard(idCardNumber), IsSuccess = result.IsMatch, FailReason = result.IsMatch ? null : result.Message, ClientIp = clientIp }); // 5. 成功则更新用户状态 if (result.IsMatch) { var isMinor = IsMinorByIdCard(idCardNumber); await _userRepository.UpdateDiy .Where(x => x.Id == userId) .Set(x => x.IsRealNameVerified, true) .Set(x => x.RealName, MaskName(realName)) .Set(x => x.IdCardNumber, EncryptAes(idCardNumber)) .Set(x => x.RealNameVerifiedAt, DateTime.Now) .Set(x => x.IsMinor, isMinor) .ExecuteAffrowsAsync(); return "实名认证成功"; } return result.Message; } private async Task IsEnabledAsync() { var val = await _settings.GetSettingAsync(REAL_NAME_ENABLED_KEY); return string.Equals(val, "true", StringComparison.OrdinalIgnoreCase); } private async Task GetProviderAsync() { var providerName = await _settings.GetSettingAsync(REAL_NAME_PROVIDER_KEY) ?? "aliyun"; return _providers.FirstOrDefault(p => p.ProviderName.Equals(providerName, StringComparison.OrdinalIgnoreCase)); } private async Task CheckRateLimitAsync(long userId) { var dailyLimitStr = await _settings.GetSettingAsync(DAILY_LIMIT_KEY) ?? "5"; var intervalStr = await _settings.GetSettingAsync(INTERVAL_KEY) ?? "60"; int.TryParse(dailyLimitStr, out var dailyLimit); int.TryParse(intervalStr, out var intervalSeconds); if (dailyLimit <= 0) dailyLimit = 5; if (intervalSeconds <= 0) intervalSeconds = 60; var today = DateTime.Today; var todayCount = await _logRepository.Select .Where(x => x.UserId == userId && x.CreatedAt >= today) .CountAsync(); if (todayCount >= dailyLimit) return "今日验证次数已用完,请明天再试"; var lastLog = await _logRepository.Select .Where(x => x.UserId == userId) .OrderByDescending(x => x.CreatedAt) .FirstAsync(); if (lastLog != null) { var elapsed = (DateTime.Now - lastLog.CreatedAt).TotalSeconds; if (elapsed < intervalSeconds) { var remaining = (int)Math.Ceiling(intervalSeconds - elapsed); return $"操作过于频繁,请 {remaining} 秒后再试"; } } return null; } private static string MaskName(string name) { if (string.IsNullOrEmpty(name)) return ""; if (name.Length <= 1) return name; if (name.Length == 2) return name[0] + "*"; return name[0] + new string('*', name.Length - 2) + name[^1]; } private static string MaskIdCard(string idCard) { if (string.IsNullOrEmpty(idCard) || idCard.Length < 4) return "****"; return "**************" + idCard[^4..]; } /// /// 根据身份证号判断是否未成年(不满18周岁) /// 身份证号第7-14位为出生日期(yyyyMMdd) /// private static bool IsMinorByIdCard(string idCardNumber) { if (string.IsNullOrEmpty(idCardNumber) || idCardNumber.Length != 18) return false; var birthStr = idCardNumber.Substring(6, 8); if (!DateTime.TryParseExact(birthStr, "yyyyMMdd", null, System.Globalization.DateTimeStyles.None, out var birthDate)) return false; var today = DateTime.Today; var age = today.Year - birthDate.Year; if (birthDate.Date > today.AddYears(-age)) age--; return age < 18; } private static string EncryptAes(string plainText) { using var aes = Aes.Create(); aes.Key = Encoding.UTF8.GetBytes(AES_KEY); aes.GenerateIV(); using var encryptor = aes.CreateEncryptor(); var plainBytes = Encoding.UTF8.GetBytes(plainText); var encrypted = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length); // IV + 密文 一起存储 var result = new byte[aes.IV.Length + encrypted.Length]; Buffer.BlockCopy(aes.IV, 0, result, 0, aes.IV.Length); Buffer.BlockCopy(encrypted, 0, result, aes.IV.Length, encrypted.Length); return Convert.ToBase64String(result); } } }