277 lines
9.8 KiB
C#
277 lines
9.8 KiB
C#
using HoneyBox.Model.Data;
|
||
using HoneyBox.Model.Entities;
|
||
using Microsoft.EntityFrameworkCore;
|
||
|
||
namespace HoneyBox.Api.BackgroundServices;
|
||
|
||
/// <summary>
|
||
/// 福利屋开奖后台服务
|
||
/// 每分钟检查一次是否有需要开奖的福利屋
|
||
/// </summary>
|
||
public class WelfareLotteryService : BackgroundService
|
||
{
|
||
private readonly IServiceProvider _serviceProvider;
|
||
private readonly ILogger<WelfareLotteryService> _logger;
|
||
private const int WelfareType = 15; // 福利屋商品类型
|
||
|
||
public WelfareLotteryService(
|
||
IServiceProvider serviceProvider,
|
||
ILogger<WelfareLotteryService> logger)
|
||
{
|
||
_serviceProvider = serviceProvider;
|
||
_logger = logger;
|
||
}
|
||
|
||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||
{
|
||
_logger.LogInformation("福利屋开奖服务已启动");
|
||
|
||
while (!stoppingToken.IsCancellationRequested)
|
||
{
|
||
try
|
||
{
|
||
await ProcessWelfareLotteryAsync(stoppingToken);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "福利屋开奖服务执行出错");
|
||
}
|
||
|
||
// 每分钟执行一次
|
||
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
|
||
}
|
||
|
||
_logger.LogInformation("福利屋开奖服务已停止");
|
||
}
|
||
|
||
private async Task ProcessWelfareLotteryAsync(CancellationToken stoppingToken)
|
||
{
|
||
using var scope = _serviceProvider.CreateScope();
|
||
var dbContext = scope.ServiceProvider.GetRequiredService<HoneyBoxDbContext>();
|
||
|
||
var now = DateTime.Now;
|
||
_logger.LogDebug("福利屋开奖检查: {Time}", now);
|
||
|
||
// 查找需要开奖的福利屋
|
||
// 条件: status=1, is_flw=1, is_open=0, open_time <= 当前时间
|
||
var welfareGoods = await dbContext.Goods
|
||
.Where(g => g.Status == 1
|
||
&& g.Type == WelfareType
|
||
&& g.IsOpen == 0
|
||
&& g.OpenTime != null
|
||
&& g.OpenTime <= now)
|
||
.ToListAsync(stoppingToken);
|
||
|
||
foreach (var goods in welfareGoods)
|
||
{
|
||
try
|
||
{
|
||
await ProcessSingleWelfareLotteryAsync(dbContext, goods, stoppingToken);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "福利屋开奖失败: GoodsId={GoodsId}, Title={Title}",
|
||
goods.Id, goods.Title);
|
||
}
|
||
}
|
||
}
|
||
|
||
private async Task ProcessSingleWelfareLotteryAsync(
|
||
HoneyBoxDbContext dbContext,
|
||
Model.Entities.Good goods,
|
||
CancellationToken stoppingToken)
|
||
{
|
||
_logger.LogInformation("开始福利屋开奖: GoodsId={GoodsId}, Title={Title}",
|
||
goods.Id, goods.Title);
|
||
|
||
// 获取所有奖品(num=0 表示第一箱)
|
||
var prizeList = await dbContext.GoodsItems
|
||
.Where(gi => gi.GoodsId == goods.Id && gi.Num == 0)
|
||
.ToListAsync(stoppingToken);
|
||
|
||
// 展开奖品列表(根据库存数量)
|
||
var expandedPrizes = new List<GoodsItem>();
|
||
foreach (var prize in prizeList)
|
||
{
|
||
for (int i = 0; i < prize.Stock; i++)
|
||
{
|
||
expandedPrizes.Add(prize);
|
||
}
|
||
}
|
||
|
||
// 打乱奖品顺序
|
||
var random = new Random();
|
||
expandedPrizes = expandedPrizes.OrderBy(_ => random.Next()).ToList();
|
||
|
||
// 获取所有参与用户的订单
|
||
var participants = await dbContext.OrderItems
|
||
.Where(oi => oi.GoodsId == goods.Id && oi.OrderType == WelfareType)
|
||
.ToListAsync(stoppingToken);
|
||
|
||
if (participants.Count == 0)
|
||
{
|
||
_logger.LogInformation("福利屋无人参与,直接结束: GoodsId={GoodsId}", goods.Id);
|
||
goods.IsOpen = 1;
|
||
goods.Status = 3; // 已结束
|
||
await dbContext.SaveChangesAsync(stoppingToken);
|
||
return;
|
||
}
|
||
|
||
// 打乱参与者顺序
|
||
participants = participants.OrderBy(_ => random.Next()).ToList();
|
||
|
||
var prizeIndex = 0;
|
||
var prizeCount = expandedPrizes.Count;
|
||
|
||
// 遍历所有参与者分配奖品
|
||
foreach (var participant in participants)
|
||
{
|
||
using var transaction = await dbContext.Database.BeginTransactionAsync(stoppingToken);
|
||
try
|
||
{
|
||
if (prizeIndex < prizeCount)
|
||
{
|
||
var prize = expandedPrizes[prizeIndex];
|
||
|
||
// 检查库存
|
||
var currentStock = await dbContext.GoodsItems
|
||
.Where(gi => gi.Id == prize.Id)
|
||
.Select(gi => gi.SurplusStock)
|
||
.FirstOrDefaultAsync(stoppingToken);
|
||
|
||
if (currentStock <= 0)
|
||
{
|
||
_logger.LogWarning("奖品库存不足: PrizeId={PrizeId}", prize.Id);
|
||
continue;
|
||
}
|
||
|
||
// 更新订单信息
|
||
participant.GoodslistId = prize.Id;
|
||
participant.GoodslistTitle = prize.Title;
|
||
participant.GoodslistImgurl = prize.ImgUrl;
|
||
participant.GoodslistMoney = prize.Money;
|
||
participant.GoodslistType = prize.GoodsType;
|
||
participant.ShangId = prize.ShangId ?? 0;
|
||
participant.PrizeCode = prize.RewardId?.ToString() ?? "";
|
||
|
||
// 如果是实物奖品,设置状态为待发货
|
||
if (participant.Status != 0 && participant.GoodslistType == 1)
|
||
{
|
||
participant.Status = 0;
|
||
participant.RecoveryNum = "";
|
||
}
|
||
|
||
// 减少库存
|
||
var updateResult = await dbContext.GoodsItems
|
||
.Where(gi => gi.Id == prize.Id && gi.SurplusStock > 0)
|
||
.ExecuteUpdateAsync(s => s
|
||
.SetProperty(gi => gi.SurplusStock, gi => gi.SurplusStock - 1),
|
||
stoppingToken);
|
||
|
||
if (updateResult == 0)
|
||
{
|
||
_logger.LogWarning("更新库存失败: PrizeId={PrizeId}", prize.Id);
|
||
await transaction.RollbackAsync(stoppingToken);
|
||
continue;
|
||
}
|
||
|
||
// 发放奖励(如果有reward_id)
|
||
if (!string.IsNullOrEmpty(prize.RewardId) && int.TryParse(prize.RewardId, out var rewardId) && rewardId > 0)
|
||
{
|
||
await SendRewardAsync(dbContext, participant.UserId, rewardId,
|
||
$"{goods.Title}开奖", stoppingToken);
|
||
}
|
||
|
||
_logger.LogInformation("发放奖品成功: GoodsId={GoodsId}, UserId={UserId}, Prize={Prize}",
|
||
goods.Id, participant.UserId, prize.Title);
|
||
|
||
prizeIndex++;
|
||
}
|
||
else
|
||
{
|
||
// 轮空处理
|
||
participant.GoodslistId = 0;
|
||
participant.GoodslistTitle = "轮空";
|
||
participant.GoodslistMoney = 0;
|
||
participant.ShangId = 0;
|
||
participant.PrizeCode = "";
|
||
}
|
||
|
||
await dbContext.SaveChangesAsync(stoppingToken);
|
||
await transaction.CommitAsync(stoppingToken);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
await transaction.RollbackAsync(stoppingToken);
|
||
_logger.LogError(ex, "处理参与者奖品失败: GoodsId={GoodsId}, UserId={UserId}",
|
||
goods.Id, participant.UserId);
|
||
}
|
||
}
|
||
|
||
// 更新福利屋状态为已开奖
|
||
goods.IsOpen = 1;
|
||
goods.Status = 3; // 已结束
|
||
await dbContext.SaveChangesAsync(stoppingToken);
|
||
|
||
_logger.LogInformation("福利屋开奖完成: GoodsId={GoodsId}, Title={Title}, 参与人数={Count}",
|
||
goods.Id, goods.Title, participants.Count);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发放奖励
|
||
/// </summary>
|
||
private async Task SendRewardAsync(
|
||
HoneyBoxDbContext dbContext,
|
||
int userId,
|
||
int rewardId,
|
||
string remark,
|
||
CancellationToken stoppingToken)
|
||
{
|
||
// 获取奖励配置
|
||
var reward = await dbContext.Rewards
|
||
.Where(r => r.Id == rewardId)
|
||
.FirstOrDefaultAsync(stoppingToken);
|
||
|
||
if (reward == null)
|
||
{
|
||
_logger.LogWarning("奖励配置不存在: RewardId={RewardId}", rewardId);
|
||
return;
|
||
}
|
||
|
||
var user = await dbContext.Users
|
||
.Where(u => u.Id == userId)
|
||
.FirstOrDefaultAsync(stoppingToken);
|
||
|
||
if (user == null)
|
||
{
|
||
_logger.LogWarning("用户不存在: UserId={UserId}", userId);
|
||
return;
|
||
}
|
||
|
||
// 根据奖励类型发放
|
||
switch (reward.RewardType)
|
||
{
|
||
case 1: // 钻石/余额
|
||
user.Money += reward.RewardValue;
|
||
_logger.LogInformation("发放钻石: UserId={UserId}, Amount={Amount}", userId, reward.RewardValue);
|
||
break;
|
||
|
||
case 2: // 积分/UU币
|
||
user.Integral += reward.RewardValue;
|
||
_logger.LogInformation("发放积分: UserId={UserId}, Amount={Amount}", userId, reward.RewardValue);
|
||
break;
|
||
|
||
case 3: // 哈尼券/达达卷
|
||
user.Money2 = (user.Money2 ?? 0) + reward.RewardValue;
|
||
_logger.LogInformation("发放哈尼券: UserId={UserId}, Amount={Amount}", userId, reward.RewardValue);
|
||
break;
|
||
|
||
default:
|
||
_logger.LogWarning("未知奖励类型: Type={Type}", reward.RewardType);
|
||
break;
|
||
}
|
||
|
||
await dbContext.SaveChangesAsync(stoppingToken);
|
||
}
|
||
}
|