11 KiB
AI 服务重构总结
title: AI调用统一封装到SmartHandleService
author: AI Assistant
date: 2026-02-10
status: final
category: 重构
概述
本次重构将项目中所有分散的 AI 调用统一封装到 SmartHandleService 中,实现了AI功能的集中管理和统一维护。
重构前的问题
在重构前,项目中有多个地方直接依赖 IOpenAiService 进行 AI 调用:
-
EmailParseServicesBase (
Service/EmailServices/EmailParse/IEmailParseServices.cs)- 邮件解析AI兜底逻辑
- 包含大量重复的类型判断代码
-
CategoryIconGenerationJob (
Service/Jobs/CategoryIconGenerationJob.cs)- 定时任务批量生成分类图标
- 包含复杂的Prompt构建逻辑
-
TransactionCategoryApplication (
Application/TransactionCategoryApplication.cs)- 手动触发单个图标生成
- 包含SVG提取和验证逻辑
-
BudgetService (
Service/Budget/BudgetService.cs)- 生成预算执行报告
- 包含复杂的数据格式化逻辑
存在的问题
- 代码重复:多处存在相似的AI调用代码
- 职责分散:AI相关逻辑散落在不同层级
- 难以维护:修改AI调用逻辑需要改动多个文件
- 测试困难:需要在多个测试类中Mock
IOpenAiService
重构方案
1. 扩展 SmartHandleService 接口
在 Service/AI/SmartHandleService.cs 中新增以下方法:
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
// 修改前
public abstract class EmailParseServicesBase(
ILogger<EmailParseServicesBase> logger,
IOpenAiService openAiService
) : IEmailParseServices
// 修改后
public abstract class EmailParseServicesBase(
ILogger<EmailParseServicesBase> logger,
ISmartHandleService smartHandleService
) : IEmailParseServices
调用改为:
result = await smartHandleService.ParseEmailByAiAsync(emailContent) ?? [];
CategoryIconGenerationJob
// 修改前
public class CategoryIconGenerationJob(
ITransactionCategoryRepository categoryRepository,
IOpenAiService openAiService,
ILogger<CategoryIconGenerationJob> logger) : IJob
// 修改后
public class CategoryIconGenerationJob(
ITransactionCategoryRepository categoryRepository,
ISmartHandleService smartHandleService,
ILogger<CategoryIconGenerationJob> logger) : IJob
调用简化为:
var icons = await smartHandleService.GenerateCategoryIconsAsync(category.Name, category.Type, iconCount: 5);
TransactionCategoryApplication
// 修改前
public class TransactionCategoryApplication(
...
IOpenAiService openAiService,
...) : ITransactionCategoryApplication
// 修改后
public class TransactionCategoryApplication(
...
ISmartHandleService smartHandleService,
...) : ITransactionCategoryApplication
调用简化为:
var svg = await smartHandleService.GenerateSingleCategoryIconAsync(category.Name, category.Type);
BudgetService
// 修改前
public class BudgetService(
...
IOpenAiService openAiService,
...) : IBudgetService
// 修改后
public class BudgetService(
...
ISmartHandleService smartHandleService,
...) : IBudgetService
调用简化为:
var htmlReport = await smartHandleService.GenerateBudgetReportAsync(dataPrompt, year, month);
4. 更新测试代码
更新所有测试类,将 IOpenAiService 的Mock改为 ISmartHandleService:
BudgetStatsTest.csTransactionCategoryApplicationTest.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
受影响的文件清单
新增/修改的文件
- Service/AI/SmartHandleService.cs
- 新增4个方法接口定义
- 新增4个方法实现
- 新增辅助方法:
ParseEmailSingleRecord,DetermineTransactionType
修改的业务服务
-
Service/EmailServices/EmailParse/IEmailParseServices.cs
- 构造函数参数改为
ISmartHandleService - 移除
ParseByAiAsync方法,改为调用smartHandleService.ParseEmailByAiAsync - 保留
DetermineTransactionType为protected方法供子类使用
- 构造函数参数改为
-
Service/EmailServices/EmailParse/EmailParseFormCcsvc.cs
- 构造函数参数改为
ISmartHandleService
- 构造函数参数改为
-
Service/EmailServices/EmailParse/EmailParseForm95555.cs
- 构造函数参数改为
ISmartHandleService
- 构造函数参数改为
-
Service/Jobs/CategoryIconGenerationJob.cs
- 构造函数参数改为
ISmartHandleService - 简化
GenerateIconsForCategoryAsync方法
- 构造函数参数改为
-
Application/TransactionCategoryApplication.cs
- 构造函数参数改为
ISmartHandleService - 简化
GenerateIconAsync方法
- 构造函数参数改为
-
Service/Budget/BudgetService.cs
- 构造函数参数改为
ISmartHandleService - 简化报告生成调用
- 构造函数参数改为
修改的测试文件
-
WebApi.Test/Budget/BudgetStatsTest.cs
- Mock对象改为
ISmartHandleService
- Mock对象改为
-
WebApi.Test/Application/TransactionCategoryApplicationTest.cs
- Mock对象改为
ISmartHandleService
- Mock对象改为
架构图
┌─────────────────────────────────────────────────────────────┐
│ 业务层 (Business Layer) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ BudgetService│ │CategoryApp │ │EmailParse │ │
│ │ │ │ │ │Services │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └─────────────────┼─────────────────┘ │
│ │ │
└───────────────────────────┼────────────────────────────────┘
│ 依赖
┌───────────────────────────┼────────────────────────────────┐
│ AI 服务层 (AI Service Layer) │
│ │ │
│ ┌───────▼────────┐ │
│ │SmartHandleService│ ◄── 统一封装AI调用 │
│ │ (业务级AI功能) │ │
│ └───────┬────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ OpenAiService │ ◄── 底层API调用 │
│ │ (HTTP Client) │ │
│ └────────────────┘ │
└─────────────────────────────────────────────────────────────┘
后续建议
- 添加单元测试:为
SmartHandleService的新方法添加独立的单元测试 - 监控日志:关注AI调用的性能和错误日志
- Prompt优化:基于实际使用反馈持续优化Prompt
- 缓存机制:考虑为图标生成等场景添加缓存
- 重试机制:考虑为AI调用添加重试逻辑
总结
本次重构成功将项目中所有AI调用统一封装到 SmartHandleService,实现了代码的集中管理和职责分离。重构后的代码更易维护、更易测试、更易扩展,符合单一职责原则和依赖倒置原则。所有测试用例全部通过,证明重构没有引入功能回归问题。