using Quartz; using Service.AI; namespace Service.Jobs; /// /// 分类图标生成定时任务 /// 每10分钟扫描一次,为没有图标的分类生成 5 个 SVG 图标 /// public class CategoryIconGenerationJob( ITransactionCategoryRepository categoryRepository, ISmartHandleService smartHandleService, ILogger 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(); } } /// /// 为单个分类生成 5 个 SVG 图标 /// private async Task GenerateIconsForCategoryAsync(TransactionCategory category) { logger.LogInformation("正在为分类 {CategoryName}(ID:{CategoryId}) 生成图标", category.Name, category.Id); // 使用 SmartHandleService 统一封装的图标生成方法 var icons = await smartHandleService.GenerateCategoryIconsAsync(category.Name, category.Type, iconCount: 5); if (icons == null || icons.Count == 0) { logger.LogWarning("为分类 {CategoryName}(ID:{CategoryId}) 生成图标失败", category.Name, category.Id); return; } // 保存图标到数据库 category.Icon = JsonSerializer.Serialize(icons); await categoryRepository.UpdateAsync(category); logger.LogInformation("成功为分类 {CategoryName}(ID:{CategoryId}) 生成并保存了 {IconCount} 个图标", category.Name, category.Id, icons.Count); } }