Electron 鸿蒙 PC 上 persist: session 存不住 cookie——排查到底发现路径被偷换成了 tmpfs Electron 鸿蒙 PC 上 persist: session 存不住 cookie——排查到底发现路径被偷换成了 tmpfs上周四晚上快十点的时候用户群里炸了——七八个人同时说鸿蒙 PC 版每次打开都要重新登录。我看了一眼日志登录接口返回 200token 也拿到了cookie 也写进去了但重启 App 就是读不回来。当时脑子里第一反应是 cookie 的过期时间设错了。我把 Max-Age 从默认的 session 级别改成 30 天发了个热更新心想稳了。结果用户反馈还是一样。这就有点不对劲了。我做的 App 叫雷达鸭鸿蒙版在华为应用市场能搜到桌面端我是用 Electron 套的。为了支持多账号切换我用了session.fromPartition(persist:account_ accountId)给每个账号建一个独立的 session登录态互不干扰。这个方案在 Windows 上跑了三个月一次问题没出过。所以我一开始根本没往 session 本身的方向想。第一轮cookie 到底写进去了没我在主进程里加了一段日志确认 cookie 在当前生命周期里是否正常工作// main.ts —— 记录 cookie 写入与回读状态const{session}require(electron);constsessession.fromPartition(persist:account_1);ses.cookies.set({url:https://api.example.com,name:auth_token,value:eyJhbGciOi...,expirationDate:Date.now()/100030*24*3600,}).then((){console.log([Cookie] set success);}).catch((err:Error){console.error([Cookie] set failed:,err.message);});// 立刻回读验证ses.cookies.get({url:https://api.example.com}).then((cookies){console.log([Cookie] get after set:,cookies.length,cookies.map((c:any)c.name));});日志输出很漂亮[Cookie] set success紧跟着[Cookie] get after set: 1 [auth_token]。说明 cookie 在当前进程周期里写和读都没问题。但用户退出重进get就返回空数组了。cookie 没有落盘。第二轮怀疑 flush 时机Electron 的 cookie 写入不是实时的Chromium 内核有个异步 flush 机制。我想会不会是鸿蒙 PC 上的 flush 间隔特别长用户在 cookie 刷到磁盘之前就把 App 关掉了我在退出前手动调了 flush甚至等了 2 秒再退出// main.ts —— 退出前强制刷盘const{app,session}require(electron);app.on(before-quit,async(){constsessession.fromPartition(persist:account_1);try{awaitses.cookies.flushStore();console.log([Cookie] flushStore completed);}catch(err:any){console.error([Cookie] flushStore failed:,err.message);}});日志显示 flush 确实执行了。我还特意在 flush 后setTimeout等了 2 秒才允许进程退出。依然不行。说真的这时候我已经有点暴躁了。同一个代码Windows 上好好的鸿蒙上就是不行。我开始怀疑persist:这个前缀在鸿蒙上根本没生效——session 会不会被当成了内存级临时 session 处理第三轮直接去磁盘上看不猜了直接扫文件系统。我把app.getPath(userData)下面的目录结构全打印出来// main.ts —— 扫描 session 存储目录const{app}require(electron);constfsrequire(fs);constpathrequire(path);constuserDataapp.getPath(userData);console.log([Path] userData:,userData);constpartitionPathpath.join(userData,Partitions,persist:account_1);console.log([Path] expected partition:,partitionPath);if(fs.existsSync(partitionPath)){constfilesfs.readdirSync(partitionPath);console.log([Path] files in partition:,files);}else{console.log([Path] partition directory DOES NOT EXIST);// 再往上看看 Partitions 目录本身constpartitionsDirpath.join(userData,Partitions);if(fs.existsSync(partitionsDir)){console.log([Path] Partitions dir contents:,fs.readdirSync(partitionsDir));}else{console.log([Path] Partitions dir also missing);}}Windows 上这个路径下面能看到Cookies、Cookies-journal、Local Storage等一堆文件。但在鸿蒙 PC 上Partitions/persist:account_1/目录根本就不存在。更关键的是日志里的userData路径/run/user/1000/app_com.radarduck.hm/看到/run/user/这个前缀我愣了好几秒。如果你对 Linux 文件系统有点概念应该知道/run/是一个 tmpfs——基于内存的临时文件系统系统重启就清空。但就算知道这一点当时的困惑也没减少为什么 Electron 的userData会指向一个 tmpfs这是谁决定的第四轮路径重定向的真相我在鸿蒙开发者文档和 electron-mus 的 GitHub issue 里翻了一圈拼出了大致的原因。HarmonyOS 为了实现应用沙箱隔离对每个 HAP 应用的文件访问做了严格限制。Electron 在鸿蒙上运行时底层用的是 electron-mus——一个把 Chromium 适配到鸿蒙 Mus 窗口系统的 Fork。这个 Fork 在初始化app.getPath(userData)时没有像标准 Electron 那样指向应用专属的持久化存储目录而是直接用了 XDG 运行时目录也就是/run/user/1000/。而session.fromPartition(persist:xxx)的工作方式是根据userData路径来拼接 partition 的存储位置——在userData/Partitions/persist:xxx/下面建目录。现在userData指向了 tmpfspartition 自然也跟着进了 tmpfs。重启之后 tmpfs 清空cookie 荡然无存。但有个奇怪的地方session.defaultSession的 cookie 为什么是好的我回头检查了一下发现默认 session 的存储路径不走Partitions/子目录而是直接在userData根下写Cookies文件。它确实也写到了 tmpfs 里——但 electron-mus 在 Chromium 初始化时用--user-data-dir参数单独把默认 profile 的存储路径改写到了持久化目录大概是/data/storage/el2/base/haps/entry/files/这一带。换句话说只有自定义 partition 的路径没被改写。这在标准 Electron 里从来不是问题因为userData本身就指向持久化目录。但在 electron-mus 上defaultSession和自定义 session 走了两套路径解析逻辑前者有特殊照顾后者被晾在了 tmpfs 里。我在 electron-mus 的源码里确认了这一点——ChromiumSandbox::OverrideUserDataDir只处理了默认 profile 的路径Partitions/子目录完全没管。这设计坦白讲像是一个先把默认的修了自定义的回头再说的半成品状态。倒不一定是开发者故意的更像是在适配 HarmonyOS 沙箱时默认 session 是 P0 优先级先修了自定义 partition 的路径映射被排到了后面。修复绕过 persist 前缀找到了根因修复反而直接了。不依赖persist:的自动路径解析自己拼绝对路径// main.ts —— 用绝对路径创建持久化 sessionconst{app,session}require(electron);constfsrequire(fs);constpathrequire(path);/** * 获取鸿蒙应用的真实持久化存储根路径。 * 不能依赖 app.getPath(userData)因为 electron-mus 可能指向 tmpfs。 */functiongetPersistentSessionRoot():string{// HarmonyOS 应用的持久化文件路径规范constdataDir/data/storage/el2/base/haps/entry/files;if(fs.existsSync(dataDir)){returnpath.join(dataDir,electron_sessions);}// 兜底如果上面的路径也不存在比如开发阶段用模拟器// 退回到 userData至少让问题在测试阶段暴露出来returnpath.join(app.getPath(userData),electron_sessions);}/** * 为指定账号创建独立的持久化 session。 * 用 fromPath 替代 fromPartition绕过 persist: 前缀的路径解析。 */functioncreateAccountSession(accountId:string):Electron.Session{constrootgetPersistentSessionRoot();constsessionPathpath.join(root,account_${accountId});if(!fs.existsSync(sessionPath)){fs.mkdirSync(sessionPath,{recursive:true});console.log([Session] created dir:,sessionPath);}// fromPath 接受绝对路径不会经过 persist: 前缀的路径映射逻辑constsessession.fromPath(sessionPath);console.log([Session] created for account:,accountId,at:,sessionPath);returnses;}// 用法示例constaliceSessioncreateAccountSession(user_alice);constbobSessioncreateAccountSession(user_bob);session.fromPath()接受一个绝对路径完全绕过了persist:前缀的路径解析逻辑。你只要保证这个路径落在持久化存储上就行。我一开始不太放心专门在鸿蒙真机上反复开关 App 二十几次——每次退出前写 cookie重启后读回来比对——确认完全没丢才合了代码。如果你也打算在鸿蒙上这么干最好在你的测试机上也跑几轮重启验证别像我一样等用户群炸了才知道有问题。这个问题的本质是 electron-mus 对app.getPath(userData)的路径映射做得不够彻底——它只处理了默认 profile 的重定向漏掉了自定义 partition。严格来说不算架构缺陷就是个漏网的细节。等 electron-mus 后续版本应该会修。但真正耗时的是排查过程cookie 丢了你第一反应肯定是检查 cookie 本身——过期时间、域名、SameSite 策略——而不是去查操作系统底层的文件路径映射。我绕了一大圈中间甚至一度想把整个多账号方案推倒改成手动把 token 存在 IndexedDB 里自己管理读写。说真的IndexedDB 那条路我也不是完全没试——写了个 POC在渲染进程里用electron-store存 token主进程每次切账号手动清缓存。代码量多了一倍不提token 自动刷新机制要跟 cookie 的自动携带逻辑完全重写光是 edge case 就多出来七八个。在准备开搞重写之前我决定再翻一遍 electron-mus 的 GitHub issues结果就在第 3 页看到了那条关于路径映射的讨论——十几条评论开发者也承认persist:session 的持久化还没适配好。行吧。如果你也在做鸿蒙 PC 的 Electron 应用用了session.fromPartition(persist:xxx)建议上线前拿真机跑一遍重启后的 cookie 持久化。不然等你发现的时候大概率跟我当时一样——大晚上对着日志和/run/user/1000/路径发呆。老三10 年软件开发经验软件设计师 人工智能应用工程师。专注鸿蒙应用开发ArkTS 北向和 Web 前端同时折腾 AI 自动化方向。不定期在 CSDN 分享鸿蒙和 AI 方面的技术文章。本文遵循 MIT 协议转载请注明出处。