# AI 服务重构总结 --- title: AI调用统一封装到SmartHandleService author: AI Assistant date: 2026-02-10 status: final category: 重构 --- ## 概述 本次重构将项目中所有分散的 AI 调用统一封装到 `SmartHandleService` 中,实现了AI功能的集中管理和统一维护。 ## 重构前的问题 在重构前,项目中有多个地方直接依赖 `IOpenAiService` 进行 AI 调用: 1. **EmailParseServicesBase** (`Service/EmailServices/EmailParse/IEmailParseServices.cs`) - 邮件解析AI兜底逻辑 - 包含大量重复的类型判断代码 2. **CategoryIconGenerationJob** (`Service/Jobs/CategoryIconGenerationJob.cs`) - 定时任务批量生成分类图标 - 包含复杂的Prompt构建逻辑 3. **TransactionCategoryApplication** (`Application/TransactionCategoryApplication.cs`) - 手动触发单个图标生成 - 包含SVG提取和验证逻辑 4. **BudgetService** (`Service/Budget/BudgetService.cs`) - 生成预算执行报告 - 包含复杂的数据格式化逻辑 ### 存在的问题 - **代码重复**:多处存在相似的AI调用代码 - **职责分散**:AI相关逻辑散落在不同层级 - **难以维护**:修改AI调用逻辑需要改动多个文件 - **测试困难**:需要在多个测试类中Mock `IOpenAiService` ## 重构方案 ### 1. 扩展 SmartHandleService 接口 在 `Service/AI/SmartHandleService.cs` 中新增以下方法: ```csharp public interface ISmartHandleService { // 原有方法 Task SmartClassifyAsync(long[] transactionIds, Action<(string type, string data)> chunkAction); Task AnalyzeBillAsync(string userInput, Action chunkAction); Task ParseOneLineBillAsync(string text); // 新增方法 /// /// 从邮件正文中使用AI提取交易记录(AI兜底方案) /// Task<(string card, string reason, decimal amount, decimal balance, TransactionType type, DateTime? occurredAt)[]?> ParseEmailByAiAsync(string emailBody); /// /// 为分类生成多个SVG图标(定时任务使用) /// Task?> GenerateCategoryIconsAsync(string categoryName, TransactionType categoryType, int iconCount = 5); /// /// 为分类生成单个SVG图标(手动触发使用) /// Task GenerateSingleCategoryIconAsync(string categoryName, TransactionType categoryType); /// /// 生成预算执行报告(HTML格式) /// Task GenerateBudgetReportAsync(string promptWithData, int year, int month); } ``` ### 2. 实现新增方法 #### ParseEmailByAiAsync - 邮件解析 将 `EmailParseServicesBase.ParseByAiAsync` 方法迁移到 `SmartHandleService`: - 保留完整的JSON解析逻辑 - 保留markdown代码块清理逻辑 - 保留单个对象兼容处理 - 将类型判断逻辑保留在基类中供子类使用 #### GenerateCategoryIconsAsync - 批量图标生成 将 `CategoryIconGenerationJob.GenerateIconsForCategoryAsync` 的核心逻辑迁移: - 封装5种不同风格的图标生成Prompt - 处理JSON数组解析和验证 - 统一的错误处理和日志记录 #### GenerateSingleCategoryIconAsync - 单个图标生成 将 `TransactionCategoryApplication.GenerateIconAsync` 的核心逻辑迁移: - 简化的极简风格Prompt - SVG标签提取逻辑 - 统一的返回格式 #### GenerateBudgetReportAsync - 预算报告生成 将 `BudgetService` 中的报告生成逻辑迁移: - 接收完整的Prompt(包含数据和格式要求) - 统一的HTML格式要求 - 统一的错误处理 ### 3. 更新调用方 #### EmailParseServicesBase ```csharp // 修改前 public abstract class EmailParseServicesBase( ILogger logger, IOpenAiService openAiService ) : IEmailParseServices // 修改后 public abstract class EmailParseServicesBase( ILogger logger, ISmartHandleService smartHandleService ) : IEmailParseServices ``` 调用改为: ```csharp result = await smartHandleService.ParseEmailByAiAsync(emailContent) ?? []; ``` #### CategoryIconGenerationJob ```csharp // 修改前 public class CategoryIconGenerationJob( ITransactionCategoryRepository categoryRepository, IOpenAiService openAiService, ILogger logger) : IJob // 修改后 public class CategoryIconGenerationJob( ITransactionCategoryRepository categoryRepository, ISmartHandleService smartHandleService, ILogger logger) : IJob ``` 调用简化为: ```csharp var icons = await smartHandleService.GenerateCategoryIconsAsync(category.Name, category.Type, iconCount: 5); ``` #### TransactionCategoryApplication ```csharp // 修改前 public class TransactionCategoryApplication( ... IOpenAiService openAiService, ...) : ITransactionCategoryApplication // 修改后 public class TransactionCategoryApplication( ... ISmartHandleService smartHandleService, ...) : ITransactionCategoryApplication ``` 调用简化为: ```csharp var svg = await smartHandleService.GenerateSingleCategoryIconAsync(category.Name, category.Type); ``` #### BudgetService ```csharp // 修改前 public class BudgetService( ... IOpenAiService openAiService, ...) : IBudgetService // 修改后 public class BudgetService( ... ISmartHandleService smartHandleService, ...) : IBudgetService ``` 调用简化为: ```csharp var htmlReport = await smartHandleService.GenerateBudgetReportAsync(dataPrompt, year, month); ``` ### 4. 更新测试代码 更新所有测试类,将 `IOpenAiService` 的Mock改为 `ISmartHandleService`: - `BudgetStatsTest.cs` - `TransactionCategoryApplicationTest.cs` ## 重构收益 ### 1. 代码集中管理 - 所有AI调用逻辑集中在 `SmartHandleService` 中 - 便于统一修改Prompt策略 - 便于添加新的AI功能 ### 2. 职责清晰 - `OpenAiService`:底层AI API调用(同步/流式) - `SmartHandleService`:业务级AI功能封装 - 业务服务层:专注业务逻辑,无需关心AI调用细节 ### 3. 易于测试 - 只需Mock `ISmartHandleService` - 测试更简洁,Mock对象更少 - 可以为 `SmartHandleService` 编写独立的单元测试 ### 4. 易于扩展 - 新增AI功能只需在 `SmartHandleService` 中添加方法 - 不影响现有业务代码 - 符合开闭原则(对扩展开放,对修改封闭) ### 5. 代码复用 - 消除了多处重复的AI调用代码 - 统一的错误处理和日志记录 - 统一的返回格式和验证逻辑 ## 测试结果 重构后运行全部211个单元测试,全部通过: ``` 已通过! - 失败: 0,通过: 211,已跳过: 0,总计: 211,持续时间: 22 s ``` ## 受影响的文件清单 ### 新增/修改的文件 1. **Service/AI/SmartHandleService.cs** - 新增4个方法接口定义 - 新增4个方法实现 - 新增辅助方法:`ParseEmailSingleRecord`, `DetermineTransactionType` ### 修改的业务服务 2. **Service/EmailServices/EmailParse/IEmailParseServices.cs** - 构造函数参数改为 `ISmartHandleService` - 移除 `ParseByAiAsync` 方法,改为调用 `smartHandleService.ParseEmailByAiAsync` - 保留 `DetermineTransactionType` 为protected方法供子类使用 3. **Service/EmailServices/EmailParse/EmailParseFormCcsvc.cs** - 构造函数参数改为 `ISmartHandleService` 4. **Service/EmailServices/EmailParse/EmailParseForm95555.cs** - 构造函数参数改为 `ISmartHandleService` 5. **Service/Jobs/CategoryIconGenerationJob.cs** - 构造函数参数改为 `ISmartHandleService` - 简化 `GenerateIconsForCategoryAsync` 方法 6. **Application/TransactionCategoryApplication.cs** - 构造函数参数改为 `ISmartHandleService` - 简化 `GenerateIconAsync` 方法 7. **Service/Budget/BudgetService.cs** - 构造函数参数改为 `ISmartHandleService` - 简化报告生成调用 ### 修改的测试文件 8. **WebApi.Test/Budget/BudgetStatsTest.cs** - Mock对象改为 `ISmartHandleService` 9. **WebApi.Test/Application/TransactionCategoryApplicationTest.cs** - Mock对象改为 `ISmartHandleService` ## 架构图 ``` ┌─────────────────────────────────────────────────────────────┐ │ 业务层 (Business Layer) │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ BudgetService│ │CategoryApp │ │EmailParse │ │ │ │ │ │ │ │Services │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ │ └─────────────────┼─────────────────┘ │ │ │ │ └───────────────────────────┼────────────────────────────────┘ │ 依赖 ┌───────────────────────────┼────────────────────────────────┐ │ AI 服务层 (AI Service Layer) │ │ │ │ │ ┌───────▼────────┐ │ │ │SmartHandleService│ ◄── 统一封装AI调用 │ │ │ (业务级AI功能) │ │ │ └───────┬────────┘ │ │ │ │ │ ┌───────▼────────┐ │ │ │ OpenAiService │ ◄── 底层API调用 │ │ │ (HTTP Client) │ │ │ └────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ## 后续建议 1. **添加单元测试**:为 `SmartHandleService` 的新方法添加独立的单元测试 2. **监控日志**:关注AI调用的性能和错误日志 3. **Prompt优化**:基于实际使用反馈持续优化Prompt 4. **缓存机制**:考虑为图标生成等场景添加缓存 5. **重试机制**:考虑为AI调用添加重试逻辑 ## 总结 本次重构成功将项目中所有AI调用统一封装到 `SmartHandleService`,实现了代码的集中管理和职责分离。重构后的代码更易维护、更易测试、更易扩展,符合单一职责原则和依赖倒置原则。所有测试用例全部通过,证明重构没有引入功能回归问题。