feat: 完成宠物模块重构与社区功能开发

本次提交包含多项核心更新:
1.  重构宠物模块数据结构:拆分皮肤图片为独立表,优化Pet、PetEvolution实体,调整字段类型与冗余字段
2.  新增宠物皮肤图片管理表,支持多进化阶段多图片展示
3.  完善宠物DTO,新增当前形态名称、皮肤信息与图片序列返回
4.  新增社区功能模块:
    - 微信端社区Feed流、点赞/取消点赞接口
    - 后台社区消息管理接口与服务实现
5.  优化商城与背包模块,替换皮肤图片获取逻辑为从新表读取预览图
6.  重构勋章服务,新增规则校验逻辑
7.  调整命名规范,修复原有控制器命名问题
This commit is contained in:
glz
2026-06-05 17:15:30 +08:00
parent eab84747a5
commit 8291b82362
19 changed files with 1753 additions and 182 deletions

View File

@ -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();
@ -85,20 +127,30 @@ public class WxMallService(
PetSkinBrief? skinBrief = null;
if (product.Type == "PetBg")
{
var skin = await exchangeRecordRepository.Context.Queryable<PetSkin>()
.Where(s => s.ProductId == product.Id && !s.IsDeleted)
.FirstAsync();
if (skin != null)
var skinId = GetSkinIdFromMetaData(product.MetaData);
if (skinId > 0)
{
skinBrief = new PetSkinBrief
var skin = await exchangeRecordRepository.Context.Queryable<PetSkin>()
.Where(s => s.Id == skinId && !s.IsDeleted)
.FirstAsync();
if (skin != null)
{
SkinId = skin.Id,
SkinName = skin.Name,
SkinImage = skin.ImageUrl,
Description = skin.Description,
Rarity = skin.Rarity
};
var previewImg = await exchangeRecordRepository.Context.Queryable<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,
Description = skin.Description,
Rarity = skin.Rarity,
PreviewImage = previewImg
};
}
}
}
@ -298,18 +350,40 @@ public class WxMallService(
.Where(p => itemIds.Contains(p.Id) && !p.IsDeleted)
.ToListAsync();
// 批量查询关联皮肤PetBg 类型
var skinProductIds = bagItems.Where(b => b.ItemType == "PetBg").Select(b => b.ItemId).Distinct().ToList();
var skins = skinProductIds.Count > 0
// 批量提取 PetBg 背包物品的 SkinId从 MetaData 解析
var skinIdMap = bagItems
.Where(b => b.ItemType == "PetBg")
.Select(b => new { BagId = b.Id, SkinId = GetSkinIdFromMetaData(b.MetaData) })
.Where(x => x.SkinId > 0)
.ToList();
var skinIds = skinIdMap.Select(x => x.SkinId).Distinct().ToList();
// 批量查询皮肤
var skins = skinIds.Count > 0
? await exchangeRecordRepository.Context.Queryable<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);
// 换肤