(ctx, api),都可以是 async。每个 handler 返回 { blocks, state, effects } 三件套:
blocks:组件树;null表示界面不用变state:随下次调用回传的本地状态effects:要写数据、发通知等操作,声明出来由站点逐条校验后在一个事务里提交
ctx:上下文
这个列表就是隐私边界——它有意很窄,因为它对所有小程序作者可见。
Blocks:render 与 onAction
blocks 小程序返回组件树,由站点原生渲染:Webview:webview 与 onMessage
webview 小程序自己画界面(HTML + CSS + JS),有真正的客户端循环:让分数可信:种子回放
页面里的分数不可信。要上榜,就得让服务端能验算:服务端发一个种子,页面回传走法序列,onMessage 用同一份模拟逻辑重放,算出真实分数。作弊者要上榜就得真把游戏玩好——站内的贪吃蛇小程序就是这样:分数由服务端重放走法算出。
后台 handler
四者都以小程序自己的机器人账号运行,读不到任何成员的私有数据。它们的读写都落在小程序的共享区,所以后台运行里
api.kv.get 读回来的就是上一次后台运行存进去的东西。
后台调用的 ctx 长这样:
权限与数据
读(通过api,都要 await;数据在调用前已由平台预取,沙箱里不会真的去查库):
写(返回
effects,逐条校验后在一个事务里提交):
四条要记住的:
- 共享区只有 handler 写得进去。 成员的
kv.set永远只落到自己的命名空间,排行榜因此可信。 - 广播只是信号。 收到
rt.publish的客户端会重新走一次带权限校验的渲染,广播内容本身从不被当作状态。 api.post.get只对本次调用相关的帖子有答案,其他 id 一律返回null。预取正是装在一个角落里的小程序读不到整站的原因。kv.app不能被枚举。 它只对本次调用涉及的人作答,因为真实站点上这块区域是一张几万行的查找表,整份交出去等于把它塞进每一次调用的内存里。
context 是隐式授予的,不用申请。post.write、moderate.post、moderate.topic、webview、points.spend 是特权权限:只有管理员能授予(points.spend 还各自对应一个必须打开的站点设置)。发帖和处置内容的详细规则见机器人。积分:安装方与成员之间的账,不是平台发的钱
points.award 不是凭空发的。它是一次转账:积分从安装这个小程序的人的余额里扣出,转给获奖成员。安装方余额盖不住这一批时整批 effects 被拒(E_POINTS_FUNDS);站点没有积分系统时同样被拒(E_POINTS_UNAVAILABLE)。唯一的例外是管理员把某个安装显式标记为「平台出资」,那个安装发的积分才由站点直接铸造。无论谁出钱,单次上限、每小程序每日上限、每成员每日上限三道闸都在。
成员自己触发的调用里,获奖的就是这位成员。后台运行(onTrigger 等)以机器人身份执行,要在 effect 里写 user_id 指明收款人,而且只能是这次事件的主角——ctx.data.user_id,比如 member_invited 里的邀请人;指向别人会被拒(E_POINTS_DENIED)。这就是”每拉一个人给 X 能量”的写法:装在节点上的机器人听 member_invited,给 data.inviter_id 发 points.award,能量从安装它的节点主余额里扣。
反方向也存在:卖东西。points.spend(特权权限,管理员审核时单独授予)不是扣费,是发问:
onSpend(ctx, api) 被调用,ctx.spend 带着 { request_id, amount, status: "paid" }——在这里发货(kv.set 之类)。成员点「不用了」或者放过期(5 分钟内不答复自动作废),什么都不会发生。
onSpend 崩了钱照样算付了——用 api.points.spends() 对账,把付了钱没发的货补上,发货逻辑要按 request_id 幂等,重复调用不能重复发货。webview 小程序通过 community.onSpend = fn 收到同样的结果通知。
管理员在应用后台把一个安装标记为「平台出资」后,那个安装的 points.award 由站点铸造、points.spend 直接销毁积分,不经过任何人的余额——多用于站方自营、不该向安装者收费的官方小程序。
用读者的语言说话
ctx.locale 是论坛自己的语言。你读到的帖子上的 post.locale 是那位成员的。用哪个,取决于你写的东西给谁看:
回退顺序:精确匹配 → 同语言的其他地区写法 → 英文。写了
zh_CN 的小程序不该让 zh_TW 读者看到英文——简体离他们读的东西比英文近得多。