diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..76069758 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "WebFetch(domain:www.donet5.com)" + ], + "deny": [], + "ask": [] + } +} diff --git a/Yi.Abp.Net8/.claude/settings.local.json b/Yi.Abp.Net8/.claude/settings.local.json new file mode 100644 index 00000000..11d555b0 --- /dev/null +++ b/Yi.Abp.Net8/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(dotnet build \"E:\\code\\github\\Yi\\Yi.Abp.Net8\\module\\ai-hub\\Yi.Framework.AiHub.Application\\Yi.Framework.AiHub.Application.csproj\" --no-restore)" + ], + "deny": [], + "ask": [] + } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/CardFlipStatusOutput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/CardFlipStatusOutput.cs index c1c218e7..3594dbb8 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/CardFlipStatusOutput.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/CardFlip/CardFlipStatusOutput.cs @@ -85,4 +85,9 @@ public class CardFlipRecord /// 翻牌类型描述 /// public string? FlipTypeDesc { get; set; } + + /// + /// 在翻牌顺序中的位置(1-10,表示第几个翻) + /// + public int FlipOrderIndex { get; set; } } diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/CardFlipService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/CardFlipService.cs index 56f0fc1d..d0668225 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/CardFlipService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/CardFlipService.cs @@ -1,52 +1,35 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Logging; -using SqlSugar; using Volo.Abp.Application.Services; using Volo.Abp.Users; using Yi.Framework.AiHub.Application.Contracts.Dtos.CardFlip; using Yi.Framework.AiHub.Application.Contracts.IServices; using Yi.Framework.AiHub.Domain.Entities; using Yi.Framework.AiHub.Domain.Extensions; +using Yi.Framework.AiHub.Domain.Managers; using Yi.Framework.SqlSugarCore.Abstractions; namespace Yi.Framework.AiHub.Application.Services; /// -/// 翻牌服务 +/// 翻牌服务 - 应用层组合服务 /// [Authorize] public class CardFlipService : ApplicationService, ICardFlipService { - private readonly ISqlSugarRepository _cardFlipTaskRepository; - private readonly ISqlSugarRepository _inviteCodeRepository; - private readonly ISqlSugarRepository _invitationRecordRepository; + private readonly CardFlipManager _cardFlipManager; + private readonly InviteCodeManager _inviteCodeManager; private readonly ISqlSugarRepository _premiumPackageRepository; private readonly ILogger _logger; - // 翻牌规则配置 - private const int MAX_FREE_FLIPS = 5; // 免费翻牌次数 - private const int MAX_BONUS_FLIPS = 3; // 赠送翻牌次数 - private const int MAX_INVITE_FLIPS = 2; // 邀请解锁翻牌次数 - private const int TOTAL_MAX_FLIPS = 10; // 总最大翻牌次数 - - private const int NINTH_FLIP = 9; // 第9次翻牌 - private const int TENTH_FLIP = 10; // 第10次翻牌 - - private const long NINTH_MIN_REWARD = 3000000; // 第9次最小奖励 300w - private const long NINTH_MAX_REWARD = 7000000; // 第9次最大奖励 700w - private const long TENTH_MIN_REWARD = 8000000; // 第10次最小奖励 800w - private const long TENTH_MAX_REWARD = 12000000; // 第10次最大奖励 1200w - public CardFlipService( - ISqlSugarRepository cardFlipTaskRepository, - ISqlSugarRepository inviteCodeRepository, - ISqlSugarRepository invitationRecordRepository, + CardFlipManager cardFlipManager, + InviteCodeManager inviteCodeManager, ISqlSugarRepository premiumPackageRepository, ILogger logger) { - _cardFlipTaskRepository = cardFlipTaskRepository; - _inviteCodeRepository = inviteCodeRepository; - _invitationRecordRepository = invitationRecordRepository; + _cardFlipManager = cardFlipManager; + _inviteCodeManager = inviteCodeManager; _premiumPackageRepository = premiumPackageRepository; _logger = logger; } @@ -57,32 +40,27 @@ public class CardFlipService : ApplicationService, ICardFlipService public async Task GetWeeklyTaskStatusAsync() { var userId = CurrentUser.GetId(); - var weekStart = GetWeekStartDate(DateTime.Now); + var weekStart = CardFlipManager.GetWeekStartDate(DateTime.Now); - // 获取或创建本周任务 - var task = await GetOrCreateWeeklyTaskAsync(userId, weekStart, createIfNotExists: false); + // 获取本周任务 + var task = await _cardFlipManager.GetOrCreateWeeklyTaskAsync(userId, weekStart, createIfNotExists: false); // 获取邀请码信息 - var inviteCode = await _inviteCodeRepository._DbQueryable - .Where(x => x.UserId == userId) - .FirstAsync(); + var inviteCode = await _inviteCodeManager.GetUserInviteCodeAsync(userId); // 统计本周邀请人数 - var invitedCount = await _invitationRecordRepository._DbQueryable - .Where(x => x.InviterId == userId) - .Where(x => x.InvitationTime >= weekStart) - .CountAsync(); + var invitedCount = await _inviteCodeManager.GetWeeklyInvitationCountAsync(userId, weekStart); // 检查用户是否已被邀请 - var isInvited = inviteCode?.IsUserInvited ?? false; + var isInvited = await _inviteCodeManager.IsUserInvitedAsync(userId); var output = new CardFlipStatusOutput { TotalFlips = task?.TotalFlips ?? 0, - RemainingFreeFlips = MAX_FREE_FLIPS - (task?.FreeFlipsUsed ?? 0), - RemainingBonusFlips = MAX_BONUS_FLIPS - (task?.BonusFlipsUsed ?? 0), - RemainingInviteFlips = MAX_INVITE_FLIPS - (task?.InviteFlipsUsed ?? 0), - CanFlip = CanFlipCard(task), + RemainingFreeFlips = CardFlipManager.MAX_FREE_FLIPS - (task?.FreeFlipsUsed ?? 0), + RemainingBonusFlips = CardFlipManager.MAX_BONUS_FLIPS - (task?.BonusFlipsUsed ?? 0), + RemainingInviteFlips = CardFlipManager.MAX_INVITE_FLIPS - (task?.InviteFlipsUsed ?? 0), + CanFlip = _cardFlipManager.CanFlipCard(task), MyInviteCode = inviteCode?.Code, InvitedCount = invitedCount, IsInvited = isInvited, @@ -101,90 +79,28 @@ public class CardFlipService : ApplicationService, ICardFlipService public async Task FlipCardAsync(FlipCardInput input) { var userId = CurrentUser.GetId(); - var weekStart = GetWeekStartDate(DateTime.Now); + var weekStart = CardFlipManager.GetWeekStartDate(DateTime.Now); - if (input.FlipNumber < 1 || input.FlipNumber > TOTAL_MAX_FLIPS) + // 执行翻牌逻辑(由Manager处理验证和翻牌) + var result = await _cardFlipManager.ExecuteFlipAsync(userId, input.FlipNumber, weekStart); + + // 如果中奖,发放奖励 + if (result.IsWin) { - throw new UserFriendlyException($"翻牌序号必须在1-{TOTAL_MAX_FLIPS}之间"); + await GrantRewardAsync(userId, result.RewardAmount, $"翻牌活动第{input.FlipNumber}次中奖"); } - // 获取或创建本周任务 - var task = await GetOrCreateWeeklyTaskAsync(userId, weekStart, createIfNotExists: true); - - // 验证翻牌次数 - if (task.TotalFlips >= TOTAL_MAX_FLIPS) - { - throw new UserFriendlyException("本周翻牌次数已用完,请下周再来!"); - } - - // 验证顺序翻牌 - if (input.FlipNumber != task.TotalFlips + 1) - { - throw new UserFriendlyException("请按顺序翻牌!"); - } - - // 判断翻牌类型 - var flipType = DetermineFlipType(task); - - // 验证是否有足够的次数 - if (!CanUseFlipType(task, flipType)) - { - throw new UserFriendlyException(GetFlipTypeErrorMessage(flipType)); - } - - // 翻牌逻辑 + // 构建输出 var output = new FlipCardOutput { - FlipNumber = input.FlipNumber, - IsWin = false, - ShowDoubleRewardTip = false + FlipNumber = result.FlipNumber, + IsWin = result.IsWin, + RewardAmount = result.RewardAmount, + RewardDesc = result.RewardDesc, + ShowDoubleRewardTip = result.ShowDoubleRewardTip, + RemainingFlips = CardFlipManager.TOTAL_MAX_FLIPS - input.FlipNumber }; - // 前8次固定失败 - if (input.FlipNumber <= 8) - { - output.IsWin = false; - output.RewardDesc = "很遗憾,未中奖"; - } - // 第9次中奖 - else if (input.FlipNumber == NINTH_FLIP) - { - var rewardAmount = GenerateRandomReward(NINTH_MIN_REWARD, NINTH_MAX_REWARD); - output.IsWin = true; - output.RewardAmount = rewardAmount; - output.RewardDesc = $"恭喜获得尊享包 {rewardAmount / 10000}w tokens!"; - output.ShowDoubleRewardTip = true; // 显示翻倍包提示 - - // 发放奖励 - await GrantRewardAsync(userId, rewardAmount, $"翻牌活动第{input.FlipNumber}次中奖"); - - // 记录奖励 - task.SetNinthReward(rewardAmount); - } - // 第10次中奖(翻倍) - else if (input.FlipNumber == TENTH_FLIP) - { - var rewardAmount = GenerateRandomReward(TENTH_MIN_REWARD, TENTH_MAX_REWARD); - output.IsWin = true; - output.RewardAmount = rewardAmount; - output.RewardDesc = $"恭喜获得尊享包 {rewardAmount / 10000}w tokens(翻倍奖励)!"; - - // 发放奖励 - await GrantRewardAsync(userId, rewardAmount, $"翻牌活动第{input.FlipNumber}次中奖(翻倍)"); - - // 记录奖励 - task.SetTenthReward(rewardAmount); - } - - // 更新翻牌次数 - task.IncrementFlip(flipType); - await _cardFlipTaskRepository.UpdateAsync(task); - - // 计算剩余次数 - output.RemainingFlips = TOTAL_MAX_FLIPS - task.TotalFlips; - - _logger.LogInformation($"用户 {userId} 完成第 {input.FlipNumber} 次翻牌,中奖:{output.IsWin}"); - return output; } @@ -194,85 +110,21 @@ public class CardFlipService : ApplicationService, ICardFlipService public async Task UseInviteCodeAsync(UseInviteCodeInput input) { var userId = CurrentUser.GetId(); - var weekStart = GetWeekStartDate(DateTime.Now); - - if (string.IsNullOrWhiteSpace(input.InviteCode)) - { - throw new UserFriendlyException("邀请码不能为空"); - } - - // 查找邀请码 - var inviteCode = await _inviteCodeRepository._DbQueryable - .Where(x => x.Code == input.InviteCode) - .FirstAsync(); - - if (inviteCode == null) - { - throw new UserFriendlyException("邀请码不存在"); - } - - // 验证不能使用自己的邀请码 - if (inviteCode.UserId == userId) - { - throw new UserFriendlyException("不能使用自己的邀请码"); - } - - // 验证邀请码是否已被使用 - if (inviteCode.IsUsed) - { - throw new UserFriendlyException("该邀请码已被使用"); - } - - // 验证邀请码拥有者是否已被邀请 - if (inviteCode.IsUserInvited) - { - throw new UserFriendlyException("该用户已被邀请,邀请码无效"); - } - - // 检查当前用户是否已被邀请 - var myInviteCode = await _inviteCodeRepository._DbQueryable - .Where(x => x.UserId == userId) - .FirstAsync(); - - if (myInviteCode?.IsUserInvited == true) - { - throw new UserFriendlyException("您已使用过邀请码,无法重复使用"); - } + var weekStart = CardFlipManager.GetWeekStartDate(DateTime.Now); // 获取本周任务 - var task = await GetOrCreateWeeklyTaskAsync(userId, weekStart, createIfNotExists: true); + var task = await _cardFlipManager.GetOrCreateWeeklyTaskAsync(userId, weekStart, createIfNotExists: true); // 验证是否已经使用了所有邀请解锁次数 - if (task.InviteFlipsUsed >= MAX_INVITE_FLIPS) + if (task.InviteFlipsUsed >= CardFlipManager.MAX_INVITE_FLIPS) { throw new UserFriendlyException("本周邀请解锁次数已用完"); } - // 标记邀请码为已使用 - inviteCode.MarkAsUsed(userId); - await _inviteCodeRepository.UpdateAsync(inviteCode); + // 使用邀请码(由Manager处理验证和邀请逻辑) + await _inviteCodeManager.UseInviteCodeAsync(userId, input.InviteCode); - // 标记当前用户已被邀请 - if (myInviteCode == null) - { - myInviteCode = new InviteCodeAggregateRoot(userId, GenerateInviteCode()); - myInviteCode.MarkUserAsInvited(); - await _inviteCodeRepository.InsertAsync(myInviteCode); - } - else - { - myInviteCode.MarkUserAsInvited(); - await _inviteCodeRepository.UpdateAsync(myInviteCode); - } - - // 创建邀请记录 - var invitationRecord = new InvitationRecordAggregateRoot( - inviteCode.UserId, - userId, - input.InviteCode); - await _invitationRecordRepository.InsertAsync(invitationRecord); - - _logger.LogInformation($"用户 {userId} 使用邀请码 {input.InviteCode} 成功"); + _logger.LogInformation($"用户 {userId} 使用邀请码 {input.InviteCode} 解锁翻牌次数成功"); } /// @@ -281,38 +133,28 @@ public class CardFlipService : ApplicationService, ICardFlipService public async Task GetMyInviteCodeAsync() { var userId = CurrentUser.GetId(); - var weekStart = GetWeekStartDate(DateTime.Now); + var weekStart = CardFlipManager.GetWeekStartDate(DateTime.Now); // 获取我的邀请码 - var inviteCode = await _inviteCodeRepository._DbQueryable - .Where(x => x.UserId == userId) - .FirstAsync(); + var inviteCode = await _inviteCodeManager.GetUserInviteCodeAsync(userId); // 统计本周邀请人数 - var invitedCount = await _invitationRecordRepository._DbQueryable - .Where(x => x.InviterId == userId) - .Where(x => x.InvitationTime >= weekStart) - .CountAsync(); + var invitedCount = await _inviteCodeManager.GetWeeklyInvitationCountAsync(userId, weekStart); // 获取邀请历史 - var invitationHistory = await _invitationRecordRepository._DbQueryable - .Where(x => x.InviterId == userId) - .OrderBy(x => x.InvitationTime, OrderByType.Desc) - .Take(10) - .Select(x => new InvitationHistoryItem - { - InvitedUserName = "用户***", // 脱敏处理 - InvitationTime = x.InvitationTime, - WeekDescription = GetWeekDescription(x.InvitationTime) - }) - .ToListAsync(); + var invitationHistory = await _inviteCodeManager.GetInvitationHistoryAsync(userId, 10); return new InviteCodeOutput { MyInviteCode = inviteCode?.Code, InvitedCount = invitedCount, IsInvited = inviteCode?.IsUserInvited ?? false, - InvitationHistory = invitationHistory + InvitationHistory = invitationHistory.Select(x => new InvitationHistoryItem + { + InvitedUserName = x.InvitedUserName, + InvitationTime = x.InvitationTime, + WeekDescription = GetWeekDescription(x.InvitationTime) + }).ToList() }; } @@ -323,120 +165,14 @@ public class CardFlipService : ApplicationService, ICardFlipService { var userId = CurrentUser.GetId(); - // 检查是否已有邀请码 - var existingCode = await _inviteCodeRepository._DbQueryable - .Where(x => x.UserId == userId) - .FirstAsync(); - - if (existingCode != null) - { - return existingCode.Code; - } - - // 生成新邀请码 - var code = GenerateInviteCode(); - var inviteCode = new InviteCodeAggregateRoot(userId, code); - await _inviteCodeRepository.InsertAsync(inviteCode); - - _logger.LogInformation($"用户 {userId} 生成邀请码 {code}"); + // 生成邀请码(由Manager处理) + var code = await _inviteCodeManager.GenerateInviteCodeForUserAsync(userId); return code; } #region 私有辅助方法 - /// - /// 获取或创建本周任务 - /// - private async Task GetOrCreateWeeklyTaskAsync( - Guid userId, - DateTime weekStart, - bool createIfNotExists) - { - var task = await _cardFlipTaskRepository._DbQueryable - .Where(x => x.UserId == userId && x.WeekStartDate == weekStart) - .FirstAsync(); - - if (task == null && createIfNotExists) - { - task = new CardFlipTaskAggregateRoot(userId, weekStart); - await _cardFlipTaskRepository.InsertAsync(task); - } - - return task; - } - - /// - /// 获取本周开始日期(周一) - /// - private DateTime GetWeekStartDate(DateTime date) - { - var dayOfWeek = (int)date.DayOfWeek; - // 将周日(0)转换为7 - if (dayOfWeek == 0) dayOfWeek = 7; - - // 计算本周一的日期 - var monday = date.Date.AddDays(-(dayOfWeek - 1)); - return monday; - } - - /// - /// 判断是否可以翻牌 - /// - private bool CanFlipCard(CardFlipTaskAggregateRoot? task) - { - if (task == null) return true; // 没有任务记录,可以开始翻牌 - - return task.TotalFlips < TOTAL_MAX_FLIPS; - } - - /// - /// 判断翻牌类型 - /// - private FlipType DetermineFlipType(CardFlipTaskAggregateRoot task) - { - if (task.FreeFlipsUsed < MAX_FREE_FLIPS) - { - return FlipType.Free; - } - else if (task.BonusFlipsUsed < MAX_BONUS_FLIPS) - { - return FlipType.Bonus; - } - else - { - return FlipType.Invite; - } - } - - /// - /// 判断是否可以使用该翻牌类型 - /// - private bool CanUseFlipType(CardFlipTaskAggregateRoot task, FlipType flipType) - { - return flipType switch - { - FlipType.Free => task.FreeFlipsUsed < MAX_FREE_FLIPS, - FlipType.Bonus => task.BonusFlipsUsed < MAX_BONUS_FLIPS, - FlipType.Invite => task.InviteFlipsUsed < MAX_INVITE_FLIPS, - _ => false - }; - } - - /// - /// 获取翻牌类型错误提示 - /// - private string GetFlipTypeErrorMessage(FlipType flipType) - { - return flipType switch - { - FlipType.Free => "免费翻牌次数已用完", - FlipType.Bonus => "赠送翻牌次数已用完", - FlipType.Invite => "需要使用邀请码解锁更多次数", - _ => "无法翻牌" - }; - } - /// /// 构建翻牌记录列表 /// @@ -444,25 +180,32 @@ public class CardFlipService : ApplicationService, ICardFlipService { var records = new List(); - for (int i = 1; i <= TOTAL_MAX_FLIPS; i++) + // 获取已翻牌的顺序 + var flippedOrder = task != null ? _cardFlipManager.GetFlippedOrder(task) : new List(); + var flippedNumbers = new HashSet(flippedOrder); + + // 构建记录,按照原始序号1-10排列 + for (int i = 1; i <= CardFlipManager.TOTAL_MAX_FLIPS; i++) { var record = new CardFlipRecord { FlipNumber = i, - IsFlipped = task != null && i <= task.TotalFlips, + IsFlipped = flippedNumbers.Contains(i), IsWin = false, - FlipTypeDesc = GetFlipTypeDesc(i) + FlipTypeDesc = CardFlipManager.GetFlipTypeDesc(i), + // 设置在翻牌顺序中的位置(0表示未翻,>0表示第几个翻的) + FlipOrderIndex = flippedOrder.IndexOf(i) >= 0 ? flippedOrder.IndexOf(i) + 1 : 0 }; // 设置中奖信息 - if (task != null && i <= task.TotalFlips) + if (task != null && flippedNumbers.Contains(i)) { - if (i == NINTH_FLIP && task.HasNinthReward) + if (i == 9 && task.HasNinthReward) { record.IsWin = true; record.RewardAmount = task.NinthRewardAmount; } - else if (i == TENTH_FLIP && task.HasTenthReward) + else if (i == 10 && task.HasTenthReward) { record.IsWin = true; record.RewardAmount = task.TenthRewardAmount; @@ -475,33 +218,14 @@ public class CardFlipService : ApplicationService, ICardFlipService return records; } - /// - /// 获取翻牌类型描述 - /// - private string GetFlipTypeDesc(int flipNumber) - { - if (flipNumber <= MAX_FREE_FLIPS) - { - return "免费"; - } - else if (flipNumber <= MAX_FREE_FLIPS + MAX_BONUS_FLIPS) - { - return "赠送"; - } - else - { - return "邀请解锁"; - } - } - /// /// 生成下次翻牌提示 /// private string GenerateNextFlipTip(CardFlipStatusOutput status) { - if (status.TotalFlips >= TOTAL_MAX_FLIPS) + if (status.TotalFlips >= CardFlipManager.TOTAL_MAX_FLIPS) { - return "本周翻牌次数已用完,请下周再来!"; + return "本周翻牌次数已用完,请下周再来!"; } if (status.RemainingFreeFlips > 0) @@ -516,21 +240,13 @@ public class CardFlipService : ApplicationService, ICardFlipService { if (status.TotalFlips == 8) { - return "再邀请一个人,马上中奖!"; + return "再邀请一个人,马上中奖!"; } + return $"使用邀请码可解锁{status.RemainingInviteFlips}次翻牌"; } - return "继续加油!"; - } - - /// - /// 生成随机奖励金额 - /// - private long GenerateRandomReward(long min, long max) - { - var random = new Random(); - return (long)(random.NextDouble() * (max - min) + min); + return "继续加油!"; } /// @@ -541,7 +257,7 @@ public class CardFlipService : ApplicationService, ICardFlipService var premiumPackage = new PremiumPackageAggregateRoot(userId, amount, description) { PurchaseAmount = 0, // 奖励不需要付费 - Remark = $"翻牌活动奖励:{amount / 10000}w tokens" + Remark = $"翻牌活动奖励:{amount / 10000}w tokens" }; await _premiumPackageRepository.InsertAsync(premiumPackage); @@ -549,33 +265,16 @@ public class CardFlipService : ApplicationService, ICardFlipService _logger.LogInformation($"用户 {userId} 获得翻牌奖励 {amount / 10000}w tokens"); } - /// - /// 生成邀请码 - /// - private string GenerateInviteCode() - { - const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - var random = new Random(); - var code = new char[8]; - - for (int i = 0; i < code.Length; i++) - { - code[i] = chars[random.Next(chars.Length)]; - } - - return new string(code); - } - /// /// 获取周描述 /// private string GetWeekDescription(DateTime date) { - var weekStart = GetWeekStartDate(date); + var weekStart = CardFlipManager.GetWeekStartDate(date); var weekEnd = weekStart.AddDays(6); return $"{weekStart:MM-dd} 至 {weekEnd:MM-dd}"; } #endregion -} +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/CardFlipTaskAggregateRoot.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/CardFlipTaskAggregateRoot.cs index 4b795299..a13bb813 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/CardFlipTaskAggregateRoot.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/CardFlipTaskAggregateRoot.cs @@ -27,6 +27,7 @@ public class CardFlipTaskAggregateRoot : FullAuditedAggregateRoot IsFirstFlipDone = false; HasNinthReward = false; HasTenthReward = false; + FlippedOrder = new List(); } /// @@ -90,6 +91,12 @@ public class CardFlipTaskAggregateRoot : FullAuditedAggregateRoot [SugarColumn(Length = 500, IsNullable = true)] public string? Remark { get; set; } + /// + /// 已翻牌的顺序(存储用户实际翻牌的序号列表,如[3,7,1,5]表示依次翻了3号、7号、1号、5号牌) + /// + [SugarColumn(IsJson = true, IsNullable = true)] + public List? FlippedOrder { get; set; } + /// /// 增加翻牌次数 /// diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/CardFlipManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/CardFlipManager.cs new file mode 100644 index 00000000..faa6ec16 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/CardFlipManager.cs @@ -0,0 +1,308 @@ +using Microsoft.Extensions.Logging; +using Volo.Abp.Domain.Services; +using Yi.Framework.AiHub.Domain.Entities; +using Yi.Framework.SqlSugarCore.Abstractions; + +namespace Yi.Framework.AiHub.Domain.Managers; + +/// +/// 翻牌管理器 - 负责翻牌核心业务逻辑 +/// +public class CardFlipManager : DomainService +{ + private readonly ISqlSugarRepository _cardFlipTaskRepository; + private readonly ILogger _logger; + + // 翻牌规则配置 + public const int MAX_FREE_FLIPS = 5; // 免费翻牌次数 + public const int MAX_BONUS_FLIPS = 3; // 赠送翻牌次数 + public const int MAX_INVITE_FLIPS = 2; // 邀请解锁翻牌次数 + public const int TOTAL_MAX_FLIPS = 10; // 总最大翻牌次数 + + private const int NINTH_FLIP = 9; // 第9次翻牌 + private const int TENTH_FLIP = 10; // 第10次翻牌 + + private const long NINTH_MIN_REWARD = 3000000; // 第9次最小奖励 300w + private const long NINTH_MAX_REWARD = 7000000; // 第9次最大奖励 700w + private const long TENTH_MIN_REWARD = 8000000; // 第10次最小奖励 800w + private const long TENTH_MAX_REWARD = 12000000; // 第10次最大奖励 1200w + + public CardFlipManager( + ISqlSugarRepository cardFlipTaskRepository, + ILogger logger) + { + _cardFlipTaskRepository = cardFlipTaskRepository; + _logger = logger; + } + + /// + /// 获取或创建本周任务 + /// + public async Task GetOrCreateWeeklyTaskAsync( + Guid userId, + DateTime weekStart, + bool createIfNotExists) + { + var task = await _cardFlipTaskRepository._DbQueryable + .Where(x => x.UserId == userId && x.WeekStartDate == weekStart) + .FirstAsync(); + + if (task == null && createIfNotExists) + { + task = new CardFlipTaskAggregateRoot(userId, weekStart); + await _cardFlipTaskRepository.InsertAsync(task); + } + + return task; + } + + /// + /// 获取已翻牌的顺序列表 + /// + public List GetFlippedOrder(CardFlipTaskAggregateRoot task) + { + return task.FlippedOrder ?? new List(); + } + + /// + /// 执行翻牌逻辑 + /// + /// 用户ID + /// 翻牌序号 + /// 本周开始日期 + /// 翻牌结果 + public async Task ExecuteFlipAsync(Guid userId, int flipNumber, DateTime weekStart) + { + // 验证翻牌序号 + if (flipNumber < 1 || flipNumber > TOTAL_MAX_FLIPS) + { + throw new UserFriendlyException($"翻牌序号必须在1-{TOTAL_MAX_FLIPS}之间"); + } + + // 获取或创建本周任务 + var task = await GetOrCreateWeeklyTaskAsync(userId, weekStart, createIfNotExists: true); + + // 验证翻牌次数 + if (task.TotalFlips >= TOTAL_MAX_FLIPS) + { + throw new UserFriendlyException("本周翻牌次数已用完,请下周再来!"); + } + + // 验证该牌是否已经翻过 + var flippedOrder = GetFlippedOrder(task); + if (flippedOrder.Contains(flipNumber)) + { + throw new UserFriendlyException($"第 {flipNumber} 号牌已经翻过了!"); + } + + // 判断翻牌类型 + var flipType = DetermineFlipType(task); + + // 验证是否有足够的次数 + if (!CanUseFlipType(task, flipType)) + { + throw new UserFriendlyException(GetFlipTypeErrorMessage(flipType)); + } + + // 计算翻牌结果 + var result = CalculateFlipResult(flipNumber); + + // 记录奖励 + if (result.IsWin) + { + if (flipNumber == NINTH_FLIP) + { + task.SetNinthReward(result.RewardAmount); + } + else if (flipNumber == TENTH_FLIP) + { + task.SetTenthReward(result.RewardAmount); + } + } + + // 更新翻牌次数 + task.IncrementFlip(flipType); + + // 记录翻牌顺序 + if (task.FlippedOrder == null) + { + task.FlippedOrder = new List(); + } + task.FlippedOrder.Add(flipNumber); + + await _cardFlipTaskRepository.UpdateAsync(task); + + _logger.LogInformation($"用户 {userId} 完成第 {flipNumber} 次翻牌,中奖:{result.IsWin}"); + + return result; + } + + /// + /// 判断是否可以翻牌 + /// + public bool CanFlipCard(CardFlipTaskAggregateRoot? task) + { + if (task == null) return true; // 没有任务记录,可以开始翻牌 + + return task.TotalFlips < TOTAL_MAX_FLIPS; + } + + /// + /// 判断翻牌类型 + /// + public FlipType DetermineFlipType(CardFlipTaskAggregateRoot task) + { + if (task.FreeFlipsUsed < MAX_FREE_FLIPS) + { + return FlipType.Free; + } + else if (task.BonusFlipsUsed < MAX_BONUS_FLIPS) + { + return FlipType.Bonus; + } + else + { + return FlipType.Invite; + } + } + + /// + /// 判断是否可以使用该翻牌类型 + /// + public bool CanUseFlipType(CardFlipTaskAggregateRoot task, FlipType flipType) + { + return flipType switch + { + FlipType.Free => task.FreeFlipsUsed < MAX_FREE_FLIPS, + FlipType.Bonus => task.BonusFlipsUsed < MAX_BONUS_FLIPS, + FlipType.Invite => task.InviteFlipsUsed < MAX_INVITE_FLIPS, + _ => false + }; + } + + /// + /// 计算翻牌结果 + /// + private FlipResult CalculateFlipResult(int flipNumber) + { + var result = new FlipResult + { + FlipNumber = flipNumber, + IsWin = false, + ShowDoubleRewardTip = false + }; + + // 前8次固定失败 + if (flipNumber <= 8) + { + result.IsWin = false; + result.RewardDesc = "很遗憾,未中奖"; + } + // 第9次中奖 + else if (flipNumber == NINTH_FLIP) + { + var rewardAmount = GenerateRandomReward(NINTH_MIN_REWARD, NINTH_MAX_REWARD); + result.IsWin = true; + result.RewardAmount = rewardAmount; + result.RewardDesc = $"恭喜获得尊享包 {rewardAmount / 10000}w tokens!"; + result.ShowDoubleRewardTip = true; // 显示翻倍包提示 + } + // 第10次中奖(翻倍) + else if (flipNumber == TENTH_FLIP) + { + var rewardAmount = GenerateRandomReward(TENTH_MIN_REWARD, TENTH_MAX_REWARD); + result.IsWin = true; + result.RewardAmount = rewardAmount; + result.RewardDesc = $"恭喜获得尊享包 {rewardAmount / 10000}w tokens(翻倍奖励)!"; + } + + return result; + } + + /// + /// 获取翻牌类型错误提示 + /// + private string GetFlipTypeErrorMessage(FlipType flipType) + { + return flipType switch + { + FlipType.Free => "免费翻牌次数已用完", + FlipType.Bonus => "赠送翻牌次数已用完", + FlipType.Invite => "需要使用邀请码解锁更多次数", + _ => "无法翻牌" + }; + } + + /// + /// 生成随机奖励金额 + /// + private long GenerateRandomReward(long min, long max) + { + var random = new Random(); + return (long)(random.NextDouble() * (max - min) + min); + } + + /// + /// 获取本周开始日期(周一) + /// + public static DateTime GetWeekStartDate(DateTime date) + { + var dayOfWeek = (int)date.DayOfWeek; + // 将周日(0)转换为7 + if (dayOfWeek == 0) dayOfWeek = 7; + + // 计算本周一的日期 + var monday = date.Date.AddDays(-(dayOfWeek - 1)); + return monday; + } + + /// + /// 获取翻牌类型描述 + /// + public static string GetFlipTypeDesc(int flipNumber) + { + if (flipNumber <= MAX_FREE_FLIPS) + { + return "免费"; + } + else if (flipNumber <= MAX_FREE_FLIPS + MAX_BONUS_FLIPS) + { + return "赠送"; + } + else + { + return "邀请解锁"; + } + } +} + +/// +/// 翻牌结果 +/// +public class FlipResult +{ + /// + /// 翻牌序号 + /// + public int FlipNumber { get; set; } + + /// + /// 是否中奖 + /// + public bool IsWin { get; set; } + + /// + /// 奖励金额 + /// + public long RewardAmount { get; set; } + + /// + /// 奖励描述 + /// + public string RewardDesc { get; set; } = string.Empty; + + /// + /// 是否显示翻倍奖励提示 + /// + public bool ShowDoubleRewardTip { get; set; } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/InviteCodeManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/InviteCodeManager.cs new file mode 100644 index 00000000..35188763 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/InviteCodeManager.cs @@ -0,0 +1,217 @@ +using Microsoft.Extensions.Logging; +using SqlSugar; +using Volo.Abp.Domain.Services; +using Yi.Framework.AiHub.Domain.Entities; +using Yi.Framework.SqlSugarCore.Abstractions; + +namespace Yi.Framework.AiHub.Domain.Managers; + +/// +/// 邀请码管理器 - 负责邀请码核心业务逻辑 +/// +public class InviteCodeManager : DomainService +{ + private readonly ISqlSugarRepository _inviteCodeRepository; + private readonly ISqlSugarRepository _invitationRecordRepository; + private readonly ILogger _logger; + + public InviteCodeManager( + ISqlSugarRepository inviteCodeRepository, + ISqlSugarRepository invitationRecordRepository, + ILogger logger) + { + _inviteCodeRepository = inviteCodeRepository; + _invitationRecordRepository = invitationRecordRepository; + _logger = logger; + } + + /// + /// 生成用户的邀请码 + /// + /// 用户ID + /// 邀请码 + public async Task GenerateInviteCodeForUserAsync(Guid userId) + { + // 检查是否已有邀请码 + var existingCode = await _inviteCodeRepository._DbQueryable + .Where(x => x.UserId == userId) + .FirstAsync(); + + if (existingCode != null) + { + return existingCode.Code; + } + + // 生成新邀请码 + var code = GenerateUniqueInviteCode(); + var inviteCode = new InviteCodeAggregateRoot(userId, code); + await _inviteCodeRepository.InsertAsync(inviteCode); + + _logger.LogInformation($"用户 {userId} 生成邀请码 {code}"); + + return code; + } + + /// + /// 获取用户的邀请码信息 + /// + public async Task GetUserInviteCodeAsync(Guid userId) + { + return await _inviteCodeRepository._DbQueryable + .Where(x => x.UserId == userId) + .FirstAsync(); + } + + /// + /// 统计用户本周邀请人数 + /// + public async Task GetWeeklyInvitationCountAsync(Guid userId, DateTime weekStart) + { + return await _invitationRecordRepository._DbQueryable + .Where(x => x.InviterId == userId) + .Where(x => x.InvitationTime >= weekStart) + .CountAsync(); + } + + /// + /// 获取邀请历史记录 + /// + public async Task> GetInvitationHistoryAsync(Guid userId, int limit = 10) + { + return await _invitationRecordRepository._DbQueryable + .Where(x => x.InviterId == userId) + .OrderBy(x => x.InvitationTime, OrderByType.Desc) + .Take(limit) + .Select(x => new InvitationHistoryDto + { + InvitedUserName = "用户***", // 脱敏处理 + InvitationTime = x.InvitationTime + }) + .ToListAsync(); + } + + /// + /// 使用邀请码 + /// + /// 使用者ID + /// 邀请码 + /// 邀请人ID + public async Task UseInviteCodeAsync(Guid userId, string inviteCode) + { + if (string.IsNullOrWhiteSpace(inviteCode)) + { + throw new UserFriendlyException("邀请码不能为空"); + } + + // 查找邀请码 + var inviteCodeEntity = await _inviteCodeRepository._DbQueryable + .Where(x => x.Code == inviteCode) + .FirstAsync(); + + if (inviteCodeEntity == null) + { + throw new UserFriendlyException("邀请码不存在"); + } + + // 验证不能使用自己的邀请码 + if (inviteCodeEntity.UserId == userId) + { + throw new UserFriendlyException("不能使用自己的邀请码"); + } + + // 验证邀请码是否已被使用 + if (inviteCodeEntity.IsUsed) + { + throw new UserFriendlyException("该邀请码已被使用"); + } + + // 验证邀请码拥有者是否已被邀请 + if (inviteCodeEntity.IsUserInvited) + { + throw new UserFriendlyException("该用户已被邀请,邀请码无效"); + } + + // 检查当前用户是否已被邀请 + var myInviteCode = await _inviteCodeRepository._DbQueryable + .Where(x => x.UserId == userId) + .FirstAsync(); + + if (myInviteCode?.IsUserInvited == true) + { + throw new UserFriendlyException("您已使用过邀请码,无法重复使用"); + } + + // 标记邀请码为已使用 + inviteCodeEntity.MarkAsUsed(userId); + await _inviteCodeRepository.UpdateAsync(inviteCodeEntity); + + // 标记当前用户已被邀请 + if (myInviteCode == null) + { + myInviteCode = new InviteCodeAggregateRoot(userId, GenerateUniqueInviteCode()); + myInviteCode.MarkUserAsInvited(); + await _inviteCodeRepository.InsertAsync(myInviteCode); + } + else + { + myInviteCode.MarkUserAsInvited(); + await _inviteCodeRepository.UpdateAsync(myInviteCode); + } + + // 创建邀请记录 + var invitationRecord = new InvitationRecordAggregateRoot( + inviteCodeEntity.UserId, + userId, + inviteCode); + await _invitationRecordRepository.InsertAsync(invitationRecord); + + _logger.LogInformation($"用户 {userId} 使用邀请码 {inviteCode} 成功"); + + return inviteCodeEntity.UserId; + } + + /// + /// 检查用户是否已被邀请 + /// + public async Task IsUserInvitedAsync(Guid userId) + { + var inviteCode = await _inviteCodeRepository._DbQueryable + .Where(x => x.UserId == userId) + .FirstAsync(); + + return inviteCode?.IsUserInvited ?? false; + } + + /// + /// 生成唯一邀请码 + /// + private string GenerateUniqueInviteCode() + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + var random = new Random(); + var code = new char[8]; + + for (int i = 0; i < code.Length; i++) + { + code[i] = chars[random.Next(chars.Length)]; + } + + return new string(code); + } +} + +/// +/// 邀请历史记录DTO +/// +public class InvitationHistoryDto +{ + /// + /// 被邀请人名称(脱敏) + /// + public string InvitedUserName { get; set; } = string.Empty; + + /// + /// 邀请时间 + /// + public DateTime InvitationTime { get; set; } +} diff --git a/Yi.Ai.Vue3/src/api/cardFlip/types.ts b/Yi.Ai.Vue3/src/api/cardFlip/types.ts index 91d452bb..4683f9a3 100644 --- a/Yi.Ai.Vue3/src/api/cardFlip/types.ts +++ b/Yi.Ai.Vue3/src/api/cardFlip/types.ts @@ -19,6 +19,7 @@ export interface CardFlipRecord { isWin: boolean; // 是否中奖 rewardAmount?: number; // 奖励金额(token数) flipTypeDesc?: string; // 翻牌类型描述 + flipOrderIndex: number; // 在翻牌顺序中的位置(1-10,表示第几个翻) } // 翻牌输入 diff --git a/Yi.Ai.Vue3/src/components/userPersonalCenter/components/CardFlipActivity.vue b/Yi.Ai.Vue3/src/components/userPersonalCenter/components/CardFlipActivity.vue index 28fb0f38..00cd173b 100644 --- a/Yi.Ai.Vue3/src/components/userPersonalCenter/components/CardFlipActivity.vue +++ b/Yi.Ai.Vue3/src/components/userPersonalCenter/components/CardFlipActivity.vue @@ -35,7 +35,10 @@ async function fetchTaskStatus() { // 翻牌 async function handleFlipCard(record: CardFlipRecord) { - if (!record.isFlipped && record.flipNumber === (taskData.value?.totalFlips || 0) + 1) { + // 检查是否可以翻牌(任何未翻过的牌都可以) + const canFlip = !record.isFlipped && (taskData.value?.canFlip || false); + + if (canFlip) { // 检查是否需要使用邀请码 if (record.flipNumber > 8 && (taskData.value?.remainingInviteFlips || 0) > 0) { const usedInviteFlips = 2 - (taskData.value?.remainingInviteFlips || 2); @@ -195,8 +198,8 @@ function getCardClass(record: CardFlipRecord): string[] { classes.push('card-flipping'); } - // 可点击的卡片 - if (!record.isFlipped && record.flipNumber === (taskData.value?.totalFlips || 0) + 1) { + // 可点击的卡片(任何未翻过的牌都可以点击) + if (!record.isFlipped && (taskData.value?.canFlip || false)) { classes.push('card-clickable'); } else if (!record.isFlipped) { classes.push('card-locked');