diff --git a/QYZH.InteractiveMagazine.Common/Helpers/DomainHelper.cs b/QYZH.InteractiveMagazine.Common/Helpers/DomainHelper.cs new file mode 100644 index 0000000..a0a580c --- /dev/null +++ b/QYZH.InteractiveMagazine.Common/Helpers/DomainHelper.cs @@ -0,0 +1,26 @@ +namespace QYZH.InteractiveMagazine.Common.Helpers +{ + public static class DomainHelper + { + public static string OssDomain { get; set; } + + public static string VodDomain { get; set; } + + public static string OssFullUrl(string url) => GetFullUrl(url, OssDomain); + + public static string VodFullUrl(string url) => GetFullUrl(url, VodDomain); + + private static string GetFullUrl(string url, string domain) + { + if (string.IsNullOrWhiteSpace(url)) return string.Empty; + + if (url.StartsWith("http")) return url; + + // 确保域名末尾没有斜杠,url开头有斜杠 + var cleanDomain = domain.TrimEnd('/'); + var cleanUrl = url.StartsWith("/") ? url : "/" + url; + + return $"{cleanDomain}{cleanUrl}"; + } + } +} diff --git a/QYZH.InteractiveMagazine.Infrastructure/QYZH.InteractiveMagazine.Infrastructure.csproj b/QYZH.InteractiveMagazine.Infrastructure/QYZH.InteractiveMagazine.Infrastructure.csproj index d889c8e..8c1e76c 100644 --- a/QYZH.InteractiveMagazine.Infrastructure/QYZH.InteractiveMagazine.Infrastructure.csproj +++ b/QYZH.InteractiveMagazine.Infrastructure/QYZH.InteractiveMagazine.Infrastructure.csproj @@ -1,4 +1,4 @@ - + diff --git a/QYZH.InteractiveMagazine.Infrastructure/SDK/SDKExtensions.cs b/QYZH.InteractiveMagazine.Infrastructure/SDK/SDKExtensions.cs new file mode 100644 index 0000000..6bd5608 --- /dev/null +++ b/QYZH.InteractiveMagazine.Infrastructure/SDK/SDKExtensions.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using QYZH.InteractiveMagazine.Common.Helpers; +using QYZH.InteractiveMagazine.Infrastructure.OSS; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace QYZH.InteractiveMagazine.Infrastructure.SDK +{ + public static class SDKExtensions + { + public static void AddSDKService(this IServiceCollection services, IConfiguration configuration) + { + services.Configure(configuration.GetSection(OSSOptions.Section)); + + DomainHelper.OssDomain = configuration.GetSection(OSSOptions.Section).Get()!.Domain; + + services.AddSingleton(); + services.AddSingleton(); + } + } +} diff --git a/QYZH.InteractiveMagazine.Models/Dto/Compensation/CompensationTaskDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Compensation/CompensationTaskDto.cs index 6dd243c..a992286 100644 --- a/QYZH.InteractiveMagazine.Models/Dto/Compensation/CompensationTaskDto.cs +++ b/QYZH.InteractiveMagazine.Models/Dto/Compensation/CompensationTaskDto.cs @@ -75,6 +75,11 @@ public class CompensationTaskOutput /// public class GetCompensationTasksInput { + /// + /// 按用户Id筛选 + /// + public long? UserId { get; set; } + /// /// 按状态筛选 /// diff --git a/QYZH.InteractiveMagazine.Models/Dto/Pet/PetSkinDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Pet/PetSkinDto.cs index 0b69af3..c1fac68 100644 --- a/QYZH.InteractiveMagazine.Models/Dto/Pet/PetSkinDto.cs +++ b/QYZH.InteractiveMagazine.Models/Dto/Pet/PetSkinDto.cs @@ -36,6 +36,11 @@ public class PetSkinInput /// 皮肤类型: Normal, Limited, Event /// public string Type { get; set; } = PetSkinTypeEnum.Normal.ToString(); + + /// + /// 封面图片地址 + /// + public string? CoverImageUrl { get; set; } } /// @@ -50,6 +55,10 @@ public class PetSkinOutput public string Rarity { get; set; } = string.Empty; public int SortOrder { get; set; } public string Type { get; set; } = string.Empty; + /// + /// 封面图片地址 + /// + public string? CoverImageUrl { get; set; } public string? CreatedBy { get; set; } public DateTime CreatedAt { get; set; } public string? UpdatedBy { get; set; } diff --git a/QYZH.InteractiveMagazine.Models/Dto/Pet/PetTemplateDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Pet/PetTemplateDto.cs index 92d10fb..a3c5051 100644 --- a/QYZH.InteractiveMagazine.Models/Dto/Pet/PetTemplateDto.cs +++ b/QYZH.InteractiveMagazine.Models/Dto/Pet/PetTemplateDto.cs @@ -3,7 +3,7 @@ using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Dto.Pet; /// -/// 宠物模板创建/更新输入 +/// 宠物模板创建/更新输入(包含进化链及图片) /// public class PetTemplateInput { @@ -17,11 +17,6 @@ public class PetTemplateInput /// public string? Description { get; set; } - /// - /// 默认初始进化形态Id - /// - public long DefaultEvolutionId { get; set; } - /// /// 模板图标地址 /// @@ -36,6 +31,63 @@ public class PetTemplateInput /// 模板类型: Normal, Special, Limited /// public string Type { get; set; } = PetTemplateTypeEnum.Normal.ToString(); + + /// + /// 进化链列表(创建时一并传入,每个阶段含一组图片) + /// + public List? Evolutions { get; set; } +} + +/// +/// 进化阶段输入(含该阶段的图片列表,用于模板创建时一并提交) +/// +public class PetEvolutionStageInput +{ + /// + /// 阶段名称 + /// + public string StageName { get; set; } = string.Empty; + + /// + /// 阶段等级 + /// + public int StageLevel { get; set; } + + /// + /// 进化所需成长值 + /// + public int RequiredGrowth { get; set; } + + /// + /// 进化类型: Normal, Special + /// + public string Type { get; set; } = PetEvolutionTypeEnum.Normal.ToString(); + + /// + /// 该阶段对应的默认皮肤图片列表(其中 Type="Cover" 的为封面图) + /// + public List? Images { get; set; } +} + +/// +/// 进化阶段图片输入(创建模板时使用) +/// +public class PetStageImageInput +{ + /// + /// 图片地址 + /// + public string ImageUrl { get; set; } = string.Empty; + + /// + /// 图片顺序(动画帧序号) + /// + public int SortOrder { get; set; } + + /// + /// 图片类型: Normal=普通帧, Cover=封面图 + /// + public string Type { get; set; } = PetSkinImageTypeEnum.Normal.ToString(); } /// @@ -48,6 +100,10 @@ public class PetTemplateOutput public string? Description { get; set; } public long DefaultEvolutionId { get; set; } public string? IconUrl { get; set; } + /// + /// 封面图片地址(默认取初始阶段封面图) + /// + public string? CoverImageUrl { get; set; } public int SortOrder { get; set; } public string Type { get; set; } = string.Empty; public string Status { get; set; } = string.Empty; diff --git a/QYZH.InteractiveMagazine.Models/Entity/PetEvolution.cs b/QYZH.InteractiveMagazine.Models/Entity/PetEvolution.cs index 8bc04c1..ff544a7 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/PetEvolution.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/PetEvolution.cs @@ -46,34 +46,6 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// public long? PreviousEvolutionId { get; set; } - /// - /// Desc:基础力量 - /// Default:0 - /// Nullable:False - /// - public int BaseStrength { get; set; } - - /// - /// Desc:基础敏捷 - /// Default:0 - /// Nullable:False - /// - public int BaseAgility { get; set; } - - /// - /// Desc:基础智力 - /// Default:0 - /// Nullable:False - /// - public int BaseIntelligence { get; set; } - - /// - /// Desc:基础魅力 - /// Default:0 - /// Nullable:False - /// - public int BaseCharm { get; set; } - /// /// Desc:进化类型: Normal, Special /// Default:Normal diff --git a/QYZH.InteractiveMagazine.Models/Entity/PetSkin.cs b/QYZH.InteractiveMagazine.Models/Entity/PetSkin.cs index cdb98ed..2f35c86 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/PetSkin.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/PetSkin.cs @@ -52,5 +52,12 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Nullable:False /// public PetSkinTypeEnum Type { get; set; } + + /// + /// Desc:封面图片地址 + /// Default: + /// Nullable:True + /// + public string? CoverImageUrl { get; set; } } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/PetTemplate.cs b/QYZH.InteractiveMagazine.Models/Entity/PetTemplate.cs index 4fb6752..c9dc8e3 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/PetTemplate.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/PetTemplate.cs @@ -53,5 +53,12 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// public PetTemplateTypeEnum Type { get; set; } + /// + /// Desc:封面图片地址(默认取初始阶段封面图) + /// Default: + /// Nullable:True + /// + public string? CoverImageUrl { get; set; } + } } diff --git a/QYZH.InteractiveMagazine.Models/Enum/PetSkinImageTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/PetSkinImageTypeEnum.cs index 318c243..77e14ac 100644 --- a/QYZH.InteractiveMagazine.Models/Enum/PetSkinImageTypeEnum.cs +++ b/QYZH.InteractiveMagazine.Models/Enum/PetSkinImageTypeEnum.cs @@ -16,5 +16,10 @@ public enum PetSkinImageTypeEnum /// 特殊图片 /// [Description("特殊图片")] - Special = 2 + Special = 2, + /// + /// 封面图 + /// + [Description("封面图")] + Cover = 3 } diff --git a/QYZH.InteractiveMagazine.Service/CompensationTaskService.cs b/QYZH.InteractiveMagazine.Service/CompensationTaskService.cs index 0a38e8c..0551b8f 100644 --- a/QYZH.InteractiveMagazine.Service/CompensationTaskService.cs +++ b/QYZH.InteractiveMagazine.Service/CompensationTaskService.cs @@ -98,6 +98,9 @@ public class CompensationTaskService( var query = taskRepository.Queryable() .Where(t => !t.IsDeleted); + if (input.UserId.HasValue) + query = query.Where(t => t.UserId == input.UserId.Value); + if (input.Status.HasValue) query = query.Where(t => t.Status == (int)input.Status); diff --git a/QYZH.InteractiveMagazine.Service/PetService.cs b/QYZH.InteractiveMagazine.Service/PetService.cs index 8ca3e22..caf6ccb 100644 --- a/QYZH.InteractiveMagazine.Service/PetService.cs +++ b/QYZH.InteractiveMagazine.Service/PetService.cs @@ -119,20 +119,38 @@ public class PetService( throw new Exception("宠物模板配置缺失"); } - // 查询模板对应的初始进化形态 - var initialEvolution = await petEvolutionRepository.Queryable() - .Where(e => e.TemplateId == defaultTemplate.Id - && e.PreviousEvolutionId == null - && e.Status == (int)PetEvolutionStatusEnum.Active) - .OrderBy(e => e.StageLevel) - .FirstAsync(); + // 确定初始进化形态:优先使用模板的 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 ?? 0, + CurrentEvolutionId = initialEvolution.Id, GrowthPoints = 0, FeedingCount = 0, CurrentSkinId = 0, @@ -352,34 +370,149 @@ public class PetService( // ==================== 后台管理:宠物模板 ==================== /// - /// 创建宠物模板 + /// 创建宠物模板(事务:同时创建进化链、默认皮肤及图片) /// public async Task CreateTemplateAsync(PetTemplateInput input) { if (string.IsNullOrWhiteSpace(input.Name)) throw new BusinessException("模板名称不能为空", 400); - var template = new PetTemplate + if (input.Evolutions == null || input.Evolutions.Count == 0) + throw new BusinessException("至少需要一个进化阶段", 400); + + PetTemplate template = null!; + + await UseTranAsync(async () => { - Name = input.Name.Trim(), - Description = input.Description, - DefaultEvolutionId = input.DefaultEvolutionId, - IconUrl = input.IconUrl, - SortOrder = input.SortOrder, - Type = Enum.Parse(input.Type, true), - Status = (int)PetTemplateStatusEnum.Active, - CreatedBy = "System", - CreatedAt = DateTime.Now, - UpdatedBy = "System", - UpdatedAt = DateTime.Now, - IsDeleted = false - }; + // 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)PetTemplateStatusEnum.Active, + CreatedBy = "System", + CreatedAt = DateTime.Now, + UpdatedBy = "System", + UpdatedAt = DateTime.Now, + IsDeleted = false + }; - var result = await petTemplateRepository.InsertAsync(template); - if (!result) - throw new BusinessException("创建宠物模板失败", 500); + var inserted = await petTemplateRepository.InsertAsync(template); + if (!inserted) + throw new BusinessException("创建宠物模板失败", 500); - logger.LogInformation("创建宠物模板成功,Id: {Id}, Name: {Name}", template.Id, template.Name); + // 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)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(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("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); } @@ -397,7 +530,6 @@ public class PetService( template.Name = input.Name.Trim(); template.Description = input.Description; - template.DefaultEvolutionId = input.DefaultEvolutionId; template.IconUrl = input.IconUrl; template.SortOrder = input.SortOrder; template.Type = Enum.Parse(input.Type, true); @@ -468,6 +600,7 @@ public class PetService( Description = t.Description, DefaultEvolutionId = t.DefaultEvolutionId, IconUrl = t.IconUrl, + CoverImageUrl = t.CoverImageUrl, SortOrder = t.SortOrder, Type = t.Type.ToString(), Status = t.Status.ToString(), @@ -509,6 +642,7 @@ public class PetService( Description = t.Description, DefaultEvolutionId = t.DefaultEvolutionId, IconUrl = t.IconUrl, + CoverImageUrl = t.CoverImageUrl, SortOrder = t.SortOrder, Type = t.Type.ToString(), Status = t.Status.ToString(), @@ -545,10 +679,6 @@ public class PetService( StageLevel = input.StageLevel, RequiredGrowth = input.RequiredGrowth, PreviousEvolutionId = input.PreviousEvolutionId, - BaseStrength = input.BaseStrength, - BaseAgility = input.BaseAgility, - BaseIntelligence = input.BaseIntelligence, - BaseCharm = input.BaseCharm, Type = Enum.Parse(input.Type, true), Status = (int)PetEvolutionStatusEnum.Active, CreatedBy = "System", @@ -583,10 +713,6 @@ public class PetService( evolution.StageLevel = input.StageLevel; evolution.RequiredGrowth = input.RequiredGrowth; evolution.PreviousEvolutionId = input.PreviousEvolutionId; - evolution.BaseStrength = input.BaseStrength; - evolution.BaseAgility = input.BaseAgility; - evolution.BaseIntelligence = input.BaseIntelligence; - evolution.BaseCharm = input.BaseCharm; evolution.Type = Enum.Parse(input.Type, true); evolution.UpdatedBy = "System"; evolution.UpdatedAt = DateTime.Now; @@ -654,10 +780,6 @@ public class PetService( StageLevel = e.StageLevel, RequiredGrowth = e.RequiredGrowth, PreviousEvolutionId = e.PreviousEvolutionId, - BaseStrength = e.BaseStrength, - BaseAgility = e.BaseAgility, - BaseIntelligence = e.BaseIntelligence, - BaseCharm = e.BaseCharm, Type = e.Type.ToString(), Status = e.Status.ToString(), CreatedBy = e.CreatedBy, @@ -680,10 +802,6 @@ public class PetService( StageLevel = e.StageLevel, RequiredGrowth = e.RequiredGrowth, PreviousEvolutionId = e.PreviousEvolutionId, - BaseStrength = e.BaseStrength, - BaseAgility = e.BaseAgility, - BaseIntelligence = e.BaseIntelligence, - BaseCharm = e.BaseCharm, Type = e.Type.ToString(), Status = e.Status.ToString(), CreatedBy = e.CreatedBy, @@ -720,6 +838,7 @@ public class PetService( Rarity = input.Rarity, SortOrder = input.SortOrder, Type = Enum.Parse(input.Type, true), + CoverImageUrl = input.CoverImageUrl, CreatedBy = "System", CreatedAt = DateTime.Now, UpdatedBy = "System", @@ -753,6 +872,7 @@ public class PetService( skin.Rarity = input.Rarity; skin.SortOrder = input.SortOrder; skin.Type = Enum.Parse(input.Type, true); + skin.CoverImageUrl = input.CoverImageUrl; skin.UpdatedBy = "System"; skin.UpdatedAt = DateTime.Now; @@ -834,6 +954,7 @@ public class PetService( Rarity = s.Rarity, SortOrder = s.SortOrder, Type = s.Type.ToString(), + CoverImageUrl = s.CoverImageUrl, CreatedBy = s.CreatedBy, CreatedAt = s.CreatedAt, UpdatedBy = s.UpdatedBy, @@ -875,6 +996,7 @@ public class PetService( Rarity = s.Rarity, SortOrder = s.SortOrder, Type = s.Type.ToString(), + CoverImageUrl = s.CoverImageUrl, CreatedBy = s.CreatedBy, CreatedAt = s.CreatedAt, UpdatedBy = s.UpdatedBy, diff --git a/QYZH.InteractiveMagazine.Service/UsersService.cs b/QYZH.InteractiveMagazine.Service/UsersService.cs index 51bb4cc..a6190bc 100644 --- a/QYZH.InteractiveMagazine.Service/UsersService.cs +++ b/QYZH.InteractiveMagazine.Service/UsersService.cs @@ -113,10 +113,11 @@ public class UsersService( { var tasks = await compensationTaskService.GetTasksAsync(new GetCompensationTasksInput { + UserId = userId, Status = CompensationTaskStatusEnum.Failed, - Limit = 50 + Limit = 20 }); - return tasks.Where(t => t.UserId == userId).ToList(); + return tasks; } catch (Exception ex) {