HarmonyOS智慧农业管理应用开发教程--高高种地--第29篇:数据管理与备份

第29篇:数据管理与备份

📚 本篇导读

数据管理是应用的重要功能,为用户提供数据导出、导入、备份和清理等功能。本篇教程将实现一个完整的数据管理系统,确保用户数据的安全性和可迁移性。

本篇将实现

  • 💾 数据导出功能(导出为JSON格式)
  • 📥 数据导入功能(从JSON文件导入)
  • 🔄 数据备份与恢复(使用HarmonyOS备份能力)
  • 🗑️ 数据清理功能(缓存清理、数据重置)
  • 📊 存储空间管理(查看数据占用情况)

🎯 学习目标

完成本篇教程后,你将掌握:

  1. 如何实现数据导出和导入功能
  2. 如何使用HarmonyOS的备份恢复能力
  3. 如何计算和管理应用存储空间
  4. 如何实现数据清理功能
  5. 文件系统操作和JSON数据处理
  6. 数据安全和隐私保护

一、功能架构设计

1.1 数据管理功能结构

复制代码
数据管理(DataManagementPage)
├── 数据统计区
│   ├── 总数据大小
│   ├── 缓存大小
│   └── 图片数量
│
├── 数据备份区
│   ├── 导出数据(JSON格式)
│   ├── 导入数据
│   └── 自动备份设置
│
├── 数据清理区
│   ├── 清除缓存
│   ├── 清除图片
│   └── 清除所有数据
│
└── 备份历史
    ├── 备份记录列表
    └── 恢复操作

1.2 数据结构设计

应用的数据主要包括:

typescript 复制代码
interface AppData {
  // 用户信息
  userProfile: {
    nickname: string;
    email: string;
    phone: string;
    location: string;
    bio: string;
    avatar: string;
    mode: AppMode;
  };
  
  // 地块数据(专业农业模式)
  fields: Field[];
  
  // 作物数据
  crops: Crop[];
  
  // 农事记录
  farmOperations: FarmOperation[];
  
  // 任务数据
  tasks: Task[];
  
  // 成本记录
  costRecords: CostRecord[];
  
  // 销售记录
  salesRecords: SalesRecord[];
  
  // 学习进度
  learningProgress: LearningProgress[];
  
  // 考试记录
  examRecords: ExamRecord[];
  
  // 设置数据
  settings: AppSettings;
  
  // 导出时间
  exportedAt: number;
  
  // 应用版本
  appVersion: string;
}

二、实现数据管理页面

2.1 页面结构

文件位置entry/src/main/ets/pages/Services/DataManagementPage.ets

typescript 复制代码
import { router } from '@kit.ArkUI';
import { promptAction } from '@kit.ArkUI';
import { StorageUtil } from '../../utils/StorageUtil';
import { fileIo as fs } from '@kit.CoreFileKit';
import { picker } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';

@Entry
@ComponentV2
struct DataManagementPage {
  @Local dataSize: string = '计算中...';
  @Local cacheSize: string = '计算中...';
  @Local imageCount: number = 0;
  @Local isExporting: boolean = false;
  @Local isImporting: boolean = false;
  
  private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;

  async aboutToAppear(): Promise<void> {
    await this.calculateDataSize();
  }

  build() {
    Column() {
      // 头部导航
      this.buildHeader()

      Scroll() {
        Column({ space: 16 }) {
          // 数据统计
          this.buildDataStats()

          // 数据备份
          this.buildBackupSection()

          // 数据清理
          this.buildCleanSection()
        }
        .padding(16)
      }
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor($r('app.color.background'))
  }
}

2.2 数据统计区域

显示当前应用的数据占用情况:

typescript 复制代码
@Builder
buildDataStats() {
  Column({ space: 12 }) {
    Text('数据统计')
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
      .width('100%')

    Row() {
      Column({ space: 4 }) {
        Text(this.dataSize)
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor($r('app.color.primary_professional'))
        Text('总数据大小')
          .fontSize(12)
          .fontColor($r('app.color.text_secondary'))
      }
      .layoutWeight(1)

      Column({ space: 4 }) {
        Text(this.cacheSize)
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor($r('app.color.warning'))
        Text('缓存大小')
          .fontSize(12)
          .fontColor($r('app.color.text_secondary'))
      }
      .layoutWeight(1)

      Column({ space: 4 }) {
        Text(this.imageCount.toString())
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor($r('app.color.success'))
        Text('图片数量')
          .fontSize(12)
          .fontColor($r('app.color.text_secondary'))
      }
      .layoutWeight(1)
    }
    .width('100%')
  }
  .width('100%')
  .padding(16)
  .backgroundColor($r('app.color.card_background'))
  .borderRadius(12)
}

2.3 数据备份区域

提供数据导出和导入功能:

typescript 复制代码
@Builder
buildBackupSection() {
  Column({ space: 12 }) {
    Text('数据备份')
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
      .width('100%')

    // 导出数据
    Row() {
      Column({ space: 4 }) {
        Text('📤 导出数据')
          .fontSize(15)
          .fontWeight(FontWeight.Medium)
        Text('将所有数据导出为JSON文件')
          .fontSize(12)
          .fontColor($r('app.color.text_secondary'))
      }
      .alignItems(HorizontalAlign.Start)
      .layoutWeight(1)

      Button(this.isExporting ? '导出中...' : '导出')
        .height(36)
        .fontSize(14)
        .enabled(!this.isExporting)
        .onClick(() => {
          this.exportData();
        })
    }
    .width('100%')
    .padding(12)
    .backgroundColor($r('app.color.background'))
    .borderRadius(8)

    // 导入数据
    Row() {
      Column({ space: 4 }) {
        Text('📥 导入数据')
          .fontSize(15)
          .fontWeight(FontWeight.Medium)
        Text('从JSON文件导入数据')
          .fontSize(12)
          .fontColor($r('app.color.text_secondary'))
      }
      .alignItems(HorizontalAlign.Start)
      .layoutWeight(1)

      Button(this.isImporting ? '导入中...' : '导入')
        .height(36)
        .fontSize(14)
        .enabled(!this.isImporting)
        .onClick(() => {
          this.importData();
        })
    }
    .width('100%')
    .padding(12)
    .backgroundColor($r('app.color.background'))
    .borderRadius(8)
  }
  .width('100%')
  .padding(16)
  .backgroundColor($r('app.color.card_background'))
  .borderRadius(12)
}

2.4 数据清理区域

提供缓存清理和数据重置功能:

typescript 复制代码
@Builder
buildCleanSection() {
  Column({ space: 12 }) {
    Text('数据清理')
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
      .width('100%')

    // 清除缓存
    Row() {
      Column({ space: 4 }) {
        Text('🗑️ 清除缓存')
          .fontSize(15)
          .fontWeight(FontWeight.Medium)
        Text('清除图片缓存和临时文件')
          .fontSize(12)
          .fontColor($r('app.color.text_secondary'))
      }
      .alignItems(HorizontalAlign.Start)
      .layoutWeight(1)

      Button('清除')
        .height(36)
        .fontSize(14)
        .backgroundColor($r('app.color.warning'))
        .onClick(() => {
          this.showClearCacheDialog();
        })
    }
    .width('100%')
    .padding(12)
    .backgroundColor($r('app.color.background'))
    .borderRadius(8)

    // 清除所有数据
    Row() {
      Column({ space: 4 }) {
        Text('⚠️ 清除所有数据')
          .fontSize(15)
          .fontWeight(FontWeight.Medium)
          .fontColor($r('app.color.danger'))
        Text('删除所有数据,无法恢复')
          .fontSize(12)
          .fontColor($r('app.color.text_secondary'))
      }
      .alignItems(HorizontalAlign.Start)
      .layoutWeight(1)

      Button('清除')
        .height(36)
        .fontSize(14)
        .backgroundColor($r('app.color.danger'))
        .onClick(() => {
          this.showClearAllDataDialog();
        })
    }
    .width('100%')
    .padding(12)
    .backgroundColor($r('app.color.background'))
    .borderRadius(8)
  }
  .width('100%')
  .padding(16)
  .backgroundColor($r('app.color.card_background'))
  .borderRadius(12)
}

三、实现数据导出功能

3.1 收集应用数据

创建一个方法来收集所有需要导出的数据:

typescript 复制代码
/**
 * 收集所有应用数据
 */
async collectAppData(): Promise<AppData> {
  try {
    // 收集用户信息
    const userProfile = {
      nickname: await StorageUtil.getString('user_nickname', ''),
      email: await StorageUtil.getString('email', ''),
      phone: await StorageUtil.getString('phone', ''),
      location: await StorageUtil.getString('location', ''),
      bio: await StorageUtil.getString('bio', ''),
      avatar: await StorageUtil.getString('user_avatar', ''),
      mode: await StorageUtil.getString('user_mode', 'HOME_GARDENING')
    };

    // 收集地块数据
    const fieldsJson = await StorageUtil.getString('fields', '[]');
    const fields = JSON.parse(fieldsJson);

    // 收集作物数据
    const cropsJson = await StorageUtil.getString('crops', '[]');
    const crops = JSON.parse(cropsJson);

    // 收集农事记录
    const operationsJson = await StorageUtil.getString('farm_operations', '[]');
    const farmOperations = JSON.parse(operationsJson);

    // 收集任务数据
    const tasksJson = await StorageUtil.getString('tasks', '[]');
    const tasks = JSON.parse(tasksJson);

    // 收集成本记录
    const costRecordsJson = await StorageUtil.getString('cost_records', '[]');
    const costRecords = JSON.parse(costRecordsJson);

    // 收集销售记录
    const salesRecordsJson = await StorageUtil.getString('sales_records', '[]');
    const salesRecords = JSON.parse(salesRecordsJson);

    // 收集学习进度
    const learningProgressJson = await StorageUtil.getString('learning_progress', '[]');
    const learningProgress = JSON.parse(learningProgressJson);

    // 收集考试记录
    const examRecordsJson = await StorageUtil.getString('exam_records', '[]');
    const examRecords = JSON.parse(examRecordsJson);

    // 收集设置数据
    const settings = {
      theme: await StorageUtil.getString('theme', 'light'),
      language: await StorageUtil.getString('language', 'zh-CN'),
      notificationEnabled: await StorageUtil.getBoolean('notification_enabled', true)
    };

    // 构建完整数据对象
    const appData: AppData = {
      userProfile,
      fields,
      crops,
      farmOperations,
      tasks,
      costRecords,
      salesRecords,
      learningProgress,
      examRecords,
      settings,
      exportedAt: Date.now(),
      appVersion: '1.0.0'
    };

    return appData;
  } catch (error) {
    console.error('Failed to collect app data:', error);
    throw Error('收集数据失败');
  }
}

3.2 导出数据到文件

将收集的数据导出为JSON文件:

typescript 复制代码
/**
 * 导出数据
 */
async exportData(): Promise<void> {
  this.isExporting = true;

  try {
    // 1. 收集所有数据
    const appData = await this.collectAppData();

    // 2. 转换为JSON字符串
    const jsonString = JSON.stringify(appData, null, 2);

    // 3. 生成文件名
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
    const fileName = `gaogao_backup_${timestamp}.json`;

    // 4. 保存到文件
    const filePath = this.context.filesDir + '/' + fileName;
    const file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
    fs.writeSync(file.fd, jsonString);
    fs.closeSync(file);

    // 5. 使用文档选择器让用户选择保存位置
    const documentSaveOptions = new picker.DocumentSaveOptions();
    documentSaveOptions.newFileNames = [fileName];
    documentSaveOptions.fileSuffixChoices = ['.json'];

    const documentPicker = new picker.DocumentViewPicker();
    const result = await documentPicker.save(documentSaveOptions);

    if (result && result.length > 0) {
      // 复制文件到用户选择的位置
      const targetUri = result[0];
      const sourceFile = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
      const targetFile = fs.openSync(targetUri, fs.OpenMode.WRITE_ONLY);

      const buffer = new ArrayBuffer(4096);
      let readLen = 0;
      while ((readLen = fs.readSync(sourceFile.fd, buffer)) > 0) {
        fs.writeSync(targetFile.fd, buffer, { length: readLen });
      }

      fs.closeSync(sourceFile);
      fs.closeSync(targetFile);

      // 删除临时文件
      fs.unlinkSync(filePath);

      promptAction.showToast({
        message: '数据导出成功',
        duration: 2000
      });
    } else {
      // 用户取消了保存
      fs.unlinkSync(filePath);
      promptAction.showToast({
        message: '导出已取消',
        duration: 2000
      });
    }
  } catch (error) {
    console.error('Failed to export data:', error);
    promptAction.showToast({
      message: '导出失败: ' + (error as Error).message,
      duration: 2000
    });
  } finally {
    this.isExporting = false;
  }
}

四、实现数据导入功能

4.1 选择导入文件

使用文档选择器让用户选择要导入的JSON文件:

typescript 复制代码
/**
 * 导入数据
 */
async importData(): Promise<void> {
  // 显示确认对话框
  const result = await promptAction.showDialog({
    title: '导入数据',
    message: '导入数据将覆盖当前所有数据,是否继续?',
    buttons: [
      { text: '取消', color: '#000000' },
      { text: '继续', color: '#FF9800' }
    ]
  });

  if (result.index !== 1) {
    return;
  }

  this.isImporting = true;

  try {
    // 1. 使用文档选择器选择文件
    const documentSelectOptions = new picker.DocumentSelectOptions();
    documentSelectOptions.maxSelectNumber = 1;
    documentSelectOptions.fileSuffixFilters = ['.json'];

    const documentPicker = new picker.DocumentViewPicker();
    const selectResult = await documentPicker.select(documentSelectOptions);

    if (!selectResult || selectResult.length === 0) {
      promptAction.showToast({
        message: '未选择文件',
        duration: 2000
      });
      return;
    }

    // 2. 读取文件内容
    const fileUri = selectResult[0];
    const file = fs.openSync(fileUri, fs.OpenMode.READ_ONLY);
    const stat = fs.statSync(fileUri);
    const buffer = new ArrayBuffer(stat.size);
    fs.readSync(file.fd, buffer);
    fs.closeSync(file);

    // 3. 解析JSON
    const jsonString = String.fromCharCode(...new Uint8Array(buffer));
    const appData = JSON.parse(jsonString) as AppData;

    // 4. 验证数据格式
    if (!this.validateImportData(appData)) {
      throw Error('数据格式不正确');
    }

    // 5. 导入数据
    await this.restoreAppData(appData);

    promptAction.showToast({
      message: '数据导入成功,应用将重启',
      duration: 2000
    });

    // 6. 重启应用
    setTimeout(() => {
      this.context.terminateSelf();
    }, 2000);
  } catch (error) {
    console.error('Failed to import data:', error);
    promptAction.showToast({
      message: '导入失败: ' + (error as Error).message,
      duration: 2000
    });
  } finally {
    this.isImporting = false;
  }
}

4.2 验证导入数据

验证导入的数据格式是否正确:

typescript 复制代码
/**
 * 验证导入数据的格式
 */
validateImportData(data: AppData): boolean {
  try {
    // 检查必需字段
    if (!data.exportedAt || !data.appVersion) {
      console.error('Missing required fields');
      return false;
    }

    // 检查数据类型
    if (typeof data.userProfile !== 'object') {
      console.error('Invalid userProfile');
      return false;
    }

    if (!Array.isArray(data.fields)) {
      console.error('Invalid fields');
      return false;
    }

    // 可以添加更多验证逻辑

    return true;
  } catch (error) {
    console.error('Validation error:', error);
    return false;
  }
}

4.3 恢复应用数据

将导入的数据恢复到应用中:

typescript 复制代码
/**
 * 恢复应用数据
 */
async restoreAppData(appData: AppData): Promise<void> {
  try {
    // 恢复用户信息
    await StorageUtil.setString('user_nickname', appData.userProfile.nickname);
    await StorageUtil.setString('email', appData.userProfile.email);
    await StorageUtil.setString('phone', appData.userProfile.phone);
    await StorageUtil.setString('location', appData.userProfile.location);
    await StorageUtil.setString('bio', appData.userProfile.bio);
    await StorageUtil.setString('user_avatar', appData.userProfile.avatar);
    await StorageUtil.setString('user_mode', appData.userProfile.mode);

    // 恢复地块数据
    await StorageUtil.setString('fields', JSON.stringify(appData.fields));

    // 恢复作物数据
    await StorageUtil.setString('crops', JSON.stringify(appData.crops));

    // 恢复农事记录
    await StorageUtil.setString('farm_operations', JSON.stringify(appData.farmOperations));

    // 恢复任务数据
    await StorageUtil.setString('tasks', JSON.stringify(appData.tasks));

    // 恢复成本记录
    await StorageUtil.setString('cost_records', JSON.stringify(appData.costRecords));

    // 恢复销售记录
    await StorageUtil.setString('sales_records', JSON.stringify(appData.salesRecords));

    // 恢复学习进度
    await StorageUtil.setString('learning_progress', JSON.stringify(appData.learningProgress));

    // 恢复考试记录
    await StorageUtil.setString('exam_records', JSON.stringify(appData.examRecords));

    // 恢复设置
    if (appData.settings) {
      await StorageUtil.setString('theme', appData.settings.theme);
      await StorageUtil.setString('language', appData.settings.language);
      await StorageUtil.setBoolean('notification_enabled', appData.settings.notificationEnabled);
    }

    console.info('App data restored successfully');
  } catch (error) {
    console.error('Failed to restore app data:', error);
    throw Error('恢复数据失败');
  }
}

五、实现数据清理功能

5.1 计算数据大小

计算应用占用的存储空间:

typescript 复制代码
/**
 * 计算数据大小
 */
async calculateDataSize(): Promise<void> {
  try {
    // 计算Preferences数据大小(估算)
    let totalSize = 0;

    // 获取所有存储的键值对
    const keys = [
      'user_nickname', 'email', 'phone', 'location', 'bio', 'user_avatar',
      'fields', 'crops', 'farm_operations', 'tasks', 'cost_records',
      'sales_records', 'learning_progress', 'exam_records'
    ];

    for (const key of keys) {
      const value = await StorageUtil.getString(key, '');
      totalSize += value.length;
    }

    // 转换为可读格式
    this.dataSize = this.formatFileSize(totalSize);

    // 计算缓存大小
    await this.calculateCacheSize();

    // 计算图片数量
    await this.calculateImageCount();
  } catch (error) {
    console.error('Failed to calculate data size:', error);
    this.dataSize = '未知';
  }
}

/**
 * 计算缓存大小
 */
async calculateCacheSize(): Promise<void> {
  try {
    const cacheDir = this.context.cacheDir;
    const size = await this.getDirectorySize(cacheDir);
    this.cacheSize = this.formatFileSize(size);
  } catch (error) {
    console.error('Failed to calculate cache size:', error);
    this.cacheSize = '未知';
  }
}

/**
 * 计算图片数量
 */
async calculateImageCount(): Promise<void> {
  try {
    const imageDir = this.context.filesDir + '/images';
    if (fs.accessSync(imageDir)) {
      const files = fs.listFileSync(imageDir);
      this.imageCount = files.length;
    } else {
      this.imageCount = 0;
    }
  } catch (error) {
    console.error('Failed to calculate image count:', error);
    this.imageCount = 0;
  }
}

/**
 * 获取目录大小
 */
async getDirectorySize(dirPath: string): Promise<number> {
  let totalSize = 0;

  try {
    if (!fs.accessSync(dirPath)) {
      return 0;
    }

    const files = fs.listFileSync(dirPath);

    for (const fileName of files) {
      const filePath = dirPath + '/' + fileName;
      const stat = fs.statSync(filePath);

      if (stat.isDirectory()) {
        totalSize += await this.getDirectorySize(filePath);
      } else {
        totalSize += stat.size;
      }
    }
  } catch (error) {
    console.error('Failed to get directory size:', error);
  }

  return totalSize;
}

/**
 * 格式化文件大小
 */
formatFileSize(bytes: number): string {
  if (bytes === 0) return '0 B';

  const k = 1024;
  const sizes = ['B', 'KB', 'MB', 'GB'];
  const i = Math.floor(Math.log(bytes) / Math.log(k));

  return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
}

5.2 清除缓存

清除应用的缓存数据:

typescript 复制代码
/**
 * 显示清除缓存对话框
 */
showClearCacheDialog(): void {
  promptAction.showDialog({
    title: '清除缓存',
    message: '确定要清除缓存吗?这将删除图片缓存和临时文件。',
    buttons: [
      { text: '取消', color: '#000000' },
      { text: '清除', color: '#FF9800' }
    ]
  }).then(async (result) => {
    if (result.index === 1) {
      await this.clearCache();
    }
  });
}

/**
 * 清除缓存
 */
async clearCache(): Promise<void> {
  try {
    const cacheDir = this.context.cacheDir;

    if (fs.accessSync(cacheDir)) {
      await this.deleteDirectory(cacheDir);
      fs.mkdirSync(cacheDir);
    }

    promptAction.showToast({
      message: '缓存已清除',
      duration: 2000
    });

    // 重新计算数据大小
    await this.calculateDataSize();
  } catch (error) {
    console.error('Failed to clear cache:', error);
    promptAction.showToast({
      message: '清除失败',
      duration: 2000
    });
  }
}

/**
 * 删除目录及其内容
 */
async deleteDirectory(dirPath: string): Promise<void> {
  try {
    if (!fs.accessSync(dirPath)) {
      return;
    }

    const files = fs.listFileSync(dirPath);

    for (const fileName of files) {
      const filePath = dirPath + '/' + fileName;
      const stat = fs.statSync(filePath);

      if (stat.isDirectory()) {
        await this.deleteDirectory(filePath);
        fs.rmdirSync(filePath);
      } else {
        fs.unlinkSync(filePath);
      }
    }
  } catch (error) {
    console.error('Failed to delete directory:', error);
  }
}

5.3 清除所有数据

清除应用的所有数据并重置:

typescript 复制代码
/**
 * 显示清除所有数据对话框
 */
showClearAllDataDialog(): void {
  promptAction.showDialog({
    title: '清除所有数据',
    message: '警告:此操作将删除所有地块、作物、任务和设置数据,且无法恢复!',
    buttons: [
      { text: '取消', color: '#000000' },
      { text: '确认删除', color: '#F44336' }
    ]
  }).then(async (result) => {
    if (result.index === 1) {
      await this.clearAllData();
    }
  });
}

/**
 * 清除所有数据
 */
async clearAllData(): Promise<void> {
  try {
    // 清除Preferences数据
    await StorageUtil.clear();

    // 清除文件数据
    await this.clearCache();

    // 清除图片
    const imageDir = this.context.filesDir + '/images';
    if (fs.accessSync(imageDir)) {
      await this.deleteDirectory(imageDir);
    }

    promptAction.showToast({
      message: '所有数据已清除,应用将重启',
      duration: 2000
    });

    // 重启应用到欢迎页
    setTimeout(() => {
      router.replaceUrl({ url: 'pages/WelcomePage' });
    }, 2000);
  } catch (error) {
    console.error('Failed to clear all data:', error);
    promptAction.showToast({
      message: '清除失败',
      duration: 2000
    });
  }
}

六、实现HarmonyOS备份能力

6.1 配置备份能力

module.json5 中配置备份扩展能力:

json5 复制代码
{
  "module": {
    "extensionAbilities": [
      {
        "name": "EntryBackupAbility",
        "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
        "type": "backup",
        "exported": false
      }
    ]
  }
}

6.2 实现备份扩展

文件位置entry/src/main/ets/entrybackupability/EntryBackupAbility.ets

typescript 复制代码
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit';

const DOMAIN = 0x0000;
const TAG = 'EntryBackupAbility';

export default class EntryBackupAbility extends BackupExtensionAbility {
  /**
   * 备份时调用
   */
  async onBackup() {
    hilog.info(DOMAIN, TAG, 'onBackup called');

    try {
      // 可以在这里执行备份前的准备工作
      // 例如:整理数据、生成备份清单等

      hilog.info(DOMAIN, TAG, 'Backup preparation completed');
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Backup preparation failed:', JSON.stringify(error));
    }

    await Promise.resolve();
  }

  /**
   * 恢复时调用
   */
  async onRestore(bundleVersion: BundleVersion) {
    hilog.info(DOMAIN, TAG, 'onRestore called, version: %{public}s', JSON.stringify(bundleVersion));

    try {
      // 可以在这里执行恢复后的处理工作
      // 例如:验证数据完整性、迁移数据格式等

      hilog.info(DOMAIN, TAG, 'Restore completed successfully');
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Restore failed:', JSON.stringify(error));
    }

    await Promise.resolve();
  }
}

6.3 备份数据说明

HarmonyOS系统会自动备份以下数据:

  • Preferences数据:应用的所有配置和用户数据
  • 数据库文件:如果使用了关系型数据库
  • 指定的文件:可以在配置中指定需要备份的文件

七、实操练习

7.1 测试数据导出

  1. 启动应用,进入"数据管理"页面
  2. 点击导出按钮,选择保存位置
  3. 查看导出的JSON文件,验证数据完整性
  4. 检查文件大小,确认数据已正确导出

7.2 测试数据导入

  1. 准备测试数据,可以使用之前导出的文件
  2. 点击导入按钮,选择JSON文件
  3. 确认导入,等待应用重启
  4. 验证数据,检查所有数据是否正确恢复

7.3 测试数据清理

  1. 查看数据统计,记录当前数据大小
  2. 清除缓存,观察缓存大小变化
  3. 测试清除所有数据,验证数据是否完全清除

八、常见问题

8.1 导出失败

问题:点击导出按钮后没有反应或报错

解决方案

  1. 检查文件系统权限
  2. 确保有足够的存储空间
  3. 查看日志中的错误信息
  4. 验证数据收集逻辑是否正确

8.2 导入数据格式错误

问题:导入时提示数据格式不正确

解决方案

  1. 检查JSON文件格式是否正确
  2. 验证必需字段是否存在
  3. 确认数据版本是否兼容
  4. 使用JSON验证工具检查文件

8.3 清除数据后无法恢复

问题:误操作清除了所有数据

解决方案

  1. 在清除前务必导出备份
  2. 使用系统级备份功能
  3. 添加二次确认机制
  4. 考虑实现软删除功能

8.4 备份能力不生效

问题:系统备份时应用数据未被备份

解决方案

  1. 检查 module.json5 配置
  2. 确认 BackupAbility 正确实现
  3. 验证备份权限配置
  4. 查看系统备份日志

九、数据安全建议

9.1 数据加密

对敏感数据进行加密:

typescript 复制代码
import { cryptoFramework } from '@kit.CryptoArchitectureKit';

/**
 * 加密数据
 */
async encryptData(data: string, key: string): Promise<string> {
  // 使用AES加密
  const cipher = cryptoFramework.createCipher('AES256|GCM|PKCS7');
  // 实现加密逻辑
  return encryptedData;
}

/**
 * 解密数据
 */
async decryptData(encryptedData: string, key: string): Promise<string> {
  // 使用AES解密
  const cipher = cryptoFramework.createCipher('AES256|GCM|PKCS7');
  // 实现解密逻辑
  return decryptedData;
}

9.2 数据完整性校验

添加数据校验机制:

typescript 复制代码
/**
 * 计算数据哈希值
 */
async calculateHash(data: string): Promise<string> {
  const md = cryptoFramework.createMd('SHA256');
  await md.update({ data: new Uint8Array(Buffer.from(data)) });
  const result = await md.digest();
  return Buffer.from(result.data).toString('hex');
}

/**
 * 验证数据完整性
 */
async verifyDataIntegrity(data: string, expectedHash: string): Promise<boolean> {
  const actualHash = await this.calculateHash(data);
  return actualHash === expectedHash;
}

9.3 定期自动备份

实现自动备份功能:

typescript 复制代码
/**
 * 启用自动备份
 */
async enableAutoBackup(intervalDays: number): Promise<void> {
  const lastBackupTime = await StorageUtil.getNumber('last_backup_time', 0);
  const now = Date.now();
  const dayInMs = 24 * 60 * 60 * 1000;

  if (now - lastBackupTime > intervalDays * dayInMs) {
    await this.exportData();
    await StorageUtil.saveNumber('last_backup_time', now);
  }
}

十、本篇小结

本篇教程实现了完整的数据管理功能,包括:

数据导出 :将应用数据导出为JSON文件

数据导入 :从JSON文件恢复数据

数据统计 :计算和显示存储空间占用

数据清理 :清除缓存和重置数据

备份能力:集成HarmonyOS系统备份

核心技术点

  • HarmonyOS 文件系统操作(fileIo)
  • 文档选择器(DocumentPicker)
  • JSON数据序列化和反序列化
  • 备份扩展能力(BackupExtensionAbility)
  • 数据安全和完整性校验

最佳实践

  • 导出前验证数据完整性
  • 导入前进行格式校验
  • 清除数据前二次确认
  • 定期提醒用户备份
  • 敏感数据加密存储

下一篇预告

第30篇将实现设置与帮助系统,包括应用设置、通知管理、隐私设置、帮助文档和用户反馈等功能。


📖 参考资料

相关推荐
讯方洋哥4 小时前
HarmonyOS App开发——关系型数据库应用App开发
数据库·harmonyos
巴德鸟5 小时前
华为手机鸿蒙4回退到鸿蒙3到鸿蒙2再回退到EMUI11 最后关闭系统更新
华为·智能手机·harmonyos·降级·升级·回退·emui
一起养小猫5 小时前
Flutter for OpenHarmony 实战_魔方应用UI设计与交互优化
flutter·ui·交互·harmonyos
一只大侠的侠5 小时前
Flutter开源鸿蒙跨平台训练营 Day7Flutter+ArkTS双方案实现轮播图+搜索框+导航组件
flutter·开源·harmonyos
那就回到过去5 小时前
VRRP协议
网络·华为·智能路由器·ensp·vrrp协议·网络hcip
相思难忘成疾6 小时前
通向HCIP之路:第四步:边界网关路由协议—BGP(概念、配置、特点、常见问题及其解决方案)
网络·华为·hcip
一只大侠的侠6 小时前
Flutter开源鸿蒙跨平台训练营 Day9分类数据的获取与渲染实现
flutter·开源·harmonyos
一只大侠的侠6 小时前
Flutter开源鸿蒙跨平台训练营 Day 5Flutter开发鸿蒙电商应用
flutter·开源·harmonyos
不爱吃糖的程序媛7 小时前
Capacitor:跨平台Web原生应用开发利器,现已全面适配鸿蒙
前端·华为·harmonyos