1. 新增数据库唯一约束和Message_Outbox表脚本 2. 新增雪花ID、Hangfire存储、MQ重试等配置实体 3. 重构各项目雪花ID生成逻辑,改为从配置读取WorkerId 4. 优化积分服务分页查询、用户背包更新逻辑 5. 新增JWT令牌Redis过期刷新逻辑 6. 完善RabbitMQ死信队列消息头信息 7. 新增可靠MQ消息发布服务和Outbox派发后台服务 8. 替换原有RabbitMQ直接发送为Outbox可靠发布 9. 优化签到服务逻辑,新增重复签到校验和补签卡扣减逻辑 10. 修复自动铺码消费逻辑,新增点阵页预占和释放机制
313 lines
14 KiB
C#
313 lines
14 KiB
C#
|
||
using Aliyun.Acs.Core.Logging;
|
||
using Microsoft.Extensions.Configuration;
|
||
using Microsoft.Extensions.Logging;
|
||
using Newtonsoft.Json;
|
||
using QYZH.InteractiveMagazine.Common.Extensions;
|
||
using QYZH.InteractiveMagazine.Common.Helpers;
|
||
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.Journal;
|
||
using QYZH.InteractiveMagazine.Models.Dto.RabbitMQ;
|
||
using QYZH.InteractiveMagazine.Models.Entity;
|
||
using QYZH.InteractiveMagazine.Models.Enum;
|
||
using QYZH.InteractiveMagazine.Repository;
|
||
using System.Diagnostics;
|
||
using System.Net;
|
||
using Yitter.IdGenerator;
|
||
|
||
namespace QYZH.InteractiveMagazine.Service;
|
||
|
||
public class JournalPageService(BaseRepository<Journal> JournalRepository,
|
||
OssService ossService,
|
||
IHttpClientFactory httpClientFactory,
|
||
IConfiguration configuration,
|
||
IMessagePublishService messagePublishService,
|
||
ILogger<AiBasePromptService> logger,
|
||
BaseRepository<JournalPage> JournalPageRepository,
|
||
BaseRepository<JournalPageTask> JournalPageTaskRepository,
|
||
BaseRepository<JournalPageTaskAnswer> JournalPageTaskAnswerRepository) : BaseRepository<JournalPage>, IJournalPageService
|
||
{
|
||
|
||
public async Task<long> InsertAsync(JournalAddV2Input input)
|
||
{
|
||
|
||
var Journal = await JournalRepository.Queryable().Where(w => w.Id == input.JournalId).FirstAsync();
|
||
BusinessException.ThrowIf(Journal.IsNull(), "未找到书本", ResultCode.NOT_FOUND);
|
||
|
||
BusinessException.ThrowIf(Journal?.Status == (int)JournalStatusEnum.Archive, "书籍已归档不能修改", ResultCode.CONFLICT);
|
||
|
||
var pages = new List<JournalPage>();
|
||
//var dotMatrixpages = new List<DotMatrixPage>();
|
||
|
||
//var dotMatrixpage = new DotMatrixPage()
|
||
//{
|
||
// Id = YitIdHelper.NextId(),
|
||
// DotMatrixNoteJournalId = input.JournalId,
|
||
// WidthMilliMeter = Journal.Width,
|
||
// HightMilliMeter = Journal.Height,
|
||
//};
|
||
|
||
var pageTemp = new JournalPage()
|
||
{
|
||
JournalId = input.JournalId,
|
||
JournalCatalogId = input.JournalCatalogId,
|
||
//DotMatrixPageId = dotMatrixpage.Id,
|
||
Url = input.Url,
|
||
PageNum = input.PageNum,
|
||
};
|
||
|
||
//dotMatrixpages.Add(dotMatrixpage);
|
||
pages.Add(pageTemp);
|
||
|
||
await UseTranAsync(async () =>
|
||
{
|
||
//using var uow = Context.Ado.BeginTran();
|
||
var pageData = await base.InsertRangeAsync(pages);
|
||
BusinessException.ThrowIf(pageData.IsNull(), "创建页失败", ResultCode.GLOBAL_ERROR);
|
||
|
||
//var result = await dotMatrixPageRepository.InsertRangeAsync(dotMatrixpages);
|
||
//BusinessException.ThrowIf(result, "创建点阵页失败");
|
||
});
|
||
|
||
return pages.Count; // 返回主目录ID
|
||
}
|
||
|
||
public async Task<bool> UpdateAsync(PageLayoutInput input)
|
||
{
|
||
var page = await Queryable().Where(w => w.Id == input.Id).FirstAsync();
|
||
BusinessException.ThrowIf(page == null, "不存在此页", ResultCode.NOT_FOUND);
|
||
|
||
var Journal = await JournalRepository.GetByIdAsync(page.JournalId);
|
||
BusinessException.ThrowIf(Journal == null, "不存在此书", ResultCode.NOT_FOUND);
|
||
|
||
BusinessException.ThrowIf(Journal?.Status == (int)JournalStatusEnum.Archive, "书籍已归档不能修改", ResultCode.CONFLICT);
|
||
var transResult = await UseTranAsync(async () =>
|
||
{
|
||
//await dotMatrixPageRepository.Updateable().SetColumns(s => s.Area == input.Layout)
|
||
// .SetColumns(s => s.AreaPoints == dotMatrixPage.Area)
|
||
// .SetColumns(s => s.WidthMilliMeter == dotMatrixPage.WidthMilliMeter)
|
||
// .SetColumns(s => s.HightMilliMeter == dotMatrixPage.HightMilliMeter)
|
||
// .SetColumns(s => s.WidthDotMatrix == dotMatrixPage.WidthDotMatrix)
|
||
// .SetColumns(s => s.HightDotMatrix == dotMatrixPage.HightDotMatrix)
|
||
// .Where(w => w.Id == page.DotMatrixPageId).ExecuteCommandAsync();
|
||
|
||
page.Layout = input.Layout;
|
||
base.Update(page);
|
||
|
||
input.TasksImages?.ForEach(it =>
|
||
{
|
||
var key = $"journal/{Journal.Id}/{input.Id}/{it.TaskId}/qustion.{it.Url.ToExtension()}";
|
||
ossService.CopyObject(it.Url.RemoveDomain(), key);
|
||
JournalPageTaskRepository.Updateable().SetColumns(s => s.TaskUrl, key).Where(w => w.Id == it.TaskId).ExecuteCommand();
|
||
});
|
||
return true;
|
||
});
|
||
return transResult;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 修改书页的点阵码
|
||
/// </summary>
|
||
/// <param name="JournalId"></param>
|
||
/// <returns></returns>
|
||
public async Task<bool> UpdatePageNoAsync(long JournalId)
|
||
{
|
||
var journalEntity = await JournalRepository.Queryable().FirstAsync(w => w.Id == JournalId);
|
||
BusinessException.ThrowIf(journalEntity == null, "不存在此期刊", ResultCode.NOT_FOUND);
|
||
|
||
//如果书籍状态不等于“已归档”,“已废弃”,“已铺码”的情况下,就更新状态为“已铺码”
|
||
BusinessException.ThrowIf((JournalStatusEnum)journalEntity.Status is JournalStatusEnum.Abandoned or JournalStatusEnum.Archive or JournalStatusEnum.Codeing, "当前【状态】不允许铺码", ResultCode.UNPROCESSABLE_ENTITY);
|
||
|
||
var journalPageList = await JournalPageRepository.Queryable().Where(x => x.JournalId == JournalId).OrderBy(x => x.PageNum).ToListAsync();
|
||
BusinessException.ThrowIf(journalPageList.Count == 0, "此期刊不存在任何书页", ResultCode.NOT_FOUND);
|
||
|
||
var uploadPdfKey = journalEntity?.PdfUrl?.RemoveDomain();
|
||
BusinessException.ThrowIf(string.IsNullOrWhiteSpace(uploadPdfKey), "此期刊上传的PDF路径错误,请检查期刊PDF文件是否上传成功", ResultCode.BAD_REQUEST);
|
||
|
||
var uploadPdfUrl = DomainHelper.OssFullUrl(uploadPdfKey);
|
||
BusinessException.ThrowIf(string.IsNullOrWhiteSpace(uploadPdfUrl), "此期刊上传的PDF路径错误,请检查期刊PDF文件是否上传成功", ResultCode.BAD_REQUEST);
|
||
|
||
logger.LogInformation("uploadPdfUrl:" + uploadPdfUrl);
|
||
//验证是否上传了PDF文件
|
||
var isPdfExists = ossService.DoesObjectExist(uploadPdfKey);
|
||
BusinessException.ThrowIf(!isPdfExists, "获取期刊上传的PDF文件失败,请检查PDF文件是否上传成功", ResultCode.GLOBAL_ERROR);
|
||
|
||
var data = new JournalPagePrintDto
|
||
{
|
||
JournalId = JournalId,
|
||
JournalPdfUrl = uploadPdfUrl,
|
||
PageNum = [.. journalPageList.Select(x => x.PageNum)]
|
||
};
|
||
|
||
return await UseTranAsync(async () =>
|
||
{
|
||
journalEntity.Status = (int)JournalStatusEnum.Codeing;
|
||
journalEntity.UpdatedAt = DateTime.Now;
|
||
await JournalRepository.Updateable(journalEntity).UpdateColumns(x => new { x.Status, x.UpdatedAt }).ExecuteCommandAsync();
|
||
await messagePublishService.PublishAsync(new MessagePublishInput<JournalPagePrintDto>
|
||
{
|
||
Exchange = "ex.journal",
|
||
Queue = "mq.journal.dotcode.auto",
|
||
RoutingKey = "rk.journal.dotcode.auto",
|
||
Data = data,
|
||
BusinessType = "JournalDotCode",
|
||
BusinessId = JournalId
|
||
});
|
||
return true;
|
||
});
|
||
}
|
||
/// <summary>
|
||
/// 回调接口-自动铺码, 书页铺码后回调接口,更新书页的点阵码
|
||
/// </summary>
|
||
/// <param name="request"></param>
|
||
/// <returns></returns>
|
||
public async Task<bool> CallbackPageUpdatePageNo(JournalPagePrintDto request)
|
||
{
|
||
return await UseTranAsync(async () =>
|
||
{
|
||
var journalEntity = await JournalRepository.Queryable().FirstAsync(w => w.Id == request.JournalId);
|
||
BusinessException.ThrowIf(journalEntity == null, "不存在此书", ResultCode.NOT_FOUND);
|
||
|
||
//如果书籍状态不等于“铺码中”则不允许回调接口更新点阵码
|
||
BusinessException.ThrowIf(journalEntity.Status != (int)JournalStatusEnum.Codeing, "当前【状态】不允许修改铺码", ResultCode.UNPROCESSABLE_ENTITY);
|
||
|
||
var bookPageList = await JournalPageRepository.Queryable().Where(x => x.JournalId == request.JournalId).OrderBy(x => x.PageNum).ToListAsync();
|
||
|
||
BusinessException.ThrowIf(bookPageList.Count == 0, "此书不存在任何书页", ResultCode.NOT_FOUND);
|
||
|
||
journalEntity.Status = (int)request.Status!.Value;
|
||
journalEntity.UpdatedAt = DateTime.Now;
|
||
|
||
//如果铺码成功了,并且之前已经有下载链接了,就删除原来的文件
|
||
if (request.Status == JournalStatusEnum.CodeSuccess && !string.IsNullOrWhiteSpace(journalEntity.DownloadJournalPagePdfName))
|
||
{
|
||
//删除oss上原来的文件
|
||
ossService.DeleteObject(journalEntity.DownloadJournalPagePdfName.RemoveDomain());
|
||
}
|
||
//如果铺码成功了,就更新下载链接,以及书页的点阵码
|
||
if (request.Status == JournalStatusEnum.CodeSuccess)
|
||
{
|
||
journalEntity.DownloadJournalPagePdfName = request.DownloadJournalPagePdfName;
|
||
|
||
BusinessException.ThrowIf(request.PageNo.Length != bookPageList.Count, $"点阵码条数与页码数量不匹配,点阵码条数:{request.PageNo.Length},页码数量:{bookPageList.Count}", ResultCode.BAD_REQUEST);
|
||
|
||
|
||
for (var i = 0; i < bookPageList.Count; i++)
|
||
{
|
||
//让也页码和点阵码的顺序必须保持一致
|
||
bookPageList[i].PageNo = request.PageNo[i];
|
||
bookPageList[i].UpdatedAt = DateTime.Now;
|
||
}
|
||
|
||
await JournalPageRepository.UpdateRangeAsync(bookPageList);
|
||
}
|
||
|
||
await JournalRepository.Updateable(journalEntity).UpdateColumns(x => new { x.Status, x.DownloadJournalPagePdfName, x.UpdatedAt }).ExecuteCommandAsync();
|
||
|
||
return true;
|
||
});
|
||
}
|
||
public async Task<JournalPageV2Output> DetailAsync(long id)
|
||
{
|
||
var output = await Queryable()
|
||
//.LeftJoin<DotMatrixPage>((a, b) => a.DotMatrixPageId == b.Id)
|
||
.Where(a => a.Id == id)
|
||
.Select(a => new JournalPageV2Output
|
||
{
|
||
JournalId = a.JournalId,
|
||
JournalCatalogId = a.JournalCatalogId,
|
||
JournalPageId = a.Id,
|
||
Url = a.Url,
|
||
Layout = a.Layout,
|
||
PageNum = a.PageNum,
|
||
})
|
||
.FirstAsync();
|
||
|
||
if (output != null)
|
||
{
|
||
var tasks = await JournalPageTaskRepository.Queryable()
|
||
.Where(a => a.JournalPageId == output.JournalPageId)
|
||
.ToListAsync();
|
||
|
||
if (tasks.Count > 0)
|
||
{
|
||
var taskIds = tasks.Select(t => t.Id).ToList();
|
||
var allAnswers = await JournalPageTaskAnswerRepository.Queryable()
|
||
.Where(a => taskIds.Contains(a.JournalPageTaskId))
|
||
.ToListAsync();
|
||
|
||
output.Tasks = tasks.Select(t => new JournalPageTaskV2Output
|
||
{
|
||
Id = t.Id,
|
||
GroupId = t.GroupId,
|
||
No = t.No,
|
||
Type = t.Type,
|
||
Answers = allAnswers
|
||
.Where(a => a.JournalPageTaskId == t.Id)
|
||
.Select(a => new JournalTaskAnswerOutput
|
||
{
|
||
Id = a.Id,
|
||
Answer = a.Answer,
|
||
AnswerUrl = a.AnswerUrL
|
||
}).ToList()
|
||
}).ToList();
|
||
}
|
||
}
|
||
//if (output?.Areas != null)
|
||
// output.Areas = await _JournalPageOtherRepository.Queryable().Where(w => w.JournalPageId == output.JournalPageId).Select<JournalPageOtherOutput>().ToListAsync();
|
||
|
||
return output;
|
||
}
|
||
|
||
public async Task<bool> DeleteAsync(long id)
|
||
{
|
||
return await Deleteable().Where(d => d.Id == id).ExecuteCommandAsync() > 0;
|
||
}
|
||
//public async Task<List<JournalPageNoArticleOutput>> PageNoArticleAsync(long JournalId)
|
||
//{
|
||
// return await Queryable().Where(d => d.JournalId == JournalId)
|
||
// .Where(w => w.JournalArticleId == null)
|
||
// .ToListAsync(x => new JournalPageNoArticleOutput()
|
||
// {
|
||
// Id = x.Id,
|
||
// PageUrl = x.Url,
|
||
// PageNum = x.PageNum
|
||
// });
|
||
//}
|
||
|
||
private int MillimeterToDotMatrixUnit(float millimeterValue)
|
||
{
|
||
return (int)(millimeterValue * 8 / 0.3);
|
||
}
|
||
}
|
||
|
||
public class JournalDotPage_Task
|
||
{
|
||
public int X { get; set; }
|
||
public int Y { get; set; }
|
||
public int W { get; set; }
|
||
public int H { get; set; }
|
||
}
|
||
|
||
public class JournalDotPage_Answer
|
||
{
|
||
public int X { get; set; }
|
||
public int Y { get; set; }
|
||
public int W { get; set; }
|
||
public int H { get; set; }
|
||
}
|
||
|
||
public class JournalDotPage_Area
|
||
{
|
||
public JournalDotPage_Task Task { get; set; }
|
||
|
||
public List<JournalDotPage_Answer> AnswerList { get; set; }
|
||
|
||
public long TaskId { get; set; }
|
||
|
||
}
|