Files
QYZH.InteractiveMagazine/QYZH.InteractiveMagazine.Service/JournalService.cs
glz bdaa6a0dc8 refactor: 统一业务异常处理,标准化结果码和错误响应
1.  新增并完善ResultCode枚举,补充标准HTTP状态码对应的业务状态码
2.  重构BusinessException,新增基于ResultCode的构造函数和ThrowIf扩展方法
3.  替换所有硬编码的HTTP状态码为统一的ResultCode枚举
4.  优化全局异常中间件,根据业务状态码映射对应HTTP状态码并规范化JSON响应
5.  修复OssImageHelper和AutoDotCodeConsumer中的OSS文件处理逻辑
6.  新增用户答题快照实体类
7.  清理废弃的宠物模块迁移脚本
2026-06-29 16:34:26 +08:00

323 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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, ILogger<JournalService> logger) : BaseRepository<Journal>, IJournalService
{
/// <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(),
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;
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;
}
}