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 _recordRepo; private readonly IMongoBaseRepository _userStatusRepo; private readonly IUserService _userService; private readonly ICapPublisher _capPublisher; private readonly IActivityConfigService _activityConfig; public LotteryService( IMongoBaseRepository recordRepo, IMongoBaseRepository userStatusRepo, IUserService userService, ICapPublisher capPublisher, IActivityConfigService activityConfig) { _recordRepo = recordRepo; _userStatusRepo = userStatusRepo; _userService = userService; _capPublisher = capPublisher; _activityConfig = activityConfig; } public async Task 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(); status.TotalDrawCount = 0; status.DrawCountByPool = new Dictionary(); 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(); 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 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 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 pools, List 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> GetUserDrawnPrizes(string userId) { var status = await GetUserStatus(userId); return status?.DrawnPrizeIds ?? new List(); } private static string BuildCycleKey(LotteryActivityConfig? activity) { return activity == null ? "0:0" : $"{activity.StartTime}:{activity.EndTime}"; } } }