# 架构设计

## 分层架构概览

OFPlayer 采用经典的分层架构，自上而下分为以下层级：

```
┌─────────────────────────────────────────────────────────────┐
│                      View Layer (视图层)                      │
│         Vue Components / Pages / Router                      │
├─────────────────────────────────────────────────────────────┤
│                   App Orchestration (应用编排层)               │
│                    ofplayerApp.js                            │
├─────────────────────────────────────────────────────────────┤
│                    State Layer (状态层)                       │
│         libraryStore / playerStore / preferencesStore        │
├─────────────────────────────────────────────────────────────┤
│                   Service Layer (服务层)                      │
│     libraryService / playlistService / trackService          │
├─────────────────────────────────────────────────────────────┤
│                   Model Layer (模型层)                        │
│         library / playlist / track / collection              │
├─────────────────────────────────────────────────────────────┤
│                   Data Layer (数据层)                         │
│         IndexedDB / localStorage / DataService               │
└─────────────────────────────────────────────────────────────┘
```

## 各层职责

### 1. View Layer (视图层)

**位置**: `src/components/`, `src/pages/`, `src/router/`

**职责**:
- 渲染 UI 界面
- 处理用户交互事件
- 通过 Props 接收数据，通过 Emit 发送事件
- 不直接访问服务层或数据层

**关键设计**:
- 组件通过 `useOFPlayerApp()` 获取应用实例
- 应用实例提供所有业务方法和响应式状态
- 组件不持有业务逻辑，仅负责展示和事件转发

### 2. App Orchestration Layer (应用编排层)

**位置**: `src/app/ofplayerApp.js`

**职责**:
- 作为中央门面（Facade），统一协调所有服务和状态
- 通过 Vue 的 `provide/inject` 机制分发应用实例
- 管理应用生命周期（创建、水合、销毁）
- 处理跨 Store 的业务逻辑

**核心方法**:

```javascript
// 创建应用实例（同步）
const ofplayer = createOFPlayerApp()

// 安装到 Vue 应用
installOFPlayerApp(app, ofplayer)

// 水合数据（异步，在 mount 后调用）
await ofplayer.hydrate()

// 在组件中获取
const ofplayer = useOFPlayerApp()
```

**提供的 API**:

| 类别 | API |
|------|-----|
| 状态 | `libraries`, `playlists`, `tracks`, `currentTrack`, `isPlaying`, `currentTime`, `duration`, `volume` |
| 播放控制 | `selectTrack`, `togglePlayback`, `playNext`, `playPrevious`, `seek`, `setVolume` |
| 库管理 | `createLibrary`, `renameLibrary`, `deleteLibrary`, `setActiveLibrary` |
| 播放列表 | `createPlaylist`, `renamePlaylist`, `deletePlaylist`, `addTrackToPlaylist`, `removeTrackFromPlaylist` |
| 导入导出 | `importFiles`, `exportAllTracks`, `clearAllData` |
| 偏好设置 | `setTheme`, `setColorScheme`, `setLanguage`, `setVolume` |

### 3. State Layer (状态层)

**位置**: `src/stores/`

**职责**:
- 管理响应式状态
- 提供状态访问和修改方法
- 与服务层交互进行数据持久化
- 维护状态一致性

**Store 列表**:

| Store | 职责 |
|-------|------|
| `libraryStore` | 库、播放列表、曲目、关系管理 |
| `playerStore` | 播放状态、当前曲目、播放历史 |
| `preferencesStore` | 用户偏好设置 |
| `sessionStore` | 会话状态、队列管理 |
| `entitlementStore` | 功能权限管理 |
| `uiStore` | UI 状态 |

**设计特点**:
- 使用 Vue Composition API 的 `ref` 和 `computed`
- 不使用 Vuex 或 Pinia
- 每个 Store 是一个工厂函数，返回响应式状态和方法
- Store 之间通过组合方式协作

### 4. Service Layer (服务层)

**位置**: `src/services/`

**职责**:
- 实现业务逻辑
- 协调数据模型和数据访问层
- 提供领域特定的操作接口

**服务列表**:

| 服务 | 职责 |
|------|------|
| `libraryService` | 库的 CRUD 操作 |
| `playlistService` | 播放列表操作、曲目关系管理 |
| `trackService` | 文件导入、曲目对象创建、资源释放 |
| `metadataService` | 音频元数据提取 |
| `externalLibraryService` | 外部库连接（WebDAV、Subsonic） |
| `albumViewService` | 专辑浏览视图逻辑 |
| `lyricsStorage` | 歌词持久化 |
| `telemetryService` | 遥测分析 |

### 5. Model Layer (模型层)

**位置**: `src/models/`

**职责**:
- 定义数据结构和形状
- 提供工厂函数创建标准化对象
- 数据验证和规范化

**模型列表**:

| 模型 | 实体 |
|------|------|
| `library.js` | Library |
| `playlist.js` | Playlist |
| `track.js` | Track |
| `collection.js` | Collection (抽象) |
| `playback.js` | Playback State |
| `preferences.js` | Preferences |
| `session.js` | Session |

### 6. Data Layer (数据层)

**位置**: `src/services/data/`

**职责**:
- 提供数据持久化抽象
- 支持多种存储驱动
- 处理数据序列化和反序列化

**驱动实现**:

| 驱动 | 实现 |
|------|------|
| `indexedDbDataService.js` | IndexedDB 实现（主驱动） |
| `localDataService.js` | localStorage 实现（降级方案） |

## 数据流

### 典型数据流（以播放曲目为例）

```
用户点击曲目
    ↓
View Layer: PlayerPanel emit('select-track', trackId)
    ↓
App Layer: ofplayer.selectTrack(trackId)
    ↓
State Layer: sessionStore.setCurrentTrack(trackId)
    ↓
Service Layer: externalLibraryService.resolvePlayableTrack(track)
    ↓
Data Layer: dataService.catalog.getTrackAssets(trackId)
    ↓
State Layer: playerStore.loadTrack(track)
    ↓
View Layer: 响应式更新 UI
```

### 初始化流程

```
main.js
    ↓
createOFPlayerApp()  // 同步创建 Store 实例
    ↓
app.mount('#app')    // Vue 应用挂载，UI 立即可交互
    ↓
ofplayer.hydrate()   // 异步加载持久化数据
    ↓
Promise.all([
  preferencesStore.hydrate(),
  libraryStore.hydrate(),
  sessionStore.hydrate(),
  playerStore.hydrate()
])
    ↓
syncQueueWithCatalog()  // 同步队列与目录
    ↓
isHydrating.value = false  // 加载完成
```

## 模块依赖关系

```
ofplayerApp.js
    ├── libraryStore
    │   ├── libraryService
    │   ├── playlistService
    │   └── trackService
    ├── playerStore
    │   └── useAudioPlayer
    ├── sessionStore
    └── preferencesStore

所有 Service
    └── dataService (Data Layer)

Data Layer
    ├── indexedDbDataService
    └── localDataService
```

## 关键设计模式

### 1. Facade 模式

`ofplayerApp.js` 作为中央门面，简化了组件与复杂子系统的交互：

```javascript
// 组件无需了解内部细节
const ofplayer = useOFPlayerApp()
ofplayer.selectTrack(trackId)  // 一个方法调用
// 内部协调了 sessionStore、playerStore、externalLibraryService
```

### 2. Factory 模式

所有 Store 和 Service 都是工厂函数：

```javascript
export function createLibraryStore({ libraryService, playlistService, trackService }) {
  // 创建并返回响应式状态和方法
  return { libraries, playlists, tracks, ... }
}
```

### 3. Repository 模式

数据访问层抽象了存储实现：

```javascript
// 统一接口
dataService.catalog.putLibrary(library)
dataService.catalog.getTrack(trackId)

// 可切换实现
createDataService({ driver: 'indexeddb' })  // 或 'local'
```

### 4. Composition API 模式

使用组合式函数封装逻辑：

```javascript
export function useAudioPlayer({ initialVolume, onPlay, onPause }) {
  const isPlaying = ref(false)
  const currentTime = ref(0)
  // ...
  return { isPlaying, currentTime, play, pause, ... }
}
```

## 性能优化策略

### 1. 虚拟列表

当曲目数量超过 90 首时，自动启用虚拟列表：

```javascript
const VIRTUALIZATION_THRESHOLD = 90
const shouldVirtualizeTracks = computed(() => 
  visibleTracks.value.length >= VIRTUALIZATION_THRESHOLD
)
```

### 2. O(1) 查找表

使用 Map 维护 ID 到对象的映射：

```javascript
const tracksById = computed(() => {
  const m = new Map()
  for (const t of tracks.value) m.set(t.id, t)
  return m
})
```

### 3. 懒加载

路由组件使用动态导入：

```javascript
{
  path: '/',
  component: () => import('../pages/PlayerPage.vue')
}
```

### 4. 版本化存储

使用构建 ID 自动失效过期数据：

```javascript
writeVersioned('ofp:visual', { theme, colorScheme, motion })
// 下次部署时自动丢弃旧数据
```
