using FreeSql; using LiveForum.Code.Redis.Contract; using LiveForum.IService.Others; using LiveForum.Model; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace LiveForum.Service.ScheduledJobs { /// /// 主播送花数量重置任务 /// 每月1号凌晨3点执行,清空所有主播的送花数量,然后从T_FlowerRecords表重新统计当月数据 /// public class StreamerFlowerResetJob : IScheduledJobService { private readonly IBaseRepository _streamersRepository; private readonly IBaseRepository _flowerRecordsRepository; private readonly IRedisService _redisService; private readonly ILogger _logger; private const int BatchSize = 500; // 每批处理500条记录 private const string FLOWER_COUNT_KEY_PREFIX = "flower_count:"; /// /// 构造函数 /// /// 主播仓储 /// 送花记录仓储 /// Redis服务 /// 日志记录器 public StreamerFlowerResetJob( IBaseRepository streamersRepository, IBaseRepository flowerRecordsRepository, IRedisService redisService, ILogger logger) { _streamersRepository = streamersRepository; _flowerRecordsRepository = flowerRecordsRepository; _redisService = redisService; _logger = logger; } /// /// 重置主播送花数量(每月清零并重新统计当月数据) /// /// public async Task ResetStreamerFlowerCountAsync() { var startTime = DateTime.Now; _logger.LogInformation("[定时任务] 开始执行主播送花数量重置任务,开始时间:{StartTime}", startTime); try { // 1. 获取当前月份(yyyy-MM格式) var currentMonth = DateTime.Now.ToString("yyyy-MM"); _logger.LogInformation("[定时任务] 当前月份:{CurrentMonth}", currentMonth); // 2. 将所有主播的FlowerCount清零(批量更新) _logger.LogInformation("[定时任务] 开始将所有主播的送花数量清零..."); var resetCount = await _streamersRepository.UpdateDiy .Set(x => x.FlowerCount == 0) .ExecuteAffrowsAsync(); _logger.LogInformation("[定时任务] 已清零 {ResetCount} 个主播的送花数量", resetCount); // 2.5 清除Redis中所有主播的花数缓存 _logger.LogInformation("[定时任务] 开始清除Redis花数缓存..."); var allStreamers = await _streamersRepository.Select.ToListAsync(); var clearedCount = 0; foreach (var streamer in allStreamers) { var cacheKey = $"{FLOWER_COUNT_KEY_PREFIX}Streamer:{streamer.Id}"; await _redisService.RemoveAsync(cacheKey); clearedCount++; } _logger.LogInformation("[定时任务] 已清除 {ClearedCount} 个主播的Redis花数缓存", clearedCount); // 3. 从T_FlowerRecords表统计当月数据 _logger.LogInformation("[定时任务] 开始统计当月送花记录..."); // 先查询所有符合条件的记录 var flowerRecords = await _flowerRecordsRepository.Select .Where(x => x.SendMonth == currentMonth && x.TargetType == "Streamer") .ToListAsync(); // 在内存中分组并统计 var monthlyStats = flowerRecords .GroupBy(x => x.TargetId) .Select(g => new { TargetId = g.Key, TotalFlowers = g.Sum(x => x.FlowerCount) }) .ToList(); _logger.LogInformation("[定时任务] 统计完成,共有 {Count} 个主播有当月送花记录", monthlyStats.Count); if (!monthlyStats.Any()) { _logger.LogInformation("[定时任务] 当月没有送花记录,任务完成"); return; } // 4. 分批更新主播的FlowerCount var totalCount = monthlyStats.Count; var processedCount = 0; var batchNumber = 0; while (processedCount < totalCount) { batchNumber++; var batch = monthlyStats.Skip(processedCount).Take(BatchSize).ToList(); var batchSize = batch.Count; _logger.LogInformation("[定时任务] 开始处理第 {BatchNumber} 批,本批 {BatchSize} 条记录", batchNumber, batchSize); // 批量更新本批主播的送花数量 foreach (var stat in batch) { await _streamersRepository.UpdateDiy .Set(x => x.FlowerCount == stat.TotalFlowers) .Where(x => x.Id == stat.TargetId) .ExecuteAffrowsAsync(); } processedCount += batchSize; _logger.LogInformation("[定时任务] 第 {BatchNumber} 批处理完成,已处理 {ProcessedCount}/{TotalCount} 条记录", batchNumber, processedCount, totalCount); } var endTime = DateTime.Now; var duration = endTime - startTime; _logger.LogInformation("[定时任务] 主播送花数量重置任务执行完成!总耗时:{Duration}秒,共处理 {TotalCount} 个主播", duration.TotalSeconds, totalCount); } catch (Exception ex) { _logger.LogError(ex, "[定时任务] 主播送花数量重置任务执行失败!"); throw; // 重新抛出异常,让Hangfire记录失败状态 } } } }