本次提交包含多方面更新: 1. 新增消息类型枚举、杂志标题字段与AI提示词状态管理 2. 重构杂志服务,新增创建杂志接口并统一OSS路径命名规范 3. 调整题库任务结构,将答案解析迁移至独立表并优化关联查询 4. 新增AI提示词无分页查询与状态切换接口 5. 精简任务添加输入DTO,移除冗余字段 6. 修复控制器路由命名大小写不一致问题
527 lines
20 KiB
C#
527 lines
20 KiB
C#
|
|
using Mapster;
|
|
using QYZH.InteractiveMagazine.Common.Extensions;
|
|
using QYZH.InteractiveMagazine.Infrastructure.OSS;
|
|
using QYZH.InteractiveMagazine.IService;
|
|
using QYZH.InteractiveMagazine.Models.Base;
|
|
using QYZH.InteractiveMagazine.Models.Common;
|
|
using QYZH.InteractiveMagazine.Models.Dto.Journal;
|
|
using QYZH.InteractiveMagazine.Models.Entity;
|
|
using QYZH.InteractiveMagazine.Models.Enum;
|
|
using QYZH.InteractiveMagazine.Repository;
|
|
using SqlSugar;
|
|
using Yitter.IdGenerator;
|
|
|
|
namespace QYZH.InteractiveMagazine.Service;
|
|
|
|
public class JournalCatalogService : BaseRepository<JournalCatalog>, IJournalCatalogService
|
|
{
|
|
private readonly BaseRepository<JournalPage> _JournalPageRepository;
|
|
private readonly BaseRepository<JournalPageTask> _JournalPageTaskRepository;
|
|
private readonly OssService _ossService;
|
|
|
|
public JournalCatalogService(BaseRepository<JournalPage> JournalPageRepository, BaseRepository<JournalPageTask> JournalPageTaskRepository, OssService ossService)
|
|
{
|
|
//_JournalRepository = JournalRepository;
|
|
_JournalPageRepository = JournalPageRepository;
|
|
_ossService = ossService;
|
|
}
|
|
|
|
public async Task<List<JournalCatalog>> DetailAsync(long JournalId)
|
|
{
|
|
return await base.Queryable().Where(w => w.ParentId == 0 && w.JournalId == JournalId).OrderBy(o => o.Sort).ToChildListAsync(it => it.ParentId, 0) ?? [];
|
|
}
|
|
|
|
public async Task<long> ImportAsync(JournalImportDto input)
|
|
{
|
|
var Journals = await Queryable<Journal>().Where(w => w.Id == input.JournalId).FirstAsync();
|
|
BusinessException.ThrowIf(Journals.IsNull(), "未找到书本");
|
|
|
|
var pageNum = await _JournalPageRepository.Queryable().Where(w => w.JournalId == input.JournalId).MaxAsync(m => m.PageNum);
|
|
BusinessException.ThrowIf(pageNum != 0, "已经存在书页");
|
|
|
|
|
|
var JournalCatalog = new JournalCatalog()
|
|
{
|
|
Id = YitIdHelper.NextId(),
|
|
JournalId = input.JournalId,
|
|
Name = "目录",
|
|
ParentId = 0,
|
|
Level = 0,
|
|
Sort = 1,
|
|
Type = JournalCatalogTypeEnum.Page,
|
|
};
|
|
|
|
int sort = 1;
|
|
var result = input.JournalCatalogs.Where(w => w.Type == 1).Select(s =>
|
|
{
|
|
var JournalPage = new JournalPage
|
|
{
|
|
Id = YitIdHelper.NextId(),
|
|
JournalId = input.JournalId,
|
|
JournalCatalogId = JournalCatalog.Id,
|
|
Sort = sort,
|
|
PageNum = sort < input.Index ? 0 : sort - (input.Index - 1)
|
|
};
|
|
var key = $"journal/{input.JournalId}/{JournalPage.Id}/page.{s.Url.ToExtension()}";
|
|
_ossService.CopyObject(s.Url.RemoveDomain(), key);
|
|
|
|
JournalPage.Url = key;
|
|
//dotMatrixPage.PreviewUrl = key;
|
|
|
|
sort = sort + 1;
|
|
return new
|
|
{
|
|
JournalPage = JournalPage
|
|
};
|
|
}).ToList();
|
|
|
|
await UseTranAsync(async () =>
|
|
{
|
|
|
|
var data = await base.InsertAsync(JournalCatalog);
|
|
|
|
var JournalPages = result.Select(x => x.JournalPage).ToList();
|
|
await _JournalPageRepository.InsertRangeAsync(JournalPages);
|
|
|
|
var key = $"journal/{input.JournalId}/Journal.pdf";
|
|
_ossService.CopyObject(input.PdfUrl.RemoveDomain(), key);
|
|
|
|
await Updateable<Journal>().SetColumns(s => s.PdfUrl, key)
|
|
.SetColumns(s => s.Status, JournalStatusEnum.Created)
|
|
.SetColumns(s => s.TotalPage, JournalPages.Count).Where(w => w.Id == input.JournalId)
|
|
.ExecuteCommandAsync();
|
|
});
|
|
|
|
return result.Count;
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// 获取书分类及页码
|
|
/// </summary>
|
|
/// <param name="JournalId"></param>
|
|
/// <returns></returns>
|
|
public async Task<List<JournalCatalogTreeListDto>> GetJournalCatalogListAsync(long JournalId)
|
|
{
|
|
var Journal = await Queryable<Journal>().Where(w => w.Id == JournalId).FirstAsync();
|
|
BusinessException.ThrowIf(Journal.IsNull(), "未找到书本");
|
|
|
|
var pageNumList = await _JournalPageRepository.Queryable()
|
|
.InnerJoin<JournalCatalog>((a, b) => a.JournalCatalogId == b.Id)
|
|
.Where((a, b) => a.JournalId == JournalId)
|
|
.OrderBy((a, b) => a.PageNum)
|
|
.Select((a, b) => new JournalCatalogTreeListDto
|
|
{
|
|
JournalId = a.JournalId,
|
|
Id = b.Id,
|
|
Name = b.Name,
|
|
PageNum = a.PageNum,
|
|
ParentName = SqlFunc.Subqueryable<JournalCatalog>().Where(c => c.Id == b.ParentId).Select(c => c.Name),
|
|
ParentId = b.ParentId,
|
|
PageId = a.Id,
|
|
})
|
|
.ToListAsync();
|
|
|
|
return pageNumList;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 获取书分类及页码
|
|
/// </summary>
|
|
/// <param name="JournalId"></param>
|
|
/// <returns></returns>
|
|
public async Task<List<IcrJournalCatalogTreeDto>> GetJournalCataloTreeAsync(long JournalId)
|
|
{
|
|
var Journal = await Queryable<Journal>().Where(w => w.Id == JournalId).FirstAsync();
|
|
BusinessException.ThrowIf(Journal.IsNull(), "未找到书本");
|
|
|
|
// 先查询所有目录
|
|
var allCatalogs = await base.Queryable()
|
|
.Where(w => w.JournalId == JournalId)
|
|
.OrderBy(o => o.Sort)
|
|
.ToListAsync();
|
|
|
|
if (!allCatalogs.Any())
|
|
return new List<IcrJournalCatalogTreeDto>();
|
|
|
|
// 再查询有页面对应的目录及页面信息
|
|
var pageNumList = await _JournalPageRepository.Queryable()
|
|
.InnerJoin<JournalCatalog>((a, b) => a.JournalCatalogId == b.Id)
|
|
.Where((a, b) => a.JournalId == JournalId)
|
|
.OrderBy((a, b) => a.PageNum)
|
|
.Select((a, b) => new JournalCatalogTreeListDto
|
|
{
|
|
JournalId = a.JournalId,
|
|
Id = b.Id,
|
|
Name = b.Name,
|
|
PageNum = a.PageNum,
|
|
ParentName = SqlFunc.Subqueryable<JournalCatalog>().Where(c => c.Id == b.ParentId).Select(c => c.Name),
|
|
ParentId = b.ParentId,
|
|
PageId = a.Id,
|
|
})
|
|
.ToListAsync();
|
|
|
|
var tree = BuildCatalogTree(allCatalogs, pageNumList);
|
|
|
|
return tree;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 构建目录树形结构(支持任意层级)
|
|
/// </summary>
|
|
/// <param name="allCatalogs">所有目录</param>
|
|
/// <param name="pageNumList">有页面对应的目录信息</param>
|
|
/// <returns></returns>
|
|
private List<IcrJournalCatalogTreeDto> BuildCatalogTree(List<JournalCatalog> allCatalogs, List<JournalCatalogTreeListDto> pageNumList)
|
|
{
|
|
// 构建有页面对应的目录到页面列表的映射
|
|
var catalogPagesMap = pageNumList
|
|
.GroupBy(x => x.Id)
|
|
.ToDictionary(
|
|
g => g.Key,
|
|
g => g.Select(p => new IcrJournalCatalogTreeDto
|
|
{
|
|
Id = p.PageId,
|
|
Name = $"第{p.PageNum}页",
|
|
Level = 0,
|
|
Child = new List<IcrJournalCatalogTreeDto>()
|
|
}).ToList()
|
|
);
|
|
|
|
// 构建所有目录节点字典
|
|
var catalogDict = new Dictionary<long, IcrJournalCatalogTreeDto>();
|
|
var rootCatalogIds = new HashSet<long>();
|
|
|
|
foreach (var catalog in allCatalogs)
|
|
{
|
|
var catalogId = catalog.Id;
|
|
var parentId = catalog.ParentId;
|
|
|
|
if (catalogDict.ContainsKey(catalogId))
|
|
continue;
|
|
|
|
var node = new IcrJournalCatalogTreeDto
|
|
{
|
|
Id = catalogId,
|
|
Name = catalog.Name,
|
|
Level = 0,
|
|
Child = catalogPagesMap.GetValueOrDefault(catalogId, new List<IcrJournalCatalogTreeDto>())
|
|
};
|
|
|
|
catalogDict[catalogId] = node;
|
|
|
|
if (parentId == 0)
|
|
{
|
|
rootCatalogIds.Add(catalogId);
|
|
}
|
|
}
|
|
|
|
// 构建树形结构:将子节点挂载到父节点
|
|
foreach (var catalog in allCatalogs)
|
|
{
|
|
var catalogId = catalog.Id;
|
|
var parentId = catalog.ParentId;
|
|
|
|
if (parentId > 0 && catalogDict.ContainsKey(parentId) && catalogDict.ContainsKey(catalogId))
|
|
{
|
|
var parentNode = catalogDict[parentId];
|
|
var childNode = catalogDict[catalogId];
|
|
|
|
if (!parentNode.Child.Any(c => c.Id == childNode.Id))
|
|
{
|
|
parentNode.Child.Add(childNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 计算每个节点的层级并返回根节点列表
|
|
var result = rootCatalogIds
|
|
.Where(id => catalogDict.ContainsKey(id))
|
|
.Select(id =>
|
|
{
|
|
var node = catalogDict[id];
|
|
node.Level = 1;
|
|
CalculateChildLevels(node.Child, 2);
|
|
return node;
|
|
})
|
|
.ToList();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 递归计算子节点层级
|
|
/// </summary>
|
|
private void CalculateChildLevels(List<IcrJournalCatalogTreeDto> children, int level)
|
|
{
|
|
foreach (var child in children)
|
|
{
|
|
child.Level = level;
|
|
if (child.Child != null && child.Child.Count > 0)
|
|
{
|
|
CalculateChildLevels(child.Child, level + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 导入书籍目录
|
|
/// </summary>
|
|
/// <param name="JournalId"></param>
|
|
/// <param name="dtos"></param>
|
|
/// <returns></returns>
|
|
public async Task<bool> ImportCatalogAsync(long JournalId, List<JournalCatalogTreeListDto> dtos)
|
|
{
|
|
dtos = dtos.Where(c => !string.IsNullOrWhiteSpace(c.Name)).ToList();
|
|
BusinessException.ThrowIf(dtos.Select(c => c.PageNum).Distinct().Count() != dtos.Count(), "存在重复的页码");
|
|
|
|
var Journal = await Queryable<Journal>().Where(w => w.Id == JournalId).FirstAsync();
|
|
BusinessException.ThrowIf(Journal.IsNull(), "未找到书本");
|
|
var pageNums = dtos.Select(c => c.PageNum).ToList();
|
|
var pageNumNotExists = await _JournalPageRepository.Queryable().AnyAsync(w => w.JournalId == JournalId && !pageNums.Contains(w.PageNum));
|
|
BusinessException.ThrowIf(pageNumNotExists, "不存在的书页");
|
|
|
|
var firstCatelogGroup = dtos.Select(c => c.ParentName).Distinct().Select(c => new JournalCatalog
|
|
{
|
|
JournalId = JournalId,
|
|
Id = YitIdHelper.NextId(),
|
|
Level = 1,
|
|
Name = c,
|
|
}).ToList();
|
|
var subCatelogGroup = dtos.Select(c => new { c.Name, c.ParentName }).Distinct().Select(c => new
|
|
{
|
|
JournalId = JournalId,
|
|
Id = YitIdHelper.NextId(),
|
|
Level = 2,
|
|
Name = c.Name,
|
|
ParentId = firstCatelogGroup.FirstOrDefault(m => m.Name == c.ParentName)?.Id,
|
|
ParentName = c.ParentName
|
|
}).ToList();
|
|
|
|
var result = await UseTranAsync(async () =>
|
|
{
|
|
await base.DeleteAsync(c => c.JournalId == JournalId);
|
|
await base.InsertRangeAsync(firstCatelogGroup);
|
|
await base.InsertRangeAsync(subCatelogGroup.Adapt<List<JournalCatalog>>());
|
|
var JournalPages = await Queryable<JournalPage>().Where(c => c.JournalId == JournalId).ToListAsync();
|
|
JournalPages.ForEach(c =>
|
|
{
|
|
var cate = dtos.FirstOrDefault(m => m.PageNum == c.PageNum);
|
|
c.JournalCatalogId = subCatelogGroup?.FirstOrDefault(m => m.Name == cate?.Name && m.ParentName == cate?.ParentName)?.Id ?? 0;
|
|
});
|
|
await base.Context.Updateable<JournalPage>(JournalPages).UpdateColumns(c => c.JournalCatalogId).ExecuteCommandAsync();
|
|
return true;
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
public async Task<long> InsertAsync(JournalCatalogInput input)
|
|
{
|
|
var map = input.Adapt<JournalCatalog>();
|
|
map.Id = YitIdHelper.NextId();
|
|
await UseTranAsync(async () =>
|
|
{
|
|
// 获取同级所有节点
|
|
var siblings = await base.Queryable().Where(w => w.JournalId == input.JournalId).Where(w => w.ParentId == input.ParentId).OrderBy(o => o.Sort).ToListAsync();
|
|
|
|
if (input.Position.HasValue && input.Position > 0 && input.Position <= siblings.Count)
|
|
{
|
|
// 调换位置,前面不变,后续加1即可
|
|
int validPosition = Math.Min(input.Position.Value, siblings.Count);
|
|
map.Sort = validPosition;
|
|
|
|
// 调整后续节点的SortOrder (+1)
|
|
await base.Updateable()
|
|
.SetColumns(x => x.Sort == x.Sort + 1)
|
|
.Where(x => x.ParentId == input.ParentId && x.Sort >= validPosition)
|
|
.ExecuteCommandAsync();
|
|
}
|
|
else
|
|
{
|
|
// 默认追加到末尾
|
|
map.Sort = siblings.Count > 0 ? siblings.Max(x => x.Sort) + 1 : 0;
|
|
}
|
|
|
|
|
|
var data = await base.InsertAsync(map);
|
|
|
|
if (input.Type == 1)
|
|
{
|
|
var Journal = await Queryable<Journal>().Where(w => w.Id == input.JournalId).FirstAsync();
|
|
BusinessException.ThrowIf(Journal.IsNull(), "未找到书本");
|
|
|
|
//var dotMatrixPage = new DotMatrixPage()
|
|
//{
|
|
// Id = YitIdHelper.NextId(),
|
|
// DotMatrixNoteJournalId = input.JournalId,
|
|
// WidthMilliMeter = Journal.Width,
|
|
// HightMilliMeter = Journal.Height,
|
|
//};
|
|
var JournalPage = new JournalPage
|
|
{
|
|
JournalId = input.JournalId,
|
|
JournalCatalogId = map.Id,
|
|
//DotMatrixPageId = dotMatrixPage.Id,
|
|
PageNum = map.Sort,
|
|
};
|
|
}
|
|
});
|
|
return map.Id;
|
|
|
|
}
|
|
|
|
public async Task<bool> UpdateAsync(JournalCatalogUpdateInput input)
|
|
{
|
|
var result = await base.Updateable(input.Adapt<JournalCatalog>()).Where(w => w.Id == input.Id).ExecuteCommandAsync() > 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
public async Task<bool> DeleteAsync(long id)
|
|
{
|
|
var catas = await base.Queryable().ToChildListAsync(it => it.ParentId, id);
|
|
var ids = catas.Select(s => s.Id).ToList();
|
|
|
|
await UseTranAsync(async () =>
|
|
{
|
|
// 删除目录
|
|
var cata = await base.Deleteable().Where(d => ids.Contains(d.Id)).ExecuteCommandAsync() > 0;
|
|
BusinessException.ThrowIf(!cata, $"删除目录失败");
|
|
// 删除所有页/问题
|
|
var pages = await _JournalPageRepository.DeleteAsync(w => ids.Contains(w.JournalCatalogId));
|
|
BusinessException.ThrowIf(pages.IsNull(), $"删除数据失败");
|
|
|
|
|
|
//var x = await _JournalPageRepository.DeleteAsync(w => ids.Contains(w.JournalCatalogId));
|
|
//var y = await _JournalPageTaskRepository.DeleteAsync(w => ids.Contains(w.JournalCatalogId));
|
|
});
|
|
return true;
|
|
}
|
|
|
|
public async Task<bool> MoveAsync(MoveInput input)
|
|
{
|
|
// 1. 获取源节点并校验
|
|
var sourceNode = await base.Queryable().Where(x => x.Id == input.SourceId).FirstAsync();
|
|
BusinessException.ThrowIf(sourceNode.IsNull(), $"要移动的节点{input.SourceId}不存在");
|
|
|
|
|
|
// 2. 校验目标父节点(如果指定)
|
|
if (input.TargetParentId > 0)
|
|
{
|
|
var targetParentExists = await base.Queryable().Where(x => x.Id == input.TargetParentId).AnyAsync();
|
|
BusinessException.ThrowIf(!targetParentExists, $"目标父节点{input.TargetParentId}不存在");
|
|
}
|
|
|
|
await base.UseTranAsync(async () =>
|
|
{
|
|
//var tree = await base.Queryable().Where(x => x.Id == input.SourceId).AsTreeCte().OrderBy(x => x.Level).ToTreeListAsync();
|
|
|
|
if (input.Position.HasValue)
|
|
{
|
|
await base.Updateable()
|
|
.SetColumns(x => x.Sort == x.Sort + 1)
|
|
.Where(x => x.ParentId == input.TargetParentId && x.Sort >= input.Position.Value)
|
|
.ExecuteCommandAsync();
|
|
}
|
|
|
|
// 3. 更新源节点的父节点
|
|
var updateCount = await base.Updateable()
|
|
.SetColumns(x => x.ParentId, input.TargetParentId)
|
|
.SetColumns(x => x.Sort, input.Position ?? 0)
|
|
.Where(x => x.Id == input.SourceId)
|
|
.ExecuteCommandAsync() > 0;
|
|
|
|
BusinessException.ThrowIf(!updateCount, $"节点移动失败");
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//public async Task<bool> CopyAsync(CopyInput input)
|
|
//{
|
|
|
|
// // 1. 获取源节点并校验
|
|
// var sourceNode = await base.Queryable().Where(x => x.Id == input.SourceId).FirstAsync();
|
|
// BusinessException.ThrowIf(sourceNode.IsNull(), $"要移动的节点{input.SourceId}不存在");
|
|
|
|
|
|
// // 2. 校验目标父节点(如果指定)
|
|
// if (input.TargetParentId > 0)
|
|
// {
|
|
// var targetParentExists = await base.Queryable().Where(x => x.Id == input.TargetParentId).AnyAsync();
|
|
// BusinessException.ThrowIf(!targetParentExists, $"目标父节点{input.TargetParentId}不存在");
|
|
// }
|
|
|
|
// using var uow = _unitOfWorkManager.Begin();
|
|
// var tree = await base.Queryable().IncludeMany(c => c.Pages, then => then.IncludeMany(x => x.PageTasks))
|
|
// .Where(x => x.Id == input.SourceId)
|
|
// .AsTreeCte()
|
|
// .OrderBy(x => x.Level)
|
|
// .ToTreeListAsync();
|
|
|
|
// if (input.Position.HasValue)
|
|
// {
|
|
// await base.Updateable()
|
|
// .Set(x => x.Sort + 1)
|
|
// .Where(x => x.ParentId == input.TargetParentId && x.Sort >= input.Position.Value)
|
|
// .ExecuteCommandAsync();
|
|
// }
|
|
|
|
// var copy = await DeepCopyAsync(tree.First(), input.TargetParentId);
|
|
// BusinessException.ThrowIf(copy.IsNull(), $"复制节点失败");
|
|
|
|
// var data = await base.InsertAsync(copy);
|
|
// BusinessException.ThrowIf(data.IsNull(), $"复制节点失败");
|
|
|
|
// var pages = copy.Where(s => s.Pages.NotNull()).Queryable()Many(s => s.Pages);
|
|
// var tempPage = await _JournalPageRepository.InsertAsync(pages);
|
|
// BusinessException.ThrowIf(tempPage.IsNull(), $"复制页面数据失败");
|
|
|
|
// //var task = pages.Where(w => w.PageTasks.NotNull()).Queryable()Many(s => s.PageTasks);
|
|
// //var tempPageTask = await _JournalPageTaskRepository.InsertAsync(task);
|
|
// //BusinessException.ThrowIf(tempPageTask.IsNull(), $"复制页面问题数据失败");
|
|
|
|
// uow.Commit();
|
|
// return true;
|
|
//}
|
|
//private async Task<List<IcrJournalCatalog>> DeepCopyAsync(IcrJournalCatalog source, long targetParentId, List<IcrJournalCatalog>? list = null)
|
|
//{
|
|
// list ??= new List<IcrJournalCatalog>();
|
|
|
|
// var copy = new IcrJournalCatalog
|
|
// {
|
|
// Id = YitIdHelper.NextId(),// 设置新ID
|
|
// Name = !list.Any() ? $"{source.Name} 副本" : source.Name,
|
|
// ParentId = targetParentId,
|
|
// Level = source.Level,
|
|
// Sort = source.Sort,
|
|
// };
|
|
// copy.Pages?.ForEach(page =>
|
|
// {
|
|
// page.Id = YitIdHelper.NextId(); // 设置新页面ID
|
|
// page.JournalCatalogId = copy.Id; // 设置为新目录的ID
|
|
// page.PageTasks?.ForEach(task =>
|
|
// {
|
|
// task.Id = YitIdHelper.NextId(); // 设置新问题ID
|
|
// task.JournalPageId = page.Id; // 设置为新页面的ID
|
|
// });
|
|
// });
|
|
// list.Add(copy);
|
|
|
|
// // 递归复制子节点
|
|
// if (source.Childs != null && source.Childs.Any())
|
|
// {
|
|
// foreach (var child in source.Childs)
|
|
// {
|
|
// await DeepCopyAsync(child, copy.Id, list);
|
|
// }
|
|
// }
|
|
// return list;
|
|
//}
|
|
}
|