# 员工端APP离线存储方案设计 **版本**: v1.0 **创建日期**: 2026-02-26 **适用范围**: ether-app-employee --- ## 一、现状分析 ### 1.1 现有实现 当前代码 (`src/utils/offline.ts`) 已实现: | 功能 | 实现状态 | 说明 | |------|---------|------| | 基础缓存 | ✅ 已实现 | `offlineStorage.set/get/remove/clear` | | 缓存过期 | ✅ 已实现 | 支持TTL过期机制 | | 网络状态监听 | ✅ 已实现 | 响应式网络状态 | | 待同步队列 | ✅ 已实现 | `pendingActions` 队列 | | 位置追踪 | ✅ 已实现 | `locationTracker` | ### 1.2 存在问题 1. **数据模型不完整**: 缺少业务数据的离线模型定义 2. **冲突解决缺失**: 没有处理数据冲突的策略 3. **存储容量管理**: 没有容量限制和清理策略 4. **数据加密**: 敏感数据未加密存储 5. **同步策略简单**: 仅支持重试,缺少增量同步 --- ## 二、行业最佳实践 ### 2.1 离线优先架构 (Offline-First) ``` ┌─────────────────────────────────────────────────────────────┐ │ 应用层 │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ 工单模块 │ │ 巡检模块 │ │ 访客模块 │ │ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 数据访问层 (Repository) │ │ │ │ - 统一数据访问接口 │ │ │ │ - 自动选择在线/离线数据源 │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ │ ┌──────────────────┼──────────────────┐ │ │ ▼ ▼ ▼ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ 在线API │ │ 同步引擎 │ │ 本地存储 │ │ │ │ (Remote) │◄──►│ (Sync) │◄──►│ (Local) │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────┐ │ │ │ │ │ 同步队列 │ │ │ │ │ │ (Queue) │ │ │ │ │ └─────────────┘ │ │ │ │ │ │ │ ▼ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 网络层 │ │ │ │ - 网络状态检测 │ │ │ │ - 请求重试 │ │ │ │ - 请求缓存 │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### 2.2 数据同步策略 #### 策略1: 最后写入胜出 (LWW - Last Write Wins) - 适用场景: 简单数据、无冲突风险 - 实现方式: 使用时间戳比较 #### 策略2: 版本向量 (Vector Clock) - 适用场景: 需要精确冲突检测 - 实现方式: 每个节点维护版本号 #### 策略3: 业务合并 (Application-Level Merge) - 适用场景: 复杂业务数据 - 实现方式: 业务逻辑决定合并策略 ### 2.3 存储方案对比 | 方案 | 容量 | 性能 | 复杂度 | 适用场景 | |------|------|------|--------|---------| | uni.setStorage | 10MB | 高 | 低 | 简单数据 | | IndexedDB | 50MB+ | 中 | 中 | 结构化数据 | | SQLite | 无限制 | 高 | 高 | 复杂查询 | | 本地文件 | 无限制 | 低 | 高 | 大文件存储 | --- ## 三、推荐方案 ### 3.1 整体架构 采用 **分层存储 + 增量同步 + 冲突检测** 的方案: ``` ┌─────────────────────────────────────────────────────────────┐ │ 离线存储架构 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 数据访问层 │ │ │ │ OfflineRepository │ │ │ │ - get(id): Promise │ │ │ │ - getAll(): Promise │ │ │ │ - save(item: T): Promise │ │ │ │ - delete(id: string): Promise │ │ │ │ - sync(): Promise │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ │ ┌──────────────────┼──────────────────┐ │ │ ▼ ▼ ▼ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ 缓存层 │ │ 持久层 │ │ 同步层 │ │ │ │ (Memory) │ │ (Storage) │ │ (Sync) │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ ├─────────────────────────────────────────────────────────────┤ │ 存储分区 │ ├─────────────────────────────────────────────────────────────┤ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 系统数据区 (System Zone) │ │ │ │ - 用户信息、Token、配置 │ │ │ │ - 容量: 1MB │ │ │ │ - 策略: 永久存储 │ │ │ └─────────────────────────────────────────────────────┘ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 业务数据区 (Business Zone) │ │ │ │ - 工单、巡检任务、访客记录 │ │ │ │ - 容量: 5MB │ │ │ │ - 策略: LRU淘汰 │ │ │ └─────────────────────────────────────────────────────┘ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 临时数据区 (Temp Zone) │ │ │ │ - 表单草稿、临时照片 │ │ │ │ - 容量: 2MB │ │ │ │ - 策略: 定时清理 │ │ │ └─────────────────────────────────────────────────────┘ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 同步队列区 (Queue Zone) │ │ │ │ - 待同步操作 │ │ │ │ - 容量: 1MB │ │ │ │ - 策略: 先进先出 │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### 3.2 数据模型设计 ```typescript // 离线数据基础模型 interface OfflineEntity { id: string _offline?: { version: number // 本地版本号 serverVersion: number // 服务器版本号 lastModified: number // 最后修改时间 syncStatus: SyncStatus // 同步状态 localChanges: boolean // 是否有本地修改 } } // 同步状态枚举 enum SyncStatus { SYNCED = 'SYNCED', // 已同步 PENDING = 'PENDING', // 待同步 CONFLICT = 'CONFLICT', // 冲突 DELETED = 'DELETED' // 已删除 } // 同步操作类型 enum SyncOperation { CREATE = 'CREATE', UPDATE = 'UPDATE', DELETE = 'DELETE' } // 同步队列项 interface SyncQueueItem { id: string entityType: string // 实体类型: WorkOrder, Inspection, Visitor entityId: string // 实体ID operation: SyncOperation payload: any // 操作数据 timestamp: number retryCount: number lastError?: string } ``` ### 3.3 核心接口设计 ```typescript // 离线存储接口 interface OfflineStorage { // 数据操作 get(id: string): Promise getAll(): Promise save(entity: T): Promise delete(id: string): Promise // 查询 query(filter: QueryFilter): Promise // 同步 sync(): Promise getPendingChanges(): Promise // 状态 isOffline(): boolean getLastSyncTime(): number } // 同步引擎接口 interface SyncEngine { // 同步操作 push(): Promise // 推送本地变更 pull(): Promise // 拉取远程变更 fullSync(): Promise // 完整同步 // 冲突处理 resolveConflict(item: SyncQueueItem, resolution: ConflictResolution): Promise // 事件 onSyncStart: Event onSyncComplete: Event onConflict: Event } // 冲突解决策略 enum ConflictResolution { LOCAL_WINS = 'LOCAL_WINS', // 本地优先 SERVER_WINS = 'SERVER_WINS', // 服务器优先 MERGE = 'MERGE', // 合并 MANUAL = 'MANUAL' // 手动解决 } ``` ### 3.4 业务数据离线策略 #### 3.4.1 工单模块 | 数据 | 离线读取 | 离线写入 | 同步策略 | |------|---------|---------|---------| | 工单列表 | ✅ 缓存 | ❌ | 下拉刷新时同步 | | 工单详情 | ✅ 缓存 | ❌ | 查看时同步 | | 接单操作 | ❌ | ✅ 队列 | 网络恢复后同步 | | 开始处理 | ❌ | ✅ 队列 | 网络恢复后同步 | | 完成工单 | ❌ | ✅ 队列 | 网络恢复后同步 | | 上传照片 | ❌ | ✅ 队列 | 网络恢复后同步 | #### 3.4.2 巡检模块 | 数据 | 离线读取 | 离线写入 | 同步策略 | |------|---------|---------|---------| | 巡检任务列表 | ✅ 缓存 | ❌ | 每日首次打开同步 | | 巡检任务详情 | ✅ 缓存 | ❌ | 查看时同步 | | 签到记录 | ❌ | ✅ 队列 | 网络恢复后同步 | | 巡检结果 | ❌ | ✅ 队列 | 网络恢复后同步 | | 异常上报 | ❌ | ✅ 队列 | 网络恢复后同步 | | 巡检照片 | ❌ | ✅ 队列 | 网络恢复后同步 | #### 3.4.3 访客模块 | 数据 | 离线读取 | 离线写入 | 同步策略 | |------|---------|---------|---------| | 访客记录 | ✅ 缓存 | ❌ | 每次进入页面同步 | | 访客登记 | ❌ | ✅ 队列 | 网络恢复后同步 | | 凭证验证 | ❌ | ❌ | 必须在线 | | 通行确认 | ❌ | ✅ 队列 | 网络恢复后同步 | --- ## 四、实现计划 ### 4.1 阶段一:基础增强 (1-2天) 1. **完善数据模型** - 定义 `OfflineEntity` 基类 - 为业务实体添加离线元数据 2. **增强存储层** - 实现存储分区管理 - 添加容量限制和LRU淘汰 - 实现数据加密 3. **优化同步队列** - 支持操作优先级 - 添加依赖关系处理 - 实现批量同步 ### 4.2 阶段二:同步引擎 (2-3天) 1. **增量同步** - 实现版本号比较 - 只同步变更数据 2. **冲突检测** - 检测版本冲突 - 记录冲突信息 3. **冲突解决** - 实现自动解决策略 - 提供手动解决接口 ### 4.3 阶段三:业务集成 (2-3天) 1. **工单模块集成** - 实现 `WorkOrderRepository` - 集成离线操作 2. **巡检模块集成** - 实现 `InspectionRepository` - 集成离线操作 3. **访客模块集成** - 实现 `VisitorRepository` - 集成离线操作 ### 4.4 阶段四:测试优化 (1-2天) 1. **单元测试** - 存储层测试 - 同步引擎测试 2. **集成测试** - 离线场景测试 - 网络恢复测试 3. **性能优化** - 存储性能优化 - 同步性能优化 --- ## 五、关键代码示例 ### 5.1 离线存储基类 ```typescript // src/stores/offline/base-repository.ts export abstract class BaseRepository { protected cacheKey: string protected syncEndpoint: string constructor(cacheKey: string, syncEndpoint: string) { this.cacheKey = cacheKey this.syncEndpoint = syncEndpoint } async get(id: string): Promise { // 先从内存缓存读取 const cached = memoryCache.get(`${this.cacheKey}:${id}`) if (cached) return cached // 再从本地存储读取 const stored = offlineStorage.get(`${this.cacheKey}:${id}`) if (stored) { memoryCache.set(`${this.cacheKey}:${id}`, stored) return stored } // 最后从服务器获取 if (networkStatus.value.isConnected) { const remote = await this.fetchFromServer(id) if (remote) { await this.saveToCache(remote) return remote } } return null } async save(entity: T): Promise { // 更新离线元数据 entity._offline = { version: (entity._offline?.version || 0) + 1, serverVersion: entity._offline?.serverVersion || 0, lastModified: Date.now(), syncStatus: SyncStatus.PENDING, localChanges: true } // 保存到本地 await this.saveToCache(entity) // 添加到同步队列 await syncQueue.add({ entityType: this.cacheKey, entityId: entity.id, operation: entity._offline.serverVersion === 0 ? SyncOperation.CREATE : SyncOperation.UPDATE, payload: entity, timestamp: Date.now(), retryCount: 0 }) } async sync(): Promise { if (!networkStatus.value.isConnected) { return { success: false, reason: 'offline' } } // 推送本地变更 const pushResult = await this.pushChanges() // 拉取远程变更 const pullResult = await this.pullChanges() return { success: pushResult.success && pullResult.success, pushed: pushResult.count, pulled: pullResult.count, conflicts: [...pushResult.conflicts, ...pullResult.conflicts] } } } ``` ### 5.2 同步队列增强 ```typescript // src/stores/offline/sync-queue.ts export class SyncQueue { private queue: SyncQueueItem[] = [] private processing = false // 添加操作到队列 async add(item: Omit): Promise { const queueItem: SyncQueueItem = { ...item, id: `${Date.now()}_${Math.random().toString(36).substr(2, 9)}` } this.queue.push(queueItem) await this.persist() // 如果在线,立即尝试同步 if (networkStatus.value.isConnected && !this.processing) { this.process() } } // 处理队列 async process(): Promise { if (this.processing || !networkStatus.value.isConnected) return this.processing = true try { // 按优先级排序 this.sortByPriority() // 批量处理 const batch = this.queue.slice(0, 10) for (const item of batch) { try { const success = await this.processItem(item) if (success) { this.queue = this.queue.filter(q => q.id !== item.id) } else { item.retryCount++ if (item.retryCount >= 3) { // 超过重试次数,标记为失败 item.lastError = 'Max retry exceeded' } } } catch (error) { item.lastError = String(error) item.retryCount++ } } await this.persist() } finally { this.processing = false } } // 处理单个操作 private async processItem(item: SyncQueueItem): Promise { const handler = this.handlers.get(item.entityType) if (!handler) return false return await handler(item) } } ``` ### 5.3 冲突检测与解决 ```typescript // src/stores/offline/conflict-resolver.ts export class ConflictResolver { // 检测冲突 detectConflict(local: OfflineEntity, remote: OfflineEntity): boolean { return local._offline.serverVersion !== remote._offline.version } // 自动解决冲突 async resolve(local: T, remote: T, strategy: ConflictResolution): Promise { switch (strategy) { case ConflictResolution.LOCAL_WINS: return this.mergeWithLocalWins(local, remote) case ConflictResolution.SERVER_WINS: return this.mergeWithServerWins(local, remote) case ConflictResolution.MERGE: return this.merge(local, remote) case ConflictResolution.MANUAL: throw new ConflictError('Manual resolution required', local, remote) } } // 合并策略:本地优先 private mergeWithLocalWins(local: T, remote: T): T { return { ...remote, ...local, _offline: { ...local._offline, serverVersion: remote._offline.version, syncStatus: SyncStatus.PENDING } } } // 合并策略:服务器优先 private mergeWithServerWins(local: T, remote: T): T { return { ...local, ...remote, _offline: { version: remote._offline.version, serverVersion: remote._offline.version, lastModified: Date.now(), syncStatus: SyncStatus.SYNCED, localChanges: false } } } // 合并策略:字段级合并 private merge(local: T, remote: T): T { const merged = { ...remote } for (const key of Object.keys(local)) { if (key === '_offline') continue const localValue = local[key] const remoteValue = remote[key] // 如果本地修改了该字段,使用本地值 if (localValue !== remoteValue && local._offline.localChanges) { merged[key] = localValue } } merged._offline = { version: Math.max(local._offline.version, remote._offline.version), serverVersion: remote._offline.version, lastModified: Date.now(), syncStatus: SyncStatus.PENDING, localChanges: true } return merged } } ``` --- ## 六、监控与调试 ### 6.1 存储监控 ```typescript // 存储监控接口 interface StorageMonitor { // 容量统计 getUsage(): { total: number used: number zones: Record } // 同步统计 getSyncStats(): { pending: number synced: number failed: number lastSyncTime: number } // 清理建议 getCleanupRecommendations(): CleanupRecommendation[] } ``` ### 6.2 调试工具 ```typescript // 开发环境调试工具 if (process.env.NODE_ENV === 'development') { window.__offlineDebug__ = { // 查看存储内容 inspect: () => offlineStorage.getAll(), // 查看同步队列 queue: () => syncQueue.getAll(), // 模拟离线 goOffline: () => networkStatus.value.isConnected = false, // 模拟在线 goOnline: () => networkStatus.value.isConnected = true, // 强制同步 forceSync: () => syncEngine.fullSync(), // 清空存储 clear: () => offlineStorage.clear() } } ``` --- ## 七、风险与对策 | 风险 | 影响 | 对策 | |------|------|------| | 存储容量超限 | 数据丢失 | LRU淘汰 + 容量监控 | | 数据冲突 | 数据不一致 | 版本控制 + 冲突检测 | | 同步失败 | 数据丢失 | 重试机制 + 本地备份 | | 性能下降 | 用户体验差 | 增量同步 + 后台处理 | | 安全风险 | 数据泄露 | 敏感数据加密 | --- ## 八、总结 本方案采用 **离线优先** 架构,通过以下措施确保数据一致性和用户体验: 1. **分层存储**: 系统数据、业务数据、临时数据分区管理 2. **增量同步**: 基于版本号的增量同步,减少数据传输 3. **冲突检测**: 自动检测和解决数据冲突 4. **容量管理**: LRU淘汰策略,防止存储溢出 5. **安全加密**: 敏感数据加密存储 **预计开发周期**: 6-10天 **技术复杂度**: 中等 **维护成本**: 低