Files
2026-10-12 16:22:04 +08:00

254 lines
9.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MongoRepository;
using MongoDB.Driver;
using CommonDomain;
using UserDomain.Entity;
using UserService.User;
using DotNetCore.CAP;
using UserService.GameConfigs;
namespace UserService
{
public class LotteryService : ILotteryService
{
private readonly IMongoBaseRepository<LotteryRecord> _recordRepo;
private readonly IMongoBaseRepository<LotteryUserStatus> _userStatusRepo;
private readonly IUserService _userService;
private readonly ICapPublisher _capPublisher;
private readonly IActivityConfigService _activityConfig;
public LotteryService(
IMongoBaseRepository<LotteryRecord> recordRepo,
IMongoBaseRepository<LotteryUserStatus> userStatusRepo,
IUserService userService,
ICapPublisher capPublisher, IActivityConfigService activityConfig)
{
_recordRepo = recordRepo;
_userStatusRepo = userStatusRepo;
_userService = userService;
_capPublisher = capPublisher;
_activityConfig = activityConfig;
}
public async Task<LotteryUserStatus?> GetUserStatus(string userId)
{
var activityConfig = await _activityConfig.Get();
var activity = activityConfig?.LotteryActivityConfig;
var cycleKey = BuildCycleKey(activity);
var status = await _userStatusRepo.FindOneAsync(s => s.UserId == userId );
if (status == null)
{
status = new LotteryUserStatus
{
UserId = userId,
ActivityCycleKey = cycleKey,
ActivityStartTime = activity?.StartTime ?? 0,
ActivityEndTime = activity?.EndTime ?? 0
};
await _userStatusRepo.InsertOneAsync(status);
}
else if (string.IsNullOrWhiteSpace(status.ActivityCycleKey))
{
status.ActivityCycleKey = cycleKey;
status.ActivityStartTime = activity?.StartTime ?? 0;
status.ActivityEndTime = activity?.EndTime ?? 0;
await _userStatusRepo.ReplaceOneAsync(status);
}
else if (status.ActivityCycleKey != cycleKey)
{
status.ActivityCycleKey = cycleKey;
status.ActivityStartTime = activity?.StartTime ?? 0;
status.ActivityEndTime = activity?.EndTime ?? 0;
status.DrawnPrizeIds = new List<string>();
status.TotalDrawCount = 0;
status.DrawCountByPool = new Dictionary<int, int>();
status.IsFinished = false;
await _userStatusRepo.ReplaceOneAsync(status);
}
return status;
}
public async Task<(bool success, string message, LotteryPrize? prize, long cost)> Draw(string userId, int poolType = 0)
{
if (poolType < 0 || poolType > 3)
return (false, "奖池类型错误", null, 0);
var activityConfig = await _activityConfig.Get();
if (activityConfig?.LotteryActivityConfig == null)
return (false, "活动不存在或未开启", null, 0);
var activity = activityConfig.LotteryActivityConfig;
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (activity.EndTime > 0 && now >= activity.EndTime)
return (false, "活动已结束", null, 0);
if (!activity.IsOpen)
return (false, "活动不存在或未开启", null, 0);
if (activity.StartTime > 0 && now < activity.StartTime)
return (false, "活动未开始", null, 0);
var effectivePoolType = poolType == 0 ? 1 : poolType;
var selectedPools = activity.Pools.Where(p => p.PoolType == effectivePoolType).ToList();
if (selectedPools.Count == 0)
return (false, "未配置对应奖池", null, 0);
var userStatus = await GetUserStatus(userId );
if (userStatus.IsFinished)
return (false, "您已抽完所有奖项,活动结束", null, 0);
if (activity.UserMaxDraw > 0 && userStatus.TotalDrawCount >= activity.UserMaxDraw)
return (false, "已达最大抽奖次数", null, 0);
if (activity.CostsByPool?.TryGetValue(effectivePoolType, out var configuredCosts) != true
|| configuredCosts == null || configuredCosts.Count == 0)
return (false, "未配置当前奖池抽奖消耗", null, 0);
userStatus.DrawCountByPool ??= new Dictionary<int, int>();
var poolDrawCount = userStatus.DrawCountByPool.GetValueOrDefault(effectivePoolType);
var costIndex = Math.Min(poolDrawCount, configuredCosts.Count - 1);
var cost = configuredCosts[costIndex];
var user = await _userService.GetUser(userId);
if (user == null)
return (false, "用户不存在", null, 0);
if (user.Assets.Gold < cost)
return (false, "金币不足", null, 0);
var level = DrawLevel(selectedPools);
if (level == null)
return (false, "奖池已空", null, 0);
var prize = DrawPrize(level, userStatus.DrawnPrizeIds);
if (prize == null)
{
prize = DrawPrizeFromAll(selectedPools, userStatus.DrawnPrizeIds);
if (prize == null)
return (false, "所有奖项已抽完", null, 0);
level = selectedPools.FirstOrDefault(p => p.PrizeList.Any(x => x.PrizeId == prize.PrizeId));
}
if (prize.TotalCount > 0 && prize.DrawnCount >= prize.TotalCount)
return (false, "该奖项已抽完", null, 0);
user.Assets.Gold -= cost;
await _userService.UpdateUser(user);
var record = new LotteryRecord
{
ActivityCycleKey = BuildCycleKey(activity),
UserId = userId,
PrizeId = prize.PrizeId,
Level = level.Level,
Cost = cost,
DrawTime = now
};
await _recordRepo.InsertOneAsync(record);
userStatus.DrawnPrizeIds.Add(prize.PrizeId);
userStatus.TotalDrawCount++;
userStatus.DrawCountByPool[effectivePoolType] = poolDrawCount + 1;
var allPrizeIds = activity.Pools.SelectMany(p => p.PrizeList).Select(x => x.PrizeId).ToList();
if (allPrizeIds.All(id => userStatus.DrawnPrizeIds.Contains(id)))
{
userStatus.IsFinished = true;
}
await _userStatusRepo.ReplaceOneAsync(userStatus);
prize.DrawnCount++;
_capPublisher.Publish("Lottery.DrawPrize", new LotteryDrawMessage
{
UserId = userId,
PrizeId = prize.PrizeId,
PrizeName = prize.Name,
Type = prize.Type,
Count = prize.Count,
OtherId = prize.OtherId
});
return (true, "抽奖成功", prize, cost);
}
private LotteryPoolLevel? DrawLevel(List<LotteryPoolLevel> pools)
{
var availablePools = pools.Where(p =>
p.PrizeList.Any(x => x.TotalCount == 0 || x.DrawnCount < x.TotalCount)).ToList();
if (!availablePools.Any())
return null;
long totalWeight = availablePools.Sum(p => (long)p.Weight);
if (totalWeight <= 0)
return null;
var random = new Random();
long randomValue = random.NextInt64(0, totalWeight);
long currentWeightSum = 0;
foreach (var pool in availablePools)
{
currentWeightSum += pool.Weight;
if (randomValue < currentWeightSum)
return pool;
}
return availablePools.LastOrDefault();
}
private LotteryPrize? DrawPrize(LotteryPoolLevel level, List<string> drawnPrizeIds)
{
var availablePrizes = level.PrizeList
.Where(p => !drawnPrizeIds.Contains(p.PrizeId) &&
(p.TotalCount == 0 || p.DrawnCount < p.TotalCount))
.ToList();
if (!availablePrizes.Any())
return null;
int totalWeight = availablePrizes.Sum(p => p.Weight);
if (totalWeight <= 0)
return null;
var random = new Random();
int randomValue = random.Next(0, totalWeight);
int currentWeightSum = 0;
foreach (var prize in availablePrizes)
{
currentWeightSum += prize.Weight;
if (randomValue < currentWeightSum)
return prize;
}
return availablePrizes.LastOrDefault();
}
private LotteryPrize? DrawPrizeFromAll(List<LotteryPoolLevel> pools, List<string> drawnPrizeIds)
{
foreach (var pool in pools.OrderByDescending(p => p.Weight))
{
var prize = DrawPrize(pool, drawnPrizeIds);
if (prize != null)
return prize;
}
return null;
}
public async Task<List<string>> GetUserDrawnPrizes(string userId)
{
var status = await GetUserStatus(userId);
return status?.DrawnPrizeIds ?? new List<string>();
}
private static string BuildCycleKey(LotteryActivityConfig? activity)
{
return activity == null ? "0:0" : $"{activity.StartTime}:{activity.EndTime}";
}
}
}