feat: 新增宠物进化分组接口、OSS图片搬运功能及配置更新
1. 新增皮肤按进化阶段分组的输出DTO 2. 新增宠物相关便捷查询接口并实现对应控制器 3. 新增OSS图片搬运辅助类,实现临时文件迁移逻辑 4. 为商品、宠物相关服务添加OSS图片自动迁移功能 5. 调整微信授权同步逻辑移除异步等待 6. 更新配置文件中的微信密钥和OSS存储信息 7. 注册SDK服务到DI容器
This commit is contained in:
@ -152,4 +152,21 @@ public interface IPetService : IBaseService<UserPet>
|
|||||||
/// 获取指定皮肤的所有图片
|
/// 获取指定皮肤的所有图片
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<List<PetSkinImageOutput>> GetSkinImagesBySkinIdAsync(long skinId);
|
Task<List<PetSkinImageOutput>> GetSkinImagesBySkinIdAsync(long skinId);
|
||||||
|
|
||||||
|
// ==================== 便捷查询接口 ====================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据模板Id获取进化链列表(非分页,按StageLevel排序)
|
||||||
|
/// </summary>
|
||||||
|
Task<List<PetEvolutionOutput>> GetEvolutionsByTemplateIdAsync(long templateId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据模板Id获取皮肤列表(非分页,按SortOrder排序)
|
||||||
|
/// </summary>
|
||||||
|
Task<List<PetSkinOutput>> GetSkinsByTemplateIdAsync(long templateId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据皮肤Id获取各阶段图片列表(按进化阶段分组,含阶段基本信息)
|
||||||
|
/// </summary>
|
||||||
|
Task<List<SkinEvolutionStageOutput>> GetSkinImagesGroupedBySkinIdAsync(long skinId);
|
||||||
}
|
}
|
||||||
|
|||||||
110
QYZH.InteractiveMagazine.Infrastructure/OSS/OssImageHelper.cs
Normal file
110
QYZH.InteractiveMagazine.Infrastructure/OSS/OssImageHelper.cs
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using QYZH.InteractiveMagazine.Common.Extensions;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Common;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Infrastructure.OSS;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// OSS 图片搬运辅助类
|
||||||
|
/// 负责将 temp 临时目录下的文件搬运到正式目录
|
||||||
|
/// </summary>
|
||||||
|
public class OssImageHelper
|
||||||
|
{
|
||||||
|
private readonly OssService _ossService;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public OssImageHelper(OssService ossService, ILogger logger)
|
||||||
|
{
|
||||||
|
_ossService = ossService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断图片是否在 temp 临时目录
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsTempImage(string? imageUrl)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(imageUrl)) return false;
|
||||||
|
return imageUrl.Contains("/temp/", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 temp 目录下的图片搬运到正式目录,返回正式路径
|
||||||
|
/// 如果图片不在 temp 目录,原样返回
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="imageUrl">原始图片 URL(可能是完整 URL 或相对路径)</param>
|
||||||
|
/// <param name="formalFolder">正式目录前缀,如 "product/makeupcard/123" 或 "pet/skin/456"</param>
|
||||||
|
/// <returns>正式路径(相对路径),搬运失败时抛出异常</returns>
|
||||||
|
public async Task<string> MoveToFormalAsync(string imageUrl, string formalFolder)
|
||||||
|
{
|
||||||
|
if (!IsTempImage(imageUrl))
|
||||||
|
return imageUrl;
|
||||||
|
|
||||||
|
// 提取 OSS 相对路径(去掉域名前缀)
|
||||||
|
var sourcePath = imageUrl.RemoveDomain();
|
||||||
|
if (string.IsNullOrWhiteSpace(sourcePath))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("图片路径解析失败,ImageUrl: {ImageUrl}", imageUrl);
|
||||||
|
return imageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL 解码(处理中文文件名)
|
||||||
|
sourcePath = Uri.UnescapeDataString(sourcePath);
|
||||||
|
|
||||||
|
// 提取文件名
|
||||||
|
var fileName = Path.GetFileName(sourcePath);
|
||||||
|
if (string.IsNullOrWhiteSpace(fileName))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("无法从路径中提取文件名,Path: {Path}", sourcePath);
|
||||||
|
return imageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成正式路径
|
||||||
|
var targetPath = $"{formalFolder}/{fileName}";
|
||||||
|
|
||||||
|
_logger.LogInformation("开始搬运图片,Source: {Source} -> Target: {Target}", sourcePath, targetPath);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 拷贝文件到正式目录
|
||||||
|
var copyResult = _ossService.CopyObject(sourcePath, targetPath);
|
||||||
|
if (!copyResult)
|
||||||
|
{
|
||||||
|
_logger.LogError("OSS 图片拷贝失败,Source: {Source}, Target: {Target}", sourcePath, targetPath);
|
||||||
|
throw new Exception("OSS 图片拷贝失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除临时文件(失败不影响主流程)
|
||||||
|
var deleteResult = _ossService.DeleteObject(sourcePath);
|
||||||
|
if (!deleteResult)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("OSS 临时文件删除失败(不影响主流程),Source: {Source}", sourcePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("图片搬运成功,Target: {Target}", targetPath);
|
||||||
|
return targetPath;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is not BusinessException)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "OSS 图片搬运异常,Source: {Source}, Target: {Target}", sourcePath, targetPath);
|
||||||
|
throw new BusinessException("图片搬运失败,请重试", 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量搬运 temp 图片到正式目录
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="imageUrls">原始图片 URL 列表</param>
|
||||||
|
/// <param name="formalFolder">正式目录前缀</param>
|
||||||
|
/// <returns>正式路径列表(顺序与输入一致)</returns>
|
||||||
|
public async Task<List<string>> MoveBatchToFormalAsync(IEnumerable<string> imageUrls, string formalFolder)
|
||||||
|
{
|
||||||
|
var results = new List<string>();
|
||||||
|
foreach (var url in imageUrls)
|
||||||
|
{
|
||||||
|
var formal = await MoveToFormalAsync(url, formalFolder);
|
||||||
|
results.Add(formal);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -138,3 +138,16 @@ public class PetSkinImageOutput
|
|||||||
public string? UpdatedBy { get; set; }
|
public string? UpdatedBy { get; set; }
|
||||||
public DateTime? UpdatedAt { get; set; }
|
public DateTime? UpdatedAt { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 皮肤按进化阶段分组的图片输出(每阶段包含阶段信息和图片列表)
|
||||||
|
/// </summary>
|
||||||
|
public class SkinEvolutionStageOutput
|
||||||
|
{
|
||||||
|
public long EvolutionId { get; set; }
|
||||||
|
public string StageName { get; set; } = string.Empty;
|
||||||
|
public int StageLevel { get; set; }
|
||||||
|
public int RequiredGrowth { get; set; }
|
||||||
|
public long? PreviousEvolutionId { get; set; }
|
||||||
|
public List<PetSkinImageOutput> Images { get; set; } = new();
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using QYZH.InteractiveMagazine.Infrastructure.OSS;
|
||||||
using QYZH.InteractiveMagazine.IService;
|
using QYZH.InteractiveMagazine.IService;
|
||||||
using QYZH.InteractiveMagazine.Models.Common;
|
using QYZH.InteractiveMagazine.Models.Common;
|
||||||
using QYZH.InteractiveMagazine.Models.Dto;
|
using QYZH.InteractiveMagazine.Models.Dto;
|
||||||
@ -20,9 +21,11 @@ public class PetService(
|
|||||||
BaseRepository<PetEvolution> petEvolutionRepository,
|
BaseRepository<PetEvolution> petEvolutionRepository,
|
||||||
BaseRepository<PetSkinImage> petSkinImageRepository,
|
BaseRepository<PetSkinImage> petSkinImageRepository,
|
||||||
BaseRepository<PetSkin> petSkinRepository,
|
BaseRepository<PetSkin> petSkinRepository,
|
||||||
|
OssService ossService,
|
||||||
ILogger<PetService> logger)
|
ILogger<PetService> logger)
|
||||||
: BaseRepository<UserPet>, IPetService
|
: BaseRepository<UserPet>, IPetService
|
||||||
{
|
{
|
||||||
|
private OssImageHelper CreateImageHelper() => new(ossService, logger);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取用户宠物信息(含当前形态名称、皮肤图片序列)
|
/// 获取用户宠物信息(含当前形态名称、皮肤图片序列)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -442,6 +445,8 @@ public class PetService(
|
|||||||
// 3. 为该阶段创建默认皮肤图片
|
// 3. 为该阶段创建默认皮肤图片
|
||||||
if (evoInput.Images != null && evoInput.Images.Count > 0)
|
if (evoInput.Images != null && evoInput.Images.Count > 0)
|
||||||
{
|
{
|
||||||
|
var helper = CreateImageHelper();
|
||||||
|
|
||||||
foreach (var imgInput in evoInput.Images.OrderBy(i => i.SortOrder))
|
foreach (var imgInput in evoInput.Images.OrderBy(i => i.SortOrder))
|
||||||
{
|
{
|
||||||
var skinImage = new PetSkinImage
|
var skinImage = new PetSkinImage
|
||||||
@ -460,6 +465,14 @@ public class PetService(
|
|||||||
|
|
||||||
await petSkinImageRepository.InsertAsync(skinImage);
|
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
|
if (previousEvolution == null
|
||||||
&& imgInput.Type == PetSkinImageTypeEnum.Cover.ToString()
|
&& imgInput.Type == PetSkinImageTypeEnum.Cover.ToString()
|
||||||
@ -850,6 +863,14 @@ public class PetService(
|
|||||||
if (!result)
|
if (!result)
|
||||||
throw new BusinessException("创建皮肤失败", 500);
|
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);
|
logger.LogInformation("创建皮肤成功,Id: {Id}, Name: {Name}", skin.Id, skin.Name);
|
||||||
return BuildSkinOutput(skin, null);
|
return BuildSkinOutput(skin, null);
|
||||||
}
|
}
|
||||||
@ -872,7 +893,16 @@ public class PetService(
|
|||||||
skin.Rarity = input.Rarity;
|
skin.Rarity = input.Rarity;
|
||||||
skin.SortOrder = input.SortOrder;
|
skin.SortOrder = input.SortOrder;
|
||||||
skin.Type = Enum.Parse<PetSkinTypeEnum>(input.Type, true);
|
skin.Type = Enum.Parse<PetSkinTypeEnum>(input.Type, true);
|
||||||
skin.CoverImageUrl = input.CoverImageUrl;
|
|
||||||
|
// 搬运封面图到正式目录
|
||||||
|
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.UpdatedBy = "System";
|
||||||
skin.UpdatedAt = DateTime.Now;
|
skin.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
@ -1045,6 +1075,14 @@ public class PetService(
|
|||||||
if (!result)
|
if (!result)
|
||||||
throw new BusinessException("创建皮肤图片失败", 500);
|
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);
|
logger.LogInformation("创建皮肤图片成功,Id: {Id}, SkinId: {SkinId}", image.Id, image.SkinId);
|
||||||
return BuildSkinImageOutput(image);
|
return BuildSkinImageOutput(image);
|
||||||
}
|
}
|
||||||
@ -1063,7 +1101,16 @@ public class PetService(
|
|||||||
|
|
||||||
image.SkinId = input.SkinId;
|
image.SkinId = input.SkinId;
|
||||||
image.EvolutionStageId = input.EvolutionStageId;
|
image.EvolutionStageId = input.EvolutionStageId;
|
||||||
image.ImageUrl = input.ImageUrl.Trim();
|
|
||||||
|
// 搬运图片到正式目录
|
||||||
|
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.SortOrder = input.SortOrder;
|
||||||
image.Type = Enum.Parse<PetSkinImageTypeEnum>(input.Type, true);
|
image.Type = Enum.Parse<PetSkinImageTypeEnum>(input.Type, true);
|
||||||
image.UpdatedBy = "System";
|
image.UpdatedBy = "System";
|
||||||
@ -1123,4 +1170,115 @@ public class PetService(
|
|||||||
UpdatedAt = i.UpdatedAt
|
UpdatedAt = i.UpdatedAt
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 便捷查询接口 ====================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据模板Id获取进化链列表(非分页,按StageLevel排序)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<List<PetEvolutionOutput>> 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据模板Id获取皮肤列表(非分页,按SortOrder排序)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<List<PetSkinOutput>> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据皮肤Id获取各阶段图片列表(按进化阶段分组,含阶段基本信息)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<List<SkinEvolutionStageOutput>> 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<PetSkinImageOutput>())
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using QYZH.InteractiveMagazine.Infrastructure.OSS;
|
||||||
using QYZH.InteractiveMagazine.IService;
|
using QYZH.InteractiveMagazine.IService;
|
||||||
using QYZH.InteractiveMagazine.Models.Common;
|
using QYZH.InteractiveMagazine.Models.Common;
|
||||||
using QYZH.InteractiveMagazine.Models.Dto;
|
using QYZH.InteractiveMagazine.Models.Dto;
|
||||||
@ -12,7 +13,10 @@ namespace QYZH.InteractiveMagazine.Service;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 商品服务实现
|
/// 商品服务实现
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ProductService(BaseRepository<Product> productRepository, ILogger<ProductService> logger) : BaseRepository<Product>, IProductService
|
public class ProductService(
|
||||||
|
BaseRepository<Product> productRepository,
|
||||||
|
OssService ossService,
|
||||||
|
ILogger<ProductService> logger) : BaseRepository<Product>, IProductService
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -62,6 +66,16 @@ public class ProductService(BaseRepository<Product> productRepository, ILogger<P
|
|||||||
throw new BusinessException("创建商品失败", 500);
|
throw new BusinessException("创建商品失败", 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将 temp 目录下的图片搬运到正式目录
|
||||||
|
if (OssImageHelper.IsTempImage(product.ImageUrl))
|
||||||
|
{
|
||||||
|
var helper = CreateImageHelper();
|
||||||
|
var folder = GetProductFormalFolder(product.Type, product.Id);
|
||||||
|
var formalPath = await helper.MoveToFormalAsync(product.ImageUrl, folder);
|
||||||
|
product.ImageUrl = formalPath;
|
||||||
|
await productRepository.UpdateAsync(product);
|
||||||
|
}
|
||||||
|
|
||||||
logger.LogInformation("商品创建成功,商品名称: {Name}, ID: {Id}", input.Name, product.Id);
|
logger.LogInformation("商品创建成功,商品名称: {Name}, ID: {Id}", input.Name, product.Id);
|
||||||
|
|
||||||
return new ProductOutput
|
return new ProductOutput
|
||||||
@ -112,9 +126,18 @@ public class ProductService(BaseRepository<Product> productRepository, ILogger<P
|
|||||||
throw new BusinessException("商品价格不能为负数", 400);
|
throw new BusinessException("商品价格不能为负数", 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将 temp 目录下的新图片搬运到正式目录
|
||||||
|
var newImageUrl = input.ImageUrl;
|
||||||
|
if (OssImageHelper.IsTempImage(newImageUrl))
|
||||||
|
{
|
||||||
|
var helper = CreateImageHelper();
|
||||||
|
var folder = GetProductFormalFolder(Enum.Parse<ProductTypeEnum>(input.Type, true), product.Id);
|
||||||
|
newImageUrl = await helper.MoveToFormalAsync(newImageUrl, folder);
|
||||||
|
}
|
||||||
|
|
||||||
product.Name = input.Name.Trim();
|
product.Name = input.Name.Trim();
|
||||||
product.Description = input.Description;
|
product.Description = input.Description;
|
||||||
product.ImageUrl = input.ImageUrl;
|
product.ImageUrl = newImageUrl;
|
||||||
product.Price = input.Price;
|
product.Price = input.Price;
|
||||||
product.Type = Enum.Parse<ProductTypeEnum>(input.Type, true);
|
product.Type = Enum.Parse<ProductTypeEnum>(input.Type, true);
|
||||||
product.Status = (int)input.Status;
|
product.Status = (int)input.Status;
|
||||||
@ -284,4 +307,21 @@ public class ProductService(BaseRepository<Product> productRepository, ILogger<P
|
|||||||
|
|
||||||
logger.LogInformation("商品上下架状态更新成功,ID: {Id}, SaleStatus: {SaleStatus}", id, product.Status);
|
logger.LogInformation("商品上下架状态更新成功,ID: {Id}, SaleStatus: {SaleStatus}", id, product.Status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region OSS 图片搬运
|
||||||
|
|
||||||
|
private OssImageHelper CreateImageHelper() => new(ossService, logger);
|
||||||
|
|
||||||
|
private static string GetProductFormalFolder(ProductTypeEnum type, long productId)
|
||||||
|
{
|
||||||
|
var typeFolder = type switch
|
||||||
|
{
|
||||||
|
ProductTypeEnum.MakeUpCard => "makeupcard",
|
||||||
|
ProductTypeEnum.PetSkin => "petskin",
|
||||||
|
_ => "other"
|
||||||
|
};
|
||||||
|
return $"product/{typeFolder}/{productId}";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@ -77,7 +77,7 @@ public class WeChatAuthService(BaseRepository<Users> usersRepository, IConfigura
|
|||||||
IsLastOnline = true
|
IsLastOnline = true
|
||||||
};
|
};
|
||||||
|
|
||||||
var insertResult = await usersRepository.Insertable(newUser).ExecuteReturnIdentityAsync();
|
var insertResult = usersRepository.Insertable(newUser).ExecuteReturnIdentity();
|
||||||
if (insertResult <= 0)
|
if (insertResult <= 0)
|
||||||
{
|
{
|
||||||
logger.LogError("创建微信用户失败,OpenId: {OpenId}", wxResponse.OpenId);
|
logger.LogError("创建微信用户失败,OpenId: {OpenId}", wxResponse.OpenId);
|
||||||
|
|||||||
@ -491,4 +491,75 @@ public class PetManageController : BaseController
|
|||||||
return BaseResponse<List<PetSkinImageOutput>>.Fail("获取皮肤图片列表失败,请稍后重试");
|
return BaseResponse<List<PetSkinImageOutput>>.Fail("获取皮肤图片列表失败,请稍后重试");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 便捷查询接口 ====================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据模板Id获取进化链列表(非分页)
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("evolutions/{templateId}")]
|
||||||
|
public async Task<BaseResponse<List<PetEvolutionOutput>>> GetEvolutionsByTemplateIdAsync(long templateId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await _petService.GetEvolutionsByTemplateIdAsync(templateId);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
catch (BusinessException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "获取进化链列表业务异常: {Message}", ex.Message);
|
||||||
|
return BaseResponse<List<PetEvolutionOutput>>.Fail(ex.Message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "获取进化链列表系统异常,TemplateId:{TemplateId}", templateId);
|
||||||
|
return BaseResponse<List<PetEvolutionOutput>>.Fail("获取进化链列表失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据模板Id获取皮肤列表(非分页)
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("skins/{templateId}")]
|
||||||
|
public async Task<BaseResponse<List<PetSkinOutput>>> GetSkinsByTemplateIdAsync(long templateId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await _petService.GetSkinsByTemplateIdAsync(templateId);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
catch (BusinessException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "获取皮肤列表业务异常: {Message}", ex.Message);
|
||||||
|
return BaseResponse<List<PetSkinOutput>>.Fail(ex.Message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "获取皮肤列表系统异常,TemplateId:{TemplateId}", templateId);
|
||||||
|
return BaseResponse<List<PetSkinOutput>>.Fail("获取皮肤列表失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据皮肤Id获取各阶段图片列表(按进化阶段分组,含阶段基本信息)
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("skin-images-grouped/{skinId}")]
|
||||||
|
public async Task<BaseResponse<List<SkinEvolutionStageOutput>>> GetSkinImagesGroupedBySkinIdAsync(long skinId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await _petService.GetSkinImagesGroupedBySkinIdAsync(skinId);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
catch (BusinessException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "获取分组皮肤图片业务异常: {Message}", ex.Message);
|
||||||
|
return BaseResponse<List<SkinEvolutionStageOutput>>.Fail(ex.Message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "获取分组皮肤图片系统异常,SkinId:{SkinId}", skinId);
|
||||||
|
return BaseResponse<List<SkinEvolutionStageOutput>>.Fail("获取分组皮肤图片失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ using QYZH.InteractiveMagazine.Infrastructure.Context;
|
|||||||
using QYZH.InteractiveMagazine.Infrastructure.Extensions;
|
using QYZH.InteractiveMagazine.Infrastructure.Extensions;
|
||||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||||
using QYZH.InteractiveMagazine.Infrastructure.Redis;
|
using QYZH.InteractiveMagazine.Infrastructure.Redis;
|
||||||
|
using QYZH.InteractiveMagazine.Infrastructure.SDK;
|
||||||
using QYZH.InteractiveMagazine.Models.Entity;
|
using QYZH.InteractiveMagazine.Models.Entity;
|
||||||
using QYZH.InteractiveMagazine.Models.Enum;
|
using QYZH.InteractiveMagazine.Models.Enum;
|
||||||
using QYZH.InteractiveMagazine.Models.Settings;
|
using QYZH.InteractiveMagazine.Models.Settings;
|
||||||
@ -162,6 +163,7 @@ builder.Services.AddCors(options =>
|
|||||||
.AllowAnyHeader();
|
.AllowAnyHeader();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
builder.Services.AddSDKService(builder.Configuration);
|
||||||
|
|
||||||
#region 配置数据压缩选项
|
#region 配置数据压缩选项
|
||||||
|
|
||||||
|
|||||||
@ -21,8 +21,8 @@
|
|||||||
"VirtualHost": "InteractiveMagazine"
|
"VirtualHost": "InteractiveMagazine"
|
||||||
},
|
},
|
||||||
"WeChatSettings": {
|
"WeChatSettings": {
|
||||||
"AppId": "your-wechat-appid",
|
"AppId": "wx7922cc9b6023f3ac",
|
||||||
"AppSecret": "your-wechat-appsecret"
|
"AppSecret": "95a1d61b7be03d1dab81fe19b7483dcf"
|
||||||
},
|
},
|
||||||
"Serilog": {
|
"Serilog": {
|
||||||
"MinimumLevel": {
|
"MinimumLevel": {
|
||||||
@ -50,12 +50,12 @@
|
|||||||
"AccessKeyID": "LTAI5tEBXGewpHSLiSxyx6Bf",
|
"AccessKeyID": "LTAI5tEBXGewpHSLiSxyx6Bf",
|
||||||
"AccessKeySecret": "w29b8wkw6XQVL8GWXgp3ZesgYeDKvf",
|
"AccessKeySecret": "w29b8wkw6XQVL8GWXgp3ZesgYeDKvf",
|
||||||
"VodBucketName": "outin-5277bbb52bec11f08dbd00163e169e2b.oss-cn-beijing.aliyuncs.com",
|
"VodBucketName": "outin-5277bbb52bec11f08dbd00163e169e2b.oss-cn-beijing.aliyuncs.com",
|
||||||
"BucketName": "qyzh-qb",
|
"BucketName": "qyzh2025test",
|
||||||
"Region": "beijing",
|
"Region": "beijing",
|
||||||
"RoleArn": "acs:ram::1064745380176636:role/aliyunosstokengeneratorrole",
|
"RoleArn": "acs:ram::1064745380176636:role/aliyunosstokengeneratorrole",
|
||||||
"DurationSeconds": 3600, //过期时间(秒)
|
"DurationSeconds": 3600, //过期时间(秒)
|
||||||
"Endpoint": "oss-cn-beijing.aliyuncs.com",
|
"Endpoint": "oss-cn-beijing.aliyuncs.com",
|
||||||
"ProjectName": "QuestionBank",
|
"ProjectName": "InteractiveMagazine",
|
||||||
"Domain": "https://qyzh-qb.oss-cn-beijing.aliyuncs.com/"
|
"Domain": "https://oss.test.qyzhjy.com/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user