Files
QYZH.InteractiveMagazine/QYZH.InteractiveMagazine.Service/UserAnswerTaskService.cs

947 lines
38 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// 用户作答任务服务实现
/// </summary>
public class UserAnswerTaskService(
BaseRepository<JournalPageTaskUserAnswer> answerRepository,
BaseRepository<Journal> journalRepository,
BaseRepository<JournalCatalog> catalogRepository,
BaseRepository<JournalPage> pageRepository,
BaseRepository<JournalPageTask> taskRepository,
BaseRepository<UserMedal> userMedalRepository,
BaseRepository<Medal> medalRepository,
BaseRepository<UserJournal> userJournalRepository,
BaseRepository<Users> usersRepository,
BaseRepository<CheckInRecord> checkInRecordRepository,
IPointsService pointsService,
ILogger<UserAnswerTaskService> logger)
: BaseRepository<JournalPageTaskUserAnswer>, IUserAnswerTaskService
{
/// <summary>
/// 获取用户期刊学习进度列表按期刊分组批量预加载避免N+1
/// </summary>
public async Task<List<JournalProgressOutput>> GetJournalProgressAsync(long userId)
{
// 1. 查询用户的所有作答记录
var userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
.Where(a => a.UserId == userId && !a.IsDeleted)
.ToListAsync();
if (userAnswers == null || userAnswers.Count == 0)
{
return new List<JournalProgressOutput>();
}
// 2. 提取关联的期刊ID去重
var journalIds = userAnswers.Select(a => a.JournalId).Distinct().ToList();
// 3. 批量预加载关联实体(按期刊维度全量加载)
var journals = await journalRepository.Context.Queryable<Journal>()
.Where(j => journalIds.Contains(j.Id) && !j.IsDeleted)
.ToListAsync();
// 加载这些期刊的一级目录ParentId == 0 表示一级)
var allCatalogs = await catalogRepository.Context.Queryable<JournalCatalog>()
.Where(c => journalIds.Contains(c.JournalId) && c.ParentId == 0 && !c.IsDeleted)
.ToListAsync();
// 加载这些期刊的所有任务(直接按 JournalId
var allTasks = await taskRepository.Context.Queryable<JournalPageTask>()
.Where(t => journalIds.Contains(t.JournalId) && !t.IsDeleted)
.ToListAsync();
// 加载这些期刊的所有页面(用于计算剩余篇数)
var allPages = await pageRepository.Context.Queryable<JournalPage>()
.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<JournalProgressOutput>();
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;
}
/// <summary>
/// 计算用户连续打卡天数
/// </summary>
private async Task<int> CalculateConsecutiveDaysAsync(long userId)
{
var today = DateTime.Now.Date;
// 查询最近一次签到记录
var lastRecord = await checkInRecordRepository.Context.Queryable<CheckInRecord>()
.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;
}
/// <summary>
/// 获取目录列表按一级目录分组二级目录含完成状态批量预加载避免N+1
/// </summary>
public async Task<List<CatalogListOutput>> GetCatalogListAsync(long journalId, long userId)
{
// 1. 查询期刊
var journal = await journalRepository.Context.Queryable<Journal>()
.Where(j => j.Id == journalId && !j.IsDeleted)
.FirstAsync();
if (journal == null)
{
return new List<CatalogListOutput>();
}
// 2. 批量加载关联数据
var allCatalogs = await catalogRepository.Context.Queryable<JournalCatalog>()
.Where(c => c.JournalId == journalId && !c.IsDeleted)
.ToListAsync();
var allPages = await pageRepository.Context.Queryable<JournalPage>()
.Where(p => p.JournalId == journalId && !p.IsDeleted)
.ToListAsync();
var allTasks = await taskRepository.Context.Queryable<JournalPageTask>()
.Where(t => t.JournalId == journalId && !t.IsDeleted)
.ToListAsync();
var userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
.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<CatalogListOutput>();
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<SecondCatalogOutput>();
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;
}
/// <summary>
/// 获取一级目录概览含二级目录详情、页码范围、任务完成状态批量预加载避免N+1
/// </summary>
/// <param name="catalogId">一级烂ID</param>
/// <param name="userId">用户ID</param>
/// <returns></returns>
public async Task<CatalogOverviewOutput> GetCatalogOverviewAsync(long catalogId, long userId)
{
// 1. 查询一级目录
var firstCatalog = await catalogRepository.Context.Queryable<JournalCatalog>()
.Where(c => c.Id == catalogId && !c.IsDeleted)
.FirstAsync();
if (firstCatalog == null)
{
return null;
}
// 2. 批量加载关联数据
var allCatalogs = await catalogRepository.Context.Queryable<JournalCatalog>()
.Where(c => c.JournalId == firstCatalog.JournalId && !c.IsDeleted)
.ToListAsync();
var allPages = await pageRepository.Context.Queryable<JournalPage>()
.Where(p => p.JournalId == firstCatalog.JournalId && !p.IsDeleted)
.ToListAsync();
var allTasks = await taskRepository.Context.Queryable<JournalPageTask>()
.Where(t => t.JournalId == firstCatalog.JournalId && !t.IsDeleted)
.ToListAsync();
var userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
.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<SubCatalogDetailOutput>();
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
};
}
/// <summary>
/// 获取用户答题任务列表
/// </summary>
public async Task<UserAnswerTaskListOutput> GetUserAnswerTaskListAsync(long catalogId, long userId)
{
// 1. 查询目录
var catalog = await catalogRepository.Context.Queryable<JournalCatalog>()
.Where(c => c.Id == catalogId && !c.IsDeleted)
.FirstAsync();
if (catalog == null)
{
return null;
}
// 2. 批量加载关联数据
var allPages = await pageRepository.Context.Queryable<JournalPage>()
.Where(p => p.JournalCatalogId == catalogId && !p.IsDeleted)
.ToListAsync();
var allTasks = await taskRepository.Context.Queryable<JournalPageTask>()
.Where(t => t.JournalId == catalog.JournalId && !t.IsDeleted)
.ToListAsync();
var userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
.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<Medal>()
.Where(m => m.JournalId == catalog.JournalId && !m.IsDeleted)
.Select(m => m.Id)
.CountAsync();
var medalIds = await medalRepository.Context.Queryable<Medal>()
.Where(m => m.JournalId == catalog.JournalId && !m.IsDeleted)
.Select(m => m.Id).ToListAsync();
var userMedalCount = await userMedalRepository.Context.Queryable<UserMedal>()
.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
};
}
/// <summary>
/// 获取跨页题目详情
/// </summary>
public async Task<CrossPageTaskOutput> GetCrossPageTaskAsync(long journalPageTaskId, long userId)
{
// 1. 查询任务信息
var task = await taskRepository.Context.Queryable<JournalPageTask>()
.Where(t => t.Id == journalPageTaskId && !t.IsDeleted)
.FirstAsync();
if (task == null)
{
return null;
}
// 2. 判断是否跨页题目
var isCrossPage = journalPageTaskId != task.GroupId;
// 3. 获取所有相关的任务根据GroupId查询
List<JournalPageTask> relatedTasks;
if (isCrossPage)
{
// 跨页题目根据GroupId查询所有数据
relatedTasks = await taskRepository.Context.Queryable<JournalPageTask>()
.Where(t => t.GroupId == task.GroupId && t.JournalId == task.JournalId && !t.IsDeleted)
.OrderBy(t => t.Id)
.ToListAsync();
}
else
{
relatedTasks = new List<JournalPageTask> { task };
}
// 4. 获取栏目名称
var page = await pageRepository.Context.Queryable<JournalPage>()
.Where(p => p.Id == task.JournalPageId && !p.IsDeleted)
.FirstAsync();
string catalogName = string.Empty;
if (page != null)
{
var catalog = await catalogRepository.Context.Queryable<JournalCatalog>()
.Where(c => c.Id == page.JournalCatalogId && !c.IsDeleted)
.FirstAsync();
catalogName = catalog?.Name ?? string.Empty;
}
// 5. 获取图片数组
var images = new List<string>();
if (isCrossPage)
{
// 跨页题目查询该GroupId下所有用户的答题记录
var userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
.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<JournalPageTaskUserAnswer>()
.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
};
}
/// <summary>
/// 领取任务积分
/// </summary>
public async Task<ClaimTaskPointsOutput> ClaimTaskPointsAsync(long journalPageTaskId, long userId)
{
// 1. 查询任务信息
var task = await taskRepository.Context.Queryable<JournalPageTask>()
.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<JournalPageTaskUserAnswer> userAnswers;
if (isCrossPage)
{
userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
.Where(a => a.JournalPageTaskGroupId == task.GroupId && a.UserId == userId && !a.IsDeleted)
.ToListAsync();
}
else
{
userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
.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<JournalPageTask>()
.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<PointsRecord>()
.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
};
}
/// <summary>
/// 批量领取任务积分(一键领取)
/// </summary>
public async Task<BatchClaimPointsOutput> 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<JournalPageTaskUserAnswer>()
.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<PointsRecord>()
.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<JournalPageTask>()
.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;
}
/// <summary>
/// 获取杂志列表
/// </summary>
public async Task<List<JournalListOutput>> 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<UserJournal>()
.Where(uj => uj.UserId == userId && !uj.IsDeleted)
.Select(uj => uj.JournalId)
.ToListAsync();
var boundJournalIds = userJournals.ToHashSet();
IEnumerable<Journal> 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;
}
/// <summary>
/// 新增学生
/// </summary>
public async Task<bool> 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);
}
}