Files
EmailBill/.doc/api-refactoring-transaction-statistics.md
SunCheng d052ae5197 fix
2026-02-10 17:49:19 +08:00

374 lines
11 KiB
Markdown
Raw 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.
---
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