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