在鸿蒙应用开发中,数据库的数据备份与导出是一个常见需求。本文将介绍如何使用ArkTS实现关系型数据库的备份、恢复及沙箱文件导出功能,解决数据库中临时文件(WAL)导致的数据导出不完整问题。

一、数据库在沙箱中的位置

鸿蒙应用的数据存储在应用沙箱中,沙箱路径结构如下:

/data/app/el2/100/base/com.example.interviewbaolongzhanshi/haps/entry/cache/图片名称

目录层级 路径结构 用途
应用沙箱根目录 /data/storage/el[?] 应用隔离存储空间
数据库目录 /database/ 数据库相关文件
具体数据库文件 /entry/rdb/Mydata.db 主数据库文件
临时文件 /entry/rdb/Mydata.db-wal WAL(预写式日志)文件

​上面注意哦:​​ el2是设备类型标识符(手机为e),应使用动态获取方式:

import app from '@ohos.app';
const context = app.getContext();
const dbDir = `${context.filesDir}/database/entry/rdb/`;

二、数据库备份的必要性

直接导出数据库文件可能面临两个问题:

  1. WAL文件未合并导致数据不完整
  2. 数据库连接未关闭可能导致导出失败

ArkTS提供了备份恢复API,可以解决这些问题:

class DatabaseService {
  private database: relationalStore.RdbStore;
  
  constructor() {
    // 初始化数据库连接
    const config = {name: 'Mydata.db'};
    relationalStore.getRdbStore(context, config, (err, store) => {
      this.database = store;
    });
  }
  
  // 数据库备份
  async backup(targetName: string) {
    this.database.close(); // 关闭连接确保WAL文件合并
    await this.database.backup(targetName);
    this.database.open(); // 恢复连接
    console.log('备份完成:', targetName);
  }
  
  // 数据库恢复
  async restore(sourceName: string) {
    this.database.close(); // 关闭连接
    await this.database.restore(sourceName);
    this.database.open(); // 重新打开
    console.log('恢复完成:', sourceName);
  }
}

三、完整导出实现方案

1. 权限申请

在导出前需要申请存储权限,在module.json5中添加:

"requestPermissions": [
  {
    "name": "ohos.permission.WRITE_USER_STORAGE"
  }
]

运行时权限检查:

import abilityAccessCtrl from '@ohos.abilityAccessCtrl';

async requestStoragePermission() {
  const atManager = abilityAccessCtrl.createAtManager();
  try {
    const { authResults } = await atManager.requestPermissionsFromUser(
      context, 
      ['ohos.permission.WRITE_USER_STORAGE']
    );
    
    if (authResults[0] === 0) {
      console.log('存储权限已授予');
    } else {
      throw new Error('存储权限被拒绝');
    }
  } catch (err) {
    console.error('权限申请失败:', err);
  }
}

2. 数据库备份与导出

import picker from '@ohos.file.picker';
import fs from '@ohos.file.fs';
import { DatabaseService } from './DatabaseService'; // 数据库服务类

class DatabaseExporter {
  private dbService = new DatabaseService();
  
  // 导出数据库到用户目录
  async exportDatabase() {
    try {
      // 申请权限
      await this.requestStoragePermission();
      
      // 生成备份文件名
      const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
      const backupName = `Backup_${timestamp}.db`;
      
      // 执行备份
      await this.dbService.backup(backupName);
      
      // 获取备份文件路径
      const backupPath = `${context.filesDir}/database/entry/rdb/${backupName}`;
      
      // 使用文件选择器保存
      const picker = new picker.DocumentViewPicker();
      const saveOptions = {
        newFileNames: [backupName],
        types: ['*/*'] // 所有文件类型
      };
      
      const saveResult = await picker.saveAs(saveOptions);
      const targetUri = saveResult[0];
      
      // 执行文件拷贝
      const targetFd = fs.openSync(targetUri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
      await fs.copyFile(backupPath, targetFd.fd);
      fs.closeSync(targetFd);
      
      // 删除临时备份文件
      fs.unlink(backupPath);
      
      console.log('数据库导出成功');
      prompt.showToast({ message: '数据库导出成功' });
    } catch (err) {
      console.error('导出失败:', err);
      prompt.showToast({ message: `导出失败: ${err.message}` });
    }
  }
}

3. 导入数据库文件

async importDatabase() {
  try {
    await this.requestStoragePermission();
    
    // 选择文件
    const picker = new picker.DocumentViewPicker();
    const selectOptions = {
      types: ['*.db'], // 只显示.db文件
      multiple: false
    };
    
    const selectResult = await picker.select(selectOptions);
    const sourceUri = selectResult[0];
    
    // 验证文件有效性
    const fileInfo = fs.statSync(sourceUri);
    if (fileInfo.size < 1024) {
      throw new Error('无效的数据库文件');
    }
    
    // 目标文件路径
    const targetName = 'Restore.db';
    const targetPath = `${context.filesDir}/database/entry/rdb/${targetName}`;
    
    // 复制文件
    const sourceFd = fs.openSync(sourceUri, fs.OpenMode.READ_ONLY);
    await fs.copyFile(sourceFd.fd, targetPath);
    fs.closeSync(sourceFd);
    
    // 恢复数据库
    await this.dbService.restore(targetName);
    
    // 删除临时文件
    fs.unlink(targetPath);
    
    console.log('数据库导入成功');
    prompt.showToast({ message: '数据库导入成功' });
  } catch (err) {
    console.error('导入失败:', err);
    prompt.showToast({ message: `导入失败: ${err.message}` });
  }
}

四、关键注意事项

  1. ​路径兼容性​

    • 不同设备沙箱路径可能不同,使用context.filesDir代替硬编码路径
    • 文件操作前检查路径有效性:fs.access(path).
    • 备份完成后删除临时文件

 以上...后面根据个人需求优化用户/个人体验

创作来自 -- 宇智波斑-白面具

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐