Files

273 lines
7.2 KiB
Markdown
Raw Permalink 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.
# 重构总结:复用现有 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<void> {
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<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**:
```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<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 也添加批量上报能力
```typescript
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` 供两种方式共享
## 验证清单
### 功能验证
- [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. **提高了可测试性** - 测试更简单、更可靠
这是一次成功的重构,既解决了代码重复问题,又为未来的统一埋点系统铺平了道路。