Files
EmailBill/.doc/ai-service-refactoring-summary.md
SunCheng d052ae5197 fix
2026-02-10 17:49:19 +08:00

331 lines
11 KiB
Markdown
Raw Permalink 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.
# 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<string> chunkAction);
Task<TransactionParseResult?> ParseOneLineBillAsync(string text);
// 新增方法
/// <summary>
/// 从邮件正文中使用AI提取交易记录AI兜底方案
/// </summary>
Task<(string card, string reason, decimal amount, decimal balance, TransactionType type, DateTime? occurredAt)[]?>
ParseEmailByAiAsync(string emailBody);
/// <summary>
/// 为分类生成多个SVG图标定时任务使用
/// </summary>
Task<List<string>?> GenerateCategoryIconsAsync(string categoryName, TransactionType categoryType, int iconCount = 5);
/// <summary>
/// 为分类生成单个SVG图标手动触发使用
/// </summary>
Task<string?> GenerateSingleCategoryIconAsync(string categoryName, TransactionType categoryType);
/// <summary>
/// 生成预算执行报告HTML格式
/// </summary>
Task<string?> 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<EmailParseServicesBase> logger,
IOpenAiService openAiService
) : IEmailParseServices
// 修改后
public abstract class EmailParseServicesBase(
ILogger<EmailParseServicesBase> logger,
ISmartHandleService smartHandleService
) : IEmailParseServices
```
调用改为:
```csharp
result = await smartHandleService.ParseEmailByAiAsync(emailContent) ?? [];
```
#### CategoryIconGenerationJob
```csharp
// 修改前
public class CategoryIconGenerationJob(
ITransactionCategoryRepository categoryRepository,
IOpenAiService openAiService,
ILogger<CategoryIconGenerationJob> logger) : IJob
// 修改后
public class CategoryIconGenerationJob(
ITransactionCategoryRepository categoryRepository,
ISmartHandleService smartHandleService,
ILogger<CategoryIconGenerationJob> 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`,实现了代码的集中管理和职责分离。重构后的代码更易维护、更易测试、更易扩展,符合单一职责原则和依赖倒置原则。所有测试用例全部通过,证明重构没有引入功能回归问题。