using Microsoft.Extensions.Logging; using QYZH.InteractiveMagazine.Infrastructure.OSS; 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; /// /// 宠物服务实现 /// public class PetService( BaseRepository petRepository, BaseRepository petTemplateRepository, BaseRepository feedingRecordRepository, BaseRepository petEvolutionRepository, BaseRepository petSkinImageRepository, BaseRepository petSkinRepository, OssService ossService, ILogger logger) : BaseRepository, IPetService { private OssImageHelper CreateImageHelper() => new(ossService, logger); /// /// 获取用户宠物信息(含当前形态名称、皮肤图片序列) /// public async Task 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 }; } /// /// 为用户创建默认宠物(最低形态、成长值为0、未激活状态) /// public async Task CreateDefaultPetAsync(long userId) { logger.LogInformation("为用户创建默认宠物,UserId: {UserId}", userId); // 检查用户是否已有宠物 var exists = petRepository.Context.Queryable() .Any(p => p.UserId == userId); if (exists) { logger.LogWarning("用户已存在宠物,跳过创建,UserId: {UserId}", userId); return; } // 查询默认宠物模板(取排序权重最低且状态为Active的模板) var defaultTemplate = await petTemplateRepository.Queryable() .Where(t => t.Status == (int)DefaultStatusEnum.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)DefaultStatusEnum.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); } /// /// 激活宠物(将状态从 Inactive 改为 Active) /// 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); } /// /// 喂养宠物(增加成长值 + 记录喂养记录 + 触发进化检查) /// public async Task 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); } // 事务保证一致性 await UseTranAsync(async () => { result = await FeedPetInTranAsync(userId, input); }); logger.LogInformation("喂养宠物成功,PetId: {PetId}, 成长值: {Before} -> {After}, 进化: {HasEvolved}", input.PetId, result.GrowthBefore, result.GrowthAfter, result.HasEvolved); return result; } /// /// 喂养宠物(无事务,需在外部事务中调用) /// public async Task FeedPetInTranAsync(long userId, FeedPetInput input) { // 查询宠物 var pet = await petRepository.GetByIdAsync(input.PetId); if (pet == null || pet.IsDeleted) throw new BusinessException("宠物不存在", 404); if (pet.UserId != userId) throw new BusinessException("无权操作该宠物", 403); if (pet.Status != (int)UserPetStatusEnum.Active) throw new BusinessException("宠物未激活,无法喂养", 400); var growthBefore = pet.GrowthPoints; var growthAfter = growthBefore + input.GrowthPoints; var hasEvolved = false; string? evolvedStageName = null; // 累加成长值和喂养次数 var updateResult = await petRepository.Context.Updateable() .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)DefaultStatusEnum.Active) .OrderBy(e => e.RequiredGrowth, OrderByType.Desc) .FirstAsync(); if (nextEvolution != null) { // 触发进化 var evolveResult = await petRepository.Context.Updateable() .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 }; } /// /// 获取宠物喂养记录列表 /// public async Task> GetFeedingRecordsAsync(long userId, long petId, PageQueryModel pageQuery) { logger.LogInformation("查询喂养记录,UserId: {UserId}, PetId: {PetId}, PageIndex: {PageIndex}, PageSize: {PageSize}", userId, petId, pageQuery.PageIndex, pageQuery.PageSize); RefAsync 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(records, pageQuery.PageIndex, pageQuery.PageSize, totalNumber); } // ==================== 后台管理:宠物模板 ==================== /// /// 创建宠物模板(事务:同时创建进化链、默认皮肤及图片) /// public async Task 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(input.Type, true), Status = (int)DefaultStatusEnum.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(evoInput.Type, true), Status = (int)DefaultStatusEnum.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) { var helper = CreateImageHelper(); 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(imgInput.Type, true), CreatedBy = "System", CreatedAt = DateTime.Now, UpdatedBy = "System", UpdatedAt = DateTime.Now, IsDeleted = false }; await petSkinImageRepository.InsertAsync(skinImage); // 搬运图片到正式目录 if (OssImageHelper.IsTempImage(skinImage.ImageUrl)) { skinImage.ImageUrl = await helper.MoveToFormalAsync( skinImage.ImageUrl, $"pet/template/{template.Id}/stage/{evolution.Id}"); await petSkinImageRepository.UpdateAsync(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("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() .Where(e => e.TemplateId == template.Id && !e.IsDeleted) .Select(e => e.Id) .ToListAsync(); if (evolutionIds.Count > 0) { await petSkinImageRepository.Context.Updateable() .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); } /// /// 更新宠物模板 /// public async Task 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(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); } /// /// 删除宠物模板(软删除,校验是否有用户宠物关联) /// 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() .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); } /// /// 获取单个宠物模板 /// public async Task GetTemplateByIdAsync(long id) { var template = await petTemplateRepository.GetByIdAsync(id); if (template == null || template.IsDeleted) throw new BusinessException("宠物模板不存在", 404); return BuildTemplateOutput(template); } /// /// 分页查询宠物模板列表 /// public async Task> GetTemplatesAsync(PetTemplateQueryInput input) { RefAsync 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(list, input.PageIndex, input.PageSize, totalNumber); } /// /// 更新模板状态(启用/禁用) /// 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)DefaultStatusEnum.Active ? (int)DefaultStatusEnum.Inactive : (int)DefaultStatusEnum.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 }; } // ==================== 后台管理:进化链 ==================== /// /// 创建进化阶段 /// public async Task 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() .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(input.Type, true), Status = (int)DefaultStatusEnum.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); } /// /// 更新进化阶段 /// public async Task 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(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); } /// /// 删除进化阶段(软删除,校验是否有用户宠物处于该形态) /// 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() .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); } /// /// 获取单个进化阶段 /// public async Task GetEvolutionByIdAsync(long id) { var evolution = await petEvolutionRepository.GetByIdAsync(id); if (evolution == null || evolution.IsDeleted) throw new BusinessException("进化阶段不存在", 404); return BuildEvolutionOutput(evolution); } /// /// 分页查询进化阶段列表 /// public async Task> GetEvolutionsAsync(PetEvolutionQueryInput input) { RefAsync 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(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 }; } // ==================== 后台管理:皮肤 ==================== /// /// 创建皮肤 /// public async Task 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() .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(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); // 搬运封面图到正式目录 if (OssImageHelper.IsTempImage(skin.CoverImageUrl)) { var helper = CreateImageHelper(); skin.CoverImageUrl = await helper.MoveToFormalAsync(skin.CoverImageUrl, $"pet/skin/{skin.Id}"); await petSkinRepository.UpdateAsync(skin); } logger.LogInformation("创建皮肤成功,Id: {Id}, Name: {Name}", skin.Id, skin.Name); return BuildSkinOutput(skin, null); } /// /// 更新皮肤 /// public async Task 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(input.Type, true); // 搬运封面图到正式目录 var newCoverUrl = input.CoverImageUrl; if (OssImageHelper.IsTempImage(newCoverUrl)) { var helper = CreateImageHelper(); newCoverUrl = await helper.MoveToFormalAsync(newCoverUrl, $"pet/skin/{skin.Id}"); } skin.CoverImageUrl = newCoverUrl; 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); } /// /// 删除皮肤(软删除,校验是否有用户宠物装备中) /// 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() .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); } /// /// 获取单个皮肤(含图片列表) /// public async Task 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); } /// /// 分页查询皮肤列表 /// public async Task> GetSkinsAsync(PetSkinQueryInput input) { RefAsync 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(list, input.PageIndex, input.PageSize, totalNumber); } private static PetSkinOutput BuildSkinOutput(PetSkin s, List? 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() }; } // ==================== 后台管理:皮肤图片 ==================== /// /// 创建皮肤图片 /// public async Task 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() .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(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); // 搬运图片到正式目录 if (OssImageHelper.IsTempImage(image.ImageUrl)) { var helper = CreateImageHelper(); image.ImageUrl = await helper.MoveToFormalAsync(image.ImageUrl, $"pet/skinimage/{image.Id}"); await petSkinImageRepository.UpdateAsync(image); } logger.LogInformation("创建皮肤图片成功,Id: {Id}, SkinId: {SkinId}", image.Id, image.SkinId); return BuildSkinImageOutput(image); } /// /// 更新皮肤图片 /// public async Task 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; // 搬运图片到正式目录 var newImageUrl = input.ImageUrl.Trim(); if (OssImageHelper.IsTempImage(newImageUrl)) { var helper = CreateImageHelper(); newImageUrl = await helper.MoveToFormalAsync(newImageUrl, $"pet/skinimage/{image.Id}"); } image.ImageUrl = newImageUrl; image.SortOrder = input.SortOrder; image.Type = Enum.Parse(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); } /// /// 删除皮肤图片(软删除) /// 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); } /// /// 获取指定皮肤的所有图片 /// public async Task> 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 }; } // ==================== 便捷查询接口 ==================== /// /// 根据模板Id获取进化链列表(非分页,按StageLevel排序) /// public async Task> GetEvolutionsByTemplateIdAsync(long templateId) { if (templateId <= 0) throw new BusinessException("模板Id不能为空", 400); var list = await petEvolutionRepository.Queryable() .Where(e => e.TemplateId == templateId && !e.IsDeleted) .OrderBy(e => e.StageLevel) .ToListAsync(); return list.Select(BuildEvolutionOutput).ToList(); } /// /// 根据模板Id获取皮肤列表(非分页,按SortOrder排序) /// public async Task> GetSkinsByTemplateIdAsync(long templateId) { if (templateId <= 0) throw new BusinessException("模板Id不能为空", 400); var list = await petSkinRepository.Queryable() .Where(s => s.TemplateId == templateId && !s.IsDeleted) .OrderBy(s => s.SortOrder) .ToListAsync(); var result = list.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 }).ToList(); // 批量查询关联图片 var skinIds = result.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(BuildSkinImageOutput).ToList()); foreach (var skin in result) { if (imageMap.ContainsKey(skin.Id)) skin.Images = imageMap[skin.Id]; } } return result; } /// /// 根据皮肤Id获取各阶段图片列表(按进化阶段分组,含阶段基本信息) /// public async Task> GetSkinImagesGroupedBySkinIdAsync(long skinId) { if (skinId <= 0) throw new BusinessException("皮肤Id不能为空", 400); // 1. 获取皮肤信息 var skin = await petSkinRepository.GetByIdAsync(skinId); if (skin == null || skin.IsDeleted) throw new BusinessException("皮肤不存在", 404); // 2. 获取该模板的所有进化阶段(按阶段等级排序) var evolutions = await petEvolutionRepository.Queryable() .Where(e => e.TemplateId == skin.TemplateId && !e.IsDeleted) .OrderBy(e => e.StageLevel) .ToListAsync(); // 3. 获取该皮肤的所有图片 var images = await petSkinImageRepository.Queryable() .Where(i => i.SkinId == skinId && !i.IsDeleted) .OrderBy(i => i.SortOrder) .ToListAsync(); var imagesByStage = images .GroupBy(i => i.EvolutionStageId) .ToDictionary(g => g.Key, g => g.Select(BuildSkinImageOutput).ToList()); // 4. 组装结果:每个进化阶段 + 对应的图片列表 return evolutions.Select(e => new SkinEvolutionStageOutput { EvolutionId = e.Id, StageName = e.StageName, StageLevel = e.StageLevel, RequiredGrowth = e.RequiredGrowth, PreviousEvolutionId = e.PreviousEvolutionId, Images = imagesByStage.GetValueOrDefault(e.Id, new List()) }).ToList(); } /// /// 获取所有皮肤下拉列表(仅返回Id和Name,按SortOrder排序) /// public async Task> GetAllSkinsForSelectAsync(List? types = null) { var list = await petSkinRepository.Queryable() .Where(s => !s.IsDeleted) .WhereIF(types != null && types.Count > 0, s => types.Contains(s.Type.ToString())) .OrderBy(s => s.SortOrder) .Select(s => new SelectOptionDto { Id = s.Id, Name = s.Name }) .ToListAsync(); return list; } }