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

1402 lines
62 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>
/// ============================================
/// 方法思路:
/// 1. 批量预加载策略为避免N+1查询问题先按期刊维度批量加载所有关联数据
/// 2. 跨页题目处理核心:
/// - 跨页题目定义JournalPageTask.Id != GroupId同一个GroupId下有多个任务
/// - 进度计算按GroupId去重已完成条件是该GroupId下所有任务都完成Status=1
/// - 积分计算按GroupId去重每个GroupId只计算一次积分取第一个任务的积分
/// 3. 状态判定逻辑:
/// - 未开始:无任何作答记录
/// - 进行中:有作答记录但未全部完成
/// - 已完成所有GroupId都完成
/// ============================================
/// </summary>
/// <param name="userId">用户ID</param>
/// <returns>用户所有期刊的学习进度列表</returns>
public async Task<List<JournalProgressOutput>> GetJournalProgressAsync(long userId)
{
// ============================================
// 跨页题目处理核心逻辑说明:
// 1. 跨页题目定义JournalPageTask.Id != GroupId
// - Id 是任务的唯一标识
// - GroupId 是跨页题目的分组标识同一个跨页题目的所有任务共享同一个GroupId
// 2. 进度计算:按 GroupId 去重
// - 已完成条件:该 GroupId 下所有任务都完成Status=1
// 3. 积分计算:按 GroupId 去重,每个 GroupId 只计算一次积分
// 4. 状态判定:
// - 未开始:无任何 GroupId 被作答
// - 进行中:有 GroupId 被作答但未全部完成
// - 已完成:所有 GroupId 都完成
// ============================================
// 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. 批量预加载关联实体按期刊维度全量加载避免N+1查询
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();
// 加载这些期刊的所有任务
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 journalTasks = allTasks.Where(t => t.JournalId == journal.Id).ToList();
// 该期刊的一级栏目数
var catalogCount = allCatalogs.Count(c => c.JournalId == journal.Id);
// 【跨页题目-进度计算】总任务数:按 GroupId 去重,跨页题目只算一个
var totalTaskCount = journalTasks.Select(t => t.GroupId).Distinct().Count();
// 【跨页题目-已完成判断】已完成任务数:按 GroupId 去重
// 关键:必须该 GroupId 下所有任务都完成,才算该 GroupId 完成
var completedTaskCount = journalTasks
.GroupBy(t => t.GroupId)
.Where(g => g.All(t => userCompletedTaskIds.Contains(t.Id)))
.Count();
// 【跨页题目-积分计算】获取已完成的 GroupId 列表
var completedGroupIds = journalTasks
.GroupBy(t => t.GroupId)
.Where(g => g.All(t => userCompletedTaskIds.Contains(t.Id)))
.Select(g => g.Key)
.ToList();
// 【跨页题目-积分去重】每个已完成的 GroupId 只取第一个任务的积分
// 原因:同一个跨页题目的多个任务只应该计算一次积分
var distinctCompletedTasks = journalTasks
.Where(t => completedGroupIds.Contains(t.GroupId))
.GroupBy(t => t.GroupId)
.Select(g => g.First())
.ToList();
// 累计已完成的跨页题目的总成长值和总积分
var totalGrowthPoints = distinctCompletedTasks.Sum(t => t.GrowthPoint ?? 0);
var totalPoints = distinctCompletedTasks.Sum(t => t.Points);
// 【跨页题目-进度百分比计算】
var totalGroupIds = journalTasks.Select(t => t.GroupId).Distinct().Count();
var completedGroupIdCount = journalTasks
.GroupBy(t => t.GroupId)
.Where(g => g.All(t => userCompletedTaskIds.Contains(t.Id)))
.Count();
var progress = totalGroupIds > 0
? Math.Round((double)completedGroupIdCount / totalGroupIds * 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>
/// ============================================
/// 方法思路:计算用户连续打卡天数
/// 1. 查询用户最近一次签到记录
/// 2. 判断是否连续:如果最后一次签到是今天或昨天,连续天数延续
/// 3. 否则返回0中断了
/// ============================================
/// </summary>
/// <param name="userId">用户ID</param>
/// <returns>连续打卡天数</returns>
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>
/// ============================================
/// 方法思路:获取目录列表(按一级目录分组展示)
/// 1. 批量预加载:该期刊下所有目录、页面、任务、用户作答记录
/// 2. 目录结构处理:
/// - 一级目录ParentId=0 且 Type=Unit
/// - 二级目录ParentId=一级目录Id 且 Type=Unit可选
/// - 文章目录Type=Page
/// 3. 跨页题目处理:
/// - 状态判断按GroupId去重已完成条件是该GroupId下所有任务都完成Status=1
/// - 积分计算按GroupId去重每个GroupId只计算一次积分
/// - 已作答判断只要有一个任务作答就算该GroupId已作答包含进行中和已完成
/// 4. 状态判定:
/// - 未开始无任何GroupId被作答
/// - 进行中有GroupId被作答但未全部完成
/// - 已完成所有GroupId都完成
/// ============================================
/// </summary>
/// <param name="journalId">期刊ID</param>
/// <param name="userId">用户ID</param>
/// <returns>目录列表(按一级目录分组)</returns>
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已完成跨页题目按GroupId去重
// 跨页题目必须所有任务都完成,才算该 GroupId 已完成
string status;
if (secondLevelTasks.Count == 0)
{
status = "未开始";
}
else
{
// 总 GroupId 数(跨页题目只算一个)
var totalGroupCount = secondLevelTasks.Select(t => t.GroupId).Distinct().Count();
// 已完成 GroupId 数(该 GroupId 下所有任务都完成才算完成)
var completedGroupCount = secondLevelTasks
.GroupBy(t => t.GroupId)
.Where(g => g.All(t => userCompletedTaskIds.Contains(t.Id)))
.Count();
// 【关键】已作答 GroupId 数:只要有一个任务作答就算该 GroupId 已作答(包含进行中和已完成)
// 这个字段用于判断状态:未开始 / 进行中 / 已完成
var answeredGroupCount = secondLevelTasks
.Where(t => userAnsweredTaskIds.Contains(t.Id))
.Select(t => t.GroupId)
.Distinct()
.Count();
// 状态判定逻辑:
// - answeredGroupCount == 0没有任何 GroupId 被作答 → 未开始
// - completedGroupCount == totalGroupCount所有 GroupId 都完成 → 已完成
// - 其他:有 GroupId 被作答但未全部完成 → 进行中
if (answeredGroupCount == 0)
status = "未开始";
else if (completedGroupCount == totalGroupCount)
status = "已完成";
else
status = "进行中";
}
// 计算该二级目录的总成长值和总积分跨页题目按GroupId去重
var distinctSecondLevelTasks = secondLevelTasks
.GroupBy(t => t.GroupId)
.Select(g => g.First())
.ToList();
var totalGrowthPoints = distinctSecondLevelTasks.Sum(t => t.GrowthPoint) ?? 0;
var totalPoints = distinctSecondLevelTasks.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已完成跨页题目按GroupId去重
// 跨页题目必须所有任务都完成,才算该 GroupId 已完成
string status;
if (articleTasks.Count == 0)
{
status = "未开始";
}
else
{
// 总 GroupId 数(跨页题目只算一个)
var totalGroupCount = articleTasks.Select(t => t.GroupId).Distinct().Count();
// 已完成 GroupId 数(该 GroupId 下所有任务都完成才算完成)
var completedGroupCount = articleTasks
.GroupBy(t => t.GroupId)
.Where(g => g.All(t => userCompletedTaskIds.Contains(t.Id)))
.Count();
// 已作答 GroupId 数(只要有一个任务作答就算已作答,包含进行中和已完成)
var answeredGroupCount = articleTasks
.Where(t => userAnsweredTaskIds.Contains(t.Id))
.Select(t => t.GroupId)
.Distinct()
.Count();
if (answeredGroupCount == 0)
status = "未开始";
else if (completedGroupCount == totalGroupCount)
status = "已完成";
else
status = "进行中";
}
// 计算该文章的总成长值和总积分跨页题目按GroupId去重
var distinctArticleTasks = articleTasks
.GroupBy(t => t.GroupId)
.Select(g => g.First())
.ToList();
var totalGrowthPoints = distinctArticleTasks.Sum(t => t.GrowthPoint) ?? 0;
var totalPoints = distinctArticleTasks.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>
/// ============================================
/// 方法思路:获取一级目录概览(含二级目录详情和任务完成状态)
/// 1. 批量预加载:该期刊下所有目录、页面、任务、用户作答记录
/// 2. 获取该一级目录下的所有文章目录Type=Page
/// 3. 跨页题目处理:
/// - 总任务数按GroupId去重跨页题目只算一个
/// - 状态判断按GroupId去重已完成条件是该GroupId下所有任务都完成Status=1
/// - 已作答判断只要有一个任务作答就算该GroupId已作答
/// 4. 状态格式:"已完成数/总数 已完成/进行中/未开始"
/// 例如:"2/3 进行中" 表示3个GroupId中已完成2个
/// ============================================
/// </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. 总任务数(按 GroupId 去重,跨页题目只算一个)
var totalTaskCount = allTasks
.Where(t => allLevelPageIds.Contains(t.JournalPageId))
.Select(t => t.GroupId)
.Distinct()
.Count();
// 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();
// 总 GroupId 数(按 GroupId 去重,跨页题目只算一个)
var totalGroupCount = articleTasks.Select(t => t.GroupId).Distinct().Count();
// 页码范围
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 未开始跨页题目按GroupId去重
// 跨页题目必须所有任务都完成,才算该 GroupId 已完成
string status;
if (totalGroupCount == 0)
{
status = $"0/0 未开始";
}
else
{
// 已完成 GroupId 数(该 GroupId 下所有任务都完成才算完成)
var completedGroupCount = articleTasks
.GroupBy(t => t.GroupId)
.Where(g => g.All(t => userCompletedTaskIds.Contains(t.Id)))
.Count();
// 已作答 GroupId 数(只要有一个任务作答就算已作答,包含进行中和已完成)
var answeredGroupCount = articleTasks
.Where(t => userAnsweredTaskIds.Contains(t.Id))
.Select(t => t.GroupId)
.Distinct()
.Count();
if (answeredGroupCount == 0)
status = $"0/{totalGroupCount} 未开始";
else if (completedGroupCount == totalGroupCount)
status = $"{completedGroupCount}/{totalGroupCount} 已完成";
else
status = $"{completedGroupCount}/{totalGroupCount} 进行中";
}
subCatalogs.Add(new SubCatalogDetailOutput
{
CatalogId = pageCatalog.Id,
SecondLevelCatalogName = pageCatalog.Name ?? string.Empty,
StartPage = startPage,
EndPage = endPage,
TaskCount = totalGroupCount,
Status = status
});
}
return new CatalogOverviewOutput
{
CatalogId = firstCatalog.Id,
FirstLevelCatalogName = firstCatalog.Name ?? string.Empty,
PageCount = articleCount,
TotalTaskCount = totalTaskCount,
SubCatalogs = subCatalogs
};
}
/// <summary>
/// ============================================
/// 方法思路:获取用户答题任务列表(某个二级栏目/文章下的所有任务)
/// 1. 查询目录信息通过catalogId查询二级栏目
/// 2. 查询一级栏目名称通过ParentId关联查询一级栏目名称
/// 3. 批量预加载:该栏目下的所有页面、该期刊下所有任务、用户作答记录
/// 4. 跨页题目处理:
/// - 状态判断按GroupId去重已完成条件是该GroupId下所有任务都完成Status=1
/// - 积分计算按GroupId去重每个GroupId只计算一次积分和成长值
/// - 已作答判断只要有一个任务作答就算该GroupId已作答
/// 5. 徽章统计:统计该期刊下已获得的徽章数量
/// ============================================
/// </summary>
/// <param name="catalogId">二级栏目/文章目录ID</param>
/// <param name="userId">用户ID</param>
/// <returns>用户答题任务列表信息</returns>
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;
}
// ============================================
// 【获取一级栏目名称】
// 通过 ParentId 关联查询一级栏目名称
// 一级栏目ParentId = 0
// 二级栏目/文章ParentId = 一级栏目IdType = Page
// ============================================
string firstLevelCatalogName = string.Empty;
if (catalog.ParentId > 0)
{
var firstLevelCatalog = await catalogRepository.Context.Queryable<JournalCatalog>()
.Where(c => c.Id == catalog.ParentId && !c.IsDeleted)
.FirstAsync();
firstLevelCatalogName = firstLevelCatalog?.Name ?? string.Empty;
}
// ============================================
// 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);
// ============================================
// 【跨页题目-状态判断】
// 状态判定:
// - 未开始:无任何 GroupId 被作答
// - 进行中:有 GroupId 被作答但未全部完成
// - 已完成:所有 GroupId 都完成
// 关键:必须该 GroupId 下所有任务都完成,才算该 GroupId 完成
// ============================================
string status;
if (catalogTasks.Count == 0)
{
status = "未开始";
}
else
{
// 总 GroupId 数(跨页题目只算一个)
var totalGroupCount = catalogTasks.Select(t => t.GroupId).Distinct().Count();
// 已完成 GroupId 数(该 GroupId 下所有任务都完成才算完成)
var completedGroupCount = catalogTasks
.GroupBy(t => t.GroupId)
.Where(g => g.All(t => userCompletedTaskIds.Contains(t.Id)))
.Count();
// 已作答 GroupId 数(只要有一个任务作答就算已作答,包含进行中和已完成)
var answeredGroupCount = catalogTasks
.Where(t => userAnsweredTaskIds.Contains(t.Id))
.Select(t => t.GroupId)
.Distinct()
.Count();
if (answeredGroupCount == 0)
status = "未开始";
else if (completedGroupCount == totalGroupCount)
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. 计算总成长值和总积分跨页题目按GroupId去重只算一次
var distinctCatalogTasks = catalogTasks
.GroupBy(t => t.GroupId)
.Select(g => g.First())
.ToList();
var totalGrowthPoints = distinctCatalogTasks.Sum(t => t.GrowthPoint) ?? 0;
var totalPoints = distinctCatalogTasks.Sum(t => t.Points);
return new UserAnswerTaskListOutput
{
FirstLevelCatalogName = firstLevelCatalogName,
CatalogName = catalog.Name ?? string.Empty,
StartPage = startPage,
EndPage = endPage,
TaskNames = taskNames,
Status = status,
AnswerMinutes = answerMinutes,
MedalCount = userMedalCount,
TotalGrowthPoints = totalGrowthPoints,
TotalPoints = totalPoints
};
}
/// <summary>
/// ============================================
/// 方法思路:获取跨页题目详情
/// 1. 判断是否跨页题目journalPageTaskId != GroupId 则为跨页题目
/// 2. 跨页题目处理根据GroupId查询所有相关任务
/// 3. 图片数组获取逻辑:
/// - 已答题使用用户上传的PageAnswerUrl数组
/// - 未答题使用任务的TaskUrl数组
/// 4. 栏目名称获取:
/// - 二级栏目名称通过页面关联的JournalCatalogId查询
/// - 一级栏目名称通过二级栏目的ParentId查询
/// ============================================
/// </summary>
/// <param name="journalPageTaskId">任务ID</param>
/// <param name="userId">用户ID</param>
/// <returns>跨页题目详情</returns>
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;
}
// ============================================
// 【跨页题目判定】
// 判断标准journalPageTaskId != task.GroupId 则为跨页题目
// 跨页题目有多个任务,但共享同一个 GroupId
// ============================================
var isCrossPage = journalPageTaskId != task.GroupId;
// ============================================
// 【获取相关任务】
// 跨页题目:根据 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 };
}
// ============================================
// 【获取栏目名称】
// 二级栏目名称:通过页面关联的 JournalCatalogId 查询
// 一级栏目名称:通过二级栏目的 ParentId 查询
// ============================================
var page = await pageRepository.Context.Queryable<JournalPage>()
.Where(p => p.Id == task.JournalPageId && !p.IsDeleted)
.FirstAsync();
string catalogName = string.Empty;
string firstLevelCatalogName = 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;
// 获取一级栏目名称
if (catalog != null && catalog.ParentId > 0)
{
var firstLevelCatalog = await catalogRepository.Context.Queryable<JournalCatalog>()
.Where(c => c.Id == catalog.ParentId && !c.IsDeleted)
.FirstAsync();
firstLevelCatalogName = firstLevelCatalog?.Name ?? string.Empty;
}
}
// ============================================
// 【获取图片数组】
// 已答题:使用用户上传的 PageAnswerUrl 数组
// 未答题:使用任务的 TaskUrl 数组
// ============================================
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
{
FirstLevelCatalogName = firstLevelCatalogName,
CatalogName = catalogName,
TaskName = task.Task ?? string.Empty,
Images = images,
IsCrossPage = isCrossPage
};
}
/// <summary>
/// ============================================
/// 方法思路:领取任务积分
/// 1. 根据 GroupId 查询任务信息,判断是否跨页题目
/// - 普通题目:该 GroupId 下只有1个任务且 Id == GroupId
/// - 跨页题目:该 GroupId 下有多个任务,或 Id != GroupId
/// 2. 通过 JournalPageTaskGroupId 查询用户答题记录
/// 3. 跨页题目完成校验:必须该 GroupId 下所有任务都完成Status=1
/// 普通题目校验只需要该任务完成Status=1
/// 4. 重复领取校验:通过 PointsRecord 表的 RelatedId + ChangeType 判断
/// 5. 积分计算:
/// - 跨页题目:累计该 GroupId 下所有任务的积分
/// - 普通题目:取当前任务的积分
/// 6. 得分计算:从 JournalPageTaskUserAnswer 表获取 Score
/// ============================================
/// </summary>
/// <param name="journalPageTaskGroupId">任务分组IdJournalPageTask.GroupId</param>
/// <param name="userId">用户ID</param>
/// <returns>领取积分结果</returns>
public async Task<ClaimTaskPointsOutput> ClaimTaskPointsAsync(long journalPageTaskGroupId, long userId)
{
// ============================================
// 1. 根据 GroupId 查询所有相关任务
// ============================================
var relatedTasks = await taskRepository.Context.Queryable<JournalPageTask>()
.Where(t => t.GroupId == journalPageTaskGroupId && !t.IsDeleted)
.ToListAsync();
if (relatedTasks == null || relatedTasks.Count == 0)
{
throw new BusinessException("任务不存在", 404);
}
// ============================================
// 2. 判断是否跨页题目
// 普通题目:该 GroupId 下只有1个任务且该任务的 Id == GroupId
// 跨页题目:该 GroupId 下有多个任务,或任务的 Id != GroupId
// ============================================
bool isCrossPage;
JournalPageTask mainTask;
if (relatedTasks.Count == 1 && relatedTasks[0].Id == journalPageTaskGroupId)
{
// 普通题目只有1个任务且 Id == GroupId
isCrossPage = false;
mainTask = relatedTasks[0];
}
else
{
// 跨页题目:有多个任务,或者 Id != GroupId
isCrossPage = true;
// 取 Id == GroupId 的任务作为主任务(跨页题目的第一条记录)
mainTask = relatedTasks.FirstOrDefault(t => t.Id == journalPageTaskGroupId) ?? relatedTasks[0];
}
// ============================================
// 3. 查询用户答题记录(统一通过 JournalPageTaskGroupId 查询)
// ============================================
var userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
.Where(a => a.JournalPageTaskGroupId == journalPageTaskGroupId && a.UserId == userId && !a.IsDeleted)
.ToListAsync();
if (userAnswers == null || userAnswers.Count == 0)
{
throw new BusinessException("请先完成答题再领取积分", 400);
}
// ============================================
// 4. 跨页题目完成校验
// 跨页题目必须该 GroupId 下所有任务都完成Status=1才能领取积分
// 普通题目只需要该任务完成Status=1即可领取
// ============================================
if (isCrossPage)
{
// 跨页题目:检查该 GroupId 下每个任务是否都有 Status=1 的答题记录
foreach (var task in relatedTasks)
{
var completedAnswer = userAnswers.FirstOrDefault(a => a.JournalPageTaskId == task.Id && a.Status == (int)UserAnswerStatusEnum.Complete);
if (completedAnswer == null)
{
throw new BusinessException("跨页题目需要完成所有任务才能领取积分", 400);
}
}
}
else
{
// 普通题目:只需要 Status=1 即可
if (userAnswers.FirstOrDefault()?.Status != (int)UserAnswerStatusEnum.Complete)
{
throw new BusinessException("请先完成答题再领取积分", 400);
}
}
// ============================================
// 5. 积分计算
// 跨页题目:累计该 GroupId 下所有任务的积分
// 普通题目:取当前任务的积分
// ============================================
int totalPoints = 0;
if (isCrossPage)
{
// 跨页题目:累计所有任务的积分
totalPoints = relatedTasks.Sum(t => t.Points);
}
else
{
// 普通题目:取主任务的积分
totalPoints = mainTask.Points;
}
if (totalPoints <= 0)
{
throw new BusinessException("该任务无积分可领取", 400);
}
// ============================================
// 6. 得分计算
// 从 JournalPageTaskUserAnswer 表获取 Score
// 【关键】Score 取 JournalPageTaskId == JournalPageTaskGroupId 的分数
// 跨页题目取主任务Id == GroupId的分数不是累加所有任务的分数
// 普通题目:取当前任务对应答题记录的分数
// ============================================
float totalScore = 0;
if (isCrossPage)
{
// 跨页题目:取 JournalPageTaskId == JournalPageTaskGroupId 的分数
totalScore = userAnswers.FirstOrDefault(a => a.JournalPageTaskId == journalPageTaskGroupId)?.Score ?? 0;
}
else
{
// 普通题目:取当前任务对应答题记录的分数
totalScore = userAnswers.FirstOrDefault()?.Score ?? 0;
}
// ============================================
// 7. 重复领取校验
// 通过 PointsRecord 表的 RelatedId + ChangeType 判断是否已领取
// 使用 GroupId 作为 RelatedId
// ============================================
var existingRecord = await answerRepository.Context.Queryable<PointsRecord>()
.Where(r => r.UserId == userId && !r.IsDeleted)
.Where(r => r.ChangeType == PointsChangeTypeEnum.TaskReward.ToString() && r.RelatedId == journalPageTaskGroupId)
.FirstAsync();
if (existingRecord != null)
{
throw new BusinessException("积分已领取,请勿重复领取", 400);
}
// ============================================
// 8. 调用积分服务添加积分记录
// ============================================
var addPointsResult = await pointsService.AddPointsAsync(new AddPointsInput
{
UserId = userId,
Amount = totalPoints,
ChangeType = PointsChangeTypeEnum.TaskReward,
RelatedId = journalPageTaskGroupId,
Description = $"完成任务奖励:{mainTask.Task}",
OperatorName = "系统"
});
return new ClaimTaskPointsOutput
{
RecordId = addPointsResult.RecordId,
PreviousBalance = addPointsResult.PreviousBalance,
NewBalance = addPointsResult.NewBalance,
AddedAmount = addPointsResult.AddedAmount,
TaskName = mainTask.Task ?? string.Empty,
Score = totalScore
};
}
/// <summary>
/// ============================================
/// 方法思路:批量领取任务积分(一键领取)
/// 1. 遍历每个传入的 GroupId
/// a. 根据 GroupId 查询所有相关任务,判断是否跨页题目
/// - 普通题目:该 GroupId 下只有1个任务且 Id == GroupId
/// - 跨页题目:该 GroupId 下有多个任务,或 Id != GroupId
/// b. 通过 JournalPageTaskGroupId 查询用户答题记录
/// c. 跨页题目完成校验:必须该 GroupId 下所有任务都完成Status=1
/// d. 重复领取校验
/// e. 计算积分、成长值、得分:
/// - 跨页题目:所有计算都只算一次(第一个任务的积分/成长值/得分)
/// - 普通题目:正常计算
/// 2. 返回结果包含:
/// - 成功数量和失败数量
/// - 成功详情列表每个GroupId的名称、积分、成长值、得分
/// - 失败详情列表每个GroupId的错误原因
/// - 总积分、总成长值、领取后余额
/// ============================================
/// </summary>
/// <param name="input">批量领取输入GroupId列表</param>
/// <param name="userId">用户ID</param>
/// <returns>批量领取结果</returns>
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. 批量查询已领取的积分记录(用于重复领取校验)
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();
// 2. 逐个处理每个 GroupId
int totalPoints = 0;
int totalGrowthPoints = 0;
int currentBalance = 0;
foreach (var groupId in groupIds)
{
try
{
// ============================================
// 2.1 根据 GroupId 查询所有相关任务
// ============================================
var relatedTasks = await taskRepository.Context.Queryable<JournalPageTask>()
.Where(t => t.GroupId == groupId && !t.IsDeleted)
.ToListAsync();
if (relatedTasks == null || relatedTasks.Count == 0)
{
result.FailItems.Add(new BatchClaimFailItem
{
GroupId = groupId,
Message = "任务不存在"
});
continue;
}
// ============================================
// 2.2 判断是否跨页题目
// 普通题目:该 GroupId 下只有1个任务且 Id == GroupId
// 跨页题目:有多个任务,或 Id != GroupId
// ============================================
bool isCrossPage;
JournalPageTask mainTask;
if (relatedTasks.Count == 1 && relatedTasks[0].Id == groupId)
{
// 普通题目
isCrossPage = false;
mainTask = relatedTasks[0];
}
else
{
// 跨页题目
isCrossPage = true;
// 取 Id == GroupId 的任务作为主任务
mainTask = relatedTasks.FirstOrDefault(t => t.Id == groupId) ?? relatedTasks[0];
}
// ============================================
// 2.3 查询用户答题记录(统一通过 JournalPageTaskGroupId 查询)
// ============================================
var userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
.Where(a => a.JournalPageTaskGroupId == groupId && a.UserId == userId && !a.IsDeleted)
.ToListAsync();
if (userAnswers == null || userAnswers.Count == 0)
{
result.FailItems.Add(new BatchClaimFailItem
{
GroupId = groupId,
Message = "请先完成答题再领取积分"
});
continue;
}
// ============================================
// 2.4 跨页题目完成校验
// 跨页题目必须该 GroupId 下所有任务都完成Status=1
// 普通题目只需要该任务完成Status=1
// ============================================
if (isCrossPage)
{
// 跨页题目:检查该 GroupId 下每个任务是否都有 Status=1 的答题记录
foreach (var task in relatedTasks)
{
var completedAnswer = userAnswers.FirstOrDefault(a => a.JournalPageTaskId == task.Id && a.Status == (int)UserAnswerStatusEnum.Complete);
if (completedAnswer == null)
{
result.FailItems.Add(new BatchClaimFailItem
{
GroupId = groupId,
Message = "跨页题目需要完成所有任务才能领取积分"
});
continue;
}
}
}
else
{
// 普通题目:只需要 Status=1 即可
if (userAnswers.FirstOrDefault()?.Status != (int)UserAnswerStatusEnum.Complete)
{
result.FailItems.Add(new BatchClaimFailItem
{
GroupId = groupId,
Message = "请先完成答题再领取积分"
});
continue;
}
}
// ============================================
// 2.5 重复领取校验
// ============================================
if (claimedGroupIds.Contains(groupId))
{
result.FailItems.Add(new BatchClaimFailItem
{
GroupId = groupId,
Message = "积分已领取,请勿重复领取"
});
continue;
}
// ============================================
// 2.6 计算积分、成长值、得分
// 【关键】跨页题目所有计算都只算一次(取 JournalPageTaskId == JournalPageTaskGroupId 的值)
// Score 取 JournalPageTaskId == JournalPageTaskGroupId 的分数,不是累加所有任务的分数
// ============================================
int points;
int growthPoints;
float score;
if (isCrossPage)
{
// 跨页题目:取 JournalPageTaskId == JournalPageTaskGroupId 的积分、成长值、得分
// mainTask 已经是 Id == GroupId 的任务,所以 mainTask.Id == groupId
points = mainTask.Points;
growthPoints = mainTask.GrowthPoint ?? 0;
// 跨页题目:取 JournalPageTaskId == JournalPageTaskGroupId 的分数
score = userAnswers.FirstOrDefault(a => a.JournalPageTaskId == groupId)?.Score ?? 0;
}
else
{
// 普通题目:正常计算
points = mainTask.Points;
growthPoints = mainTask.GrowthPoint ?? 0;
score = userAnswers.FirstOrDefault()?.Score ?? 0;
}
if (points <= 0)
{
result.FailItems.Add(new BatchClaimFailItem
{
GroupId = groupId,
Message = "该任务无积分可领取"
});
continue;
}
// 获取任务名称
var taskName = mainTask.Task ?? string.Empty;
// 调用积分服务增加积分
var addResult = await pointsService.AddPointsAsync(new AddPointsInput
{
UserId = userId,
Amount = points,
ChangeType = PointsChangeTypeEnum.TaskReward,
RelatedId = groupId,
Description = $"完成任务奖励:{taskName}",
OperatorName = "系统"
});
// 累计总积分和总成长值
totalPoints += points;
totalGrowthPoints += growthPoints;
currentBalance = addResult.NewBalance;
result.SuccessCount++;
// 添加成功详情到成功列表
result.SuccessItems.Add(new BatchClaimSuccessItem
{
GroupId = groupId,
TaskName = taskName,
Points = points,
GrowthPoints = growthPoints,
Score = score
});
}
catch (Exception ex)
{
// 捕获异常并添加到失败列表,不中断其他任务的处理
result.FailItems.Add(new BatchClaimFailItem
{
GroupId = groupId,
Message = ex.Message
});
}
}
result.FailCount = result.FailItems.Count;
result.TotalPoints = totalPoints;
result.TotalGrowthPoints = totalGrowthPoints;
result.BalanceAfter = currentBalance;
return result;
}
/// <summary>
/// ============================================
/// 方法思路:获取杂志列表
/// 1. 查询所有已归档的杂志Status=Archive
/// 2. 查询用户已绑定的杂志ID列表
/// 3. 根据queryType过滤
/// - "Bound":只返回已绑定的杂志
/// - "Unbound":只返回未绑定的杂志
/// - 其他:返回所有杂志
/// 4. 返回每个杂志的ID、名称、标题、封面、是否已绑定
/// ============================================
/// </summary>
/// <param name="userId">用户ID</param>
/// <param name="queryType">查询类型Bound/Unbound/空(全部)</param>
/// <returns>杂志列表</returns>
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>
/// ============================================
/// 方法思路:新增学生
/// 1. 创建新的Users记录
/// 2. WxUserId设置为当前登录人IDwxUserId参数传入
/// 3. 初始化积分和成长值为0
/// 4. 用户类型设置为普通用户Normal
/// 5. 插入数据库并返回结果
/// ============================================
/// </summary>
/// <param name="input">新增学生输入姓名、头像URL</param>
/// <param name="wxUserId">当前登录人ID微信用户ID</param>
/// <returns>是否插入成功</returns>
public async Task<bool> AddStudentAsync(AddStudentInput input, long wxUserId)
{
// 创建新的学生记录
var student = new Users
{
WxUserId = wxUserId,
Name = input.Name,
AvatarUrl = input.AvatarUrl,
GrowthPoints = 0,
Points = 0,
Type = UsersTypeEnum.Normal,
IsLastOnline = false
};
// 插入数据库
return await usersRepository.InsertAsync(student);
}
}