Merge branch 'master' of http://8.137.159.94:3000/glz/QYZH.InteractiveMagazine
This commit is contained in:
@ -1314,9 +1314,7 @@ public class UserAnswerTaskService(
|
||||
var newGrowthPointsBalance = user.GrowthPoints + totalGrowthPoints;
|
||||
await answerRepository.Context.Updateable<Users>()
|
||||
.SetColumns(u => u.Points == newPointsBalance)
|
||||
.SetColumns(u => u.GrowthPoints == newGrowthPointsBalance)
|
||||
.SetColumns(u => u.UpdatedBy == userId.ToString())
|
||||
.SetColumns(u => u.UpdatedAt == DateTime.Now)
|
||||
.SetColumns(u => u.GrowthPoints == newGrowthPointsBalance)
|
||||
.Where(u => u.Id == userId)
|
||||
.ExecuteCommandAsync();
|
||||
|
||||
@ -1326,16 +1324,12 @@ public class UserAnswerTaskService(
|
||||
UserId = userId,
|
||||
ChangeAmount = totalPoints,
|
||||
BalanceAfter = newPointsBalance,
|
||||
ChangeType = PointsChangeTypeEnum.TaskReward.ToString(),
|
||||
ChangeType = nameof(PointsChangeTypeEnum.TaskReward),
|
||||
RelatedId = groupId,
|
||||
Description = $"完成任务奖励,GroupId:{mainTask.GroupId}",
|
||||
Type = PointsFlowTypeEnum.Income,
|
||||
Status = (int)PointsRecordStatusEnum.Success,
|
||||
IsDeleted = false,
|
||||
CreatedBy = "系统",
|
||||
CreatedAt = DateTime.Now,
|
||||
UpdatedBy = "系统",
|
||||
UpdatedAt = DateTime.Now
|
||||
Status = (int)PointsRecordStatusEnum.Success
|
||||
|
||||
};
|
||||
await answerRepository.Context.Insertable(pointsRecord).ExecuteCommandAsync();
|
||||
|
||||
@ -1438,7 +1432,7 @@ public class UserAnswerTaskService(
|
||||
// 3. 在内存中逐个校验每个 GroupId,筛选可领取的任务
|
||||
// 所有数据已在步骤2中一次性查询完成,这里只做内存筛选
|
||||
// ============================================
|
||||
var claimableTasks = new List<(long GroupId, int Points, int GrowthPoints, float Score, string TaskNo)>();
|
||||
var claimableTasks = new List<(long GroupId, int Points, int GrowthPoints, float Score)>();
|
||||
|
||||
foreach (var groupId in input.GroupIds)
|
||||
{
|
||||
@ -1449,8 +1443,7 @@ public class UserAnswerTaskService(
|
||||
{
|
||||
result.FailItems.Add(new BatchClaimFailItem
|
||||
{
|
||||
GroupId = groupId,
|
||||
TaskNo = string.Empty,
|
||||
GroupId = groupId,
|
||||
Message = "任务不存在"
|
||||
});
|
||||
continue;
|
||||
@ -1458,30 +1451,13 @@ public class UserAnswerTaskService(
|
||||
|
||||
// 3.2 判断是否跨页题目并找主任务
|
||||
bool isCrossPage = relatedTasks.Count > 1;
|
||||
var mainTask = relatedTasks.FirstOrDefault(t => t.Id == groupId);
|
||||
|
||||
// ============================================
|
||||
// 【任务编号计算】
|
||||
// 从 JournalPageTask.No 字段按 - 分割,取索引为 2 的部分,前面加"任务"
|
||||
// 如果找不到主任务,尝试从子任务中计算
|
||||
// ============================================
|
||||
string taskNo = string.Empty;
|
||||
var taskForNo = mainTask ?? relatedTasks.FirstOrDefault();
|
||||
if (taskForNo != null && !string.IsNullOrEmpty(taskForNo.No))
|
||||
{
|
||||
var parts = taskForNo.No.Split('-');
|
||||
if (parts.Length >= 3)
|
||||
{
|
||||
taskNo = $"任务{parts[2]}";
|
||||
}
|
||||
}
|
||||
var mainTask = relatedTasks.FirstOrDefault(t => t.Id == groupId);
|
||||
|
||||
if (mainTask == null)
|
||||
{
|
||||
result.FailItems.Add(new BatchClaimFailItem
|
||||
{
|
||||
GroupId = groupId,
|
||||
TaskNo = taskNo,
|
||||
GroupId = groupId,
|
||||
Message = "子题目无法单独领取积分,请从主任务入口领取"
|
||||
});
|
||||
continue;
|
||||
@ -1492,8 +1468,7 @@ public class UserAnswerTaskService(
|
||||
{
|
||||
result.FailItems.Add(new BatchClaimFailItem
|
||||
{
|
||||
GroupId = groupId,
|
||||
TaskNo = taskNo,
|
||||
GroupId = groupId,
|
||||
Message = "请先完成答题再领取积分"
|
||||
});
|
||||
continue;
|
||||
@ -1505,8 +1480,7 @@ public class UserAnswerTaskService(
|
||||
{
|
||||
result.FailItems.Add(new BatchClaimFailItem
|
||||
{
|
||||
GroupId = groupId,
|
||||
TaskNo = taskNo,
|
||||
GroupId = groupId,
|
||||
Message = "请先完成答题再领取积分"
|
||||
});
|
||||
continue;
|
||||
@ -1524,8 +1498,7 @@ public class UserAnswerTaskService(
|
||||
{
|
||||
result.FailItems.Add(new BatchClaimFailItem
|
||||
{
|
||||
GroupId = groupId,
|
||||
TaskNo = taskNo,
|
||||
GroupId = groupId,
|
||||
Message = "跨页题目需要完成所有任务才能领取积分"
|
||||
});
|
||||
continue;
|
||||
@ -1537,8 +1510,7 @@ public class UserAnswerTaskService(
|
||||
{
|
||||
result.FailItems.Add(new BatchClaimFailItem
|
||||
{
|
||||
GroupId = groupId,
|
||||
TaskNo = taskNo,
|
||||
GroupId = groupId,
|
||||
Message = "积分已领取,请勿重复领取"
|
||||
});
|
||||
continue;
|
||||
@ -1553,36 +1525,20 @@ public class UserAnswerTaskService(
|
||||
{
|
||||
result.FailItems.Add(new BatchClaimFailItem
|
||||
{
|
||||
GroupId = groupId,
|
||||
TaskNo = taskNo,
|
||||
GroupId = groupId,
|
||||
Message = "该任务无积分可领取"
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// 添加到可领取列表
|
||||
claimableTasks.Add((groupId, points, growthPoints, score, taskNo));
|
||||
claimableTasks.Add((groupId, points, growthPoints, score));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 异常情况下,尝试从已查询的数据中计算 taskNo
|
||||
string taskNo = string.Empty;
|
||||
if (tasksByGroupId.TryGetValue(groupId, out var tasks) && tasks.Count > 0)
|
||||
{
|
||||
var task = tasks.FirstOrDefault(t => t.Id == groupId) ?? tasks.FirstOrDefault();
|
||||
if (task != null && !string.IsNullOrEmpty(task.No))
|
||||
{
|
||||
var parts = task.No.Split('-');
|
||||
if (parts.Length >= 3)
|
||||
{
|
||||
taskNo = $"任务{parts[2]}";
|
||||
}
|
||||
}
|
||||
}
|
||||
result.FailItems.Add(new BatchClaimFailItem
|
||||
{
|
||||
GroupId = groupId,
|
||||
TaskNo = taskNo,
|
||||
GroupId = groupId,
|
||||
Message = ex.Message
|
||||
});
|
||||
}
|
||||
@ -1593,6 +1549,7 @@ public class UserAnswerTaskService(
|
||||
// ============================================
|
||||
// 4. 事务执行所有领取操作
|
||||
// - 手动更新 Users.Points(积分累加)
|
||||
// - 手动更新 Users.GrowthPoints(成长值累加)
|
||||
// - 插入积分流水记录
|
||||
// - 喂养宠物(如有活跃宠物,更新宠物成长值)
|
||||
// ============================================
|
||||
@ -1622,8 +1579,7 @@ public class UserAnswerTaskService(
|
||||
|
||||
// 添加成功详情
|
||||
result.SuccessItems.Add(new BatchClaimSuccessItem
|
||||
{
|
||||
TaskNo = task.TaskNo,
|
||||
{
|
||||
Points = task.Points,
|
||||
GrowthPoints = task.GrowthPoints,
|
||||
Score = task.Score
|
||||
@ -1637,31 +1593,27 @@ public class UserAnswerTaskService(
|
||||
|
||||
await answerRepository.Context.Updateable<Users>()
|
||||
.SetColumns(u => u.Points == newPointsBalance)
|
||||
.SetColumns(u => u.GrowthPoints == newGrowthPointsBalance)
|
||||
.SetColumns(u => u.UpdatedBy == userId.ToString())
|
||||
.SetColumns(u => u.UpdatedAt == DateTime.Now)
|
||||
.SetColumns(u => u.GrowthPoints == newGrowthPointsBalance)
|
||||
.Where(u => u.Id == userId)
|
||||
.ExecuteCommandAsync();
|
||||
|
||||
// 4b. 批量插入积分流水记录
|
||||
var records = new List<PointsRecord>();
|
||||
var tempNewPointsBalance = user.Points;
|
||||
foreach (var task in claimableTasks)
|
||||
{
|
||||
tempNewPointsBalance += task.Points;
|
||||
// 计算每条记录的领取后余额
|
||||
records.Add(new PointsRecord
|
||||
{
|
||||
UserId = userId,
|
||||
ChangeAmount = task.Points,
|
||||
BalanceAfter = newPointsBalance,
|
||||
ChangeType = PointsChangeTypeEnum.TaskReward.ToString(),
|
||||
BalanceAfter = tempNewPointsBalance,
|
||||
ChangeType = nameof(PointsChangeTypeEnum.TaskReward),
|
||||
RelatedId = task.GroupId,
|
||||
Description = $"完成任务奖励,GroupId:{task.GroupId}",
|
||||
Type = PointsFlowTypeEnum.Income,
|
||||
Status = (int)PointsRecordStatusEnum.Success,
|
||||
IsDeleted = false,
|
||||
CreatedBy = "系统",
|
||||
CreatedAt = DateTime.Now,
|
||||
UpdatedBy = "系统",
|
||||
UpdatedAt = DateTime.Now
|
||||
});
|
||||
}
|
||||
await answerRepository.Context.Insertable(records).ExecuteCommandAsync();
|
||||
@ -1683,8 +1635,269 @@ public class UserAnswerTaskService(
|
||||
|
||||
#region 5. 组装返回结果
|
||||
result.FailCount = result.FailItems.Count;
|
||||
|
||||
for (int i = 0; i < result.SuccessItems.Count; i++)
|
||||
{
|
||||
result.SuccessItems[i].TaskNo ="任务" + (i + 1);
|
||||
}
|
||||
for (int i = 0; i < result.FailItems.Count; i++)
|
||||
{
|
||||
result.FailItems[i].TaskNo ="任务" + (i + 1);
|
||||
}
|
||||
return result;
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ============================================
|
||||
/// 方法思路:获取目录树形结构(一级目录和二级目录的完整树形结构,包含任务详情)
|
||||
/// 【核心逻辑】
|
||||
/// 1. 用户绑定验证:查询 UserJournal 表验证用户是否绑定该杂志
|
||||
/// 2. 批量预加载:一次性加载目录、页面、任务、用户作答记录,避免N+1查询
|
||||
/// 3. 一级目录统计:文章篇数(二级目录有题目页面数总和)、任务数(GroupId去重总和)
|
||||
/// 4. 二级目录构建:页码范围、任务数、状态、总成长值、总积分、任务列表
|
||||
/// 5. 任务详情:任务名称(取主任务Task字段)、状态、成长值、积分值、答题时长
|
||||
/// 【状态判定规则】
|
||||
/// - 未开始:无任何任务作答记录
|
||||
/// - 进行中:有作答记录但未全部完成
|
||||
/// - 已完成:所有任务都完成(Status=1)
|
||||
/// 【跨页题目处理】
|
||||
/// - 任务名称:取主任务(Id == GroupId)的Task字段
|
||||
/// - 成长值/积分:取主任务数据,按GroupId去重计算
|
||||
/// - 答题时长:该GroupId下所有任务的AnswerSeconds累加后转换为分钟
|
||||
/// ============================================
|
||||
/// </summary>
|
||||
/// <param name="journalId">期刊Id</param>
|
||||
/// <param name="userId">用户Id</param>
|
||||
/// <returns>目录树形结构数据</returns>
|
||||
public async Task<CatalogTreeOutput> GetCatalogTreeAsync(long journalId, long userId)
|
||||
{
|
||||
#region 0. 用户绑定验证
|
||||
|
||||
// 验证用户是否绑定该杂志,未绑定直接返回空结果
|
||||
var isBound = await userJournalRepository.Context.Queryable<UserJournal>()
|
||||
.Where(uj => uj.UserId == userId && uj.JournalId == journalId)
|
||||
.AnyAsync();
|
||||
|
||||
if (!isBound)
|
||||
{
|
||||
return new CatalogTreeOutput();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 1. 批量预加载关联数据
|
||||
|
||||
// 一次性加载所有关联数据,避免N+1查询
|
||||
var allCatalogs = await catalogRepository.Context.Queryable<JournalCatalog>()
|
||||
.Where(c => c.JournalId == journalId)
|
||||
.ToListAsync();
|
||||
|
||||
var allPages = await pageRepository.Context.Queryable<JournalPage>()
|
||||
.Where(p => p.JournalId == journalId)
|
||||
.ToListAsync();
|
||||
|
||||
var allTasks = await taskRepository.Context.Queryable<JournalPageTask>()
|
||||
.Where(t => t.JournalId == journalId)
|
||||
.ToListAsync();
|
||||
|
||||
var userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
|
||||
.Where(a => a.JournalId == journalId && a.UserId == userId)
|
||||
.ToListAsync();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 2. 构建用户作答状态集合
|
||||
|
||||
// 用户已作答的任务ID集合(包含进行中和已完成)
|
||||
var userAnsweredTaskIds = userAnswers.Select(a => a.JournalPageTaskId).ToHashSet();
|
||||
|
||||
// 用户已完成的任务ID集合(Status=1表示已批阅完成)
|
||||
var userCompletedTaskIds = userAnswers
|
||||
.Where(a => a.Status == (int)UserAnswerStatusEnum.Complete)
|
||||
.Select(a => a.JournalPageTaskId)
|
||||
.ToHashSet();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3. 构建树形结构
|
||||
|
||||
// 获取一级目录(ParentId=0),按排序字段排序
|
||||
var firstLevelCatalogs = allCatalogs.Where(c => c.ParentId == 0).OrderBy(c => c.Sort).ToList();
|
||||
var result = new CatalogTreeOutput();
|
||||
|
||||
foreach (var firstCatalog in firstLevelCatalogs)
|
||||
{
|
||||
int articleCount = 0;
|
||||
int taskCount = 0;
|
||||
var children = new List<SecondLevelCatalogItem>();
|
||||
|
||||
// 获取该一级目录下的二级目录,按排序字段排序
|
||||
var secondLevelCatalogs = allCatalogs
|
||||
.Where(c => c.ParentId == firstCatalog.Id)
|
||||
.OrderBy(c => c.Sort)
|
||||
.ToList();
|
||||
|
||||
foreach (var secondCatalog in secondLevelCatalogs)
|
||||
{
|
||||
// 获取二级目录下的所有页面,按页码排序
|
||||
var secondLevelPages = allPages
|
||||
.Where(p => p.JournalCatalogId == secondCatalog.Id)
|
||||
.OrderBy(p => p.PageNum)
|
||||
.ToList();
|
||||
|
||||
var secondLevelPageIds = secondLevelPages.Select(p => p.Id).ToHashSet();
|
||||
|
||||
// 统计有题目的页面数(用于文章篇数计算)
|
||||
var pagesWithTasks = allTasks
|
||||
.Where(t => secondLevelPageIds.Contains(t.JournalPageId))
|
||||
.Select(t => t.JournalPageId)
|
||||
.ToHashSet();
|
||||
|
||||
articleCount += pagesWithTasks.Count;
|
||||
|
||||
// 获取二级目录下的所有任务
|
||||
var secondLevelTasks = allTasks.Where(t => secondLevelPageIds.Contains(t.JournalPageId)).ToList();
|
||||
|
||||
// 按GroupId分组(跨页题目处理)
|
||||
var groupTasks = secondLevelTasks.GroupBy(t => t.GroupId).ToList();
|
||||
var catalogTaskCount = groupTasks.Count;
|
||||
|
||||
// 如果二级目录没有任务,则跳过不显示
|
||||
if (catalogTaskCount == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
taskCount += catalogTaskCount;
|
||||
|
||||
// 计算页码范围
|
||||
var startPage = secondLevelPages.Count > 0 ? secondLevelPages.Min(p => p.PageNum) : 0;
|
||||
var endPage = secondLevelPages.Count > 0 ? secondLevelPages.Max(p => p.PageNum) : 0;
|
||||
|
||||
// 计算二级目录状态
|
||||
var status = CalculateCatalogStatus(groupTasks, userAnsweredTaskIds, userCompletedTaskIds);
|
||||
|
||||
// 计算总成长值和总积分(按GroupId去重,取主任务数据)
|
||||
var (totalGrowthPoints, totalPoints) = CalculateCatalogPoints(groupTasks);
|
||||
|
||||
// 构建任务列表
|
||||
var taskItems = BuildTaskItems(groupTasks, userAnswers, userAnsweredTaskIds, userCompletedTaskIds);
|
||||
|
||||
// 添加二级目录项
|
||||
children.Add(new SecondLevelCatalogItem
|
||||
{
|
||||
CatalogId = secondCatalog.Id,
|
||||
CatalogName = secondCatalog.Name ?? string.Empty,
|
||||
StartPage = startPage,
|
||||
EndPage = endPage,
|
||||
TaskCount = catalogTaskCount,
|
||||
Status = status,
|
||||
TotalGrowthPoints = totalGrowthPoints,
|
||||
TotalPoints = totalPoints,
|
||||
Tasks = taskItems
|
||||
});
|
||||
}
|
||||
|
||||
// 添加一级目录项
|
||||
result.FirstLevelCatalogs.Add(new FirstLevelCatalogItem
|
||||
{
|
||||
CatalogId = firstCatalog.Id,
|
||||
CatalogName = firstCatalog.Name ?? string.Empty,
|
||||
ArticleCount = articleCount,
|
||||
TaskCount = taskCount,
|
||||
Children = children
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
#region 私有方法,逻辑处理
|
||||
|
||||
/// <summary>
|
||||
/// 计算目录状态(格式:已完成数/总数 状态描述)
|
||||
/// </summary>
|
||||
/// <param name="groupTasks">按GroupId分组的任务列表</param>
|
||||
/// <param name="userAnsweredTaskIds">用户已作答任务ID集合</param>
|
||||
/// <param name="userCompletedTaskIds">用户已完成任务ID集合</param>
|
||||
/// <returns>状态描述(格式:1/2 进行中、0/2 未开始、2/2 已完成)</returns>
|
||||
private static string CalculateCatalogStatus(List<IGrouping<long, JournalPageTask>> groupTasks, HashSet<long> userAnsweredTaskIds, HashSet<long> userCompletedTaskIds)
|
||||
{
|
||||
var catalogTaskCount = groupTasks.Count;
|
||||
|
||||
// 已完成的GroupId数量(该GroupId下所有任务都完成)
|
||||
var completedGroupCount = groupTasks.Count(g => g.All(t => userCompletedTaskIds.Contains(t.Id)));
|
||||
|
||||
// 已作答的GroupId数量(该GroupId下至少有一个任务被作答)
|
||||
var answeredGroupCount = groupTasks.Count(g => g.Any(t => userAnsweredTaskIds.Contains(t.Id)));
|
||||
|
||||
if (answeredGroupCount == 0)
|
||||
return $"0/{catalogTaskCount} 未开始";
|
||||
if (completedGroupCount == catalogTaskCount)
|
||||
return $"{completedGroupCount}/{catalogTaskCount} 已完成";
|
||||
return $"{completedGroupCount}/{catalogTaskCount} 进行中";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算目录总成长值和总积分(按GroupId去重)
|
||||
/// </summary>
|
||||
/// <param name="groupTasks">按GroupId分组的任务列表</param>
|
||||
/// <returns>总成长值和总积分</returns>
|
||||
private static (int TotalGrowthPoints, int TotalPoints) CalculateCatalogPoints(List<IGrouping<long, JournalPageTask>> groupTasks)
|
||||
{
|
||||
// 取每个GroupId的主任务(Id == GroupId),若不存在则取第一个任务
|
||||
var mainTasks = groupTasks.ConvertAll(g => g.FirstOrDefault(t => t.Id == g.Key) ?? g.First());
|
||||
return (mainTasks.Sum(t => t.GrowthPoint), mainTasks.Sum(t => t.Points));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建任务详情列表
|
||||
/// </summary>
|
||||
/// <param name="groupTasks">按GroupId分组的任务列表</param>
|
||||
/// <param name="userAnswers">用户作答记录</param>
|
||||
/// <param name="userAnsweredTaskIds">用户已作答任务ID集合</param>
|
||||
/// <param name="userCompletedTaskIds">用户已完成任务ID集合</param>
|
||||
/// <returns>任务详情列表</returns>
|
||||
private static List<CatalogTaskItem> BuildTaskItems(List<IGrouping<long, JournalPageTask>> groupTasks, List<JournalPageTaskUserAnswer> userAnswers, HashSet<long> userAnsweredTaskIds, HashSet<long> userCompletedTaskIds)
|
||||
{
|
||||
var taskItems = new List<CatalogTaskItem>();
|
||||
|
||||
foreach (var group in groupTasks)
|
||||
{
|
||||
var groupId = group.Key;
|
||||
// 取主任务(Id == GroupId),若不存在则取第一个任务
|
||||
var mainTask = group.FirstOrDefault(t => t.Id == groupId) ?? group.First();
|
||||
|
||||
// 计算该GroupId下所有任务的答题时间(秒 → 分钟)
|
||||
var taskIds = group.Select(t => t.Id).ToHashSet();
|
||||
var groupAnswerSeconds = userAnswers
|
||||
.Where(a => taskIds.Contains(a.JournalPageTaskId))
|
||||
.Sum(a => a.AnswerSeconds);
|
||||
var groupAnswerMinutes = Math.Round(groupAnswerSeconds / 60.0, 2);
|
||||
|
||||
// 判断任务状态
|
||||
bool hasAnswer = group.Any(t => userAnsweredTaskIds.Contains(t.Id));
|
||||
bool isAllCompleted = group.All(t => userCompletedTaskIds.Contains(t.Id));
|
||||
|
||||
string taskStatus = !hasAnswer ? "未开始" : (isAllCompleted ? "已完成" : "进行中");
|
||||
|
||||
taskItems.Add(new CatalogTaskItem
|
||||
{
|
||||
GroupId = groupId,
|
||||
TaskName = mainTask.Task ?? string.Empty,
|
||||
Status = taskStatus,
|
||||
GrowthPoints = mainTask.GrowthPoint,
|
||||
Points = mainTask.Points,
|
||||
AnswerMinutes = groupAnswerMinutes
|
||||
});
|
||||
}
|
||||
|
||||
return taskItems;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user