254 lines
9.7 KiB
C#
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}";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|