refactor,feat: 批量代码重构与新增业务模块

1. 重命名枚举项与实体字段,修正类型引用
2. 新增IsNull扩展方法与多项字符串处理扩展
3. 新增大量业务DTO、服务接口与枚举定义
4. 重构RabbitMQ服务实现,替换旧版消息队列组件
5. 优化签到服务的宠物喂养事务逻辑
6. 移除冗余的项目引用与旧版消息队列代码
7. 新增Excel导出、导入模板相关工具方法
This commit is contained in:
glz
2026-06-11 17:01:24 +08:00
parent ae4cd627df
commit e493c85d08
57 changed files with 4477 additions and 399 deletions

View File

@ -68,7 +68,7 @@ public class CheckInService(
.Where(p => p.UserId == userId && !p.IsDeleted)
.FirstAsync();
// 6. 事务执行签到相关写操作
// 6. 事务执行签到相关写操作(含宠物喂养,统一事务)
var result = new CheckInOutput();
await checkInRecordRepository.UseTranAsync(async () =>
@ -107,6 +107,17 @@ public class CheckInService(
Description = $"签到奖励(连续{consecutiveDays}天)"
});
// 6d. 如果有活跃宠物,喂养宠物(同一事务内)
FeedPetOutput? feedResult = null;
if (pet != null && pet.Status == (int)UserPetStatusEnum.Active && growthReward > 0)
{
feedResult = await petService.FeedPetInTranAsync(userId, new FeedPetInput
{
PetId = pet.Id,
GrowthPoints = growthReward
});
}
// 构建返回结果
result.RecordId = (long)recordId;
result.CheckInDate = today;
@ -116,41 +127,17 @@ public class CheckInService(
result.PointsBalance = pointsResult.NewBalance;
result.GrowthPointsBalance = newGrowthBalance;
result.HasPet = pet != null;
});
// 7. 如果用户有活跃宠物,调用 PetService 喂养(含进化检查),独立事务
if (pet != null && pet.Status == (int)UserPetStatusEnum.Active && growthReward > 0)
{
try
if (feedResult != null)
{
var feedResult = await petService.FeedPetAsync(userId, new FeedPetInput
{
PetId = pet.Id,
GrowthPoints = growthReward
});
result.HasEvolved = feedResult.HasEvolved;
result.EvolvedStageName = feedResult.EvolvedStageName;
logger.LogInformation("签到成长值已喂养宠物PetId: {PetId}, 进化: {HasEvolved}",
pet.Id, feedResult.HasEvolved);
}
catch (Exception ex)
{
logger.LogWarning(ex, "签到后喂养宠物失败PetId: {PetId},将创建补偿任务", pet.Id);
});
await compensationTaskService.CreateTaskAsync(new CreateCompensationTaskInput
{
TaskType = CompensationTaskTypeEnum.PetFeeding,
BusinessSource = "CheckIn",
BusinessId = result.RecordId.ToString(),
UserId = userId,
Payload = new { PetId = pet.Id, GrowthPoints = growthReward },
ErrorMessage = ex.Message,
ErrorSource = "CheckInService.CheckInAsync → PetService.FeedPetAsync",
MaxRetries = 3
});
}
if (pet != null && pet.Status == (int)UserPetStatusEnum.Active && growthReward > 0)
{
logger.LogInformation("签到成长值已喂养宠物PetId: {PetId}, 进化: {HasEvolved}",
pet.Id, result.HasEvolved);
}
logger.LogInformation("用户签到成功UserId: {UserId}, 连续{Days}天, 积分+{Points}, 成长值+{Growth}",
@ -339,6 +326,17 @@ public class CheckInService(
.ExecuteCommandAsync();
}
// 如果有活跃宠物,喂养宠物(同一事务内)
FeedPetOutput? feedResult = null;
if (pet != null && pet.Status == (int)UserPetStatusEnum.Active && growthReward > 0)
{
feedResult = await petService.FeedPetInTranAsync(userId, new Models.Dto.Pet.FeedPetInput
{
PetId = pet.Id,
GrowthPoints = growthReward
});
}
result.RecordId = (long)recordId;
result.CheckInDate = targetDate;
result.ConsecutiveDays = 0;
@ -347,27 +345,17 @@ public class CheckInService(
result.PointsBalance = pointsResult.NewBalance;
result.GrowthPointsBalance = newGrowthBalance;
result.HasPet = pet != null;
});
// 如果有活跃宠物,喂养成长值
if (pet != null && pet.Status == (int)UserPetStatusEnum.Active && growthReward > 0)
{
try
if (feedResult != null)
{
var feedResult = await petService.FeedPetAsync(userId, new Models.Dto.Pet.FeedPetInput
{
PetId = pet.Id,
GrowthPoints = growthReward
});
result.HasEvolved = feedResult.HasEvolved;
result.EvolvedStageName = feedResult.EvolvedStageName;
}
catch (Exception ex)
{
logger.LogWarning(ex, "补签后喂养宠物失败PetId: {PetId}", pet.Id);
// 补偿机制:如需可在此创建补偿任务
}
});
if (pet != null && pet.Status == (int)UserPetStatusEnum.Active && growthReward > 0)
{
logger.LogInformation("补签成长值已喂养宠物PetId: {PetId}, 进化: {HasEvolved}",
pet.Id, result.HasEvolved);
}
logger.LogInformation("补签成功UserId: {UserId}, Date: {Date}, 积分+{Points}, 成长值+{Growth}",

View File

@ -0,0 +1,526 @@

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;
//}
}

View File

@ -0,0 +1,211 @@

using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
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.Journal;
using QYZH.InteractiveMagazine.Models.Entity;
using QYZH.InteractiveMagazine.Models.Enum;
using QYZH.InteractiveMagazine.Repository;
using System.Diagnostics;
using Yitter.IdGenerator;
namespace QYZH.InteractiveMagazine.Service;
public class JournalPageService(BaseRepository<Journal> JournalRepository,
OssService ossService,
IHttpClientFactory httpClientFactory,
IConfiguration configuration,
IRabbitMQService rabbitMqService,
BaseRepository<JournalPageTask> JournalPageTaskRepository) : 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(), "未找到书本");
BusinessException.ThrowIf(Journal?.Status == (int)JournalStatusEnum.Archive, "书籍已归档不能修改");
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(), "创建页失败");
//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, "不存在此页");
var Journal = await JournalRepository.GetByIdAsync(page.JournalId);
BusinessException.ThrowIf(Journal == null, "不存在此书");
BusinessException.ThrowIf(Journal?.Status == (int)JournalStatusEnum.Archive, "书籍已归档不能修改");
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)
{
//这里不能修改 Exchange = "icr.direct" 否则会报错
var msRes = await rabbitMqService.SendAsync(new RabbitMQSendParam { Exchange = "icr.direct", Queue = "mq.Journal.updatePageNo.queue", RoutingKey = "mq.Journal.updatePageNo", Data = JournalId });//发生消息
return msRes;
}
/// <summary>
/// 打印书页(全部)
/// </summary>
/// <param name="JournalId"></param>
/// <returns></returns>
public async Task<bool> PrintJournalPageAsync(long JournalId)
{
//这里不能修改 Exchange = "icr.direct" 否则会报错
var msRes = await rabbitMqService.SendAsync(new RabbitMQSendParam { Exchange = "icr.direct", Queue = "mq.Journal.printJournalPage.queue", RoutingKey = "mq.Journal.printJournalPage", Data = JournalId });//发生消息
return msRes;
}
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)
{
output.Tasks = await JournalPageTaskRepository.Queryable()//.Select<IcrJournalAssignTask>()
.Where(a => a.JournalPageId == output.JournalPageId)
.Select(a => new JournalPageTaskV2Output
{
Id = a.Id,
No = a.No,
Type = (TaskBankTypeEnum)a.Type,
Options = a.Options,
Answers = a.Answer,
Analysis = a.Analysis,
//Assign = SqlFunc.Subqueryable<IcrJournalAssignTask>().Where(m => a.Id == m.JournalPageTaskId).Any()
})
.ToListAsync();
}
//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; }
}

View File

@ -0,0 +1,176 @@
using Mapster;
using QYZH.InteractiveMagazine.Common.Extensions;
using QYZH.InteractiveMagazine.Infrastructure.OSS;
using QYZH.InteractiveMagazine.IService;
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 JournalPageTaskService(BaseRepository<Journal> journalRepository, OssService ossService) : BaseRepository<JournalPageTask>, IJournalPageTaskService
{
public async Task<long?> InsertAsync(JournalPageTaskAddInput input)
{
var JournalStatus = await journalRepository.Queryable().Where(w => w.Id == input.JournalId).Select(s => s.Status).FirstAsync();
BusinessException.ThrowIf(JournalStatus == (int)JournalStatusEnum.Archive, "书籍已归档不能添加题目");
var map = input.Adapt<JournalPageTask>();
map.Id = YitIdHelper.NextId();
map.GroupId = map.Id;
var no = input.No.Split('-').Select(int.Parse).ToArray();
if (no.Last() >= 2)
{
var startNo = $"{no[0]}-{no[1]}-{no[2]}-1";
var groupTask = await Queryable().Where(w => w.No == startNo && w.JournalId == input.JournalId).FirstAsync();
BusinessException.ThrowIf(groupTask.IsNull(), "未找到关联跨页的第一部分");
map.GroupId = groupTask.GroupId;
map.Score = groupTask.Score;
map.AnswerTime = groupTask.AnswerTime;
map.Type = groupTask.Type;
}
var data = await base.InsertAsync(map);
return map?.Id;
}
public async Task<bool> UpdateAsync(JournalPageTaskUpdateInput input)
{
var JournalStatus = await journalRepository.Queryable().Where(w => w.Id == input.JournalId).Select(s => s.Status).FirstAsync();
BusinessException.ThrowIf(JournalStatus == (int)JournalStatusEnum.Archive, "书籍已归档不能修改题目");
var task = await base.Queryable().Where(w => w.Id == input.Id).FirstAsync();
BusinessException.ThrowIf(task.IsNull(), "未找到关联的题号");
var sameTask = await base.Queryable().Where(w => w.No == input.No && w.JournalId == input.JournalId).FirstAsync();
BusinessException.ThrowIf(sameTask.IsNotEmpty(), "已经存在相同题号题目");
var key = $"Journal/{task.JournalId}/{task.JournalPageId}/{task.GroupId}";
//if (task.AnswerUrl != input.AnswerUrl && input.AnswerUrl.NotNull())
//{
// ossService.CopyObject(input.AnswerUrl.RemoveDomain(), $"{key}/answer.{input.AnswerUrl.ToExtension()}");
// input.AnswerUrl = $"{key}/answer.{input.AnswerUrl.ToExtension()}";
//}
//if (task.AnalysisUrl != input.AnalysisUrl && input.AnalysisUrl.NotNull())
//{
// ossService.CopyObject(input.AnalysisUrl.RemoveDomain(), $"{key}/analysis.{input.AnalysisUrl.ToExtension()}");
// input.AnalysisUrl = $"{key}/analysis.{input.AnalysisUrl.ToExtension()}";
//}
//if (task.VideoUrl != input.VideoUrl && input.VideoUrl.NotNull())
//{
// ossService.CopyObject(input.VideoUrl.RemoveDomain(), $"{key}/video.{input.VideoUrl.ToExtension()}");
// input.VideoUrl = $"{key}/video.{input.VideoUrl.ToExtension()}";
//}
input.Task = Deal(task.Task, input.Task, key, "task");
input.Answer = Deal(task.Answer, input.Answer, key, "answer");
input.Analysis = Deal(task.Analysis, input.Analysis, key, "analysis");
task.Type = input.Type;
task.Task = input.Task;
task.Options = input.Options;
task.Answer = input.Answer;
//task.AnswerUrl = input.AnswerUrl;
task.Analysis = input.Analysis;
//task.AnalysisUrl = input.AnalysisUrl;
task.Score = input.Score;
task.AnswerTime = input.AnswerTime;
// task.VideoUrl = input.VideoUrl;
task.No = input.No;
var no = input.No.Split('-').Select(int.Parse).ToArray();
if (no.Last() >= 2)
{
var startNo = $"{no[0]}-{no[1]}-{no[2]}-1";
var groupTask = await Queryable().Where(w => w.No == startNo && w.JournalId == input.JournalId).FirstAsync();
BusinessException.ThrowIf(groupTask.IsNull(), "未找到关联跨页的第一部分");
task.GroupId = groupTask.GroupId;
task.Score = groupTask.Score;
task.AnswerTime = groupTask.AnswerTime;
task.Type = groupTask.Type;
}
//task.JournalId = input.JournalId;
//task.JournalPageId = input.JournalPageId;
// 这里不能直接用Set,因为jsonmap无法赋值 不知道为啥
return await base.Updateable(task).ExecuteCommandAsync() > 0;
}
public async Task<bool> KeywordAnalysisAsync(JournalPageTaskKeywordAnalysis input)
{
return await base.Updateable().SetColumns(s => s.KeywordAnalysis, input.KeywordAnalysis).Where(w => w.GroupId == input.Id).ExecuteCommandAsync() > 0;
}
public async Task<JournalPageTaskOutput> DetailAsync(long id)
{
var data = await base.Queryable()
.Where(w => w.Id == id)
.Select(w => new JournalPageTaskOutput()
, true).FirstAsync();
return data;
}
public async Task<bool> DeleteAsync(long id)
{
var task = await base.Queryable().Where(w => w.Id == id).FirstAsync();
BusinessException.ThrowIf(task.IsNull(), "问题不存在");
var key = $"Journal/{task.JournalId}/{task.JournalPageId}/{id}";
ossService.DeleteObject(key);
return await base.DeleteAsync(d => d.Id == id);
}
public async Task<bool> ComplementAsync(JournalPageTaskComplementInput input)
{
var task = await base.Queryable().Where(w => w.Id == input.Id).FirstAsync();
BusinessException.ThrowIf(task.IsNull(), "问题不存在");
if (input.AudioUrl.NotNull())
{
var key = $"Journal/{task.JournalId}/{task.JournalPageId}/{task.Id}/audio.{input.AudioUrl.ToExtension()}";
ossService.CopyObject(input.AudioUrl, key);
input.AudioUrl = key;
}
if (input.PointsUrl.NotNull())
{
var key = $"Journal/{task.JournalId}/{task.JournalPageId}/{task.Id}/points.json";
ossService.CopyObject(input.PointsUrl, key);
input.PointsUrl = key;
}
return await Updateable(input.Adapt<JournalPageTask>()).Where(w => w.Id == input.Id).ExecuteCommandAsync() > 0;
}
private string Deal(string oldUrls, string newUrls, string key, string type)
{
// 如果 newUrls 为空,则直接返回
if (newUrls.IsNull()) return null;
// 处理 oldUrls 为 null 的情况,将其视为空字符串
var oldTaskUrls = oldUrls.AllUrl().Distinct();
var taskUrls = newUrls.AllUrl().Distinct().Select(s => s.RemoveDomain());
// 删除不存在的(存在于旧集合但不在新集合中)
var removedUrls = oldTaskUrls.Except(taskUrls).ToList();
ossService.DeleteObjects(removedUrls);
// 添加新增的(存在于新集合但不在旧集合中)
var addedUrls = taskUrls.Except(oldTaskUrls).ToList();
foreach (var item in addedUrls)
{
var value = $"{key}/{type}/{YitIdHelper.NextId()}.{item.ToExtension()}";
ossService.CopyObject(item, value);
newUrls = newUrls.Replace(item, value);
}
return newUrls;
}
}

View File

@ -0,0 +1,313 @@
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<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></returns>
public async Task<BaseResponse<bool>> EditAsync(JournalEditDto input)
{
var Journal = await base.GetByIdAsync(input.Id);
BusinessException.ThrowIf(Journal.IsNull(), "不存在的Id");
BusinessException.ThrowIf(Journal.Status == (int)JournalStatusEnum.Archive, "已归档不可编辑");
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不存在");
BusinessException.ThrowIf(base.Queryable().Any(w => ids.Contains(w.Id) && w.Status == (int)JournalStatusEnum.Archive), "已归档不可删除");
var result = await UseTranAsync(async () =>
{
await base.DeleteAsync(d => ids.Contains(d.Id));
await JournalPageRepository.DeleteAsync(d => ids.Contains(d.JournalId));
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(), "不存在书");
BusinessException.ThrowIf(Journal.Status == (int)JournalStatusEnum.Codeing, "正在生成中...");
var pages = await JournalPageRepository.Queryable().Where(w => w.JournalId == id).ToListAsync();
BusinessException.ThrowIf(pages.IsNull(), "不存在页");
var output = new DotMatrixOutput()
{
Method = "Journal",
Name = Journal.Name,
NoteId = id,
PdfUrl = Journal.PdfUrl
};
//铺码业务后续调整
//var exist = pages.All(c => c.PageNo.IsNull());
//if (!exist)
//{
// var dotIds = pages.Select(s => s.DotId).Distinct();
// var dots = await dotFileRepository.Queryable().Where(w => dotIds.Contains(w.Id)).ToListAsync();
// output.DotMatrixs = dots.Select(s => new DotMatrixMqOutput() { DotId = s.Id, FileAddress = s.FileAddress }).ToList();
// foreach (var page in pages)
// {
// var find = output.DotMatrixs.Find(f => f.DotId == page.DotId);
// find!.Pages.Add(new DotMatrixPageDto()
// {
// PageNo = page.PageNo,
// PageNum = 1
// });
// }
// var ok = await producingService.SendAsync(new RabbitMQSendParam() { Exchange = "icr.direct", RoutingKey = "mq.dotmatrix", Data = output });
// BusinessException.ThrowIf(!ok, "消息发送失败,生成失败");
// await base.Updateable().SetColumns(s => s.Status, JournalStatusEnum.Codeing).Where(w => w.Id == id).ExecuteCommandAsync();
//}
//else
//{
// //var first = await dotFileDetailRepository.Queryable().Where(w => w.IsUse == false).GroupBy(g => g.DotId).Having(h => h.Count() >= pages.Count).FirstAsync(a => new { DotId = a.Key, Count = a.Count() });
// var first = await dotFileDetailRepository.Queryable().Where(w => w.IsUse == false).GroupBy(g => g.DotId).Having(h => SqlFunc.AggregateCount(h.Id) >= pages.Count).Select(a => new { DotId = a.DotId, Count = SqlFunc.AggregateCount(a.Id) }).FirstAsync();
// BusinessException.ThrowIf(first.IsNull(), "点阵页码不足,生成失败");
// var dotFile = await dotFileRepository.Queryable().Where(w => w.Id == first.DotId).FirstAsync();
// if (dotFile != null)
// dotFile.Details = dotFileDetailRepository.Queryable().Where(w => !w.IsUse && w.DotId == dotFile.Id).Take(pages.Count()).ToList();
// var dotMatrix = new DotMatrixMqOutput()
// {
// DotId = dotFile.Id,
// FileAddress = dotFile.FileAddress,
// };
// output.DotMatrixs.Add(dotMatrix);
// foreach (var item in dotFile.Details)
// {
// var index = dotFile.Details.IndexOf(item);
// pages[index].PageNo = item.PageName;
// pages[index].DotId = dotFile.Id;
// dotMatrix.Pages.Add(new DotMatrixPageDto()
// {
// PageNo = item.PageName,
// PageNum = 1
// });
// }
// await UseTranAsync(async () =>
// {
// // 更新使用数量
// //await dotFileRepository.Updateable().SetColumns(s => s.TotalUse, dotFile.TotalUse + pages.Count).Where(w => w.Id == dotFile.Id).ExecuteCommandAsync();
// // 更新使用
// await dotFileDetailRepository.Updateable().SetColumns(s => s.IsUse, true).Where(w => dotFile.Details.Select(s => s.Id).Contains(w.Id)).ExecuteCommandAsync();
// // 更新页码
// await dotMatrixPageRepository.UpdateRangeAsync(pages);
// await Updateable().SetColumns(s => s.Status, JournalStatusEnum.CodeSuccess).Where(w => w.Id == id).ExecuteCommandAsync();
// //发送消息
// //var ok = await producingService.SendAsync(new RabbitMQSendParam() { Exchange = "icr.direct", RoutingKey = "mq.dotmatrix", Data = output });
// //BusinessException.ThrowIf(!ok, "消息发送失败,生成失败");
// });
//}
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, "未添加任何题目,无法归档");
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())}未保存,无法归档");
var res = await base.Updateable().SetColumns(s => s.Status, status).Where(w => w.Id == id).ExecuteCommandAsync() > 0;
return res;
}
}

View File

@ -253,79 +253,103 @@ public class PetService(
throw new BusinessException("宠物未激活,无法喂养", 400);
}
// 事务保证一致性
await UseTranAsync(async () =>
{
result = await FeedPetInTranAsync(userId, input);
});
logger.LogInformation("喂养宠物成功PetId: {PetId}, 成长值: {Before} -> {After}, 进化: {HasEvolved}",
input.PetId, result.GrowthBefore, result.GrowthAfter, result.HasEvolved);
return result;
}
/// <summary>
/// 喂养宠物(无事务,需在外部事务中调用)
/// </summary>
public async Task<FeedPetOutput> FeedPetInTranAsync(long userId, FeedPetInput input)
{
// 查询宠物
var pet = await petRepository.GetByIdAsync(input.PetId);
if (pet == null || pet.IsDeleted)
throw new BusinessException("宠物不存在", 404);
if (pet.UserId != userId)
throw new BusinessException("无权操作该宠物", 403);
if (pet.Status != (int)UserPetStatusEnum.Active)
throw new BusinessException("宠物未激活,无法喂养", 400);
var growthBefore = pet.GrowthPoints;
var growthAfter = growthBefore + input.GrowthPoints;
var hasEvolved = false;
string? evolvedStageName = null;
// 事务保证一致性
await UseTranAsync(async () =>
// 累加成长值和喂养次数
var updateResult = await petRepository.Context.Updateable<UserPet>()
.SetColumns(p => p.GrowthPoints == growthAfter)
.SetColumns(p => p.FeedingCount == p.FeedingCount + 1)
.SetColumns(p => p.UpdatedAt == DateTime.Now)
.SetColumns(p => p.UpdatedBy == userId.ToString())
.Where(p => p.Id == input.PetId)
.ExecuteCommandAsync();
if (updateResult <= 0)
{
// 累加成长值和喂养次数
var updateResult = await petRepository.Context.Updateable<UserPet>()
.SetColumns(p => p.GrowthPoints == growthAfter)
.SetColumns(p => p.FeedingCount == p.FeedingCount + 1)
throw new BusinessException("更新宠物成长值失败", 500);
}
// 进化检查查找下一阶段进化形态PreviousEvolutionId 类型为 long?
var nextEvolution = await petEvolutionRepository.Queryable()
.Where(e => e.PreviousEvolutionId == pet.CurrentEvolutionId
&& e.RequiredGrowth <= growthAfter
&& e.Status == (int)DefaultStatusEnum.Active)
.OrderBy(e => e.RequiredGrowth, OrderByType.Desc)
.FirstAsync();
if (nextEvolution != null)
{
// 触发进化
var evolveResult = await petRepository.Context.Updateable<UserPet>()
.SetColumns(p => p.CurrentEvolutionId == nextEvolution.Id)
.SetColumns(p => p.UpdatedAt == DateTime.Now)
.SetColumns(p => p.UpdatedBy == userId.ToString())
.Where(p => p.Id == input.PetId)
.ExecuteCommandAsync();
if (updateResult <= 0)
if (evolveResult > 0)
{
throw new BusinessException("更新宠物成长值失败", 500);
hasEvolved = true;
evolvedStageName = nextEvolution.StageName;
logger.LogInformation("宠物进化成功PetId: {PetId}, 新形态: {StageName} (Level {StageLevel})",
input.PetId, nextEvolution.StageName, nextEvolution.StageLevel);
}
}
// 进化检查查找下一阶段进化形态PreviousEvolutionId 类型为 long?
var nextEvolution = await petEvolutionRepository.Queryable()
.Where(e => e.PreviousEvolutionId == pet.CurrentEvolutionId
&& e.RequiredGrowth <= growthAfter
&& e.Status == (int)DefaultStatusEnum.Active)
.OrderBy(e => e.RequiredGrowth, OrderByType.Desc)
.FirstAsync();
// 写入喂养记录
var record = new PetFeedingRecord
{
PetId = input.PetId,
UserId = userId,
PointsUsed = 0, // 预留:后期可扩展为消耗积分喂养
GrowthChange = input.GrowthPoints,
GrowthBefore = growthBefore,
GrowthAfter = growthAfter,
Type = PetFeedingRecordTypeEnum.Normal,
Status = (int)PetFeedingRecordStatusEnum.Success,
IsDeleted = false,
CreatedBy = userId.ToString(),
CreatedAt = DateTime.Now,
UpdatedBy = userId.ToString(),
UpdatedAt = DateTime.Now
};
if (nextEvolution != null)
{
// 触发进化
var evolveResult = await petRepository.Context.Updateable<UserPet>()
.SetColumns(p => p.CurrentEvolutionId == nextEvolution.Id)
.SetColumns(p => p.UpdatedAt == DateTime.Now)
.SetColumns(p => p.UpdatedBy == userId.ToString())
.Where(p => p.Id == input.PetId)
.ExecuteCommandAsync();
if (evolveResult > 0)
{
hasEvolved = true;
evolvedStageName = nextEvolution.StageName;
logger.LogInformation("宠物进化成功PetId: {PetId}, 新形态: {StageName} (Level {StageLevel})",
input.PetId, nextEvolution.StageName, nextEvolution.StageLevel);
}
}
// 写入喂养记录
var record = new PetFeedingRecord
{
PetId = input.PetId,
UserId = userId,
PointsUsed = 0, // 预留:后期可扩展为消耗积分喂养
GrowthChange = input.GrowthPoints,
GrowthBefore = growthBefore,
GrowthAfter = growthAfter,
Type = PetFeedingRecordTypeEnum.Normal,
Status = (int)PetFeedingRecordStatusEnum.Success,
IsDeleted = false,
CreatedBy = userId.ToString(),
CreatedAt = DateTime.Now,
UpdatedBy = userId.ToString(),
UpdatedAt = DateTime.Now
};
var insertResult = await feedingRecordRepository.InsertAsync(record);
if (!insertResult)
{
throw new BusinessException("写入喂养记录失败", 500);
}
});
var insertResult = await feedingRecordRepository.InsertAsync(record);
if (!insertResult)
{
throw new BusinessException("写入喂养记录失败", 500);
}
logger.LogInformation("喂养宠物成功PetId: {PetId}, 成长值: {Before} -> {After}, 进化: {HasEvolved}",
input.PetId, growthBefore, growthAfter, hasEvolved);

View File

@ -158,7 +158,7 @@ public class UsersService(
if (journalDict.TryGetValue(item.JournalId, out var journal))
{
item.JournalTitle = journal.Title;
item.CoverImageUrl = journal.CoverImageUrl;
item.CoverImageUrl = journal.Cover;
}
}
}