开发后台服务和面板
为插件提供持续状态与用户交互
创建项目时增加 --with-panel,得到一个可编译的计数面板。插件安装后由描述文件贡献面板;工作流节点只操作已有面板,不负责动态注册面板定义。
示例的三个部分
internal/paneldefinition:稳定的 panel ID、一个 number 字段、计数组件和 increment 按钮,附带中英文标签。cmd/companion:内存中的计数状态,提供 snapshot 和 event HTTP 接口。cmd/package/panel.go:把 companion 可执行文件、面板定义和接口路径放进签名包。
纯节点项目没有 companion。为已有项目增加面板时,补全这三部分,不能仅修改 plugin.json.panel=true。
组件与字段
| 组件 | 用途 |
|---|---|
| group | 分组与组件层级 |
| text / number / status | 文本、数字和布尔状态 |
| timer / progress | 会话计时和进度 |
| log | 有界记录列表 |
| button / select / toggle / input | 用户操作与输入 |
字段类型是 string、number、boolean。字段和组件 ID 保持稳定;修改显示标签不改变 ID。交互组件声明事件名,select 还声明 string 选项。数字可以设置显示精度,组件可以使用已内置的 i-tabler-* 图标。
面板布局是声明式组件树,不能直接嵌入任意 HTML、JavaScript 或 Vue 应用。拓展新的组件种类需要先扩展正式面板合同和宿主渲染器。
Provider 接口
计数器模板使用这些相对路径:
| 方法与路径 | 返回 |
|---|---|
GET /health |
200 JSON,包含与 descriptor 一致的 protocol 和 status |
GET /v1/panel |
panel.Snapshot |
POST /v1/events |
接收 panel.Event,返回 panel.Result |
POST /stop |
204,随后完成请求并关闭服务 |
路径在 descriptor 中声明,不要求所有插件采用同一路径。plugin.json.panelPort 同时控制包内启动参数与 loopback origin,选择空闲端口后重新打包,不要在用户环境中悄悄改到另一个未声明端口。
健康接口示例:{"protocol":"yotta.panel-provider/v1","status":"ready"}。仅返回空 204 不满足 companion 启动检查,宿主会认为服务不匹配并判定启动失败。
Snapshot 包含协议 yotta.panel-provider/v1、sessionId、revision、status、字段值、controlRevisions 和记录列表。每次 provider 启动生成新的 sessionId;revision 单调递增。支持的 status 为 ready、waiting、stale、unavailable、ended。
Event 包含 sessionId、eventId、componentId、事件名、控制版本和带类型的 value。按钮 value 为 null;toggle 是 boolean;input/select 是 string。Result 回传相同 eventId 和当前 snapshot。
不把重试当成第二次点击
示例对 eventId 保存最近 1024 个请求和结果:完全相同的事件返回原结果,不重复修改计数;相同 ID 携带不同请求返回 409。旧 session 或过期 control revision 也返回 409。HTTP 响应丢失时,不能生成新的 ID 自动重试一次有副作用的操作。
数据刷新 revision 与 control revision 含义不同。计数器只有一个按钮,所以示例把它们一起推进;加入高频遥测后,仅数据更新不应使用户正在填写的控制失效。正常数据刷新也不应生成用户交互事件。
有外部副作用或需要跨重启重放的服务,应按业务要求持久化事件回执并说明保留时间。示例的内存回执和计数在重启后清空,旧会话事件被拒绝。
生命周期和工作流
companion 的可执行文件和所需资源随包交付,Yotta 根据安装信息管理启动、健康检查和停止。面板隐藏时可以停止刷新,但关闭面板窗口不会自动关闭仍被工作流或采集任务使用的 provider。
持续采集放在 companion;工作流周期性读取观察结果,单次节点调用及时完成。世界位置可使用 SDK 的 WorldPosition 标准类型,再连接工作流状态;不要为每个采集插件设计另一套位置字段协议。
工作流通过正式“面板”节点族选择、显示、读取、写入、追加日志和等待交互。插件遥测字段归 provider 管理,工作流不能直接改写;交互控制通过事件协议修改。普通面板数值写入不冒充用户点击事件。
需要验证的现象
- 打包后安装能发现面板,重新打开仍保持正确的稳定引用。
- 按钮点击一次只处理一次,重复事件返回相同结果。
- provider 重启后产生新会话,旧会话的动作被拒绝。
- 网络或 provider 中断时保留最后视图并显示不可用状态,恢复后重新可用。
- 关闭面板不误杀其他使用者;卸载或停用沿插件生命周期执行。
模板通过 httptest 覆盖数据与交互协议,完整 UI 行为仍需在隔离的真实 Yotta App 中验证。
面板设置多页签
面板标题栏的齿轮支持多个设置页签。尺寸、透明度、失焦行为和穿透放在“窗口设置”;换算系数、显示规则等业务参数放在插件设置页签。设置页签不会注册成独立面板,也不会增加窗口里的内容页签。不要为设置另造网页弹窗或重复的数值面板。
Website 插件在 contributes.websites[] 的对应条目中添加 settingsPanels(最多八项)。每项复用 panel.Contribution,由宿主渲染标准控件:
{
"settingsPanels": [{
"companionId": "hud",
"snapshotPath": "/v1/settings",
"eventPath": "/v1/settings/events",
"definition": {
"format": "yotta.panel/v1",
"id": "gauge-settings",
"titleKey": "plugin.example.settings",
"fields": [{ "id": "factor", "kind": "string" }],
"components": [{
"id": "factor", "kind": "input",
"titleKey": "plugin.example.factor",
"field": "factor", "event": "set-factor"
}]
}
}]
}这是 Website 条目的局部示例;完整条目仍需 format、id、titleKey、url,并声明 companion 和中英文 messages。每个设置页签的 definition ID 在所属 Website 内唯一。输入通过标准 snapshot/event 协议读写:input 的值是字符串,provider 解析并验证范围;按钮值是 null。控制 revision 与高频遥测 revision 分开维护,事件去重沿用上面的规则。
宿主为声明位置依赖的插件提供数据来源选择,放在第一个插件设置页签;没有声明业务设置页签时,单独显示“数据来源”页签。来源按插件共享,应用后重启消费服务并重置会话统计。网页内容仍没有 Wails 或宿主接口。
要保留业务配置,可给 companion 参数传入 ${plugin-data}/settings.json。${plugin-data} 由宿主替换为当前 profile 下按包身份隔离的稳定目录,更新包不会改变该路径;多个 companion 使用不同文件名。provider 负责校验、原子保存和损坏文件备份恢复。已有网页 localStorage 配置应提供一次迁移,避免升级丢失用户参数。
验收时检查:小窗口里的页签和控件可操作、关闭重开仍保留设置、普通面板列表无额外入口、切换来源后生效、provider 重启后的会话和配置正确。开发版需要配套支持 settingsPanels 与 ${plugin-data} 的 SDK 和宿主。
Companion 网站与动态端口
插件自身服务的 HUD 网页可声明 {"format":"yotta.website/v1","id":"combat-hud","titleKey":"plugin.example.hud","companionId":"capture","path":"/hud"}。companionId 必须引用同一包内服务;宿主按当前配置解析端口,打开面板前启动服务与依赖。不要再填写 url,两种地址方式互斥。设置继续使用 settingsPanels;网页不获得宿主接口。