2026-02-10 17:49:19 +08:00
|
|
|
|
using Quartz;
|
2026-02-02 11:07:34 +08:00
|
|
|
|
using Service.AI;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Service.Jobs;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 分类图标生成定时任务
|
|
|
|
|
|
/// 每10分钟扫描一次,为没有图标的分类生成 5 个 SVG 图标
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class CategoryIconGenerationJob(
|
|
|
|
|
|
ITransactionCategoryRepository categoryRepository,
|
2026-02-10 17:49:19 +08:00
|
|
|
|
ISmartHandleService smartHandleService,
|
2026-02-02 11:07:34 +08:00
|
|
|
|
ILogger<CategoryIconGenerationJob> logger) : IJob
|
|
|
|
|
|
{
|
|
|
|
|
|
private static readonly SemaphoreSlim _semaphore = new(1, 1);
|
|
|
|
|
|
|
|
|
|
|
|
public async Task Execute(IJobExecutionContext context)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 尝试获取锁,如果失败则跳过本次执行
|
|
|
|
|
|
if (!await _semaphore.WaitAsync(0))
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("上一个分类图标生成任务尚未完成,跳过本次执行");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("开始执行分类图标生成任务");
|
|
|
|
|
|
|
|
|
|
|
|
// 查询所有分类,然后过滤出没有图标的
|
|
|
|
|
|
var allCategories = await categoryRepository.GetAllAsync();
|
|
|
|
|
|
var categoriesWithoutIcon = allCategories
|
|
|
|
|
|
.Where(c => string.IsNullOrEmpty(c.Icon))
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
if (categoriesWithoutIcon.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("所有分类都已有图标,跳过本次任务");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("发现 {Count} 个分类没有图标,开始生成", categoriesWithoutIcon.Count);
|
|
|
|
|
|
|
|
|
|
|
|
// 为每个分类生成图标
|
|
|
|
|
|
foreach (var category in categoriesWithoutIcon)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await GenerateIconsForCategoryAsync(category);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogError(ex, "为分类 {CategoryName}(ID:{CategoryId}) 生成图标失败",
|
|
|
|
|
|
category.Name, category.Id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("分类图标生成任务执行完成");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogError(ex, "分类图标生成任务执行出错");
|
|
|
|
|
|
throw;
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
_semaphore.Release();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 为单个分类生成 5 个 SVG 图标
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task GenerateIconsForCategoryAsync(TransactionCategory category)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("正在为分类 {CategoryName}(ID:{CategoryId}) 生成图标",
|
|
|
|
|
|
category.Name, category.Id);
|
|
|
|
|
|
|
2026-02-10 17:49:19 +08:00
|
|
|
|
// 使用 SmartHandleService 统一封装的图标生成方法
|
|
|
|
|
|
var icons = await smartHandleService.GenerateCategoryIconsAsync(category.Name, category.Type, iconCount: 5);
|
2026-02-02 11:07:34 +08:00
|
|
|
|
|
2026-02-10 17:49:19 +08:00
|
|
|
|
if (icons == null || icons.Count == 0)
|
2026-02-02 11:07:34 +08:00
|
|
|
|
{
|
2026-02-10 17:49:19 +08:00
|
|
|
|
logger.LogWarning("为分类 {CategoryName}(ID:{CategoryId}) 生成图标失败",
|
|
|
|
|
|
category.Name, category.Id);
|
2026-02-02 11:07:34 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-10 17:49:19 +08:00
|
|
|
|
// 保存图标到数据库
|
|
|
|
|
|
category.Icon = JsonSerializer.Serialize(icons);
|
|
|
|
|
|
await categoryRepository.UpdateAsync(category);
|
2026-02-02 11:07:34 +08:00
|
|
|
|
|
2026-02-10 17:49:19 +08:00
|
|
|
|
logger.LogInformation("成功为分类 {CategoryName}(ID:{CategoryId}) 生成并保存了 {IconCount} 个图标",
|
|
|
|
|
|
category.Name, category.Id, icons.Count);
|
2026-02-02 11:07:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|