feat(journal): add journal publish feature

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
This commit is contained in:
glz
2026-06-30 11:03:39 +08:00
parent fa1acec8af
commit 6985a4af42
6 changed files with 133 additions and 60 deletions

View File

@ -49,6 +49,8 @@ public interface IJournalService : IBaseService<Journal>
Task<bool> StatusAsync(long id, JournalStatusEnum status);
Task<bool> PublishAsync(long id);
Task<DotMatrixOutput> PrintCodeAsync(long id);
Task<bool> ResultReportAsync(DotMatrixNoteJournalReportInput input);

View File

@ -120,8 +120,33 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
/// </summary>
public string? DownloadJournalPagePdfName { get; set; }
/// <summary>
/// 发布开始时间
/// </summary>
public DateTime? StartTime { get; set; }
/// <summary>
/// 发布结束时间
/// </summary>
public DateTime? EndTime { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedAt { get; set; }
}
public class JournalPublishBookMessage
{
public long BookId { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
}
public class JournalPublishBookPageMessage
{
public long BookId { get; set; }
public long PageId { get; set; }
public string? PageNo { get; set; }
public string? Layout { get; set; }
}
}

View File

@ -69,6 +69,16 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
/// </summary>
public string? PdfPreviewUrl { get; set; }
/// <summary>
/// Desc:发布开始时间
/// </summary>
public DateTime? StartTime { get; set; }
/// <summary>
/// Desc:发布结束时间
/// </summary>
public DateTime? EndTime { get; set; }
}
public partial class JournalEditDto: JournalAddDto
{

View File

@ -106,6 +106,20 @@ namespace QYZH.InteractiveMagazine.Models.Entity
/// </summary>
public string DownloadJournalPagePdfName { get; set; }
/// <summary>
/// Desc:发布日期开始时间
/// Default:
/// Nullable:True
/// </summary>
public DateTime? StartTime { get; set; }
/// <summary>
/// Desc:发布日期结束时间
/// Default:
/// Nullable:True
/// </summary>
public DateTime? EndTime { get; set; }
/// <summary>
/// Desc:书籍描述/简介

View File

@ -21,8 +21,17 @@ public class JournalService(BaseRepository<JournalPage> JournalPageRepository,
BaseRepository<JournalPageTaskAnswer> JournalPageTaskAnswerRepository,
BaseRepository<JournalCatalog> JournalCatalogRepository,
BaseRepository<DotFile> dotFileRepository,
BaseRepository<DotFileDetail> dotFileDetailRepository, OssService ossService, ILogger<JournalService> logger) : BaseRepository<Journal>, IJournalService
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>
@ -82,6 +91,8 @@ public class JournalService(BaseRepository<JournalPage> JournalPageRepository,
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,
@ -167,6 +178,9 @@ public class JournalService(BaseRepository<JournalPage> JournalPageRepository,
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 () =>
{
@ -320,4 +334,59 @@ public class JournalService(BaseRepository<JournalPage> JournalPageRepository,
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;
}
}

View File

@ -141,6 +141,17 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
return BaseResponse<bool>.Success(data);
}
/// <summary>
/// 书籍发布
/// </summary>
/// <returns></returns>
[HttpPost, Route("journal/publish/{id:long}")]
public async Task<BaseResponse<bool>> Publish(long id)
{
var data = await JournalService.PublishAsync(id);
return BaseResponse<bool>.Success(data);
}
/// <summary>
/// 书籍铺码
/// </summary>
@ -200,17 +211,6 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
#region
///// <summary>
///// 书籍目录详情
///// </summary>
///// <returns></returns>
//[HttpGet, Route("catalog/tree/{JournalId:long}")]
//public async Task<BaseResponse<List<IcrJournalCatalog>>> CatalogDetailAsync(long JournalId)
//{
// var data = await _JournalCatalogService.DetailAsync(JournalId);
// return BaseResponse<List<IcrJournalCatalog>>.Success(data);
//}
/// <summary>
/// 书籍目录详情
/// </summary>
@ -312,18 +312,6 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
return BaseResponse<bool>.Success(data);
}
///// <summary>
///// 书籍目录复制
///// </summary>
///// <param name="input"></param>
///// <returns></returns>
//[HttpPost, Route("catalog/copy")]
//[ProducesResponseType(typeof(BaseResponse<bool>), 200)]
//public async Task<BaseResponse> CatalogCopy(CopyInput input)
//{
// var data = await _JournalCatalogServices.CopyAsync(input);
// return ApiResult(data, "书籍目录删除失败!");
//}
/// <summary>
/// 书籍目录移动
@ -411,30 +399,6 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
}
///// <summary>
///// 打印书页(全部) 暂时废弃这个方,直接使用 Adobe Reader X 软件打印就可以了
///// </summary>
///// <returns></returns>
//[HttpPost, Route("page/printJournalPage/{JournalId:long}")]
//public async Task<BaseResponse<bool>> PrintJournalPage([Required(ErrorMessage = "书籍编号不允许为空")] long JournalId)
//{
// var data = await JournalPageService.PrintJournalPageAsync(JournalId);
// return BaseResponse<bool>.Success(data);
//}
///// <summary>
///// 书页删除
///// </summary>
///// <returns></returns>
//[HttpPost, Route("page/delete/{id:long}")]
//[ProducesResponseType(typeof(BaseResponse<bool>), 200)]
//public async Task<IActionResult> PageDelete(long id)
//{
// var data = await _JournalPageServices.DeleteAsync(id);
// return ApiResult(data, "更新书页失败!");
//}
/// <summary>
/// 书页详情
/// </summary>
@ -446,17 +410,6 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
return BaseResponse<JournalPageV2Output>.Success(data);
}
///// <summary>
///// 不包含有文章的书页
///// </summary>
///// <returns></returns>
//[HttpGet, Route("page/no-article/{JournalId:long}")]
//[ProducesResponseType(typeof(BaseResponse<List<JournalPageNoArticleOutput>>), 200)]
//public async Task<IActionResult> PageNoArticleAsync(long JournalId)
//{
// var data = await _JournalPageService.PageNoArticleAsync(JournalId);
// return BaseResponse<List<JournalPageNoArticleOutput>>(data, "查询书页失败!");
//}
#endregion