1. add PublishAsync method to IJournalService and implement it in JournalService 2. add publish start/end time fields to Journal entity, DTOs and edit form 3. add journal publish API endpoint 4. add rabbitMQ message sending for book and page publish events 5. clean up some commented-out old controller methods
393 lines
16 KiB
C#
393 lines
16 KiB
C#
using Mapster;
|
||
using Microsoft.Extensions.Logging;
|
||
using QYZH.InteractiveMagazine.Common.Extensions;
|
||
using QYZH.InteractiveMagazine.Infrastructure.OSS;
|
||
using QYZH.InteractiveMagazine.Infrastructure.RabbitMQ;
|
||
using QYZH.InteractiveMagazine.IService;
|
||
using QYZH.InteractiveMagazine.Models.Common;
|
||
using QYZH.InteractiveMagazine.Models.Dto;
|
||
using QYZH.InteractiveMagazine.Models.Dto.DotMatrix;
|
||
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 JournalService(BaseRepository<JournalPage> JournalPageRepository,
|
||
BaseRepository<JournalPageTask> JournalPageTaskRepository,
|
||
BaseRepository<JournalPageTaskAnswer> JournalPageTaskAnswerRepository,
|
||
BaseRepository<JournalCatalog> JournalCatalogRepository,
|
||
BaseRepository<DotFile> dotFileRepository,
|
||
BaseRepository<DotFileDetail> dotFileDetailRepository,
|
||
OssService ossService,
|
||
IRabbitMQService rabbitMqService,
|
||
ILogger<JournalService> logger) : BaseRepository<Journal>, IJournalService
|
||
{
|
||
private const string JournalExchange = "ex.journal";
|
||
private const string PublishBookQueue = "mq.journal.publish.book";
|
||
private const string PublishBookRoutingKey = "rk.journal.publish.book";
|
||
private const string PublishBookPageQueue = "mq.journal.publish.bookpage";
|
||
private const string PublishBookPageRoutingKey = "rk.journal.publish.bookpage";
|
||
|
||
/// <summary>
|
||
/// 查询List
|
||
/// </summary>
|
||
/// <param name="dto"></param>
|
||
/// <returns></returns>
|
||
public async Task<List<JournalDto>> GetListAsync(JournalQueryDto dto)
|
||
{
|
||
var query = await Queryable()
|
||
.WhereIF(!string.IsNullOrWhiteSpace(dto.Name), a => a.Name.Contains(dto.Name))
|
||
.WhereIF(dto.Status.HasValue, a => a.Status == (int)dto.Status)
|
||
.Select(a => new JournalDto(), true)
|
||
.ToListAsync();
|
||
|
||
return query;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 分页查询
|
||
/// </summary>
|
||
/// <param name="search"></param>
|
||
/// <returns></returns>
|
||
public async Task<PageListModel<JournalDto>> GetPageListAsync(PageQueryModel<JournalQueryDto> search)
|
||
{
|
||
RefAsync<int> totalNumber = 0;
|
||
|
||
var dataList = await Queryable()
|
||
//.InnerJoin<IcrJournalOrganization>((a, b) => a.Id == b.JournalId)
|
||
.WhereIF(!string.IsNullOrWhiteSpace(search.Params.Name), a => a.Name.Contains(search.Params.Name))
|
||
.WhereIF(search.Params.Status.HasValue, a => a.Status == (int)search.Params.Status)
|
||
.WhereIF(!string.IsNullOrWhiteSpace(search.Params.Name), a => a.Name.Contains(search.Params.Name))
|
||
.OrderByDescending(a => a.CreatedAt)
|
||
.Select(a => new JournalDto(), true)
|
||
|
||
.ToPageListAsync(search.PageIndex, search.PageSize, totalNumber);
|
||
|
||
return new PageListModel<JournalDto>(dataList, search.PageIndex, search.PageSize, totalNumber);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建杂志
|
||
/// </summary>
|
||
/// <param name="input">杂志信息</param>
|
||
/// <returns>新杂志ID</returns>
|
||
public async Task<BaseResponse<long>> AddAsync(JournalAddDto input)
|
||
{
|
||
BusinessException.ThrowIf(string.IsNullOrWhiteSpace(input.Name), "书籍名称不能为空", ResultCode.BAD_REQUEST);
|
||
|
||
var journal = new Journal
|
||
{
|
||
Name = input.Name.Trim(),
|
||
Title = input.Title,
|
||
TotalPage = input.TotalPage,
|
||
Status = (int)JournalStatusEnum.Created,
|
||
PdfUrl = input.PdfUrl?.RemoveDomain(),
|
||
Width = input.Width,
|
||
Height = input.Height,
|
||
Cover = input.Cover?.RemoveDomain(),
|
||
BackCover = input.BackCover?.RemoveDomain(),
|
||
PdfPreviewUrl = input.PdfPreviewUrl?.RemoveDomain(),
|
||
StartTime = input.StartTime,
|
||
EndTime = input.EndTime,
|
||
CreatedBy = "System",
|
||
UpdatedBy = "System",
|
||
CreatedAt = DateTime.Now,
|
||
UpdatedAt = DateTime.Now,
|
||
IsDeleted = false
|
||
};
|
||
|
||
// Id 已由 YitIdHelper 预生成,先搬运文件到正式目录,再一次性插入
|
||
if (!string.IsNullOrWhiteSpace(input.Cover))
|
||
{
|
||
var coverKey = $"journal/{journal.Id}/cover.{input.Cover.ToExtension()}";
|
||
ossService.CopyObject(input.Cover.RemoveDomain(), coverKey);
|
||
journal.Cover = coverKey;
|
||
}
|
||
|
||
if (!string.IsNullOrWhiteSpace(input.BackCover))
|
||
{
|
||
var backCoverKey = $"journal/{journal.Id}/backCover.{input.BackCover.ToExtension()}";
|
||
ossService.CopyObject(input.BackCover.RemoveDomain(), backCoverKey);
|
||
journal.BackCover = backCoverKey;
|
||
}
|
||
|
||
if (!string.IsNullOrWhiteSpace(input.PdfUrl))
|
||
{
|
||
var pdfKey = $"journal/{journal.Id}/Journal.{input.PdfUrl.ToExtension()}";
|
||
ossService.CopyObject(input.PdfUrl.RemoveDomain(), pdfKey);
|
||
journal.PdfUrl = pdfKey;
|
||
}
|
||
|
||
var res = await base.InsertAsync(journal);
|
||
if (!res)
|
||
{
|
||
new BusinessException("创建失败", ResultCode.GLOBAL_ERROR);
|
||
}
|
||
logger.LogInformation("杂志创建成功,ID: {Id}, 名称: {Name}", journal.Id, input.Name);
|
||
return BaseResponse<long>.Success(journal.Id);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 编辑
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
public async Task<BaseResponse<bool>> EditAsync(JournalEditDto input)
|
||
{
|
||
|
||
var Journal = await base.GetByIdAsync(input.Id);
|
||
BusinessException.ThrowIf(Journal.IsNull(), "不存在的Id", ResultCode.NOT_FOUND);
|
||
BusinessException.ThrowIf(Journal.Status == (int)JournalStatusEnum.Archive, "已归档不可编辑", ResultCode.CONFLICT);
|
||
if (string.IsNullOrWhiteSpace(input.Cover))
|
||
{
|
||
Journal.Cover = null;
|
||
}
|
||
else if (input.Cover.RemoveDomain() != Journal.Cover)
|
||
{
|
||
var key = $"journal/{Journal.Id}/cover.{input.Cover.ToExtension()}";
|
||
ossService.CopyObject(input.Cover.RemoveDomain(), key);
|
||
Journal.Cover = key;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(input.BackCover))
|
||
{
|
||
Journal.BackCover = null;
|
||
}
|
||
else if (input.BackCover.RemoveDomain() != Journal.BackCover)
|
||
{
|
||
var backCoverkey = $"journal/{Journal.Id}/backCover.{input.BackCover.ToExtension()}";
|
||
ossService.CopyObject(input.BackCover.RemoveDomain(), backCoverkey);
|
||
Journal.BackCover = backCoverkey;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(input.PdfUrl))
|
||
{
|
||
Journal.PdfUrl = null;
|
||
}
|
||
else if (input.PdfUrl.RemoveDomain() != Journal.PdfUrl)
|
||
{
|
||
var pdfUrlkey = $"journal/{Journal.Id}/Journal.{input.PdfUrl.ToExtension()}";
|
||
ossService.CopyObject(input.PdfUrl.RemoveDomain(), pdfUrlkey);
|
||
Journal.PdfUrl = pdfUrlkey;
|
||
}
|
||
Journal.Width = input.Width;
|
||
Journal.Height = input.Height;
|
||
Journal.Name = input.Name;
|
||
Journal.Title = input.Title;
|
||
Journal.StartTime = input.StartTime;
|
||
Journal.EndTime = input.EndTime;
|
||
Journal.UpdatedAt = DateTime.Now;
|
||
|
||
var res = await UseTranAsync(async () =>
|
||
{
|
||
var result = await Context.Updateable(Journal).IgnoreColumns(c => c.Status).ExecuteCommandAsync();
|
||
return result > 0;
|
||
});
|
||
|
||
|
||
|
||
return BaseResponse<bool>.Success(res);
|
||
}
|
||
|
||
|
||
public async Task<List<JournalTaskOutput>> Tasks(long id)
|
||
{
|
||
var data = await JournalPageTaskRepository.Queryable().Where(w => w.JournalId == id)
|
||
.Select(x => new JournalTaskOutput()
|
||
{
|
||
TaskId = x.Id,
|
||
TaskNo = x.No,
|
||
TaskSubType = x.Type
|
||
}).ToListAsync();
|
||
return data.OrderBy(x =>
|
||
{
|
||
var parts = x.TaskNo.Split('-').Select(int.Parse).ToArray();
|
||
return (parts[0], parts[1], parts[2], parts[3]); // 元组
|
||
}).ToList();
|
||
}
|
||
|
||
public async Task<JournalDto> DetailAsync(long id)
|
||
{
|
||
var Journal = await base.Queryable().Where(w => w.Id == id).Select<JournalDto>().FirstAsync();
|
||
return Journal;
|
||
}
|
||
|
||
public async Task<bool> DeleteAsync(List<long> ids)
|
||
{
|
||
BusinessException.ThrowIf(!base.Queryable().Any(w => ids.Contains(w.Id)), "ID不存在", ResultCode.NOT_FOUND);
|
||
BusinessException.ThrowIf(base.Queryable().Any(w => ids.Contains(w.Id) && w.Status == (int)JournalStatusEnum.Archive), "已归档不可删除", ResultCode.CONFLICT);
|
||
var result = await UseTranAsync(async () =>
|
||
{
|
||
await base.DeleteAsync(d => ids.Contains(d.Id));
|
||
await JournalPageRepository.DeleteAsync(d => ids.Contains(d.JournalId));
|
||
await JournalCatalogRepository.DeleteAsync(d => ids.Contains(d.JournalId));
|
||
|
||
// 先查询要删除的 JournalPageTask Id 列表
|
||
var taskIds = await JournalPageTaskRepository.Queryable()
|
||
.Where(d => ids.Contains(d.JournalId))
|
||
.Select(d => d.Id)
|
||
.ToListAsync();
|
||
|
||
// 删除 JournalPageTaskAnswer
|
||
if (taskIds.Any())
|
||
{
|
||
await JournalPageTaskAnswerRepository.DeleteAsync(d => taskIds.Contains(d.JournalPageTaskId));
|
||
}
|
||
|
||
// 再删除 JournalPageTask
|
||
await JournalPageTaskRepository.DeleteAsync(d => ids.Contains(d.JournalId));
|
||
return true;
|
||
});
|
||
|
||
//删除OSS上的journal/{JournalId}文件夹
|
||
if (result)
|
||
{
|
||
foreach (var JournalId in ids)
|
||
{
|
||
var prefix = $"journal/{JournalId}/";
|
||
var keys = ossService.ListObjects(prefix);
|
||
if (keys?.Count > 0)
|
||
{
|
||
ossService.DeleteObjects(keys);
|
||
}
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
///
|
||
/// </summary>
|
||
/// <param name="id">书id</param>
|
||
/// <param name="index">起始页码</param>
|
||
/// <returns></returns>
|
||
public async Task<bool> StartPageAsync(long id, int index)
|
||
{
|
||
var oldIndex = await JournalPageRepository.Queryable().Where(w => w.JournalId == id && w.PageNum == 1).Select(x => x.Sort).FirstAsync();
|
||
|
||
// 如果位置没有变化,直接返回
|
||
if (index == oldIndex) return true;
|
||
|
||
if (index > oldIndex)
|
||
{
|
||
// 向后移动的情况
|
||
var x = index - oldIndex;
|
||
await JournalPageRepository.Updateable().SetColumns(s => s.PageNum, 0).Where(w => w.Sort < index).Where(w => w.JournalId == id).ExecuteCommandAsync();
|
||
await JournalPageRepository.Updateable().SetColumns(s => s.PageNum == s.PageNum - x).Where(w => w.Sort >= index).Where(w => w.JournalId == id).ExecuteCommandAsync();
|
||
}
|
||
else
|
||
{
|
||
// 向前移动的情况
|
||
//var x = oldIndex - input.Index;
|
||
await JournalPageRepository.Updateable().SetColumns(s => s.PageNum, 0).Where(w => w.Sort < index).Where(w => w.JournalId == id).ExecuteCommandAsync();
|
||
|
||
// 将目标页面设置为第一页
|
||
await JournalPageRepository.Updateable().SetColumns(s => s.PageNum == s.Sort - (index - 1)).Where(w => w.Sort >= index).Where(w => w.JournalId == id).ExecuteCommandAsync();
|
||
}
|
||
|
||
// await JournalPageRepository.Updateable().SetColumns(s => s.PageNum == s.Sort).Where(w => w.JournalId == input.Id).ExecuteCommandAsync();
|
||
return true;
|
||
}
|
||
|
||
public async Task<DotMatrixOutput> PrintCodeAsync(long id)
|
||
{
|
||
var Journal = await base.Queryable().Where(w => w.Id == id).FirstAsync();
|
||
BusinessException.ThrowIf(Journal.IsNull(), "不存在书", ResultCode.NOT_FOUND);
|
||
BusinessException.ThrowIf(Journal.Status == (int)JournalStatusEnum.Codeing, "正在生成中...", ResultCode.CONFLICT);
|
||
|
||
var pages = await JournalPageRepository.Queryable().Where(w => w.JournalId == id).ToListAsync();
|
||
BusinessException.ThrowIf(pages.IsNull(), "不存在页", ResultCode.NOT_FOUND);
|
||
|
||
var output = new DotMatrixOutput()
|
||
{
|
||
Method = "Journal",
|
||
Name = Journal.Name,
|
||
NoteId = id,
|
||
PdfUrl = Journal.PdfUrl
|
||
};
|
||
|
||
return output;
|
||
}
|
||
|
||
public async Task<bool> ResultReportAsync(DotMatrixNoteJournalReportInput input)
|
||
{
|
||
if (input.FileUrl.IsNull() && input.FileKey.IsNull())
|
||
{
|
||
return await Updateable().SetColumns(s => s.Status, input.Success ? JournalStatusEnum.CodeSuccess : JournalStatusEnum.CodeFail)
|
||
.SetColumns(s => s.PdfPreviewUrl, input.FileKey)
|
||
.Where(w => w.Id == input.Id)
|
||
.ExecuteCommandAsync() > 0;
|
||
}
|
||
return await Updateable().SetColumns(s => s.Status, JournalStatusEnum.CodeFail).Where(w => w.Id == input.Id).ExecuteCommandAsync() > 0;
|
||
}
|
||
|
||
public async Task<bool> StatusAsync(long id, JournalStatusEnum status)
|
||
{
|
||
var book = await base.GetByIdAsync(id);
|
||
var tasks = await Context.Queryable<JournalPageTask>().Where(w => w.JournalId == id).ToListAsync();
|
||
BusinessException.ThrowIf(tasks.Count == 0 && status == JournalStatusEnum.Archive, "未添加任何题目,无法归档", ResultCode.UNPROCESSABLE_ENTITY);
|
||
BusinessException.ThrowIf(tasks.Any(a => string.IsNullOrWhiteSpace(a.TaskUrl)) && status == JournalStatusEnum.Archive, $"{string.Join(',', tasks.Where(a => string.IsNullOrWhiteSpace(a.TaskUrl)).Select(a => a.No).ToList())}未保存,无法归档", ResultCode.UNPROCESSABLE_ENTITY);
|
||
var res = await base.Updateable().SetColumns(s => s.Status, status).Where(w => w.Id == id).ExecuteCommandAsync() > 0;
|
||
return res;
|
||
}
|
||
|
||
public async Task<bool> PublishAsync(long id)
|
||
{
|
||
var book = await base.GetByIdAsync(id);
|
||
BusinessException.ThrowIf(book == null || book.IsDeleted, "杂志不存在", ResultCode.NOT_FOUND);
|
||
|
||
var pages = await JournalPageRepository.Queryable()
|
||
.Where(x => x.JournalId == id && !x.IsDeleted)
|
||
.OrderBy(x => x.PageNum)
|
||
.OrderBy(x => x.Sort)
|
||
.ToListAsync();
|
||
BusinessException.ThrowIf(pages.Count == 0, "书籍未添加任何书页,无法发布", ResultCode.UNPROCESSABLE_ENTITY);
|
||
|
||
BusinessException.ThrowIf(!book.StartTime.HasValue, "发布开始时间不能为空", ResultCode.UNPROCESSABLE_ENTITY);
|
||
BusinessException.ThrowIf(!book.EndTime.HasValue, "发布结束时间不能为空", ResultCode.UNPROCESSABLE_ENTITY);
|
||
BusinessException.ThrowIf(book.EndTime < book.StartTime, "发布结束时间不能早于开始时间", ResultCode.UNPROCESSABLE_ENTITY);
|
||
|
||
var bookMessageSent = await rabbitMqService.SendAsync(new RabbitMQSendParam
|
||
{
|
||
Exchange = JournalExchange,
|
||
Queue = PublishBookQueue,
|
||
RoutingKey = PublishBookRoutingKey,
|
||
Data = new JournalPublishBookMessage
|
||
{
|
||
BookId = id,
|
||
StartTime = book.StartTime.Value,
|
||
EndTime = book.EndTime.Value
|
||
}
|
||
});
|
||
BusinessException.ThrowIf(!bookMessageSent, "发布书籍消息发送失败", ResultCode.GLOBAL_ERROR);
|
||
|
||
foreach (var page in pages)
|
||
{
|
||
var pageMessageSent = await rabbitMqService.SendAsync(new RabbitMQSendParam
|
||
{
|
||
Exchange = JournalExchange,
|
||
Queue = PublishBookPageQueue,
|
||
RoutingKey = PublishBookPageRoutingKey,
|
||
Data = new JournalPublishBookPageMessage
|
||
{
|
||
BookId = id,
|
||
PageId = page.Id,
|
||
PageNo = page.PageNo,
|
||
Layout = page.Layout
|
||
}
|
||
});
|
||
BusinessException.ThrowIf(!pageMessageSent, $"发布书页消息发送失败,PageId: {page.Id}", ResultCode.GLOBAL_ERROR);
|
||
}
|
||
|
||
return await base.Updateable()
|
||
.SetColumns(s => s.Status, JournalStatusEnum.Published)
|
||
.SetColumns(s => s.UpdatedAt, DateTime.Now)
|
||
.Where(w => w.Id == id)
|
||
.ExecuteCommandAsync() > 0;
|
||
}
|
||
}
|