# 重构总结:复用现有 Umami 工具 **Date**: 2025-12-05 **Issue**: 发现重复代码 - 新实现的 UmamiAdapter 与现有 `umami-analytics.ts` 功能重叠 ## 问题发现 在实现声明式埋点系统时,发现以下重复: ### 原有实现 - **位置**: `packages/drawnix/src/utils/umami-analytics.ts` - **内容**: 完整的 UmamiAnalytics 工具类 - `window.umami` 接口定义 - 基础 `track()` 方法 - AI 生成事件专用方法 - 用户交互、特性使用追踪 - 统一的日志输出 `[Analytics]` ### 新实现(重复部分) - **位置**: `packages/drawnix/src/services/tracking/umami-adapter.ts` - **重复内容**: - ❌ 重复定义 `window.umami` 接口 - ❌ 重复实现 `track()` 基础功能 - ❌ 重复 SDK 可用性检查逻辑 ## 重构方案 ### 修改前的架构 ``` 声明式埋点 ↓ UmamiAdapter (直接调用 window.umami) ↓ window.umami.track() ↓ Umami 服务器 ``` ### 修改后的架构(复用现有工具) ``` 声明式埋点 ↓ UmamiAdapter (适配器层) ↓ analytics.track() (复用现有 UmamiAnalytics) ↓ window.umami.track() ↓ Umami 服务器 ``` ## 代码变更 ### 1. UmamiAdapter 重构 **文件**: `packages/drawnix/src/services/tracking/umami-adapter.ts` **Before**: ```typescript // 直接使用 window.umami export class UmamiTrackingAdapter { isAvailable(): boolean { return typeof window.umami !== 'undefined'; } async track(event: TrackEvent): Promise { await window.umami!.track(event.eventName, enrichedData); } } ``` **After**: ```typescript // 复用现有 analytics 单例 import { analytics } from '../../utils/umami-analytics'; export class UmamiTrackingAdapter { isAvailable(): boolean { return analytics.isAnalyticsEnabled(); // ✅ 复用现有方法 } async track(event: TrackEvent): Promise { analytics.track(event.eventName, enrichedData); // ✅ 复用现有方法 } } ``` **变更说明**: - ✅ 删除了 `window.umami` 接口定义(使用 umami-analytics.ts 中的定义) - ✅ 调用 `analytics.track()` 而不是 `window.umami.track()` - ✅ 使用 `analytics.isAnalyticsEnabled()` 检查 SDK 可用性 - ✅ 保留了元数据注入逻辑(声明式埋点特有) ### 2. 测试文件重构 **文件**: `packages/drawnix/src/services/tracking/__tests__/umami-adapter.test.ts` **Before**: ```typescript // Mock window.umami const mockUmami = { track: jest.fn().mockResolvedValue(undefined), }; (global as any).window = { umami: mockUmami, }; ``` **After**: ```typescript // Mock analytics utility jest.mock('../../../utils/umami-analytics', () => ({ analytics: { track: jest.fn(), isAnalyticsEnabled: jest.fn(), }, })); // 使用 analytics mock 进行测试 expect(analytics.track).toHaveBeenCalledWith('test_event', {...}); ``` **变更说明**: - ✅ Mock `analytics` 而不是 `window.umami` - ✅ 验证 `analytics.track()` 被正确调用 - ✅ 验证 `analytics.isAnalyticsEnabled()` 被正确使用 - ✅ 所有测试用例更新完毕 ### 3. 文档更新 **文件**: `specs/005-declarative-tracking/contracts/umami-api.md` - ✅ 更新集成策略说明 - ✅ 更新示例代码 - ✅ 添加集成优势说明 **文件**: `specs/005-declarative-tracking/INTEGRATION.md` (新增) - ✅ 详细说明两种埋点方式的差异 - ✅ 使用场景建议 - ✅ 数据流对比 - ✅ 元数据对比 - ✅ 混合使用示例 ## 重构收益 ### 代码质量 | 指标 | 重构前 | 重构后 | 改进 | |------|--------|--------|------| | 代码重复 | 是(window.umami 接口定义重复) | 否 | ✅ 消除重复 | | 依赖管理 | 直接依赖 window.umami | 依赖 analytics 单例 | ✅ 统一依赖 | | 日志格式 | 不一致([Tracking] vs [Analytics]) | 统一通过 analytics | ✅ 日志统一 | | 错误处理 | 独立实现 | 复用 analytics | ✅ 逻辑统一 | ### 维护性 - ✅ **单一数据源**: 只有 `umami-analytics.ts` 定义 Umami 接口 - ✅ **自动受益**: analytics 的任何改进会自动惠及声明式埋点 - ✅ **降低维护成本**: 减少需要维护的代码量 - ✅ **提高可测试性**: Mock analytics 比 Mock window.umami 更简单 ### 一致性 - ✅ **日志格式统一**: 都通过 analytics 输出,格式一致 - ✅ **错误处理统一**: 复用 analytics 的 try-catch 逻辑 - ✅ **SDK 检查统一**: 都使用 `analytics.isAnalyticsEnabled()` ## 未来优化方向 虽然已经消除了重复代码,但仍有改进空间: ### 1. 统一元数据注入 **当前**: - 手动埋点: `timestamp` (由 analytics 注入) - 声明式埋点: `version`, `url`, `sessionId`, `viewport`, `eventType` (由 adapter 注入) **优化建议**: 考虑在 `UmamiAnalytics` 中统一注入所有元数据,让两种埋点方式都受益 ```typescript // 在 UmamiAnalytics.track() 中统一注入 track(eventName: string, eventData?: Record): void { const enrichedData = { ...eventData, timestamp: Date.now(), version: this.getVersion(), // 新增 url: window.location.href, // 新增 sessionId: this.getSessionId(), // 新增 }; window.umami.track(eventName, enrichedData); } ``` ### 2. 统一批量上报 **当前**: 只有声明式埋点支持批量上报 **优化建议**: 考虑为 UmamiAnalytics 也添加批量上报能力 ```typescript class UmamiAnalytics { private batchService: BatchService; track(eventName: string, eventData?: Record): void { if (this.config.enableBatch) { this.batchService.enqueue({eventName, eventData}); } else { window.umami.track(eventName, eventData); } } } ``` ### 3. 统一离线缓存 **当前**: 只有声明式埋点支持离线缓存 **优化建议**: 考虑为所有埋点提供统一的离线缓存支持 ### 4. 统一配置管理 **当前**: - UmamiAnalytics: 无配置 - TrackingService: TrackConfig (日志级别、重试策略、批量配置等) **优化建议**: 考虑创建统一的 `AnalyticsConfig` 供两种方式共享 ## 验证清单 ### 功能验证 - [X] UmamiAdapter 正确调用 `analytics.track()` - [X] UmamiAdapter 正确使用 `analytics.isAnalyticsEnabled()` - [X] 所有单元测试通过(使用 analytics mock) - [X] 元数据正确注入(version, url, sessionId, viewport, eventType) - [X] 批量上报功能正常 - [X] 离线缓存功能正常 - [X] 手动埋点未受影响 ### 代码质量验证 - [X] 无 `window.umami` 接口重复定义 - [X] 无基础 `track()` 方法重复实现 - [X] 日志输出使用 `[Tracking]` 前缀区分 - [X] 所有文件符合 <500 行限制 - [X] TypeScript 类型检查通过 ### 文档验证 - [X] umami-api.md 更新集成策略说明 - [X] INTEGRATION.md 详细说明两种埋点方式 - [X] quickstart.md 示例正确 ## 总结 通过这次重构: 1. **消除了代码重复** - UmamiAdapter 现在是现有 analytics 工具的适配器 2. **保持了功能完整** - 声明式埋点的所有特性(批量、缓存、防抖、元数据)都保留 3. **提高了一致性** - 两种埋点方式共享底层实现 4. **降低了维护成本** - 减少了需要维护的代码量 5. **提高了可测试性** - 测试更简单、更可靠 这是一次成功的重构,既解决了代码重复问题,又为未来的统一埋点系统铺平了道路。