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