Files

7.2 KiB
Raw Permalink Blame History

重构总结:复用现有 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:

// 直接使用 window.umami
export class UmamiTrackingAdapter {
  isAvailable(): boolean {
    return typeof window.umami !== 'undefined';
  }

  async track(event: TrackEvent): Promise<void> {
    await window.umami!.track(event.eventName, enrichedData);
  }
}

After:

// 复用现有 analytics 单例
import { analytics } from '../../utils/umami-analytics';

export class UmamiTrackingAdapter {
  isAvailable(): boolean {
    return analytics.isAnalyticsEnabled();  // ✅ 复用现有方法
  }

  async track(event: TrackEvent): Promise<void> {
    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:

// Mock window.umami
const mockUmami = {
  track: jest.fn().mockResolvedValue(undefined),
};

(global as any).window = {
  umami: mockUmami,
};

After:

// 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 中统一注入所有元数据,让两种埋点方式都受益

// 在 UmamiAnalytics.track() 中统一注入
track(eventName: string, eventData?: Record<string, any>): void {
  const enrichedData = {
    ...eventData,
    timestamp: Date.now(),
    version: this.getVersion(),     // 新增
    url: window.location.href,      // 新增
    sessionId: this.getSessionId(), // 新增
  };
  window.umami.track(eventName, enrichedData);
}

2. 统一批量上报

当前: 只有声明式埋点支持批量上报

优化建议: 考虑为 UmamiAnalytics 也添加批量上报能力

class UmamiAnalytics {
  private batchService: BatchService;

  track(eventName: string, eventData?: Record<string, any>): void {
    if (this.config.enableBatch) {
      this.batchService.enqueue({eventName, eventData});
    } else {
      window.umami.track(eventName, eventData);
    }
  }
}

3. 统一离线缓存

当前: 只有声明式埋点支持离线缓存

优化建议: 考虑为所有埋点提供统一的离线缓存支持

4. 统一配置管理

当前:

  • UmamiAnalytics: 无配置
  • TrackingService: TrackConfig (日志级别、重试策略、批量配置等)

优化建议: 考虑创建统一的 AnalyticsConfig 供两种方式共享

验证清单

功能验证

  • UmamiAdapter 正确调用 analytics.track()
  • UmamiAdapter 正确使用 analytics.isAnalyticsEnabled()
  • 所有单元测试通过(使用 analytics mock
  • 元数据正确注入version, url, sessionId, viewport, eventType
  • 批量上报功能正常
  • 离线缓存功能正常
  • 手动埋点未受影响

代码质量验证

  • window.umami 接口重复定义
  • 无基础 track() 方法重复实现
  • 日志输出使用 [Tracking] 前缀区分
  • 所有文件符合 <500 行限制
  • TypeScript 类型检查通过

文档验证

  • umami-api.md 更新集成策略说明
  • INTEGRATION.md 详细说明两种埋点方式
  • quickstart.md 示例正确

总结

通过这次重构:

  1. 消除了代码重复 - UmamiAdapter 现在是现有 analytics 工具的适配器
  2. 保持了功能完整 - 声明式埋点的所有特性(批量、缓存、防抖、元数据)都保留
  3. 提高了一致性 - 两种埋点方式共享底层实现
  4. 降低了维护成本 - 减少了需要维护的代码量
  5. 提高了可测试性 - 测试更简单、更可靠

这是一次成功的重构,既解决了代码重复问题,又为未来的统一埋点系统铺平了道路。