HaniBlindBox/server/HoneyBox/src/HoneyBox.Admin.Business/Services/GoodsService.cs
2026-02-04 02:22:05 +08:00

1231 lines
42 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

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

using HoneyBox.Admin.Business.Models;
using HoneyBox.Admin.Business.Models.Goods;
using HoneyBox.Admin.Business.Services.Interfaces;
using HoneyBox.Model.Data;
using HoneyBox.Model.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace HoneyBox.Admin.Business.Services;
/// <summary>
/// 商品管理服务实现
/// </summary>
public class GoodsService : IGoodsService
{
private readonly HoneyBoxDbContext _dbContext;
private readonly ILogger<GoodsService> _logger;
// 盒子类型名称映射
private static readonly Dictionary<int, string> BoxTypeNames = new()
{
{ 1, "一番赏" },
{ 2, "无限赏" },
{ 3, "擂台赏" },
{ 4, "抽卡机" },
{ 5, "福袋" },
{ 6, "幸运赏" },
{ 8, "盲盒" },
{ 9, "扭蛋" },
{ 10, "积分商城" },
{ 11, "转转赏" },
{ 12, "连击赏" },
{ 15, "福利屋" },
{ 16, "连抽赏" },
{ 17, "大乱斗" }
};
public GoodsService(
HoneyBoxDbContext dbContext,
ILogger<GoodsService> logger)
{
_dbContext = dbContext;
_logger = logger;
}
#region
/// <inheritdoc />
public async Task<PagedResult<GoodsListResponse>> GetGoodsListAsync(GoodsListRequest request)
{
var query = _dbContext.Goods
.AsNoTracking()
.Where(g => g.DeletedAt == null);
// 应用过滤条件
query = ApplyGoodsFilters(query, request);
// 获取总数
var total = await query.CountAsync();
// 获取商品列表
var goods = await query
.OrderByDescending(g => g.Sort)
.ThenByDescending(g => g.Id)
.Skip(request.Skip)
.Take(request.PageSize)
.ToListAsync();
// 映射结果
var list = goods.Select(MapToGoodsListResponse).ToList();
return PagedResult<GoodsListResponse>.Create(list, total, request.Page, request.PageSize);
}
/// <inheritdoc />
public async Task<GoodsDetailResponse?> GetGoodsDetailAsync(int goodsId)
{
var goods = await _dbContext.Goods
.AsNoTracking()
.FirstOrDefaultAsync(g => g.Id == goodsId && g.DeletedAt == null);
if (goods == null)
{
return null;
}
return MapToGoodsDetailResponse(goods);
}
/// <inheritdoc />
public async Task<int> CreateGoodsAsync(GoodsCreateRequest request, int operatorId)
{
// 验证商品类型
if (!BoxTypeNames.ContainsKey(request.Type))
{
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "无效的商品类型");
}
var now = DateTime.Now;
var goods = new Good
{
Title = request.Title,
Price = request.Price,
Type = (byte)request.Type,
ImgUrl = request.ImgUrl,
ImgUrlDetail = request.ImgUrlDetail,
Stock = request.Stock,
SaleStock = 0,
Sort = request.Sort,
DailyXiangou = request.DailyLimit,
LockIs = (byte)request.LockIs,
IntegralIs = (byte)request.IntegralIs,
ShowIs = (byte)request.ShowIs,
CouponIs = (byte)request.CouponIs,
CouponPro = request.CouponPro,
FlwStartTime = request.FlwStartTime,
FlwEndTime = request.FlwEndTime,
OpenTime = request.OpenTime,
ChoujiangXianzhi = request.ChoujiangXianzhi ?? 0,
CategoryId = request.CategoryId,
GoodsDescribe = request.GoodsDescribe,
NewIs = (byte)request.NewIs,
IsShouZhe = (byte)request.IsShouZhe,
RageIs = (byte)request.RageIs,
Rage = request.Rage,
LingzhuIs = (byte)request.LingzhuIs,
LingzhuFan = request.LingzhuFan,
IsAutoXiajia = (byte)request.IsAutoXiajia,
XiajiaLirun = (int)request.XiajiaLirun,
XiajiaAutoCoushu = (int)request.XiajiaAutoCoushu,
XiajiaJine = (int)request.XiajiaJine,
Status = 0, // 默认下架
PrizeNum = 0,
IsFlw = (byte)(request.Type == 15 ? 1 : 0),
CreatedAt = now,
UpdatedAt = now
};
_dbContext.Goods.Add(goods);
await _dbContext.SaveChangesAsync();
// 记录操作日志
await LogGoodsOperationAsync(goods.Id, "create", $"创建商品: {goods.Title}", operatorId);
_logger.LogInformation("创建商品成功: GoodsId={GoodsId}, Title={Title}, Operator={Operator}",
goods.Id, goods.Title, operatorId);
return goods.Id;
}
/// <inheritdoc />
public async Task<bool> UpdateGoodsAsync(int goodsId, GoodsUpdateRequest request, int operatorId)
{
var goods = await _dbContext.Goods.FirstOrDefaultAsync(g => g.Id == goodsId && g.DeletedAt == null);
if (goods == null)
{
throw new BusinessException(BusinessErrorCodes.NotFound, "商品不存在");
}
// 验证商品类型
if (!BoxTypeNames.ContainsKey(request.Type))
{
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "无效的商品类型");
}
// 防止库存减少
if (request.Stock < goods.Stock)
{
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "不允许减少库存");
}
// 检查是否需要复制奖品配置(库存增加时)
var stockIncrease = request.Stock - goods.Stock;
if (stockIncrease > 0)
{
await CopyPrizeConfigurationsAsync(goodsId, stockIncrease);
}
// 更新商品信息
goods.Title = request.Title;
goods.Price = request.Price;
goods.Type = (byte)request.Type;
goods.ImgUrl = request.ImgUrl;
goods.ImgUrlDetail = request.ImgUrlDetail;
goods.Stock = request.Stock;
goods.Sort = request.Sort;
goods.DailyXiangou = request.DailyLimit;
goods.LockIs = (byte)request.LockIs;
goods.IntegralIs = (byte)request.IntegralIs;
goods.ShowIs = (byte)request.ShowIs;
goods.CouponIs = (byte)request.CouponIs;
goods.CouponPro = request.CouponPro;
goods.FlwStartTime = request.FlwStartTime;
goods.FlwEndTime = request.FlwEndTime;
goods.OpenTime = request.OpenTime;
goods.ChoujiangXianzhi = request.ChoujiangXianzhi ?? 0;
goods.CategoryId = request.CategoryId;
goods.GoodsDescribe = request.GoodsDescribe;
goods.NewIs = (byte)request.NewIs;
goods.IsShouZhe = (byte)request.IsShouZhe;
goods.RageIs = (byte)request.RageIs;
goods.Rage = request.Rage;
goods.LingzhuIs = (byte)request.LingzhuIs;
goods.LingzhuFan = request.LingzhuFan;
goods.IsAutoXiajia = (byte)request.IsAutoXiajia;
goods.XiajiaLirun = (int)request.XiajiaLirun;
goods.XiajiaAutoCoushu = (int)request.XiajiaAutoCoushu;
goods.XiajiaJine = (int)request.XiajiaJine;
goods.IsFlw = (byte)(request.Type == 15 ? 1 : 0);
goods.UpdatedAt = DateTime.Now;
var result = await _dbContext.SaveChangesAsync() > 0;
// 记录操作日志
await LogGoodsOperationAsync(goodsId, "update", $"更新商品: {goods.Title}", operatorId);
_logger.LogInformation("更新商品成功: GoodsId={GoodsId}, Operator={Operator}", goodsId, operatorId);
return result;
}
/// <inheritdoc />
public async Task<bool> DeleteGoodsAsync(int goodsId, int operatorId)
{
var goods = await _dbContext.Goods.FirstOrDefaultAsync(g => g.Id == goodsId && g.DeletedAt == null);
if (goods == null)
{
throw new BusinessException(BusinessErrorCodes.NotFound, "商品不存在");
}
// 软删除
goods.DeletedAt = DateTime.Now;
goods.UpdatedAt = DateTime.Now;
var result = await _dbContext.SaveChangesAsync() > 0;
// 记录操作日志
await LogGoodsOperationAsync(goodsId, "delete", $"删除商品: {goods.Title}", operatorId);
_logger.LogInformation("删除商品成功: GoodsId={GoodsId}, Operator={Operator}", goodsId, operatorId);
return result;
}
/// <inheritdoc />
public async Task<bool> SetGoodsStatusAsync(int goodsId, int status, int operatorId)
{
var goods = await _dbContext.Goods.FirstOrDefaultAsync(g => g.Id == goodsId && g.DeletedAt == null);
if (goods == null)
{
throw new BusinessException(BusinessErrorCodes.NotFound, "商品不存在");
}
var oldStatus = goods.Status;
goods.Status = (byte)status;
goods.UpdatedAt = DateTime.Now;
var result = await _dbContext.SaveChangesAsync() > 0;
// 记录操作日志
var operation = status == 1 ? "上架" : "下架";
await LogGoodsOperationAsync(goodsId, "status", $"商品{operation}: {goods.Title}", operatorId);
_logger.LogInformation("商品状态变更: GoodsId={GoodsId}, OldStatus={OldStatus}, NewStatus={NewStatus}, Operator={Operator}",
goodsId, oldStatus, status, operatorId);
return result;
}
#endregion
#region
/// <inheritdoc />
public async Task<List<PrizeDto>> GetPrizesAsync(int goodsId)
{
// 验证商品存在
var goodsExists = await _dbContext.Goods.AnyAsync(g => g.Id == goodsId && g.DeletedAt == null);
if (!goodsExists)
{
throw new BusinessException(BusinessErrorCodes.NotFound, "商品不存在");
}
var prizes = await _dbContext.GoodsItems
.AsNoTracking()
.Where(gi => gi.GoodsId == goodsId)
.OrderBy(gi => gi.Sort)
.ThenBy(gi => gi.Num)
.ToListAsync();
// 获取所有奖品等级ID
var shangIds = prizes
.Where(p => p.ShangId.HasValue)
.Select(p => p.ShangId!.Value)
.Distinct()
.ToList();
// 批量查询等级信息
var prizeLevels = await _dbContext.PrizeLevels
.AsNoTracking()
.Where(pl => shangIds.Contains(pl.Id))
.ToDictionaryAsync(pl => pl.Id, pl => new PrizeLevelInfo { Title = pl.Title, Color = pl.Color });
return prizes.Select(p => MapToPrizeDto(p, prizeLevels)).ToList();
}
/// <summary>
/// 奖品等级信息
/// </summary>
private class PrizeLevelInfo
{
public string Title { get; set; } = string.Empty;
public string? Color { get; set; }
}
/// <inheritdoc />
public async Task<int> AddPrizeAsync(int goodsId, PrizeCreateRequest request)
{
// 验证商品存在
var goods = await _dbContext.Goods.FirstOrDefaultAsync(g => g.Id == goodsId && g.DeletedAt == null);
if (goods == null)
{
throw new BusinessException(BusinessErrorCodes.NotFound, "商品不存在");
}
// 获取当前最大编号
var maxNum = await _dbContext.GoodsItems
.Where(gi => gi.GoodsId == goodsId)
.MaxAsync(gi => (int?)gi.Num) ?? 0;
var now = DateTime.Now;
var prize = new GoodsItem
{
GoodsId = goodsId,
Num = maxNum + 1,
Title = request.Title,
ImgUrl = request.ImgUrl,
ImgUrlDetail = request.ImgUrlDetail,
Stock = request.Stock,
SurplusStock = request.Stock,
Price = request.Price,
Money = request.Money,
ScMoney = request.ScMoney,
RealPro = request.RealPro,
GoodsType = (byte)request.GoodsType,
Sort = request.Sort,
ShangId = request.ShangId,
RewardNum = request.RewardNum,
Rank = request.Rank,
GiveMoney = request.GiveMoney,
CardNo = request.CardNo,
PrizeCode = GeneratePrizeCode(),
Type = (byte)request.Type,
LianJiType = (byte)request.LianJiType,
RewardId = request.RewardId,
Doubling = request.Doubling,
IsLingzhu = (byte)request.IsLingzhu,
PrizeNum = 1,
GoodsListId = 0,
CreatedAt = now,
UpdatedAt = now
};
_dbContext.GoodsItems.Add(prize);
// 更新商品奖品数量
goods.PrizeNum = await _dbContext.GoodsItems.CountAsync(gi => gi.GoodsId == goodsId) + 1;
goods.UpdatedAt = now;
await _dbContext.SaveChangesAsync();
_logger.LogInformation("添加奖品成功: GoodsId={GoodsId}, PrizeId={PrizeId}, Title={Title}",
goodsId, prize.Id, prize.Title);
return prize.Id;
}
/// <inheritdoc />
public async Task<bool> UpdatePrizeAsync(int prizeId, PrizeUpdateRequest request)
{
var prize = await _dbContext.GoodsItems.FirstOrDefaultAsync(gi => gi.Id == prizeId);
if (prize == null)
{
throw new BusinessException(BusinessErrorCodes.NotFound, "奖品不存在");
}
prize.Title = request.Title;
prize.ImgUrl = request.ImgUrl;
prize.ImgUrlDetail = request.ImgUrlDetail;
prize.Stock = request.Stock;
prize.SurplusStock = request.Stock; // 更新剩余库存
prize.Price = request.Price;
prize.Money = request.Money;
prize.ScMoney = request.ScMoney;
prize.RealPro = request.RealPro;
prize.GoodsType = (byte)request.GoodsType;
prize.Sort = request.Sort;
prize.ShangId = request.ShangId;
prize.RewardNum = request.RewardNum;
prize.Rank = request.Rank;
prize.GiveMoney = request.GiveMoney;
prize.CardNo = request.CardNo;
prize.Type = (byte)request.Type;
prize.LianJiType = (byte)request.LianJiType;
prize.RewardId = request.RewardId;
prize.Doubling = request.Doubling;
prize.IsLingzhu = (byte)request.IsLingzhu;
prize.UpdatedAt = DateTime.Now;
var result = await _dbContext.SaveChangesAsync() > 0;
_logger.LogInformation("更新奖品成功: PrizeId={PrizeId}, Title={Title}", prizeId, prize.Title);
return result;
}
/// <inheritdoc />
public async Task<bool> DeletePrizeAsync(int prizeId)
{
var prize = await _dbContext.GoodsItems.FirstOrDefaultAsync(gi => gi.Id == prizeId);
if (prize == null)
{
throw new BusinessException(BusinessErrorCodes.NotFound, "奖品不存在");
}
var goodsId = prize.GoodsId;
_dbContext.GoodsItems.Remove(prize);
// 更新商品奖品数量
var goods = await _dbContext.Goods.FirstOrDefaultAsync(g => g.Id == goodsId);
if (goods != null)
{
goods.PrizeNum = await _dbContext.GoodsItems.CountAsync(gi => gi.GoodsId == goodsId) - 1;
goods.UpdatedAt = DateTime.Now;
}
var result = await _dbContext.SaveChangesAsync() > 0;
_logger.LogInformation("删除奖品成功: PrizeId={PrizeId}, GoodsId={GoodsId}", prizeId, goodsId);
return result;
}
#endregion
#region
/// <inheritdoc />
public async Task<List<GoodsTypeDto>> GetGoodsTypesAsync()
{
var types = await _dbContext.GoodsTypes
.AsNoTracking()
.OrderBy(t => t.SortOrder)
.ToListAsync();
return types.Select(t => new GoodsTypeDto
{
Id = t.Id,
Name = t.Name,
Value = t.Value,
SortOrder = t.SortOrder,
IsShow = t.IsShow,
IsFenlei = t.IsFenlei,
FlName = t.FlName,
CornerText = t.CornerText,
PayWechat = t.PayWechat,
PayBalance = t.PayBalance,
PayCurrency = t.PayCurrency,
PayCurrency2 = t.PayCurrency2,
PayCoupon = t.PayCoupon,
IsDeduction = t.IsDeduction,
Remark = t.Remark
}).ToList();
}
/// <inheritdoc />
public async Task<int> CreateGoodsTypeAsync(GoodsTypeCreateRequest request)
{
// 验证类型名称
if (string.IsNullOrWhiteSpace(request.Name))
{
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "类型名称不能为空");
}
// 检查类型值是否已存在
var exists = await _dbContext.GoodsTypes.AnyAsync(t => t.Value == request.Value);
if (exists)
{
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "类型Key已存在请更换");
}
// 验证分类显示设置
if (request.IsFenlei == 1 && string.IsNullOrWhiteSpace(request.FlName))
{
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "选择分类显示时,必须填写分类名称");
}
var goodsType = new GoodsType
{
Name = request.Name,
Value = request.Value,
SortOrder = request.SortOrder,
IsShow = (byte)request.IsShow,
IsFenlei = (byte)request.IsFenlei,
FlName = request.FlName ?? string.Empty,
CornerText = request.CornerText,
PayWechat = (byte)request.PayWechat,
PayBalance = (byte)request.PayBalance,
PayCurrency = (byte)request.PayCurrency,
PayCurrency2 = (byte)request.PayCurrency2,
PayCoupon = (byte)request.PayCoupon,
IsDeduction = (byte)request.IsDeduction,
Remark = request.Remark
};
_dbContext.GoodsTypes.Add(goodsType);
await _dbContext.SaveChangesAsync();
_logger.LogInformation("创建盒子类型成功: TypeId={TypeId}, Name={Name}", goodsType.Id, goodsType.Name);
return goodsType.Id;
}
/// <inheritdoc />
public async Task<bool> UpdateGoodsTypeAsync(int typeId, GoodsTypeUpdateRequest request)
{
var goodsType = await _dbContext.GoodsTypes.FirstOrDefaultAsync(t => t.Id == typeId);
if (goodsType == null)
{
throw new BusinessException(BusinessErrorCodes.NotFound, "盒子类型不存在");
}
// 验证类型名称
if (string.IsNullOrWhiteSpace(request.Name))
{
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "类型名称不能为空");
}
// 检查类型值是否已存在(排除自身)
var exists = await _dbContext.GoodsTypes.AnyAsync(t => t.Value == request.Value && t.Id != typeId);
if (exists)
{
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "类型Key已存在请更换");
}
// 验证分类显示设置
if (request.IsFenlei == 1 && string.IsNullOrWhiteSpace(request.FlName))
{
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "选择分类显示时,必须填写分类名称");
}
goodsType.Name = request.Name;
goodsType.Value = request.Value;
goodsType.SortOrder = request.SortOrder;
goodsType.IsShow = (byte)request.IsShow;
goodsType.IsFenlei = (byte)request.IsFenlei;
goodsType.FlName = request.FlName ?? string.Empty;
goodsType.CornerText = request.CornerText;
goodsType.PayWechat = (byte)request.PayWechat;
goodsType.PayBalance = (byte)request.PayBalance;
goodsType.PayCurrency = (byte)request.PayCurrency;
goodsType.PayCurrency2 = (byte)request.PayCurrency2;
goodsType.PayCoupon = (byte)request.PayCoupon;
goodsType.IsDeduction = (byte)request.IsDeduction;
goodsType.Remark = request.Remark;
var result = await _dbContext.SaveChangesAsync() > 0;
_logger.LogInformation("更新盒子类型成功: TypeId={TypeId}, Name={Name}", typeId, goodsType.Name);
return result;
}
/// <inheritdoc />
public async Task<bool> DeleteGoodsTypeAsync(int typeId)
{
var goodsType = await _dbContext.GoodsTypes.FirstOrDefaultAsync(t => t.Id == typeId);
if (goodsType == null)
{
throw new BusinessException(BusinessErrorCodes.NotFound, "盒子类型不存在");
}
// 检查是否有商品使用此类型
var hasGoods = await _dbContext.Goods.AnyAsync(g => g.Type == goodsType.Value && g.DeletedAt == null);
if (hasGoods)
{
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "该类型下存在商品,无法删除");
}
_dbContext.GoodsTypes.Remove(goodsType);
var result = await _dbContext.SaveChangesAsync() > 0;
_logger.LogInformation("删除盒子类型成功: TypeId={TypeId}, Name={Name}", typeId, goodsType.Name);
return result;
}
/// <inheritdoc />
public async Task<bool> SetGoodsTypeStatusAsync(int typeId, GoodsTypeStatusRequest request)
{
var goodsType = await _dbContext.GoodsTypes.FirstOrDefaultAsync(t => t.Id == typeId);
if (goodsType == null)
{
throw new BusinessException(BusinessErrorCodes.NotFound, "盒子类型不存在");
}
// 根据类型更新对应字段
switch (request.Type.ToLower())
{
case "is_show":
goodsType.IsShow = (byte)request.Value;
break;
case "is_fenlei":
goodsType.IsFenlei = (byte)request.Value;
break;
default:
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "无效的状态类型");
}
var result = await _dbContext.SaveChangesAsync() > 0;
_logger.LogInformation("更新盒子类型状态成功: TypeId={TypeId}, Type={Type}, Value={Value}",
typeId, request.Type, request.Value);
return result;
}
#endregion
#region Private Helper Methods
/// <summary>
/// 应用商品过滤条件
/// </summary>
private IQueryable<Good> ApplyGoodsFilters(IQueryable<Good> query, GoodsListRequest request)
{
if (!string.IsNullOrWhiteSpace(request.Title))
{
query = query.Where(g => g.Title.Contains(request.Title));
}
if (request.Status.HasValue)
{
query = query.Where(g => g.Status == request.Status.Value);
}
// 只有当 Type 有值且大于0时才过滤0表示"全部"
if (request.Type.HasValue && request.Type.Value > 0)
{
query = query.Where(g => g.Type == request.Type.Value);
}
return query;
}
/// <summary>
/// 映射商品到列表响应
/// </summary>
private GoodsListResponse MapToGoodsListResponse(Good goods)
{
return new GoodsListResponse
{
Id = goods.Id,
Title = goods.Title,
ImgUrl = goods.ImgUrl,
Price = goods.Price,
Type = goods.Type,
TypeName = BoxTypeNames.GetValueOrDefault(goods.Type, "未知类型"),
Status = goods.Status,
Stock = goods.Stock,
SaleStock = goods.SaleStock,
Sort = goods.Sort,
FlwStartTime = goods.FlwStartTime,
FlwEndTime = goods.FlwEndTime,
OpenTime = goods.OpenTime,
ChoujiangXianzhi = goods.ChoujiangXianzhi,
CreatedAt = goods.CreatedAt,
UpdatedAt = goods.UpdatedAt
};
}
/// <summary>
/// 映射商品到详情响应
/// </summary>
private GoodsDetailResponse MapToGoodsDetailResponse(Good goods)
{
return new GoodsDetailResponse
{
Id = goods.Id,
Title = goods.Title,
ImgUrl = goods.ImgUrl,
ImgUrlDetail = goods.ImgUrlDetail,
Price = goods.Price,
Type = goods.Type,
TypeName = BoxTypeNames.GetValueOrDefault(goods.Type, "未知类型"),
Status = goods.Status,
Stock = goods.Stock,
SaleStock = goods.SaleStock,
Sort = goods.Sort,
CategoryId = goods.CategoryId,
DailyLimit = goods.DailyXiangou,
LockIs = goods.LockIs,
LockTime = goods.LockTime,
IntegralIs = goods.IntegralIs,
ShowIs = goods.ShowIs,
CouponIs = goods.CouponIs,
CouponPro = goods.CouponPro,
FlwStartTime = goods.FlwStartTime,
FlwEndTime = goods.FlwEndTime,
OpenTime = goods.OpenTime,
ChoujiangXianzhi = goods.ChoujiangXianzhi,
GoodsDescribe = goods.GoodsDescribe,
NewIs = goods.NewIs,
IsShouZhe = goods.IsShouZhe,
RageIs = goods.RageIs,
Rage = goods.Rage,
LingzhuIs = goods.LingzhuIs,
LingzhuFan = goods.LingzhuFan,
PrizeNum = goods.PrizeNum,
IsFlw = goods.IsFlw,
CreatedAt = goods.CreatedAt,
UpdatedAt = goods.UpdatedAt
};
}
/// <summary>
/// 映射奖品到DTO
/// </summary>
private PrizeDto MapToPrizeDto(GoodsItem prize, Dictionary<int, PrizeLevelInfo>? prizeLevels = null)
{
string? shangTitle = null;
string? shangColor = null;
if (prize.ShangId.HasValue && prizeLevels != null && prizeLevels.TryGetValue(prize.ShangId.Value, out var level))
{
shangTitle = level.Title;
shangColor = level.Color;
}
return new PrizeDto
{
Id = prize.Id,
GoodsId = prize.GoodsId,
Num = prize.Num,
Title = prize.Title,
ImgUrl = prize.ImgUrl,
ImgUrlDetail = prize.ImgUrlDetail,
Stock = prize.Stock,
SurplusStock = prize.SurplusStock,
Price = prize.Price,
Money = prize.Money,
ScMoney = prize.ScMoney,
RealPro = prize.RealPro,
GoodsType = prize.GoodsType,
Sort = prize.Sort,
ShangId = prize.ShangId,
ShangTitle = shangTitle,
ShangColor = shangColor,
RewardNum = prize.RewardNum,
Rank = prize.Rank,
GiveMoney = prize.GiveMoney,
CardNo = prize.CardNo,
PrizeCode = prize.PrizeCode,
Type = prize.Type,
LianJiType = prize.LianJiType,
RewardId = prize.RewardId,
Doubling = prize.Doubling,
IsLingzhu = prize.IsLingzhu,
CreatedAt = prize.CreatedAt,
UpdatedAt = prize.UpdatedAt
};
}
/// <summary>
/// 生成唯一奖品编码
/// </summary>
public static string GeneratePrizeCode()
{
return $"PC{DateTime.Now:yyyyMMddHHmmss}{Guid.NewGuid().ToString("N")[..8].ToUpper()}";
}
/// <summary>
/// 库存增加时复制奖品配置
/// </summary>
private async Task CopyPrizeConfigurationsAsync(int goodsId, int stockIncrease)
{
// 获取第一套奖品配置(作为模板)
var templatePrizes = await _dbContext.GoodsItems
.AsNoTracking()
.Where(gi => gi.GoodsId == goodsId)
.OrderBy(gi => gi.Num)
.ToListAsync();
if (!templatePrizes.Any())
{
return; // 没有奖品配置,无需复制
}
var now = DateTime.Now;
var newPrizes = new List<GoodsItem>();
// 获取当前最大编号
var maxNum = templatePrizes.Max(p => p.Num);
// 为每个新增的库存复制奖品配置
for (int i = 0; i < stockIncrease; i++)
{
foreach (var template in templatePrizes)
{
var newPrize = new GoodsItem
{
GoodsId = goodsId,
Num = maxNum + 1 + (i * templatePrizes.Count) + templatePrizes.IndexOf(template),
Title = template.Title,
ImgUrl = template.ImgUrl,
ImgUrlDetail = template.ImgUrlDetail,
Stock = template.Stock,
SurplusStock = template.Stock,
Price = template.Price,
Money = template.Money,
ScMoney = template.ScMoney,
RealPro = template.RealPro,
GoodsType = template.GoodsType,
Sort = template.Sort,
ShangId = template.ShangId,
RewardNum = template.RewardNum,
Rank = template.Rank,
GiveMoney = template.GiveMoney,
CardNo = template.CardNo,
PrizeCode = GeneratePrizeCode(),
Type = template.Type,
LianJiType = template.LianJiType,
RewardId = template.RewardId,
Doubling = template.Doubling,
IsLingzhu = template.IsLingzhu,
PrizeNum = template.PrizeNum,
GoodsListId = template.GoodsListId,
SpecialStock = template.SpecialStock,
CreatedAt = now,
UpdatedAt = now
};
newPrizes.Add(newPrize);
}
}
if (newPrizes.Any())
{
_dbContext.GoodsItems.AddRange(newPrizes);
_logger.LogInformation("复制奖品配置: GoodsId={GoodsId}, StockIncrease={StockIncrease}, NewPrizes={NewPrizes}",
goodsId, stockIncrease, newPrizes.Count);
}
}
/// <summary>
/// 记录商品操作日志
/// </summary>
private async Task LogGoodsOperationAsync(int goodsId, string operation, string content, int operatorId)
{
var log = new AdminOperationLog
{
AdminId = operatorId,
Operation = $"goods:{operation}",
Content = $"GoodsId={goodsId}, {content}",
Ip = string.Empty,
CreatedAt = DateTime.Now
};
_dbContext.AdminOperationLogs.Add(log);
await _dbContext.SaveChangesAsync();
}
#endregion
#region
/// <inheritdoc />
public async Task<GoodsExtendDto> GetGoodsExtendAsync(int goodsId)
{
// 验证盒子存在
var goods = await _dbContext.Goods
.AsNoTracking()
.Where(g => g.Id == goodsId && g.DeletedAt == null)
.Select(g => new { g.Id, g.Type })
.FirstOrDefaultAsync();
if (goods == null)
{
throw new BusinessException(BusinessErrorCodes.NotFound, "盒子不存在");
}
// 1. 先尝试从goods_extensions表获取盒子特定配置
var goodsExtend = await _dbContext.GoodsExtensions
.AsNoTracking()
.Where(ge => ge.GoodsId == goodsId)
.Select(ge => new GoodsExtendDto
{
Id = ge.Id,
GoodsId = ge.GoodsId,
PayWechat = ge.PayWechat,
PayBalance = ge.PayBalance,
PayCurrency = ge.PayCurrency,
PayCurrency2 = ge.PayCurrency2,
PayCoupon = ge.PayCoupon,
IsDeduction = ge.IsDeduction,
IsInherited = false
})
.FirstOrDefaultAsync();
if (goodsExtend != null)
{
return goodsExtend;
}
// 2. 如果没有盒子特定配置从goods_types表获取类型默认配置
var typeConfig = await _dbContext.GoodsTypes
.AsNoTracking()
.Where(gt => gt.Value == goods.Type)
.Select(gt => new GoodsExtendDto
{
Id = 0,
GoodsId = goodsId,
PayWechat = gt.PayWechat,
PayBalance = gt.PayBalance,
PayCurrency = gt.PayCurrency,
PayCurrency2 = gt.PayCurrency2,
PayCoupon = gt.PayCoupon,
IsDeduction = gt.IsDeduction,
IsInherited = true
})
.FirstOrDefaultAsync();
if (typeConfig != null)
{
return typeConfig;
}
// 3. 如果都没有找到,返回默认值(全部启用)
return new GoodsExtendDto
{
Id = 0,
GoodsId = goodsId,
PayWechat = 1,
PayBalance = 1,
PayCurrency = 1,
PayCurrency2 = 1,
PayCoupon = 1,
IsDeduction = 1,
IsInherited = true
};
}
/// <inheritdoc />
public async Task<bool> UpdateGoodsExtendAsync(int goodsId, GoodsExtendUpdateRequest request)
{
// 验证盒子存在
var goodsExists = await _dbContext.Goods.AnyAsync(g => g.Id == goodsId && g.DeletedAt == null);
if (!goodsExists)
{
throw new BusinessException(BusinessErrorCodes.NotFound, "盒子不存在");
}
// 查找现有扩展配置
var existingExtend = await _dbContext.GoodsExtensions
.FirstOrDefaultAsync(ge => ge.GoodsId == goodsId);
if (existingExtend != null)
{
// 更新现有配置
existingExtend.PayWechat = (byte)request.PayWechat;
existingExtend.PayBalance = (byte)request.PayBalance;
existingExtend.PayCurrency = (byte)request.PayCurrency;
existingExtend.PayCurrency2 = (byte)request.PayCurrency2;
existingExtend.PayCoupon = (byte)request.PayCoupon;
existingExtend.IsDeduction = (byte)request.IsDeduction;
}
else
{
// 创建新配置
var newExtend = new GoodsExtension
{
GoodsId = goodsId,
PayWechat = (byte)request.PayWechat,
PayBalance = (byte)request.PayBalance,
PayCurrency = (byte)request.PayCurrency,
PayCurrency2 = (byte)request.PayCurrency2,
PayCoupon = (byte)request.PayCoupon,
IsDeduction = (byte)request.IsDeduction
};
_dbContext.GoodsExtensions.Add(newExtend);
}
var result = await _dbContext.SaveChangesAsync() > 0;
_logger.LogInformation("更新盒子扩展设置: GoodsId={GoodsId}", goodsId);
return result;
}
/// <inheritdoc />
public async Task<bool> DeleteGoodsExtendAsync(int goodsId)
{
// 验证盒子存在
var goodsExists = await _dbContext.Goods.AnyAsync(g => g.Id == goodsId && g.DeletedAt == null);
if (!goodsExists)
{
throw new BusinessException(BusinessErrorCodes.NotFound, "盒子不存在");
}
// 查找并删除扩展配置
var existingExtend = await _dbContext.GoodsExtensions
.FirstOrDefaultAsync(ge => ge.GoodsId == goodsId);
if (existingExtend == null)
{
// 没有独立配置,无需删除
return true;
}
_dbContext.GoodsExtensions.Remove(existingExtend);
var result = await _dbContext.SaveChangesAsync() > 0;
_logger.LogInformation("删除盒子扩展设置: GoodsId={GoodsId}", goodsId);
return result;
}
#endregion
#region
/// <inheritdoc />
public async Task<int> CopyGoodsAsync(int goodsId, int operatorId)
{
// 获取源盒子
var sourceGoods = await _dbContext.Goods
.AsNoTracking()
.FirstOrDefaultAsync(g => g.Id == goodsId && g.DeletedAt == null);
if (sourceGoods == null)
{
throw new BusinessException(BusinessErrorCodes.NotFound, "盒子不存在");
}
var now = DateTime.Now;
// 创建新盒子(复制基本信息)
var newGoods = new Good
{
Title = $"{sourceGoods.Title}_副本",
Price = sourceGoods.Price,
Type = sourceGoods.Type,
ImgUrl = sourceGoods.ImgUrl,
ImgUrlDetail = sourceGoods.ImgUrlDetail,
Stock = sourceGoods.Stock,
SaleStock = 0, // 新盒子销量为0
Sort = sourceGoods.Sort,
DailyXiangou = sourceGoods.DailyXiangou,
LockIs = sourceGoods.LockIs,
LockTime = sourceGoods.LockTime,
IntegralIs = sourceGoods.IntegralIs,
ShowIs = sourceGoods.ShowIs,
CouponIs = sourceGoods.CouponIs,
CouponPro = sourceGoods.CouponPro,
FlwStartTime = sourceGoods.FlwStartTime,
FlwEndTime = sourceGoods.FlwEndTime,
OpenTime = sourceGoods.OpenTime,
ChoujiangXianzhi = sourceGoods.ChoujiangXianzhi,
CategoryId = sourceGoods.CategoryId,
GoodsDescribe = sourceGoods.GoodsDescribe,
NewIs = sourceGoods.NewIs,
IsShouZhe = sourceGoods.IsShouZhe,
RageIs = sourceGoods.RageIs,
Rage = sourceGoods.Rage,
LingzhuIs = sourceGoods.LingzhuIs,
LingzhuFan = sourceGoods.LingzhuFan,
Status = 0, // 新盒子默认下架
PrizeNum = 0,
IsFlw = sourceGoods.IsFlw,
QuanjuXiangou = sourceGoods.QuanjuXiangou,
IsAutoXiajia = sourceGoods.IsAutoXiajia,
XiajiaLirun = sourceGoods.XiajiaLirun,
XiajiaAutoCoushu = sourceGoods.XiajiaAutoCoushu,
XiajiaJine = sourceGoods.XiajiaJine,
UnlockAmount = sourceGoods.UnlockAmount,
ItemCardId = sourceGoods.ItemCardId,
LianJiNum = sourceGoods.LianJiNum,
LianJiShangId = sourceGoods.LianJiShangId,
LingzhuShangId = sourceGoods.LingzhuShangId,
CreatedAt = now,
UpdatedAt = now
};
_dbContext.Goods.Add(newGoods);
await _dbContext.SaveChangesAsync();
// 复制奖品
var sourcePrizes = await _dbContext.GoodsItems
.AsNoTracking()
.Where(gi => gi.GoodsId == goodsId)
.ToListAsync();
if (sourcePrizes.Any())
{
var newPrizes = sourcePrizes.Select(p => new GoodsItem
{
GoodsId = newGoods.Id,
Num = p.Num,
Title = p.Title,
ImgUrl = p.ImgUrl,
ImgUrlDetail = p.ImgUrlDetail,
Stock = p.Stock,
SurplusStock = p.Stock, // 重置剩余库存
Price = p.Price,
Money = p.Money,
ScMoney = p.ScMoney,
RealPro = p.RealPro,
GoodsType = p.GoodsType,
Sort = p.Sort,
ShangId = p.ShangId,
RewardNum = p.RewardNum,
Rank = p.Rank,
GiveMoney = p.GiveMoney,
CardNo = p.CardNo,
PrizeCode = GeneratePrizeCode(),
Type = p.Type,
LianJiType = p.LianJiType,
RewardId = p.RewardId,
Doubling = p.Doubling,
IsLingzhu = p.IsLingzhu,
PrizeNum = p.PrizeNum,
GoodsListId = 0,
SpecialStock = p.SpecialStock,
CreatedAt = now,
UpdatedAt = now
}).ToList();
_dbContext.GoodsItems.AddRange(newPrizes);
// 更新新盒子的奖品数量
newGoods.PrizeNum = newPrizes.Count;
}
// 复制扩展设置(如果有)
var sourceExtend = await _dbContext.GoodsExtensions
.AsNoTracking()
.FirstOrDefaultAsync(ge => ge.GoodsId == goodsId);
if (sourceExtend != null)
{
var newExtend = new GoodsExtension
{
GoodsId = newGoods.Id,
PayWechat = sourceExtend.PayWechat,
PayBalance = sourceExtend.PayBalance,
PayCurrency = sourceExtend.PayCurrency,
PayCurrency2 = sourceExtend.PayCurrency2,
PayCoupon = sourceExtend.PayCoupon,
IsDeduction = sourceExtend.IsDeduction
};
_dbContext.GoodsExtensions.Add(newExtend);
}
await _dbContext.SaveChangesAsync();
// 记录操作日志
await LogGoodsOperationAsync(newGoods.Id, "copy", $"复制盒子: 源ID={goodsId}, 新ID={newGoods.Id}", operatorId);
_logger.LogInformation("复制盒子成功: SourceId={SourceId}, NewId={NewId}, Operator={Operator}",
goodsId, newGoods.Id, operatorId);
return newGoods.Id;
}
/// <inheritdoc />
public async Task<bool> ClearGoodsLotteryAsync(int goodsId, int operatorId)
{
// 验证盒子存在
var goods = await _dbContext.Goods.FirstOrDefaultAsync(g => g.Id == goodsId && g.DeletedAt == null);
if (goods == null)
{
throw new BusinessException(BusinessErrorCodes.NotFound, "盒子不存在");
}
// 使用事务确保数据一致性
using var transaction = await _dbContext.Database.BeginTransactionAsync();
try
{
// 1. 重置盒子销量
goods.SaleStock = 0;
goods.UpdatedAt = DateTime.Now;
// 2. 重置所有奖品的剩余库存
var prizes = await _dbContext.GoodsItems
.Where(gi => gi.GoodsId == goodsId)
.ToListAsync();
foreach (var prize in prizes)
{
prize.SurplusStock = prize.Stock;
prize.UpdatedAt = DateTime.Now;
}
// 3. 删除抽奖记录如果有lottery_records表
// 注意:这里需要根据实际的抽奖记录表结构来实现
// 暂时只重置库存,抽奖记录的清理可能需要额外处理
await _dbContext.SaveChangesAsync();
await transaction.CommitAsync();
// 记录操作日志
await LogGoodsOperationAsync(goodsId, "clear_lottery", $"清空抽奖记录: {goods.Title}", operatorId);
_logger.LogInformation("清空盒子抽奖记录: GoodsId={GoodsId}, Operator={Operator}", goodsId, operatorId);
return true;
}
catch (Exception ex)
{
await transaction.RollbackAsync();
_logger.LogError(ex, "清空盒子抽奖记录失败: GoodsId={GoodsId}", goodsId);
throw;
}
}
#endregion
}