using Microsoft.Extensions.Logging; using QYZH.InteractiveMagazine.Common.Extensions; using QYZH.InteractiveMagazine.IService; using QYZH.InteractiveMagazine.Models.Common; using QYZH.InteractiveMagazine.Models.Dto; using QYZH.InteractiveMagazine.Models.Dto.CheckIn; using QYZH.InteractiveMagazine.Models.Dto.Compensation; using QYZH.InteractiveMagazine.Models.Dto.Pet; using QYZH.InteractiveMagazine.Models.Dto.Points; using QYZH.InteractiveMagazine.Models.Entity; using QYZH.InteractiveMagazine.Models.Enum; using QYZH.InteractiveMagazine.Repository; namespace QYZH.InteractiveMagazine.Service; /// /// 签到服务实现 /// public class CheckInService( BaseRepository checkInRecordRepository, IPetService petService, ICompensationTaskService compensationTaskService, IPointsService pointsService, 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("今日已签到,请明天再来", ResultCode.BAD_REQUEST); } // 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("用户不存在", ResultCode.NOT_FOUND); } // 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 = CheckInRecordTypeEnum.Normal, Status = (int)CheckInRecordStatusEnum.Success, CreatedBy = userId.ToString(), UpdatedBy = userId.ToString() }; var recordId = await checkInRecordRepository.Insertable(checkInRecord).ExecuteReturnIdentityAsync(); checkInRecord.Id = recordId; // 6b. 更新用户成长值 var newGrowthBalance = user.GrowthPoints + growthReward; await checkInRecordRepository.Context.Updateable() .SetColumns(u => u.GrowthPoints == newGrowthBalance) .Where(u => u.Id == userId && !u.IsDeleted) .ExecuteCommandAsync(); // 6c. 通过积分服务增加积分 var pointsResult = await pointsService.AddPointsInTranAsync(new AddPointsInput { UserId = userId, Amount = pointsReward, ChangeType = PointsChangeTypeEnum.SignIn, RelatedId = recordId, Description = $"签到奖励(连续{consecutiveDays}天)" }); // 6d. 如果有活跃宠物,喂养宠物(同一事务内) FeedPetOutput? feedResult = null; if (pet != null && pet.Status == (int)UserPetStatusEnum.Active && growthReward > 0) { feedResult = await petService.FeedPetInTranAsync(userId, new FeedPetInput { PetId = pet.Id, GrowthPoints = growthReward }); } // 构建返回结果 result.RecordId = (long)recordId; result.CheckInDate = today; result.ConsecutiveDays = consecutiveDays; result.PointsAwarded = pointsReward; result.GrowthPointsAwarded = growthReward; result.PointsBalance = pointsResult.NewBalance; result.GrowthPointsBalance = newGrowthBalance; result.HasPet = pet != null; if (feedResult != null) { result.HasEvolved = feedResult.HasEvolved; result.EvolvedStageName = feedResult.EvolvedStageName; } }); if (pet != null && pet.Status == (int)UserPetStatusEnum.Active && growthReward > 0) { logger.LogInformation("签到成长值已喂养宠物,PetId: {PetId}, 进化: {HasEvolved}", pet.Id, result.HasEvolved); } 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; } } // 查询用户余额 var user = await checkInRecordRepository.Context.Queryable() .Where(u => u.Id == userId && !u.IsDeleted) .FirstAsync(); // 最近 7 天签到记录 var thirtyDaysAgo = today.AddDays(-6); 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, CreatedAt = r.CreatedAt, ConsecutiveDays = r.ConsecutiveDays, PointsAwarded = r.PointsAwarded, GrowthPointsAwarded = r.GrowthPointsAwarded, Type = r.Type.ToString(), Status = r.Status.ToString() }) .ToListAsync(); return new CheckInInfoOutput { HasCheckedInToday = hasCheckedInToday, ConsecutiveDays = consecutiveDays, TotalCheckInDays = totalCheckInDays, PointsBalance = user?.Points ?? 0, GrowthPointsBalance = user?.GrowthPoints ?? 0, RecentRecords = recentRecords }; } /// /// 补签(消耗补签卡,补签历史漏签日期) /// public async Task MakeUpCheckInAsync(long userId, DateTime targetDate) { logger.LogInformation("用户补签,UserId: {UserId}, TargetDate: {Date}", userId, targetDate); targetDate = targetDate.Date; if (targetDate >= DateTime.Now.Date) throw new BusinessException("只能补签过去的日期", ResultCode.BAD_REQUEST); // 检查目标日期是否已有签到记录 var alreadyCheckedIn = await checkInRecordRepository.Context.Queryable() .Where(r => r.UserId == userId && !r.IsDeleted && r.CheckInDate >= targetDate && r.CheckInDate < targetDate.AddDays(1)) .AnyAsync(); if (alreadyCheckedIn) throw new BusinessException($"{targetDate:yyyy-MM-dd} 已签到,无需补签", ResultCode.BAD_REQUEST); // 检查用户背包中是否有补签卡 var makeUpCard = await checkInRecordRepository.Context.Queryable() .Where(b => b.UserId == userId && b.ItemType == "MakeUpCard" && b.Quantity > 0 && b.Status == (int)UserBagStatusEnum.Available && !b.IsDeleted) .OrderByDescending(b => b.CreatedAt) .FirstAsync(); if (makeUpCard == null) throw new BusinessException("补签卡不足,无法补签", ResultCode.BAD_REQUEST); // 查询用户信息 var user = await checkInRecordRepository.Context.Queryable() .Where(u => u.Id == userId && !u.IsDeleted) .FirstAsync(); if (user == null) throw new BusinessException("用户不存在", ResultCode.NOT_FOUND); // 查询宠物 var pet = await checkInRecordRepository.Context.Queryable() .Where(p => p.UserId == userId && !p.IsDeleted) .FirstAsync(); // 补签奖励按基础值计算(不享受连续签到加成) var (pointsReward, growthReward) = await CalculateRewardsAsync(1); var result = new CheckInOutput(); await checkInRecordRepository.UseTranAsync(async () => { // 创建补签记录 var checkInRecord = new CheckInRecord { UserId = userId, CheckInDate = targetDate, PointsAwarded = pointsReward, GrowthPointsAwarded = growthReward, ConsecutiveDays = 0, // 补签不纳入连续天数 Type = CheckInRecordTypeEnum.MakeUp, Status = (int)CheckInRecordStatusEnum.Success, IsDeleted = false, CreatedBy = userId.ToString(), CreatedAt = DateTime.Now, UpdatedBy = userId.ToString(), UpdatedAt = DateTime.Now }; var recordId = await checkInRecordRepository.Insertable(checkInRecord).ExecuteReturnIdentityAsync(); checkInRecord.Id = recordId; // 更新用户成长值 var newGrowthBalance = user.GrowthPoints + growthReward; await checkInRecordRepository.Context.Updateable() .SetColumns(u => u.GrowthPoints == newGrowthBalance) .Where(u => u.Id == userId && !u.IsDeleted) .ExecuteCommandAsync(); // 通过积分服务增加积分 var pointsResult = await pointsService.AddPointsInTranAsync(new AddPointsInput { UserId = userId, Amount = pointsReward, ChangeType = PointsChangeTypeEnum.MakeUpSign, RelatedId = recordId, Description = $"补签奖励({targetDate:yyyy-MM-dd})" }); // 扣减补签卡 if (makeUpCard.Quantity <= 1) { await checkInRecordRepository.Context.Updateable() .SetColumns(b => b.Status == (int)UserBagStatusEnum.Expired) .SetColumns(b => b.Quantity == 0) .SetColumns(b => b.UpdatedAt == DateTime.Now) .Where(b => b.Id == makeUpCard.Id) .ExecuteCommandAsync(); } else { await checkInRecordRepository.Context.Updateable() .SetColumns(b => b.Quantity == makeUpCard.Quantity - 1) .SetColumns(b => b.UpdatedAt == DateTime.Now) .Where(b => b.Id == makeUpCard.Id) .ExecuteCommandAsync(); } // 如果有活跃宠物,喂养宠物(同一事务内) FeedPetOutput? feedResult = null; if (pet != null && pet.Status == (int)UserPetStatusEnum.Active && growthReward > 0) { feedResult = await petService.FeedPetInTranAsync(userId, new Models.Dto.Pet.FeedPetInput { PetId = pet.Id, GrowthPoints = growthReward }); } result.RecordId = (long)recordId; result.CheckInDate = targetDate; result.ConsecutiveDays = 0; result.PointsAwarded = pointsReward; result.GrowthPointsAwarded = growthReward; result.PointsBalance = pointsResult.NewBalance; result.GrowthPointsBalance = newGrowthBalance; result.HasPet = pet != null; if (feedResult != null) { result.HasEvolved = feedResult.HasEvolved; result.EvolvedStageName = feedResult.EvolvedStageName; } }); if (pet != null && pet.Status == (int)UserPetStatusEnum.Active && growthReward > 0) { logger.LogInformation("补签成长值已喂养宠物,PetId: {PetId}, 进化: {HasEvolved}", pet.Id, result.HasEvolved); } logger.LogInformation("补签成功,UserId: {UserId}, Date: {Date}, 积分+{Points}, 成长值+{Growth}", userId, targetDate, pointsReward, growthReward); return result; } /// /// 获取用户漏签日期列表 /// public async Task> GetMissedDatesAsync(long userId, int days = 7) { var startDate = DateTime.Now.Date.AddDays(-days); // 查询该时间段内所有签到记录 var checkedDates = await checkInRecordRepository.Context.Queryable() .Where(r => r.UserId == userId && !r.IsDeleted && r.CheckInDate >= startDate) .Select(r => r.CheckInDate.Date) .ToListAsync(); var checkedDateSet = new HashSet(checkedDates); var missedDates = new List(); // 遍历每一天,找出漏签的日期(排除今天,今天不算漏签) for (var date = startDate; date < DateTime.Now.Date; date = date.AddDays(1)) { if (!checkedDateSet.Contains(date)) { missedDates.Add(date); } } return missedDates; } /// /// 计算连续签到天数 /// 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 == (int)DefaultStatusEnum.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); } }