Files
EmailBill/openspec/changes/archive/2026-02-17-remove-v1-calendar-stats-budget/design.md
SunCheng c49f66757e
Some checks failed
Docker Build & Deploy / Build Docker Image (push) Waiting to run
Docker Build & Deploy / Deploy to Production (push) Has been cancelled
Docker Build & Deploy / Cleanup Dangling Images (push) Has been cancelled
Docker Build & Deploy / WeChat Notification (push) Has been cancelled
1
2026-02-18 21:16:45 +08:00

12 KiB
Raw Blame History

Context

EmailBill 项目在早期开发了 V1 版本的日历、统计、预算三大核心功能模块。随着业务迭代V2 版本已经重构并稳定运行,提供了更优的用户体验和代码架构。当前代码库中同时维护 V1 和 V2 两套实现,导致:

  1. 代码冗余: 前端包含 CalendarView.vue, BudgetView.vue, statisticsV1/Index.vue 等页面,后端包含多个仅服务于 V1 的 API 端点
  2. 维护成本: 任何共享组件或数据模型的变更需要同时考虑 V1 和 V2 的兼容性
  3. 认知负担: 新开发者需要理解两套架构,增加了上手难度
  4. 测试负担: 需要维护两套测试用例,且部分测试已失效

当前状态

  • V2 版本已完成功能对齐,用户已逐步迁移
  • V1 相关 API 端点部分已标记 [Obsolete] (如 TransactionRecordController.GetDailyStatisticsAsync)
  • 路由守卫中包含 V1/V2 版本切换逻辑

约束条件

  • 不能影响 V2 功能: 必须保证 V2 页面完全正常运行
  • 共享组件不能删除: TransactionList, TransactionDetail, PopupContainer 等组件被 V2 使用
  • Repository 层不能动: V1 和 V2 共用相同的数据访问层
  • 需要手动测试: 删除后必须打开 V2 页面验证功能完整性

利益相关者

  • 开发团队: 减少维护负担,简化架构
  • 测试团队: 减少测试用例数量
  • 最终用户: 无感知(已迁移到 V2

Goals / Non-Goals

Goals

  1. 完全移除 V1 代码: 删除所有 V1 专用的页面、API、Service 方法
  2. 保持 V2 功能完整: 确保删除操作不影响 V2 版本的任何功能
  3. 清理路由配置: 移除 V1 路由和版本切换逻辑
  4. 验证功能正常: 通过手动测试验证 V2 页面能正常打开和使用

Non-Goals

  1. 不修改 Repository 层: V2 继续使用现有的 Repository不进行任何修改
  2. 不修改数据库: 不涉及数据迁移或表结构变更
  3. 不重构 V2 代码: 仅删除 V1 代码,不对 V2 进行任何优化或重构
  4. 不移除共享组件: 即使组件原本为 V1 开发,只要 V2 在用就保留
  5. 不更新文档: API 文档和用户文档的更新不在本次变更范围内

Decisions

Decision 1: 采用自底向上的删除顺序 (Backend → Frontend)

选择: 先删除后端 Service/Application → 再删除 Controller → 最后删除前端页面和路由

理由:

  • 依赖方向: 前端依赖后端 API先删除底层可以避免悬挂引用
  • 验证便利: 删除后端接口后,前端调用会立即报 404易于发现遗漏
  • 回滚简单: 如果出问题,可以快速恢复后端接口,前端暂时保留也不影响

备选方案 (未选择):

  • 自顶向下 (Frontend → Backend): 先删除前端页面,可能导致后端接口成为"僵尸代码"难以发现
  • 同步删除: 一次性删除所有层,风险较高且难以定位问题

Decision 2: 通过代码搜索确认 V1 专用性

选择: 对每个待删除的方法/接口进行全局搜索,确认仅被 V1 页面调用

搜索关键字:

  • 前端页面: CalendarView.vue, BudgetView.vue, statisticsV1/Index.vue
  • API 方法: GetDailyStatistics, GetUncoveredCategories, GetArchiveSummary, GetBalanceStatistics
  • 路由: /calendar, /budget, name: 'statistics'

验证标准:

  • 如果搜索结果仅出现在以上三个页面中 → 可以安全删除
  • 如果出现在其他文件中 → 标记为"需保留"或"需进一步分析"

理由:

  • 准确性: 避免误删 V2 依赖的代码
  • 可追溯: 搜索结果可作为删除依据留存

Decision 3: 保留共享组件,即使其仅在 V1 中被直接使用

选择: 保留 TransactionList, TransactionDetail, PopupContainer, SmartClassifyButton 等组件

判断依据:

  • 通过搜索确认这些组件在 V2 页面中有引用
  • 即使组件内部包含 V1 特定逻辑(如全局事件监听),也不在本次变更中修改

理由:

  • 最小化风险: 删除共享组件可能导致 V2 功能异常
  • 职责分离: 组件优化应作为独立的重构任务,不在本次删除范围内

Decision 4: 移除全局事件监听 (条件性)

选择: 如果全局事件 (transaction-deleted, transactions-changed) 仅在 V1 页面中监听,则移除事件触发代码

验证流程:

  1. 搜索 window.addEventListener('transaction-deleted')
  2. 确认监听器仅在 CalendarView.vuestatisticsV1/Index.vue 中注册
  3. 搜索 window.dispatchEvent(new Event('transaction-deleted'))
  4. 如果触发位置在共享组件中(如 TransactionDetail.vue),检查该组件是否被 V2 使用
    • 如果被 V2 使用 → 保留事件触发代码(即使 V1 删除后无人监听)
    • 如果仅 V1 使用 → 删除事件触发代码

理由:

  • 保守策略: 优先保证 V2 功能不受影响
  • 技术债务: 遗留的无用事件触发代码可作为后续清理任务

Decision 5: 采用手动测试验证 V2 功能

选择: 删除完成后,手动打开以下 V2 页面并验证核心功能

测试清单:

  1. 统计 V2 (/statistics-v2):
    • 月度统计数据正常加载
    • 分类统计饼图正常渲染
    • 日度统计折线图正常渲染
    • 交易列表正常展示和滚动加载
  2. 预算 V2 (/budget-v2):
    • 预算列表正常加载
    • 分类统计(月度+年度)正常显示
    • 预算创建/编辑/删除功能正常
    • 存款预算导航和显示正常
  3. 日历 V2 (/calendar-v2):
    • 日历视图正常渲染
    • 日期选择和统计数据加载正常
    • 交易详情查看和编辑正常

理由:

  • V2 无单元测试覆盖: 当前项目主要测试集中在后端,前端缺少自动化测试
  • 快速反馈: 手动测试可以在 10 分钟内完成所有关键路径验证
  • 用户体验保证: 确保最终用户不会遇到功能异常

备选方案 (未选择):

  • 仅依赖编译检查: 无法发现运行时错误(如路由配置错误)
  • 编写自动化测试: 时间成本过高,且本次变更不涉及逻辑修改

Risks / Trade-offs

Risk 1: 误删 V2 依赖的代码

表现: 删除后端接口或前端方法,导致 V2 页面调用失败

缓解措施:

  • 采用 Decision 2 的搜索验证流程,确保每个删除操作都经过确认
  • 先删除后端 Service 层,观察前端是否有新的 404 错误
  • 使用 Git 进行版本控制,支持快速回滚

残留影响:

  • 如果 V2 通过动态路由或字符串拼接调用接口,静态搜索可能遗漏

Risk 2: 共享组件包含 V1 特定逻辑但未被清理

表现: 保留的组件中包含 V1 特定的条件判断或事件监听,成为"死代码"

缓解措施:

  • 标记为 Non-Goal不在本次清理范围内
  • 在任务清单中添加"后续优化"任务,记录需要清理的组件列表

残留影响:

  • 代码库中保留少量无用代码,但不影响功能正确性

Risk 3: 路由守卫中遗留 V1 相关逻辑

表现: 删除 V1 路由后,router/index.jsuseVersionStore 中仍有版本切换逻辑

缓解措施:

  • 在任务清单中明确包含"清理路由守卫"任务
  • 搜索 isV2(), useVersionStore, router.push 等关键字,逐一检查

残留影响:

  • 可能导致用户访问不存在的路由时出现 404 错误

Risk 4: V2 页面功能异常但手动测试未覆盖

表现: 删除操作影响了 V2 的边缘功能(如智能分类、批量操作),但未在测试清单中

缓解措施:

  • 优先测试 V2 的核心流程(查看、创建、编辑、删除)
  • 如果发现问题,立即回滚对应的删除操作
  • 记录测试结果,作为归档的一部分

残留影响:

  • 边缘功能可能在生产环境中被用户发现,需要后续修复

Trade-off 1: 保留共享组件 vs 彻底清理

权衡: 为了保证 V2 功能稳定,选择保留所有共享组件,即使部分组件包含 V1 特定逻辑

代价:

  • 代码库中保留部分冗余代码
  • 后续维护时可能需要额外的上下文理解

收益:

  • 大幅降低误删风险
  • 缩短变更周期,快速上线

Trade-off 2: 自动化测试 vs 手动测试

权衡: 采用手动测试而非编写自动化测试用例

代价:

  • 无法在 CI/CD 中自动验证 V2 功能
  • 未来的变更可能再次破坏 V2 功能

收益:

  • 节省测试编写时间(预计 2-3 小时)
  • 适用于一次性删除任务,性价比高

Migration Plan

阶段 1: 准备工作 (Pre-deletion)

  1. 创建 feature 分支 feature/remove-v1-modules
  2. 运行所有现有测试,确认当前状态正常
  3. 备份 V1 相关文件列表(用于回滚)

阶段 2: 后端删除 (Backend Removal)

  1. Service 层删除:

    • 删除 BudgetService.GetUncoveredCategoriesAsync
    • 删除 BudgetService.GetArchiveSummaryAsync
    • 删除 TransactionStatisticsService.GetBalanceStatisticsAsync
    • 编译验证,确认无编译错误
  2. Application 层删除:

    • 删除 BudgetApplication.GetUncoveredCategoriesAsync
    • 删除 BudgetApplication.GetArchiveSummaryAsync
    • 删除 TransactionStatisticsApplication.GetBalanceStatisticsAsync
    • 编译验证
  3. Controller 层删除:

    • 删除 TransactionRecordController.GetDailyStatisticsAsync
    • 删除 BudgetController.GetUncoveredCategoriesAsync
    • 删除 BudgetController.GetArchiveSummaryAsync
    • 删除 TransactionStatisticsController.GetBalanceStatisticsAsync
    • 编译验证
  4. 运行后端测试:

    dotnet test WebApi.Test/WebApi.Test.csproj
    
    • 移除失败的 V1 相关测试用例
    • 确保其他测试通过

阶段 3: 前端删除 (Frontend Removal)

  1. API 客户端清理:

    • 清理 Web/src/api/transactionRecord.js 中的 GetDailyStatistics 调用
    • 清理 Web/src/api/budget.js 中的 GetUncoveredCategories, GetArchiveSummary
    • 清理 Web/src/api/statistics.js 中的 GetBalanceStatistics
  2. 页面文件删除:

    • 删除 Web/src/views/CalendarView.vue
    • 删除 Web/src/views/BudgetView.vue
    • 删除 Web/src/views/statisticsV1/Index.vue
    • 删除 Web/src/views/statisticsV1/ 目录
  3. 路由配置清理:

    • 删除 Web/src/router/index.js 中的 V1 路由定义:
      • /calendar
      • /budget
      • / (statisticsV1)
    • 简化 useVersionStore 中的版本切换逻辑
    • 移除路由守卫中的 V1 相关判断
  4. 编译验证:

    cd Web && pnpm build
    

阶段 4: 手动测试验证 (Manual Testing)

按照 Decision 5 的测试清单,逐项验证 V2 功能:

  1. 启动开发服务器: pnpm dev
  2. 打开浏览器,访问 V2 页面
  3. 验证核心功能(数据加载、交互、导航)
  4. 记录测试结果

阶段 5: 代码审查和合并 (Code Review & Merge)

  1. 提交变更到远程分支
  2. 创建 Pull Request
  3. 代码审查关注点:
    • 确认删除的代码不在 V2 中被引用
    • 检查是否有遗漏的 V1 特定逻辑
  4. 合并到主分支

回滚策略

如果发现 V2 功能异常:

  1. 立即回滚: git revert <commit-hash>
  2. 定位问题: 使用 git diff 找到引起问题的删除操作
  3. 部分恢复: 仅恢复必要的文件或方法
  4. 重新测试: 确认恢复后 V2 功能正常

Open Questions

Q1: 是否需要保留 [Obsolete] 标记的接口一段时间?

当前决策: 直接删除,因为 V2 已稳定运行

待确认: 是否有外部系统或移动端 APP 仍在调用这些接口?如果有,需要先通知相关方并提供迁移时间


Q2: 全局事件监听 (transaction-deleted) 在 V2 中是否还需要?

当前决策: 保留事件触发代码,即使 V1 删除后无人监听

待确认: V2 是否使用其他机制(如 Pinia store来实现组件间通信如果是可以在后续任务中移除全局事件


Q3: 是否需要更新 API 文档 (Swagger/Scalar)

当前决策: 标记为 Non-Goal不在本次范围内

待确认: API 文档是否自动生成?如果是手动维护,需要添加任务来移除已删除接口的文档


Q4: V2 页面是否有完整的功能对齐?

当前假设: V2 已完全替代 V1 功能

待确认: 是否有用户或内部团队仍在使用 V1 特有的功能(如 GetUncoveredCategories)?如果有,需要先在 V2 中实现对应功能


设计文档完成。本文档为实施团队提供了清晰的技术决策依据和操作步骤,确保删除操作安全可控。