2026-06-03 16:09:56 +08:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
2026-06-10 17:53:38 +08:00
|
|
|
|
using QYZH.InteractiveMagazine.Common.Helpers;
|
2026-06-09 18:02:26 +08:00
|
|
|
|
using QYZH.InteractiveMagazine.Infrastructure.OSS;
|
2026-06-03 16:09:56 +08:00
|
|
|
|
using QYZH.InteractiveMagazine.IService;
|
|
|
|
|
|
using QYZH.InteractiveMagazine.Models.Common;
|
|
|
|
|
|
using QYZH.InteractiveMagazine.Models.Dto;
|
|
|
|
|
|
using QYZH.InteractiveMagazine.Models.Entity;
|
2026-06-08 16:57:44 +08:00
|
|
|
|
using QYZH.InteractiveMagazine.Models.Enum;
|
2026-06-03 16:09:56 +08:00
|
|
|
|
using QYZH.InteractiveMagazine.Repository;
|
|
|
|
|
|
using SqlSugar;
|
|
|
|
|
|
|
|
|
|
|
|
namespace QYZH.InteractiveMagazine.Service;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 商品服务实现
|
|
|
|
|
|
/// </summary>
|
2026-06-09 18:02:26 +08:00
|
|
|
|
public class ProductService(
|
|
|
|
|
|
BaseRepository<Product> productRepository,
|
|
|
|
|
|
OssService ossService,
|
|
|
|
|
|
ILogger<ProductService> logger) : BaseRepository<Product>, IProductService
|
2026-06-03 16:09:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 创建商品
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task<ProductOutput> CreateAsync(ProductInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("正在创建商品,商品名称: {Name}", input.Name);
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(input.Name))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BusinessException("商品名称不能为空", 400);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(input.Type))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BusinessException("商品类型不能为空", 400);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (input.Price < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BusinessException("商品价格不能为负数", 400);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var product = new Product
|
|
|
|
|
|
{
|
|
|
|
|
|
Name = input.Name.Trim(),
|
|
|
|
|
|
Description = input.Description,
|
|
|
|
|
|
ImageUrl = input.ImageUrl,
|
|
|
|
|
|
Price = input.Price,
|
2026-06-08 16:57:44 +08:00
|
|
|
|
Type = Enum.Parse<ProductTypeEnum>(input.Type, true),
|
2026-06-09 11:41:28 +08:00
|
|
|
|
Status = (int)input.Status,
|
2026-06-03 16:09:56 +08:00
|
|
|
|
MetaData = input.MetaData,
|
|
|
|
|
|
IsActive = input.IsActive,
|
|
|
|
|
|
Stock = input.Stock,
|
2026-06-10 17:53:38 +08:00
|
|
|
|
IsVirtual = input.IsVirtual,
|
|
|
|
|
|
TypeId = input.TypeId,
|
2026-06-03 16:09:56 +08:00
|
|
|
|
CreatedBy = "System",
|
|
|
|
|
|
UpdatedBy = "System",
|
|
|
|
|
|
CreatedAt = DateTime.Now,
|
|
|
|
|
|
UpdatedAt = DateTime.Now,
|
|
|
|
|
|
IsDeleted = false
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var result = await productRepository.InsertAsync(product);
|
|
|
|
|
|
if (!result)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogError("商品创建失败,商品名称: {Name}", input.Name);
|
|
|
|
|
|
throw new BusinessException("创建商品失败", 500);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-09 18:02:26 +08:00
|
|
|
|
// 将 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-03 16:09:56 +08:00
|
|
|
|
logger.LogInformation("商品创建成功,商品名称: {Name}, ID: {Id}", input.Name, product.Id);
|
|
|
|
|
|
|
|
|
|
|
|
return new ProductOutput
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = product.Id,
|
|
|
|
|
|
Name = product.Name,
|
|
|
|
|
|
Description = product.Description,
|
|
|
|
|
|
ImageUrl = product.ImageUrl,
|
|
|
|
|
|
Price = product.Price,
|
2026-06-08 16:57:44 +08:00
|
|
|
|
Type = product.Type.ToString(),
|
2026-06-09 11:41:28 +08:00
|
|
|
|
Status = product.Status.ToString(),
|
2026-06-03 16:09:56 +08:00
|
|
|
|
MetaData = product.MetaData,
|
|
|
|
|
|
IsActive = product.IsActive,
|
|
|
|
|
|
Stock = product.Stock,
|
2026-06-10 17:53:38 +08:00
|
|
|
|
IsVirtual = product.IsVirtual,
|
|
|
|
|
|
TypeId = product.TypeId,
|
2026-06-03 16:09:56 +08:00
|
|
|
|
CreatedBy = product.CreatedBy,
|
|
|
|
|
|
CreatedAt = product.CreatedAt,
|
|
|
|
|
|
UpdatedBy = product.UpdatedBy,
|
|
|
|
|
|
UpdatedAt = product.UpdatedAt
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 更新商品
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task<ProductOutput> UpdateAsync(long id, ProductInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("正在更新商品,ID: {Id}", id);
|
|
|
|
|
|
|
|
|
|
|
|
var product = await productRepository.GetByIdAsync(id);
|
|
|
|
|
|
if (product == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("未找到要更新的商品,ID: {Id}", id);
|
|
|
|
|
|
throw new BusinessException("商品不存在", 404);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(input.Name))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BusinessException("商品名称不能为空", 400);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(input.Type))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BusinessException("商品类型不能为空", 400);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (input.Price < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BusinessException("商品价格不能为负数", 400);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-09 18:02:26 +08:00
|
|
|
|
// 将 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-03 16:09:56 +08:00
|
|
|
|
product.Name = input.Name.Trim();
|
|
|
|
|
|
product.Description = input.Description;
|
2026-06-09 18:02:26 +08:00
|
|
|
|
product.ImageUrl = newImageUrl;
|
2026-06-03 16:09:56 +08:00
|
|
|
|
product.Price = input.Price;
|
2026-06-08 16:57:44 +08:00
|
|
|
|
product.Type = Enum.Parse<ProductTypeEnum>(input.Type, true);
|
2026-06-09 11:41:28 +08:00
|
|
|
|
product.Status = (int)input.Status;
|
2026-06-03 16:09:56 +08:00
|
|
|
|
product.MetaData = input.MetaData;
|
|
|
|
|
|
product.IsActive = input.IsActive;
|
|
|
|
|
|
product.Stock = input.Stock;
|
2026-06-10 17:53:38 +08:00
|
|
|
|
product.IsVirtual = input.IsVirtual;
|
|
|
|
|
|
product.TypeId = input.TypeId;
|
2026-06-03 16:09:56 +08:00
|
|
|
|
product.UpdatedBy = "System";
|
|
|
|
|
|
product.UpdatedAt = DateTime.Now;
|
|
|
|
|
|
|
|
|
|
|
|
var result = await productRepository.UpdateAsync(product);
|
|
|
|
|
|
if (!result)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogError("商品更新失败,ID: {Id}", id);
|
|
|
|
|
|
throw new BusinessException("更新商品失败", 500);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("商品更新成功,ID: {Id}", id);
|
|
|
|
|
|
|
|
|
|
|
|
return new ProductOutput
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = product.Id,
|
|
|
|
|
|
Name = product.Name,
|
|
|
|
|
|
Description = product.Description,
|
|
|
|
|
|
ImageUrl = product.ImageUrl,
|
|
|
|
|
|
Price = product.Price,
|
2026-06-08 16:57:44 +08:00
|
|
|
|
Type = product.Type.ToString(),
|
2026-06-09 11:41:28 +08:00
|
|
|
|
Status = product.Status.ToString(),
|
2026-06-03 16:09:56 +08:00
|
|
|
|
MetaData = product.MetaData,
|
|
|
|
|
|
IsActive = product.IsActive,
|
|
|
|
|
|
Stock = product.Stock,
|
2026-06-10 17:53:38 +08:00
|
|
|
|
IsVirtual = product.IsVirtual,
|
|
|
|
|
|
TypeId = product.TypeId,
|
2026-06-03 16:09:56 +08:00
|
|
|
|
CreatedBy = product.CreatedBy,
|
|
|
|
|
|
CreatedAt = product.CreatedAt,
|
|
|
|
|
|
UpdatedBy = product.UpdatedBy,
|
|
|
|
|
|
UpdatedAt = product.UpdatedAt
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 删除商品(软删除)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task DeleteAsync(long id)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("正在删除商品,ID: {Id}", id);
|
|
|
|
|
|
|
|
|
|
|
|
var product = await productRepository.GetByIdAsync(id);
|
|
|
|
|
|
if (product == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("未找到要删除的商品,ID: {Id}", id);
|
|
|
|
|
|
throw new BusinessException("商品不存在", 404);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var result = await productRepository.DeleteByIdAsync(id);
|
|
|
|
|
|
if (!result)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogError("商品删除失败,ID: {Id}", id);
|
|
|
|
|
|
throw new BusinessException("删除商品失败", 500);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("商品删除成功,ID: {Id}", id);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 根据ID获取商品
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task<ProductOutput> GetByIdAsync(long id)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("正在获取商品信息,ID: {Id}", id);
|
|
|
|
|
|
|
|
|
|
|
|
var product = await productRepository.GetByIdAsync(id);
|
|
|
|
|
|
if (product == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("未找到商品,ID: {Id}", id);
|
|
|
|
|
|
throw new BusinessException("商品不存在", 404);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new ProductOutput
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = product.Id,
|
|
|
|
|
|
Name = product.Name,
|
|
|
|
|
|
Description = product.Description,
|
2026-06-10 17:53:38 +08:00
|
|
|
|
ImageUrl = DomainHelper.OssFullUrl(product.ImageUrl),
|
2026-06-03 16:09:56 +08:00
|
|
|
|
Price = product.Price,
|
2026-06-08 16:57:44 +08:00
|
|
|
|
Type = product.Type.ToString(),
|
2026-06-09 11:41:28 +08:00
|
|
|
|
Status = product.Status.ToString(),
|
2026-06-03 16:09:56 +08:00
|
|
|
|
MetaData = product.MetaData,
|
|
|
|
|
|
IsActive = product.IsActive,
|
|
|
|
|
|
Stock = product.Stock,
|
2026-06-10 17:53:38 +08:00
|
|
|
|
IsVirtual = product.IsVirtual,
|
|
|
|
|
|
TypeId = product.TypeId,
|
2026-06-03 16:09:56 +08:00
|
|
|
|
CreatedBy = product.CreatedBy,
|
|
|
|
|
|
CreatedAt = product.CreatedAt,
|
|
|
|
|
|
UpdatedBy = product.UpdatedBy,
|
|
|
|
|
|
UpdatedAt = product.UpdatedAt
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 分页查询商品列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task<PageListModel<ProductOutput>> GetListAsync(ProductQueryInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("正在查询商品列表,页码: {PageIndex}, 每页条数: {PageSize}", input.PageIndex, input.PageSize);
|
|
|
|
|
|
|
|
|
|
|
|
if (input.PageIndex <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BusinessException("页码必须大于0", 400);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (input.PageSize <= 0 || input.PageSize > 100)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BusinessException("每页条数必须在1-100之间", 400);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RefAsync<int> totalNumber = 0;
|
|
|
|
|
|
var pageResult = await productRepository.Queryable()
|
|
|
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), p => p.Name.Contains(input.Name))
|
2026-06-08 16:57:44 +08:00
|
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(input.Type), p => p.Type.ToString() == input.Type)
|
2026-06-09 11:41:28 +08:00
|
|
|
|
.WhereIF(input.Status.HasValue, p => p.Status == (int)input.Status)
|
2026-06-03 16:09:56 +08:00
|
|
|
|
.WhereIF(input.IsActive.HasValue, p => p.IsActive == input.IsActive.Value)
|
|
|
|
|
|
.OrderByDescending(p => p.CreatedAt)
|
|
|
|
|
|
.ToPageListAsync(input.PageIndex, input.PageSize, totalNumber);
|
|
|
|
|
|
|
2026-06-10 17:53:38 +08:00
|
|
|
|
var result = pageResult.Select(p => new ProductOutput
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = p.Id,
|
|
|
|
|
|
Name = p.Name,
|
|
|
|
|
|
Description = p.Description,
|
|
|
|
|
|
ImageUrl = DomainHelper.OssFullUrl(p.ImageUrl),
|
|
|
|
|
|
Price = p.Price,
|
|
|
|
|
|
Type = p.Type.ToString(),
|
|
|
|
|
|
Status = p.Status.ToString(),
|
|
|
|
|
|
MetaData = p.MetaData,
|
|
|
|
|
|
IsActive = p.IsActive,
|
|
|
|
|
|
Stock = p.Stock,
|
|
|
|
|
|
IsVirtual = p.IsVirtual,
|
|
|
|
|
|
TypeId = p.TypeId,
|
|
|
|
|
|
CreatedBy = p.CreatedBy,
|
|
|
|
|
|
CreatedAt = p.CreatedAt,
|
|
|
|
|
|
UpdatedBy = p.UpdatedBy,
|
|
|
|
|
|
UpdatedAt = p.UpdatedAt
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
return new PageListModel<ProductOutput>(result, input.PageIndex, input.PageSize, totalNumber);
|
2026-06-03 16:09:56 +08:00
|
|
|
|
}
|
2026-06-09 11:41:28 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 更新商品上架/下架状态(取反)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task UpdateSaleStatusAsync(long id)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("正在更新商品上下架状态,ID: {Id}", id);
|
|
|
|
|
|
|
|
|
|
|
|
var product = await productRepository.GetByIdAsync(id);
|
|
|
|
|
|
if (product == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("未找到要更新状态的商品,ID: {Id}", id);
|
|
|
|
|
|
throw new BusinessException("商品不存在", 404);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
product.Status = product.Status == (int)ProductStatusEnum.OnSale
|
|
|
|
|
|
? (int)ProductStatusEnum.OffSale
|
|
|
|
|
|
: (int)ProductStatusEnum.OnSale;
|
|
|
|
|
|
product.UpdatedBy = "System";
|
|
|
|
|
|
product.UpdatedAt = DateTime.Now;
|
|
|
|
|
|
|
|
|
|
|
|
var result = await productRepository.UpdateAsync(product);
|
|
|
|
|
|
if (!result)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogError("商品状态更新失败,ID: {Id}", id);
|
|
|
|
|
|
throw new BusinessException("更新商品状态失败", 500);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("商品上下架状态更新成功,ID: {Id}, SaleStatus: {SaleStatus}", id, product.Status);
|
|
|
|
|
|
}
|
2026-06-09 18:02:26 +08:00
|
|
|
|
|
|
|
|
|
|
#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
|
2026-06-03 16:09:56 +08:00
|
|
|
|
}
|