Files
QYZH.InteractiveMagazine/QYZH.InteractiveMagazine.Service/PetService.cs
glz badf68a8f8 feat: 新增宠物封面图支持,优化补偿任务查询与宠物模板创建流程
1.  新增PetSkinImageTypeEnum.Cover枚举,添加封面图类型支持
2.  为PetSkin、PetTemplate实体添加封面图片地址字段
3.  新增补偿任务按用户ID筛选功能,优化UsersService查询逻辑
4.  重构宠物模板创建流程,支持批量创建进化链与图片
5.  移除PetEvolution实体的基础属性字段,简化进化形态结构
6.  新增OSS域名辅助工具与SDK注册扩展类
7.  优化宠物皮肤、进化阶段的DTO与服务层逻辑
2026-06-09 16:15:22 +08:00

1127 lines
43 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.Extensions.Logging;
using QYZH.InteractiveMagazine.IService;
using QYZH.InteractiveMagazine.Models.Common;
using QYZH.InteractiveMagazine.Models.Dto;
using QYZH.InteractiveMagazine.Models.Dto.Pet;
using QYZH.InteractiveMagazine.Models.Entity;
using QYZH.InteractiveMagazine.Models.Enum;
using QYZH.InteractiveMagazine.Repository;
using SqlSugar;
namespace QYZH.InteractiveMagazine.Service;
/// <summary>
/// 宠物服务实现
/// </summary>
public class PetService(
BaseRepository<UserPet> petRepository,
BaseRepository<PetTemplate> petTemplateRepository,
BaseRepository<PetFeedingRecord> feedingRecordRepository,
BaseRepository<PetEvolution> petEvolutionRepository,
BaseRepository<PetSkinImage> petSkinImageRepository,
BaseRepository<PetSkin> petSkinRepository,
ILogger<PetService> logger)
: BaseRepository<UserPet>, IPetService
{
/// <summary>
/// 获取用户宠物信息(含当前形态名称、皮肤图片序列)
/// </summary>
public async Task<PetOutput?> GetPetByUserIdAsync(long userId)
{
logger.LogInformation("获取用户宠物信息UserId: {UserId}", userId);
var pet = await petRepository.Queryable()
.Where(p => p.UserId == userId)
.FirstAsync();
if (pet == null) return null;
// 查询当前进化阶段名称
var evolution = await petEvolutionRepository.Queryable()
.Where(e => e.Id == pet.CurrentEvolutionId)
.FirstAsync();
// 查询宠物模板名称
var template = await petTemplateRepository.Queryable()
.Where(t => t.Id == pet.TemplateId && !t.IsDeleted)
.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,
TemplateId = pet.TemplateId,
TemplateName = template?.Name,
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.ToString(),
Status = pet.Status.ToString(),
CreatedAt = pet.CreatedAt
};
}
/// <summary>
/// 为用户创建默认宠物最低形态、成长值为0、未激活状态
/// </summary>
public async Task CreateDefaultPetAsync(long userId)
{
logger.LogInformation("为用户创建默认宠物UserId: {UserId}", userId);
// 检查用户是否已有宠物
var exists = petRepository.Context.Queryable<UserPet>()
.Any(p => p.UserId == userId);
if (exists)
{
logger.LogWarning("用户已存在宠物跳过创建UserId: {UserId}", userId);
return;
}
// 查询默认宠物模板取排序权重最低且状态为Active的模板
var defaultTemplate = await petTemplateRepository.Queryable()
.Where(t => t.Status == (int)PetTemplateStatusEnum.Active && !t.IsDeleted)
.OrderBy(t => t.SortOrder)
.FirstAsync();
if (defaultTemplate == null)
{
logger.LogError("未找到可用的宠物模板");
throw new Exception("宠物模板配置缺失");
}
// 确定初始进化形态:优先使用模板的 DefaultEvolutionId为 0 时动态查询兜底
PetEvolution? initialEvolution = null;
if (defaultTemplate.DefaultEvolutionId > 0)
{
initialEvolution = await petEvolutionRepository.Queryable()
.Where(e => e.Id == defaultTemplate.DefaultEvolutionId && !e.IsDeleted)
.FirstAsync();
}
if (initialEvolution == null)
{
// 兜底:查询该模板下 PreviousEvolutionId 为 null 的初始形态
initialEvolution = await petEvolutionRepository.Queryable()
.Where(e => e.TemplateId == defaultTemplate.Id
&& e.PreviousEvolutionId == null
&& e.Status == (int)PetEvolutionStatusEnum.Active)
.OrderBy(e => e.StageLevel)
.FirstAsync();
}
if (initialEvolution == null)
{
logger.LogError("模板 {TemplateId} 未配置初始进化形态", defaultTemplate.Id);
throw new Exception("宠物模板初始进化形态配置缺失");
}
var pet = new UserPet
{
UserId = userId,
TemplateId = defaultTemplate.Id,
Name = defaultTemplate.Name,
CurrentEvolutionId = initialEvolution.Id,
GrowthPoints = 0,
FeedingCount = 0,
CurrentSkinId = 0,
Type = UserPetTypeEnum.Normal,
Status = (int)UserPetStatusEnum.Inactive,
IsDeleted = false,
CreatedBy = userId.ToString(),
CreatedAt = DateTime.Now,
UpdatedBy = userId.ToString(),
UpdatedAt = DateTime.Now
};
var result = await petRepository.InsertAsync(pet);
if (!result)
{
logger.LogError("创建默认宠物失败UserId: {UserId}", userId);
throw new Exception("创建宠物失败");
}
logger.LogInformation("用户默认宠物创建成功UserId: {UserId}, PetId: {PetId}, EvolutionId: {EvolutionId}",
userId, pet.Id, pet.CurrentEvolutionId);
}
/// <summary>
/// 激活宠物(将状态从 Inactive 改为 Active
/// </summary>
public async Task ActivatePetAsync(long userId)
{
logger.LogInformation("激活用户宠物UserId: {UserId}", userId);
var pet = await petRepository.Queryable()
.Where(p => p.UserId == userId)
.FirstAsync();
if (pet == null)
{
logger.LogWarning("用户宠物不存在无法激活UserId: {UserId}", userId);
return;
}
if (pet.Status != (int)UserPetStatusEnum.Inactive)
{
logger.LogInformation("用户宠物已非未激活状态跳过激活UserId: {UserId}, Status: {Status}", userId, pet.Status);
return;
}
var result = await petRepository.UpdateAsync(
p => new UserPet { Status = (int)UserPetStatusEnum.Active },
p => p.UserId == userId);
if (!result)
{
logger.LogError("激活宠物失败UserId: {UserId}", userId);
throw new Exception("激活宠物失败");
}
logger.LogInformation("用户宠物激活成功UserId: {UserId}", userId);
}
/// <summary>
/// 喂养宠物(增加成长值 + 记录喂养记录 + 触发进化检查)
/// </summary>
public async Task<FeedPetOutput> FeedPetAsync(long userId, FeedPetInput input)
{
logger.LogInformation("喂养宠物UserId: {UserId}, PetId: {PetId}, GrowthPoints: {GrowthPoints}",
userId, input.PetId, input.GrowthPoints);
if (input.PetId <= 0)
{
throw new BusinessException("宠物Id不能为空", 400);
}
if (input.GrowthPoints <= 0)
{
throw new BusinessException("成长值必须大于0", 400);
}
// 查询宠物
var pet = await petRepository.GetByIdAsync(input.PetId);
if (pet == null || pet.IsDeleted)
{
logger.LogWarning("喂养失败宠物不存在PetId: {PetId}", input.PetId);
throw new BusinessException("宠物不存在", 404);
}
// 校验宠物归属
if (pet.UserId != userId)
{
logger.LogWarning("喂养失败无权操作该宠物UserId: {UserId}, PetUserId: {PetUserId}", userId, pet.UserId);
throw new BusinessException("无权操作该宠物", 403);
}
// 校验宠物状态
if (pet.Status != (int)UserPetStatusEnum.Active)
{
logger.LogWarning("喂养失败宠物未激活PetId: {PetId}, Status: {Status}", input.PetId, pet.Status);
throw new BusinessException("宠物未激活,无法喂养", 400);
}
var growthBefore = pet.GrowthPoints;
var growthAfter = growthBefore + input.GrowthPoints;
var hasEvolved = false;
string? evolvedStageName = null;
// 事务保证一致性
await UseTranAsync(async () =>
{
// 累加成长值和喂养次数
var updateResult = await petRepository.Context.Updateable<UserPet>()
.SetColumns(p => p.GrowthPoints == growthAfter)
.SetColumns(p => p.FeedingCount == p.FeedingCount + 1)
.SetColumns(p => p.UpdatedAt == DateTime.Now)
.SetColumns(p => p.UpdatedBy == userId.ToString())
.Where(p => p.Id == input.PetId)
.ExecuteCommandAsync();
if (updateResult <= 0)
{
throw new BusinessException("更新宠物成长值失败", 500);
}
// 进化检查查找下一阶段进化形态PreviousEvolutionId 类型为 long?
var nextEvolution = await petEvolutionRepository.Queryable()
.Where(e => e.PreviousEvolutionId == pet.CurrentEvolutionId
&& e.RequiredGrowth <= growthAfter
&& e.Status == (int)PetEvolutionStatusEnum.Active)
.OrderBy(e => e.RequiredGrowth, OrderByType.Desc)
.FirstAsync();
if (nextEvolution != null)
{
// 触发进化
var evolveResult = await petRepository.Context.Updateable<UserPet>()
.SetColumns(p => p.CurrentEvolutionId == nextEvolution.Id)
.SetColumns(p => p.UpdatedAt == DateTime.Now)
.SetColumns(p => p.UpdatedBy == userId.ToString())
.Where(p => p.Id == input.PetId)
.ExecuteCommandAsync();
if (evolveResult > 0)
{
hasEvolved = true;
evolvedStageName = nextEvolution.StageName;
logger.LogInformation("宠物进化成功PetId: {PetId}, 新形态: {StageName} (Level {StageLevel})",
input.PetId, nextEvolution.StageName, nextEvolution.StageLevel);
}
}
// 写入喂养记录
var record = new PetFeedingRecord
{
PetId = input.PetId,
UserId = userId,
PointsUsed = 0, // 预留:后期可扩展为消耗积分喂养
GrowthChange = input.GrowthPoints,
GrowthBefore = growthBefore,
GrowthAfter = growthAfter,
Type = PetFeedingRecordTypeEnum.Normal,
Status = (int)PetFeedingRecordStatusEnum.Success,
IsDeleted = false,
CreatedBy = userId.ToString(),
CreatedAt = DateTime.Now,
UpdatedBy = userId.ToString(),
UpdatedAt = DateTime.Now
};
var insertResult = await feedingRecordRepository.InsertAsync(record);
if (!insertResult)
{
throw new BusinessException("写入喂养记录失败", 500);
}
});
logger.LogInformation("喂养宠物成功PetId: {PetId}, 成长值: {Before} -> {After}, 进化: {HasEvolved}",
input.PetId, growthBefore, growthAfter, hasEvolved);
return new FeedPetOutput
{
PetId = input.PetId,
GrowthBefore = growthBefore,
GrowthAfter = growthAfter,
GrowthChange = input.GrowthPoints,
HasEvolved = hasEvolved,
EvolvedStageName = evolvedStageName
};
}
/// <summary>
/// 获取宠物喂养记录列表
/// </summary>
public async Task<PageListModel<FeedingRecordOutput>> GetFeedingRecordsAsync(long userId, long petId, PageQueryModel pageQuery)
{
logger.LogInformation("查询喂养记录UserId: {UserId}, PetId: {PetId}, PageIndex: {PageIndex}, PageSize: {PageSize}",
userId, petId, pageQuery.PageIndex, pageQuery.PageSize);
RefAsync<int> totalNumber = 0;
var records = await feedingRecordRepository.Queryable()
.Where(r => r.UserId == userId && r.PetId == petId)
.OrderByDescending(r => r.CreatedAt)
.Select(r => new FeedingRecordOutput
{
Id = r.Id,
PetId = r.PetId,
UserId = r.UserId,
GrowthChange = r.GrowthChange,
GrowthBefore = r.GrowthBefore,
GrowthAfter = r.GrowthAfter,
Type = r.Type.ToString(),
Status = r.Status.ToString(),
CreatedAt = r.CreatedAt
}, true)
.ToPageListAsync(pageQuery.PageIndex, pageQuery.PageSize, totalNumber);
return new PageListModel<FeedingRecordOutput>(records, pageQuery.PageIndex, pageQuery.PageSize, totalNumber);
}
// ==================== 后台管理:宠物模板 ====================
/// <summary>
/// 创建宠物模板(事务:同时创建进化链、默认皮肤及图片)
/// </summary>
public async Task<PetTemplateOutput> CreateTemplateAsync(PetTemplateInput input)
{
if (string.IsNullOrWhiteSpace(input.Name))
throw new BusinessException("模板名称不能为空", 400);
if (input.Evolutions == null || input.Evolutions.Count == 0)
throw new BusinessException("至少需要一个进化阶段", 400);
PetTemplate template = null!;
await UseTranAsync(async () =>
{
// 1. 创建模板DefaultEvolutionId 先置 0稍后回填
template = new PetTemplate
{
Name = input.Name.Trim(),
Description = input.Description,
DefaultEvolutionId = 0,
IconUrl = input.IconUrl,
SortOrder = input.SortOrder,
Type = Enum.Parse<PetTemplateTypeEnum>(input.Type, true),
Status = (int)PetTemplateStatusEnum.Active,
CreatedBy = "System",
CreatedAt = DateTime.Now,
UpdatedBy = "System",
UpdatedAt = DateTime.Now,
IsDeleted = false
};
var inserted = await petTemplateRepository.InsertAsync(template);
if (!inserted)
throw new BusinessException("创建宠物模板失败", 500);
// 2. 按 StageLevel 排序,依次创建进化阶段
var sortedEvolutions = input.Evolutions.OrderBy(e => e.StageLevel).ToList();
PetEvolution? previousEvolution = null;
long initialEvolutionId = 0;
string? initialCoverUrl = null;
foreach (var evoInput in sortedEvolutions)
{
var evolution = new PetEvolution
{
TemplateId = template.Id,
StageName = evoInput.StageName.Trim(),
StageLevel = evoInput.StageLevel,
RequiredGrowth = evoInput.RequiredGrowth,
PreviousEvolutionId = previousEvolution?.Id, // 第一个为 null
Type = Enum.Parse<PetEvolutionTypeEnum>(evoInput.Type, true),
Status = (int)PetEvolutionStatusEnum.Active,
CreatedBy = "System",
CreatedAt = DateTime.Now,
UpdatedBy = "System",
UpdatedAt = DateTime.Now,
IsDeleted = false
};
var evoInserted = await petEvolutionRepository.InsertAsync(evolution);
if (!evoInserted)
throw new BusinessException($"创建进化阶段 [{evoInput.StageName}] 失败", 500);
// 记录初始阶段(第一个)
if (previousEvolution == null)
{
initialEvolutionId = evolution.Id;
}
// 3. 为该阶段创建默认皮肤图片
if (evoInput.Images != null && evoInput.Images.Count > 0)
{
foreach (var imgInput in evoInput.Images.OrderBy(i => i.SortOrder))
{
var skinImage = new PetSkinImage
{
SkinId = 0, // 先置 0等默认皮肤创建后回填
EvolutionStageId = evolution.Id,
ImageUrl = imgInput.ImageUrl.Trim(),
SortOrder = imgInput.SortOrder,
Type = Enum.Parse<PetSkinImageTypeEnum>(imgInput.Type, true),
CreatedBy = "System",
CreatedAt = DateTime.Now,
UpdatedBy = "System",
UpdatedAt = DateTime.Now,
IsDeleted = false
};
await petSkinImageRepository.InsertAsync(skinImage);
// 记录初始阶段的封面图
if (previousEvolution == null
&& imgInput.Type == PetSkinImageTypeEnum.Cover.ToString()
&& initialCoverUrl == null)
{
initialCoverUrl = skinImage.ImageUrl;
}
}
}
previousEvolution = evolution;
}
// 4. 创建默认皮肤
var defaultSkin = new PetSkin
{
TemplateId = template.Id,
Name = "默认皮肤",
Description = "系统默认皮肤",
Rarity = "Normal",
SortOrder = 0,
Type = Enum.Parse<PetSkinTypeEnum>("Normal", true),
CoverImageUrl = initialCoverUrl,
CreatedBy = "System",
CreatedAt = DateTime.Now,
UpdatedBy = "System",
UpdatedAt = DateTime.Now,
IsDeleted = false
};
await petSkinRepository.InsertAsync(defaultSkin);
// 5. 回填图片的 SkinId
var evolutionIds = await petEvolutionRepository.Context.Queryable<PetEvolution>()
.Where(e => e.TemplateId == template.Id && !e.IsDeleted)
.Select(e => e.Id)
.ToListAsync();
if (evolutionIds.Count > 0)
{
await petSkinImageRepository.Context.Updateable<PetSkinImage>()
.SetColumns(i => i.SkinId == defaultSkin.Id)
.Where(i => i.SkinId == 0 && i.IsDeleted == false
&& evolutionIds.Contains(i.EvolutionStageId))
.ExecuteCommandAsync();
}
// 6. 回填模板的 DefaultEvolutionId 和 CoverImageUrl
template.DefaultEvolutionId = initialEvolutionId;
template.CoverImageUrl = initialCoverUrl;
await petTemplateRepository.UpdateAsync(template);
});
logger.LogInformation("创建宠物模板成功Id: {Id}, 初始进化Id: {EvoId}", template.Id, template.DefaultEvolutionId);
return BuildTemplateOutput(template);
}
/// <summary>
/// 更新宠物模板
/// </summary>
public async Task<PetTemplateOutput> UpdateTemplateAsync(long id, PetTemplateInput input)
{
var template = await petTemplateRepository.GetByIdAsync(id);
if (template == null || template.IsDeleted)
throw new BusinessException("宠物模板不存在", 404);
if (string.IsNullOrWhiteSpace(input.Name))
throw new BusinessException("模板名称不能为空", 400);
template.Name = input.Name.Trim();
template.Description = input.Description;
template.IconUrl = input.IconUrl;
template.SortOrder = input.SortOrder;
template.Type = Enum.Parse<PetTemplateTypeEnum>(input.Type, true);
template.UpdatedBy = "System";
template.UpdatedAt = DateTime.Now;
var result = await petTemplateRepository.UpdateAsync(template);
if (!result)
throw new BusinessException("更新宠物模板失败", 500);
logger.LogInformation("更新宠物模板成功Id: {Id}", id);
return BuildTemplateOutput(template);
}
/// <summary>
/// 删除宠物模板(软删除,校验是否有用户宠物关联)
/// </summary>
public async Task DeleteTemplateAsync(long id)
{
var template = await petTemplateRepository.GetByIdAsync(id);
if (template == null || template.IsDeleted)
throw new BusinessException("宠物模板不存在", 404);
// 校验是否有用户宠物实例关联
var hasUserPet = petRepository.Context.Queryable<UserPet>()
.Any(p => p.TemplateId == id && !p.IsDeleted);
if (hasUserPet)
throw new BusinessException("该模板下存在用户宠物实例,无法删除", 400);
template.IsDeleted = true;
template.UpdatedBy = "System";
template.UpdatedAt = DateTime.Now;
await petTemplateRepository.UpdateAsync(template);
logger.LogInformation("删除宠物模板成功Id: {Id}", id);
}
/// <summary>
/// 获取单个宠物模板
/// </summary>
public async Task<PetTemplateOutput> GetTemplateByIdAsync(long id)
{
var template = await petTemplateRepository.GetByIdAsync(id);
if (template == null || template.IsDeleted)
throw new BusinessException("宠物模板不存在", 404);
return BuildTemplateOutput(template);
}
/// <summary>
/// 分页查询宠物模板列表
/// </summary>
public async Task<PageListModel<PetTemplateOutput>> GetTemplatesAsync(PetTemplateQueryInput input)
{
RefAsync<int> totalNumber = 0;
var query = petTemplateRepository.Queryable()
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), t => t.Name.Contains(input.Name))
.WhereIF(!string.IsNullOrWhiteSpace(input.Type), t => t.Type.ToString() == input.Type)
.WhereIF(!string.IsNullOrWhiteSpace(input.Status), t => t.Status.ToString() == input.Status)
.OrderBy(t => t.SortOrder)
.OrderByDescending(t => t.CreatedAt);
var list = await query
.Select(t => new PetTemplateOutput
{
Id = t.Id,
Name = t.Name,
Description = t.Description,
DefaultEvolutionId = t.DefaultEvolutionId,
IconUrl = t.IconUrl,
CoverImageUrl = t.CoverImageUrl,
SortOrder = t.SortOrder,
Type = t.Type.ToString(),
Status = t.Status.ToString(),
CreatedBy = t.CreatedBy,
CreatedAt = t.CreatedAt,
UpdatedBy = t.UpdatedBy,
UpdatedAt = t.UpdatedAt
})
.ToPageListAsync(input.PageIndex, input.PageSize, totalNumber);
return new PageListModel<PetTemplateOutput>(list, input.PageIndex, input.PageSize, totalNumber);
}
/// <summary>
/// 更新模板状态(启用/禁用)
/// </summary>
public async Task UpdateTemplateStatusAsync(long id)
{
var template = await petTemplateRepository.GetByIdAsync(id);
if (template == null || template.IsDeleted)
throw new BusinessException("宠物模板不存在", 404);
template.Status = template.Status == (int)PetTemplateStatusEnum.Active
? (int)PetTemplateStatusEnum.Inactive
: (int)PetTemplateStatusEnum.Active;
template.UpdatedBy = "System";
template.UpdatedAt = DateTime.Now;
await petTemplateRepository.UpdateAsync(template);
logger.LogInformation("更新模板状态成功Id: {Id}, Status: {Status}", id, template.Status);
}
private static PetTemplateOutput BuildTemplateOutput(PetTemplate t)
{
return new PetTemplateOutput
{
Id = t.Id,
Name = t.Name,
Description = t.Description,
DefaultEvolutionId = t.DefaultEvolutionId,
IconUrl = t.IconUrl,
CoverImageUrl = t.CoverImageUrl,
SortOrder = t.SortOrder,
Type = t.Type.ToString(),
Status = t.Status.ToString(),
CreatedBy = t.CreatedBy,
CreatedAt = t.CreatedAt,
UpdatedBy = t.UpdatedBy,
UpdatedAt = t.UpdatedAt
};
}
// ==================== 后台管理:进化链 ====================
/// <summary>
/// 创建进化阶段
/// </summary>
public async Task<PetEvolutionOutput> CreateEvolutionAsync(PetEvolutionInput input)
{
if (input.TemplateId <= 0)
throw new BusinessException("模板Id不能为空", 400);
if (string.IsNullOrWhiteSpace(input.StageName))
throw new BusinessException("阶段名称不能为空", 400);
// 校验模板是否存在
var templateExists = petTemplateRepository.Context.Queryable<PetTemplate>()
.Any(t => t.Id == input.TemplateId && !t.IsDeleted);
if (!templateExists)
throw new BusinessException("宠物模板不存在", 404);
var evolution = new PetEvolution
{
TemplateId = input.TemplateId,
StageName = input.StageName.Trim(),
StageLevel = input.StageLevel,
RequiredGrowth = input.RequiredGrowth,
PreviousEvolutionId = input.PreviousEvolutionId,
Type = Enum.Parse<PetEvolutionTypeEnum>(input.Type, true),
Status = (int)PetEvolutionStatusEnum.Active,
CreatedBy = "System",
CreatedAt = DateTime.Now,
UpdatedBy = "System",
UpdatedAt = DateTime.Now,
IsDeleted = false
};
var result = await petEvolutionRepository.InsertAsync(evolution);
if (!result)
throw new BusinessException("创建进化阶段失败", 500);
logger.LogInformation("创建进化阶段成功Id: {Id}, StageName: {StageName}", evolution.Id, evolution.StageName);
return BuildEvolutionOutput(evolution);
}
/// <summary>
/// 更新进化阶段
/// </summary>
public async Task<PetEvolutionOutput> UpdateEvolutionAsync(long id, PetEvolutionInput input)
{
var evolution = await petEvolutionRepository.GetByIdAsync(id);
if (evolution == null || evolution.IsDeleted)
throw new BusinessException("进化阶段不存在", 404);
if (string.IsNullOrWhiteSpace(input.StageName))
throw new BusinessException("阶段名称不能为空", 400);
evolution.TemplateId = input.TemplateId;
evolution.StageName = input.StageName.Trim();
evolution.StageLevel = input.StageLevel;
evolution.RequiredGrowth = input.RequiredGrowth;
evolution.PreviousEvolutionId = input.PreviousEvolutionId;
evolution.Type = Enum.Parse<PetEvolutionTypeEnum>(input.Type, true);
evolution.UpdatedBy = "System";
evolution.UpdatedAt = DateTime.Now;
var result = await petEvolutionRepository.UpdateAsync(evolution);
if (!result)
throw new BusinessException("更新进化阶段失败", 500);
logger.LogInformation("更新进化阶段成功Id: {Id}", id);
return BuildEvolutionOutput(evolution);
}
/// <summary>
/// 删除进化阶段(软删除,校验是否有用户宠物处于该形态)
/// </summary>
public async Task DeleteEvolutionAsync(long id)
{
var evolution = await petEvolutionRepository.GetByIdAsync(id);
if (evolution == null || evolution.IsDeleted)
throw new BusinessException("进化阶段不存在", 404);
// 校验是否有用户宠物处于该形态
var hasUserPet = petRepository.Context.Queryable<UserPet>()
.Any(p => p.CurrentEvolutionId == id && !p.IsDeleted);
if (hasUserPet)
throw new BusinessException("有用户宠物正处于该形态,无法删除", 400);
evolution.IsDeleted = true;
evolution.UpdatedBy = "System";
evolution.UpdatedAt = DateTime.Now;
await petEvolutionRepository.UpdateAsync(evolution);
logger.LogInformation("删除进化阶段成功Id: {Id}", id);
}
/// <summary>
/// 获取单个进化阶段
/// </summary>
public async Task<PetEvolutionOutput> GetEvolutionByIdAsync(long id)
{
var evolution = await petEvolutionRepository.GetByIdAsync(id);
if (evolution == null || evolution.IsDeleted)
throw new BusinessException("进化阶段不存在", 404);
return BuildEvolutionOutput(evolution);
}
/// <summary>
/// 分页查询进化阶段列表
/// </summary>
public async Task<PageListModel<PetEvolutionOutput>> GetEvolutionsAsync(PetEvolutionQueryInput input)
{
RefAsync<int> totalNumber = 0;
var query = petEvolutionRepository.Queryable()
.Where(e => e.TemplateId == input.TemplateId)
.WhereIF(!string.IsNullOrWhiteSpace(input.StageName), e => e.StageName.Contains(input.StageName))
.OrderBy(e => e.StageLevel);
var list = await query
.Select(e => new PetEvolutionOutput
{
Id = e.Id,
TemplateId = e.TemplateId,
StageName = e.StageName,
StageLevel = e.StageLevel,
RequiredGrowth = e.RequiredGrowth,
PreviousEvolutionId = e.PreviousEvolutionId,
Type = e.Type.ToString(),
Status = e.Status.ToString(),
CreatedBy = e.CreatedBy,
CreatedAt = e.CreatedAt,
UpdatedBy = e.UpdatedBy,
UpdatedAt = e.UpdatedAt
})
.ToPageListAsync(input.PageIndex, input.PageSize, totalNumber);
return new PageListModel<PetEvolutionOutput>(list, input.PageIndex, input.PageSize, totalNumber);
}
private static PetEvolutionOutput BuildEvolutionOutput(PetEvolution e)
{
return new PetEvolutionOutput
{
Id = e.Id,
TemplateId = e.TemplateId,
StageName = e.StageName,
StageLevel = e.StageLevel,
RequiredGrowth = e.RequiredGrowth,
PreviousEvolutionId = e.PreviousEvolutionId,
Type = e.Type.ToString(),
Status = e.Status.ToString(),
CreatedBy = e.CreatedBy,
CreatedAt = e.CreatedAt,
UpdatedBy = e.UpdatedBy,
UpdatedAt = e.UpdatedAt
};
}
// ==================== 后台管理:皮肤 ====================
/// <summary>
/// 创建皮肤
/// </summary>
public async Task<PetSkinOutput> CreateSkinAsync(PetSkinInput input)
{
if (input.TemplateId <= 0)
throw new BusinessException("模板Id不能为空", 400);
if (string.IsNullOrWhiteSpace(input.Name))
throw new BusinessException("皮肤名称不能为空", 400);
// 校验模板是否存在
var templateExists = petTemplateRepository.Context.Queryable<PetTemplate>()
.Any(t => t.Id == input.TemplateId && !t.IsDeleted);
if (!templateExists)
throw new BusinessException("宠物模板不存在", 404);
var skin = new PetSkin
{
TemplateId = input.TemplateId,
Name = input.Name.Trim(),
Description = input.Description,
Rarity = input.Rarity,
SortOrder = input.SortOrder,
Type = Enum.Parse<PetSkinTypeEnum>(input.Type, true),
CoverImageUrl = input.CoverImageUrl,
CreatedBy = "System",
CreatedAt = DateTime.Now,
UpdatedBy = "System",
UpdatedAt = DateTime.Now,
IsDeleted = false
};
var result = await petSkinRepository.InsertAsync(skin);
if (!result)
throw new BusinessException("创建皮肤失败", 500);
logger.LogInformation("创建皮肤成功Id: {Id}, Name: {Name}", skin.Id, skin.Name);
return BuildSkinOutput(skin, null);
}
/// <summary>
/// 更新皮肤
/// </summary>
public async Task<PetSkinOutput> UpdateSkinAsync(long id, PetSkinInput input)
{
var skin = await petSkinRepository.GetByIdAsync(id);
if (skin == null || skin.IsDeleted)
throw new BusinessException("皮肤不存在", 404);
if (string.IsNullOrWhiteSpace(input.Name))
throw new BusinessException("皮肤名称不能为空", 400);
skin.TemplateId = input.TemplateId;
skin.Name = input.Name.Trim();
skin.Description = input.Description;
skin.Rarity = input.Rarity;
skin.SortOrder = input.SortOrder;
skin.Type = Enum.Parse<PetSkinTypeEnum>(input.Type, true);
skin.CoverImageUrl = input.CoverImageUrl;
skin.UpdatedBy = "System";
skin.UpdatedAt = DateTime.Now;
var result = await petSkinRepository.UpdateAsync(skin);
if (!result)
throw new BusinessException("更新皮肤失败", 500);
logger.LogInformation("更新皮肤成功Id: {Id}", id);
// 查询关联图片
var images = await petSkinImageRepository.Queryable()
.Where(i => i.SkinId == id && !i.IsDeleted)
.OrderBy(i => i.SortOrder)
.ToListAsync();
return BuildSkinOutput(skin, images);
}
/// <summary>
/// 删除皮肤(软删除,校验是否有用户宠物装备中)
/// </summary>
public async Task DeleteSkinAsync(long id)
{
var skin = await petSkinRepository.GetByIdAsync(id);
if (skin == null || skin.IsDeleted)
throw new BusinessException("皮肤不存在", 404);
// 校验是否有用户宠物正在使用该皮肤
var inUse = petRepository.Context.Queryable<UserPet>()
.Any(p => p.CurrentSkinId == id && !p.IsDeleted);
if (inUse)
throw new BusinessException("有用户宠物正在使用该皮肤,无法删除", 400);
skin.IsDeleted = true;
skin.UpdatedBy = "System";
skin.UpdatedAt = DateTime.Now;
await petSkinRepository.UpdateAsync(skin);
logger.LogInformation("删除皮肤成功Id: {Id}", id);
}
/// <summary>
/// 获取单个皮肤(含图片列表)
/// </summary>
public async Task<PetSkinOutput> GetSkinByIdAsync(long id)
{
var skin = await petSkinRepository.GetByIdAsync(id);
if (skin == null || skin.IsDeleted)
throw new BusinessException("皮肤不存在", 404);
var images = await petSkinImageRepository.Queryable()
.Where(i => i.SkinId == id && !i.IsDeleted)
.OrderBy(i => i.SortOrder)
.ToListAsync();
return BuildSkinOutput(skin, images);
}
/// <summary>
/// 分页查询皮肤列表
/// </summary>
public async Task<PageListModel<PetSkinOutput>> GetSkinsAsync(PetSkinQueryInput input)
{
RefAsync<int> totalNumber = 0;
var query = petSkinRepository.Queryable()
.Where(s => s.TemplateId == input.TemplateId)
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), s => s.Name.Contains(input.Name))
.WhereIF(!string.IsNullOrWhiteSpace(input.Rarity), s => s.Rarity == input.Rarity)
.OrderBy(s => s.SortOrder)
.OrderByDescending(s => s.CreatedAt);
var list = await query
.Select(s => new PetSkinOutput
{
Id = s.Id,
TemplateId = s.TemplateId,
Name = s.Name,
Description = s.Description,
Rarity = s.Rarity,
SortOrder = s.SortOrder,
Type = s.Type.ToString(),
CoverImageUrl = s.CoverImageUrl,
CreatedBy = s.CreatedBy,
CreatedAt = s.CreatedAt,
UpdatedBy = s.UpdatedBy,
UpdatedAt = s.UpdatedAt
})
.ToPageListAsync(input.PageIndex, input.PageSize, totalNumber);
// 批量查询预览图片
var skinIds = list.Select(s => s.Id).ToList();
if (skinIds.Count > 0)
{
var allImages = await petSkinImageRepository.Queryable()
.Where(i => skinIds.Contains(i.SkinId) && !i.IsDeleted)
.OrderBy(i => i.SortOrder)
.ToListAsync();
var imageMap = allImages
.GroupBy(i => i.SkinId)
.ToDictionary(g => g.Key, g => g.Select(i => BuildSkinImageOutput(i)).ToList());
foreach (var skin in list)
{
if (imageMap.ContainsKey(skin.Id))
skin.Images = imageMap[skin.Id];
}
}
return new PageListModel<PetSkinOutput>(list, input.PageIndex, input.PageSize, totalNumber);
}
private static PetSkinOutput BuildSkinOutput(PetSkin s, List<PetSkinImage>? images)
{
return new PetSkinOutput
{
Id = s.Id,
TemplateId = s.TemplateId,
Name = s.Name,
Description = s.Description,
Rarity = s.Rarity,
SortOrder = s.SortOrder,
Type = s.Type.ToString(),
CoverImageUrl = s.CoverImageUrl,
CreatedBy = s.CreatedBy,
CreatedAt = s.CreatedAt,
UpdatedBy = s.UpdatedBy,
UpdatedAt = s.UpdatedAt,
Images = images?.Select(BuildSkinImageOutput).ToList()
};
}
// ==================== 后台管理:皮肤图片 ====================
/// <summary>
/// 创建皮肤图片
/// </summary>
public async Task<PetSkinImageOutput> CreateSkinImageAsync(PetSkinImageInput input)
{
if (input.SkinId <= 0)
throw new BusinessException("皮肤Id不能为空", 400);
if (input.EvolutionStageId <= 0)
throw new BusinessException("进化阶段Id不能为空", 400);
if (string.IsNullOrWhiteSpace(input.ImageUrl))
throw new BusinessException("图片地址不能为空", 400);
// 校验皮肤是否存在
var skinExists = petSkinRepository.Context.Queryable<PetSkin>()
.Any(s => s.Id == input.SkinId && !s.IsDeleted);
if (!skinExists)
throw new BusinessException("皮肤不存在", 404);
var image = new PetSkinImage
{
SkinId = input.SkinId,
EvolutionStageId = input.EvolutionStageId,
ImageUrl = input.ImageUrl.Trim(),
SortOrder = input.SortOrder,
Type = Enum.Parse<PetSkinImageTypeEnum>(input.Type, true),
CreatedBy = "System",
CreatedAt = DateTime.Now,
UpdatedBy = "System",
UpdatedAt = DateTime.Now,
IsDeleted = false
};
var result = await petSkinImageRepository.InsertAsync(image);
if (!result)
throw new BusinessException("创建皮肤图片失败", 500);
logger.LogInformation("创建皮肤图片成功Id: {Id}, SkinId: {SkinId}", image.Id, image.SkinId);
return BuildSkinImageOutput(image);
}
/// <summary>
/// 更新皮肤图片
/// </summary>
public async Task<PetSkinImageOutput> UpdateSkinImageAsync(long id, PetSkinImageInput input)
{
var image = await petSkinImageRepository.GetByIdAsync(id);
if (image == null || image.IsDeleted)
throw new BusinessException("皮肤图片不存在", 404);
if (string.IsNullOrWhiteSpace(input.ImageUrl))
throw new BusinessException("图片地址不能为空", 400);
image.SkinId = input.SkinId;
image.EvolutionStageId = input.EvolutionStageId;
image.ImageUrl = input.ImageUrl.Trim();
image.SortOrder = input.SortOrder;
image.Type = Enum.Parse<PetSkinImageTypeEnum>(input.Type, true);
image.UpdatedBy = "System";
image.UpdatedAt = DateTime.Now;
var result = await petSkinImageRepository.UpdateAsync(image);
if (!result)
throw new BusinessException("更新皮肤图片失败", 500);
logger.LogInformation("更新皮肤图片成功Id: {Id}", id);
return BuildSkinImageOutput(image);
}
/// <summary>
/// 删除皮肤图片(软删除)
/// </summary>
public async Task DeleteSkinImageAsync(long id)
{
var image = await petSkinImageRepository.GetByIdAsync(id);
if (image == null || image.IsDeleted)
throw new BusinessException("皮肤图片不存在", 404);
image.IsDeleted = true;
image.UpdatedBy = "System";
image.UpdatedAt = DateTime.Now;
await petSkinImageRepository.UpdateAsync(image);
logger.LogInformation("删除皮肤图片成功Id: {Id}", id);
}
/// <summary>
/// 获取指定皮肤的所有图片
/// </summary>
public async Task<List<PetSkinImageOutput>> GetSkinImagesBySkinIdAsync(long skinId)
{
var images = await petSkinImageRepository.Queryable()
.Where(i => i.SkinId == skinId && !i.IsDeleted)
.OrderBy(i => i.SortOrder)
.ToListAsync();
return images.Select(BuildSkinImageOutput).ToList();
}
private static PetSkinImageOutput BuildSkinImageOutput(PetSkinImage i)
{
return new PetSkinImageOutput
{
Id = i.Id,
SkinId = i.SkinId,
EvolutionStageId = i.EvolutionStageId,
ImageUrl = i.ImageUrl,
SortOrder = i.SortOrder,
Type = i.Type.ToString(),
CreatedBy = i.CreatedBy,
CreatedAt = i.CreatedAt,
UpdatedBy = i.UpdatedBy,
UpdatedAt = i.UpdatedAt
};
}
}