持久化问题排查方法论

06/232 浏览综合
从"数据保存不了"到最终解决,经历了7次错误尝试。复盘形成通用排查流程。
问题现象 玩家造了一个兵 → 刷新页面 → 兵没了 排查过程(按时间线) 轮次 尝试的方案 结果 失败原因 1 File("save.json", FILE_WRITE) 刷新后丢失 WASM 平台文件系统是内存的,刷新即清空 2 File:new(path, mode) 构建失败 LSP 报类型错误,File:new 不是正确调用方式 3 clientCloud:Set(key, value) clientCloud 是 nil 多人模式下没有 clientCloud,只有 UserDataProxy 4 UserDataProxy.Set(key, json) 保存成功(ACK=true) — 5 UserDataProxy.GetAsync(key) 立即读取 返回空 时序问题:数据还没从服务器回传 6 加 OnReady 回调等就绪后再读 仍然返回空 服务端推送数据时不包含这个 key 7 在 USER_DATA_KEYS 白名单加入新 key 最终解决 — 方法论:四层排查模型 第1层:存储介质是否正确? ↓ 第2层:写入是否成功? ↓ 第3层:读取时序是否正确? ↓ 第4层:数据通路是否完整? 第1层:存储介质 问题:你用的存储方式,在目标平台上是否真的持久?
平台 File API clientCloud UserDataProxy→serverCloud 本地原生 持久 持久 持久 Web/WASM 刷新丢失 视模式而定 持久 多人模式 刷新丢失 nil 不可用 持久 排查方法:
lua
复制 print("clientCloud =", clientCloud) -- nil? 不可用 print("File write test:", File("test.txt", FILE_WRITE):IsOpen()) -- false? 不可用 print("UserDataProxy ready:", UserDataProxy.IsReady()) -- 唯一可靠方式 结论:先确认你的运行环境,选对存储介质。多人 Web 环境只有 UserDataProxy→serverCloud 可靠。
第2层:写入是否成功 问题:数据确实发出去了吗?服务器确认了吗?
排查方法:
lua
复制 -- 写入时打日志 UserDataProxy.Set("my_key", jsonStr) print("[Save] Sent: " .. #jsonStr .. " bytes")
-- 看控制台是否有: -- [UserDataProxy] Sent USER_DATA_SAVE: reqId=1 entries=1 -- [UserDataProxy] Save ACK: reqId=1 success=true 关键确认:
Sent USER_DATA_SAVE — 客户端发出了请求 Save ACK: success=true — 服务器确认保存成功 如果有 ACK=true 但刷新后读不到,问题在第3层或第4层。
第3层:读取时序 问题:你在什么时机读取?数据到了吗?
时间线: t=0 页面加载 t=0.1 WebSocket 连接建立 t=0.3 服务器推送用户数据 → UserDataProxy 缓存填充 t=0.5 UserDataProxy.IsReady() = true, OnReady 回调触发
如果在 t=0.1 就读取 → dataCache 是空的 必须在 t=0.5 之后读取 排查方法:
lua
复制 -- 错误:立即读取 UserDataProxy.GetAsync("my_key", {...}) -- 可能空
-- 正确:等就绪后读取 if UserDataProxy.IsReady() then doLoad() else UserDataProxy.OnReady(function() doLoad() -- 此时 dataCache 已填充 end) end 确认:日志中是否看到 Data ready! {uid} 出现在你的读取之前?
第4层:数据通路完整性(最隐蔽的坑) 问题:服务端保存了,但推送回来时有没有包含你的key?
完整链路: 客户端 Set("paper_war_bp", data) → 服务端保存 客户端重连 → 服务端 HandleUserDataRequest → serverCloud:BatchGet(uid):Key(k1):Key(k2)... → 只读取白名单里的key! → 推送给客户端
如果 "paper_war_bp" 不在白名单 → 服务端不读它 → 客户端收不到 排查方法:
看刷新后控制台日志: score[inv_holdings] = {...} score[inv_settings] = {...} score[user_password] = 1234 ... Data ready!
问自己:我的key (paper_war_bp) 出现了吗? 如果没出现 → 白名单问题 修复:
lua
复制 -- Network/共享定义.lua Shared.USER_DATA_KEYS = { "inv_meta", "inv_holdings", ..., "paper_war_bp", -- ← 加这一行 } 通用排查流程图 数据刷新后丢了? │ ├─ 用的什么存储? │ ├─ File API → WASM 上不持久,换 UserDataProxy │ ├─ clientCloud → 多人模式下是 nil,换 UserDataProxy │ └─ UserDataProxy → 继续排查 │ ├─ 写入有 ACK 吗? │ ├─ 没有 ACK → UserDataProxy 可能没 ready,加 OnReady │ └─ 有 ACK=true → 继续排查 │ ├─ 读取时机对吗? │ ├─ 在 Data ready 之前读的 → 加 OnReady 等待 │ └─ 在 Data ready 之后读的 → 继续排查 │ └─ 刷新后日志里有你的 key 吗? ├─ 没有 → 白名单问题!加入 USER_DATA_KEYS └─ 有 → 检查 JSON 编解码、key名拼写 核心教训
  1. 永远不要假设"保存成功=能读回来" 保存成功 ≠ 能读回来
保存成功只代表:数据到了服务器硬盘上 能读回来还需要:服务器在重连时把这个key推送给你 2. 多人模式的数据流是"推送制"而非"拉取制" 不是客户端主动 Get → 服务器查库返回 而是:客户端重连 → 服务器按白名单批量推送 → 客户端缓存
客户端的 GetAsync 只是读本地缓存,不会再发网络请求 3. 新功能的数据key,必须同时改两个地方 客户端代码用 UserDataProxy.Set(newKey, data) 服务端白名单 Shared.USER_DATA_KEYS 加入 newKey
少了任何一步都不完整 4. 排查口诀 介质对不对?→ 写到没?→ ACK 收到没?→ Ready 了没?→ 白名单有没? 五步走完,必能定位问题。
防呆设计建议 方案A:新增key时自动注册(推荐) lua
复制 -- 纸上存档.lua 初始化时自动确保 key 在白名单 local Shared = require("Network.共享定义") local KEY = "paper_war_bp"
-- 自动注册(如果白名单是运行时可修改的) local found = false for _, k in ipairs(Shared.USER_DATA_KEYS) do if k == KEY then found = true; break end end if not found then table.insert(Shared.USER_DATA_KEYS, KEY) end 方案B:文档检查清单 每次新增持久化key,在 共享定义.lua 的白名单注释中写明来源:
lua
复制 Shared.USER_DATA_KEYS = { "inv_meta", -- Store 元数据 "inv_holdings", -- 持仓数据 "inv_settings", -- 设置 "paper_war_bp", -- 纸上谈兵兵蓝图 (Pages/纸上存档.lua) } 文档版本: v1.0 创建日期: 2026-06-23 经验来源: 纸上谈兵持久化排查(7轮迭代)
3