下载 App
便签墙 - 云端跨用户数据 & 嘲讽文案系统
06/234 浏览综合
- 功能概述 登录页展示一面"便签墙",显示所有玩家的昵称和留言。留言内容根据玩家持仓盈亏自动生成:
盈利玩家:显示 “收益 +¥X,XXX” 亏损玩家:根据亏损金额分级,随机显示网络嘲讽A股段子 2. 技术方案 2.1 核心问题:跨用户数据共享 clientCloud 数据按用户隔离,无法直接读取其他用户的数据。 解决方案:利用排行榜(GetRankList)机制实现跨用户数据可见。
2.2 数据存储设计 Key 存储类型 用途 说明 wall_profit iscores (整数) 持仓盈亏排名 单位:分(×100取整),因为 iscores 只接受整数 wall_msg values (字符串) 便签留言内容 嘲讽文案或收益文案 2.3 数据流 登录成功 ↓ Store.LoadFromCloud() 加载用户数据 ↓ Store.SyncWallToCloud() 强制同步便签墙(不依赖 dirty 状态) ↓ Login.FetchWallData() 从排行榜拉取所有用户数据 ↓ GetUserNickname() 批量获取昵称 ↓ Login.RenderWall() 动态生成便签 UI 2.4 关键 API 用法 lua
复制 -- 写入(Store.SyncWallToCloud) local batch = clientCloud:BatchSet() batch:SetInt("wall_profit", profitCents) -- iscores,可排名 batch:Set("wall_msg", msg) -- values,附加数据 batch:Save(...)
-- 读取(Login.FetchWallData) clientCloud:GetRankList("wall_profit", 0, 24, { ok = function(list) -- list[i].userId, list[i].iscore, list[i].score (即 wall_msg) end, }, "wall_msg") -- 最后参数指定同时拉取的 values key
-- 获取昵称 GetUserNickname({ userIds = userIdList, onSuccess = function(nicknames) end, }) 3. 踩坑经验 3.1 GetPortfolioSummary() 不含 totalPL Store.GetPortfolioSummary() 返回的汇总数据中没有 totalPL 字段(只有 netWorth、totalAssets 等)。 totalPL 在 GetPortfolioSummaryByAccount() 中才有计算。
解决方案:在 _appendWallData() 中直接计算:
lua
复制 local totalPL = 0 for _, h in ipairs(Store.holdings.items) do totalPL = totalPL + (h.currentPrice - h.costPrice) * h.annual.sharesCurrent end 3.2 SyncToCloud 有 dirty 门槛 Store.SyncToCloud() 开头有 if not Store.IsDirty() then return end。 如果用户只是查看数据没有修改,便签墙数据不会同步。
解决方案:独立出 Store.SyncWallToCloud() 函数,不依赖 dirty 状态,在每次登录成功后强制调用。
3.3 iscores 只接受整数 排行榜的 iscores 只能存整数,但盈亏金额是浮点数。
解决方案:以"分"为单位存储(×100 四舍五入取整):
local profitCents = math.floor(totalPL * 100 + 0.5) 3.4 GetRankList 附加 values GetRankList 第5个参数可传入额外的 values key 名,返回结果的 .score 字段即为该 key 的值:
lua
复制 clientCloud:GetRankList("wall_profit", 0, 24, callbacks, "wall_msg") -- list[i].score == wall_msg 的值 4. 嘲讽文案分级体系 根据亏损金额分为四档,亏得越多段子越狠:
4.1 小亏(< 5,000 元)— 轻度自嘲 文案 风格 今天又绿了 日常感慨 绿到我发慌 轻微焦虑 又被割韭菜 经典自嘲 只是数字游戏! 自我安慰 加仓还是割肉? 纠结心态 小亏怡情嘛… 佛系 A股日常而已 见怪不怪 反向指标本人 自黑 买啥跌啥 卖啥涨啥 玄学 仅0.48万家下跌 反讽 4.2 中亏(5,000 ~ 50,000 元)— 明显肉痛 文案 风格 心态崩了 情绪崩溃 肠子悔青了 后悔 不看了不看了 逃避 大A专治各种不服 认命 满仓踏空更惨 比惨 主力的打工仔 身份自嘲 抄底抄在半山腰 操作失误 想退坑了 想放弃 可以七天无理由退款吗 电商梗 韭零后报到 世代梗 山顶洞人就是我 高位站岗 碎钞机 进多少吞多少 绝望 4.3 大亏(50,000 ~ 200,000 元)— 严重受伤 文案 风格 关灯吃面 经典典故 大A关了吧 极端建议 联合收割机来了 市场比喻 满仓踩雷 救命 呼救 回本遥遥无期 绝望 血亏 想哭 情绪直白 天台的风好大 夸张段子 跌妈不认 热词 上午看K线 下午看心电图 对比梗 涨时如便秘 跌时如拉稀 比喻 亏的是真金白银 赚的是人生阅历 苦中作乐 问君亏钱有多愁 文艺 4.4 巨亏(> 200,000 元)— 极限自嘲 文案 风格 天台见 经典 刚炒股半年就瘦成壁虎了 段子 三千点保卫战又输了 时事 留着账号给后人 文天祥梗 股市自古谁无赔 文天祥梗 生当买股票 死亦有跌停 李清照梗 沉舟侧畔千帆过 我的账户全是绿 刘禹锡梗 千点狂泻 万股折腰 沁园春梗 恰似A股绿油油 李清照梗 利好落地必跌 利空直接暴跌 写实 熊市漫道何时了 不知亏多少 虞美人梗 进来多少吞多少 绝望 5. 文案扩展指南 如需添加新文案,编辑 scripts/Data/Store.lua 中 Store._appendWallData() 函数的对应 pool 表:
lua
复制 if absLoss < 5000 then pool = { ... } -- 小亏池 elseif absLoss < 50000 then pool = { ... } -- 中亏池 elseif absLoss < 200000 then pool = { ... } -- 大亏池 else pool = { ... } -- 巨亏池 end 添加规则:
每条文案控制在 15 字以内(便签卡片宽度有限) 避免真正冒犯性内容,保持"苦中作乐"的基调 每个池子建议 10~15 条,保证随机性 6. 相关文件 文件 职责 scripts/Data/Store.lua _appendWallData() 生成文案、SyncWallToCloud() 强制同步 scripts/Pages/登录.lua FetchWallData() 拉取云端数据、RenderWall() 渲染便签 UI scripts/Network/便签墙服务端.lua 多人模式:便签广播 + 用户数据代理 + 迁移导入 scripts/Network/数据代理.lua UserDataProxy:客户端服务端数据中转 scripts/Data/数据迁移.lua ForceSync:单机模式数据导出到 clientCloud scripts/main.lua 登录回调中调用数据加载和便签同步 7. 聚光灯效果模块 7.1 设计意图 收益最高(或满足条件)的玩家便签获得"聚光灯"视觉加成,突出社区榜样效应。
7.2 触发条件 lua
复制 -- Pages/登录.lua → RefreshWallWithData() highlight = profitCents > 500000 -- 盈利超过 5000 元(单位:分) 当前策略:绝对阈值 5000 元。可改为相对策略(如"收益最高者始终高亮")。
7.3 渲染效果 元素 普通便签 聚光灯便签 尺寸 90~120px 随机 150px 固定(放大) 透明度 0.28~0.38 1.0(完全不透明) 颜色 随机色 / 亏损色 #FFFDE0(暖黄) 旋转 ±15° 随机 ±5° 微倾 边框 无 2px 金色描边 #FFD700 光晕 无 双层径向渐变光晕 7.4 光晕实现 lua
复制 -- 外层光晕:260x260 圆形,暖黄色径向渐变 UI.Panel { width = 260, height = 260, borderRadius = 130, backgroundGradient = { type = "radial", from = { 255, 210, 100, 45 }, -- 中心:暖黄半透明 to = { 255, 180, 60, 0 }, -- 边缘:完全透明 }, }
-- 内层光晕:120x120 圆形,更亮的核心 UI.Panel { width = 120, height = 120, borderRadius = 60, backgroundGradient = { type = "radial", from = { 255, 230, 160, 35 }, to = { 255, 200, 80, 0 }, }, } 7.5 扩展建议 策略 实现 适合场景 绝对阈值(当前) profitCents > 500000 多人活跃,有人盈利 相对最大值 取 max(profitCents) 者高亮 竞争感强,无论盈亏 多级高亮 前3名不同光环颜色 排行榜式竞争 亏损聚光灯 亏最多的也给一个"惨"光环 娱乐向 8. 数据迁移模块(clientCloud → serverCloud) 8.1 核心发现 事实 说明 clientCloud 和 serverCloud 不共享存储 尽管文档声称共享后端,实测写入 clientCloud 的数据 serverCloud 读不到 单机模式 clientCloud 可用,多人模式为 nil 多人客户端只能通过 UserDataProxy → 服务端 → serverCloud 两者 userId 相同(1118015608) 但数据命名空间隔离(可能是 map/scope 不同) 服务端有 serverCloud 完全读写权限 迁移必须经过服务端 8.2 迁移架构 ┌─────────────────────────────────────────────────────────────┐ │ 迁移流程(一次性) │ ├─────────────────────────────────────────────────────────────┤ │ │ │ [单机模式] │ │ clientCloud:BatchGet() → 37只持仓数据 │ │ ↓ │ │ print("[MIGRATION_CHUNK_N]" .. json) │ │ ↓ │ │ 开发环境读取 /opt/log/dev/user_script.log │ │ ↓ │ │ 提取 JSON → scripts/Data/migration_payload.json │ │ │ │ [多人模式] │ │ 服务端启动 → HandleUserDataRequest() │ │ ↓ │ │ 检测 currentHoldings < 5 │ │ ↓ (是) │ │ cache:GetFile("Data/migration_payload.json") │ │ ↓ │ │ 解析 blob → serverCloud:BatchSet(uid) 写入 │ │ ↓ │ │ 返回完整数据给客户端 │ │ │ └─────────────────────────────────────────────────────────────┘ 8.3 关键代码位置 文件 函数 职责 scripts/Data/数据迁移.lua Migration.ForceSync() 单机模式:Store → clientCloud scripts/Data/数据中心/云同步.lua Store.LoadFromClientCloud() 单机模式:clientCloud → Store scripts/Network/便签墙服务端.lua HandleUserDataRequest() 多人服务端:检测迁移 → 导入 JSON → 写入 serverCloud scripts/Data/migration_payload.json (数据文件) 迁移数据快照(导入后可删除) 8.4 提取命令 bash
复制
从预览日志中提取迁移数据
grep "MIGRATION_CHUNK" /opt/log/dev/user_script.log
| sed 's/.[MIGRATION_CHUNK_[0-9]] //'
| tr -d '\n' \
| sed 's/.[MIGRATION_CHUNK_[0-9]] //'
| tr -d '\n' \
scripts/Data/migration_payload.json
验证
python3 -c "import json; d=json.load(open('scripts/Data/migration_payload.json'));
print('holdings:', len(d['holdings']['items']))" 8.5 迁移后清理 数据成功导入 serverCloud 后,应删除迁移相关代码以避免重复导入:
print('holdings:', len(d['holdings']['items']))" 8.5 迁移后清理 数据成功导入 serverCloud 后,应删除迁移相关代码以避免重复导入:
删除 scripts/Data/migration_payload.json 移除 HandleUserDataRequest() 中的迁移检测逻辑(或将阈值改为 0) 保留 UserDataProxy 超时保护(15秒降级)作为永久性容错 8.6 经验教训 教训 说明 不要假设 clientCloud = serverCloud 实测验证,不同模式可能隔离 ForceSync 前先验证数据完整性 加 if count > 0 防空写覆盖 迁移数据通过日志桥接 WASM 无法直接写入 workspace,但 print→日志文件可被读取 服务端是唯一可信写入点 多人模式下只有服务端能写 serverCloud 加超时保护 UserDataProxy 15秒超时避免永久卡死 9. 多人网络模块架构 9.1 模式配置 json
复制 // .project/settings.json → @runtime.multiplayer { "enabled": true, "max_players": 100, "persistent_world": { "enabled": true } } persistent_world 模式:服务器常驻运行,玩家随进随出(适合社区型应用)。
9.2 数据流(多人模式) 客户端启动 → UserDataProxy.Init() 订阅事件 → WallClient.Init() 订阅 ServerReady → 服务器连接成功 → ServerReady 事件 → UserDataProxy.OnServerReady() → 发送 USER_DATA_REQUEST → 服务端 HandleUserDataRequest() → serverCloud:BatchGet(uid) 读取用户数据 → 检测迁移需求(migration_payload.json) → 返回 USER_DATA_RESPONSE → 客户端缓存数据 → isReady = true → Store.LoadFromCloud() → 解析到 Store → BuildUI() 9.3 容错机制 机制 实现 触发条件 UserDataProxy 超时 15秒后标记 ready(空数据) 服务器连接失败 ForceSync 空数据保护 if count > 0 才写入 防止空 Store 覆盖云端 迁移阈值 currentHoldings < 5 才触发导入 避免重复导入





