鸿蒙应用开发实战【38】— 数据导出与备份:JSON 序列化

鸿蒙应用开发实战【38】--- 数据导出与备份:JSON 序列化

本文是「号码助手全栈开发系列」第 38 篇,持续更新中...

开源社区:https://openharmonycrossplatform.csdn.net


前言

数据是用户最宝贵的资产。号码助手提供了备份导出功能,让用户可以将数据库中的卡号、绑定关系导出为 JSON 文件,保存到本地或云盘。当换机或重装时,可以通过导入备份文件一键恢复。本篇将实现数据导出与备份的完整链路。

本篇涵盖:数据导出流程图、BackupData 备份数据模型设计、三张表的 JSON 序列化、使用 @ohos.file.fs 写入文件、文件保存到沙盒与分享导出、增量备份策略、备份文件格式版本管理。


一、备份数据模型

1.1 BackupData 结构

typescript 复制代码
export interface BackupData {
  // 备份元信息
  version: number;          // 备份文件格式版本
  app: string;              // 应用标识
  exported_at: string;      // 导出时间

  // 核心数据
  cards: CardEntity[];
  app_bindings: AppBindingEntity[];
  sms_candidates: SmsCandidateEntity[];

  // 可选的统计摘要
  summary?: {
    totalCards: number;
    totalBindings: number;
    totalCandidates: number;
  };
}

1.2 版本管理

typescript 复制代码
export const BACKUP_VERSION = 1;

未来数据库结构变更时,备份格式版本号递增,导入时可以按版本做兼容处理。


二、导出流程

2.1 BackupService

typescript 复制代码
import { relationalStore } from '@kit.ArkData';
import { fileIo } from '@kit.CoreFileKit';
import { CardDao } from '../dao/CardDao';
import { AppBindingDao } from '../dao/AppBindingDao';
import { SmsCandidateDao } from '../dao/SmsCandidateDao';

export class BackupService {
  private cardDao: CardDao;
  private bindingDao: AppBindingDao;
  private candidateDao: SmsCandidateDao;

  constructor(store: relationalStore.RdbStore) {
    this.cardDao = new CardDao(store);
    this.bindingDao = new AppBindingDao(store);
    this.candidateDao = new SmsCandidateDao(store);
  }

  /**
   * 导出全量数据
   */
  async exportAll(): Promise<BackupData> {
    // 并行查询所有数据
    const [cards, app_bindings, sms_candidates] = await Promise.all([
      this.cardDao.listAll(),
      this.bindingDao.listAll(),
      this.candidateDao.listAll(),
    ]);

    const backup: BackupData = {
      version: BACKUP_VERSION,
      app: 'haomazhushou',
      exported_at: new Date().toISOString(),
      cards,
      app_bindings,
      sms_candidates,
      summary: {
        totalCards: cards.length,
        totalBindings: app_bindings.length,
        totalCandidates: sms_candidates.length,
      },
    };

    return backup;
  }
}

2.2 序列化为 JSON 字符串

typescript 复制代码
function backupToJson(backup: BackupData): string {
  return JSON.stringify(backup, null, 2);
}

使用 JSON.stringify 的第三个参数 2 格式化输出,让导出的 JSON 文件可读:

json 复制代码
{
  "version": 1,
  "app": "haomazhushou",
  "exported_at": "2026-07-15T10:30:00.000Z",
  "cards": [
    {
      "id": 1,
      "label": "主卡",
      "phone": "13800138000",
      "operator": "中国移动",
      "color": "#4F7CFF",
      "sort_order": 0,
      "created_at": "2026-07-15T10:00:00",
      "updated_at": "2026-07-15T10:00:00"
    }
  ],
  "app_bindings": [],
  "sms_candidates": []
}

三、写入文件

3.1 保存到应用沙盒

typescript 复制代码
import { fileIo } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';

/**
 * 将备份 JSON 写入沙盒文件
 */
async function saveBackupToFile(
  context: common.UIAbilityContext,
  backupData: BackupData
): Promise<string> {
  const json = backupToJson(backupData);

  // 生成文件名:haomazhushou_backup_20260715_103000.json
  const timestamp = new Date()
    .toISOString()
    .replace(/[:.]/g, '')
    .replace('T', '_')
    .slice(0, 15);
  const fileName = `haomazhushou_backup_${timestamp}.json`;

  // 获取沙盒缓存目录
  const cacheDir = context.cacheDir;
  const filePath = `${cacheDir}/${fileName}`;

  // 写入文件
  const file = fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);
  fileIo.writeSync(file.fd, json);
  fileIo.closeSync(file);

  console.info(`Backup saved: ${filePath} (${json.length} bytes)`);
  return filePath;
}

3.2 分享导出文件

使用系统分享能力让用户将备份文件保存到云盘或发送给他人:

typescript 复制代码
import { common, UIAbilityContext } from '@kit.AbilityKit';
import { wantConstant } from '@kit.AbilityKit';

/**
 * 分享备份文件
 */
async function shareBackupFile(context: UIAbilityContext, filePath: string): Promise<void> {
  const uri = fileIo.uri.getUriFromPath(filePath);

  try {
    const want: Want = {
      action: wantConstant.Action.SEND_DATA,
      type: 'application/json',
      uri: [uri],
      parameters: {
        'ability.params.stream': [uri],
      },
    };

    await context.startAbility(want);
    console.info('Share intent launched');
  } catch (error) {
    console.error('Failed to share backup', error);
    throw error;
  }
}

四、完整导出流程

typescript 复制代码
async function performExport(context: UIAbilityContext, store: relationalStore.RdbStore): Promise<void> {
  try {
    // 1. 查询数据
    const service = new BackupService(store);
    const backupData = await service.exportAll();

    // 2. 写入文件
    const filePath = await saveBackupToFile(context, backupData);

    // 3. 提示成功
    AlertDialog.show({
      title: '备份成功',
      message: `已导出 ${backupData.summary?.totalCards} 张卡、${backupData.summary?.totalBindings} 条绑定`,
      primaryButton: {
        value: '分享文件',
        action: () => shareBackupFile(context, filePath),
      },
      secondaryButton: {
        value: '完成',
      },
    });
  } catch (error) {
    console.error('Export failed', error);
    AlertDialog.show({ title: '备份失败', message: error.message });
  }
}

五、增量备份策略

全量备份每次导出所有数据,数据量大时耗时增加。可以考虑增量备份:

typescript 复制代码
/**
 * 增量备份:只导出指定日期之后修改的记录
 */
async exportIncremental(since: Date): Promise<BackupData> {
  const sinceStr = since.toISOString();

  // 只查询 updated_at > since 的记录
  // 需要在 DAO 中新增查询方法
  const cards = await this.cardDao.listUpdatedSince(sinceStr);
  const bindings = await this.bindingDao.listUpdatedSince(sinceStr);
  const candidates = await this.candidateDao.listCreatedSince(sinceStr);

  return {
    version: BACKUP_VERSION,
    app: 'haomazhushou',
    exported_at: new Date().toISOString(),
    cards,
    app_bindings: bindings,
    sms_candidates: candidates,
  };
}

DAO 中的增量查询方法:

typescript 复制代码
// CardDao
async listUpdatedSince(since: string): Promise<CardEntity[]> {
  let predicates = new relationalStore.RdbPredicates('card');
  predicates.greaterThan('updated_at', since);
  return await this.queryList(predicates);
}

六、备份文件验证

导出后需要对备份文件做完整性验证:

typescript 复制代码
function validateBackup(data: unknown): data is BackupData {
  if (!data || typeof data !== 'object') return false;

  const backup = data as Record<string, unknown>;

  // 必填字段检查
  if (typeof backup.version !== 'number') return false;
  if (typeof backup.app !== 'string') return false;
  if (typeof backup.exported_at !== 'string') return false;

  // 数组字段检查
  if (!Array.isArray(backup.cards)) return false;
  if (!Array.isArray(backup.app_bindings)) return false;
  if (!Array.isArray(backup.sms_candidates)) return false;

  return true;
}

七、文件管理

7.1 列出所有备份文件

typescript 复制代码
async function listBackupFiles(context: UIAbilityContext): Promise<string[]> {
  const cacheDir = context.cacheDir;
  const files = fileIo.listFileSync(cacheDir);

  return files
    .filter(name => name.startsWith('haomazhushou_backup_') && name.endsWith('.json'))
    .map(name => `${cacheDir}/${name}`);
}

7.2 删除旧备份

typescript 复制代码
async function cleanOldBackups(context: UIAbilityContext, keepCount: number = 5): Promise<void> {
  const files = await listBackupFiles(context);

  if (files.length <= keepCount) return;

  // 按文件名排序(时间戳按字典序排列)
  files.sort();

  // 删除最旧的
  const toDelete = files.slice(0, files.length - keepCount);
  for (const filePath of toDelete) {
    try {
      fileIo.unlinkSync(filePath);
    } catch (error) {
      console.warn(`Failed to delete old backup: ${filePath}`, error);
    }
  }
}

小结

本篇完成了数据导出与备份的完整实现:

模块 关键点
BackupData 模型 version + app + 三张表数据 + summary
JSON 序列化 JSON.stringify 带格式化输出
文件写入 fileIo.openSync + writeSync
文件分享 startAbility SEND_DATA
增量备份 listUpdatedSince 按时间过滤
备份验证 类型守卫 + 字段完整性检查
文件管理 列出/清理旧备份文件

7.1 备份策略对比

策略 数据量 耗时 存储空间 适用场景
全量备份 全部数据 较长 较大 首次备份/恢复
增量备份 变更数据 日常定期备份

7.2 fileIo 核心方法

方法 功能 参数
openSync(path, mode) 打开/创建文件 文件路径、打开模式
writeSync(fd, data) 写入数据 文件描述符、字符串/ArrayBuffer
readSync(fd, buffer) 读取数据 文件描述符、缓冲区
closeSync(file) 关闭文件 文件描述符
unlinkSync(path) 删除文件 文件路径
statSync(path) 获取文件信息 文件路径
listFileSync(path) 列出目录文件 目录路径

下一篇将实现「数据导入与恢复批量插入策略」,读取备份 JSON 并安全恢复数据。


如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

相关推荐
条tiao条1 小时前
MVVM架构与ArkUI状态管理
华为·架构·harmonyos·鸿蒙·mvvm
程序员黑豆3 小时前
鸿蒙应用开发:一次开发多端适配与自适应布局实战
前端·harmonyos
三翼鸟数字化技术团队4 小时前
GN (generate ninja) 学习手册
harmonyos
程序员黑豆5 小时前
鸿蒙应用开发:网络请求三种方式详解(http / rcp / axios)
前端·harmonyos
小雨青年5 小时前
【HarmonyOS 7 沉浸光感深度实战】 02 全局开关、MaterialState 与最小 Demo
华为·harmonyos
lilian2336 小时前
Harmony os 技术实战|拼豆制图06:收藏 ID、生成记录与重启恢复怎么不打架
android·java·数据库·harmonyos
程序员黑豆7 小时前
鸿蒙应用开发之生命周期方法完全指南
前端·harmonyos
萌新源9 小时前
电赛C题:我用星闪+UWB做了一个数字钥匙门锁系统,开源了
c语言·华为·开源
HMS Core9 小时前
借助AR Engine人脸识别与跟踪能力,直播不露脸也生动
ar·harmonyos