第4篇|时间轨迹的隐私安全与沙盒保存:让数据留得住,也收得稳
2026/6/3 10:35:43 网站建设 项目流程

第4篇|时间轨迹的隐私安全与沙盒保存:让数据留得住,也收得稳

如果前几篇更多是在讲“怎么用”“怎么搭”“怎么更耐看”,那第四篇我想补上一个经常被忽略、但非常关键的问题:数据到底怎么存,才既安全又稳定

时间轨迹这类应用,天然会碰到几类敏感信息:

  • 时间
  • 位置
  • 照片
  • 账号
  • 自定义水印内容
  • 工作记录
  • 历史地址

这些内容里,有些是用户愿意展示出来的,有些只是为了完成记录流程必须临时使用,有些则是明确不应该“随手放出去”的。
所以这一篇不讲“再加一个功能”,而是讲隐私分级、沙盒保存、账号隔离、退出清理这四件事。

先把结论说清楚

我对这类应用的保存原则只有一句话:

能公开的,放在业务数据里;需要长期保留的,放进应用沙盒;临时用的,放进缓存;敏感的,按账号拆开。

如果把所有内容都塞进同一份 JSON,前期看起来省事,后面会很快变成:

  • 账号切换后数据串了
  • 退出登录后历史还在乱跳
  • 图片缓存越来越多
  • 位置历史和个人资料混在一起
  • 用户一旦担心隐私,就不敢继续用

这不是“功能问题”,而是“数据治理问题”。

一张图先看清数据流

拍照 / 导入图片

权限检查

是否允许访问

生成时间轨迹内容

降级提示 / 仅浏览

脱敏处理

写入应用沙盒

按 openId 分目录保存

历史记录 / 预览 / 恢复

缓存目录保存临时文件

使用后清理

这张图表达的不是“流程很多”,而是不同类型的数据,应该走不同的保存路径

数据分层,比“统一保存”更重要

在时间轨迹里,我建议把数据分成四层。

数据类型示例保存方式生命周期
用户偏好自动水印、显示时间、显示地址、主题模式PersistentStorage/AppStorage长期保留
业务记录工作记录、地点历史、模板配置应用沙盒中的账号目录跟随账号
临时文件预览图、导出图、缓存缩略图cacheDir/ 临时目录用完即清
敏感会话登录态、账号标识、当前会话单独会话文件退出后可清理

这里最容易混淆的是两类:

  1. 偏好设置
  2. 账号数据

这两类绝对不能混。

偏好设置是“这个用户喜欢什么样的界面”,可以跟着设备走。
账号数据是“这个账号自己的历史记录”,必须跟着账号走。

为什么要做账号隔离

这一步其实是整个应用里最值得做的设计之一。

如果时间轨迹支持多账号,或者未来准备接云同步,那么数据目录一定不能是一锅端,而应该按openId或者其他稳定账号标识拆开。

一个更合理的沙盒结构可以长这样:

filesDir/ session.json accounts/ openId_001/ userdata.json wm_location_history.json work_records.json templates.json cache/ openId_002/ userdata.json wm_location_history.json work_records.json templates.json cache/ cacheDir/ preview_*.png export_*.jpg

这样做有三个好处:

  • 账号切换时不会互相覆盖
  • 退出登录时可以只清 session,不一定删历史
  • 后续做同步、导出、迁移时边界很清楚

这也是为什么我很喜欢把AccountService单独拎出来。
它不只是“读写账号”,更像是数据边界的守门人

账号数据怎么写,才不会乱

下面这个思路比“所有内容都存一个文件”稳得多。

// 这是示意代码,重点是结构,不是逐行追求 API 细节interfaceUserData{wmLocationHistory:string[];wrRecords:WorkRecord[];templateIds:string[];customAddress:string;}privategetAccountRoot(openId:string):string{return`${this.context.filesDir}/accounts/${openId}`;}privategetUserDataPath(openId:string):string{return`${this.getAccountRoot(openId)}/userdata.json`;}privatesaveUserData(openId:string,data:UserData):void{constroot=this.getAccountRoot(openId);this.ensureDirectory(root);this.writeJson(this.getUserDataPath(openId),data);}privateloadUserData(openId:string):UserData{constpath=this.getUserDataPath(openId);returnthis.readJson<UserData>(path)??{wmLocationHistory:[],wrRecords:[],templateIds:[],customAddress:''};}

这段代码真正想表达的不是文件操作本身,而是这几个设计原则:

  • 每个账号有自己的根目录
  • 数据文件名固定,便于恢复
  • 不同类型的业务数据不要挤在一份超大 JSON 里
  • 缺省值要能自动回退,避免文件损坏后直接崩

沙盒保存,不只是“能写进去”

很多人会把“沙盒保存”理解成一句话:存到应用自己的目录里就行
但真正做的时候,还应该再往前走一步:

1. 公开数据和敏感数据分开

比如:

  • 模板列表可以保存
  • 水印开关可以保存
  • 工作记录可以保存
  • 地址历史可以保存
  • 会话 token 不应该和记录混在一起

2. 临时文件和长期文件分开

导出的图片、缩略图、分享前的预览文件,应该优先放缓存目录。
一旦用户完成保存、分享或退出,就可以清掉。

3. 退出登录时要做“边界动作”

退出登录不是简单把页面跳回去,而是要明确区分:

  • 释放当前会话
  • 切换当前账号标识
  • 是否清理当前账号的临时数据
  • 是否保留历史记录供下次恢复

这件事如果不做,用户会感觉“我明明退出了,怎么内容还跟着我跑”。

权限策略也属于隐私设计

时间轨迹会碰到的权限,通常不止一个:

  • 相机权限
  • 相册权限
  • 位置权限
  • 存储相关权限

但这里最重要的不是“申请了多少”,而是什么时候申请

我更建议的方式是:

  1. 进入拍照页,再申请相机权限
  2. 进入时间地点页,再申请位置权限
  3. 进入导入页,再申请相册权限
  4. 先给用户解释用途,再弹系统授权

这样做的好处很直接:

  • 用户知道为什么要授权
  • 拒绝后不会把整个应用拖死
  • 业务页面可以优雅降级

比如:

if(!awaitthis.permissionUtil.ensure(['ohos.permission.CAMERA'])){this.toast('未授权相机权限,仍可继续浏览模板与记录');return;}

真正好的隐私体验不是“强行拿权限”,而是用户知道自己为什么授权,也知道拒绝后会发生什么

私密信息最好只在本地完成闭环

如果一个功能完全可以在本地完成,就不要早早把数据往外送。
时间轨迹里的很多内容其实都适合本地闭环:

  • 自动水印生成
  • 地址历史整理
  • 记录归档
  • 模板切换
  • 账号本地恢复

这样做的价值不只是性能好,还包括:

  • 断网也能用
  • 敏感数据不必经过额外链路
  • 用户更容易信任这个产品

如果未来真的要做云同步,我建议也先做两层分级:

  1. 先同步非敏感配置
  2. 再同步可脱敏的业务记录

不要一上来就把所有东西都推上云。

退出、切换、恢复,三件事要分开

这三个动作看起来很像,但本质上不同:

  • 退出:结束当前会话
  • 切换:从账号 A 进入账号 B
  • 恢复:重新把历史内容载回当前账号

这也是为什么要把session.jsonuserdata.json分开。

privateclearSession():void{this.removeFile(`${this.context.filesDir}/session.json`);AppStorage.setOrCreate('currentOpenId','');}privateswitchAccount(openId:string):void{AppStorage.setOrCreate('currentOpenId',openId);constdata=this.loadUserData(openId);this.restoreRecords(data);}

这样一来,退出时只处理“会话”,恢复时只处理“内容”。
边界清楚了,体验就稳了。

日志也要脱敏

隐私文章里经常被忽略的一层,是日志本身。真正上线时,下面这些字段都不建议原样打印:

  • openId、session token、账号手机号
  • 头像本地路径、导出文件路径、缓存目录
  • 位置历史、工作记录明细、图片原始路径
  • 任何能直接关联到用户身份的调试参数

更稳妥的做法是:

  • 只保留必要的状态名
  • 账号 ID 做掩码展示
  • 路径只打印目录级别,不打印完整文件名
  • 线上错误上报前先做脱敏处理
privatemaskId(id:string):string{if(!id)return'';if(id.length<=6)return'***';returnid.slice(0,2)+'***'+id.slice(-2);}logger.info('save user data for '+this.maskId(openId));
  • 这一篇最想留下的结论

时间轨迹做到第四篇,其实已经能看出一个很明确的产品脉络:

  • 第一篇讲的是入口和闭环
  • 第二篇讲的是架构和技术栈
  • 第三篇讲的是视觉和交互体验
  • 第四篇讲的是隐私、安全和保存策略

前面三篇让它“能用、好用、耐看”,这一篇让它“可信、可控、可恢复”。

我觉得一个产品真正成熟,不是把按钮做多,而是把数据边界想清楚。
该长期保存的有去处,该临时保存的会自动清理,该敏感的能隔离,该恢复的能恢复。

这就是时间轨迹这类应用最值得被认真对待的地方。

参考

  • HarmonyOS app data persistence
  • HarmonyOS HUKS encryption/decryption
  • entry/src/main/ets/utils/AccountService.ets
  • entry/src/main/ets/entryability/EntryAbility.ets
  • entry/src/main/ets/pages/LoginPage.ets
  • entry/src/main/ets/pages/ProfilePage.ets

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询