1. 新增并完善ResultCode枚举,补充标准HTTP状态码对应的业务状态码 2. 重构BusinessException,新增基于ResultCode的构造函数和ThrowIf扩展方法 3. 替换所有硬编码的HTTP状态码为统一的ResultCode枚举 4. 优化全局异常中间件,根据业务状态码映射对应HTTP状态码并规范化JSON响应 5. 修复OssImageHelper和AutoDotCodeConsumer中的OSS文件处理逻辑 6. 新增用户答题快照实体类 7. 清理废弃的宠物模块迁移脚本
298 lines
9.9 KiB
C#
298 lines
9.9 KiB
C#
using Microsoft.Extensions.Logging;
|
||
using QYZH.InteractiveMagazine.IService;
|
||
using QYZH.InteractiveMagazine.Models.Common;
|
||
using QYZH.InteractiveMagazine.Models.Dto;
|
||
using QYZH.InteractiveMagazine.Models.Entity;
|
||
using QYZH.InteractiveMagazine.Models.Enum;
|
||
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,
|
||
Type = m.Type.ToString(),
|
||
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)
|
||
{
|
||
throw new BusinessException("消息ID无效", ResultCode.BAD_REQUEST);
|
||
}
|
||
|
||
var message = await messageRepository.GetByIdAsync(input.MessageId);
|
||
if (message == null)
|
||
{
|
||
throw new BusinessException("消息不存在", ResultCode.NOT_FOUND);
|
||
}
|
||
|
||
// 检查是否已点赞
|
||
var existingLike = await Context.Queryable<CommunityMessageLike>()
|
||
.Where(l => l.UserId == userId && l.MessageId == input.MessageId && l.Type == CommunityMessageLikeTypeEnum.Like)
|
||
.FirstAsync();
|
||
|
||
if (existingLike != null)
|
||
{
|
||
throw new BusinessException("您已点赞过该消息", ResultCode.BAD_REQUEST);
|
||
}
|
||
|
||
// 插入点赞记录
|
||
var like = new CommunityMessageLike
|
||
{
|
||
UserId = userId,
|
||
MessageId = input.MessageId,
|
||
Type = CommunityMessageLikeTypeEnum.Like,
|
||
CreatedBy = "System",
|
||
UpdatedBy = "System",
|
||
CreatedAt = DateTime.Now,
|
||
UpdatedAt = DateTime.Now,
|
||
IsDeleted = false
|
||
};
|
||
|
||
var insertResult = await Context.Insertable(like).ExecuteCommandAsync();
|
||
if (insertResult <= 0)
|
||
{
|
||
throw new BusinessException("点赞失败", ResultCode.GLOBAL_ERROR);
|
||
}
|
||
|
||
// 更新点赞数
|
||
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)
|
||
{
|
||
throw new BusinessException("消息ID无效", ResultCode.BAD_REQUEST);
|
||
}
|
||
|
||
var message = await messageRepository.GetByIdAsync(messageId);
|
||
if (message == null)
|
||
{
|
||
throw new BusinessException("消息不存在", ResultCode.NOT_FOUND);
|
||
}
|
||
|
||
var existingLike = await Context.Queryable<CommunityMessageLike>()
|
||
.Where(l => l.UserId == userId && l.MessageId == messageId && l.Type == CommunityMessageLikeTypeEnum.Like)
|
||
.FirstAsync();
|
||
|
||
if (existingLike == null)
|
||
{
|
||
throw new BusinessException("您尚未点赞过该消息", ResultCode.BAD_REQUEST);
|
||
}
|
||
|
||
// 软删除点赞记录
|
||
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>()
|
||
.Where(uj => uj.UserId == userId && uj.Status == (int)UserJournalStatusEnum.Active)
|
||
.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>()
|
||
.Where(l => l.UserId == userId && messageIds.Contains(l.MessageId) && l.Type == CommunityMessageLikeTypeEnum.Like && !l.IsDeleted)
|
||
.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
|
||
}
|