HaniBlindBox/server/HoneyBox/src/HoneyBox.Api/BackgroundServices/WelfareLotteryService.cs
2026-02-04 00:38:51 +08:00

277 lines
9.8 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.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);
}
}