Files
EmailBill/.doc/api-refactoring-transaction-statistics.md

374 lines
11 KiB
Markdown
Raw Normal View History

2026-02-10 17:49:19 +08:00
---
title: TransactionStatistics 接口重构文档
author: AI Assistant
date: 2026-02-10
status: final
category: API重构
---
# TransactionStatistics 接口重构文档
## 概述
本次重构针对 `TransactionStatisticsController` 中的接口进行了统一优化,采用**方案一(激进重构)**,将原有 9 个接口精简为 4 个核心接口,同时保留旧接口用于向后兼容(标记为 `[Obsolete]`)。
**重构时间**: 2026-02-10
**影响范围**: Backend (Service/Application/Controller) + Frontend (API/Views)
---
## 一、重构目标
### 问题分析
1. **接口冗余**: 存在多组功能重复的接口对
- `GetMonthlyStatistics` vs `GetRangeStatistics`
- `GetDailyStatistics` vs `GetWeeklyStatistics`
- `GetCategoryStatistics` vs `GetCategoryStatisticsByDateRange`
2. **参数不统一**: 部分接口使用 `(year, month)`,部分使用 `(startDate, endDate)`
3. **未使用接口**: `GetReasonGroups` 在前端完全未被调用
### 重构原则
- ✅ 统一使用日期范围查询 (`startDate`, `endDate`)
- ✅ 保留旧接口用于向后兼容,标记为 `[Obsolete]`
- ✅ 删除未使用的接口
- ✅ 前端优先迁移到新接口
---
## 二、接口变更对照表
### 新接口(推荐使用)
| 新接口名称 | 路径 | 参数 | 说明 | 替代的旧接口 |
|-----------|------|------|------|------------|
| `GetDailyStatisticsByRange` | `/TransactionStatistics/GetDailyStatisticsByRange` | startDate, endDate, savingClassify? | 按日期范围获取每日统计 | `GetDailyStatistics`<br>`GetWeeklyStatistics` |
| `GetSummaryByRange` | `/TransactionStatistics/GetSummaryByRange` | startDate, endDate | 按日期范围获取汇总统计 | `GetMonthlyStatistics`<br>`GetRangeStatistics` |
| `GetCategoryStatisticsByRange` | `/TransactionStatistics/GetCategoryStatisticsByRange` | startDate, endDate, type | 按日期范围获取分类统计 | `GetCategoryStatistics`<br>`GetCategoryStatisticsByDateRange` |
| `GetTrendStatistics` | `/TransactionStatistics/GetTrendStatistics` | startYear, startMonth, monthCount | 多月趋势统计 | (保持不变) |
### 标记为 Obsolete 的接口(兼容性保留)
| 接口名称 | 状态 | 建议迁移方案 |
|---------|------|------------|
| `GetBalanceStatistics` | `[Obsolete]` | 使用 `GetDailyStatisticsByRange` 并在前端计算累积余额 |
| `GetDailyStatistics` | `[Obsolete]` | 使用 `GetDailyStatisticsByRange` |
| `GetWeeklyStatistics` | `[Obsolete]` | 使用 `GetDailyStatisticsByRange` |
| `GetMonthlyStatistics` | `[Obsolete]` | 使用 `GetSummaryByRange` |
| `GetRangeStatistics` | `[Obsolete]` | 使用 `GetSummaryByRange` |
| `GetCategoryStatistics` | `[Obsolete]` | 使用 `GetCategoryStatisticsByRange` |
| `GetCategoryStatisticsByDateRange` | `[Obsolete]` | 使用 `GetCategoryStatisticsByRange` (DateTime 参数版本) |
### 删除的接口
| 接口名称 | 删除原因 |
|---------|---------|
| `GetReasonGroups` | 前端完全未使用 |
---
## 三、技术实现细节
### 1. Service 层变更
**文件**: `Service/Transaction/TransactionStatisticsService.cs`
**新增方法**:
```csharp
/// <summary>
/// 按日期范围获取汇总统计数据(新统一接口)
/// </summary>
Task<MonthlyStatistics> GetSummaryByRangeAsync(DateTime startDate, DateTime endDate);
```
**实现**: 直接查询指定日期范围的交易记录并汇总统计。
---
### 2. Application 层变更
**文件**: `Application/TransactionStatisticsApplication.cs`
**新增方法**:
```csharp
// 新统一接口
Task<List<DailyStatisticsDto>> GetDailyStatisticsByRangeAsync(DateTime startDate, DateTime endDate, string? savingClassify = null);
Task<MonthlyStatistics> GetSummaryByRangeAsync(DateTime startDate, DateTime endDate);
Task<List<CategoryStatistics>> GetCategoryStatisticsByRangeAsync(DateTime startDate, DateTime endDate, TransactionType type);
```
**标记为过时的方法**: 所有旧方法均添加 `[Obsolete]` 特性,指引开发者迁移到新接口。
---
### 3. Controller 层变更
**文件**: `WebApi/Controllers/TransactionStatisticsController.cs`
**新增接口**:
```csharp
[HttpGet]
public async Task<BaseResponse<List<DailyStatisticsDto>>> GetDailyStatisticsByRangeAsync(
[FromQuery] DateTime startDate,
[FromQuery] DateTime endDate,
[FromQuery] string? savingClassify = null)
[HttpGet]
public async Task<BaseResponse<Service.Transaction.MonthlyStatistics>> GetSummaryByRangeAsync(
[FromQuery] DateTime startDate,
[FromQuery] DateTime endDate)
[HttpGet]
public async Task<BaseResponse<List<Service.Transaction.CategoryStatistics>>> GetCategoryStatisticsByRangeAsync(
[FromQuery] DateTime startDate,
[FromQuery] DateTime endDate,
[FromQuery] TransactionType type)
```
**标记为过时的接口**: 所有旧接口均添加 `[Obsolete]` 特性。
**删除的接口**: `GetReasonGroupsAsync`
---
### 4. 前端变更
#### API 定义文件
**文件**: `Web/src/api/statistics.js`
**新增方法**:
```javascript
// 新统一接口
export const getDailyStatisticsByRange = (params) => { /* ... */ }
export const getSummaryByRange = (params) => { /* ... */ }
export const getCategoryStatisticsByRange = (params) => { /* ... */ }
```
**标记为过时的方法**: 添加 `@deprecated` JSDoc 注释。
#### 视图文件
**文件**: `Web/src/views/statisticsV2/Index.vue`
**迁移内容**:
1. 周度数据加载: `getRangeStatistics``getSummaryByRange`
2. 周度每日统计: `getWeeklyStatistics``getDailyStatisticsByRange`
3. 周度分类统计: `getCategoryStatisticsByDateRange``getCategoryStatisticsByRange`
**关键修改**:
- 修改 `endDate` 计算逻辑: `+6天``+7天`(因为新接口的 endDate 是不包含的)
- 移除 `weekEnd.setHours(23, 59, 59, 999)` 逻辑(不再需要)
---
## 四、迁移指南
### 后端开发者
#### 场景 1: 获取月度统计
**旧代码**:
```csharp
await statisticsService.GetMonthlyStatisticsAsync(2025, 2);
```
**新代码**:
```csharp
var startDate = new DateTime(2025, 2, 1);
var endDate = new DateTime(2025, 3, 1); // 不包含3月1日
await statisticsService.GetSummaryByRangeAsync(startDate, endDate);
```
#### 场景 2: 获取每日统计
**旧代码**:
```csharp
await statisticsService.GetDailyStatisticsAsync(2025, 2, savingClassify);
```
**新代码**:
```csharp
var startDate = new DateTime(2025, 2, 1);
var endDate = new DateTime(2025, 3, 1);
await statisticsService.GetDailyStatisticsByRangeAsync(startDate, endDate, savingClassify);
```
---
### 前端开发者
#### 场景 1: 获取月度汇总
**旧代码**:
```javascript
await getMonthlyStatistics({ year: 2025, month: 2 })
```
**新代码**:
```javascript
await getSummaryByRange({
startDate: '2025-02-01',
endDate: '2025-03-01'
})
```
#### 场景 2: 获取周度每日统计
**旧代码**:
```javascript
await getWeeklyStatistics({
startDate: '2025-02-01',
endDate: '2025-02-07'
})
```
**新代码**:
```javascript
await getDailyStatisticsByRange({
startDate: '2025-02-01',
endDate: '2025-02-08' // 注意endDate 不包含
})
```
#### 场景 3: 获取分类统计
**旧代码**:
```javascript
await getCategoryStatistics({ year: 2025, month: 2, type: 0 })
// 或
await getCategoryStatisticsByDateRange({
startDate: '2025-02-01',
endDate: '2025-02-28',
type: 0
})
```
**新代码**:
```javascript
await getCategoryStatisticsByRange({
startDate: '2025-02-01',
endDate: '2025-03-01', // 注意endDate 不包含
type: 0
})
```
---
## 五、重要注意事项
### ⚠️ endDate 语义变更
**新接口的 `endDate` 参数是不包含的exclusive**。
- ❌ 错误: `startDate: '2025-02-01', endDate: '2025-02-28'` → 缺少2月28日数据
- ✅ 正确: `startDate: '2025-02-01', endDate: '2025-03-01'` → 包含整个2月
### 🔒 向后兼容策略
- 所有旧接口保持可用,仅标记为 `[Obsolete]`
- 前端 V1 版本继续使用旧接口
- 前端 V2 版本已迁移到新接口
- 建议在后续版本中逐步废弃旧接口
### 📝 测试覆盖
- ✅ 后端单元测试: `TransactionStatisticsServiceTest` 全部通过9个测试
- ⚠️ 前端测试: 建议手动测试以下场景
- 月度视图切换
- 年度视图切换
- 周度视图切换
- 分类统计数据展示
---
## 六、优化效果
### 接口精简
| 指标 | 重构前 | 重构后 | 优化 |
|------|-------|-------|------|
| 接口总数 | 9 | 4 | ⬇️ 55% |
| 推荐使用接口 | - | 4 | - |
| 兼容性接口 | - | 7 | - |
| 未使用接口 | 1 | 0 | ⬇️ 100% |
### 参数统一度
- **重构前**: 3种参数模式year+month, startDate+endDate, startYear+startMonth+monthCount
- **重构后**: 2种参数模式startDate+endDate, startYear+startMonth+monthCount
### API 可维护性
- ✅ 接口语义更清晰
- ✅ 参数命名更统一
- ✅ 减少代码重复
- ✅ 降低学习成本
---
## 七、后续计划
1. **V1 版本迁移** (优先级: 中)
-`statisticsV1/Index.vue` 迁移到新接口
- 移除对 `getBalanceStatistics` 的依赖
2. **旧接口废弃** (优先级: 低)
- 确认所有前端页面迁移完成
- 在下个大版本中彻底删除旧接口
3. **性能优化** (优先级: 高)
- 考虑提供聚合接口,一次返回多种类型的分类统计
- 前端添加数据缓存机制
---
## 八、相关文件清单
### 后端文件
- `Service/Transaction/TransactionStatisticsService.cs`
- `Application/TransactionStatisticsApplication.cs`
- `WebApi/Controllers/TransactionStatisticsController.cs`
### 前端文件
- `Web/src/api/statistics.js`
- `Web/src/views/statisticsV2/Index.vue`
### 测试文件
- `WebApi.Test/Transaction/TransactionStatisticsServiceTest.cs`
- `WebApi.Test/Application/TransactionStatisticsApplicationTest.cs`
---
## 九、常见问题 (FAQ)
### Q1: 旧接口什么时候会被删除?
A: 目前旧接口仅标记为 `[Obsolete]`,不会被立即删除。计划在确认所有前端页面迁移完成后,在下个大版本中删除。
### Q2: 为什么 `GetTrendStatistics` 没有改为日期范围?
A: `GetTrendStatistics` 的业务场景是获取"连续N个月"的趋势数据,使用 `(startYear, startMonth, monthCount)` 更符合业务语义。如果改为日期范围,反而需要前端自行计算月份跨度。
### Q3: 新接口的 `endDate` 为什么是不包含的?
A: 采用"左闭右开区间 `[startDate, endDate)`"是业界常见做法,优点包括:
- 方便表示连续时间段如2月: `[2025-02-01, 2025-03-01)`
- 避免时区和时间精度问题(不需要 `23:59:59.999`
- 与大多数编程语言的区间语义一致(如 Python 的 `range()`
### Q4: 如何确认迁移是否成功?
A: 检查以下几点:
1. 前端代码中没有引用旧的 API 方法
2. 统计数据展示正常,与旧版本数据一致
3. 控制台无 API 调用错误
---
**文档版本**: v1.0
**最后更新**: 2026-02-10