鸿蒙应用开发实战【38】--- 数据导出与备份:JSON 序列化
本文是「号码助手全栈开发系列」第 38 篇,持续更新中...
前言
数据是用户最宝贵的资产。号码助手提供了备份导出功能,让用户可以将数据库中的卡号、绑定关系导出为 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 并安全恢复数据。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- openHarmony 跨平台社区:https://openharmonycrossplatform.csdn.net
- HarmonyOS 官方文档:https://developer.huawei.com/consumer/cn/doc/
- HarmonyOS RDB开发指南:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/data-persistence-by-rdb
- HarmonyOS hilog日志:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/hilog