2026-06-05 17:15:30 +08:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
using QYZH.InteractiveMagazine.IService;
|
|
|
|
|
|
using QYZH.InteractiveMagazine.Models.Common;
|
|
|
|
|
|
using QYZH.InteractiveMagazine.Models.Dto;
|
|
|
|
|
|
using QYZH.InteractiveMagazine.Models.Entity;
|
2026-06-08 16:57:44 +08:00
|
|
|
|
using QYZH.InteractiveMagazine.Models.Enum;
|
2026-06-05 17:15:30 +08:00
|
|
|
|
using QYZH.InteractiveMagazine.Repository;
|
|
|
|
|
|
using SqlSugar;
|
|
|
|
|
|
|
|
|
|
|
|
namespace QYZH.InteractiveMagazine.Service;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 社区微信端服务实现
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class WeChatCommunityService(
|
|
|
|
|
|
BaseRepository<CommunityMessage> messageRepository,
|
|
|
|
|
|
ILogger<WeChatCommunityService> logger)
|
|
|
|
|
|
: BaseRepository<CommunityMessage>, IWeChatCommunityService
|
|
|
|
|
|
{
|
|
|
|
|
|
private const int FeaturedCount = 5;
|
|
|
|
|
|
private const int NormalCount = 5;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取社区Feed流(翻页)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task<WxFeedOutput> GetFeedAsync(long userId, long? cursor)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("正在获取社区Feed,用户ID: {UserId}, Cursor: {Cursor}", userId, cursor);
|
|
|
|
|
|
|
|
|
|
|
|
var journalIds = await GetUserJournalIds(userId);
|
|
|
|
|
|
if (!journalIds.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
return new WxFeedOutput { Messages = new List<WxFeedMessageOutput>(), HasMore = false };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查询精选消息
|
|
|
|
|
|
var featuredQuery = messageRepository.Queryable()
|
|
|
|
|
|
.Where(m => journalIds.Contains(m.JournalId)
|
|
|
|
|
|
&& m.Status == 1
|
|
|
|
|
|
&& m.IsActive
|
|
|
|
|
|
&& m.UserId != userId
|
|
|
|
|
|
&& m.IsFeatured == 1);
|
|
|
|
|
|
|
|
|
|
|
|
// 查询普通消息
|
|
|
|
|
|
var normalQuery = messageRepository.Queryable()
|
|
|
|
|
|
.Where(m => journalIds.Contains(m.JournalId)
|
|
|
|
|
|
&& m.Status == 1
|
|
|
|
|
|
&& m.IsActive
|
|
|
|
|
|
&& m.UserId != userId
|
|
|
|
|
|
&& m.IsFeatured == 0);
|
|
|
|
|
|
|
|
|
|
|
|
// 游标分页:按ID降序(ID越大越新)
|
|
|
|
|
|
if (cursor.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
featuredQuery = featuredQuery.Where(m => m.Id < cursor.Value);
|
|
|
|
|
|
normalQuery = normalQuery.Where(m => m.Id < cursor.Value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var featured = await featuredQuery
|
|
|
|
|
|
.OrderByDescending(m => m.SortOrder)
|
|
|
|
|
|
.Take(FeaturedCount)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var normal = await normalQuery
|
|
|
|
|
|
.OrderByDescending(m => m.SortOrder)
|
|
|
|
|
|
.Take(NormalCount)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
// 混排
|
|
|
|
|
|
var mixed = MixMessages(featured, normal);
|
|
|
|
|
|
|
|
|
|
|
|
// 查询当前用户点赞状态
|
|
|
|
|
|
var messageIds = mixed.Select(m => m.Id).ToList();
|
|
|
|
|
|
var likedIds = await GetLikedMessageIds(userId, messageIds);
|
|
|
|
|
|
|
|
|
|
|
|
var output = mixed.Select(m => new WxFeedMessageOutput
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = m.Id,
|
|
|
|
|
|
JournalId = m.JournalId,
|
|
|
|
|
|
UserId = m.UserId,
|
|
|
|
|
|
Content = m.Content,
|
|
|
|
|
|
ImageUrl = m.ImageUrl,
|
2026-06-08 16:57:44 +08:00
|
|
|
|
Type = m.Type.ToString(),
|
2026-06-05 17:15:30 +08:00
|
|
|
|
LikeCount = m.LikeCount,
|
|
|
|
|
|
IsFeatured = m.IsFeatured,
|
|
|
|
|
|
IsLiked = likedIds.Contains(m.Id),
|
|
|
|
|
|
CreatedAt = m.CreatedAt
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
var nextCursor = mixed.Any() ? mixed.Min(m => m.Id) : (long?)null;
|
|
|
|
|
|
var hasMore = featured.Count >= FeaturedCount || normal.Count >= NormalCount;
|
|
|
|
|
|
|
|
|
|
|
|
return new WxFeedOutput
|
|
|
|
|
|
{
|
|
|
|
|
|
Messages = output,
|
|
|
|
|
|
NextCursor = hasMore ? nextCursor : null,
|
|
|
|
|
|
HasMore = hasMore
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 下拉刷新(重置游标,返回最新列表)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task<WxFeedOutput> RefreshFeedAsync(long userId)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("正在刷新社区Feed,用户ID: {UserId}", userId);
|
|
|
|
|
|
return await GetFeedAsync(userId, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 点赞
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task<WxLikeOutput> LikeAsync(long userId, WxLikeInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("用户正在点赞,用户ID: {UserId}, 消息ID: {MessageId}", userId, input.MessageId);
|
|
|
|
|
|
|
|
|
|
|
|
if (input.MessageId <= 0)
|
|
|
|
|
|
{
|
2026-06-29 16:34:26 +08:00
|
|
|
|
throw new BusinessException("消息ID无效", ResultCode.BAD_REQUEST);
|
2026-06-05 17:15:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var message = await messageRepository.GetByIdAsync(input.MessageId);
|
|
|
|
|
|
if (message == null)
|
|
|
|
|
|
{
|
2026-06-29 16:34:26 +08:00
|
|
|
|
throw new BusinessException("消息不存在", ResultCode.NOT_FOUND);
|
2026-06-05 17:15:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否已点赞
|
|
|
|
|
|
var existingLike = await Context.Queryable<CommunityMessageLike>()
|
2026-06-08 16:57:44 +08:00
|
|
|
|
.Where(l => l.UserId == userId && l.MessageId == input.MessageId && l.Type == CommunityMessageLikeTypeEnum.Like)
|
2026-06-05 17:15:30 +08:00
|
|
|
|
.FirstAsync();
|
|
|
|
|
|
|
|
|
|
|
|
if (existingLike != null)
|
|
|
|
|
|
{
|
2026-06-29 16:34:26 +08:00
|
|
|
|
throw new BusinessException("您已点赞过该消息", ResultCode.BAD_REQUEST);
|
2026-06-05 17:15:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 插入点赞记录
|
|
|
|
|
|
var like = new CommunityMessageLike
|
|
|
|
|
|
{
|
|
|
|
|
|
UserId = userId,
|
|
|
|
|
|
MessageId = input.MessageId,
|
2026-06-08 16:57:44 +08:00
|
|
|
|
Type = CommunityMessageLikeTypeEnum.Like,
|
2026-06-05 17:15:30 +08:00
|
|
|
|
CreatedBy = "System",
|
|
|
|
|
|
UpdatedBy = "System",
|
|
|
|
|
|
CreatedAt = DateTime.Now,
|
|
|
|
|
|
UpdatedAt = DateTime.Now,
|
|
|
|
|
|
IsDeleted = false
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var insertResult = await Context.Insertable(like).ExecuteCommandAsync();
|
|
|
|
|
|
if (insertResult <= 0)
|
|
|
|
|
|
{
|
2026-06-29 16:34:26 +08:00
|
|
|
|
throw new BusinessException("点赞失败", ResultCode.GLOBAL_ERROR);
|
2026-06-05 17:15:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新点赞数
|
|
|
|
|
|
await Context.Updateable<CommunityMessage>()
|
|
|
|
|
|
.SetColumns(m => new CommunityMessage
|
|
|
|
|
|
{
|
|
|
|
|
|
LikeCount = m.LikeCount + 1,
|
|
|
|
|
|
UpdatedAt = DateTime.Now
|
|
|
|
|
|
})
|
|
|
|
|
|
.Where(m => m.Id == input.MessageId)
|
|
|
|
|
|
.ExecuteCommandAsync();
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("点赞成功,用户ID: {UserId}, 消息ID: {MessageId}", userId, input.MessageId);
|
|
|
|
|
|
|
|
|
|
|
|
return new WxLikeOutput
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageId = input.MessageId,
|
|
|
|
|
|
LikeCount = message.LikeCount + 1,
|
|
|
|
|
|
IsLiked = true
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 取消点赞
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task<WxLikeOutput> UnlikeAsync(long userId, long messageId)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("用户正在取消点赞,用户ID: {UserId}, 消息ID: {MessageId}", userId, messageId);
|
|
|
|
|
|
|
|
|
|
|
|
if (messageId <= 0)
|
|
|
|
|
|
{
|
2026-06-29 16:34:26 +08:00
|
|
|
|
throw new BusinessException("消息ID无效", ResultCode.BAD_REQUEST);
|
2026-06-05 17:15:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var message = await messageRepository.GetByIdAsync(messageId);
|
|
|
|
|
|
if (message == null)
|
|
|
|
|
|
{
|
2026-06-29 16:34:26 +08:00
|
|
|
|
throw new BusinessException("消息不存在", ResultCode.NOT_FOUND);
|
2026-06-05 17:15:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var existingLike = await Context.Queryable<CommunityMessageLike>()
|
2026-06-08 16:57:44 +08:00
|
|
|
|
.Where(l => l.UserId == userId && l.MessageId == messageId && l.Type == CommunityMessageLikeTypeEnum.Like)
|
2026-06-05 17:15:30 +08:00
|
|
|
|
.FirstAsync();
|
|
|
|
|
|
|
|
|
|
|
|
if (existingLike == null)
|
|
|
|
|
|
{
|
2026-06-29 16:34:26 +08:00
|
|
|
|
throw new BusinessException("您尚未点赞过该消息", ResultCode.BAD_REQUEST);
|
2026-06-05 17:15:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 软删除点赞记录
|
|
|
|
|
|
await Context.Updateable<CommunityMessageLike>()
|
|
|
|
|
|
.SetColumns(l => new CommunityMessageLike { IsDeleted = true, UpdatedAt = DateTime.Now })
|
|
|
|
|
|
.Where(l => l.Id == existingLike.Id)
|
|
|
|
|
|
.ExecuteCommandAsync();
|
|
|
|
|
|
|
|
|
|
|
|
// 更新点赞数(不小于0)
|
|
|
|
|
|
var newLikeCount = Math.Max(0, message.LikeCount - 1);
|
|
|
|
|
|
await Context.Updateable<CommunityMessage>()
|
|
|
|
|
|
.SetColumns(m => new CommunityMessage
|
|
|
|
|
|
{
|
|
|
|
|
|
LikeCount = newLikeCount,
|
|
|
|
|
|
UpdatedAt = DateTime.Now
|
|
|
|
|
|
})
|
|
|
|
|
|
.Where(m => m.Id == messageId)
|
|
|
|
|
|
.ExecuteCommandAsync();
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("取消点赞成功,用户ID: {UserId}, 消息ID: {MessageId}", userId, messageId);
|
|
|
|
|
|
|
|
|
|
|
|
return new WxLikeOutput
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageId = messageId,
|
|
|
|
|
|
LikeCount = newLikeCount,
|
|
|
|
|
|
IsLiked = false
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region 私有辅助方法
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取用户拥有的期刊ID列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task<List<long>> GetUserJournalIds(long userId)
|
|
|
|
|
|
{
|
|
|
|
|
|
return await Context.Queryable<UserJournal>()
|
2026-06-08 17:54:36 +08:00
|
|
|
|
.Where(uj => uj.UserId == userId && uj.Status == (int)UserJournalStatusEnum.Active)
|
2026-06-05 17:15:30 +08:00
|
|
|
|
.Select(uj => uj.JournalId)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取当前用户已点赞的消息ID集合
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task<HashSet<long>> GetLikedMessageIds(long userId, List<long> messageIds)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!messageIds.Any()) return new HashSet<long>();
|
|
|
|
|
|
|
|
|
|
|
|
var likes = await Context.Queryable<CommunityMessageLike>()
|
2026-06-08 16:57:44 +08:00
|
|
|
|
.Where(l => l.UserId == userId && messageIds.Contains(l.MessageId) && l.Type == CommunityMessageLikeTypeEnum.Like && !l.IsDeleted)
|
2026-06-05 17:15:30 +08:00
|
|
|
|
.Select(l => l.MessageId)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
return likes.ToHashSet();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 混排精选消息和普通消息(按权重随机插入)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static List<CommunityMessage> MixMessages(List<CommunityMessage> featured, List<CommunityMessage> normal)
|
|
|
|
|
|
{
|
|
|
|
|
|
var result = new List<CommunityMessage>();
|
|
|
|
|
|
var allMessages = new List<(CommunityMessage msg, bool isFeatured)>();
|
|
|
|
|
|
|
|
|
|
|
|
allMessages.AddRange(featured.Select(m => (m, true)));
|
|
|
|
|
|
allMessages.AddRange(normal.Select(m => (m, false)));
|
|
|
|
|
|
|
|
|
|
|
|
// 按SortOrder加权随机排序:SortOrder越大出现越靠前
|
|
|
|
|
|
var random = new Random();
|
|
|
|
|
|
while (allMessages.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
var totalWeight = allMessages.Sum(x => Math.Max(1, x.msg.SortOrder));
|
|
|
|
|
|
var pick = random.Next(0, totalWeight + 1);
|
|
|
|
|
|
var cumulative = 0;
|
|
|
|
|
|
var selectedIndex = 0;
|
|
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < allMessages.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
cumulative += Math.Max(1, allMessages[i].msg.SortOrder);
|
|
|
|
|
|
if (pick <= cumulative)
|
|
|
|
|
|
{
|
|
|
|
|
|
selectedIndex = i;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
result.Add(allMessages[selectedIndex].msg);
|
|
|
|
|
|
allMessages.RemoveAt(selectedIndex);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|