using FreeSql; using LiveForum.Code.Base; using LiveForum.Code.Redis.Contract; using LiveForum.Model; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace LiveForum.WebApi.BackgroundServices { /// /// 送花批量同步后台服务 /// 从Redis Hash获取待处理操作,定时批量同步到数据库 /// 触发条件:每1分钟执行一次 /// public class FlowerBatchSyncService : BackgroundService { private readonly IRedisService _redisService; private readonly IFreeSql _fsql; private readonly ILogger _logger; private const string FLOWER_PENDING_OPERATIONS_KEY = "flower:pending:operations"; private static readonly TimeSpan SYNC_INTERVAL = TimeSpan.FromSeconds(30); // 缩短间隔 private const int MAX_BATCH_SIZE = 100; // 限制批次大小 public FlowerBatchSyncService( IRedisService redisService, IFreeSql fsql, ILogger logger) { _redisService = redisService; _fsql = fsql; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("[FlowerBatchSync] 送花批量同步服务已启动"); while (!stoppingToken.IsCancellationRequested) { try { // 等待同步间隔 await Task.Delay(SYNC_INTERVAL, stoppingToken); _logger.LogDebug("[FlowerBatchSync] 开始定时批量同步"); await ProcessBatchSyncAsync(stoppingToken); } catch (OperationCanceledException) { // 正常停止 break; } catch (Exception ex) { _logger.LogError(ex, "[FlowerBatchSync] 批量同步过程中发生异常"); // 发生异常后等待30秒再继续 await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken); } } _logger.LogInformation("[FlowerBatchSync] 送花批量同步服务已停止"); } /// /// 执行批量同步 /// private async Task ProcessBatchSyncAsync(CancellationToken stoppingToken) { try { // 1. 限量获取待处理操作,避免一次性处理过多数据 var (toInsert, processedFields) = await GetLimitedOperationsAsync(); if (toInsert.Count == 0) { _logger.LogDebug("[FlowerBatchSync] 没有待处理的送花操作"); return; } _logger.LogInformation("[FlowerBatchSync] 本次处理 {Count} 条送花记录", toInsert.Count); // 2. 批量写入数据库 await ProcessDatabaseOperationsAsync(toInsert, stoppingToken); // 3. 清除已处理的待处理记录 if (processedFields.Count > 0) { foreach (var field in processedFields) { await _redisService.HashDeleteAsync(FLOWER_PENDING_OPERATIONS_KEY, field); } _logger.LogInformation("[FlowerBatchSync] 已清除 {Count} 个已处理的操作记录", processedFields.Count); } _logger.LogInformation("[FlowerBatchSync] 批量同步完成。处理: {Count} 条送花记录", toInsert.Count); } catch (Exception ex) { _logger.LogError(ex, "[FlowerBatchSync] 批量同步处理失败"); throw; } } /// /// 限量获取Redis中的操作,避免一次性处理过多 /// private async Task<(List<(long UserId, string TargetType, long TargetId, string SendMonth, DateTime SendTime, long? ReceiverId)> toInsert, List processedFields)> GetLimitedOperationsAsync() { var operationsByKey = new Dictionary(); var processedCount = 0; // 获取所有操作,但限制处理数量 var allOperations = await _redisService.HashGetAllAsync(FLOWER_PENDING_OPERATIONS_KEY); if (allOperations == null || allOperations.Length == 0) { return (new List<(long, string, long, string, DateTime, long?)>(), new List()); } foreach (var entry in allOperations) { if (processedCount >= MAX_BATCH_SIZE) break; var field = entry.Name.ToString(); // {userId}:{targetType}:{targetId} var value = entry.Value.ToString(); try { var operation = JsonSerializer.Deserialize(value, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); if (operation != null) { // 如果已存在,比较时间戳,保留最新的 if (operationsByKey.TryGetValue(field, out var existing)) { if (operation.OccurredAt > existing.OccurredAt) { operationsByKey[field] = operation; } } else { operationsByKey[field] = operation; } processedCount++; } } catch (Exception ex) { _logger.LogWarning(ex, "[FlowerBatchSync] 解析操作失败。Field: {Field}, Value: {Value}", field, value); } } // 准备批量插入数据 var toInsert = new List<(long UserId, string TargetType, long TargetId, string SendMonth, DateTime SendTime, long? ReceiverId)>(); var processedFields = new List(); foreach (var kvp in operationsByKey) { var field = kvp.Key; // {userId}:{targetType}:{targetId} var parts = field.Split(':'); if (parts.Length != 3) { _logger.LogWarning("[FlowerBatchSync] 无效的操作字段格式: {Field}", field); continue; } if (!long.TryParse(parts[0], out var userId) || !long.TryParse(parts[2], out var targetId)) { _logger.LogWarning("[FlowerBatchSync] 解析操作字段失败: {Field}", field); continue; } var targetType = parts[1]; var operation = kvp.Value; processedFields.Add(field); toInsert.Add((userId, targetType, targetId, operation.SendMonth, operation.OccurredAt, operation.ReceiverId)); } return (toInsert, processedFields); } /// /// 处理数据库操作 /// private async Task ProcessDatabaseOperationsAsync( List<(long UserId, string TargetType, long TargetId, string SendMonth, DateTime SendTime, long? ReceiverId)> toInsert, CancellationToken stoppingToken) { const int maxRetries = 3; var retryCount = 0; while (retryCount < maxRetries) { try { await ProcessDatabaseOperationsInternalAsync(toInsert, stoppingToken); return; // 成功则退出 } catch (Exception ex) when (ex.Message.Contains("FOREIGN KEY constraint") && retryCount < maxRetries - 1) { retryCount++; _logger.LogWarning("[FlowerBatchSync] 外键约束错误,第 {RetryCount} 次重试", retryCount); await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); } catch (Exception ex) when (IsDeadlockException(ex) && retryCount < maxRetries - 1) { retryCount++; var delay = TimeSpan.FromMilliseconds(100 * Math.Pow(2, retryCount)); _logger.LogWarning(ex, "[FlowerBatchSync] 检测到死锁,第 {Retry} 次重试,延迟 {Delay}ms", retryCount, delay.TotalMilliseconds); await Task.Delay(delay, stoppingToken); } catch (Exception ex) { _logger.LogError(ex, "[FlowerBatchSync] 处理数据库操作时发生异常"); throw; } } } /// /// 内部数据库操作处理 /// private async Task ProcessDatabaseOperationsInternalAsync( List<(long UserId, string TargetType, long TargetId, string SendMonth, DateTime SendTime, long? ReceiverId)> toInsert, CancellationToken stoppingToken) { try { // 使用FreeSql的事务 using (var uow = _fsql.CreateUnitOfWork()) { try { var flowerRecordsInfoRepo = uow.GetRepository(); var flowerRecordsRepo = uow.GetRepository(); var streamersRepo = uow.GetRepository(); var postsRepo = uow.GetRepository(); var usersRepo = uow.GetRepository(); // 验证用户ID有效性,过滤无效数据 var userIds = toInsert.Select(x => x.UserId).Distinct().ToList(); var validUserIds = await usersRepo.Select .Where(u => userIds.Contains(u.Id)) .ToListAsync(u => u.Id); var validUserIdSet = new HashSet(validUserIds); var originalCount = toInsert.Count; toInsert = toInsert.Where(x => validUserIdSet.Contains(x.UserId)).ToList(); if (originalCount != toInsert.Count) { _logger.LogWarning("[FlowerBatchSync] 过滤掉 {InvalidCount} 条无效用户ID的记录,剩余 {ValidCount} 条", originalCount - toInsert.Count, toInsert.Count); } if (toInsert.Count == 0) { _logger.LogWarning("[FlowerBatchSync] 所有记录都包含无效用户ID,跳过本批次"); return; } // 按SendMonth分组,便于处理月度汇总 var groupedByMonth = toInsert.GroupBy(x => x.SendMonth).ToList(); foreach (var monthGroup in groupedByMonth) { var sendMonth = monthGroup.Key; var monthOperations = monthGroup.ToList(); // 1. 批量插入送花详细记录 var flowerRecordsInfo = monthOperations.Select(x => new T_FlowerRecordsInfo { UserId = x.UserId, TargetId = x.TargetId, TargetType = x.TargetType, FlowerCount = 1, SendMonth = sendMonth, SendTime = x.SendTime }).ToList(); if (flowerRecordsInfo.Count > 0) { await flowerRecordsInfoRepo.InsertAsync(flowerRecordsInfo); _logger.LogInformation("[FlowerBatchSync] 批量插入 {Count} 条送花详细记录,月份: {Month}", flowerRecordsInfo.Count, sendMonth); } // 2. 处理月度汇总记录(按 (userId, targetType, targetId, sendMonth) 分组) var monthlyGroups = monthOperations .GroupBy(x => new { x.UserId, x.TargetType, x.TargetId }) .ToList(); foreach (var monthlyGroup in monthlyGroups) { var userId = monthlyGroup.Key.UserId; var targetType = monthlyGroup.Key.TargetType; var targetId = monthlyGroup.Key.TargetId; var count = monthlyGroup.Count(); // 查询是否已存在月度汇总记录 var monthlyRecord = await flowerRecordsRepo.Select .Where(r => r.UserId == userId && r.TargetId == targetId && r.TargetType == targetType && r.SendMonth == sendMonth) .FirstAsync(); if (monthlyRecord != null) { monthlyRecord.FlowerCount += count; await flowerRecordsRepo.UpdateAsync(monthlyRecord); } else { var newMonthlyRecord = new T_FlowerRecords { UserId = userId, TargetId = targetId, TargetType = targetType, FlowerCount = count, SendMonth = sendMonth, SendTime = monthlyGroup.First().SendTime }; await flowerRecordsRepo.InsertAsync(newMonthlyRecord); } } _logger.LogInformation("[FlowerBatchSync] 处理完成 {Count} 个月度汇总记录,月份: {Month}", monthlyGroups.Count, sendMonth); } // 3. 使用SQL批量更新目标表的花数,避免逐条更新 await UpdateTargetFlowerCountsAsync(toInsert); // 4. 提交事务 uow.Commit(); } catch (Exception ex) { uow.Rollback(); _logger.LogError(ex, "[FlowerBatchSync] 数据库操作失败,已回滚"); throw; } } } catch (Exception ex) { _logger.LogError(ex, "[FlowerBatchSync] 处理数据库操作时发生异常"); throw; } } /// /// 批量更新目标表的花数 /// private async Task UpdateTargetFlowerCountsAsync( List<(long UserId, string TargetType, long TargetId, string SendMonth, DateTime SendTime, long? ReceiverId)> toInsert) { // 3.1 更新帖子花数 var postOperations = toInsert.Where(x => x.TargetType == "Post").ToList(); if (postOperations.Any()) { var postGroups = postOperations .GroupBy(x => x.TargetId) .OrderBy(g => g.Key) // 统一顺序避免死锁 .ToList(); var updateCases = postGroups.Select(g => $"WHEN {g.Key} THEN FlowerCount + {g.Count()}").ToList(); if (updateCases.Any()) { var postIds = string.Join(",", postGroups.Select(g => g.Key)); var sql = $@" UPDATE T_Posts WITH (UPDLOCK, READPAST, ROWLOCK) SET FlowerCount = CASE Id {string.Join(" ", updateCases)} ELSE FlowerCount END WHERE Id IN ({postIds})"; await _fsql.Ado.ExecuteNonQueryAsync(sql); _logger.LogInformation("[FlowerBatchSync] 更新了 {Count} 个帖子的花数", postGroups.Count); } } // 3.2 更新主播花数 var streamerOperations = toInsert.Where(x => x.TargetType == "Streamer").ToList(); if (streamerOperations.Any()) { var streamerGroups = streamerOperations .GroupBy(x => x.TargetId) .OrderBy(g => g.Key) // 统一顺序避免死锁 .ToList(); var updateCases = streamerGroups.Select(g => $"WHEN {g.Key} THEN FlowerCount + {g.Count()}").ToList(); if (updateCases.Any()) { var streamerIds = string.Join(",", streamerGroups.Select(g => g.Key)); var sql = $@" UPDATE T_Streamers WITH (UPDLOCK, READPAST, ROWLOCK) SET FlowerCount = CASE Id {string.Join(" ", updateCases)} ELSE FlowerCount END WHERE Id IN ({streamerIds})"; await _fsql.Ado.ExecuteNonQueryAsync(sql); _logger.LogInformation("[FlowerBatchSync] 更新了 {Count} 个主播的花数", streamerGroups.Count); } } } private bool IsDeadlockException(Exception ex) { return ex.Message.Contains("deadlock") || ex.Message.Contains("deadlocked") || ex.Message.Contains("was chosen as the deadlock victim"); } /// /// 操作信息(用于反序列化) /// private class OperationInfo { public string SendMonth { get; set; } public DateTime OccurredAt { get; set; } public long? ReceiverId { get; set; } } public override Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("[FlowerBatchSync] 正在停止送花批量同步服务..."); return base.StopAsync(cancellationToken); } } }