开源鸿蒙 Flutter for OpenHarmony:离线笔记收官(全量备份导出/导入)
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
离线笔记做到 Day9,单条笔记已经能导出/导入(二维码+复制文本)。
但到了真正“换机/重装/迁移”的场景,大家更需要的是:全量备份。
Day10 收官把这件事补齐:
一键把当前数据库里所有笔记导出成一段 JSON 文本(复制到剪贴板),另一台设备/重装后直接粘贴导入恢复。
这篇的重点依然是“怎么把三方库用到位”,但全量备份本身不需要新增插件:
- 数据来自
sqflite - 私密笔记的密文字段照样备份(不会导出明文)
- 复制/粘贴用 Flutter 自带剪贴板即可
1. 全量备份为什么要单独做(单条导出不够用)
单条导出的缺点很明显:
- 50 条笔记要导出 50 次
- 迁移成本高,容易漏
全量备份的目标只有一个:
一次复制,一次导入,全部恢复。
2. 备份格式(v=1):包含 salt + notes 数组
为了兼容 Day4 的私密笔记,我们把salt_b64一起带上。
全量备份结构如下:
{"v":1,"salt_b64":"xxxx","notes":[{"title":"xxx","content":"xxx","pinned":0,"is_private":0,"content_cipher":null,"content_nonce":null,"content_mac":null,"created_at":0,"updated_at":0}]}关键点:
- 普通笔记:
content有明文 - 私密笔记:
content固定为空,密文在content_cipher/nonce/mac
所以全量导出不会“把私密内容明文泄露”。
3. DAO 增加 listAllNotes:一次性取出所有未删除笔记
📌 文件:lib/features/note/data/note_dao.dart
Future<List<Note>>listAllNotes()async{finaldb=await_db.database;finalrows=awaitdb.query('notes',where:'is_deleted = ?',whereArgs:const[0],orderBy:'pinned DESC, updated_at DESC',);returnrows.map(_fromRow).toList(growable:false);}这里不设 limit:因为我们就是为了“全量”。
4. Repository 实现全量导出/导入
📌 文件:lib/features/note/data/note_repository.dart
4.1 导出:exportAllNotesAsJson()
Future<String>exportAllNotesAsJson()async{constsaltKey='private_pin_salt_b64';finalsaltB64=await_dao.appDb.getKv(saltKey);finalall=await_dao.listAllNotes();finalpayload=<String,Object?>{'v':1,'salt_b64':saltB64,'notes':all.map((note)=><String,Object?>{'title':note.title,'content':note.content,'pinned':note.pinned?1:0,'is_private':note.isPrivate?1:0,'content_cipher':note.contentCipher,'content_nonce':note.contentNonce,'content_mac':note.contentMac,'created_at':note.createdAt.millisecondsSinceEpoch,'updated_at':note.updatedAt.millisecondsSinceEpoch,}).toList(growable:false),};returnjsonEncode(payload);}4.2 导入:importAllNotesFromJson(text)
导入逻辑也很直接:
- 解析 JSON,校验
v - 如果带了
salt_b64,先写回app_kv - 循环
notes,逐条 insert
Future<int>importAllNotesFromJson(Stringtext)async{constsaltKey='private_pin_salt_b64';finalobj=jsonDecode(text);if(objis!Map)throwStateError('Invalid json');finalv=obj['v'];if(v!=1)throwStateError('Unsupported version');finalsaltB64=obj['salt_b64'];if(saltB64isString&&saltB64.isNotEmpty){await_dao.appDb.setKv(saltKey,saltB64);}finallist=obj['notes'];if(listis!List)throwStateError('Missing notes');varcount=0;for(finaliteminlist){if(itemis!Map)continue;...await_dao.insert(note);count++;}returncount;}5. 新增一个“数据工具”页面:导出复制 + 粘贴导入
📌 文件:lib/features/debug/ui/data_tools_page.dart
5.1 一键导出:复制到剪贴板
finaltext=awaitwidget.repo.exportAllNotesAsJson();awaitClipboard.setData(ClipboardData(text:text));awaitshowToast('已复制全量备份');5.2 粘贴导入:导入成功提示“导入了多少条”
finaln=awaitwidget.repo.importAllNotesFromJson(text);awaitshowToast('已导入$n条');Navigator.pop(context,true);6. 入口:列表页右上角加一个“数据工具”按钮
📌 文件:lib/features/note/ui/notes_list_page.dart
IconButton(onPressed:()async{finalchanged=awaitNavigator.of(context).push<bool>(MaterialPageRoute(builder:(_)=>DataToolsPage(repo:_repo),),);if(changed==true&&mounted){await_controller.load();}},icon:constIcon(Icons.settings),tooltip:'数据工具',),导入成功后返回列表自动刷新。
📷
7. 自测清单(Day10)
- 创建几条普通笔记 + 私密笔记 + 置顶笔记
- 打开“数据工具” → 点导出 → 粘贴到备忘录确认是一段 JSON
- 清空应用数据/重装(或换设备)后 → 打开“数据工具” → 粘贴 JSON → 导入
- 返回列表:
- 笔记数量一致
- 置顶排序仍然有效
- 私密笔记仍然需要 PIN 才能看到正文(导入不会变明文)