using FreeRedis;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.Caching;
using Volo.Abp.Domain.Services;
using Volo.Abp.EventBus.Local;
using Volo.Abp.Settings;
using Volo.Abp.Threading;
using Yi.Framework.DigitalCollectibles.Domain.Dtos;
using Yi.Framework.DigitalCollectibles.Domain.Entities;
using Yi.Framework.DigitalCollectibles.Domain.Shared.Consts;
using Yi.Framework.DigitalCollectibles.Domain.Shared.Enums;
using Yi.Framework.DigitalCollectibles.Domain.Shared.Etos;
using Yi.Framework.SettingManagement.Domain;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.DigitalCollectibles.Domain.Managers;
///
/// 矿池领域服务
/// 处理矿池相关业务,例如挖矿等
///
public class MiningPoolManager : DomainService
{
private readonly ISqlSugarRepository _collectiblesRepository;
private readonly ISqlSugarRepository _onHookRepository;
private readonly ISettingProvider _settingProvider;
private readonly IDistributedCache _miningPoolCache;
private readonly IDistributedCache _userMiningLimitCache;
private readonly ISqlSugarRepository _userStoreRepository;
private readonly ILogger _logger;
private IRedisClient RedisClient => LazyServiceProvider.LazyGetRequiredService();
private ILocalEventBus LocalEventBus => LazyServiceProvider.LazyGetRequiredService();
public MiningPoolManager(ISettingProvider settingProvider, IDistributedCache miningPoolCache,
ISqlSugarRepository collectiblesRepository,
ISqlSugarRepository onHookRepository,
ISqlSugarRepository userStoreRepository,
IDistributedCache userMiningLimitCache, ILogger logger)
{
_settingProvider = settingProvider;
this._miningPoolCache = miningPoolCache;
_collectiblesRepository = collectiblesRepository;
_onHookRepository = onHookRepository;
_userStoreRepository = userStoreRepository;
_userMiningLimitCache = userMiningLimitCache;
_logger = logger;
}
///
/// 扣减矿池
///
/// 矿物等级
public async Task DeductionPoolAsync(RarityEnum rarity)
{
var pool = await GetMiningPoolContentAsync();
switch (rarity)
{
case RarityEnum.Ordinary:
pool.I0_OrdinaryNumber -= 1;
break;
case RarityEnum.Senior:
pool.I1_SeniorNumber -= 1;
break;
case RarityEnum.Rare:
pool.I2_RareNumber -= 1;
break;
case RarityEnum.Gem:
pool.I3_GemNumber -= 1;
break;
case RarityEnum.Legend:
pool.I4_LegendNumber -= 1;
break;
}
//重新设置
await SetMiningPoolAsync(pool);
}
///
/// 每次挖矿概率,每天根据特定算法计算
///
private decimal CurrentMiningProbability => AsyncHelper.RunSync(async () =>
{
return await ComputeMiningProbabilityAsync();
});
public async Task GetMiningPoolContentAsync()
{
var pool = await _miningPoolCache.GetAsync(MiningCacheConst.MiningPoolContent);
return pool;
}
public async Task GetOnHookAsync(Guid userId)
{
var onHook = await _onHookRepository._DbQueryable.Where(x => x.UserId == userId)
.Where(x => x.IsActive == true)
.Where(x => x.EndTime > DateTime.Now)
.FirstAsync();
if (onHook is not null)
{
throw new UserFriendlyException($"当前你正在进行自动挂机,结束时间:{onHook.EndTime.Value.ToString("MM月dd日HH分mm秒")})");
}
await _onHookRepository.InsertAsync(new OnHookAggregateRoot(userId, 24));
}
///
/// 校验挖矿限制
///
///
private async Task VerifyMiningLimitAsync(Guid userId)
{
//每天最大次数限制
var miningMaxLimit = int.Parse(await _settingProvider.GetOrNullAsync("MiningMaxLimit"));
//每次间隔
var miningMinIntervalSeconds = int.Parse(await _settingProvider.GetOrNullAsync("MiningMinIntervalSeconds"));
var currentNumber = 1;
//根据用户进行上锁
if (await RedisClient.SetNxAsync($"UserMiningLimitLock:{userId}", true,
TimeSpan.FromSeconds(miningMinIntervalSeconds)))
{
var userLimit = await _userMiningLimitCache.GetAsync($"{MiningCacheConst.UserMiningLimit}:{userId}");
if (userLimit is not null)
{
//符合条件,成功挖矿
if (userLimit.Number < miningMaxLimit)
{
currentNumber = userLimit.Number + 1;
}
else
{
throw new UserFriendlyException($"失败,你已达当轮矿池最大限制,给其他人留点吧");
}
}
//没有缓存过,必定成功,直接走
await _userMiningLimitCache.SetAsync($"{MiningCacheConst.UserMiningLimit}:{userId}",
new UserMiningLimitCacheDto
{
Number = currentNumber,
LastMiningTime = DateTime.Now,
},
new DistributedCacheEntryOptions()
{
//虽然新增的是一天,但是每次刷新是早上10点,矿池刷新时,还需要清除限制
AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1)
});
return;
}
//已上过锁,并且没有到限制时间,必定失败
//不符合条件,直接走
throw new UserFriendlyException($"失败,你都挖的冒烟了,请稍后再挖");
}
///
/// 用户进行一次挖矿
///
///
public async Task MiningAsync(Guid userId)
{
//检验限制
await VerifyMiningLimitAsync(userId);
var result = new MiningPoolResult();
//从矿池中开挖,判断矿池是否还有矿
//根据挖矿概率,进行挖掘
//挖到了放到用户仓库即可
//如果概率是挖到了矿,再从矿物中随机选择一个稀有度,再在当前稀有度中的矿物列表,随机选择一个具体的矿物
var pool = await GetMiningPoolContentAsync();
if (pool is null || pool.TotalNumber == 0)
{
throw new UserFriendlyException($"失败,矿池已经被掏空了,请等矿池刷新后再来");
}
// 生成一个 0 到 1 之间的随机数
double randomValue = new Random().NextDouble();
//如果随机的概率在当前概率内,成功
if (randomValue.To() < CurrentMiningProbability)
{
//成功后在藏品中根据稀有度概率必定获取一个
var probabilityArray = RarityEnumExtensions.GetProbabilityArray();
var index = GetRandomIndex(probabilityArray);
var rarityType = (RarityEnum)Enum.GetValues(typeof(RarityEnum)).GetValue(index)!;
var collectiblesList =
await _collectiblesRepository._DbQueryable.Where(x => x.Rarity == rarityType).ToListAsync();
//当前等级的矿物没有设置
if (collectiblesList.Count == 0)
{
throw new UserFriendlyException($"可惜!差一点就挖到了");
}
bool poolState = true;
switch (rarityType)
{
case RarityEnum.Ordinary:
if (pool.I0_OrdinaryNumber <= 0)
{
poolState = false;
}
break;
case RarityEnum.Senior:
if (pool.I1_SeniorNumber <= 0)
{
poolState = false;
}
break;
case RarityEnum.Rare:
if (pool.I2_RareNumber <= 0)
{
poolState = false;
}
break;
case RarityEnum.Gem:
if (pool.I3_GemNumber <= 0)
{
poolState = false;
}
break;
case RarityEnum.Legend:
if (pool.I4_LegendNumber <= 0)
{
poolState = false;
}
break;
}
if (poolState == false)
{
throw new UserFriendlyException($"超级可惜!真的真的只差最后一点就挖到了");
}
int randomIndex = new Random().Next(collectiblesList.Count);
var currentCollectibles = collectiblesList[randomIndex];
result.Collectibles = currentCollectibles;
await LocalEventBus.PublishAsync(new SuccessMiningEto
{
CollectiblesId = currentCollectibles.Id,
UserId = userId
}, false);
return result;
}
throw new UserFriendlyException($"恭喜!空空如也,挖了个寂寞");
}
///
/// 挂机挖矿
///
public async Task OnHookMiningAsync()
{
//获取当前激活的挂机挖矿
var currentOnHook = await _onHookRepository._DbQueryable.Where(x => x.IsActive == true)
.Where(x => x.EndTime <= DateTime.Now).ToListAsync();
//根据用户对挂机卡hash关系
var userOnHookDic = currentOnHook.GroupBy(x => x.UserId).ToDictionary(x => x.Key, y => y.First());
foreach (var onHookItem in userOnHookDic)
{
try
{
await MiningAsync(onHookItem.Value.UserId);
}
catch (UserFriendlyException e)
{
_logger.LogInformation($"自动挖矿-{onHookItem.Value.UserId},{e.Message}");
}
}
}
private int GetRandomIndex(decimal[] probabilities)
{
// 生成0到1之间的随机数
Random random = new Random();
decimal randomValue = random.NextDouble().To();
decimal cumulativeProbability = 0.0m;
for (int i = 0; i < probabilities.Length; i++)
{
cumulativeProbability += probabilities[i];
// 判断随机数是否小于当前的累积概率
if (randomValue < cumulativeProbability)
{
return i; // 返回中标的索引
}
}
//剩余情况都是普通
return 0;
}
///
/// 计算当前挖矿概率
///
///
public async Task ComputeMiningProbabilityAsync()
{
//当前的挖矿期望:当天的所有藏品能被刚好挖完
//保底概率:最大不能高过一个值,百分之10
//手动挖矿:1天可挖10次,每次至少间隔6秒
//自动挖矿:1天可以挖24次 每次间隔60分钟(需要使用自动挖矿卡)
//可影响因素:剩余手动挖矿次数+剩余自动挖矿次数
//简单模式,1/15
var miningMaxLimit = decimal.Parse(await _settingProvider.GetOrNullAsync("MiningMinProbability"));
return miningMaxLimit;
}
///
/// 刷新矿池
///
///
public async Task RefreshMiningPoolAsync()
{
//获取当前最大的限制
// var maximumPoolLimit = int.Parse(await _settingProvider.GetOrNullAsync("MaxPoolLimit"));
var poolData = (await _settingProvider.GetOrNullAsync("PoolData")).Split(',').Select(x => int.Parse(x))
.ToList();
DateTime startTime = DateTime.Today.AddHours(10);
DateTime endTime = startTime.AddDays(1);
// var probabilityValues = RarityEnumExtensions.GetProbabilityArray();
// var result = GenerateDistribution(maximumPoolLimit, probabilityValues);
//根据配置,将不同比例的矿,塞入矿池,
//矿池,交给redis
await SetMiningPoolAsync(new MiningPoolContent(startTime, endTime)
{
I0_OrdinaryNumber = poolData[0],
I1_SeniorNumber = poolData[1],
I2_RareNumber = poolData[2],
I3_GemNumber = poolData[3],
I4_LegendNumber = poolData[4]
});
}
private async Task SetMiningPoolAsync(MiningPoolContent content)
{
await _miningPoolCache.SetAsync(MiningCacheConst.MiningPoolContent, content
, new DistributedCacheEntryOptions
{
AbsoluteExpiration = content.EndTime
});
}
///
/// 缓存前缀
///
private string CacheKeyPrefix => LazyServiceProvider.LazyGetRequiredService>()
.Value.KeyPrefix;
///
/// 刷新用户挖矿限制
///
public async Task RefreshMiningUserLimitAsync()
{
var needKeys = await RedisClient.KeysAsync($"{CacheKeyPrefix}{MiningCacheConst.UserMiningLimit}*");
foreach (var needKey in needKeys)
{
await RedisClient.DelAsync(needKey);
}
}
///
/// 根据概率生成给对应稀有度藏品
///
///
///
///
///
private int[] GenerateDistribution(int totalCount, decimal[] probabilities)
{
int[] counts = new int[probabilities.Length];
decimal totalProbability = 0;
// 计算概率总和,确保为 1
foreach (var prob in probabilities)
{
totalProbability += prob;
}
// 检查概率之和是否为 1
if (totalProbability < 0.99m || totalProbability > 1.01m)
{
throw new ArgumentException("概率总和必须接近1");
}
// 生成分布
for (int i = 0; i < probabilities.Length; i++)
{
counts[i] = (int)(totalCount * probabilities[i]);
}
// 处理可能因精度问题导致的总数不足
int sum = 0;
foreach (var count in counts)
{
sum += count;
}
int difference = totalCount - sum;
// 将差值分配给概率最大的一项
if (difference > 0)
{
int maxIndex = Array.IndexOf(counts, Math.Max(counts[0], counts[1]));
counts[maxIndex] += difference;
}
return counts;
}
}