新增微信接口以及一些问天
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
using QYZH.InteractiveMagazine.Models.Dto.Journal;
|
using QYZH.InteractiveMagazine.Models.Dto.Journal;
|
||||||
using QYZH.InteractiveMagazine.Models.Dto.DotMatrix;
|
using QYZH.InteractiveMagazine.Models.Dto.DotMatrix;
|
||||||
using QYZH.InteractiveMagazine.Models.Enum;
|
using QYZH.InteractiveMagazine.Models.Enum;
|
||||||
|
|||||||
81
QYZH.InteractiveMagazine.IService/IUserAnswerTaskService.cs
Normal file
81
QYZH.InteractiveMagazine.IService/IUserAnswerTaskService.cs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
using QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Entity;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.IService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户作答任务服务接口
|
||||||
|
/// </summary>
|
||||||
|
public interface IUserAnswerTaskService : IBaseService<JournalPageTaskUserAnswer>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取用户期刊学习进度列表(按期刊分组)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">用户Id</param>
|
||||||
|
/// <returns>按期刊分组的学习进度数据</returns>
|
||||||
|
Task<List<JournalProgressOutput>> GetJournalProgressAsync(long userId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取目录列表(按一级目录分组,二级目录含完成状态)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="journalId">期刊Id</param>
|
||||||
|
/// <param name="userId">用户Id</param>
|
||||||
|
/// <returns>按一级目录分组的目录列表</returns>
|
||||||
|
Task<List<CatalogListOutput>> GetCatalogListAsync(long journalId, long userId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取一级目录概览(含二级目录详情、页码范围、任务完成状态)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="catalogId">一级目录Id(JournalCatalogId)</param>
|
||||||
|
/// <param name="userId">用户Id</param>
|
||||||
|
/// <returns>一级目录概览数据</returns>
|
||||||
|
Task<CatalogOverviewOutput> GetCatalogOverviewAsync(long catalogId, long userId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取用户答题任务列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="catalogId">二级目录Id(JournalCatalogId)</param>
|
||||||
|
/// <param name="userId">用户Id</param>
|
||||||
|
/// <returns>答题任务列表</returns>
|
||||||
|
Task<UserAnswerTaskListOutput> GetUserAnswerTaskListAsync(long catalogId, long userId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取跨页题目详情
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="journalPageTaskId">任务Id</param>
|
||||||
|
/// <param name="userId">用户Id</param>
|
||||||
|
/// <returns>跨页题目详情</returns>
|
||||||
|
Task<CrossPageTaskOutput> GetCrossPageTaskAsync(long journalPageTaskId, long userId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 领取任务积分
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="journalPageTaskId">任务Id</param>
|
||||||
|
/// <param name="userId">用户Id</param>
|
||||||
|
/// <returns>领取结果</returns>
|
||||||
|
Task<ClaimTaskPointsOutput> ClaimTaskPointsAsync(long journalPageTaskId, long userId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量领取任务积分(一键领取)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">批量领取参数</param>
|
||||||
|
/// <param name="userId">用户Id</param>
|
||||||
|
/// <returns>批量领取结果</returns>
|
||||||
|
Task<BatchClaimPointsOutput> BatchClaimPointsAsync(BatchClaimPointsInput input, long userId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取杂志列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">用户Id</param>
|
||||||
|
/// <param name="queryType">查询类型:Bound=已绑定,Unbound=未绑定(添加书籍用),默认是:Bound</param>
|
||||||
|
/// <returns>杂志列表</returns>
|
||||||
|
Task<List<JournalListOutput>> GetJournalListAsync(long userId, string? queryType);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 新增学生
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">新增学生参数</param>
|
||||||
|
/// <param name="userId">当前登录用户Id(WxUserId)</param>
|
||||||
|
/// <returns>新增学生结果</returns>
|
||||||
|
Task<bool> AddStudentAsync(AddStudentInput input, long userId);
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 新增学生输入
|
||||||
|
/// </summary>
|
||||||
|
public class AddStudentInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 昵称
|
||||||
|
/// </summary>
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 头像地址
|
||||||
|
/// </summary>
|
||||||
|
public string? AvatarUrl { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 新增学生输出
|
||||||
|
/// </summary>
|
||||||
|
public class AddStudentOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 学生Id
|
||||||
|
/// </summary>
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关联微信用户Id
|
||||||
|
/// </summary>
|
||||||
|
public long WxUserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 昵称
|
||||||
|
/// </summary>
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 头像地址
|
||||||
|
/// </summary>
|
||||||
|
public string? AvatarUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户类型
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; set; } = "Normal";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前成长值
|
||||||
|
/// </summary>
|
||||||
|
public int GrowthPoints { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 积分余额
|
||||||
|
/// </summary>
|
||||||
|
public int Points { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量领取任务积分输入
|
||||||
|
/// </summary>
|
||||||
|
public class BatchClaimPointsInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 任务分组Id列表(JournalPageTaskGroupId)
|
||||||
|
/// </summary>
|
||||||
|
public List<long> GroupIds { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量领取任务积分输出
|
||||||
|
/// </summary>
|
||||||
|
public class BatchClaimPointsOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 成功领取数量
|
||||||
|
/// </summary>
|
||||||
|
public int SuccessCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 失败数量
|
||||||
|
/// </summary>
|
||||||
|
public int FailCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 累计获得积分
|
||||||
|
/// </summary>
|
||||||
|
public int TotalPoints { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 领取后积分余额
|
||||||
|
/// </summary>
|
||||||
|
public int BalanceAfter { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 失败详情
|
||||||
|
/// </summary>
|
||||||
|
public List<BatchClaimFailItem> FailItems { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量领取失败项
|
||||||
|
/// </summary>
|
||||||
|
public class BatchClaimFailItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 任务分组Id
|
||||||
|
/// </summary>
|
||||||
|
public long GroupId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 失败原因
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 目录列表输出(按一级目录分组)
|
||||||
|
/// </summary>
|
||||||
|
public class CatalogListOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 一级目录ID
|
||||||
|
/// </summary>
|
||||||
|
public long CatalogId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 一级目录名称
|
||||||
|
/// </summary>
|
||||||
|
public string CatalogName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 杂志名称
|
||||||
|
/// </summary>
|
||||||
|
public string JournalName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文章篇数
|
||||||
|
/// </summary>
|
||||||
|
public int ArticleCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 二级栏目列表
|
||||||
|
/// </summary>
|
||||||
|
public List<SecondCatalogOutput> Children { get; set; } = new();
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 一级目录概览输出(含二级目录详情列表)
|
||||||
|
/// </summary>
|
||||||
|
public class CatalogOverviewOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 一级目录ID
|
||||||
|
/// </summary>
|
||||||
|
public long CatalogId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 一级目录名称
|
||||||
|
/// </summary>
|
||||||
|
public string FirstLevelCatalogName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文章篇数(该一级目录下 JournalPage 的个数)
|
||||||
|
/// </summary>
|
||||||
|
public int PageCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总任务数(该一级目录下所有 JournalPageTask 的条数)
|
||||||
|
/// </summary>
|
||||||
|
public int TotalTaskCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 二级目录详情列表
|
||||||
|
/// </summary>
|
||||||
|
public List<SubCatalogDetailOutput> SubCatalogs { get; set; } = new();
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 目录任务详情输出(含任务列表和徽章数)
|
||||||
|
/// </summary>
|
||||||
|
public class CatalogTaskDetailOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 目录ID
|
||||||
|
/// </summary>
|
||||||
|
public long CatalogId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 目录名称
|
||||||
|
/// </summary>
|
||||||
|
public string CatalogName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 杂志起始页码
|
||||||
|
/// </summary>
|
||||||
|
public int StartPage { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 杂志结束页码
|
||||||
|
/// </summary>
|
||||||
|
public int EndPage { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 徽章个数
|
||||||
|
/// </summary>
|
||||||
|
public int MedalCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务列表
|
||||||
|
/// </summary>
|
||||||
|
public List<TaskStatusOutput> Tasks { get; set; } = new();
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 领取任务积分输出
|
||||||
|
/// </summary>
|
||||||
|
public class ClaimTaskPointsOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 积分流水记录Id
|
||||||
|
/// </summary>
|
||||||
|
public long RecordId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作前积分余额
|
||||||
|
/// </summary>
|
||||||
|
public int PreviousBalance { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作后积分余额
|
||||||
|
/// </summary>
|
||||||
|
public int NewBalance { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 实际增加数量
|
||||||
|
/// </summary>
|
||||||
|
public int AddedAmount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务名称
|
||||||
|
/// </summary>
|
||||||
|
public string TaskName { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 跨页题目详情输出
|
||||||
|
/// </summary>
|
||||||
|
public class CrossPageTaskOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 栏目名称
|
||||||
|
/// </summary>
|
||||||
|
public string CatalogName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务名称
|
||||||
|
/// </summary>
|
||||||
|
public string TaskName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 图片数组(已答题用PageAnswerUrl,未答题用TaskUrl)
|
||||||
|
/// </summary>
|
||||||
|
public List<string> Images { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否跨页题目
|
||||||
|
/// </summary>
|
||||||
|
public bool IsCrossPage { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 杂志列表输出
|
||||||
|
/// </summary>
|
||||||
|
public class JournalListOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 杂志Id
|
||||||
|
/// </summary>
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 杂志名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 杂志标题
|
||||||
|
/// </summary>
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 封面
|
||||||
|
/// </summary>
|
||||||
|
public string Cover { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否已绑定
|
||||||
|
/// </summary>
|
||||||
|
public bool IsBound { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户期刊学习进度输出(按期刊分组)
|
||||||
|
/// </summary>
|
||||||
|
public class JournalProgressOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 期刊ID
|
||||||
|
/// </summary>
|
||||||
|
public long JournalId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 期刊名称
|
||||||
|
/// </summary>
|
||||||
|
public string JournalName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 栏目数(一级目录个数)
|
||||||
|
/// </summary>
|
||||||
|
public int CatalogCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务总数(该期刊下 JournalPageTask 的条数)
|
||||||
|
/// </summary>
|
||||||
|
public int TotalTaskCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已完成任务数(按 JournalPageTaskGroupId 去重,同一题跨页算1个)
|
||||||
|
/// </summary>
|
||||||
|
public int CompletedTaskCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 成长值(已完成任务的 GrowthPoint 累加)
|
||||||
|
/// </summary>
|
||||||
|
public int TotalGrowthPoints { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 积分值(已完成任务的 Points 累加)
|
||||||
|
/// </summary>
|
||||||
|
public int TotalPoints { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 进度百分比(已完成任务ID数 / 总任务数 × 100)
|
||||||
|
/// </summary>
|
||||||
|
public double Progress { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 剩余篇数(该期刊总页数 - 用户已作答页数)
|
||||||
|
/// </summary>
|
||||||
|
public int RemainingPages { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 连续打卡天数
|
||||||
|
/// </summary>
|
||||||
|
public int ConsecutiveDays { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 二级栏目输出
|
||||||
|
/// </summary>
|
||||||
|
public class SecondCatalogOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 二级栏目ID
|
||||||
|
/// </summary>
|
||||||
|
public long CatalogId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 二级栏目名称
|
||||||
|
/// </summary>
|
||||||
|
public string CatalogName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总积分值
|
||||||
|
/// </summary>
|
||||||
|
public int TotalPoints { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总成长值
|
||||||
|
/// </summary>
|
||||||
|
public int TotalGrowthPoints { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 状态:未开始 / 进行中 / 已完成
|
||||||
|
/// </summary>
|
||||||
|
public string Status { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 二级目录详情输出(含页码范围、任务数、完成状态)
|
||||||
|
/// </summary>
|
||||||
|
public class SubCatalogDetailOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 二级目录ID
|
||||||
|
/// </summary>
|
||||||
|
public long CatalogId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 二级目录名字
|
||||||
|
/// </summary>
|
||||||
|
public string SecondLevelCatalogName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 起始页码(杂志第几页开始)
|
||||||
|
/// </summary>
|
||||||
|
public int StartPage { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 结束页码(杂志第几页结束)
|
||||||
|
/// </summary>
|
||||||
|
public int EndPage { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务数量
|
||||||
|
/// </summary>
|
||||||
|
public int TaskCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 状态描述(格式如:1/2 进行中、2/2 已完成、0/2 未开始)
|
||||||
|
/// </summary>
|
||||||
|
public string Status { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务状态输出
|
||||||
|
/// </summary>
|
||||||
|
public class TaskStatusOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 任务ID
|
||||||
|
/// </summary>
|
||||||
|
public long TaskId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务名字
|
||||||
|
/// </summary>
|
||||||
|
public string TaskName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务状态:未开始 / 进行中 / 已完成
|
||||||
|
/// </summary>
|
||||||
|
public string Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 答题时间(分钟,由 AnswerSeconds 累加后转换)
|
||||||
|
/// </summary>
|
||||||
|
public double AnswerTime { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户答题任务列表输出
|
||||||
|
/// </summary>
|
||||||
|
public class UserAnswerTaskListOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 目录名称
|
||||||
|
/// </summary>
|
||||||
|
public string CatalogName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开始页码
|
||||||
|
/// </summary>
|
||||||
|
public int StartPage { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 结束页码
|
||||||
|
/// </summary>
|
||||||
|
public int EndPage { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务名称列表
|
||||||
|
/// </summary>
|
||||||
|
public List<string> TaskNames { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务状态(进行中、已完成、未开始)
|
||||||
|
/// </summary>
|
||||||
|
public string Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 答题时间(分钟)
|
||||||
|
/// </summary>
|
||||||
|
public double AnswerMinutes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 徽章个数
|
||||||
|
/// </summary>
|
||||||
|
public int MedalCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总成长值
|
||||||
|
/// </summary>
|
||||||
|
public int TotalGrowthPoints { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总积分
|
||||||
|
/// </summary>
|
||||||
|
public int TotalPoints { get; set; }
|
||||||
|
}
|
||||||
@ -1,7 +1,4 @@
|
|||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.Models.Entity
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
{
|
{
|
||||||
@ -9,208 +6,203 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
|||||||
///杂志任务用户作答记录;
|
///杂志任务用户作答记录;
|
||||||
///</summary>
|
///</summary>
|
||||||
[SugarTable("JournalPageTaskUserAnswer")]
|
[SugarTable("JournalPageTaskUserAnswer")]
|
||||||
public partial class JournalPageTaskUserAnswer : SqlSugarBaseEntity
|
public class JournalPageTaskUserAnswer : SqlSugarBaseEntity
|
||||||
{
|
{
|
||||||
public JournalPageTaskUserAnswer(){
|
/// <summary>
|
||||||
|
/// Desc:书id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long JournalId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:书页id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long JournalPageId { get; set; }
|
||||||
|
|
||||||
}
|
/// <summary>
|
||||||
|
/// Desc:任务id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long JournalPageTaskId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:书id
|
/// Desc:任务分组id
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:False
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long JournalId {get;set;}
|
public long JournalPageTaskGroupId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:书页id
|
/// Desc:用户id
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:False
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long JournalPageId {get;set;}
|
public long UserId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:任务id
|
/// Desc:作答结果
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:False
|
/// Nullable:True
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long JournalPageTaskId {get;set;}
|
public string Result { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:任务分组id
|
/// Desc:积分
|
||||||
/// Default:
|
/// Default:0
|
||||||
/// Nullable:False
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long JournalPageTaskGroupId {get;set;}
|
public float Points { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:用户id
|
/// Desc:成长值
|
||||||
/// Default:
|
/// Default:0
|
||||||
/// Nullable:False
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long UserId {get;set;}
|
public float GrowthPoints { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:作答结果
|
/// Desc:题目作答图片地址
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:True
|
/// Nullable:True
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Result {get;set;}
|
public string QuestionAnswerUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:积分
|
/// Desc:答案图片地址
|
||||||
/// Default:0
|
/// Default:
|
||||||
/// Nullable:False
|
/// Nullable:True
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float Points {get;set;}
|
public string AnswerUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:成长值
|
/// Desc:页面作答图片地址
|
||||||
/// Default:0
|
/// Default:
|
||||||
/// Nullable:False
|
/// Nullable:True
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float GrowthPoints {get;set;}
|
public string PageAnswerUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:题目作答图片地址
|
/// Desc:乐观锁
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:True
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string QuestionAnswerUrl {get;set;}
|
public int Revision { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:答案图片地址
|
/// Desc:作答状态
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:True
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string AnswerUrl {get;set;}
|
public int AnswerStatus { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:页面作答图片地址
|
/// Desc:作答开始时间
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:True
|
/// Nullable:True
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string PageAnswerUrl {get;set;}
|
public DateTime AnswerStartTime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:乐观锁
|
/// Desc:作答结束时间
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:False
|
/// Nullable:True
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Revision {get;set;}
|
public DateTime AnswerEndTime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:作答状态
|
/// Desc:作答耗时(秒)
|
||||||
/// Default:
|
/// Default:0
|
||||||
/// Nullable:False
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int AnswerStatus {get;set;}
|
public int AnswerSeconds { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:作答开始时间
|
/// Desc:图像识别结果
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:True
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime AnswerStartTime {get;set;}
|
public int ImageRecognition { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:作答结束时间
|
/// Desc:书页序号
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:True
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime AnswerEndTime {get;set;}
|
public int JournalPageNum { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:作答耗时(秒)
|
/// Desc:修改次数
|
||||||
/// Default:0
|
/// Default:
|
||||||
/// Nullable:False
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int AnswerSeconds {get;set;}
|
public int Modify { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:图像识别结果
|
/// Desc:最后标签id
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:False
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int ImageRecognition {get;set;}
|
public long LastTag { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:书页序号
|
/// Desc:点读书页序号
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:False
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int JournalPageNum {get;set;}
|
public int DotPageNum { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:修改次数
|
/// Desc:页面结果图片地址
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:False
|
/// Nullable:True
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Modify {get;set;}
|
public string PageResultUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:最后标签id
|
/// Desc:题型
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:False
|
/// Nullable:True
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long LastTag {get;set;}
|
public string Type { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:点读书页序号
|
/// Desc:点读书页编号
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:False
|
/// Nullable:True
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int DotPageNum {get;set;}
|
public string DotPageNo { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:页面结果图片地址
|
/// Desc:页面作答点读图片地址
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:True
|
/// Nullable:True
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string PageResultUrl {get;set;}
|
public string PageAnswerDotUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:题型
|
/// Desc:中断次数
|
||||||
/// Default:
|
/// Default:0
|
||||||
/// Nullable:True
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Type {get;set;}
|
public int BreakCount { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:点读书页编号
|
/// Desc:中断时间记录
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:True
|
/// Nullable:True
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string DotPageNo {get;set;}
|
public string BreakTimes { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:页面作答点读图片地址
|
/// Desc:作业状态
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:True
|
/// Nullable:True
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string PageAnswerDotUrl {get;set;}
|
public string AssignmentStatus { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Desc:中断次数
|
|
||||||
/// Default:0
|
|
||||||
/// Nullable:False
|
|
||||||
/// </summary>
|
|
||||||
public int BreakCount {get;set;}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Desc:中断时间记录
|
|
||||||
/// Default:
|
|
||||||
/// Nullable:True
|
|
||||||
/// </summary>
|
|
||||||
public string BreakTimes {get;set;}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Desc:作业状态
|
|
||||||
/// Default:
|
|
||||||
/// Nullable:True
|
|
||||||
/// </summary>
|
|
||||||
public string AssignmentStatus {get;set;}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
|||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:False
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int MedalId {get;set;}
|
public long MedalId {get;set;}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:获得时间
|
/// Desc:获得时间
|
||||||
|
|||||||
21
QYZH.InteractiveMagazine.Models/Enum/UserAnswerStatusEnum.cs
Normal file
21
QYZH.InteractiveMagazine.Models/Enum/UserAnswerStatusEnum.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Enum;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 学生答题的完成状态:0-进行中,1-已完成,未开始(没有数据)
|
||||||
|
/// </summary>
|
||||||
|
public enum UserAnswerStatusEnum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 进行中
|
||||||
|
/// </summary>
|
||||||
|
[Description("进行中")]
|
||||||
|
Processing = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已完成
|
||||||
|
/// </summary>
|
||||||
|
[Description("已完成")]
|
||||||
|
Complete = 1,
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
using Mapster;
|
using Mapster;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using QYZH.InteractiveMagazine.Common.Extensions;
|
using QYZH.InteractiveMagazine.Common.Extensions;
|
||||||
using QYZH.InteractiveMagazine.Infrastructure.OSS;
|
using QYZH.InteractiveMagazine.Infrastructure.OSS;
|
||||||
|
|||||||
946
QYZH.InteractiveMagazine.Service/UserAnswerTaskService.cs
Normal file
946
QYZH.InteractiveMagazine.Service/UserAnswerTaskService.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,326 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using QYZH.InteractiveMagazine.IService;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Common;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Dto;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.WebApi.Controllers.WeChat;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 小程序首页控制器
|
||||||
|
/// </summary>
|
||||||
|
public class UserAnswerTaskController : WeChatBaseController
|
||||||
|
{
|
||||||
|
private readonly IUserAnswerTaskService _userAnswerTaskService;
|
||||||
|
private readonly ILogger<UserAnswerTaskController> _logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造函数
|
||||||
|
/// </summary>
|
||||||
|
public UserAnswerTaskController(
|
||||||
|
IUserAnswerTaskService userAnswerTaskService,
|
||||||
|
ILogger<UserAnswerTaskController> logger)
|
||||||
|
{
|
||||||
|
_userAnswerTaskService = userAnswerTaskService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前用户的期刊学习进度列表
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>按期刊分组的学习进度数据</returns>
|
||||||
|
[HttpGet("progress")]
|
||||||
|
public async Task<BaseResponse<List<JournalProgressOutput>>> GetProgressAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == 0)
|
||||||
|
{
|
||||||
|
return BaseResponse<List<JournalProgressOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _userAnswerTaskService.GetJournalProgressAsync(userId);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "获取学习进度失败,UserId: {UserId}", GetCurrentUserId());
|
||||||
|
return BaseResponse<List<JournalProgressOutput>>.Fail("获取学习进度失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取目录列表(按一级目录分组,二级目录含完成状态)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="journalId">期刊Id</param>
|
||||||
|
/// <returns>按一级目录分组的目录列表</returns>
|
||||||
|
[HttpGet("catalog-list/{journalId:long}")]
|
||||||
|
public async Task<BaseResponse<List<CatalogListOutput>>> GetCatalogListAsync([Required] long journalId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == 0)
|
||||||
|
{
|
||||||
|
return BaseResponse<List<CatalogListOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (journalId <= 0)
|
||||||
|
{
|
||||||
|
return BaseResponse<List<CatalogListOutput>>.Fail(ResultCode.PARAM_ERROR, "期刊Id不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _userAnswerTaskService.GetCatalogListAsync(journalId, userId);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "获取目录列表失败,JournalId: {JournalId}, UserId: {UserId}", journalId, GetCurrentUserId());
|
||||||
|
return BaseResponse<List<CatalogListOutput>>.Fail("获取目录列表失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取一级目录概览(含二级目录详情、页码范围、任务完成状态)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="catalogId">一级目录Id</param>
|
||||||
|
/// <returns>一级目录概览数据</returns>
|
||||||
|
[HttpGet("catalog-overview/{catalogId:long}")]
|
||||||
|
public async Task<BaseResponse<CatalogOverviewOutput>> GetCatalogOverviewAsync([Required] long catalogId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == 0)
|
||||||
|
{
|
||||||
|
return BaseResponse<CatalogOverviewOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (catalogId <= 0)
|
||||||
|
{
|
||||||
|
return BaseResponse<CatalogOverviewOutput>.Fail(ResultCode.PARAM_ERROR, "目录Id不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _userAnswerTaskService.GetCatalogOverviewAsync(catalogId, userId);
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
return BaseResponse<CatalogOverviewOutput>.Fail("未找到该目录");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "获取目录概览失败,CatalogId: {CatalogId}, UserId: {UserId}", catalogId, GetCurrentUserId());
|
||||||
|
return BaseResponse<CatalogOverviewOutput>.Fail("获取目录概览失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取用户答题任务列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="catalogId">二级目录Id</param>
|
||||||
|
/// <returns>答题任务列表</returns>
|
||||||
|
[HttpGet("task-list/{catalogId:long}")]
|
||||||
|
public async Task<BaseResponse<UserAnswerTaskListOutput>> GetTaskListAsync([Required] long catalogId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == 0)
|
||||||
|
{
|
||||||
|
return BaseResponse<UserAnswerTaskListOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (catalogId <= 0)
|
||||||
|
{
|
||||||
|
return BaseResponse<UserAnswerTaskListOutput>.Fail(ResultCode.PARAM_ERROR, "目录Id不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _userAnswerTaskService.GetUserAnswerTaskListAsync(catalogId, userId);
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
return BaseResponse<UserAnswerTaskListOutput>.Fail("未找到该目录");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "获取任务列表失败,CatalogId: {CatalogId}, UserId: {UserId}", catalogId, GetCurrentUserId());
|
||||||
|
return BaseResponse<UserAnswerTaskListOutput>.Fail("获取任务列表失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取跨页题目详情
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="journalPageTaskId">任务Id</param>
|
||||||
|
/// <returns>跨页题目详情</returns>
|
||||||
|
[HttpGet("cross-page-task/{journalPageTaskId:long}")]
|
||||||
|
public async Task<BaseResponse<CrossPageTaskOutput>> GetCrossPageTaskAsync([Required] long journalPageTaskId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == 0)
|
||||||
|
{
|
||||||
|
return BaseResponse<CrossPageTaskOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (journalPageTaskId <= 0)
|
||||||
|
{
|
||||||
|
return BaseResponse<CrossPageTaskOutput>.Fail(ResultCode.PARAM_ERROR, "任务Id不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _userAnswerTaskService.GetCrossPageTaskAsync(journalPageTaskId, userId);
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
return BaseResponse<CrossPageTaskOutput>.Fail("未找到该任务");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "获取跨页题目失败,JournalPageTaskId: {JournalPageTaskId}, UserId: {UserId}", journalPageTaskId, GetCurrentUserId());
|
||||||
|
return BaseResponse<CrossPageTaskOutput>.Fail("获取跨页题目失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 领取任务积分
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="journalPageTaskId">任务Id</param>
|
||||||
|
/// <returns>领取结果</returns>
|
||||||
|
[HttpPost("claim-points/{journalPageTaskId:long}")]
|
||||||
|
public async Task<BaseResponse<ClaimTaskPointsOutput>> ClaimTaskPointsAsync([Required] long journalPageTaskId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == 0)
|
||||||
|
{
|
||||||
|
return BaseResponse<ClaimTaskPointsOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (journalPageTaskId <= 0)
|
||||||
|
{
|
||||||
|
return BaseResponse<ClaimTaskPointsOutput>.Fail(ResultCode.PARAM_ERROR, "任务Id不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _userAnswerTaskService.ClaimTaskPointsAsync(journalPageTaskId, userId);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
catch (BusinessException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "领取任务积分业务异常,JournalPageTaskId: {JournalPageTaskId}, UserId: {UserId}", journalPageTaskId, GetCurrentUserId());
|
||||||
|
return BaseResponse<ClaimTaskPointsOutput>.Fail(ex.Message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "领取任务积分失败,JournalPageTaskId: {JournalPageTaskId}, UserId: {UserId}", journalPageTaskId, GetCurrentUserId());
|
||||||
|
return BaseResponse<ClaimTaskPointsOutput>.Fail("领取任务积分失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量领取任务积分(一键领取)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">批量领取参数</param>
|
||||||
|
/// <returns>批量领取结果</returns>
|
||||||
|
[HttpPost("batch-claim-points")]
|
||||||
|
public async Task<BaseResponse<BatchClaimPointsOutput>> BatchClaimPointsAsync([FromBody] BatchClaimPointsInput input)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == 0)
|
||||||
|
{
|
||||||
|
return BaseResponse<BatchClaimPointsOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input == null || input.GroupIds == null || input.GroupIds.Count == 0)
|
||||||
|
{
|
||||||
|
return BaseResponse<BatchClaimPointsOutput>.Fail(ResultCode.PARAM_ERROR, "任务分组Id列表不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _userAnswerTaskService.BatchClaimPointsAsync(input, userId);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
catch (BusinessException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "批量领取积分业务异常,UserId: {UserId}", GetCurrentUserId());
|
||||||
|
return BaseResponse<BatchClaimPointsOutput>.Fail(ex.Message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "批量领取积分失败,UserId: {UserId}", GetCurrentUserId());
|
||||||
|
return BaseResponse<BatchClaimPointsOutput>.Fail("批量领取积分失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取杂志列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="queryType">查询类型:Bound=已绑定,Unbound=未绑定(添加书籍用),默认是:Bound</param>
|
||||||
|
/// <returns>杂志列表</returns>
|
||||||
|
[HttpGet("journal-list")]
|
||||||
|
public async Task<BaseResponse<List<JournalListOutput>>> GetJournalListAsync([FromQuery] string? queryType = "Bound")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == 0)
|
||||||
|
{
|
||||||
|
return BaseResponse<List<JournalListOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _userAnswerTaskService.GetJournalListAsync(userId, queryType);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
catch (BusinessException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "查询杂志列表业务异常,UserId: {UserId}", GetCurrentUserId());
|
||||||
|
return BaseResponse<List<JournalListOutput>>.Fail(ex.Message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "查询杂志列表失败,UserId: {UserId}", GetCurrentUserId());
|
||||||
|
return BaseResponse<List<JournalListOutput>>.Fail("查询杂志列表失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 新增学生
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">新增学生参数</param>
|
||||||
|
/// <returns>新增学生结果</returns>
|
||||||
|
[HttpPost("add-student")]
|
||||||
|
public async Task<BaseResponse<bool>> AddStudentAsync([FromBody] AddStudentInput input)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == 0)
|
||||||
|
{
|
||||||
|
return BaseResponse<bool>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _userAnswerTaskService.AddStudentAsync(input, userId);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
catch (BusinessException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "新增学生业务异常,UserId: {UserId}", GetCurrentUserId());
|
||||||
|
return BaseResponse<bool>.Fail(ex.Message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "新增学生失败,UserId: {UserId}", GetCurrentUserId());
|
||||||
|
return BaseResponse<bool>.Fail("新增学生失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<key id="22eabfbc-9d6d-4d02-a009-12a7eb9886c2" version="1">
|
||||||
|
<creationDate>2026-06-24T08:30:52.1384894Z</creationDate>
|
||||||
|
<activationDate>2026-06-24T08:30:52.1128731Z</activationDate>
|
||||||
|
<expirationDate>2026-09-22T08:30:52.1128731Z</expirationDate>
|
||||||
|
<descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
|
||||||
|
<descriptor>
|
||||||
|
<encryption algorithm="AES_256_CBC" />
|
||||||
|
<validation algorithm="HMACSHA256" />
|
||||||
|
<masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
|
||||||
|
<!-- Warning: the key below is in an unencrypted form. -->
|
||||||
|
<value>pejyMxgU0u9FWIYfhFHKOIK2tiEFlkbgD4WVeVom3GNwAP1/JRCwVGkaqaceJzwpzVBcNpwM2JrunPK/KQeAfQ==</value>
|
||||||
|
</masterKey>
|
||||||
|
</descriptor>
|
||||||
|
</descriptor>
|
||||||
|
</key>
|
||||||
Reference in New Issue
Block a user