diff --git a/QYZH.InteractiveMagazine.IService/ICommunityMessageService.cs b/QYZH.InteractiveMagazine.IService/ICommunityMessageService.cs
new file mode 100644
index 0000000..8a2b896
--- /dev/null
+++ b/QYZH.InteractiveMagazine.IService/ICommunityMessageService.cs
@@ -0,0 +1,44 @@
+using QYZH.InteractiveMagazine.Models.Dto;
+using QYZH.InteractiveMagazine.Models.Entity;
+
+namespace QYZH.InteractiveMagazine.IService;
+
+///
+/// 社区消息后台管理服务接口
+///
+public interface ICommunityMessageService : IBaseService
+{
+ ///
+ /// 分页查询消息列表
+ ///
+ Task> GetListAsync(AdminMessageQueryInput input);
+
+ ///
+ /// 查看消息详情
+ ///
+ Task GetDetailAsync(long id);
+
+ ///
+ /// 软删除消息
+ ///
+ Task DeleteAsync(long id);
+
+ ///
+ /// 冻结/解冻消息
+ ///
+ /// 消息ID
+ /// 状态: 1=解冻(通过), 2=冻结
+ Task FreezeAsync(long id, int status);
+
+ ///
+ /// 设置/取消精选
+ ///
+ /// 消息ID
+ /// 0=取消, 1=精选
+ Task SetFeaturedAsync(long id, int isFeatured);
+
+ ///
+ /// 设置排序权重
+ ///
+ Task SetSortOrderAsync(long id, int sortOrder);
+}
diff --git a/QYZH.InteractiveMagazine.IService/IWeChatCommunityService.cs b/QYZH.InteractiveMagazine.IService/IWeChatCommunityService.cs
new file mode 100644
index 0000000..5d339c7
--- /dev/null
+++ b/QYZH.InteractiveMagazine.IService/IWeChatCommunityService.cs
@@ -0,0 +1,31 @@
+using QYZH.InteractiveMagazine.Models.Dto;
+
+namespace QYZH.InteractiveMagazine.IService;
+
+///
+/// 社区微信端服务接口
+///
+public interface IWeChatCommunityService
+{
+ ///
+ /// 获取社区Feed流(翻页)
+ ///
+ /// 当前用户ID
+ /// 游标(上一页最后一条消息的ID,首次传null)
+ Task GetFeedAsync(long userId, long? cursor);
+
+ ///
+ /// 下拉刷新(返回最新列表)
+ ///
+ Task RefreshFeedAsync(long userId);
+
+ ///
+ /// 点赞
+ ///
+ Task LikeAsync(long userId, WxLikeInput input);
+
+ ///
+ /// 取消点赞
+ ///
+ Task UnlikeAsync(long userId, long messageId);
+}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Bag/UserBagDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Bag/UserBagDto.cs
index d23cdab..3e678fc 100644
--- a/QYZH.InteractiveMagazine.Models/Dto/Bag/UserBagDto.cs
+++ b/QYZH.InteractiveMagazine.Models/Dto/Bag/UserBagDto.cs
@@ -41,8 +41,12 @@ public class BagSkinBrief
{
public long SkinId { get; set; }
public string SkinName { get; set; } = string.Empty;
- public string? SkinImage { get; set; }
public string Rarity { get; set; } = string.Empty;
+
+ ///
+ /// 皮肤预览图(取第一张图片)
+ ///
+ public string? PreviewImage { get; set; }
}
///
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Community/CommunityDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Community/CommunityDto.cs
new file mode 100644
index 0000000..dda8f57
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/Community/CommunityDto.cs
@@ -0,0 +1,288 @@
+namespace QYZH.InteractiveMagazine.Models.Dto;
+
+#region 后台管理 DTO
+
+///
+/// 社区消息分页查询输入(后台)
+///
+public class AdminMessageQueryInput : PageQueryModel
+{
+ ///
+ /// 期刊ID
+ ///
+ public long? JournalId { get; set; }
+
+ ///
+ /// 消息类型: Article, Quote, Announcement
+ ///
+ public string? Type { get; set; }
+
+ ///
+ /// 状态: 0=审核中, 1=已通过, 2=已冻结
+ ///
+ public int? Status { get; set; }
+
+ ///
+ /// 是否精选: 0=否, 1=是
+ ///
+ public int? IsFeatured { get; set; }
+
+ ///
+ /// 是否启用
+ ///
+ public bool? IsActive { get; set; }
+
+ ///
+ /// 内容关键词(模糊查询)
+ ///
+ public string? Keyword { get; set; }
+
+ ///
+ /// 用户ID
+ ///
+ public long? UserId { get; set; }
+}
+
+///
+/// 社区消息详情输出(后台)
+///
+public class AdminMessageDetailOutput
+{
+ ///
+ /// 主键ID
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// 期刊ID
+ ///
+ public long JournalId { get; set; }
+
+ ///
+ /// 用户ID
+ ///
+ public long UserId { get; set; }
+
+ ///
+ /// 用户实例期刊ID
+ ///
+ public long UserJournalId { get; set; }
+
+ ///
+ /// 期刊任务ID
+ ///
+ public long JournalTaskId { get; set; }
+
+ ///
+ /// 用户期刊任务回答ID
+ ///
+ public long JournalTaskAnswerId { get; set; }
+
+ ///
+ /// 消息内容
+ ///
+ public string Content { get; set; } = string.Empty;
+
+ ///
+ /// 配图
+ ///
+ public string? ImageUrl { get; set; }
+
+ ///
+ /// 排序权重
+ ///
+ public int SortOrder { get; set; }
+
+ ///
+ /// 是否启用
+ ///
+ public bool IsActive { get; set; }
+
+ ///
+ /// 消息类型
+ ///
+ public string Type { get; set; } = string.Empty;
+
+ ///
+ /// 点赞数
+ ///
+ public int LikeCount { get; set; }
+
+ ///
+ /// 是否精选
+ ///
+ public int IsFeatured { get; set; }
+
+ ///
+ /// 状态: 0=审核中, 1=已通过, 2=已冻结
+ ///
+ public int Status { get; set; }
+
+ ///
+ /// 创建人
+ ///
+ public string? CreatedBy { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreatedAt { get; set; }
+
+ ///
+ /// 更新人
+ ///
+ public string? UpdatedBy { get; set; }
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime? UpdatedAt { get; set; }
+}
+
+///
+/// 设置精选入参
+///
+public class AdminSetFeaturedInput
+{
+ ///
+ /// 是否精选: 0=否, 1=是
+ ///
+ public int IsFeatured { get; set; }
+}
+
+///
+/// 设置排序权重入参
+///
+public class AdminSetSortOrderInput
+{
+ ///
+ /// 排序权重
+ ///
+ public int SortOrder { get; set; }
+}
+
+///
+/// 冻结/解冻入参
+///
+public class AdminFreezeInput
+{
+ ///
+ /// 状态: 1=解冻(通过), 2=冻结
+ ///
+ public int Status { get; set; }
+}
+
+#endregion
+
+#region 微信端 DTO
+
+///
+/// 微信端社区Feed消息输出
+///
+public class WxFeedMessageOutput
+{
+ ///
+ /// 消息ID
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// 期刊ID
+ ///
+ public long JournalId { get; set; }
+
+ ///
+ /// 作者用户ID
+ ///
+ public long UserId { get; set; }
+
+ ///
+ /// 消息内容
+ ///
+ public string Content { get; set; } = string.Empty;
+
+ ///
+ /// 配图
+ ///
+ public string? ImageUrl { get; set; }
+
+ ///
+ /// 消息类型
+ ///
+ public string Type { get; set; } = string.Empty;
+
+ ///
+ /// 点赞数
+ ///
+ public int LikeCount { get; set; }
+
+ ///
+ /// 是否精选
+ ///
+ public int IsFeatured { get; set; }
+
+ ///
+ /// 当前用户是否已点赞
+ ///
+ public bool IsLiked { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreatedAt { get; set; }
+}
+
+///
+/// 微信端社区Feed输出(含游标)
+///
+public class WxFeedOutput
+{
+ ///
+ /// 消息列表
+ ///
+ public List Messages { get; set; } = new();
+
+ ///
+ /// 下一页游标(为null表示没有更多数据)
+ ///
+ public long? NextCursor { get; set; }
+
+ ///
+ /// 是否还有更多数据
+ ///
+ public bool HasMore { get; set; }
+}
+
+///
+/// 微信端点赞入参
+///
+public class WxLikeInput
+{
+ ///
+ /// 消息ID
+ ///
+ public long MessageId { get; set; }
+}
+
+///
+/// 微信端点赞输出
+///
+public class WxLikeOutput
+{
+ ///
+ /// 消息ID
+ ///
+ public long MessageId { get; set; }
+
+ ///
+ /// 当前点赞数
+ ///
+ public int LikeCount { get; set; }
+
+ ///
+ /// 是否已点赞
+ ///
+ public bool IsLiked { get; set; }
+}
+
+#endregion
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Mall/WxMallDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Mall/WxMallDto.cs
index c5cb70f..92f6c9f 100644
--- a/QYZH.InteractiveMagazine.Models/Dto/Mall/WxMallDto.cs
+++ b/QYZH.InteractiveMagazine.Models/Dto/Mall/WxMallDto.cs
@@ -30,9 +30,13 @@ public class PetSkinBrief
{
public long SkinId { get; set; }
public string SkinName { get; set; } = string.Empty;
- public string? SkinImage { get; set; }
public string? Description { get; set; }
public string Rarity { get; set; } = string.Empty;
+
+ ///
+ /// 皮肤预览图(取当前进化阶段第一张图片)
+ ///
+ public string? PreviewImage { get; set; }
}
///
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Pet/PetDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Pet/PetDto.cs
index d82dd3e..a935ab0 100644
--- a/QYZH.InteractiveMagazine.Models/Dto/Pet/PetDto.cs
+++ b/QYZH.InteractiveMagazine.Models/Dto/Pet/PetDto.cs
@@ -23,7 +23,12 @@ public class PetOutput
///
/// 当前进化形态Id
///
- public int CurrentEvolutionId { get; set; }
+ public long CurrentEvolutionId { get; set; }
+
+ ///
+ /// 当前进化阶段名称
+ ///
+ public string? EvolutionStageName { get; set; }
///
/// 当前成长值
@@ -35,6 +40,21 @@ public class PetOutput
///
public int FeedingCount { get; set; }
+ ///
+ /// 当前皮肤Id
+ ///
+ public long CurrentSkinId { get; set; }
+
+ ///
+ /// 当前皮肤名称
+ ///
+ public string? CurrentSkinName { get; set; }
+
+ ///
+ /// 当前形态+当前皮肤 的图片列表(按SortOrder排序)
+ ///
+ public List CurrentImages { get; set; } = new();
+
///
/// 宠物类型: Normal
///
@@ -51,6 +71,27 @@ public class PetOutput
public DateTime CreatedAt { get; set; }
}
+///
+/// 皮肤图片输出
+///
+public class SkinImageOutput
+{
+ ///
+ /// 图片Id
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// 图片地址
+ ///
+ public string ImageUrl { get; set; } = string.Empty;
+
+ ///
+ /// 图片顺序
+ ///
+ public int SortOrder { get; set; }
+}
+
///
/// 喂养宠物输入
///
diff --git a/QYZH.InteractiveMagazine.Models/Entity/Pet.cs b/QYZH.InteractiveMagazine.Models/Entity/Pet.cs
index 55a8881..8beb35c 100644
--- a/QYZH.InteractiveMagazine.Models/Entity/Pet.cs
+++ b/QYZH.InteractiveMagazine.Models/Entity/Pet.cs
@@ -8,64 +8,85 @@ namespace QYZH.InteractiveMagazine.Models.Entity
[SugarTable("Pet")]
public partial class Pet : SqlSugarBaseEntity
{
- public Pet(){
+ public Pet()
+ {
- }
+ }
///
/// Desc:用户Id
/// Default:
/// Nullable:False
///
- public long UserId {get;set;}
+ public long UserId { get; set; }
- ///
- /// Desc:宠物昵称
- /// Default:
- /// Nullable:True
- ///
- public string Name {get;set;}
+ ///
+ /// Desc:宠物昵称
+ /// Default:
+ /// Nullable:True
+ ///
+ public string Name { get; set; }
- ///
- /// Desc:当前进化形态Id
- /// Default:
- /// Nullable:False
- ///
- public int CurrentEvolutionId {get;set;}
+ ///
+ /// Desc:当前进化形态Id
+ /// Default:
+ /// Nullable:False
+ ///
+ public long CurrentEvolutionId { get; set; }
- ///
- /// Desc:当前成长值
- /// Default:0
- /// Nullable:False
- ///
- public int GrowthPoints {get;set;}
+ ///
+ /// Desc:当前成长值
+ /// Default:0
+ /// Nullable:False
+ ///
+ public int GrowthPoints { get; set; }
- ///
- /// Desc:累计喂养次数
- /// Default:0
- /// Nullable:False
- ///
- public int FeedingCount {get;set;}
+ ///
+ /// Desc:累计喂养次数
+ /// Default:0
+ /// Nullable:False
+ ///
+ public int FeedingCount { get; set; }
- ///
- /// Desc:当前皮肤Id(0为默认皮肤)
- /// Default:0
- /// Nullable:False
- ///
- public long CurrentSkinId {get;set;}
+ ///
+ /// Desc:当前皮肤Id(0为默认皮肤)
+ /// Default:0
+ /// Nullable:False
+ ///
+ public long CurrentSkinId { get; set; }
- ///
- /// Desc:宠物类型
- /// Default:Normal
- /// Nullable:False
- ///
- public string Type {get;set;}
+ ///
+ /// Desc:宠物类型
+ /// Default:Normal
+ /// Nullable:False
+ ///
+ public string Type { get; set; }
- ///
- /// Desc:状态: Inactive, Active, Sleeping
- /// Default:Inactive
- /// Nullable:False
- ///
- public string Status {get;set;}
+ ///
+ /// Desc:状态: Inactive, Active, Sleeping
+ /// Default:Inactive
+ /// Nullable:False
+ ///
+ public new string Status { get; set; }
+
+ ///
+ /// 理解力
+ ///
+ public int Comprehension { get; set; }
+
+ ///
+ /// 判断力
+ ///
+ public int Judgment { get; set; }
+
+ ///
+ /// 表达力
+ ///
+ public int Expression { get; set; }
+
+ ///
+ /// 说服力
+ ///
+ public int Persuasiveness { get; set; }
}
}
diff --git a/QYZH.InteractiveMagazine.Models/Entity/PetEvolution.cs b/QYZH.InteractiveMagazine.Models/Entity/PetEvolution.cs
index be6fd5d..3873010 100644
--- a/QYZH.InteractiveMagazine.Models/Entity/PetEvolution.cs
+++ b/QYZH.InteractiveMagazine.Models/Entity/PetEvolution.cs
@@ -8,86 +8,76 @@ namespace QYZH.InteractiveMagazine.Models.Entity
[SugarTable("PetEvolution")]
public partial class PetEvolution : SqlSugarBaseEntity
{
- public PetEvolution(){
+ public PetEvolution() { }
+ ///
+ /// Desc:阶段名称
+ /// Default:
+ /// Nullable:False
+ ///
+ public string StageName { get; set; }
- }
+ ///
+ /// Desc:阶段等级
+ /// Default:
+ /// Nullable:False
+ ///
+ public int StageLevel { get; set; }
- ///
- /// Desc:阶段名称
- /// Default:
- /// Nullable:False
- ///
- public string StageName {get;set;}
+ ///
+ /// Desc:进化所需成长值
+ /// Default:
+ /// Nullable:False
+ ///
+ public int RequiredGrowth { get; set; }
- ///
- /// Desc:阶段等级
- /// Default:
- /// Nullable:False
- ///
- public int StageLevel {get;set;}
+ ///
+ /// Desc:前一形态Id(null表示初始形态)
+ /// Default:
+ /// Nullable:True
+ ///
+ public long? PreviousEvolutionId { get; set; }
- ///
- /// Desc:进化所需成长值
- /// Default:
- /// Nullable:False
- ///
- public int RequiredGrowth {get;set;}
+ ///
+ /// Desc:基础力量
+ /// Default:0
+ /// Nullable:False
+ ///
+ public int BaseStrength { get; set; }
- ///
- /// Desc:形态图片
- /// Default:
- /// Nullable:True
- ///
- public string ImageUrl {get;set;}
+ ///
+ /// Desc:基础敏捷
+ /// Default:0
+ /// Nullable:False
+ ///
+ public int BaseAgility { get; set; }
- ///
- /// Desc:前一形态Id
- /// Default:
- /// Nullable:True
- ///
- public int? PreviousEvolutionId {get;set;}
+ ///
+ /// Desc:基础智力
+ /// Default:0
+ /// Nullable:False
+ ///
+ public int BaseIntelligence { get; set; }
- ///
- /// Desc:基础力量
- /// Default:0
- /// Nullable:False
- ///
- public int BaseStrength {get;set;}
+ ///
+ /// Desc:基础魅力
+ /// Default:0
+ /// Nullable:False
+ ///
+ public int BaseCharm { get; set; }
- ///
- /// Desc:基础敏捷
- /// Default:0
- /// Nullable:False
- ///
- public int BaseAgility {get;set;}
+ ///
+ /// Desc:进化类型: Normal, Special
+ /// Default:Normal
+ /// Nullable:False
+ ///
+ public string Type { get; set; }
- ///
- /// Desc:基础智力
- /// Default:0
- /// Nullable:False
- ///
- public int BaseIntelligence {get;set;}
-
- ///
- /// Desc:基础魅力
- /// Default:0
- /// Nullable:False
- ///
- public int BaseCharm {get;set;}
-
- ///
- /// Desc:进化类型: Normal, Special
- /// Default:Normal
- /// Nullable:False
- ///
- public string Type {get;set;}
-
- ///
- /// Desc:状态: Active, Inactive
- /// Default:Active
- /// Nullable:False
- ///
- public string Status {get;set;}
+ ///
+ /// Desc:状态: Active, Inactive
+ /// Default:Active
+ /// Nullable:False
+ ///
+ public new string Status { get; set; }
}
}
diff --git a/QYZH.InteractiveMagazine.Models/Entity/PetSkin.cs b/QYZH.InteractiveMagazine.Models/Entity/PetSkin.cs
index 9450908..bcbea9a 100644
--- a/QYZH.InteractiveMagazine.Models/Entity/PetSkin.cs
+++ b/QYZH.InteractiveMagazine.Models/Entity/PetSkin.cs
@@ -17,13 +17,6 @@ namespace QYZH.InteractiveMagazine.Models.Entity
///
public string Name { get; set; }
- ///
- /// Desc:皮肤图片
- /// Default:
- /// Nullable:True
- ///
- public string ImageUrl { get; set; }
-
///
/// Desc:描述/特效说明
/// Default:
@@ -31,13 +24,6 @@ namespace QYZH.InteractiveMagazine.Models.Entity
///
public string Description { get; set; }
- ///
- /// Desc:关联商品Id(用于兑换)
- /// Default:
- /// Nullable:False
- ///
- public long ProductId { get; set; }
-
///
/// Desc:稀有度: Normal, Rare, Epic, Legendary
/// Default:Normal
@@ -51,5 +37,12 @@ namespace QYZH.InteractiveMagazine.Models.Entity
/// Nullable:False
///
public int SortOrder { get; set; }
+
+ ///
+ /// Desc:皮肤类型: Normal, Limited, Event
+ /// Default:Normal
+ /// Nullable:False
+ ///
+ public string Type { get; set; }
}
}
diff --git a/QYZH.InteractiveMagazine.Models/Entity/PetSkinImage.cs b/QYZH.InteractiveMagazine.Models/Entity/PetSkinImage.cs
new file mode 100644
index 0000000..e5d9714
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Entity/PetSkinImage.cs
@@ -0,0 +1,48 @@
+using SqlSugar;
+
+namespace QYZH.InteractiveMagazine.Models.Entity
+{
+ ///
+ /// 宠物皮肤图片表(每个皮肤在每个进化阶段下有一组有序图片,用于动态表示)
+ ///
+ [SugarTable("PetSkinImage")]
+ public partial class PetSkinImage : SqlSugarBaseEntity
+ {
+ public PetSkinImage() { }
+
+ ///
+ /// Desc:皮肤Id,关联PetSkin.Id(0表示默认皮肤的图片)
+ /// Default:
+ /// Nullable:False
+ ///
+ public long SkinId { get; set; }
+
+ ///
+ /// Desc:进化阶段Id,关联PetEvolution.Id
+ /// Default:
+ /// Nullable:False
+ ///
+ public long EvolutionStageId { get; set; }
+
+ ///
+ /// Desc:图片地址
+ /// Default:
+ /// Nullable:False
+ ///
+ public string ImageUrl { get; set; }
+
+ ///
+ /// Desc:图片顺序(动画帧序号)
+ /// Default:0
+ /// Nullable:False
+ ///
+ public int SortOrder { get; set; }
+
+ ///
+ /// Desc:图片类型: Normal, Special
+ /// Default:Normal
+ /// Nullable:False
+ ///
+ public string Type { get; set; }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Service/CommunityMessageService.cs b/QYZH.InteractiveMagazine.Service/CommunityMessageService.cs
new file mode 100644
index 0000000..591eb97
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Service/CommunityMessageService.cs
@@ -0,0 +1,219 @@
+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.Repository;
+using SqlSugar;
+
+namespace QYZH.InteractiveMagazine.Service;
+
+///
+/// 社区消息后台管理服务实现
+///
+public class CommunityMessageService(BaseRepository messageRepository, ILogger logger)
+ : BaseRepository, ICommunityMessageService
+{
+
+ ///
+ /// 分页查询消息列表
+ ///
+ public async Task> GetListAsync(AdminMessageQueryInput input)
+ {
+ logger.LogInformation("正在查询社区消息列表,页码: {PageIndex}, 每页条数: {PageSize}", input.PageIndex, input.PageSize);
+
+ if (input.PageIndex <= 0) throw new BusinessException("页码必须大于0", 400);
+ if (input.PageSize <= 0 || input.PageSize > 100) throw new BusinessException("每页条数必须在1-100之间", 400);
+
+ RefAsync totalNumber = 0;
+ var pageResult = await messageRepository.Queryable()
+ .WhereIF(input.JournalId.HasValue, m => m.JournalId == input.JournalId.Value)
+ .WhereIF(!string.IsNullOrWhiteSpace(input.Type), m => m.Type == input.Type)
+ .WhereIF(input.Status.HasValue, m => m.Status == input.Status.Value)
+ .WhereIF(input.IsFeatured.HasValue, m => m.IsFeatured == input.IsFeatured.Value)
+ .WhereIF(input.IsActive.HasValue, m => m.IsActive == input.IsActive.Value)
+ .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), m => m.Content.Contains(input.Keyword))
+ .WhereIF(input.UserId.HasValue, m => m.UserId == input.UserId.Value)
+ .OrderByDescending(m => m.IsFeatured)
+ .OrderByDescending(m => m.CreatedAt)
+ .Select(m => new AdminMessageDetailOutput
+ {
+ Id = m.Id,
+ JournalId = m.JournalId,
+ UserId = m.UserId,
+ UserJournalId = m.UserJournalId,
+ JournalTaskId = m.JournalTaskId,
+ JournalTaskAnswerId = m.JournalTaskAnswerId,
+ Content = m.Content,
+ ImageUrl = m.ImageUrl,
+ SortOrder = m.SortOrder,
+ IsActive = m.IsActive,
+ Type = m.Type,
+ LikeCount = m.LikeCount,
+ IsFeatured = m.IsFeatured,
+ Status = m.Status,
+ CreatedBy = m.CreatedBy,
+ CreatedAt = m.CreatedAt,
+ UpdatedBy = m.UpdatedBy,
+ UpdatedAt = m.UpdatedAt
+ }, true)
+ .ToPageListAsync(input.PageIndex, input.PageSize, totalNumber);
+
+ return new PageListModel(pageResult, input.PageIndex, input.PageSize, totalNumber);
+ }
+
+ ///
+ /// 查看消息详情
+ ///
+ public async Task GetDetailAsync(long id)
+ {
+ logger.LogInformation("正在获取社区消息详情,ID: {Id}", id);
+
+ var message = await messageRepository.GetByIdAsync(id);
+ if (message == null)
+ {
+ logger.LogWarning("未找到社区消息,ID: {Id}", id);
+ throw new BusinessException("消息不存在", 404);
+ }
+
+ return new AdminMessageDetailOutput
+ {
+ Id = message.Id,
+ JournalId = message.JournalId,
+ UserId = message.UserId,
+ UserJournalId = message.UserJournalId,
+ JournalTaskId = message.JournalTaskId,
+ JournalTaskAnswerId = message.JournalTaskAnswerId,
+ Content = message.Content,
+ ImageUrl = message.ImageUrl,
+ SortOrder = message.SortOrder,
+ IsActive = message.IsActive,
+ Type = message.Type,
+ LikeCount = message.LikeCount,
+ IsFeatured = message.IsFeatured,
+ Status = message.Status,
+ CreatedBy = message.CreatedBy,
+ CreatedAt = message.CreatedAt,
+ UpdatedBy = message.UpdatedBy,
+ UpdatedAt = message.UpdatedAt
+ };
+ }
+
+ ///
+ /// 软删除消息
+ ///
+ public async Task DeleteAsync(long id)
+ {
+ logger.LogInformation("正在删除社区消息,ID: {Id}", id);
+
+ var message = await messageRepository.GetByIdAsync(id);
+ if (message == null)
+ {
+ logger.LogWarning("未找到要删除的社区消息,ID: {Id}", id);
+ throw new BusinessException("消息不存在", 404);
+ }
+
+ var result = await messageRepository.DeleteByIdAsync(id);
+ if (!result)
+ {
+ logger.LogError("社区消息删除失败,ID: {Id}", id);
+ throw new BusinessException("删除消息失败", 500);
+ }
+
+ logger.LogInformation("社区消息删除成功,ID: {Id}", id);
+ }
+
+ ///
+ /// 冻结/解冻消息
+ ///
+ public async Task FreezeAsync(long id, int status)
+ {
+ logger.LogInformation("正在更新社区消息冻结状态,ID: {Id}, Status: {Status}", id, status);
+
+ if (status != 1 && status != 2)
+ {
+ throw new BusinessException("状态值无效,只能为1(解冻/通过)或2(冻结)", 400);
+ }
+
+ var message = await messageRepository.GetByIdAsync(id);
+ if (message == null)
+ {
+ logger.LogWarning("未找到社区消息,ID: {Id}", id);
+ throw new BusinessException("消息不存在", 404);
+ }
+
+ message.Status = status;
+ message.UpdatedBy = "System";
+ message.UpdatedAt = DateTime.Now;
+
+ var result = await messageRepository.UpdateAsync(message);
+ if (!result)
+ {
+ logger.LogError("社区消息冻结状态更新失败,ID: {Id}", id);
+ throw new BusinessException("更新冻结状态失败", 500);
+ }
+
+ logger.LogInformation("社区消息冻结状态更新成功,ID: {Id}, Status: {Status}", id, status);
+ }
+
+ ///
+ /// 设置/取消精选
+ ///
+ public async Task SetFeaturedAsync(long id, int isFeatured)
+ {
+ logger.LogInformation("正在设置社区消息精选状态,ID: {Id}, IsFeatured: {IsFeatured}", id, isFeatured);
+
+ if (isFeatured != 0 && isFeatured != 1)
+ {
+ throw new BusinessException("精选值无效,只能为0(取消)或1(精选)", 400);
+ }
+
+ var message = await messageRepository.GetByIdAsync(id);
+ if (message == null)
+ {
+ logger.LogWarning("未找到社区消息,ID: {Id}", id);
+ throw new BusinessException("消息不存在", 404);
+ }
+
+ message.IsFeatured = isFeatured;
+ message.UpdatedBy = "System";
+ message.UpdatedAt = DateTime.Now;
+
+ var result = await messageRepository.UpdateAsync(message);
+ if (!result)
+ {
+ logger.LogError("社区消息精选设置失败,ID: {Id}", id);
+ throw new BusinessException("设置精选失败", 500);
+ }
+
+ logger.LogInformation("社区消息精选设置成功,ID: {Id}, IsFeatured: {IsFeatured}", id, isFeatured);
+ }
+
+ ///
+ /// 设置排序权重
+ ///
+ public async Task SetSortOrderAsync(long id, int sortOrder)
+ {
+ logger.LogInformation("正在设置社区消息排序权重,ID: {Id}, SortOrder: {SortOrder}", id, sortOrder);
+
+ var message = await messageRepository.GetByIdAsync(id);
+ if (message == null)
+ {
+ logger.LogWarning("未找到社区消息,ID: {Id}", id);
+ throw new BusinessException("消息不存在", 404);
+ }
+
+ message.SortOrder = sortOrder;
+ message.UpdatedBy = "System";
+ message.UpdatedAt = DateTime.Now;
+
+ var result = await messageRepository.UpdateAsync(message);
+ if (!result)
+ {
+ logger.LogError("社区消息排序权重设置失败,ID: {Id}", id);
+ throw new BusinessException("设置排序权重失败", 500);
+ }
+
+ logger.LogInformation("社区消息排序权重设置成功,ID: {Id}, SortOrder: {SortOrder}", id, sortOrder);
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Service/MedalService.cs b/QYZH.InteractiveMagazine.Service/MedalService.cs
index f1074d6..782bbf9 100644
--- a/QYZH.InteractiveMagazine.Service/MedalService.cs
+++ b/QYZH.InteractiveMagazine.Service/MedalService.cs
@@ -1,3 +1,4 @@
+using System.Text.Json;
using Microsoft.Extensions.Logging;
using QYZH.InteractiveMagazine.IService;
using QYZH.InteractiveMagazine.Models.Common;
@@ -380,6 +381,14 @@ public class MedalService(BaseRepository medalRepository, ILogger()
.Where(um => um.UserId == userId && um.MedalId == (int)input.MedalId && um.Status == "Awarded")
.FirstAsync();
@@ -498,5 +507,142 @@ public class MedalService(BaseRepository medalRepository, ILogger
+ /// 校验勋章规则是否满足
+ ///
+ /// 勋章ID
+ /// 用户ID
+ /// (是否通过, 失败原因)
+ private async Task<(bool passed, string? failReason)> ValidateMedalRulesAsync(long medalId, long userId)
+ {
+ var rules = await Context.Queryable()
+ .Where(r => r.MedalId == medalId && !r.IsDeleted)
+ .OrderBy(r => r.RuleOrder)
+ .ToListAsync();
+
+ if (!rules.Any())
+ {
+ return (true, null);
+ }
+
+ var failReasons = new List();
+
+ foreach (var rule in rules)
+ {
+ var rulePassed = await EvaluateRuleAsync(rule, userId);
+
+ if (!rulePassed)
+ {
+ var reason = !string.IsNullOrWhiteSpace(rule.Description)
+ ? rule.Description
+ : $"未满足条件: {rule.TargetField} {rule.Operator} {rule.ThresholdValue}";
+ failReasons.Add(reason);
+ }
+
+ var logicOp = rule.LogicOperator?.ToUpper() ?? "AND";
+
+ if (logicOp == "AND" && !rulePassed)
+ {
+ return (false, $"未满足获得条件:{string.Join(";", failReasons)}");
+ }
+
+ if (logicOp == "OR" && rulePassed)
+ {
+ return (true, null);
+ }
+ }
+
+ // AND 模式下所有规则都通过才到这里
+ // OR 模式下所有规则都没通过才到这里
+ var firstLogicOp = rules.First().LogicOperator?.ToUpper() ?? "AND";
+ if (firstLogicOp == "AND")
+ {
+ return (true, null);
+ }
+ else
+ {
+ return (false, $"未满足获得条件:{string.Join(";", failReasons)}");
+ }
+ }
+
+ ///
+ /// 评估单条规则是否满足
+ ///
+ /// 规则配置
+ /// 用户ID
+ /// 规则是否满足
+ private async Task EvaluateRuleAsync(MedalRule rule, long userId)
+ {
+ try
+ {
+ var sql = $"SELECT COUNT(1) FROM [{rule.TargetTable}] WHERE [{rule.TargetField}] {rule.Operator} @ThresholdValue AND IsDeleted = 0 AND UserId = @UserId";
+ var parameters = new List
+ {
+ new SugarParameter("@ThresholdValue", rule.ThresholdValue),
+ new SugarParameter("@UserId", userId)
+ };
+
+ // 处理额外筛选条件
+ if (!string.IsNullOrWhiteSpace(rule.FilterCondition))
+ {
+ var filters = JsonSerializer.Deserialize>(rule.FilterCondition);
+ if (filters != null)
+ {
+ var index = 0;
+ foreach (var kv in filters)
+ {
+ var paramName = $"@Filter_{index}";
+ sql += $" AND [{kv.Key}] = {paramName}";
+ var paramValue = kv.Value.ValueKind switch
+ {
+ JsonValueKind.Number => kv.Value.GetInt32() as object,
+ JsonValueKind.String => kv.Value.GetString() as object,
+ JsonValueKind.True => true as object,
+ JsonValueKind.False => false as object,
+ _ => kv.Value.GetRawText()
+ };
+ parameters.Add(new SugarParameter(paramName, paramValue!));
+ index++;
+ }
+ }
+ }
+
+ // 处理时间范围
+ var dateRange = GetDateRange(rule);
+ if (dateRange.HasValue)
+ {
+ sql += " AND CreatedAt >= @DateStart AND CreatedAt <= @DateEnd";
+ parameters.Add(new SugarParameter("@DateStart", dateRange.Value.start));
+ parameters.Add(new SugarParameter("@DateEnd", dateRange.Value.end));
+ }
+
+ var count = await Context.Ado.GetIntAsync(sql, parameters.ToArray());
+ return count > 0;
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "规则评估异常,规则ID: {RuleId}, 目标表: {Table}, 目标字段: {Field}",
+ rule.Id, rule.TargetTable, rule.TargetField);
+ return false;
+ }
+ }
+
+ ///
+ /// 根据规则的时间范围类型计算起止时间
+ ///
+ private static (DateTime start, DateTime end)? GetDateRange(MedalRule rule)
+ {
+ var now = DateTime.Now;
+
+ return rule.DateRangeType?.ToLower() switch
+ {
+ "currentmonth" => (new DateTime(now.Year, now.Month, 1), now),
+ "currentweek" => (now.AddDays(-(int)now.DayOfWeek), now),
+ "last7days" => (now.AddDays(-7), now),
+ "custom" when rule.DateRangeStart.HasValue && rule.DateRangeEnd.HasValue => (rule.DateRangeStart.Value, rule.DateRangeEnd.Value),
+ _ => null
+ };
+ }
+
#endregion
}
diff --git a/QYZH.InteractiveMagazine.Service/PetService.cs b/QYZH.InteractiveMagazine.Service/PetService.cs
index aebd326..0e3d1bc 100644
--- a/QYZH.InteractiveMagazine.Service/PetService.cs
+++ b/QYZH.InteractiveMagazine.Service/PetService.cs
@@ -16,11 +16,13 @@ public class PetService(
BaseRepository petRepository,
BaseRepository feedingRecordRepository,
BaseRepository petEvolutionRepository,
+ BaseRepository petSkinImageRepository,
+ BaseRepository petSkinRepository,
ILogger logger)
: BaseRepository, IPetService
{
///
- /// 获取用户宠物信息
+ /// 获取用户宠物信息(含当前形态名称、皮肤图片序列)
///
public async Task GetPetByUserIdAsync(long userId)
{
@@ -28,21 +30,56 @@ public class PetService(
var pet = await petRepository.Queryable()
.Where(p => p.UserId == userId)
- .Select(p => new PetOutput
- {
- Id = p.Id,
- UserId = p.UserId,
- Name = p.Name,
- CurrentEvolutionId = p.CurrentEvolutionId,
- GrowthPoints = p.GrowthPoints,
- FeedingCount = p.FeedingCount,
- Type = p.Type,
- Status = p.Status,
- CreatedAt = p.CreatedAt
- })
.FirstAsync();
- return pet;
+ if (pet == null) return null;
+
+ // 查询当前进化阶段名称
+ var evolution = await petEvolutionRepository.Queryable()
+ .Where(e => e.Id == pet.CurrentEvolutionId)
+ .FirstAsync();
+
+ // 查询当前皮肤名称
+ string? skinName = null;
+ if (pet.CurrentSkinId > 0)
+ {
+ var skin = await petSkinRepository.Queryable()
+ .Where(s => s.Id == pet.CurrentSkinId && !s.IsDeleted)
+ .FirstAsync();
+ skinName = skin?.Name;
+ }
+
+ // 查询当前形态+当前皮肤 的图片序列
+ var skinId = pet.CurrentSkinId; // 0 = 默认皮肤
+ var images = await petSkinImageRepository.Queryable()
+ .Where(i => i.SkinId == skinId
+ && i.EvolutionStageId == pet.CurrentEvolutionId
+ && !i.IsDeleted)
+ .OrderBy(i => i.SortOrder)
+ .Select(i => new SkinImageOutput
+ {
+ Id = i.Id,
+ ImageUrl = i.ImageUrl,
+ SortOrder = i.SortOrder
+ })
+ .ToListAsync();
+
+ return new PetOutput
+ {
+ Id = pet.Id,
+ UserId = pet.UserId,
+ Name = pet.Name,
+ CurrentEvolutionId = pet.CurrentEvolutionId,
+ EvolutionStageName = evolution?.StageName,
+ GrowthPoints = pet.GrowthPoints,
+ FeedingCount = pet.FeedingCount,
+ CurrentSkinId = pet.CurrentSkinId,
+ CurrentSkinName = skinName,
+ CurrentImages = images,
+ Type = pet.Type,
+ Status = pet.Status,
+ CreatedAt = pet.CreatedAt
+ };
}
///
@@ -61,13 +98,20 @@ public class PetService(
return;
}
+ // 查询初始进化形态(PreviousEvolutionId 为 null 的即为初始形态)
+ var initialEvolution = await petEvolutionRepository.Queryable()
+ .Where(e => e.PreviousEvolutionId == null && e.Status == "Active")
+ .OrderBy(e => e.StageLevel)
+ .FirstAsync();
+
var pet = new Pet
{
UserId = userId,
Name = "小精灵",
- CurrentEvolutionId = 1,
+ CurrentEvolutionId = initialEvolution?.Id ?? 0,
GrowthPoints = 0,
FeedingCount = 0,
+ CurrentSkinId = 0,
Type = "Normal",
Status = "Inactive",
IsDeleted = false,
@@ -84,7 +128,8 @@ public class PetService(
throw new Exception("创建宠物失败");
}
- logger.LogInformation("用户默认宠物创建成功,UserId: {UserId}, PetId: {PetId}", userId, pet.Id);
+ logger.LogInformation("用户默认宠物创建成功,UserId: {UserId}, PetId: {PetId}, EvolutionId: {EvolutionId}",
+ userId, pet.Id, pet.CurrentEvolutionId);
}
///
@@ -185,7 +230,7 @@ public class PetService(
throw new BusinessException("更新宠物成长值失败", 500);
}
- // 进化检查:查找下一阶段进化形态
+ // 进化检查:查找下一阶段进化形态(PreviousEvolutionId 类型为 long?)
var nextEvolution = await petEvolutionRepository.Queryable()
.Where(e => e.PreviousEvolutionId == pet.CurrentEvolutionId
&& e.RequiredGrowth <= growthAfter
diff --git a/QYZH.InteractiveMagazine.Service/WeChatCommunityService.cs b/QYZH.InteractiveMagazine.Service/WeChatCommunityService.cs
new file mode 100644
index 0000000..333c96d
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Service/WeChatCommunityService.cs
@@ -0,0 +1,296 @@
+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.Repository;
+using SqlSugar;
+
+namespace QYZH.InteractiveMagazine.Service;
+
+///
+/// 社区微信端服务实现
+///
+public class WeChatCommunityService(
+ BaseRepository messageRepository,
+ ILogger logger)
+ : BaseRepository, IWeChatCommunityService
+{
+ private const int FeaturedCount = 5;
+ private const int NormalCount = 5;
+
+ ///
+ /// 获取社区Feed流(翻页)
+ ///
+ public async Task 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(), 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,
+ 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
+ };
+ }
+
+ ///
+ /// 下拉刷新(重置游标,返回最新列表)
+ ///
+ public async Task RefreshFeedAsync(long userId)
+ {
+ logger.LogInformation("正在刷新社区Feed,用户ID: {UserId}", userId);
+ return await GetFeedAsync(userId, null);
+ }
+
+ ///
+ /// 点赞
+ ///
+ public async Task LikeAsync(long userId, WxLikeInput input)
+ {
+ logger.LogInformation("用户正在点赞,用户ID: {UserId}, 消息ID: {MessageId}", userId, input.MessageId);
+
+ if (input.MessageId <= 0)
+ {
+ throw new BusinessException("消息ID无效", 400);
+ }
+
+ var message = await messageRepository.GetByIdAsync(input.MessageId);
+ if (message == null)
+ {
+ throw new BusinessException("消息不存在", 404);
+ }
+
+ // 检查是否已点赞
+ var existingLike = await Context.Queryable()
+ .Where(l => l.UserId == userId && l.MessageId == input.MessageId && l.Type == "Like")
+ .FirstAsync();
+
+ if (existingLike != null)
+ {
+ throw new BusinessException("您已点赞过该消息", 400);
+ }
+
+ // 插入点赞记录
+ var like = new CommunityMessageLike
+ {
+ UserId = userId,
+ MessageId = input.MessageId,
+ Type = "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("点赞失败", 500);
+ }
+
+ // 更新点赞数
+ await Context.Updateable()
+ .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
+ };
+ }
+
+ ///
+ /// 取消点赞
+ ///
+ public async Task UnlikeAsync(long userId, long messageId)
+ {
+ logger.LogInformation("用户正在取消点赞,用户ID: {UserId}, 消息ID: {MessageId}", userId, messageId);
+
+ if (messageId <= 0)
+ {
+ throw new BusinessException("消息ID无效", 400);
+ }
+
+ var message = await messageRepository.GetByIdAsync(messageId);
+ if (message == null)
+ {
+ throw new BusinessException("消息不存在", 404);
+ }
+
+ var existingLike = await Context.Queryable()
+ .Where(l => l.UserId == userId && l.MessageId == messageId && l.Type == "Like")
+ .FirstAsync();
+
+ if (existingLike == null)
+ {
+ throw new BusinessException("您尚未点赞过该消息", 400);
+ }
+
+ // 软删除点赞记录
+ await Context.Updateable()
+ .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()
+ .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 私有辅助方法
+
+ ///
+ /// 获取用户拥有的期刊ID列表
+ ///
+ private async Task> GetUserJournalIds(long userId)
+ {
+ return await Context.Queryable()
+ .Where(uj => uj.UserId == userId && uj.Status == "Active")
+ .Select(uj => uj.JournalId)
+ .ToListAsync();
+ }
+
+ ///
+ /// 获取当前用户已点赞的消息ID集合
+ ///
+ private async Task> GetLikedMessageIds(long userId, List messageIds)
+ {
+ if (!messageIds.Any()) return new HashSet();
+
+ var likes = await Context.Queryable()
+ .Where(l => l.UserId == userId && messageIds.Contains(l.MessageId) && l.Type == "Like" && !l.IsDeleted)
+ .Select(l => l.MessageId)
+ .ToListAsync();
+
+ return likes.ToHashSet();
+ }
+
+ ///
+ /// 混排精选消息和普通消息(按权重随机插入)
+ ///
+ private static List MixMessages(List featured, List normal)
+ {
+ var result = new List();
+ 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
+}
diff --git a/QYZH.InteractiveMagazine.Service/WxMallService.cs b/QYZH.InteractiveMagazine.Service/WxMallService.cs
index 8ace22b..e7db82c 100644
--- a/QYZH.InteractiveMagazine.Service/WxMallService.cs
+++ b/QYZH.InteractiveMagazine.Service/WxMallService.cs
@@ -5,6 +5,7 @@ using QYZH.InteractiveMagazine.Models.Dto.Bag;
using QYZH.InteractiveMagazine.Models.Dto.Mall;
using QYZH.InteractiveMagazine.Models.Entity;
using QYZH.InteractiveMagazine.Repository;
+using System.Text.Json;
namespace QYZH.InteractiveMagazine.Service;
@@ -17,6 +18,24 @@ public class WxMallService(
ILogger logger)
: BaseRepository, IWxMallService
{
+ ///
+ /// 从 MetaData JSON 中提取 SkinId
+ ///
+ private static long GetSkinIdFromMetaData(string? metaData)
+ {
+ if (string.IsNullOrWhiteSpace(metaData)) return 0;
+ try
+ {
+ using var doc = JsonDocument.Parse(metaData);
+ if (doc.RootElement.TryGetProperty("SkinId", out var skinIdElement))
+ return skinIdElement.GetInt64();
+ if (doc.RootElement.TryGetProperty("skinId", out var skinIdLower))
+ return skinIdLower.GetInt64();
+ }
+ catch { }
+ return 0;
+ }
+
///
/// 获取商城商品列表(仅上架 + 在售)
///
@@ -29,15 +48,36 @@ public class WxMallService(
var products = await query.ToListAsync();
- // 批量查询皮肤信息
- var skinProductIds = products.Where(p => p.Type == "PetBg").Select(p => p.Id).ToList();
- var skins = skinProductIds.Count > 0
+ // 批量提取 PetBg 商品的 SkinId
+ var skinProductMap = products
+ .Where(p => p.Type == "PetBg")
+ .Select(p => new { ProductId = p.Id, SkinId = GetSkinIdFromMetaData(p.MetaData) })
+ .Where(x => x.SkinId > 0)
+ .ToList();
+
+ var skinIds = skinProductMap.Select(x => x.SkinId).Distinct().ToList();
+
+ // 批量查询皮肤
+ var skins = skinIds.Count > 0
? await exchangeRecordRepository.Context.Queryable()
- .Where(s => skinProductIds.Contains(s.ProductId) && !s.IsDeleted)
+ .Where(s => skinIds.Contains(s.Id) && !s.IsDeleted)
.ToListAsync()
: new List();
+ // 批量查询皮肤预览图(取每个皮肤的第一张图)
+ var allPreviewImages = skinIds.Count > 0
+ ? await exchangeRecordRepository.Context.Queryable()
+ .Where(i => skinIds.Contains(i.SkinId) && !i.IsDeleted)
+ .OrderBy(i => i.SortOrder)
+ .ToListAsync()
+ : new List();
+
+ var previewImageMap = allPreviewImages
+ .GroupBy(i => i.SkinId)
+ .ToDictionary(g => g.Key, g => g.First().ImageUrl);
+
// 批量查询用户背包(判断已拥有)
+ var skinProductIds = skinProductMap.Select(x => x.ProductId).ToList();
var bagItems = skinProductIds.Count > 0
? await exchangeRecordRepository.Context.Queryable()
.Where(b => b.UserId == userId && !b.IsDeleted && b.Status == "Available"
@@ -47,8 +87,10 @@ public class WxMallService(
return products.Select(p =>
{
- var skin = skins.FirstOrDefault(s => s.ProductId == p.Id);
+ var skinMapping = skinProductMap.FirstOrDefault(x => x.ProductId == p.Id);
+ var skin = skinMapping != null ? skins.FirstOrDefault(s => s.Id == skinMapping.SkinId) : null;
var owned = bagItems.Any(b => b.ItemId == p.Id);
+ var previewImg = skin != null && previewImageMap.ContainsKey(skin.Id) ? previewImageMap[skin.Id] : null;
return new WxProductOutput
{
@@ -63,9 +105,9 @@ public class WxMallService(
{
SkinId = skin.Id,
SkinName = skin.Name,
- SkinImage = skin.ImageUrl,
Description = skin.Description,
- Rarity = skin.Rarity
+ Rarity = skin.Rarity,
+ PreviewImage = previewImg
} : null
};
}).ToList();
@@ -85,20 +127,30 @@ public class WxMallService(
PetSkinBrief? skinBrief = null;
if (product.Type == "PetBg")
{
- var skin = await exchangeRecordRepository.Context.Queryable()
- .Where(s => s.ProductId == product.Id && !s.IsDeleted)
- .FirstAsync();
-
- if (skin != null)
+ var skinId = GetSkinIdFromMetaData(product.MetaData);
+ if (skinId > 0)
{
- skinBrief = new PetSkinBrief
+ var skin = await exchangeRecordRepository.Context.Queryable()
+ .Where(s => s.Id == skinId && !s.IsDeleted)
+ .FirstAsync();
+
+ if (skin != null)
{
- SkinId = skin.Id,
- SkinName = skin.Name,
- SkinImage = skin.ImageUrl,
- Description = skin.Description,
- Rarity = skin.Rarity
- };
+ var previewImg = await exchangeRecordRepository.Context.Queryable()
+ .Where(i => i.SkinId == skinId && !i.IsDeleted)
+ .OrderBy(i => i.SortOrder)
+ .Select(i => i.ImageUrl)
+ .FirstAsync();
+
+ skinBrief = new PetSkinBrief
+ {
+ SkinId = skin.Id,
+ SkinName = skin.Name,
+ Description = skin.Description,
+ Rarity = skin.Rarity,
+ PreviewImage = previewImg
+ };
+ }
}
}
@@ -298,18 +350,40 @@ public class WxMallService(
.Where(p => itemIds.Contains(p.Id) && !p.IsDeleted)
.ToListAsync();
- // 批量查询关联皮肤(PetBg 类型)
- var skinProductIds = bagItems.Where(b => b.ItemType == "PetBg").Select(b => b.ItemId).Distinct().ToList();
- var skins = skinProductIds.Count > 0
+ // 批量提取 PetBg 背包物品的 SkinId(从 MetaData 解析)
+ var skinIdMap = bagItems
+ .Where(b => b.ItemType == "PetBg")
+ .Select(b => new { BagId = b.Id, SkinId = GetSkinIdFromMetaData(b.MetaData) })
+ .Where(x => x.SkinId > 0)
+ .ToList();
+
+ var skinIds = skinIdMap.Select(x => x.SkinId).Distinct().ToList();
+
+ // 批量查询皮肤
+ var skins = skinIds.Count > 0
? await exchangeRecordRepository.Context.Queryable()
- .Where(s => skinProductIds.Contains(s.ProductId) && !s.IsDeleted)
+ .Where(s => skinIds.Contains(s.Id) && !s.IsDeleted)
.ToListAsync()
: new List();
+ // 批量查询皮肤预览图
+ var allBagPreviewImages = skinIds.Count > 0
+ ? await exchangeRecordRepository.Context.Queryable()
+ .Where(i => skinIds.Contains(i.SkinId) && !i.IsDeleted)
+ .OrderBy(i => i.SortOrder)
+ .ToListAsync()
+ : new List();
+
+ var bagPreviewImageMap = allBagPreviewImages
+ .GroupBy(i => i.SkinId)
+ .ToDictionary(g => g.Key, g => g.First().ImageUrl);
+
return bagItems.Select(b =>
{
var product = products.FirstOrDefault(p => p.Id == b.ItemId);
- var skin = skins.FirstOrDefault(s => s.ProductId == b.ItemId);
+ var skinMapping = skinIdMap.FirstOrDefault(x => x.BagId == b.Id);
+ var skin = skinMapping != null ? skins.FirstOrDefault(s => s.Id == skinMapping.SkinId) : null;
+ var previewImg = skin != null && bagPreviewImageMap.ContainsKey(skin.Id) ? bagPreviewImageMap[skin.Id] : null;
return new UserBagOutput
{
@@ -329,8 +403,8 @@ public class WxMallService(
{
SkinId = skin.Id,
SkinName = skin.Name,
- SkinImage = skin.ImageUrl,
- Rarity = skin.Rarity
+ Rarity = skin.Rarity,
+ PreviewImage = previewImg
} : null
};
}).ToList();
@@ -449,13 +523,15 @@ public class WxMallService(
if (skin == null)
throw new BusinessException("皮肤不存在", 404);
- // 校验背包中是否拥有该皮肤(通过关联商品Id判断)
+ // 校验背包中是否拥有该皮肤(通过 MetaData 中的 SkinId 判断)
var hasSkin = await exchangeRecordRepository.Context.Queryable()
- .Where(b => b.UserId == userId && b.ItemId == skin.ProductId
- && !b.IsDeleted && b.Status == "Available" && b.Quantity > 0)
- .AnyAsync();
+ .Where(b => b.UserId == userId && !b.IsDeleted && b.Status == "Available" && b.Quantity > 0
+ && b.ItemType == "PetBg")
+ .ToListAsync();
- if (!hasSkin)
+ var owned = hasSkin.Any(b => GetSkinIdFromMetaData(b.MetaData) == input.SkinId);
+
+ if (!owned)
throw new BusinessException("您尚未拥有该皮肤,请先兑换", 400);
// 换肤
diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/CommunityMessageController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/CommunityMessageController.cs
new file mode 100644
index 0000000..a3fabc4
--- /dev/null
+++ b/QYZH.InteractiveMagazine.WebApi/Controllers/CommunityMessageController.cs
@@ -0,0 +1,163 @@
+using Microsoft.AspNetCore.Mvc;
+using QYZH.InteractiveMagazine.IService;
+using QYZH.InteractiveMagazine.Models.Common;
+using QYZH.InteractiveMagazine.Models.Dto;
+using QYZH.InteractiveMagazine.Models.Enum;
+
+namespace QYZH.InteractiveMagazine.WebApi.Controllers;
+
+///
+/// 社区消息管理控制器
+///
+[Route("api/[controller]")]
+[ApiController]
+[ApiExplorerSettings(GroupName = nameof(ApiVersionEnum.Platform))]
+public class CommunityMessageController : BaseController
+{
+ private readonly ICommunityMessageService _communityMessageService;
+ private readonly ILogger _logger;
+
+ public CommunityMessageController(ICommunityMessageService communityMessageService, ILogger logger)
+ {
+ _communityMessageService = communityMessageService;
+ _logger = logger;
+ }
+
+ ///
+ /// 分页查询社区消息列表
+ ///
+ [HttpPost("list")]
+ public async Task>> GetListAsync([FromBody] AdminMessageQueryInput input)
+ {
+ try
+ {
+ var result = await _communityMessageService.GetListAsync(input);
+ return Success(result);
+ }
+ catch (BusinessException ex)
+ {
+ _logger.LogWarning(ex, "查询社区消息列表业务异常: {Message}", ex.Message);
+ return BaseResponse>.Fail(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "查询社区消息列表系统异常,参数:{Input}", input);
+ return BaseResponse>.Fail("查询社区消息列表失败,请稍后重试");
+ }
+ }
+
+ ///
+ /// 查看消息详情
+ ///
+ [HttpGet("{id}")]
+ public async Task> GetDetailAsync(long id)
+ {
+ try
+ {
+ var result = await _communityMessageService.GetDetailAsync(id);
+ return Success(result);
+ }
+ catch (BusinessException ex)
+ {
+ _logger.LogWarning(ex, "获取社区消息详情业务异常: {Message}", ex.Message);
+ return BaseResponse.Fail(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "获取社区消息详情系统异常,ID:{Id}", id);
+ return BaseResponse.Fail("获取消息详情失败,请稍后重试");
+ }
+ }
+
+ ///
+ /// 删除消息
+ ///
+ [HttpDelete("{id}")]
+ public async Task> DeleteAsync(long id)
+ {
+ try
+ {
+ await _communityMessageService.DeleteAsync(id);
+ return Success(new object(), "删除消息成功");
+ }
+ catch (BusinessException ex)
+ {
+ _logger.LogWarning(ex, "删除社区消息业务异常: {Message}", ex.Message);
+ return BaseResponse