下载 App

持久化问题排查方法论

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轮迭代)
【求积分】
因为没有积分继续完善框架,目前测试连接进入游戏界面,可能需要如下操作:
1、先随意输入用户名,默认密码123点击“进入系统”
2、稍等一会儿,会读取到当前用户ID的用户名,默认密码123就可以进入了
3、进入游戏修改密码,下次默认密码就不是123了
如果大家都能帮忙来体验,可能这个便签墙上会有越来越多的便签,需要玩家来丰富便签墙截图
【求积分】 因为没有积分继续完善框架,目前测试连接进入游戏界面,可能需要如下操作: 1、先随意输入用户名,默认密码123点击“进入系统” 2、稍等一会儿,会读取到当前用户ID的用户名,默认密码123就可以进入了 3、进入游戏修改密码,下次默认密码就不是123了 如果大家都能帮忙来体验,可能这个便签墙上会有越来越多的便签,需要玩家来丰富便签墙
3 赞
7 回复
耦合性 V5
结论:主游戏业务耦合优化已经基本完成,可以收尾。 不是说项目里完全没有高入度/高出度模块,而是: 主要业务热点已经拆完 原 API 都保持兼容 每批拆分都通过了 LSP 每批拆分都通过了官方构建 每批局部依赖检查都没有循环 当前项目仍然构建成功 当前 Lua LSP errors = 0 当前验证状态 刚刚重新确认: Lua LSP errors = 0 官方构建成功 当前 Lua 文件数 = 4
官方
3 赞
耦合性 V2
目前耦合性已经从“偏高”降到了 中等偏低、可控。 简单评价: 大问题已经收口,剩下的是结构继续细分的问题。 当前不会再出现明显的全局入口乱写、页面直接抢 UI Root、Network 回调污染全局这类高风险耦合。但 State、App 启动层、投资数据页面仍有进一步拆分空间。 已明显改善的部分 1. 全局 UI Root 已收口 现在 UI.SetRoot 只剩一处: scripts/App/R
官方
4 赞
耦合性 V4
当前游戏耦合性评估 结论:低到中低耦合。 经过多轮拆分后,原来的高入度 Facade / shim 基本已经清空业务依赖,当前主要热点已经转为“细粒度状态模块”和“流程编排模块”,结构比最初健康很多。 1. 当前核心指标 Lua 文件数: 200 内部 require 边数: 615 外部 require 引用: 79 平均内部出度: 3.08 循环节点: 0 关键结论: 循环依赖: 0 这是最重
官方
4 赞
耦合性 V3
当前耦合性已经从“中低耦合”进一步改善到: 低到中低耦合;核心玩法聚合直连基本消除,剩余主要是状态 Facade 和数据 Store 的中心化耦合。 量化结果 只读扫描 scripts/: Lua 文件数:175 内部 require 边数:534 循环依赖数:0 关键聚合模块入度: Pages.纸上障碍 incoming = 0 Pages.怪物工坊.战斗机制 incoming = 0 Page
官方
4 赞
耦合 V1
已分析当前 scripts/ 用户代码,未修改文件。整体看,项目是一个 main.lua 主入口 + 投资页面模块 + 纸上谈兵插件模块 + 网络/数据代理模块 的结构,耦合点比较集中。 1. 主入口如何耦合其他模块 主入口是: scripts/main.lua 它分服务端和客户端两条启动路径。 服务端路径 main.lua 先判断服务端模式: scripts/main.lua:7:判断 IsSe
官方
4 赞
01:32
【号外:新游】《小韭成长记》截图
【号外:新游】《小韭成长记》
《小韭成长记》是一款“投资记录 + 游戏化成长”的复合型小游戏: 投资端:记录股票、资金流水、账户管理等投资行为,生成多维度分析(组合估值、融资成本、指数对比等)。 游戏特色:你可以给后面来踢馆的玩家制造麻烦! 游戏端:通过“纸上谈兵”将投资数据转化为游戏战力(核心、甲片、行动点等),支持造兵、闯关、建设安全屋,让投资成果可视化、趣味化。 股票操作:支持「买入/卖出/分红/融资借入/还款」,自
【申请积分】刚把架构搭建了一些,希望能申请一些积分把游戏跑通截图
【申请积分】刚把架构搭建了一些,希望能申请一些积分把游戏跑通
4 赞
3 回复