HarmonyOS分布式文件开发小技巧

举报
Jack20 发表于 2026/06/19 22:49:01 2026/06/19
【摘要】 文件是最常见的数据载体:照片、文档、视频、音频…当这些文件能在手机、平板、电脑间自由流转,多设备协同才真正有了"烟火气"。 一、背景与动机:为什么需要分布式文件? 1.1 传统文件共享的痛点场景1:照片分享手机拍了照片,想在电视上展示传统方案:微信传输、数据线拷贝、网盘中转问题:操作繁琐、速度慢、隐私风险场景2:文档编辑在平板上编辑文档,回办公室想在电脑继续传统方案:邮件发送、网盘同步、U盘...

文件是最常见的数据载体:照片、文档、视频、音频…当这些文件能在手机、平板、电脑间自由流转,多设备协同才真正有了"烟火气"。

一、背景与动机:为什么需要分布式文件?

1.1 传统文件共享的痛点

场景1:照片分享

  • 手机拍了照片,想在电视上展示
  • 传统方案:微信传输、数据线拷贝、网盘中转
  • 问题:操作繁琐、速度慢、隐私风险

场景2:文档编辑

  • 在平板上编辑文档,回办公室想在电脑继续
  • 传统方案:邮件发送、网盘同步、U盘拷贝
  • 问题:版本混乱、忘记同步、覆盖冲突

场景3:视频观看

  • 下载的电影在手机上,想在智慧屏播放
  • 传统方案:重新下载、局域网共享、投屏
  • 问题:流量浪费、配置复杂、格式限制

1.2 分布式文件的设计目标

HarmonyOS分布式文件服务的设计目标:
图片.png

二、核心原理:分布式文件系统架构

2.1 虚拟文件系统

分布式文件系统为应用提供统一的文件视图:

应用视角:
/data/user/0/com.example.app/
├── local/          # 本地文件(仅本设备可见)
├── distributed/    # 分布式文件(跨设备可见)
│   ├── device_A/   # 设备A的文件
│   ├── device_B/   # 设备B的文件
│   └── shared/     # 共享文件
└── cache/          # 缓存文件

2.2 文件访问流程

sequenceDiagram
    participant App as 应用
    participant VFS as 虚拟文件系统
    participant Cache as 本地缓存
    participant Remote as 远端设备
    
    App->>VFS: open('/distributed/photo.jpg')
    VFS->>Cache: 检查本地缓存
    
    alt 缓存命中
        Cache-->>VFS: 返回缓存文件
        VFS-->>App: 文件句柄
    else 缓存未命中
        VFS->>Remote: 请求文件
        Remote-->>VFS: 传输文件数据
        VFS->>Cache: 写入缓存
        VFS-->>App: 文件句柄
    end
    
    App->>VFS: read()
    VFS-->>App: 文件内容
    
    classDef primary fill:#4A90E2,stroke:#2E5C8A,stroke-width:2px,color:#fff
    classDef warning fill:#F5A623,stroke:#C17D10,stroke-width:2px,color:#fff
    classDef info fill:#7ED321,stroke:#5BA318,stroke-width:2px,color:#fff
    
    class App primary
    class VFS,Cache warning
    class Remote info

2.3 文件传输策略

按需传输

  • 只有实际访问的文件才传输
  • 避免全量同步浪费带宽

增量传输

  • 文件修改时,只传输变更部分
  • 适用于大文件的频繁修改

预取策略

  • 根据访问模式预测,提前传输可能需要的文件
  • 提升用户体验

2.4 文件元数据

每个分布式文件都携带元数据:

interface FileMetadata {
  // 基本信息
  name: string;              // 文件名
  size: number;              // 文件大小
  type: string;              // MIME类型
  extension: string;         // 扩展名
  
  // 位置信息
  deviceId: string;          // 来源设备ID
  path: string;              // 在来源设备的路径
  
  // 时间信息
  createTime: number;        // 创建时间
  modifyTime: number;        // 修改时间
  accessTime: number;        // 访问时间
  
  // 校验信息
  hash: string;              // 文件哈希值
  version: number;           // 版本号
  
  // 权限信息
  permissions: FilePermission;
}

interface FilePermission {
  readable: boolean;
  writable: boolean;
  shareable: boolean;
  deletable: boolean;
}

三、代码实战:实现分布式文件共享

3.1 基础示例:文件共享与访问

import fileShare from '@ohos.file.fileShare';
import fs from '@ohos.file.fs';
import { BusinessError } from '@ohos.base';

/**
 * 分布式文件管理器
 * 提供文件的共享、访问、传输能力
 */
export class DistributedFileManager {
  private fileShareManager: fileShare.FileShareManager | null = null;
  
  /**
   * 初始化文件共享服务
   */
  async init(): Promise<void> {
    try {
      // 创建文件共享管理器
      this.fileShareManager = fileShare.createFileShareManager();
      
      console.info('[DistributedFile] 文件共享服务初始化成功');
    } catch (error) {
      const err = error as BusinessError;
      console.error(`[DistributedFile] 初始化失败: ${err.code} - ${err.message}`);
      throw error;
    }
  }
  
  /**
   * 共享文件
   * 将本地文件共享给其他设备
   * @param localPath 本地文件路径
   * @param shareName 共享名称(其他设备看到的名称)
   */
  async shareFile(localPath: string, shareName?: string): Promise<string> {
    if (!this.fileShareManager) {
      throw new Error('文件共享服务未初始化');
    }
    
    try {
      // 检查文件是否存在
      const stat = fs.statSync(localPath);
      if (!stat.isFile()) {
        throw new Error('指定路径不是文件');
      }
      
      // 共享配置
      const shareConfig: fileShare.ShareConfig = {
        path: localPath,
        name: shareName || this.extractFileName(localPath),
        mode: fileShare.ShareMode.READ_WRITE,  // 读写模式
        autoSync: true                          // 自动同步
      };
      
      // 执行共享
      const shareResult = await this.fileShareManager.share(shareConfig);
      
      console.info(`[DistributedFile] 文件共享成功: ${localPath} -> ${shareResult.shareId}`);
      console.info(`  共享名称: ${shareConfig.name}`);
      console.info(`  文件大小: ${stat.size} bytes`);
      
      return shareResult.shareId;
    } catch (error) {
      console.error(`[DistributedFile] 共享失败: ${error}`);
      throw error;
    }
  }
  
  /**
   * 访问远端文件
   * @param deviceId 远端设备ID
   * @param shareId 共享ID
   * @returns 本地访问路径
   */
  async accessRemoteFile(deviceId: string, shareId: string): Promise<string> {
    if (!this.fileShareManager) {
      throw new Error('文件共享服务未初始化');
    }
    
    try {
      // 访问配置
      const accessConfig: fileShare.AccessConfig = {
        deviceId: deviceId,
        shareId: shareId,
        cacheMode: fileShare.CacheMode.AUTO  // 自动缓存
      };
      
      // 访问远端文件
      const accessResult = await this.fileShareManager.access(accessConfig);
      
      console.info(`[DistributedFile] 远端文件访问成功`);
      console.info(`  设备: ${deviceId}`);
      console.info(`  本地路径: ${accessResult.localPath}`);
      console.info(`  文件大小: ${accessResult.size} bytes`);
      
      return accessResult.localPath;
    } catch (error) {
      console.error(`[DistributedFile] 访问失败: ${error}`);
      throw error;
    }
  }
  
  /**
   * 列出可访问的远端文件
   * @param deviceId 远端设备ID
   */
  async listRemoteFiles(deviceId: string): Promise<RemoteFileInfo[]> {
    if (!this.fileShareManager) {
      throw new Error('文件共享服务未初始化');
    }
    
    try {
      const files = await this.fileShareManager.listSharedFiles(deviceId);
      
      console.info(`[DistributedFile] 设备 ${deviceId} 共享了 ${files.length} 个文件:`);
      for (const file of files) {
        console.info(`  - ${file.name} (${this.formatSize(file.size)})`);
      }
      
      return files;
    } catch (error) {
      console.error(`[DistributedFile] 列出文件失败: ${error}`);
      throw error;
    }
  }
  
  /**
   * 取消文件共享
   * @param shareId 共享ID
   */
  async unshareFile(shareId: string): Promise<void> {
    if (!this.fileShareManager) {
      throw new Error('文件共享服务未初始化');
    }
    
    try {
      await this.fileShareManager.unshare(shareId);
      console.info(`[DistributedFile] 文件共享已取消: ${shareId}`);
    } catch (error) {
      console.error(`[DistributedFile] 取消共享失败: ${error}`);
      throw error;
    }
  }
  
  /**
   * 从路径提取文件名
   */
  private extractFileName(path: string): string {
    const parts = path.split('/');
    return parts[parts.length - 1];
  }
  
  /**
   * 格式化文件大小
   */
  private formatSize(bytes: number): string {
    if (bytes < 1024) return `${bytes} B`;
    if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
    if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
    return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
  }
}

interface RemoteFileInfo {
  shareId: string;
  name: string;
  size: number;
  type: string;
  modifyTime: number;
}

3.2 进阶示例:文件传输与监控

import fileShare from '@ohos.file.fileShare';
import fs from '@ohos.file.fs';

/**
 * 文件传输管理器
 * 提供传输进度监控、断点续传能力
 */
export class FileTransferManager {
  private fileShareManager: fileShare.FileShareManager | null = null;
  private transferCallbacks: Map<string, TransferCallback> = new Map();
  
  /**
   * 初始化
   */
  async init(): Promise<void> {
    this.fileShareManager = fileShare.createFileShareManager();
    this.setupTransferListener();
  }
  
  /**
   * 下载远端文件(带进度)
   * @param deviceId 远端设备ID
   * @param shareId 共享ID
   * @param localPath 本地保存路径
   * @param onProgress 进度回调
   */
  async downloadFile(deviceId: string, shareId: string, localPath: string,
                     onProgress?: (progress: TransferProgress) => void): Promise<void> {
    if (!this.fileShareManager) {
      throw new Error('文件共享服务未初始化');
    }
    
    try {
      // 获取远端文件信息
      const accessResult = await this.fileShareManager.access({
        deviceId,
        shareId,
        cacheMode: fileShare.CacheMode.MANUAL
      });
      
      const remotePath = accessResult.localPath;
      const fileSize = accessResult.size;
      
      // 注册进度回调
      const transferId = `download_${Date.now()}`;
      if (onProgress) {
        this.transferCallbacks.set(transferId, { onProgress });
      }
      
      // 执行下载
      await this.copyFileWithProgress(remotePath, localPath, fileSize, (copied, total) => {
        if (onProgress) {
          onProgress({
            transferId,
            deviceId,
            fileName: accessResult.name,
            transferred: copied,
            total: total,
            speed: 0,  // 实际应计算速度
            percentage: (copied / total) * 100
          });
        }
      });
      
      // 清理回调
      this.transferCallbacks.delete(transferId);
      
      console.info(`[FileTransfer] 文件下载完成: ${localPath}`);
    } catch (error) {
      console.error(`[FileTransfer] 下载失败: ${error}`);
      throw error;
    }
  }
  
  /**
   * 带进度的文件复制
   */
  private async copyFileWithProgress(srcPath: string, dstPath: string, 
                                     totalSize: number,
                                     onProgress: (copied: number, total: number) => void): Promise<void> {
    const CHUNK_SIZE = 64 * 1024;  // 64KB每块
    
    // 打开源文件
    const srcFile = fs.openSync(srcPath, fs.OpenMode.READ_ONLY);
    
    // 创建目标文件
    const dstFile = fs.openSync(dstPath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY);
    
    try {
      let copied = 0;
      const buffer = new ArrayBuffer(CHUNK_SIZE);
      
      while (copied < totalSize) {
        // 读取一块数据
        const readLen = fs.readSync(srcFile.fd, buffer);
        if (readLen === 0) break;
        
        // 写入目标文件
        fs.writeSync(dstFile.fd, buffer, { length: readLen });
        
        copied += readLen;
        
        // 报告进度
        onProgress(copied, totalSize);
        
        // 让出执行权,避免阻塞
        await new Promise(resolve => setTimeout(resolve, 0));
      }
    } finally {
      // 关闭文件
      fs.closeSync(srcFile);
      fs.closeSync(dstFile);
    }
  }
  
  /**
   * 设置传输监听
   */
  private setupTransferListener(): void {
    if (!this.fileShareManager) return;
    
    // 监听传输状态变化
    this.fileShareManager.on('transferStateChange', (event: fileShare.TransferEvent) => {
      const callback = this.transferCallbacks.get(event.transferId);
      if (callback && callback.onProgress) {
        callback.onProgress({
          transferId: event.transferId,
          deviceId: event.deviceId,
          fileName: event.fileName,
          transferred: event.transferred,
          total: event.total,
          speed: event.speed,
          percentage: (event.transferred / event.total) * 100
        });
      }
    });
  }
  
  /**
   * 取消传输
   */
  async cancelTransfer(transferId: string): Promise<void> {
    if (!this.fileShareManager) return;
    
    await this.fileShareManager.cancelTransfer(transferId);
    this.transferCallbacks.delete(transferId);
    
    console.info(`[FileTransfer] 传输已取消: ${transferId}`);
  }
}

interface TransferCallback {
  onProgress?: (progress: TransferProgress) => void;
}

interface TransferProgress {
  transferId: string;
  deviceId: string;
  fileName: string;
  transferred: number;    // 已传输字节数
  total: number;          // 总字节数
  speed: number;          // 传输速度(bytes/s)
  percentage: number;     // 完成百分比
}

3.3 高级示例:文件同步与版本管理

import fileShare from '@ohos.file.fileShare';
import fs from '@ohos.file.fs';
import crypto from '@ohos.security.crypto';

/**
 * 文件版本信息
 */
interface FileVersion {
  version: number;
  hash: string;
  timestamp: number;
  deviceId: string;
  size: number;
}

/**
 * 文件同步状态
 */
enum SyncStatus {
  SYNCED = 'synced',           // 已同步
  PENDING = 'pending',         // 待同步
  CONFLICT = 'conflict',       // 冲突
  SYNCING = 'syncing'          // 同步中
}

/**
 * 高级文件同步管理器
 * 提供版本控制、冲突检测、增量同步能力
 */
export class AdvancedFileSync {
  private fileShareManager: fileShare.FileShareManager | null = null;
  private versionCache: Map<string, FileVersion> = new Map();
  
  /**
   * 初始化
   */
  async init(): Promise<void> {
    this.fileShareManager = fileShare.createFileShareManager();
    await this.loadVersionCache();
  }
  
  /**
   * 同步文件(带版本控制)
   * @param localPath 本地文件路径
   * @param remoteDeviceId 远端设备ID
   * @param remoteShareId 远端共享ID
   */
  async syncFile(localPath: string, remoteDeviceId: string, remoteShareId: string): Promise<SyncResult> {
    const result: SyncResult = {
      status: SyncStatus.SYNCED,
      localPath,
      message: ''
    };
    
    try {
      // 计算本地文件哈希
      const localHash = await this.calculateFileHash(localPath);
      const localStat = fs.statSync(localPath);
      
      // 获取本地版本信息
      const localVersion = this.versionCache.get(localPath);
      
      // 获取远端文件信息
      const remoteInfo = await this.fileShareManager!.getShareInfo(remoteShareId, remoteDeviceId);
      
      // 检查是否需要同步
      if (localVersion && localVersion.hash === remoteInfo.hash) {
        // 哈希相同,无需同步
        result.message = '文件已是最新,无需同步';
        return result;
      }
      
      // 检测冲突
      if (localVersion && localVersion.timestamp < remoteInfo.modifyTime) {
        // 远端更新,拉取远端
        result.status = SyncStatus.SYNCING;
        await this.pullRemoteFile(localPath, remoteDeviceId, remoteShareId);
        result.message = '已从远端拉取最新文件';
      } else if (localVersion && localVersion.timestamp > remoteInfo.modifyTime) {
        // 本地更新,推送到远端
        result.status = SyncStatus.SYNCING;
        await this.pushLocalFile(localPath, remoteDeviceId, remoteShareId);
        result.message = '已将本地文件推送到远端';
      } else {
        // 并发修改,冲突
        result.status = SyncStatus.CONFLICT;
        result.message = '检测到并发修改,需要手动解决冲突';
        result.conflict = {
          localHash,
          remoteHash: remoteInfo.hash,
          localTime: localVersion?.timestamp || 0,
          remoteTime: remoteInfo.modifyTime
        };
      }
      
      // 更新版本缓存
      this.updateVersionCache(localPath, {
        version: (localVersion?.version || 0) + 1,
        hash: localHash,
        timestamp: Date.now(),
        deviceId: await this.getDeviceId(),
        size: localStat.size
      });
      
      return result;
    } catch (error) {
      result.status = SyncStatus.PENDING;
      result.message = `同步失败: ${error}`;
      return result;
    }
  }
  
  /**
   * 增量同步
   * 只同步文件变更部分
   */
  async incrementalSync(localPath: string, remoteDeviceId: string, remoteShareId: string): Promise<void> {
    // 获取本地文件版本
    const localVersion = this.versionCache.get(localPath);
    if (!localVersion) {
      // 没有版本信息,执行全量同步
      await this.syncFile(localPath, remoteDeviceId, remoteShareId);
      return;
    }
    
    // 获取远端文件版本
    const remoteInfo = await this.fileShareManager!.getShareInfo(remoteShareId, remoteDeviceId);
    
    if (remoteInfo.modifyTime <= localVersion.timestamp) {
      // 远端未更新
      console.info('[FileSync] 远端文件未更新,无需同步');
      return;
    }
    
    // 执行增量同步
    // 这里简化处理,实际应根据文件类型实现增量算法
    // 例如:文本文件可以diff,二进制文件可以分块比较
    
    console.info('[FileSync] 执行增量同步');
    await this.pullRemoteFile(localPath, remoteDeviceId, remoteShareId);
  }
  
  /**
   * 批量同步目录
   */
  async syncDirectory(localDir: string, remoteDeviceId: string): Promise<BatchSyncResult> {
    const result: BatchSyncResult = {
      total: 0,
      synced: 0,
      failed: 0,
      conflicts: 0,
      details: []
    };
    
    try {
      // 列出本地文件
      const localFiles = this.listFilesRecursively(localDir);
      
      // 列出远端文件
      const remoteFiles = await this.fileShareManager!.listSharedFiles(remoteDeviceId);
      
      result.total = localFiles.length + remoteFiles.length;
      
      // 同步本地文件
      for (const localFile of localFiles) {
        try {
          const relativePath = localFile.substring(localDir.length);
          const remoteFile = remoteFiles.find(r => r.name === relativePath);
          
          if (remoteFile) {
            // 两端都有,执行同步
            const syncResult = await this.syncFile(localFile, remoteDeviceId, remoteFile.shareId);
            
            if (syncResult.status === SyncStatus.SYNCED) {
              result.synced++;
            } else if (syncResult.status === SyncStatus.CONFLICT) {
              result.conflicts++;
            }
            
            result.details.push({
              file: localFile,
              status: syncResult.status,
              message: syncResult.message
            });
          } else {
            // 仅本地有,推送到远端
            await this.shareFile(localFile);
            result.synced++;
          }
        } catch (error) {
          result.failed++;
          result.details.push({
            file: localFile,
            status: SyncStatus.PENDING,
            message: `同步失败: ${error}`
          });
        }
      }
      
      console.info(`[FileSync] 批量同步完成: 总计 ${result.total}, 成功 ${result.synced}, 失败 ${result.failed}, 冲突 ${result.conflicts}`);
      
      return result;
    } catch (error) {
      console.error(`[FileSync] 批量同步失败: ${error}`);
      throw error;
    }
  }
  
  /**
   * 计算文件哈希
   */
  private async calculateFileHash(filePath: string): Promise<string> {
    const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
    
    try {
      const stat = fs.statSync(filePath);
      const buffer = new ArrayBuffer(stat.size);
      fs.readSync(file.fd, buffer);
      
      // 使用MD5计算哈希
      const md = crypto.createMd('MD5');
      md.update(buffer);
      const hash = md.digest();
      
      // 转换为十六进制字符串
      return this.bufferToHex(hash);
    } finally {
      fs.closeSync(file);
    }
  }
  
  /**
   * ArrayBuffer转十六进制字符串
   */
  private bufferToHex(buffer: ArrayBuffer): string {
    const view = new Uint8Array(buffer);
    let hex = '';
    for (let i = 0; i < view.length; i++) {
      hex += view[i].toString(16).padStart(2, '0');
    }
    return hex;
  }
  
  /**
   * 递归列出目录下所有文件
   */
  private listFilesRecursively(dir: string): string[] {
    const files: string[] = [];
    const list = fs.listFileSync(dir);
    
    for (const name of list) {
      const path = `${dir}/${name}`;
      const stat = fs.statSync(path);
      
      if (stat.isFile()) {
        files.push(path);
      } else if (stat.isDirectory()) {
        files.push(...this.listFilesRecursively(path));
      }
    }
    
    return files;
  }
  
  /**
   * 拉取远端文件
   */
  private async pullRemoteFile(localPath: string, deviceId: string, shareId: string): Promise<void> {
    const accessResult = await this.fileShareManager!.access({
      deviceId,
      shareId,
      cacheMode: fileShare.CacheMode.AUTO
    });
    
    // 复制文件
    fs.copyFileSync(accessResult.localPath, localPath);
  }
  
  /**
   * 推送本地文件
   */
  private async pushLocalFile(localPath: string, deviceId: string, shareId: string): Promise<void> {
    // 实际项目中应实现推送逻辑
    console.info(`[FileSync] 推送文件到设备 ${deviceId}: ${localPath}`);
  }
  
  /**
   * 共享文件
   */
  private async shareFile(localPath: string): Promise<string> {
    const result = await this.fileShareManager!.share({
      path: localPath,
      name: this.extractFileName(localPath),
      mode: fileShare.ShareMode.READ_WRITE,
      autoSync: true
    });
    
    return result.shareId;
  }
  
  /**
   * 加载版本缓存
   */
  private async loadVersionCache(): Promise<void> {
    // 从本地存储加载版本信息
    // 这里简化处理
  }
  
  /**
   * 更新版本缓存
   */
  private updateVersionCache(path: string, version: FileVersion): void {
    this.versionCache.set(path, version);
    // 持久化到本地存储
  }
  
  private extractFileName(path: string): string {
    const parts = path.split('/');
    return parts[parts.length - 1];
  }
  
  private async getDeviceId(): Promise<string> {
    return 'device_' + Date.now();
  }
}

interface SyncResult {
  status: SyncStatus;
  localPath: string;
  message: string;
  conflict?: {
    localHash: string;
    remoteHash: string;
    localTime: number;
    remoteTime: number;
  };
}

interface BatchSyncResult {
  total: number;
  synced: number;
  failed: number;
  conflicts: number;
  details: Array<{
    file: string;
    status: SyncStatus;
    message: string;
  }>;
}

四、踩坑与注意事项

4.1 大文件传输超时

问题:大文件传输时,可能因超时而中断。

解决方案:分块传输 + 断点续传

/**
 * 断点续传管理器
 */
export class ResumableTransfer {
  private readonly CHUNK_SIZE = 1024 * 1024;  // 1MB每块
  private progressFile: string = '/data/transfer_progress.json';
  
  /**
   * 断点续传下载
   */
  async resumableDownload(srcPath: string, dstPath: string): Promise<void> {
    // 加载进度
    const progress = await this.loadProgress(dstPath);
    
    const srcFile = fs.openSync(srcPath, fs.OpenMode.READ_ONLY);
    const dstFile = fs.openSync(dstPath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY);
    
    try {
      const stat = fs.statSync(srcPath);
      const totalSize = stat.size;
      
      // 跳过已传输的部分
      if (progress.transferred > 0) {
        fs.lseekSync(dstFile.fd, progress.transferred, fs.WhenceType.SEEK_SET);
        console.info(`[ResumableTransfer] 从 ${progress.transferred} 字节处恢复传输`);
      }
      
      const buffer = new ArrayBuffer(this.CHUNK_SIZE);
      let offset = progress.transferred;
      
      while (offset < totalSize) {
        try {
          // 定位读取位置
          fs.lseekSync(srcFile.fd, offset, fs.WhenceType.SEEK_SET);
          
          // 读取一块
          const readLen = fs.readSync(srcFile.fd, buffer);
          if (readLen === 0) break;
          
          // 写入
          fs.writeSync(dstFile.fd, buffer, { length: readLen });
          
          offset += readLen;
          
          // 保存进度
          await this.saveProgress(dstPath, { transferred: offset, total: totalSize });
          
        } catch (error) {
          // 传输中断,保存进度后退出
          console.error(`[ResumableTransfer] 传输中断: ${error}`);
          await this.saveProgress(dstPath, { transferred: offset, total: totalSize });
          throw error;
        }
      }
      
      // 传输完成,清除进度
      await this.clearProgress(dstPath);
      
    } finally {
      fs.closeSync(srcFile);
      fs.closeSync(dstFile);
    }
  }
  
  /**
   * 加载传输进度
   */
  private async loadProgress(filePath: string): Promise<{ transferred: number; total: number }> {
    try {
      const data = fs.readTextSync(this.progressFile);
      const all = JSON.parse(data);
      return all[filePath] || { transferred: 0, total: 0 };
    } catch {
      return { transferred: 0, total: 0 };
    }
  }
  
  /**
   * 保存传输进度
   */
  private async saveProgress(filePath: string, progress: { transferred: number; total: number }): Promise<void> {
    let all: Record<string, any> = {};
    try {
      const data = fs.readTextSync(this.progressFile);
      all = JSON.parse(data);
    } catch {
      // 文件不存在
    }
    
    all[filePath] = progress;
    fs.writeTextSync(this.progressFile, JSON.stringify(all));
  }
  
  /**
   * 清除传输进度
   */
  private async clearProgress(filePath: string): Promise<void> {
    let all: Record<string, any> = {};
    try {
      const data = fs.readTextSync(this.progressFile);
      all = JSON.parse(data);
      delete all[filePath];
      fs.writeTextSync(this.progressFile, JSON.stringify(all));
    } catch {
      // 忽略
    }
  }
}

4.2 文件权限问题

问题:访问远端文件时,可能因权限不足而失败。

解决方案:权限检查 + 降级处理

/**
 * 文件权限检查器
 */
export class FilePermissionChecker {
  /**
   * 检查文件访问权限
   */
  async checkAccess(path: string, mode: 'r' | 'w' | 'rw'): Promise<boolean> {
    try {
      const stat = fs.statSync(path);
      
      // 检查读权限
      if (mode.includes('r')) {
        fs.accessSync(path, fs.AccessModeType.READ);
      }
      
      // 检查写权限
      if (mode.includes('w')) {
        fs.accessSync(path, fs.AccessModeType.WRITE);
      }
      
      return true;
    } catch (error) {
      console.warn(`[Permission] 文件访问权限不足: ${path}, 模式: ${mode}`);
      return false;
    }
  }
  
  /**
   * 安全打开文件
   * 失败时返回null而不是抛异常
   */
  async safeOpen(path: string, mode: number): Promise<fs.File | null> {
    const hasAccess = await this.checkAccess(path, mode & fs.OpenMode.READ_ONLY ? 'r' : 'rw');
    
    if (!hasAccess) {
      return null;
    }
    
    try {
      return fs.openSync(path, mode);
    } catch {
      return null;
    }
  }
}

4.3 缓存空间管理

问题:大量文件缓存占用过多空间。

解决方案:LRU缓存 + 空间限制

/**
 * 文件缓存管理器
 */
export class FileCacheManager {
  private maxCacheSize: number = 500 * 1024 * 1024;  // 500MB
  private cacheDir: string = '/data/file_cache';
  private cacheIndex: Map<string, CacheEntry> = new Map();
  
  /**
   * 初始化缓存
   */
  async init(): Promise<void> {
    await this.loadCacheIndex();
    await this.cleanupCache();
  }
  
  /**
   * 获取缓存文件
   */
  async getCache(key: string): Promise<string | null> {
    const entry = this.cacheIndex.get(key);
    if (!entry) {
      return null;
    }
    
    // 更新访问时间
    entry.lastAccess = Date.now();
    
    return entry.path;
  }
  
  /**
   * 添加缓存
   */
  async putCache(key: string, filePath: string): Promise<string> {
    // 计算需要的空间
    const stat = fs.statSync(filePath);
    const neededSize = stat.size;
    
    // 检查是否有足够空间
    const currentSize = this.getCurrentCacheSize();
    if (currentSize + neededSize > this.maxCacheSize) {
      // 空间不足,清理旧缓存
      await this.evictCache(neededSize);
    }
    
    // 复制文件到缓存目录
    const cachePath = `${this.cacheDir}/${key}`;
    fs.copyFileSync(filePath, cachePath);
    
    // 更新索引
    this.cacheIndex.set(key, {
      path: cachePath,
      size: neededSize,
      createTime: Date.now(),
      lastAccess: Date.now()
    });
    
    await this.saveCacheIndex();
    
    return cachePath;
  }
  
  /**
   * 清理缓存
   * 使用LRU策略
   */
  private async evictCache(neededSize: number): Promise<void> {
    // 按访问时间排序
    const entries = Array.from(this.cacheIndex.entries())
      .sort((a, b) => a[1].lastAccess - b[1].lastAccess);
    
    let freedSize = 0;
    
    for (const [key, entry] of entries) {
      // 删除缓存文件
      try {
        fs.unlinkSync(entry.path);
      } catch {
        // 忽略删除失败
      }
      
      // 从索引移除
      this.cacheIndex.delete(key);
      
      freedSize += entry.size;
      
      if (freedSize >= neededSize) {
        break;
      }
    }
    
    console.info(`[CacheManager] 清理了 ${freedSize} 字节缓存空间`);
    await this.saveCacheIndex();
  }
  
  /**
   * 获取当前缓存大小
   */
  private getCurrentCacheSize(): number {
    let total = 0;
    for (const entry of this.cacheIndex.values()) {
      total += entry.size;
    }
    return total;
  }
  
  /**
   * 加载缓存索引
   */
  private async loadCacheIndex(): Promise<void> {
    try {
      const indexPath = `${this.cacheDir}/index.json`;
      const data = fs.readTextSync(indexPath);
      const obj = JSON.parse(data);
      
      for (const [key, value] of Object.entries(obj)) {
        this.cacheIndex.set(key, value as CacheEntry);
      }
    } catch {
      // 索引不存在,忽略
    }
  }
  
  /**
   * 保存缓存索引
   */
  private async saveCacheIndex(): Promise<void> {
    const obj: Record<string, CacheEntry> = {};
    for (const [key, value] of this.cacheIndex) {
      obj[key] = value;
    }
    
    const indexPath = `${this.cacheDir}/index.json`;
    fs.writeTextSync(indexPath, JSON.stringify(obj));
  }
  
  /**
   * 清理无效缓存
   */
  private async cleanupCache(): Promise<void> {
    const toDelete: string[] = [];
    
    for (const [key, entry] of this.cacheIndex) {
      try {
        fs.accessSync(entry.path);
      } catch {
        // 文件不存在,标记删除
        toDelete.push(key);
      }
    }
    
    for (const key of toDelete) {
      this.cacheIndex.delete(key);
    }
    
    if (toDelete.length > 0) {
      await this.saveCacheIndex();
      console.info(`[CacheManager] 清理了 ${toDelete.length} 个无效缓存`);
    }
  }
}

interface CacheEntry {
  path: string;
  size: number;
  createTime: number;
  lastAccess: number;
}

五、HarmonyOS 6适配指南

5.1 API变更

5.1.1 文件共享API增强

// HarmonyOS 5.0
const result = await fileShareManager.share({
  path: localPath,
  name: fileName,
  mode: fileShare.ShareMode.READ_WRITE
});

// HarmonyOS 6 - 增强的共享配置
import fileShare from '@kit.FileShare';

const shareConfig: fileShare.ShareConfig = {
  path: localPath,
  name: fileName,
  mode: fileShare.ShareMode.READ_WRITE,
  
  // HarmonyOS 6新增配置
  autoSync: true,
  
  // 访问控制
  accessControl: {
    maxAccessCount: 10,           // 最大访问设备数
    expireTime: 7 * 24 * 60 * 60 * 1000,  // 过期时间(7天)
    password: '123456'            // 访问密码(可选)
  },
  
  // 传输配置
  transferConfig: {
    compress: true,               // 启用压缩
    encrypt: true,                // 启用加密
    chunkSize: 1024 * 1024,       // 分块大小(1MB)
    priority: 'high'              // 传输优先级
  },
  
  // 元数据
  metadata: {
    category: 'document',         // 文件分类
    tags: ['work', 'important'],  // 标签
    description: '项目文档'        // 描述
  }
};

const result = await fileShareManager.share(shareConfig);

5.1.2 文件访问API增强

// HarmonyOS 6新增:流式访问
const accessConfig: fileShare.AccessConfig = {
  deviceId: remoteDeviceId,
  shareId: shareId,
  
  // 访问模式
  accessMode: fileShare.AccessMode.STREAM,  // 流式访问
  
  // 缓存配置
  cacheConfig: {
    enabled: true,
    maxSize: 100 * 1024 * 1024,  // 最大缓存100MB
    policy: 'lru'                 // LRU策略
  },
  
  // 预取配置
  prefetch: {
    enabled: true,
    relatedFiles: ['file2.jpg', 'file3.jpg']  // 预取相关文件
  }
};

const stream = await fileShareManager.accessStream(accessConfig);

// 流式读取
const buffer = new ArrayBuffer(1024);
const readLen = await stream.read(buffer);

5.2 行为变更

5.2.1 自动压缩

// HarmonyOS 6:自动压缩传输
// 对于可压缩文件类型(文本、图片等),自动启用压缩

const shareConfig: fileShare.ShareConfig = {
  path: localPath,
  name: fileName,
  mode: fileShare.ShareMode.READ_WRITE,
  
  // 自动压缩配置
  compress: {
    enabled: true,
    algorithm: 'zstd',           // 压缩算法
    level: 3,                     // 压缩级别(1-9)
    threshold: 10 * 1024          // 超过10KB才压缩
  }
};

5.3 性能优化

/**
 * HarmonyOS 6文件共享性能优化
 */
export class HarmonyOS6FileOptimization {
  /**
   * 推荐配置
   */
  getOptimizedShareConfig(): fileShare.ShareConfig {
    return {
      path: '',
      name: '',
      mode: fileShare.ShareMode.READ_WRITE,
      autoSync: true,
      
      // 传输优化
      transferConfig: {
        compress: true,
        encrypt: false,  // 非敏感数据不加密,提升性能
        chunkSize: 2 * 1024 * 1024,  // 2MB分块
        priority: 'normal',
        
        // 并行传输
        parallel: {
          enabled: true,
          maxConnections: 4  // 最多4个并行连接
        }
      },
      
      // 缓存优化
      cacheConfig: {
        enabled: true,
        maxSize: 200 * 1024 * 1024,  // 200MB缓存
        policy: 'lru',
        prefetch: true               // 启用预取
      }
    };
  }
}

六、总结

分布式文件是HarmonyOS多设备协同的重要基础设施。通过本文的深度解析,我们掌握了:

核心要点

  1. 架构理解:虚拟文件系统、统一文件视图、按需传输
  2. API使用:文件共享、远端访问、传输监控、版本管理
  3. 传输优化:断点续传、分块传输、压缩传输
  4. 缓存管理:LRU策略、空间限制、预取策略
  5. 避坑指南:大文件超时、权限问题、缓存空间
  6. 版本适配:HarmonyOS 6的API增强、性能优化

最佳实践

  • 小文件:直接共享,自动同步
  • 大文件:分块传输,断点续传
  • 频繁访问:启用缓存,LRU管理
  • 敏感文件:启用加密,设置访问密码

应用场景

  • 照片共享:手机拍照,电视展示
  • 文档协同:平板编辑,电脑查看
  • 媒体播放:手机下载,智慧屏播放
  • 数据备份:重要文件跨设备备份

掌握分布式文件,让你的应用真正实现"文件随设备而动,设备随用户而在"的分布式愿景。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。