新增微信接口以及一些问天

This commit is contained in:
2026-06-24 16:32:59 +08:00
parent 16ad80f2a8
commit 00fa7a21f6
23 changed files with 2079 additions and 180 deletions

View File

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