using Microsoft.Extensions.Logging; using QYZH.InteractiveMagazine.IService; using QYZH.InteractiveMagazine.Models.Common; using QYZH.InteractiveMagazine.Models.Dto.CheckIn; using QYZH.InteractiveMagazine.Models.Dto.Compensation; using QYZH.InteractiveMagazine.Models.Dto.Pet; using QYZH.InteractiveMagazine.Models.Entity; using QYZH.InteractiveMagazine.Repository; namespace QYZH.InteractiveMagazine.Service; /// /// 签到服务实现 /// public class CheckInService( BaseRepository checkInRecordRepository, IPetService petService, ICompensationTaskService compensationTaskService, ILogger logger) : BaseRepository, ICheckInService { /// /// 默认签到奖励积分(无配置时的兜底值) /// private const int DefaultRewardPoints = 10; /// /// 用户签到 /// public async Task CheckInAsync(long userId) { logger.LogInformation("用户签到,UserId: {UserId}", userId); var today = DateTime.Now.Date; // 1. 检查今日是否已签到 var alreadyCheckedIn = await checkInRecordRepository.Context.Queryable() .Where(r => r.UserId == userId && !r.IsDeleted && r.CheckInDate >= today && r.CheckInDate < today.AddDays(1)) .AnyAsync(); if (alreadyCheckedIn) { throw new BusinessException("今日已签到,请明天再来", 400); } // 2. 计算连续签到天数 var consecutiveDays = await CalculateConsecutiveDaysAsync(userId, today); // 3. 查询签到配置,计算奖励 var (pointsReward, growthReward) = await CalculateRewardsAsync(consecutiveDays); // 4. 查询用户信息 var user = await checkInRecordRepository.Context.Queryable() .Where(u => u.Id == userId && !u.IsDeleted) .FirstAsync(); if (user == null) { throw new BusinessException("用户不存在", 404); } // 5. 查询用户宠物(如果有) var pet = await checkInRecordRepository.Context.Queryable() .Where(p => p.UserId == userId && !p.IsDeleted) .FirstAsync(); // 6. 事务执行签到相关写操作 var result = new CheckInOutput(); await checkInRecordRepository.UseTranAsync(async () => { // 6a. 创建签到记录 var checkInRecord = new CheckInRecord { UserId = userId, CheckInDate = today, PointsAwarded = pointsReward, GrowthPointsAwarded = growthReward, ConsecutiveDays = consecutiveDays, Type = "Normal", Status = "Success" }; var recordId = await checkInRecordRepository.Insertable(checkInRecord).ExecuteReturnIdentityAsync(); checkInRecord.Id = recordId; // 6b. 更新用户积分余额 var newPointsBalance = user.Points + pointsReward; var newGrowthBalance = user.GrowthPoints + growthReward; await checkInRecordRepository.Context.Updateable() .SetColumns(u => u.Points == newPointsBalance) .SetColumns(u => u.GrowthPoints == newGrowthBalance) .Where(u => u.Id == userId && !u.IsDeleted) .ExecuteCommandAsync(); // 6c. 创建积分变动记录 var pointsRecord = new PointsRecord { UserId = userId, ChangeAmount = pointsReward, BalanceAfter = newPointsBalance, ChangeType = "SignIn", RelatedId = recordId, Description = $"签到奖励(连续{consecutiveDays}天)", Type = "Income", Status = "Success" }; await checkInRecordRepository.Context.Insertable(pointsRecord).ExecuteCommandAsync(); // 构建返回结果 result.RecordId = (long)recordId; result.CheckInDate = today; result.ConsecutiveDays = consecutiveDays; result.PointsAwarded = pointsReward; result.GrowthPointsAwarded = growthReward; result.PointsBalance = newPointsBalance; result.GrowthPointsBalance = newGrowthBalance; result.HasPet = pet != null; }); // 7. 如果用户有活跃宠物,调用 PetService 喂养(含进化检查),独立事务 if (pet != null && pet.Status == "Active" && growthReward > 0) { try { var feedResult = await petService.FeedPetAsync(userId, new FeedPetInput { PetId = pet.Id, GrowthPoints = growthReward }); result.HasEvolved = feedResult.HasEvolved; result.EvolvedStageName = feedResult.EvolvedStageName; logger.LogInformation("签到成长值已喂养宠物,PetId: {PetId}, 进化: {HasEvolved}", pet.Id, feedResult.HasEvolved); } catch (Exception ex) { logger.LogWarning(ex, "签到后喂养宠物失败,PetId: {PetId},将创建补偿任务", pet.Id); await compensationTaskService.CreateTaskAsync(new CreateCompensationTaskInput { TaskType = CompensationTaskType.PetFeeding, BusinessSource = "CheckIn", BusinessId = result.RecordId.ToString(), UserId = userId, Payload = new { PetId = pet.Id, GrowthPoints = growthReward }, ErrorMessage = ex.Message, ErrorSource = "CheckInService.CheckInAsync → PetService.FeedPetAsync", MaxRetries = 3 }); } } logger.LogInformation("用户签到成功,UserId: {UserId}, 连续{Days}天, 积分+{Points}, 成长值+{Growth}", userId, consecutiveDays, pointsReward, growthReward); return result; } /// /// 获取用户签到信息 /// public async Task GetCheckInInfoAsync(long userId) { logger.LogInformation("获取签到信息,UserId: {UserId}", userId); var today = DateTime.Now.Date; // 今日是否已签到 var hasCheckedInToday = await checkInRecordRepository.Context.Queryable() .Where(r => r.UserId == userId && !r.IsDeleted && r.CheckInDate >= today && r.CheckInDate < today.AddDays(1)) .AnyAsync(); // 累计签到天数 var totalCheckInDays = await checkInRecordRepository.Context.Queryable() .Where(r => r.UserId == userId && !r.IsDeleted) .CountAsync(); // 最近一次签到记录(用于获取连续天数) var lastRecord = await checkInRecordRepository.Context.Queryable() .Where(r => r.UserId == userId && !r.IsDeleted) .OrderBy(r => r.CheckInDate, SqlSugar.OrderByType.Desc) .FirstAsync(); // 判断连续天数:如果最后一次签到是今天或昨天,则连续天数延续 var consecutiveDays = 0; if (lastRecord != null) { var lastDate = lastRecord.CheckInDate.Date; if (lastDate == today || lastDate == today.AddDays(-1)) { consecutiveDays = lastRecord.ConsecutiveDays; if (lastDate == today) { // 今天已签到,连续天数就是今天的值 } // 如果是昨天,则连续天数保持(今天还没签到) } } // 查询用户余额 var user = await checkInRecordRepository.Context.Queryable() .Where(u => u.Id == userId && !u.IsDeleted) .FirstAsync(); // 最近 30 天签到记录 var thirtyDaysAgo = today.AddDays(-29); var recentRecords = await checkInRecordRepository.Context.Queryable() .Where(r => r.UserId == userId && !r.IsDeleted && r.CheckInDate >= thirtyDaysAgo) .OrderBy(r => r.CheckInDate, SqlSugar.OrderByType.Desc) .Select(r => new CheckInRecordOutput { Id = (long)r.Id, CheckInDate = r.CheckInDate, ConsecutiveDays = r.ConsecutiveDays, PointsAwarded = r.PointsAwarded, GrowthPointsAwarded = r.GrowthPointsAwarded, Type = r.Type, Status = r.Status }) .ToListAsync(); return new CheckInInfoOutput { HasCheckedInToday = hasCheckedInToday, ConsecutiveDays = consecutiveDays, TotalCheckInDays = totalCheckInDays, PointsBalance = user?.Points ?? 0, GrowthPointsBalance = user?.GrowthPoints ?? 0, RecentRecords = recentRecords }; } /// /// 计算连续签到天数 /// private async Task CalculateConsecutiveDaysAsync(long userId, DateTime today) { var yesterday = today.AddDays(-1); var yesterdayRecord = await checkInRecordRepository.Context.Queryable() .Where(r => r.UserId == userId && !r.IsDeleted && r.CheckInDate >= yesterday && r.CheckInDate < today) .FirstAsync(); // 昨天有签到记录,连续天数 +1;否则从 1 开始 return yesterdayRecord != null ? yesterdayRecord.ConsecutiveDays + 1 : 1; } /// /// 根据连续签到天数计算奖励(积分 + 成长值) /// private async Task<(int PointsReward, int GrowthReward)> CalculateRewardsAsync(int consecutiveDays) { // 查询签到配置(按 DayNumber 升序) var configs = await checkInRecordRepository.Context.Queryable() .Where(c => c.Status == "Active" && !c.IsDeleted) .OrderBy(c => c.DayNumber) .ToListAsync(); if (configs.Count == 0) { // 无配置时使用默认值 return (DefaultRewardPoints, DefaultRewardPoints); } // 找到匹配的奖励档位:取 DayNumber <= 连续天数 的最大档位 var matchedConfig = configs.LastOrDefault(c => c.DayNumber <= consecutiveDays) ?? configs.First(); var totalPoints = matchedConfig.RewardPoints + matchedConfig.BonusPoints; // 成长值与积分相同(签到同时获得积分和成长值) return (totalPoints, totalPoints); } }