下载 App
医生权限申请/审批 链路问题
本轮"医生权限申请/审批"链路问题全部来自同一个链条:离线写入 → 云端补传 → 拉取 → 过滤 → 分页 → 渲染同步。以下按问题归纳经验 + 关键代码(均为 scripts/ 下用户代码)。


经验一:链式系统要逐段取证,诊断行比猜有用
空列表的原因可能有 6 层过滤、缓存、分页、渲染态……肉眼无法区分"数据没到云端"和"到了但被拦"。做法:把每一层的计数和具体 ID 直接渲染到页面上。
lua-- doctor_auth_admin.lua:过滤循环逐条记录拦截原因 local skippedList = {} -- 诊断: 记录每条被拦记录的 ID 和原因 ... if item.userId == myAdminId then skipSelf = skipSelf + 1 if #skippedList < 20 then skippedList[#skippedList + 1] = uid .. "=自己" end goto cf end ... filterStat_ = { total = #rankList, kept = #filtered, self = skipSelf, noTs = skipNoTs, admin = skipAdmin, approved = skipApproved, dismissed = skipDismissed, keptIds = table.concat(keptIds, ","), skipped = table.concat(skippedList, " "), } -- 诊断文本:常驻显示(列表非空也在底部出现),兼做版本指纹 function AdminUI._buildDiagText(s) local t = string.format( "诊断: 云端%d 保留%d | 自己%d 无标记%d 管理%d 已批%d 已移除%d 无昵称%d", s.total, s.kept, s.self, s.noTs, s.admin, s.approved, s.dismissed, s.noNick or 0) if s.keptIds and s.keptIds ~= "" then t = t .. "\n保留ID: " .. s.keptIds end if s.noNickIds and s.noNickIds ~= "" then t = t .. "\n无昵称ID: " .. s.noNickIds end if s.skipped and s.skipped ~= "" then t = t .. "\n被拦: " .. s.skipped end return t end
收获:"云端2 保留1 但显示0"这个矛盾直接把问题从"云端没数据"翻转到"保留后渲染丢了",一小时内定位到分页 bug。


经验二:缓存代理的可变参数 API 必须原样转发(丢参数=静默丢数据)
GetRankList 支持多个 extraKey 变参;代理层签名只接了第一个,导致 approved_ids、dismissed_ids 永远拿不到——不报错,只是数据消失。
lua-- CloudCache.lua:用 ... 收集并整体转发 function Proxy:GetRankList(key, offset, limit, callbacks, ...) local extraKeys = { ... } local cacheKey = key .. "_" .. tostring(offset) .. "_" .. tostring(limit) for _, ek in ipairs(extraKeys) do if type(ek) == "string" then cacheKey = cacheKey .. "_" .. ek end end ... -- 转发时一个都不能少 realAPI_:GetRankList(fwd[1], fwd[2], fwd[3], fwd[4], table.unpack(fwd, 5)) end


经验三:模块级"翻页状态"必须跟随"全量重拉"一起重置
"换一批"用 seenUserIds_ 记住翻过的 ID,但它只在进页面时清空;任何页面内后台重拉都会让新数据被旧标记永久隐藏。
lua-- doctor_auth_admin.lua function AdminUI._fetchApplicationsFromCloud() local cloudAPI = CloudCache.getAPI() isRefreshing_ = true -- 全量重新拉取 = 回到第 1 页;否则旧的"换一批"标记会把新数据里的申请者永久隐藏 seenUserIds_ = {} ...


经验四:两端判定标准必须一致(新增字段要兼容历史数据)
玩家端认"已申请"= doctor_apply==1 或 apply_ts 存在;开发端却只认 apply_ts。老版本提交的申请没有新字段,被当噪音永久拦掉。
lua-- doctor_auth_admin.lua 过滤处 -- 必须有申请标记:apply_ts(新版) 或 doctor_apply==1(旧版, 无时间戳)。 -- 玩家端"等待审核"用同样的判定,这里必须一致,否则旧版本申请会被永久拦掉 if (ts == nil or ts == "") and applyScore ~= 1 then skipNoTs = skipNoTs + 1 ... goto cf end


经验五:写路径成功后,所有读该数据的内存缓存都要同步(抄"对的参考实现")
数据链路是 云端 + 本地 + 页面内存缓存 三层。批准写成功、数据没丢,但页面缓存 cachedApprovals_/allCloudApprovals_ 没更新 → 切页"回退"。手动批准弹窗的代码是对的,行内批准漏了同样的两行——同一状态多入口写入时,每个入口都要做全套。
lua-- doctor_auth_admin.lua 行内批准回调(与手动批准弹窗对齐) DoctorAuth_.ApproveDoctorPlayer(capturedUserId, function(errMsg) if errMsg then self.backgroundColor = { 220, 60, 60, 255 } self.children[1].text = "云端失败!" else -- 与手动批准弹窗同步内存状态,否则切"已批准"页/切回来仍是旧数据 local uid = formatUserId(capturedUserId) cachedApprovals_[uid] = true allCloudApprovals_[uid] = true AdminUI._renderUI() end end)


经验六:离线写入用"脏队列 + 上线重放",业务方不感知(CloudCache.lua)
lua-- 离线分支:值先进本地缓存,再入脏队列,回调挂起等补传 local function flushDirty() if flushing_ or not isOnline() or dirtyIsEmpty() then return end flushing_ = true local batch = realAPI_:BatchSet() for key in pairs(dirtyInt_) do local v = intCache_[key] if v ~= nil then batch:SetInt(key, v); n = n + 1 end end for key in pairs(dirtyVal_) do local v = memCache_[key] if v == nil then v = loadCacheFile(key) end if v ~= nil then batch:Set(key, v); n = n + 1 end end batch:Save("离线补传", { ok = function() touchCloudOk() dirtyVal_, dirtyInt_, dirtyDel_ = {}, {}, {} -- 成功才清空 saveDirty() for _, fn in ipairs(flushedCallbacks_) do pcall(fn) end -- 通知业务方翻转UI end, ... -- 失败保留队列,每帧限流重试 }) end
配套三件事:
- upgrade()(检测到真云注入)立即 flushDirty(),main.lua 每帧调用 upgrade(),晚注入也能补传
- 队列持久化到 _dirty 文件,杀进程不丢
- CloudCache.onFlushed(fn) 一次性回调:申请页从 🕐已记录(本机)自动翻成 ✅已提交
- 绝不假报成功:deferred(离线暂存)与失败分开走不同页面,玩家截图里那行橙色"暂存本机待自动提交"就是它


经验七:诊断数据要留"证据时间戳",身份取值要多源兜底
lua-- CloudCache.lua:身份三源(GetUserId 全局函数最可靠,API 对象字段在部分端为空) local function syncUserId() local ok, uid = pcall(function() return GetUserId and GetUserId() end) if ok and uid and uid ~= 0 then ... return end -- 其次 realAPI_.userId、再 lobby:GetMyUserId() end -- 云端成功证据持久化,用于"从未连云"类问题的取证 local function touchCloudOk() ... saveCacheFile("_last_cloud_ok", ...) end


通用方法论(本轮所有修复的共性)
- 别信单一信号:"显示成功" ≠ "已上云","列表空" ≠ "没申请",逐层拿计数
- 状态回退 = 存在第二份缓存:找到写入路径没同步的那份读缓存
- 改动后先问"旧数据怎么办":新字段/新过滤必须兼容历史值(本轮 apply_ts 和 seenUserIds_ 都是这个坑)
- 用版本指纹确认线上跑的是哪版(诊断行"有没有"本身就是指纹——这轮最后卡住的原因正是手机还在跑旧包)
- Lua 词法作用域:local function 定义之前的代码看不到它(本会话两次踩坑,isOnline 前向引用、extraKey 丢参)
验证流程不变:新版包上手机 → 列表底部看到灰色"诊断:"行 → 批一人看它是否即时消失且切页不回退。确认 OK 告诉我,我清掉全部临时代码出正式版。



