using FreeSql; using LiveForum.Code.Redis.Contract; using LiveForum.IService.Posts; using LiveForum.Model; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Threading.Tasks; namespace LiveForum.Service.Posts { /// /// 点赞服务实现 /// public class LikeService : ILikeService { private readonly IRedisService _redisService; private readonly IBaseRepository _likesRepository; private readonly ILogger _logger; // Redis键命名规则常量 private const string LIKE_STATUS_KEY_PREFIX = "like:status:"; private const string LIKE_COUNT_KEY_PREFIX = "like_count:"; private const string LIKE_PENDING_OPERATIONS_KEY = "like:pending:operations"; // 缓存过期时间 private static readonly TimeSpan LIKE_STATUS_CACHE_EXPIRATION = TimeSpan.FromHours(1); public LikeService( IRedisService redisService, IBaseRepository likesRepository, ILogger logger) { _redisService = redisService; _likesRepository = likesRepository; _logger = logger; } /// /// 获取点赞状态键 /// private string GetLikeStatusKey(long userId, int targetType, long targetId) { return $"{LIKE_STATUS_KEY_PREFIX}{userId}:{targetType}:{targetId}"; } /// /// 获取点赞计数键 /// private string GetLikeCountKey(int targetType, long targetId) { return $"{LIKE_COUNT_KEY_PREFIX}{targetType}:{targetId}"; } /// /// 获取待处理操作Hash字段名 /// private string GetPendingOperationField(long userId, int targetType, long targetId) { return $"{userId}:{targetType}:{targetId}"; } public async Task IsLikedAsync(long userId, int targetType, long targetId) { try { // 1. 优先从Redis查询 var statusKey = GetLikeStatusKey(userId, targetType, targetId); var exists = await _redisService.SetContainsAsync(statusKey, "1"); if (exists) { return true; } // 2. Redis中没有,查询数据库 var like = await _likesRepository.Select .Where(x => x.UserId == userId && x.TargetType == targetType && x.TargetId == targetId) .FirstAsync(); var isLiked = like != null; // 3. 将结果写入Redis缓存 if (isLiked) { await _redisService.SetAddAsync(statusKey, "1"); await _redisService.SetExpirationAsync(statusKey, LIKE_STATUS_CACHE_EXPIRATION); } return isLiked; } catch (Exception ex) { // Redis异常时降级到数据库查询 _logger.LogWarning(ex, "Redis查询点赞状态失败,降级到数据库查询。UserId: {UserId}, TargetType: {TargetType}, TargetId: {TargetId}", userId, targetType, targetId); try { var like = await _likesRepository.Select .Where(x => x.UserId == userId && x.TargetType == targetType && x.TargetId == targetId) .FirstAsync(); return like != null; } catch (Exception dbEx) { _logger.LogError(dbEx, "数据库查询点赞状态失败。UserId: {UserId}, TargetType: {TargetType}, TargetId: {TargetId}", userId, targetType, targetId); throw; } } } public async Task GetLikeCountAsync(int targetType, long targetId) { try { // 1. 优先从Redis查询 var countKey = GetLikeCountKey(targetType, targetId); var count = await _redisService.StringGetInt64Async(countKey); if (count > 0) { return (int)count; } // 2. Redis中没有或为0,查询数据库 var dbCount = await _likesRepository.Select .Where(x => x.TargetType == targetType && x.TargetId == targetId) .CountAsync(); // 3. 将结果写入Redis缓存(如果计数大于0) if (dbCount > 0) { await _redisService.StringSetAsync(countKey, dbCount); } return (int)dbCount; } catch (Exception ex) { // Redis异常时降级到数据库查询 _logger.LogWarning(ex, "Redis查询点赞数失败,降级到数据库查询。TargetType: {TargetType}, TargetId: {TargetId}", targetType, targetId); try { return (int)await _likesRepository.Select .Where(x => x.TargetType == targetType && x.TargetId == targetId) .CountAsync(); } catch (Exception dbEx) { _logger.LogError(dbEx, "数据库查询点赞数失败。TargetType: {TargetType}, TargetId: {TargetId}", targetType, targetId); throw; } } } public async Task> BatchIsLikedAsync(long userId, int targetType, List targetIds) { if (targetIds == null || !targetIds.Any()) { return new Dictionary(); } var result = new Dictionary(); var needQueryFromDb = new List(); try { // 1. 批量从Redis查询 foreach (var targetId in targetIds) { var statusKey = GetLikeStatusKey(userId, targetType, targetId); var exists = await _redisService.SetContainsAsync(statusKey, "1"); result[targetId] = exists; if (!exists) { needQueryFromDb.Add(targetId); } } // 2. 批量查询数据库中缺失的 if (needQueryFromDb.Any()) { var dbLikes = await _likesRepository.Select .Where(x => x.UserId == userId && x.TargetType == targetType && needQueryFromDb.Contains(x.TargetId)) .ToListAsync(); var likedIds = dbLikes.Select(x => x.TargetId).ToList(); // 3. 更新结果并写入Redis缓存 foreach (var targetId in needQueryFromDb) { var isLiked = likedIds.Contains(targetId); result[targetId] = isLiked; if (isLiked) { var statusKey = GetLikeStatusKey(userId, targetType, targetId); await _redisService.SetAddAsync(statusKey, "1"); await _redisService.SetExpirationAsync(statusKey, LIKE_STATUS_CACHE_EXPIRATION); } } } return result; } catch (Exception ex) { // Redis异常时降级到数据库查询 _logger.LogWarning(ex, "Redis批量查询点赞状态失败,降级到数据库查询。UserId: {UserId}, TargetType: {TargetType}", userId, targetType); try { var dbLikes = await _likesRepository.Select .Where(x => x.UserId == userId && x.TargetType == targetType && targetIds.Contains(x.TargetId)) .ToListAsync(); var likedIds = dbLikes.Select(x => x.TargetId).ToHashSet(); foreach (var targetId in targetIds) { result[targetId] = likedIds.Contains(targetId); } return result; } catch (Exception dbEx) { _logger.LogError(dbEx, "数据库批量查询点赞状态失败。UserId: {UserId}, TargetType: {TargetType}", userId, targetType); throw; } } } public async Task> BatchGetLikeCountAsync(int targetType, List targetIds) { if (targetIds == null || !targetIds.Any()) { return new Dictionary(); } var result = new Dictionary(); var needQueryFromDb = new List(); try { // 1. 批量从Redis查询 foreach (var targetId in targetIds) { var countKey = GetLikeCountKey(targetType, targetId); var count = await _redisService.StringGetInt64Async(countKey); result[targetId] = (int)count; if (count == 0) { needQueryFromDb.Add(targetId); } } // 2. 批量查询数据库中缺失的 if (needQueryFromDb.Any()) { var dbCounts = _likesRepository.Select .Where(x => x.TargetType == targetType && needQueryFromDb.Contains(x.TargetId)) .GroupBy(x => x.TargetId) .Select(g => new { TargetId = g.Key, Count = g.Count() }) .ToList(); var countsDict = dbCounts.ToDictionary(x => x.TargetId, x => x.Count); // 3. 更新结果并写入Redis缓存 foreach (var targetId in needQueryFromDb) { var count = countsDict.ContainsKey(targetId) ? countsDict[targetId] : 0; result[targetId] = count; if (count > 0) { var countKey = GetLikeCountKey(targetType, targetId); await _redisService.StringSetAsync(countKey, count); } } } return result; } catch (Exception ex) { // Redis异常时降级到数据库查询 _logger.LogWarning(ex, "Redis批量查询点赞数失败,降级到数据库查询。TargetType: {TargetType}", targetType); try { var dbCounts = _likesRepository.Select .Where(x => x.TargetType == targetType && targetIds.Contains(x.TargetId)) .GroupBy(x => x.TargetId) .Select(g => new { TargetId = g.Key, Count = g.Count() }) .ToList(); var countsDict = dbCounts.ToDictionary(x => x.TargetId, x => x.Count); foreach (var targetId in targetIds) { result[targetId] = countsDict.ContainsKey(targetId) ? countsDict[targetId] : 0; } return result; } catch (Exception dbEx) { _logger.LogError(dbEx, "数据库批量查询点赞数失败。TargetType: {TargetType}", targetType); throw; } } } public async Task LikeAsync(long userId, int targetType, long targetId, long receiverId) { try { // 1. 更新Redis状态(立即) var statusKey = GetLikeStatusKey(userId, targetType, targetId); await _redisService.SetAddAsync(statusKey, "1"); await _redisService.SetExpirationAsync(statusKey, LIKE_STATUS_CACHE_EXPIRATION); // 2. 更新Redis计数(立即) var countKey = GetLikeCountKey(targetType, targetId); var newCount = await _redisService.IncrementAsync(countKey); // 3. 写入待处理操作Hash(用于定时批量同步到数据库) var pendingField = GetPendingOperationField(userId, targetType, targetId); var operation = new { Action = "INSERT", OccurredAt = DateTime.Now, ReceiverId = receiverId }; var operationJson = JsonSerializer.Serialize(operation); await _redisService.HashSetAsync(LIKE_PENDING_OPERATIONS_KEY, pendingField, operationJson); _logger.LogInformation( "[LikeService] 点赞操作成功。UserId: {UserId}, TargetType: {TargetType}, TargetId: {TargetId}, NewCount: {NewCount}", userId, targetType, targetId, newCount); return true; } catch (Exception ex) { _logger.LogError(ex, "点赞操作失败。UserId: {UserId}, TargetType: {TargetType}, TargetId: {TargetId}", userId, targetType, targetId); throw; } } public async Task UnlikeAsync(long userId, int targetType, long targetId) { try { // 1. 更新Redis状态(立即) var statusKey = GetLikeStatusKey(userId, targetType, targetId); await _redisService.SetRemoveAsync(statusKey, "1"); // 2. 更新Redis计数(立即,但不能小于0) var countKey = GetLikeCountKey(targetType, targetId); var newCount = await _redisService.DecrementAsync(countKey); if (newCount < 0) { // 如果计数小于0,重置为0(可能Redis和数据库不一致) await _redisService.StringSetAsync(countKey, 0); newCount = 0; } // 3. 写入待处理操作Hash(用于定时批量同步到数据库) var pendingField = GetPendingOperationField(userId, targetType, targetId); var operation = new { Action = "DELETE", OccurredAt = DateTime.Now }; var operationJson = JsonSerializer.Serialize(operation); await _redisService.HashSetAsync(LIKE_PENDING_OPERATIONS_KEY, pendingField, operationJson); _logger.LogInformation( "[LikeService] 取消点赞操作成功。UserId: {UserId}, TargetType: {TargetType}, TargetId: {TargetId}, NewCount: {NewCount}", userId, targetType, targetId, newCount); return true; } catch (Exception ex) { _logger.LogError(ex, "取消点赞操作失败。UserId: {UserId}, TargetType: {TargetType}, TargetId: {TargetId}", userId, targetType, targetId); throw; } } } }