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;
}
}
}
}