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 long SkinId { get; set; }
|
||||||
public string SkinName { get; set; } = string.Empty;
|
public string SkinName { get; set; } = string.Empty;
|
||||||
public string? SkinImage { get; set; }
|
|
||||||
public string Rarity { get; set; } = string.Empty;
|
public string Rarity { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 皮肤预览图(取第一张图片)
|
||||||
|
/// </summary>
|
||||||
|
public string? PreviewImage { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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 long SkinId { get; set; }
|
||||||
public string SkinName { get; set; } = string.Empty;
|
public string SkinName { get; set; } = string.Empty;
|
||||||
public string? SkinImage { get; set; }
|
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public string Rarity { get; set; } = string.Empty;
|
public string Rarity { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 皮肤预览图(取当前进化阶段第一张图片)
|
||||||
|
/// </summary>
|
||||||
|
public string? PreviewImage { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -23,7 +23,12 @@ public class PetOutput
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前进化形态Id
|
/// 当前进化形态Id
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int CurrentEvolutionId { get; set; }
|
public long CurrentEvolutionId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前进化阶段名称
|
||||||
|
/// </summary>
|
||||||
|
public string? EvolutionStageName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前成长值
|
/// 当前成长值
|
||||||
@ -35,6 +40,21 @@ public class PetOutput
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int FeedingCount { get; set; }
|
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>
|
/// <summary>
|
||||||
/// 宠物类型: Normal
|
/// 宠物类型: Normal
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -51,6 +71,27 @@ public class PetOutput
|
|||||||
public DateTime CreatedAt { get; set; }
|
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>
|
||||||
/// 喂养宠物输入
|
/// 喂养宠物输入
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -8,7 +8,8 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
|||||||
[SugarTable("Pet")]
|
[SugarTable("Pet")]
|
||||||
public partial class Pet : SqlSugarBaseEntity
|
public partial class Pet : SqlSugarBaseEntity
|
||||||
{
|
{
|
||||||
public Pet(){
|
public Pet()
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -31,7 +32,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
|||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:False
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int CurrentEvolutionId {get;set;}
|
public long CurrentEvolutionId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:当前成长值
|
/// Desc:当前成长值
|
||||||
@ -66,6 +67,26 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
|||||||
/// Default:Inactive
|
/// Default:Inactive
|
||||||
/// Nullable:False
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </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,10 +8,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
|||||||
[SugarTable("PetEvolution")]
|
[SugarTable("PetEvolution")]
|
||||||
public partial class PetEvolution : SqlSugarBaseEntity
|
public partial class PetEvolution : SqlSugarBaseEntity
|
||||||
{
|
{
|
||||||
public PetEvolution(){
|
public PetEvolution() { }
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:阶段名称
|
/// Desc:阶段名称
|
||||||
@ -35,18 +32,11 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
|||||||
public int RequiredGrowth { get; set; }
|
public int RequiredGrowth { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:形态图片
|
/// Desc:前一形态Id(null表示初始形态)
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:True
|
/// Nullable:True
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ImageUrl {get;set;}
|
public long? PreviousEvolutionId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Desc:前一形态Id
|
|
||||||
/// Default:
|
|
||||||
/// Nullable:True
|
|
||||||
/// </summary>
|
|
||||||
public int? PreviousEvolutionId {get;set;}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:基础力量
|
/// Desc:基础力量
|
||||||
@ -88,6 +78,6 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
|||||||
/// Default:Active
|
/// Default:Active
|
||||||
/// Nullable:False
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Status {get;set;}
|
public new string Status { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,13 +17,6 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Desc:皮肤图片
|
|
||||||
/// Default:
|
|
||||||
/// Nullable:True
|
|
||||||
/// </summary>
|
|
||||||
public string ImageUrl { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:描述/特效说明
|
/// Desc:描述/特效说明
|
||||||
/// Default:
|
/// Default:
|
||||||
@ -31,13 +24,6 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Desc:关联商品Id(用于兑换)
|
|
||||||
/// Default:
|
|
||||||
/// Nullable:False
|
|
||||||
/// </summary>
|
|
||||||
public long ProductId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:稀有度: Normal, Rare, Epic, Legendary
|
/// Desc:稀有度: Normal, Rare, Epic, Legendary
|
||||||
/// Default:Normal
|
/// Default:Normal
|
||||||
@ -51,5 +37,12 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
|||||||
/// Nullable:False
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int SortOrder { get; set; }
|
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 Microsoft.Extensions.Logging;
|
||||||
using QYZH.InteractiveMagazine.IService;
|
using QYZH.InteractiveMagazine.IService;
|
||||||
using QYZH.InteractiveMagazine.Models.Common;
|
using QYZH.InteractiveMagazine.Models.Common;
|
||||||
@ -380,6 +381,14 @@ public class MedalService(BaseRepository<Medal> medalRepository, ILogger<MedalSe
|
|||||||
throw new BusinessException("该勋章当前不可获得", 400);
|
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>()
|
var existingUserMedal = await Context.Queryable<UserMedal>()
|
||||||
.Where(um => um.UserId == userId && um.MedalId == (int)input.MedalId && um.Status == "Awarded")
|
.Where(um => um.UserId == userId && um.MedalId == (int)input.MedalId && um.Status == "Awarded")
|
||||||
.FirstAsync();
|
.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
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,11 +16,13 @@ public class PetService(
|
|||||||
BaseRepository<Pet> petRepository,
|
BaseRepository<Pet> petRepository,
|
||||||
BaseRepository<PetFeedingRecord> feedingRecordRepository,
|
BaseRepository<PetFeedingRecord> feedingRecordRepository,
|
||||||
BaseRepository<PetEvolution> petEvolutionRepository,
|
BaseRepository<PetEvolution> petEvolutionRepository,
|
||||||
|
BaseRepository<PetSkinImage> petSkinImageRepository,
|
||||||
|
BaseRepository<PetSkin> petSkinRepository,
|
||||||
ILogger<PetService> logger)
|
ILogger<PetService> logger)
|
||||||
: BaseRepository<Pet>, IPetService
|
: BaseRepository<Pet>, IPetService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取用户宠物信息
|
/// 获取用户宠物信息(含当前形态名称、皮肤图片序列)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<PetOutput?> GetPetByUserIdAsync(long userId)
|
public async Task<PetOutput?> GetPetByUserIdAsync(long userId)
|
||||||
{
|
{
|
||||||
@ -28,21 +30,56 @@ public class PetService(
|
|||||||
|
|
||||||
var pet = await petRepository.Queryable()
|
var pet = await petRepository.Queryable()
|
||||||
.Where(p => p.UserId == userId)
|
.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();
|
.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>
|
/// <summary>
|
||||||
@ -61,13 +98,20 @@ public class PetService(
|
|||||||
return;
|
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
|
var pet = new Pet
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
Name = "小精灵",
|
Name = "小精灵",
|
||||||
CurrentEvolutionId = 1,
|
CurrentEvolutionId = initialEvolution?.Id ?? 0,
|
||||||
GrowthPoints = 0,
|
GrowthPoints = 0,
|
||||||
FeedingCount = 0,
|
FeedingCount = 0,
|
||||||
|
CurrentSkinId = 0,
|
||||||
Type = "Normal",
|
Type = "Normal",
|
||||||
Status = "Inactive",
|
Status = "Inactive",
|
||||||
IsDeleted = false,
|
IsDeleted = false,
|
||||||
@ -84,7 +128,8 @@ public class PetService(
|
|||||||
throw new Exception("创建宠物失败");
|
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>
|
/// <summary>
|
||||||
@ -185,7 +230,7 @@ public class PetService(
|
|||||||
throw new BusinessException("更新宠物成长值失败", 500);
|
throw new BusinessException("更新宠物成长值失败", 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 进化检查:查找下一阶段进化形态
|
// 进化检查:查找下一阶段进化形态(PreviousEvolutionId 类型为 long?)
|
||||||
var nextEvolution = await petEvolutionRepository.Queryable()
|
var nextEvolution = await petEvolutionRepository.Queryable()
|
||||||
.Where(e => e.PreviousEvolutionId == pet.CurrentEvolutionId
|
.Where(e => e.PreviousEvolutionId == pet.CurrentEvolutionId
|
||||||
&& e.RequiredGrowth <= growthAfter
|
&& 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.Dto.Mall;
|
||||||
using QYZH.InteractiveMagazine.Models.Entity;
|
using QYZH.InteractiveMagazine.Models.Entity;
|
||||||
using QYZH.InteractiveMagazine.Repository;
|
using QYZH.InteractiveMagazine.Repository;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.Service;
|
namespace QYZH.InteractiveMagazine.Service;
|
||||||
|
|
||||||
@ -17,6 +18,24 @@ public class WxMallService(
|
|||||||
ILogger<WxMallService> logger)
|
ILogger<WxMallService> logger)
|
||||||
: BaseRepository<ExchangeRecord>, IWxMallService
|
: 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>
|
||||||
/// 获取商城商品列表(仅上架 + 在售)
|
/// 获取商城商品列表(仅上架 + 在售)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -29,15 +48,36 @@ public class WxMallService(
|
|||||||
|
|
||||||
var products = await query.ToListAsync();
|
var products = await query.ToListAsync();
|
||||||
|
|
||||||
// 批量查询皮肤信息
|
// 批量提取 PetBg 商品的 SkinId
|
||||||
var skinProductIds = products.Where(p => p.Type == "PetBg").Select(p => p.Id).ToList();
|
var skinProductMap = products
|
||||||
var skins = skinProductIds.Count > 0
|
.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>()
|
? await exchangeRecordRepository.Context.Queryable<PetSkin>()
|
||||||
.Where(s => skinProductIds.Contains(s.ProductId) && !s.IsDeleted)
|
.Where(s => skinIds.Contains(s.Id) && !s.IsDeleted)
|
||||||
.ToListAsync()
|
.ToListAsync()
|
||||||
: new List<PetSkin>();
|
: 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
|
var bagItems = skinProductIds.Count > 0
|
||||||
? await exchangeRecordRepository.Context.Queryable<UserBag>()
|
? await exchangeRecordRepository.Context.Queryable<UserBag>()
|
||||||
.Where(b => b.UserId == userId && !b.IsDeleted && b.Status == "Available"
|
.Where(b => b.UserId == userId && !b.IsDeleted && b.Status == "Available"
|
||||||
@ -47,8 +87,10 @@ public class WxMallService(
|
|||||||
|
|
||||||
return products.Select(p =>
|
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 owned = bagItems.Any(b => b.ItemId == p.Id);
|
||||||
|
var previewImg = skin != null && previewImageMap.ContainsKey(skin.Id) ? previewImageMap[skin.Id] : null;
|
||||||
|
|
||||||
return new WxProductOutput
|
return new WxProductOutput
|
||||||
{
|
{
|
||||||
@ -63,9 +105,9 @@ public class WxMallService(
|
|||||||
{
|
{
|
||||||
SkinId = skin.Id,
|
SkinId = skin.Id,
|
||||||
SkinName = skin.Name,
|
SkinName = skin.Name,
|
||||||
SkinImage = skin.ImageUrl,
|
|
||||||
Description = skin.Description,
|
Description = skin.Description,
|
||||||
Rarity = skin.Rarity
|
Rarity = skin.Rarity,
|
||||||
|
PreviewImage = previewImg
|
||||||
} : null
|
} : null
|
||||||
};
|
};
|
||||||
}).ToList();
|
}).ToList();
|
||||||
@ -84,23 +126,33 @@ public class WxMallService(
|
|||||||
|
|
||||||
PetSkinBrief? skinBrief = null;
|
PetSkinBrief? skinBrief = null;
|
||||||
if (product.Type == "PetBg")
|
if (product.Type == "PetBg")
|
||||||
|
{
|
||||||
|
var skinId = GetSkinIdFromMetaData(product.MetaData);
|
||||||
|
if (skinId > 0)
|
||||||
{
|
{
|
||||||
var skin = await exchangeRecordRepository.Context.Queryable<PetSkin>()
|
var skin = await exchangeRecordRepository.Context.Queryable<PetSkin>()
|
||||||
.Where(s => s.ProductId == product.Id && !s.IsDeleted)
|
.Where(s => s.Id == skinId && !s.IsDeleted)
|
||||||
.FirstAsync();
|
.FirstAsync();
|
||||||
|
|
||||||
if (skin != null)
|
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
|
skinBrief = new PetSkinBrief
|
||||||
{
|
{
|
||||||
SkinId = skin.Id,
|
SkinId = skin.Id,
|
||||||
SkinName = skin.Name,
|
SkinName = skin.Name,
|
||||||
SkinImage = skin.ImageUrl,
|
|
||||||
Description = skin.Description,
|
Description = skin.Description,
|
||||||
Rarity = skin.Rarity
|
Rarity = skin.Rarity,
|
||||||
|
PreviewImage = previewImg
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var owned = await exchangeRecordRepository.Context.Queryable<UserBag>()
|
var owned = await exchangeRecordRepository.Context.Queryable<UserBag>()
|
||||||
.Where(b => b.UserId == userId && b.ItemId == product.Id && !b.IsDeleted && b.Status == "Available")
|
.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)
|
.Where(p => itemIds.Contains(p.Id) && !p.IsDeleted)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
// 批量查询关联皮肤(PetBg 类型)
|
// 批量提取 PetBg 背包物品的 SkinId(从 MetaData 解析)
|
||||||
var skinProductIds = bagItems.Where(b => b.ItemType == "PetBg").Select(b => b.ItemId).Distinct().ToList();
|
var skinIdMap = bagItems
|
||||||
var skins = skinProductIds.Count > 0
|
.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>()
|
? await exchangeRecordRepository.Context.Queryable<PetSkin>()
|
||||||
.Where(s => skinProductIds.Contains(s.ProductId) && !s.IsDeleted)
|
.Where(s => skinIds.Contains(s.Id) && !s.IsDeleted)
|
||||||
.ToListAsync()
|
.ToListAsync()
|
||||||
: new List<PetSkin>();
|
: 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 =>
|
return bagItems.Select(b =>
|
||||||
{
|
{
|
||||||
var product = products.FirstOrDefault(p => p.Id == b.ItemId);
|
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
|
return new UserBagOutput
|
||||||
{
|
{
|
||||||
@ -329,8 +403,8 @@ public class WxMallService(
|
|||||||
{
|
{
|
||||||
SkinId = skin.Id,
|
SkinId = skin.Id,
|
||||||
SkinName = skin.Name,
|
SkinName = skin.Name,
|
||||||
SkinImage = skin.ImageUrl,
|
Rarity = skin.Rarity,
|
||||||
Rarity = skin.Rarity
|
PreviewImage = previewImg
|
||||||
} : null
|
} : null
|
||||||
};
|
};
|
||||||
}).ToList();
|
}).ToList();
|
||||||
@ -449,13 +523,15 @@ public class WxMallService(
|
|||||||
if (skin == null)
|
if (skin == null)
|
||||||
throw new BusinessException("皮肤不存在", 404);
|
throw new BusinessException("皮肤不存在", 404);
|
||||||
|
|
||||||
// 校验背包中是否拥有该皮肤(通过关联商品Id判断)
|
// 校验背包中是否拥有该皮肤(通过 MetaData 中的 SkinId 判断)
|
||||||
var hasSkin = await exchangeRecordRepository.Context.Queryable<UserBag>()
|
var hasSkin = await exchangeRecordRepository.Context.Queryable<UserBag>()
|
||||||
.Where(b => b.UserId == userId && b.ItemId == skin.ProductId
|
.Where(b => b.UserId == userId && !b.IsDeleted && b.Status == "Available" && b.Quantity > 0
|
||||||
&& !b.IsDeleted && b.Status == "Available" && b.Quantity > 0)
|
&& b.ItemType == "PetBg")
|
||||||
.AnyAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
if (!hasSkin)
|
var owned = hasSkin.Any(b => GetSkinIdFromMetaData(b.MetaData) == input.SkinId);
|
||||||
|
|
||||||
|
if (!owned)
|
||||||
throw new BusinessException("您尚未拥有该皮肤,请先兑换", 400);
|
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>
|
||||||
/// 小程序勋章控制器
|
/// 小程序勋章控制器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WeChatMedalController(IMedalService medalService, ILogger<WeChatMedalController> logger) : WeChatBaseController
|
public class MedalController(IMedalService medalService, ILogger<MedalController> logger) : WeChatBaseController
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <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