using Microsoft.Extensions.Logging; using QYZH.InteractiveMagazine.IService; using QYZH.InteractiveMagazine.Models.Common; using QYZH.InteractiveMagazine.Models.Dto.Points; using QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService; using QYZH.InteractiveMagazine.Models.Entity; using QYZH.InteractiveMagazine.Models.Enum; using QYZH.InteractiveMagazine.Repository; namespace QYZH.InteractiveMagazine.Service; /// /// 用户作答任务服务实现 /// public class UserAnswerTaskService( BaseRepository answerRepository, BaseRepository journalRepository, BaseRepository catalogRepository, BaseRepository pageRepository, BaseRepository taskRepository, BaseRepository userMedalRepository, BaseRepository medalRepository, BaseRepository userJournalRepository, BaseRepository usersRepository, BaseRepository checkInRecordRepository, IPointsService pointsService, ILogger logger) : BaseRepository, IUserAnswerTaskService { /// /// 获取用户期刊学习进度列表(按期刊分组,批量预加载避免N+1) /// public async Task> GetJournalProgressAsync(long userId) { // 1. 查询用户的所有作答记录 var userAnswers = await answerRepository.Context.Queryable() .Where(a => a.UserId == userId && !a.IsDeleted) .ToListAsync(); if (userAnswers == null || userAnswers.Count == 0) { return new List(); } // 2. 提取关联的期刊ID(去重) var journalIds = userAnswers.Select(a => a.JournalId).Distinct().ToList(); // 3. 批量预加载关联实体(按期刊维度全量加载) var journals = await journalRepository.Context.Queryable() .Where(j => journalIds.Contains(j.Id) && !j.IsDeleted) .ToListAsync(); // 加载这些期刊的一级目录(ParentId == 0 表示一级) var allCatalogs = await catalogRepository.Context.Queryable() .Where(c => journalIds.Contains(c.JournalId) && c.ParentId == 0 && !c.IsDeleted) .ToListAsync(); // 加载这些期刊的所有任务(直接按 JournalId) var allTasks = await taskRepository.Context.Queryable() .Where(t => journalIds.Contains(t.JournalId) && !t.IsDeleted) .ToListAsync(); // 加载这些期刊的所有页面(用于计算剩余篇数) var allPages = await pageRepository.Context.Queryable() .Where(p => journalIds.Contains(p.JournalId) && !p.IsDeleted) .ToListAsync(); // 4. 查询用户连续打卡天数 var consecutiveDays = await CalculateConsecutiveDaysAsync(userId); // 5. 提取用户已作答的 ID 集合(包含进行中和已完成) var userAnsweredTaskIds = userAnswers.Select(a => a.JournalPageTaskId).Distinct().ToHashSet(); var userAnsweredPageIds = userAnswers.Select(a => a.JournalPageId).Distinct().ToHashSet(); // 用户已完成的任务ID集合(Status=1表示已完成) var userCompletedTaskIds = userAnswers.Where(a => a.Status == (int)UserAnswerStatusEnum.Complete).Select(a => a.JournalPageTaskId).Distinct().ToHashSet(); // 6. 按期刊分组聚合 var result = new List(); foreach (var journal in journals) { // 一级栏目数 var catalogCount = allCatalogs.Count(c => c.JournalId == journal.Id); // 该期刊下所有任务 var journalTasks = allTasks.Where(t => t.JournalId == journal.Id).ToList(); var totalTaskCount = journalTasks.Count; // 已完成任务数(用户作答中属于本期刊的 Distinct JournalPageTaskGroupId) var completedTaskCount = userAnswers .Where(a => a.JournalId == journal.Id) .Select(a => a.JournalPageTaskGroupId) .Distinct() .Count(); // 成长值 & 积分值(用户已完成任务的 JournalPageTask 聚合,Status=1) var completedTasks = journalTasks.Where(t => userCompletedTaskIds.Contains(t.Id)).ToList(); var totalGrowthPoints = completedTasks.Sum(t => t.GrowthPoint ?? 0); var totalPoints = completedTasks.Sum(t => t.Points); // 进度:已完成任务ID数 / 总任务数(Status=1才算完成) var completedTaskIdCount = journalTasks.Count(t => userCompletedTaskIds.Contains(t.Id)); var progress = totalTaskCount > 0 ? Math.Round((double)completedTaskIdCount / totalTaskCount * 100, 2) : 0; // 剩余篇数:该期刊总页数 - 用户已作答页数 var journalPages = allPages.Where(p => p.JournalId == journal.Id).ToList(); var answeredPageCount = journalPages.Count(p => userAnsweredPageIds.Contains(p.Id)); var remainingPages = journalPages.Count - answeredPageCount; result.Add(new JournalProgressOutput { JournalId = journal.Id, JournalName = journal.Name ?? string.Empty, CatalogCount = catalogCount, TotalTaskCount = totalTaskCount, CompletedTaskCount = completedTaskCount, TotalGrowthPoints = totalGrowthPoints, TotalPoints = totalPoints, Progress = progress, RemainingPages = remainingPages, ConsecutiveDays = consecutiveDays }); } return result; } /// /// 计算用户连续打卡天数 /// private async Task CalculateConsecutiveDaysAsync(long userId) { var today = DateTime.Now.Date; // 查询最近一次签到记录 var lastRecord = await checkInRecordRepository.Context.Queryable() .Where(r => r.UserId == userId && !r.IsDeleted) .OrderByDescending(r => r.CheckInDate) .FirstAsync(); if (lastRecord == null) { return 0; } var lastDate = lastRecord.CheckInDate.Date; // 如果最后一次签到是今天或昨天,则连续天数延续 if (lastDate == today || lastDate == today.AddDays(-1)) { return lastRecord.ConsecutiveDays; } return 0; } /// /// 获取目录列表(按一级目录分组,二级目录含完成状态,批量预加载避免N+1) /// public async Task> GetCatalogListAsync(long journalId, long userId) { // 1. 查询期刊 var journal = await journalRepository.Context.Queryable() .Where(j => j.Id == journalId && !j.IsDeleted) .FirstAsync(); if (journal == null) { return new List(); } // 2. 批量加载关联数据 var allCatalogs = await catalogRepository.Context.Queryable() .Where(c => c.JournalId == journalId && !c.IsDeleted) .ToListAsync(); var allPages = await pageRepository.Context.Queryable() .Where(p => p.JournalId == journalId && !p.IsDeleted) .ToListAsync(); var allTasks = await taskRepository.Context.Queryable() .Where(t => t.JournalId == journalId && !t.IsDeleted) .ToListAsync(); var userAnswers = await answerRepository.Context.Queryable() .Where(a => a.JournalId == journalId && a.UserId == userId && !a.IsDeleted) .ToListAsync(); // 3. 用户已作答的任务ID集合(包含进行中和已完成) var userAnsweredTaskIds = userAnswers.Select(a => a.JournalPageTaskId).Distinct().ToHashSet(); // 用户已完成的任务ID集合(Status=1表示已完成) var userCompletedTaskIds = userAnswers.Where(a => a.Status == (int)UserAnswerStatusEnum.Complete).Select(a => a.JournalPageTaskId).Distinct().ToHashSet(); // 4. 按一级目录分组 var firstLevelCatalogs = allCatalogs.Where(c => c.ParentId == 0 && c.Type == JournalCatalogTypeEnum.Unit) .OrderBy(c => c.Sort).ToList(); var result = new List(); foreach (var firstCatalog in firstLevelCatalogs) { // 该一级目录下的 Type=Page 子目录(即文章) var pageCatalogs = allCatalogs .Where(c => c.ParentId == firstCatalog.Id && c.Type == JournalCatalogTypeEnum.Page) .OrderBy(c => c.Sort) .ToList(); // 文章篇数 = Type=Page 的子目录数量 var articleCount = pageCatalogs.Count; var pageCatalogIds = pageCatalogs.Select(c => c.Id).ToHashSet(); // 获取所有文章下的页面ID var allArticlePageIds = allPages .Where(p => pageCatalogIds.Contains(p.JournalCatalogId)) .Select(p => p.Id) .ToHashSet(); // 该一级目录下的二级单元目录(Type=Unit 的子目录) var secondLevelCatalogs = allCatalogs .Where(c => c.ParentId == firstCatalog.Id && c.Type == JournalCatalogTypeEnum.Unit) .OrderBy(c => c.Sort) .ToList(); // 如果没有二级单元,则将 Type=Page 的文章作为二级列表展示 var children = new List(); if (secondLevelCatalogs.Count > 0) { // 有二级单元:按二级单元分组 foreach (var secondCatalog in secondLevelCatalogs) { // 二级单元下的 Type=Page 子目录 var subPageCatalogs = allCatalogs .Where(c => c.ParentId == secondCatalog.Id && c.Type == JournalCatalogTypeEnum.Page) .ToList(); var subPageCatalogIds = subPageCatalogs.Select(c => c.Id).ToHashSet(); // 二级单元下的所有页面 var secondLevelPageIds = allPages .Where(p => subPageCatalogIds.Contains(p.JournalCatalogId)) .Select(p => p.Id) .ToHashSet(); // 二级单元下的所有任务 var secondLevelTasks = allTasks.Where(t => secondLevelPageIds.Contains(t.JournalPageId)).ToList(); // 判定状态(Status=0进行中,Status=1已完成) string status; if (secondLevelTasks.Count == 0) { status = "未开始"; } else { var completedCount = secondLevelTasks.Count(t => userCompletedTaskIds.Contains(t.Id)); var answeredCount = secondLevelTasks.Count(t => userAnsweredTaskIds.Contains(t.Id)); if (answeredCount == 0) status = "未开始"; else if (completedCount == secondLevelTasks.Count) status = "已完成"; else status = "进行中"; } // 计算该二级目录的总成长值和总积分 var totalGrowthPoints = secondLevelTasks.Sum(t => t.GrowthPoint) ?? 0; var totalPoints = secondLevelTasks.Sum(t => t.Points); children.Add(new SecondCatalogOutput { CatalogId = secondCatalog.Id, CatalogName = secondCatalog.Name ?? string.Empty, TotalPoints = totalPoints, TotalGrowthPoints = totalGrowthPoints, Status = status }); } } else { // 没有二级单元:直接将文章作为二级列表 foreach (var pageCatalog in pageCatalogs) { // 该文章下的所有页面 var articlePageIds = allPages .Where(p => p.JournalCatalogId == pageCatalog.Id) .Select(p => p.Id) .ToHashSet(); // 该文章下的所有任务 var articleTasks = allTasks.Where(t => articlePageIds.Contains(t.JournalPageId)).ToList(); // 判定状态(Status=0进行中,Status=1已完成) string status; if (articleTasks.Count == 0) { status = "未开始"; } else { var completedCount = articleTasks.Count(t => userCompletedTaskIds.Contains(t.Id)); var answeredCount = articleTasks.Count(t => userAnsweredTaskIds.Contains(t.Id)); if (answeredCount == 0) status = "未开始"; else if (completedCount == articleTasks.Count) status = "已完成"; else status = "进行中"; } // 计算该文章的总成长值和总积分 var totalGrowthPoints = articleTasks.Sum(t => t.GrowthPoint) ?? 0; var totalPoints = articleTasks.Sum(t => t.Points); children.Add(new SecondCatalogOutput { CatalogId = pageCatalog.Id, CatalogName = pageCatalog.Name ?? string.Empty, TotalPoints = totalPoints, TotalGrowthPoints = totalGrowthPoints, Status = status }); } } // 计算该一级目录的总成长值和总积分(基于所有文章页面的任务) var firstLevelTasks = allTasks.Where(t => allArticlePageIds.Contains(t.JournalPageId)).ToList(); result.Add(new CatalogListOutput { CatalogId = firstCatalog.Id, CatalogName = firstCatalog.Name ?? string.Empty, JournalName = journal.Name ?? string.Empty, ArticleCount = articleCount, Children = children }); } return result; } /// /// 获取一级目录概览(含二级目录详情、页码范围、任务完成状态,批量预加载避免N+1) /// /// 一级烂ID /// 用户ID /// public async Task GetCatalogOverviewAsync(long catalogId, long userId) { // 1. 查询一级目录 var firstCatalog = await catalogRepository.Context.Queryable() .Where(c => c.Id == catalogId && !c.IsDeleted) .FirstAsync(); if (firstCatalog == null) { return null; } // 2. 批量加载关联数据 var allCatalogs = await catalogRepository.Context.Queryable() .Where(c => c.JournalId == firstCatalog.JournalId && !c.IsDeleted) .ToListAsync(); var allPages = await pageRepository.Context.Queryable() .Where(p => p.JournalId == firstCatalog.JournalId && !p.IsDeleted) .ToListAsync(); var allTasks = await taskRepository.Context.Queryable() .Where(t => t.JournalId == firstCatalog.JournalId && !t.IsDeleted) .ToListAsync(); var userAnswers = await answerRepository.Context.Queryable() .Where(a => a.JournalId == firstCatalog.JournalId && a.UserId == userId && !a.IsDeleted) .ToListAsync(); // 3. 用户已作答的任务ID集合(包含进行中和已完成) var userAnsweredTaskIds = userAnswers.Select(a => a.JournalPageTaskId).Distinct().ToHashSet(); // 用户已完成的任务ID集合(Status=1表示已完成) var userCompletedTaskIds = userAnswers.Where(a => a.Status == (int)UserAnswerStatusEnum.Complete).Select(a => a.JournalPageTaskId).Distinct().ToHashSet(); // 4. 获取该一级目录下所有 Type=Page 的子目录(即文章/页面目录) var pageCatalogs = allCatalogs .Where(c => c.ParentId == firstCatalog.Id && c.Type == JournalCatalogTypeEnum.Page) .OrderBy(c => c.Sort) .ToList(); // 5. 文章篇数 = Type=Page 的子目录数量 var articleCount = pageCatalogs.Count; // 6. 获取所有文章目录下的页面 var pageCatalogIds = pageCatalogs.Select(c => c.Id).ToHashSet(); var allLevelPages = allPages.Where(p => pageCatalogIds.Contains(p.JournalCatalogId)).ToList(); var allLevelPageIds = allLevelPages.Select(p => p.Id).ToHashSet(); // 7. 总任务数 var totalTaskCount = allTasks.Count(t => allLevelPageIds.Contains(t.JournalPageId)); // 8. 构建子目录详情列表 var subCatalogs = new List(); foreach (var pageCatalog in pageCatalogs) { // 该文章下的所有页面 var articlePages = allPages .Where(p => p.JournalCatalogId == pageCatalog.Id) .OrderBy(p => p.PageNum) .ToList(); var articlePageIds = articlePages.Select(p => p.Id).ToHashSet(); // 该文章下的所有任务 var articleTasks = allTasks.Where(t => articlePageIds.Contains(t.JournalPageId)).ToList(); var totalTasks = articleTasks.Count; // 已完成任务数(Status=1) var completedTasks = articleTasks.Count(t => userCompletedTaskIds.Contains(t.Id)); // 已作答任务数(包含进行中和已完成) var answeredTasks = articleTasks.Count(t => userAnsweredTaskIds.Contains(t.Id)); // 页码范围 var startPage = articlePages.Count > 0 ? articlePages.Min(p => p.PageNum) : 0; var endPage = articlePages.Count > 0 ? articlePages.Max(p => p.PageNum) : 0; // 状态格式:1/2 进行中、2/2 已完成、0/2 未开始(Status=0进行中,Status=1已完成) string status; if (totalTasks == 0) status = $"0/0 未开始"; else if (answeredTasks == 0) status = $"0/{totalTasks} 未开始"; else if (completedTasks == totalTasks) status = $"{completedTasks}/{totalTasks} 已完成"; else status = $"{completedTasks}/{totalTasks} 进行中"; subCatalogs.Add(new SubCatalogDetailOutput { CatalogId = pageCatalog.Id, SecondLevelCatalogName = pageCatalog.Name ?? string.Empty, StartPage = startPage, EndPage = endPage, TaskCount = totalTasks, Status = status }); } return new CatalogOverviewOutput { CatalogId = firstCatalog.Id, FirstLevelCatalogName = firstCatalog.Name ?? string.Empty, PageCount = articleCount, TotalTaskCount = totalTaskCount, SubCatalogs = subCatalogs }; } /// /// 获取用户答题任务列表 /// public async Task GetUserAnswerTaskListAsync(long catalogId, long userId) { // 1. 查询目录 var catalog = await catalogRepository.Context.Queryable() .Where(c => c.Id == catalogId && !c.IsDeleted) .FirstAsync(); if (catalog == null) { return null; } // 2. 批量加载关联数据 var allPages = await pageRepository.Context.Queryable() .Where(p => p.JournalCatalogId == catalogId && !p.IsDeleted) .ToListAsync(); var allTasks = await taskRepository.Context.Queryable() .Where(t => t.JournalId == catalog.JournalId && !t.IsDeleted) .ToListAsync(); var userAnswers = await answerRepository.Context.Queryable() .Where(a => a.JournalId == catalog.JournalId && a.UserId == userId && !a.IsDeleted) .ToListAsync(); // 3. 获取该目录下的所有页面ID var pageIds = allPages.Select(p => p.Id).ToHashSet(); // 4. 该目录下的所有任务 var catalogTasks = allTasks.Where(t => pageIds.Contains(t.JournalPageId)).ToList(); // 5. 任务名称列表 var taskNames = catalogTasks .Where(t => !string.IsNullOrEmpty(t.Task)) .Select(t => t.Task) .Distinct() .ToList(); // 6. 用户已作答的任务ID集合(包含进行中和已完成) var userAnsweredTaskIds = userAnswers.Select(a => a.JournalPageTaskId).Distinct().ToHashSet(); // 用户已完成的任务ID集合(Status=1表示已完成) var userCompletedTaskIds = userAnswers.Where(a => a.Status == (int)UserAnswerStatusEnum.Complete).Select(a => a.JournalPageTaskId).Distinct().ToHashSet(); // 7. 计算答题时间(分钟) var totalAnswerSeconds = userAnswers .Where(a => pageIds.Contains(a.JournalPageId)) .Sum(a => a.AnswerSeconds); var answerMinutes = Math.Round(totalAnswerSeconds / 60.0, 2); // 8. 判定状态(Status=0进行中,Status=1已完成) string status; if (catalogTasks.Count == 0) { status = "未开始"; } else { var completedCount = catalogTasks.Count(t => userCompletedTaskIds.Contains(t.Id)); var answeredCount = catalogTasks.Count(t => userAnsweredTaskIds.Contains(t.Id)); if (answeredCount == 0) status = "未开始"; else if (completedCount == catalogTasks.Count) status = "已完成"; else status = "进行中"; } // 9. 查询徽章个数(通过Medal表关联JournalId) var medalCount = await medalRepository.Context.Queryable() .Where(m => m.JournalId == catalog.JournalId && !m.IsDeleted) .Select(m => m.Id) .CountAsync(); var medalIds = await medalRepository.Context.Queryable() .Where(m => m.JournalId == catalog.JournalId && !m.IsDeleted) .Select(m => m.Id).ToListAsync(); var userMedalCount = await userMedalRepository.Context.Queryable() .Where(um => um.UserId == userId && !um.IsDeleted) .Where(um => medalIds.Contains(um.MedalId)) .CountAsync(); // 10. 计算页码范围 var startPage = allPages.Count > 0 ? allPages.Min(p => p.PageNum) : 0; var endPage = allPages.Count > 0 ? allPages.Max(p => p.PageNum) : 0; // 11. 计算总成长值和总积分 var totalGrowthPoints = catalogTasks.Sum(t => t.GrowthPoint) ?? 0; var totalPoints = catalogTasks.Sum(t => t.Points); return new UserAnswerTaskListOutput { CatalogName = catalog.Name ?? string.Empty, StartPage = startPage, EndPage = endPage, TaskNames = taskNames, Status = status, AnswerMinutes = answerMinutes, MedalCount = userMedalCount, TotalGrowthPoints = totalGrowthPoints, TotalPoints = totalPoints }; } /// /// 获取跨页题目详情 /// public async Task GetCrossPageTaskAsync(long journalPageTaskId, long userId) { // 1. 查询任务信息 var task = await taskRepository.Context.Queryable() .Where(t => t.Id == journalPageTaskId && !t.IsDeleted) .FirstAsync(); if (task == null) { return null; } // 2. 判断是否跨页题目 var isCrossPage = journalPageTaskId != task.GroupId; // 3. 获取所有相关的任务(根据GroupId查询) List relatedTasks; if (isCrossPage) { // 跨页题目:根据GroupId查询所有数据 relatedTasks = await taskRepository.Context.Queryable() .Where(t => t.GroupId == task.GroupId && t.JournalId == task.JournalId && !t.IsDeleted) .OrderBy(t => t.Id) .ToListAsync(); } else { relatedTasks = new List { task }; } // 4. 获取栏目名称 var page = await pageRepository.Context.Queryable() .Where(p => p.Id == task.JournalPageId && !p.IsDeleted) .FirstAsync(); string catalogName = string.Empty; if (page != null) { var catalog = await catalogRepository.Context.Queryable() .Where(c => c.Id == page.JournalCatalogId && !c.IsDeleted) .FirstAsync(); catalogName = catalog?.Name ?? string.Empty; } // 5. 获取图片数组 var images = new List(); if (isCrossPage) { // 跨页题目:查询该GroupId下所有用户的答题记录 var userAnswers = await answerRepository.Context.Queryable() .Where(a => a.JournalPageTaskGroupId == task.GroupId && a.UserId == userId && !a.IsDeleted) .OrderBy(a => a.JournalPageTaskId) .ToListAsync(); if (userAnswers.Any(a => !string.IsNullOrEmpty(a.PageAnswerUrl))) { // 已答题,使用PageAnswerUrl数组 images.AddRange(userAnswers.Where(a => !string.IsNullOrEmpty(a.PageAnswerUrl)).Select(a => a.PageAnswerUrl)); } else { // 未答题,使用TaskUrl数组 foreach (var relatedTask in relatedTasks) { if (!string.IsNullOrEmpty(relatedTask.TaskUrl)) { images.Add(relatedTask.TaskUrl); } } } } else { // 非跨页题目:查询单条答题记录 var userAnswer = await answerRepository.Context.Queryable() .Where(a => a.JournalPageTaskId == journalPageTaskId && a.UserId == userId && !a.IsDeleted) .FirstAsync(); if (userAnswer != null && !string.IsNullOrEmpty(userAnswer.PageAnswerUrl)) { // 已答题,使用PageAnswerUrl images.Add(userAnswer.PageAnswerUrl); } else { // 未答题,使用TaskUrl if (!string.IsNullOrEmpty(task.TaskUrl)) { images.Add(task.TaskUrl); } } } return new CrossPageTaskOutput { CatalogName = catalogName, TaskName = task.Task ?? string.Empty, Images = images, IsCrossPage = isCrossPage }; } /// /// 领取任务积分 /// public async Task ClaimTaskPointsAsync(long journalPageTaskId, long userId) { // 1. 查询任务信息 var task = await taskRepository.Context.Queryable() .Where(t => t.Id == journalPageTaskId && !t.IsDeleted) .FirstAsync(); if (task == null) { throw new BusinessException("任务不存在", 404); } // 2. 判断是否跨页题目 var isCrossPage = journalPageTaskId != task.GroupId; // 3. 查询用户答题记录(跨页题目按GroupId查询,普通题目按任务Id查询) List userAnswers; if (isCrossPage) { userAnswers = await answerRepository.Context.Queryable() .Where(a => a.JournalPageTaskGroupId == task.GroupId && a.UserId == userId && !a.IsDeleted) .ToListAsync(); } else { userAnswers = await answerRepository.Context.Queryable() .Where(a => a.JournalPageTaskId == journalPageTaskId && a.UserId == userId && !a.IsDeleted) .ToListAsync(); } if (userAnswers == null || userAnswers.Count == 0) { throw new BusinessException("请先完成答题再领取积分", 400); } // 4. 计算积分(跨页题目需要累计所有相关任务的积分) int totalPoints = 0; if (isCrossPage) { var relatedTasks = await taskRepository.Context.Queryable() .Where(t => t.GroupId == task.GroupId && t.JournalId == task.JournalId && !t.IsDeleted) .ToListAsync(); totalPoints = relatedTasks.Sum(t => t.Points); } else { totalPoints = task.Points; } if (totalPoints <= 0) { throw new BusinessException("该任务无积分可领取", 400); } // 5. 检查是否已领取过积分(跨页题目按GroupId作为RelatedId) var relatedId = isCrossPage ? task.GroupId : journalPageTaskId; var existingRecord = await answerRepository.Context.Queryable() .Where(r => r.UserId == userId && !r.IsDeleted) .Where(r => r.ChangeType == PointsChangeTypeEnum.TaskReward.ToString() && r.RelatedId == relatedId) .FirstAsync(); if (existingRecord != null) { throw new BusinessException("积分已领取,请勿重复领取", 400); } // 6. 调用积分服务增加积分 var addPointsResult = await pointsService.AddPointsAsync(new AddPointsInput { UserId = userId, Amount = totalPoints, ChangeType = PointsChangeTypeEnum.TaskReward, RelatedId = relatedId, Description = $"完成任务奖励:{task.Task}", OperatorName = "系统" }); return new ClaimTaskPointsOutput { RecordId = addPointsResult.RecordId, PreviousBalance = addPointsResult.PreviousBalance, NewBalance = addPointsResult.NewBalance, AddedAmount = addPointsResult.AddedAmount, TaskName = task.Task ?? string.Empty }; } /// /// 批量领取任务积分(一键领取) /// public async Task BatchClaimPointsAsync(BatchClaimPointsInput input, long userId) { var result = new BatchClaimPointsOutput(); if (input == null || input.GroupIds == null || input.GroupIds.Count == 0) { throw new BusinessException("任务分组Id列表不能为空", 400); } var groupIds = input.GroupIds.Distinct().ToList(); // 1. 批量查询用户答题记录(按GroupId) var userAnswers = await answerRepository.Context.Queryable() .Where(a => groupIds.Contains(a.JournalPageTaskGroupId) && a.UserId == userId && !a.IsDeleted) .ToListAsync(); // 已答题的GroupId集合 var answeredGroupIds = userAnswers.Select(a => a.JournalPageTaskGroupId).Distinct().ToHashSet(); // 2. 批量查询已领取的积分记录 var claimedRecords = await answerRepository.Context.Queryable() .Where(r => groupIds.Contains((long)r.RelatedId!) && r.UserId == userId && !r.IsDeleted) .Where(r => r.ChangeType == PointsChangeTypeEnum.TaskReward.ToString()) .ToListAsync(); var claimedGroupIds = claimedRecords.Select(r => r.RelatedId!.Value).Distinct().ToHashSet(); // 3. 批量查询任务信息 var allTasks = await taskRepository.Context.Queryable() .Where(t => groupIds.Contains(t.GroupId) && !t.IsDeleted) .ToListAsync(); // 按GroupId分组任务 var tasksByGroup = allTasks.GroupBy(t => t.GroupId) .ToDictionary(g => g.Key, g => g.ToList()); // 4. 逐个处理领取 int totalPoints = 0; int currentBalance = 0; foreach (var groupId in groupIds) { try { // 检查是否已答题 if (!answeredGroupIds.Contains(groupId)) { result.FailItems.Add(new BatchClaimFailItem { GroupId = groupId, Message = "请先完成答题再领取积分" }); continue; } // 检查是否已领取 if (claimedGroupIds.Contains(groupId)) { result.FailItems.Add(new BatchClaimFailItem { GroupId = groupId, Message = "积分已领取,请勿重复领取" }); continue; } // 获取该分组的任务 if (!tasksByGroup.TryGetValue(groupId, out var groupTasks) || groupTasks.Count == 0) { result.FailItems.Add(new BatchClaimFailItem { GroupId = groupId, Message = "任务不存在" }); continue; } // 计算积分 int points = groupTasks.Sum(t => t.Points); if (points <= 0) { result.FailItems.Add(new BatchClaimFailItem { GroupId = groupId, Message = "该任务无积分可领取" }); continue; } // 获取任务名称(取第一个任务的名称) var taskName = groupTasks.First().Task ?? string.Empty; // 调用积分服务增加积分 var addResult = await pointsService.AddPointsAsync(new AddPointsInput { UserId = userId, Amount = points, ChangeType = PointsChangeTypeEnum.TaskReward, RelatedId = groupId, Description = $"完成任务奖励:{taskName}", OperatorName = "系统" }); totalPoints += points; currentBalance = addResult.NewBalance; result.SuccessCount++; } catch (Exception ex) { result.FailItems.Add(new BatchClaimFailItem { GroupId = groupId, Message = ex.Message }); } } result.FailCount = result.FailItems.Count; result.TotalPoints = totalPoints; result.BalanceAfter = currentBalance; return result; } /// /// 获取杂志列表 /// public async Task> GetJournalListAsync(long userId, string? queryType) { var allJournals = await journalRepository.Queryable() .Where(j => !j.IsDeleted && j.Status == (int)JournalStatusEnum.Archive) .OrderByDescending(j => j.CreatedAt) .ToListAsync(); var userJournals = await userJournalRepository.Context.Queryable() .Where(uj => uj.UserId == userId && !uj.IsDeleted) .Select(uj => uj.JournalId) .ToListAsync(); var boundJournalIds = userJournals.ToHashSet(); IEnumerable filteredJournals = queryType switch { "Bound" => allJournals.Where(j => boundJournalIds.Contains(j.Id)), "Unbound" => allJournals.Where(j => !boundJournalIds.Contains(j.Id)), _ => allJournals }; var result = filteredJournals.Select(j => new JournalListOutput { Id = j.Id, Name = j.Name ?? string.Empty, Title = j.Title ?? string.Empty, Cover = j.Cover ?? string.Empty, IsBound = boundJournalIds.Contains(j.Id) }).ToList(); return result; } /// /// 新增学生 /// public async Task AddStudentAsync(AddStudentInput input, long userId) { // 创建新的学生记录 var student = new Users { WxUserId = userId, Name = input.Name, AvatarUrl = input.AvatarUrl, GrowthPoints = 0, Points = 0, Type = UsersTypeEnum.Normal, IsLastOnline = false }; // 插入数据库 return await usersRepository.InsertAsync(student); } }