可靠的本地投递
目标读者:验证离线、重启和附件部分结果的开发者。
Godot SDK 只使用一套有界本地队列。启用队列后,首次 HTTP 请求之前先原子保存冻结的报告。磁盘或容量错误会停止本次提交,不能称作“已排队”。显式本地 offline_queue: false 或已验证的项目设置 false 会禁止新反馈落盘;此时截图只放在内存中,进程退出后无法恢复这次提交。
最小示例
EchoNebulaFeedback.configure({
"project_key": "enb_pk_xxx",
"public_api_base_url": "http://localhost:4000",
"offline_queue": true,
"max_offline_queue_items": 100,
"max_offline_queue_attempts": 3,
"max_offline_queue_bytes": 67108864
})
默认最多 100 条待发送记录、100 条死信,受管理记录与附件合计 64 MiB,每份报告或附件最多尝试 3 次。本地设置的硬上限是每类 100 条、每阶段 10 次、256 MiB;字节预算最低 8 KiB。队列会为原子进度保存预留空间。容量已满时拒绝新提交,不挤掉正在等待的报告;较旧死信可以被清理。将字节预算降低到现有用量以下,可能暂停投递,直到恢复空间或预算。
确认与重启
提交入口在截图等待之前锁定一次动作,并冻结报告 ID、正文、上下文、语言和 consents。截图期间关闭面板会取消尚未完成的动作。完成保存后,关闭面板或退出游戏不会为原反馈生成新身份。
SDK 收到报告回执 feedback_id 后,先保存它,再上传附件。每个附件保存独立幂等键、内容摘要、尝试次数和已确认的回执。重启会跳过已确认阶段,只用原内容、原幂等键重发未确认请求。响应丢失可能增加一次 HTTP 请求,但服务端幂等约束仍只保留一份报告或附件。游戏进程之外没有投递服务。
feedback_payload_ready 只表示准备好的意图。投递事实使用不含载荷的 feedback_delivery_changed(result) 通知:
| 状态 | 含义 |
|---|---|
sending |
正在调度符合条件的请求,还没有确认回执。 |
queued |
已保存在本地,等待具备发送条件或重试。 |
partial |
报告已接受,部分附件尚未确认;retryable 表明是否继续自动恢复。不要为补传附件重新创建报告。 |
succeeded |
报告和全部所选附件都有确认回执。 |
failed |
未能完整确认结果,投递已停止;不代表服务端一定什么都没收到。 |
结果字段为 client_report_id、feedback_id、state、retryable、reason、attachments_total、attachments_accepted、attachments_failed。不包含反馈正文、联系方式、日志、Key、本地路径或签名 URL。不要打印完整 payload 或队列 JSON。团队工作流仍为 new/reviewing/resolved/ignored/archived;Console 只展示服务端已确认的报告与附件。
网络错误、408、429 和 5xx 可以在持久化预算内重试,遵守秒数及 HTTP 日期形式的 Retry-After。其他 4xx、本地文件缺失/为空/发生变化和耗尽次数会自动停止,同时保留已有部分回执。进度写盘失败后停止调度,直到 SDK 重新配置或启动,不覆盖最后成功保存的记录。配置请求最多重试 10 次;解决持续配置故障后可调用 refresh_sdk_config()。调度忙碌时刷新可能返回 false。
项目归属
记录绑定规范化 endpoint、已知时由服务端验证的 project ID,以及不可逆 Project Key 指纹,不保存 Key 明文。已知项目更换 Key 后,必须先由配置接口确认相同项目、相同 endpoint,才能继续恢复;不同项目或 endpoint 不会静默接收旧载荷。
首次配置请求离线时,由本地队列选项决定是否保存最小记录;可选数据控件在项目策略确认前不可用。此时不会猜测 project ID,只允许原 Key 指纹与原 endpoint 在配置成功后补足归属。更换一个尚未验证的 Key,不能迁移这份记录,即使新 Key 后来属于同一项目。HTTP 不携带凭证跟随重定向。
本地 7 天期限与文件
本地未发送或失败的报告、死信及受管理附件,从首次落盘起最长保留 7 天(604800 秒)。已经保存过的附件可能使期限更早。重试、重新入队、更换 Key 和转入死信都不续期;请求前会拒绝到期条目。SDK 启动和活跃清理时删除到期数据,包括游戏树暂停或时间倍率为零时。游戏关闭后没有代码执行,物理清理要等下次启动,不存在宿主外后台清理服务。
无效时间和时钟回拨采用保守拒绝,不重新授予保留期。存储拒绝删除时返回 cleanup_failed,过期内容仍禁止补发,并在活跃期间重试本地清理,不会声称删除成功。这个期限不删除或改变服务端已经收到的数据;服务端保留期是独立政策。
SDK 拥有 user://echo_nebula_feedback/queue/、dead_letter/ 和 attachments/,不要用这些目录保存宿主游戏文件。只管理经过校验的直接文件引用,拒绝路径越界和符号链接。启动时清理受管理孤儿文件,活跃期间也会清理遗留临时文件。当前 v3 记录继续使用冻结意图、分阶段回执、项目绑定和7天上限。旧 v1/v2 记录只从经过校验的 SDK 专有路径中删除,不迁移、不发送。删除被拒绝时保持不可发送并报告 cleanup_failed,直到本地清理成功。不可读内容最多保留有界、不含原始载荷的诊断。
验证
在 SDK 仓库执行 ./scripts/check-sdk-alpha.sh 和 GODOT_BIN=/path/to/godot ./scripts/run-godot-headless.sh。Python 工具只构造当前 payload fixture,队列和崩溃恢复由真实 Godot 门禁验证。
Infra 中的 node scripts/e2e-worktree-isolated.mjs --g2-sdk 会建立自有临时 API/PostgreSQL/MinIO/Console 栈,以真实 Addon 验证响应丢失、强制结束进程、附件恢复、项目隔离、到期和磁盘错误。追加 --g2-native 运行需要鼠标交互的原生窗口场景。提交后的 canonical 验收仍用 ./scripts/e2e-fresh-stack.sh,clean gate 不变。当前运行证据只覆盖实测桌面引擎,不构成全平台或导出游戏的支持承诺。
常见错误
- 队列文件被删除不证明送达:到期或清理也会删除文件。
- 不要在部分成功后重复创建报告,也不要记录完整载荷。
- 不要承诺游戏关闭时立即执行物理清理。