feat: 完成宠物模块重构与社区功能开发
本次提交包含多项核心更新:
1. 重构宠物模块数据结构:拆分皮肤图片为独立表,优化Pet、PetEvolution实体,调整字段类型与冗余字段
2. 新增宠物皮肤图片管理表,支持多进化阶段多图片展示
3. 完善宠物DTO,新增当前形态名称、皮肤信息与图片序列返回
4. 新增社区功能模块:
- 微信端社区Feed流、点赞/取消点赞接口
- 后台社区消息管理接口与服务实现
5. 优化商城与背包模块,替换皮肤图片获取逻辑为从新表读取预览图
6. 重构勋章服务,新增规则校验逻辑
7. 调整命名规范,修复原有控制器命名问题
This commit is contained in:
@ -0,0 +1,44 @@
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Entity;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.IService;
|
||||
|
||||
/// <summary>
|
||||
/// 社区消息后台管理服务接口
|
||||
/// </summary>
|
||||
public interface ICommunityMessageService : IBaseService<CommunityMessage>
|
||||
{
|
||||
/// <summary>
|
||||
/// 分页查询消息列表
|
||||
/// </summary>
|
||||
Task<PageListModel<AdminMessageDetailOutput>> GetListAsync(AdminMessageQueryInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 查看消息详情
|
||||
/// </summary>
|
||||
Task<AdminMessageDetailOutput> GetDetailAsync(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 软删除消息
|
||||
/// </summary>
|
||||
Task DeleteAsync(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 冻结/解冻消息
|
||||
/// </summary>
|
||||
/// <param name="id">消息ID</param>
|
||||
/// <param name="status">状态: 1=解冻(通过), 2=冻结</param>
|
||||
Task FreezeAsync(long id, int status);
|
||||
|
||||
/// <summary>
|
||||
/// 设置/取消精选
|
||||
/// </summary>
|
||||
/// <param name="id">消息ID</param>
|
||||
/// <param name="isFeatured">0=取消, 1=精选</param>
|
||||
Task SetFeaturedAsync(long id, int isFeatured);
|
||||
|
||||
/// <summary>
|
||||
/// 设置排序权重
|
||||
/// </summary>
|
||||
Task SetSortOrderAsync(long id, int sortOrder);
|
||||
}
|
||||
31
QYZH.InteractiveMagazine.IService/IWeChatCommunityService.cs
Normal file
31
QYZH.InteractiveMagazine.IService/IWeChatCommunityService.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.IService;
|
||||
|
||||
/// <summary>
|
||||
/// 社区微信端服务接口
|
||||
/// </summary>
|
||||
public interface IWeChatCommunityService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取社区Feed流(翻页)
|
||||
/// </summary>
|
||||
/// <param name="userId">当前用户ID</param>
|
||||
/// <param name="cursor">游标(上一页最后一条消息的ID,首次传null)</param>
|
||||
Task<WxFeedOutput> GetFeedAsync(long userId, long? cursor);
|
||||
|
||||
/// <summary>
|
||||
/// 下拉刷新(返回最新列表)
|
||||
/// </summary>
|
||||
Task<WxFeedOutput> RefreshFeedAsync(long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 点赞
|
||||
/// </summary>
|
||||
Task<WxLikeOutput> LikeAsync(long userId, WxLikeInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 取消点赞
|
||||
/// </summary>
|
||||
Task<WxLikeOutput> UnlikeAsync(long userId, long messageId);
|
||||
}
|
||||
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 皮肤预览图(取第一张图片)
|
||||
/// </summary>
|
||||
public string? PreviewImage { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
288
QYZH.InteractiveMagazine.Models/Dto/Community/CommunityDto.cs
Normal file
288
QYZH.InteractiveMagazine.Models/Dto/Community/CommunityDto.cs
Normal file
@ -0,0 +1,288 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto;
|
||||
|
||||
#region 后台管理 DTO
|
||||
|
||||
/// <summary>
|
||||
/// 社区消息分页查询输入(后台)
|
||||
/// </summary>
|
||||
public class AdminMessageQueryInput : PageQueryModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 期刊ID
|
||||
/// </summary>
|
||||
public long? JournalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息类型: Article, Quote, Announcement
|
||||
/// </summary>
|
||||
public string? Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态: 0=审核中, 1=已通过, 2=已冻结
|
||||
/// </summary>
|
||||
public int? Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否精选: 0=否, 1=是
|
||||
/// </summary>
|
||||
public int? IsFeatured { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用
|
||||
/// </summary>
|
||||
public bool? IsActive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 内容关键词(模糊查询)
|
||||
/// </summary>
|
||||
public string? Keyword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户ID
|
||||
/// </summary>
|
||||
public long? UserId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 社区消息详情输出(后台)
|
||||
/// </summary>
|
||||
public class AdminMessageDetailOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 主键ID
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 期刊ID
|
||||
/// </summary>
|
||||
public long JournalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户ID
|
||||
/// </summary>
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户实例期刊ID
|
||||
/// </summary>
|
||||
public long UserJournalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 期刊任务ID
|
||||
/// </summary>
|
||||
public long JournalTaskId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户期刊任务回答ID
|
||||
/// </summary>
|
||||
public long JournalTaskAnswerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息内容
|
||||
/// </summary>
|
||||
public string Content { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 配图
|
||||
/// </summary>
|
||||
public string? ImageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序权重
|
||||
/// </summary>
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用
|
||||
/// </summary>
|
||||
public bool IsActive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息类型
|
||||
/// </summary>
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 点赞数
|
||||
/// </summary>
|
||||
public int LikeCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否精选
|
||||
/// </summary>
|
||||
public int IsFeatured { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态: 0=审核中, 1=已通过, 2=已冻结
|
||||
/// </summary>
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建人
|
||||
/// </summary>
|
||||
public string? CreatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新人
|
||||
/// </summary>
|
||||
public string? UpdatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新时间
|
||||
/// </summary>
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置精选入参
|
||||
/// </summary>
|
||||
public class AdminSetFeaturedInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否精选: 0=否, 1=是
|
||||
/// </summary>
|
||||
public int IsFeatured { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置排序权重入参
|
||||
/// </summary>
|
||||
public class AdminSetSortOrderInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 排序权重
|
||||
/// </summary>
|
||||
public int SortOrder { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 冻结/解冻入参
|
||||
/// </summary>
|
||||
public class AdminFreezeInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态: 1=解冻(通过), 2=冻结
|
||||
/// </summary>
|
||||
public int Status { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 微信端 DTO
|
||||
|
||||
/// <summary>
|
||||
/// 微信端社区Feed消息输出
|
||||
/// </summary>
|
||||
public class WxFeedMessageOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息ID
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 期刊ID
|
||||
/// </summary>
|
||||
public long JournalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 作者用户ID
|
||||
/// </summary>
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息内容
|
||||
/// </summary>
|
||||
public string Content { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 配图
|
||||
/// </summary>
|
||||
public string? ImageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息类型
|
||||
/// </summary>
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 点赞数
|
||||
/// </summary>
|
||||
public int LikeCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否精选
|
||||
/// </summary>
|
||||
public int IsFeatured { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前用户是否已点赞
|
||||
/// </summary>
|
||||
public bool IsLiked { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信端社区Feed输出(含游标)
|
||||
/// </summary>
|
||||
public class WxFeedOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息列表
|
||||
/// </summary>
|
||||
public List<WxFeedMessageOutput> Messages { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 下一页游标(为null表示没有更多数据)
|
||||
/// </summary>
|
||||
public long? NextCursor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否还有更多数据
|
||||
/// </summary>
|
||||
public bool HasMore { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信端点赞入参
|
||||
/// </summary>
|
||||
public class WxLikeInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息ID
|
||||
/// </summary>
|
||||
public long MessageId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信端点赞输出
|
||||
/// </summary>
|
||||
public class WxLikeOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息ID
|
||||
/// </summary>
|
||||
public long MessageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前点赞数
|
||||
/// </summary>
|
||||
public int LikeCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已点赞
|
||||
/// </summary>
|
||||
public bool IsLiked { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 皮肤预览图(取当前进化阶段第一张图片)
|
||||
/// </summary>
|
||||
public string? PreviewImage { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -23,7 +23,12 @@ public class PetOutput
|
||||
/// <summary>
|
||||
/// 当前进化形态Id
|
||||
/// </summary>
|
||||
public int CurrentEvolutionId { get; set; }
|
||||
public long CurrentEvolutionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前进化阶段名称
|
||||
/// </summary>
|
||||
public string? EvolutionStageName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前成长值
|
||||
@ -35,6 +40,21 @@ public class PetOutput
|
||||
/// </summary>
|
||||
public int FeedingCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前皮肤Id
|
||||
/// </summary>
|
||||
public long CurrentSkinId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前皮肤名称
|
||||
/// </summary>
|
||||
public string? CurrentSkinName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前形态+当前皮肤 的图片列表(按SortOrder排序)
|
||||
/// </summary>
|
||||
public List<SkinImageOutput> CurrentImages { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 宠物类型: Normal
|
||||
/// </summary>
|
||||
@ -51,6 +71,27 @@ public class PetOutput
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 皮肤图片输出
|
||||
/// </summary>
|
||||
public class SkinImageOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 图片Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图片地址
|
||||
/// </summary>
|
||||
public string ImageUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 图片顺序
|
||||
/// </summary>
|
||||
public int SortOrder { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 喂养宠物输入
|
||||
/// </summary>
|
||||
|
||||
@ -8,7 +8,8 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
[SugarTable("Pet")]
|
||||
public partial class Pet : SqlSugarBaseEntity
|
||||
{
|
||||
public Pet(){
|
||||
public Pet()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
@ -17,55 +18,75 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long UserId {get;set;}
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:宠物昵称
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string Name {get;set;}
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:当前进化形态Id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int CurrentEvolutionId {get;set;}
|
||||
public long CurrentEvolutionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:当前成长值
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int GrowthPoints {get;set;}
|
||||
public int GrowthPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:累计喂养次数
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int FeedingCount {get;set;}
|
||||
public int FeedingCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:当前皮肤Id(0为默认皮肤)
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long CurrentSkinId {get;set;}
|
||||
public long CurrentSkinId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:宠物类型
|
||||
/// Default:Normal
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string Type {get;set;}
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:状态: Inactive, Active, Sleeping
|
||||
/// Default:Inactive
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string Status {get;set;}
|
||||
public new string Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 理解力
|
||||
/// </summary>
|
||||
public int Comprehension { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 判断力
|
||||
/// </summary>
|
||||
public int Judgment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 表达力
|
||||
/// </summary>
|
||||
public int Expression { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 说服力
|
||||
/// </summary>
|
||||
public int Persuasiveness { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,86 +8,76 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
[SugarTable("PetEvolution")]
|
||||
public partial class PetEvolution : SqlSugarBaseEntity
|
||||
{
|
||||
public PetEvolution(){
|
||||
|
||||
|
||||
}
|
||||
public PetEvolution() { }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:阶段名称
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string StageName {get;set;}
|
||||
public string StageName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:阶段等级
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int StageLevel {get;set;}
|
||||
public int StageLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:进化所需成长值
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int RequiredGrowth {get;set;}
|
||||
public int RequiredGrowth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:形态图片
|
||||
/// Desc:前一形态Id(null表示初始形态)
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string ImageUrl {get;set;}
|
||||
|
||||
/// <summary>
|
||||
/// Desc:前一形态Id
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public int? PreviousEvolutionId {get;set;}
|
||||
public long? PreviousEvolutionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:基础力量
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int BaseStrength {get;set;}
|
||||
public int BaseStrength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:基础敏捷
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int BaseAgility {get;set;}
|
||||
public int BaseAgility { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:基础智力
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int BaseIntelligence {get;set;}
|
||||
public int BaseIntelligence { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:基础魅力
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int BaseCharm {get;set;}
|
||||
public int BaseCharm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:进化类型: Normal, Special
|
||||
/// Default:Normal
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string Type {get;set;}
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:状态: Active, Inactive
|
||||
/// Default:Active
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string Status {get;set;}
|
||||
public new string Status { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,13 +17,6 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:皮肤图片
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string ImageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:描述/特效说明
|
||||
/// Default:
|
||||
@ -31,13 +24,6 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:关联商品Id(用于兑换)
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long ProductId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:稀有度: Normal, Rare, Epic, Legendary
|
||||
/// Default:Normal
|
||||
@ -51,5 +37,12 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:皮肤类型: Normal, Limited, Event
|
||||
/// Default:Normal
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string Type { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
48
QYZH.InteractiveMagazine.Models/Entity/PetSkinImage.cs
Normal file
48
QYZH.InteractiveMagazine.Models/Entity/PetSkinImage.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 宠物皮肤图片表(每个皮肤在每个进化阶段下有一组有序图片,用于动态表示)
|
||||
/// </summary>
|
||||
[SugarTable("PetSkinImage")]
|
||||
public partial class PetSkinImage : SqlSugarBaseEntity
|
||||
{
|
||||
public PetSkinImage() { }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:皮肤Id,关联PetSkin.Id(0表示默认皮肤的图片)
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long SkinId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:进化阶段Id,关联PetEvolution.Id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long EvolutionStageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:图片地址
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string ImageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:图片顺序(动画帧序号)
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:图片类型: Normal, Special
|
||||
/// Default:Normal
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string Type { get; set; }
|
||||
}
|
||||
}
|
||||
219
QYZH.InteractiveMagazine.Service/CommunityMessageService.cs
Normal file
219
QYZH.InteractiveMagazine.Service/CommunityMessageService.cs
Normal file
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 社区消息后台管理服务实现
|
||||
/// </summary>
|
||||
public class CommunityMessageService(BaseRepository<CommunityMessage> messageRepository, ILogger<CommunityMessageService> logger)
|
||||
: BaseRepository<CommunityMessage>, ICommunityMessageService
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询消息列表
|
||||
/// </summary>
|
||||
public async Task<PageListModel<AdminMessageDetailOutput>> 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<int> 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<AdminMessageDetailOutput>(pageResult, input.PageIndex, input.PageSize, totalNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查看消息详情
|
||||
/// </summary>
|
||||
public async Task<AdminMessageDetailOutput> 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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 软删除消息
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 冻结/解冻消息
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置/取消精选
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置排序权重
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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<Medal> medalRepository, ILogger<MedalSe
|
||||
throw new BusinessException("该勋章当前不可获得", 400);
|
||||
}
|
||||
|
||||
// 规则校验
|
||||
var (passed, failReason) = await ValidateMedalRulesAsync(medal.Id, userId);
|
||||
if (!passed)
|
||||
{
|
||||
logger.LogWarning("勋章规则校验未通过,用户ID: {UserId}, 勋章ID: {MedalId}, 原因: {Reason}", userId, input.MedalId, failReason);
|
||||
throw new BusinessException(failReason!, 400);
|
||||
}
|
||||
|
||||
var existingUserMedal = await Context.Queryable<UserMedal>()
|
||||
.Where(um => um.UserId == userId && um.MedalId == (int)input.MedalId && um.Status == "Awarded")
|
||||
.FirstAsync();
|
||||
@ -498,5 +507,142 @@ public class MedalService(BaseRepository<Medal> medalRepository, ILogger<MedalSe
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验勋章规则是否满足
|
||||
/// </summary>
|
||||
/// <param name="medalId">勋章ID</param>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <returns>(是否通过, 失败原因)</returns>
|
||||
private async Task<(bool passed, string? failReason)> ValidateMedalRulesAsync(long medalId, long userId)
|
||||
{
|
||||
var rules = await Context.Queryable<MedalRule>()
|
||||
.Where(r => r.MedalId == medalId && !r.IsDeleted)
|
||||
.OrderBy(r => r.RuleOrder)
|
||||
.ToListAsync();
|
||||
|
||||
if (!rules.Any())
|
||||
{
|
||||
return (true, null);
|
||||
}
|
||||
|
||||
var failReasons = new List<string>();
|
||||
|
||||
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)}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 评估单条规则是否满足
|
||||
/// </summary>
|
||||
/// <param name="rule">规则配置</param>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <returns>规则是否满足</returns>
|
||||
private async Task<bool> 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<SugarParameter>
|
||||
{
|
||||
new SugarParameter("@ThresholdValue", rule.ThresholdValue),
|
||||
new SugarParameter("@UserId", userId)
|
||||
};
|
||||
|
||||
// 处理额外筛选条件
|
||||
if (!string.IsNullOrWhiteSpace(rule.FilterCondition))
|
||||
{
|
||||
var filters = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据规则的时间范围类型计算起止时间
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
|
||||
@ -16,11 +16,13 @@ public class PetService(
|
||||
BaseRepository<Pet> petRepository,
|
||||
BaseRepository<PetFeedingRecord> feedingRecordRepository,
|
||||
BaseRepository<PetEvolution> petEvolutionRepository,
|
||||
BaseRepository<PetSkinImage> petSkinImageRepository,
|
||||
BaseRepository<PetSkin> petSkinRepository,
|
||||
ILogger<PetService> logger)
|
||||
: BaseRepository<Pet>, IPetService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取用户宠物信息
|
||||
/// 获取用户宠物信息(含当前形态名称、皮肤图片序列)
|
||||
/// </summary>
|
||||
public async Task<PetOutput?> 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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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
|
||||
|
||||
296
QYZH.InteractiveMagazine.Service/WeChatCommunityService.cs
Normal file
296
QYZH.InteractiveMagazine.Service/WeChatCommunityService.cs
Normal file
@ -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;
|
||||
|
||||
/// <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,
|
||||
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无效", 400);
|
||||
}
|
||||
|
||||
var message = await messageRepository.GetByIdAsync(input.MessageId);
|
||||
if (message == null)
|
||||
{
|
||||
throw new BusinessException("消息不存在", 404);
|
||||
}
|
||||
|
||||
// 检查是否已点赞
|
||||
var existingLike = await Context.Queryable<CommunityMessageLike>()
|
||||
.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<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无效", 400);
|
||||
}
|
||||
|
||||
var message = await messageRepository.GetByIdAsync(messageId);
|
||||
if (message == null)
|
||||
{
|
||||
throw new BusinessException("消息不存在", 404);
|
||||
}
|
||||
|
||||
var existingLike = await Context.Queryable<CommunityMessageLike>()
|
||||
.Where(l => l.UserId == userId && l.MessageId == messageId && l.Type == "Like")
|
||||
.FirstAsync();
|
||||
|
||||
if (existingLike == null)
|
||||
{
|
||||
throw new BusinessException("您尚未点赞过该消息", 400);
|
||||
}
|
||||
|
||||
// 软删除点赞记录
|
||||
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 == "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 == "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
|
||||
}
|
||||
@ -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<WxMallService> logger)
|
||||
: BaseRepository<ExchangeRecord>, IWxMallService
|
||||
{
|
||||
/// <summary>
|
||||
/// 从 MetaData JSON 中提取 SkinId
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取商城商品列表(仅上架 + 在售)
|
||||
/// </summary>
|
||||
@ -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<PetSkin>()
|
||||
.Where(s => skinProductIds.Contains(s.ProductId) && !s.IsDeleted)
|
||||
.Where(s => skinIds.Contains(s.Id) && !s.IsDeleted)
|
||||
.ToListAsync()
|
||||
: new List<PetSkin>();
|
||||
|
||||
// 批量查询皮肤预览图(取每个皮肤的第一张图)
|
||||
var allPreviewImages = skinIds.Count > 0
|
||||
? await exchangeRecordRepository.Context.Queryable<PetSkinImage>()
|
||||
.Where(i => skinIds.Contains(i.SkinId) && !i.IsDeleted)
|
||||
.OrderBy(i => i.SortOrder)
|
||||
.ToListAsync()
|
||||
: new List<PetSkinImage>();
|
||||
|
||||
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<UserBag>()
|
||||
.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();
|
||||
@ -84,23 +126,33 @@ public class WxMallService(
|
||||
|
||||
PetSkinBrief? skinBrief = null;
|
||||
if (product.Type == "PetBg")
|
||||
{
|
||||
var skinId = GetSkinIdFromMetaData(product.MetaData);
|
||||
if (skinId > 0)
|
||||
{
|
||||
var skin = await exchangeRecordRepository.Context.Queryable<PetSkin>()
|
||||
.Where(s => s.ProductId == product.Id && !s.IsDeleted)
|
||||
.Where(s => s.Id == skinId && !s.IsDeleted)
|
||||
.FirstAsync();
|
||||
|
||||
if (skin != null)
|
||||
{
|
||||
var previewImg = await exchangeRecordRepository.Context.Queryable<PetSkinImage>()
|
||||
.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,
|
||||
SkinImage = skin.ImageUrl,
|
||||
Description = skin.Description,
|
||||
Rarity = skin.Rarity
|
||||
Rarity = skin.Rarity,
|
||||
PreviewImage = previewImg
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var owned = await exchangeRecordRepository.Context.Queryable<UserBag>()
|
||||
.Where(b => b.UserId == userId && b.ItemId == product.Id && !b.IsDeleted && b.Status == "Available")
|
||||
@ -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<PetSkin>()
|
||||
.Where(s => skinProductIds.Contains(s.ProductId) && !s.IsDeleted)
|
||||
.Where(s => skinIds.Contains(s.Id) && !s.IsDeleted)
|
||||
.ToListAsync()
|
||||
: new List<PetSkin>();
|
||||
|
||||
// 批量查询皮肤预览图
|
||||
var allBagPreviewImages = skinIds.Count > 0
|
||||
? await exchangeRecordRepository.Context.Queryable<PetSkinImage>()
|
||||
.Where(i => skinIds.Contains(i.SkinId) && !i.IsDeleted)
|
||||
.OrderBy(i => i.SortOrder)
|
||||
.ToListAsync()
|
||||
: new List<PetSkinImage>();
|
||||
|
||||
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<UserBag>()
|
||||
.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);
|
||||
|
||||
// 换肤
|
||||
|
||||
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 社区消息管理控制器
|
||||
/// </summary>
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[ApiExplorerSettings(GroupName = nameof(ApiVersionEnum.Platform))]
|
||||
public class CommunityMessageController : BaseController
|
||||
{
|
||||
private readonly ICommunityMessageService _communityMessageService;
|
||||
private readonly ILogger<CommunityMessageController> _logger;
|
||||
|
||||
public CommunityMessageController(ICommunityMessageService communityMessageService, ILogger<CommunityMessageController> logger)
|
||||
{
|
||||
_communityMessageService = communityMessageService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询社区消息列表
|
||||
/// </summary>
|
||||
[HttpPost("list")]
|
||||
public async Task<BaseResponse<PageListModel<AdminMessageDetailOutput>>> 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<PageListModel<AdminMessageDetailOutput>>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "查询社区消息列表系统异常,参数:{Input}", input);
|
||||
return BaseResponse<PageListModel<AdminMessageDetailOutput>>.Fail("查询社区消息列表失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查看消息详情
|
||||
/// </summary>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<BaseResponse<AdminMessageDetailOutput>> GetDetailAsync(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _communityMessageService.GetDetailAsync(id);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "获取社区消息详情业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<AdminMessageDetailOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取社区消息详情系统异常,ID:{Id}", id);
|
||||
return BaseResponse<AdminMessageDetailOutput>.Fail("获取消息详情失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除消息
|
||||
/// </summary>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<BaseResponse<object>> DeleteAsync(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _communityMessageService.DeleteAsync(id);
|
||||
return Success(new object(), "删除消息成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "删除社区消息业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<object>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "删除社区消息系统异常,ID:{Id}", id);
|
||||
return BaseResponse<object>.Fail("删除消息失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 冻结/解冻消息
|
||||
/// </summary>
|
||||
[HttpPut("{id}/freeze")]
|
||||
public async Task<BaseResponse<object>> FreezeAsync(long id, [FromBody] AdminFreezeInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _communityMessageService.FreezeAsync(id, input.Status);
|
||||
return Success(new object(), input.Status == 2 ? "冻结消息成功" : "解冻消息成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "冻结社区消息业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<object>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "冻结社区消息系统异常,ID:{Id}", id);
|
||||
return BaseResponse<object>.Fail("操作失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置/取消精选
|
||||
/// </summary>
|
||||
[HttpPut("{id}/featured")]
|
||||
public async Task<BaseResponse<object>> SetFeaturedAsync(long id, [FromBody] AdminSetFeaturedInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _communityMessageService.SetFeaturedAsync(id, input.IsFeatured);
|
||||
return Success(new object(), input.IsFeatured == 1 ? "设置精选成功" : "取消精选成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "设置社区消息精选业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<object>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "设置社区消息精选系统异常,ID:{Id}", id);
|
||||
return BaseResponse<object>.Fail("操作失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置排序权重
|
||||
/// </summary>
|
||||
[HttpPut("{id}/sort")]
|
||||
public async Task<BaseResponse<object>> SetSortOrderAsync(long id, [FromBody] AdminSetSortOrderInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _communityMessageService.SetSortOrderAsync(id, input.SortOrder);
|
||||
return Success(new object(), "设置排序权重成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "设置社区消息排序权重业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<object>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "设置社区消息排序权重系统异常,ID:{Id}", id);
|
||||
return BaseResponse<object>.Fail("操作失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,129 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.WebApi.Controllers.WeChat;
|
||||
|
||||
/// <summary>
|
||||
/// 小程序社区控制器
|
||||
/// </summary>
|
||||
public class CommunityController(IWeChatCommunityService communityService, ILogger<CommunityController> logger) : WeChatBaseController
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取社区Feed流(翻页)
|
||||
/// </summary>
|
||||
/// <param name="cursor">游标(上一页最后一条消息ID,首次不传)</param>
|
||||
[HttpGet("feed")]
|
||||
public async Task<BaseResponse<WxFeedOutput>> GetFeed([FromQuery] long? cursor = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return BaseResponse<WxFeedOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await communityService.GetFeedAsync(userId.Value, cursor);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
logger.LogWarning(ex, "获取社区Feed业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<WxFeedOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "获取社区Feed系统异常");
|
||||
return BaseResponse<WxFeedOutput>.Fail("获取社区内容失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下拉刷新社区Feed
|
||||
/// </summary>
|
||||
[HttpGet("refresh")]
|
||||
public async Task<BaseResponse<WxFeedOutput>> RefreshFeed()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return BaseResponse<WxFeedOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await communityService.RefreshFeedAsync(userId.Value);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
logger.LogWarning(ex, "刷新社区Feed业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<WxFeedOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "刷新社区Feed系统异常");
|
||||
return BaseResponse<WxFeedOutput>.Fail("刷新社区内容失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 点赞
|
||||
/// </summary>
|
||||
[HttpPost("like")]
|
||||
public async Task<BaseResponse<WxLikeOutput>> Like([FromBody] WxLikeInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return BaseResponse<WxLikeOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await communityService.LikeAsync(userId.Value, input);
|
||||
return Success(result, "点赞成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
logger.LogWarning(ex, "点赞业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<WxLikeOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "点赞系统异常,参数:{Input}", input);
|
||||
return BaseResponse<WxLikeOutput>.Fail("点赞失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消点赞
|
||||
/// </summary>
|
||||
[HttpPost("unlike")]
|
||||
public async Task<BaseResponse<WxLikeOutput>> Unlike([FromBody] WxLikeInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return BaseResponse<WxLikeOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await communityService.UnlikeAsync(userId.Value, input.MessageId);
|
||||
return Success(result, "已取消点赞");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
logger.LogWarning(ex, "取消点赞业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<WxLikeOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "取消点赞系统异常,参数:{Input}", input);
|
||||
return BaseResponse<WxLikeOutput>.Fail("取消点赞失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers.WeChat;
|
||||
/// <summary>
|
||||
/// 小程序勋章控制器
|
||||
/// </summary>
|
||||
public class WeChatMedalController(IMedalService medalService, ILogger<WeChatMedalController> logger) : WeChatBaseController
|
||||
public class MedalController(IMedalService medalService, ILogger<MedalController> logger) : WeChatBaseController
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取所有勋章列表(含当前用户拥有状态)
|
||||
33
SqlMigrations/pet_module_optimization.sql
Normal file
33
SqlMigrations/pet_module_optimization.sql
Normal file
@ -0,0 +1,33 @@
|
||||
-- ========================================
|
||||
-- 宠物模块数据结构优化迁移脚本
|
||||
-- 执行前请备份相关表数据
|
||||
-- ========================================
|
||||
|
||||
-- 1. 修改 Pet 表:CurrentEvolutionId int → bigint
|
||||
ALTER TABLE Pet MODIFY COLUMN CurrentEvolutionId BIGINT NOT NULL DEFAULT 0;
|
||||
|
||||
-- 2. 修改 PetEvolution 表:PreviousEvolutionId int → bigint, 删除 ImageUrl
|
||||
ALTER TABLE PetEvolution MODIFY COLUMN PreviousEvolutionId BIGINT NULL;
|
||||
ALTER TABLE PetEvolution DROP COLUMN ImageUrl;
|
||||
|
||||
-- 3. 修改 PetSkin 表:删除 ImageUrl 和 ProductId
|
||||
ALTER TABLE PetSkin DROP COLUMN ImageUrl;
|
||||
ALTER TABLE PetSkin DROP COLUMN ProductId;
|
||||
|
||||
-- 4. 新建 PetSkinImage 表(每个皮肤在每个进化阶段下有一组有序图片)
|
||||
CREATE TABLE IF NOT EXISTS PetSkinImage (
|
||||
Id BIGINT NOT NULL PRIMARY KEY COMMENT '主键(雪花ID)',
|
||||
SkinId BIGINT NOT NULL COMMENT '皮肤Id,关联PetSkin.Id(0表示默认皮肤)',
|
||||
EvolutionStageId BIGINT NOT NULL COMMENT '进化阶段Id,关联PetEvolution.Id',
|
||||
ImageUrl VARCHAR(500) NOT NULL COMMENT '图片地址',
|
||||
SortOrder INT NOT NULL DEFAULT 0 COMMENT '图片顺序(动画帧序号)',
|
||||
Type VARCHAR(50) NOT NULL DEFAULT 'Normal' COMMENT '图片类型: Normal, Special',
|
||||
Status INT NOT NULL DEFAULT 0 COMMENT '基础状态',
|
||||
IsDeleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除',
|
||||
CreatedBy VARCHAR(100) NULL COMMENT '创建人',
|
||||
CreatedAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
UpdatedBy VARCHAR(100) NULL COMMENT '更新人',
|
||||
UpdatedAt DATETIME NULL COMMENT '更新时间',
|
||||
INDEX idx_skin_evolution (SkinId, EvolutionStageId),
|
||||
INDEX idx_sort (SkinId, EvolutionStageId, SortOrder)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宠物皮肤图片表';
|
||||
Reference in New Issue
Block a user