2026-07-27 14:28:07 +08:00
|
|
|
|
# Worker 插件开发指南
|
2026-07-21 16:14:02 +08:00
|
|
|
|
|
2026-08-05 18:19:01 +08:00
|
|
|
|
每个 `src/plugins/<name>/` 子目录是一个独立的 Worker 插件,封装特定领域的业务逻辑。插件通过 Zenoh RPC 对外提供服务,HTTP 路由由框架根据 TOML 配置自动生成。
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 目录结构约定
|
2026-07-21 16:14:02 +08:00
|
|
|
|
|
|
|
|
|
|
```text
|
2026-08-05 18:19:01 +08:00
|
|
|
|
<plugin_name>/
|
2026-08-12 10:24:32 +08:00
|
|
|
|
├── __init__.py # 入口:Plugin 子类 + create_workers/cleanup 绑定
|
2026-08-05 18:19:01 +08:00
|
|
|
|
├── global_var.py # 模块级全局状态(Database、API client 等)
|
2026-07-27 14:28:07 +08:00
|
|
|
|
├── router/ # RPC 端点实现
|
2026-08-05 18:19:01 +08:00
|
|
|
|
│ ├── __init__.py # 导入所有端点模块(`from . import *` 或显式导入)
|
2026-07-27 14:28:07 +08:00
|
|
|
|
│ └── <操作名>.py # 每个端点一个文件,使用 @rpc_method 装饰器
|
|
|
|
|
|
├── models/ # 数据模型
|
2026-08-05 18:19:01 +08:00
|
|
|
|
│ ├── __init__.py # 模型导出(由 gen_models 自动生成,勿手动修改)
|
|
|
|
|
|
│ ├── pydantic.py # Pydantic 请求/响应模型(由 gen_models 自动生成)
|
2026-07-27 14:28:07 +08:00
|
|
|
|
│ └── <表名>.py # SQLModel ORM 模型(如需要数据库)
|
2026-08-05 18:19:01 +08:00
|
|
|
|
├── routes.py # 自定义 FastAPI 路由(可选,用于特殊端点)
|
|
|
|
|
|
└── README.md # 插件说明文档(可选)
|
2026-07-27 14:28:07 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-08-05 18:19:01 +08:00
|
|
|
|
## 已有插件概览
|
|
|
|
|
|
|
2026-08-12 10:24:32 +08:00
|
|
|
|
| 插件 | 特点 | 适合参考 |
|
|
|
|
|
|
| ------------------------ | -------------------------------------------- | ---------- |
|
|
|
|
|
|
| **example** | 最简示例,Plugin 基类 + Database + 生命周期 | 入门学习 |
|
|
|
|
|
|
| **auth** | SQLModel ORM + 管理员初始化 + 多进程幂等 | 数据库操作 |
|
|
|
|
|
|
| **doubao** | 外部 API 客户端(Ark/OpenAI)+ AICC 安全通道 | 外部集成 |
|
2026-08-14 16:04:39 +08:00
|
|
|
|
| **aibox_sophon_adapter** | 自定义 `APIRouter` + MQTT 客户端组件(`mqtt_clients.py`)+ 后台任务 | 高级模式 |
|
2026-08-05 18:19:01 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-07-27 14:28:07 +08:00
|
|
|
|
## 开发新插件:完整流程
|
|
|
|
|
|
|
|
|
|
|
|
### 第一步:创建目录结构
|
|
|
|
|
|
|
2026-08-12 10:24:32 +08:00
|
|
|
|
> **推荐**:直接用脚手架生成统一风格的插件骨架,无需手动创建目录:
|
|
|
|
|
|
>
|
|
|
|
|
|
> ```bash
|
|
|
|
|
|
> uv run gen_plugin <name> [operation ...]
|
|
|
|
|
|
> ```
|
|
|
|
|
|
>
|
|
|
|
|
|
> 以下为手动创建方式(了解结构时参考)。
|
|
|
|
|
|
|
2026-07-27 14:28:07 +08:00
|
|
|
|
```bash
|
2026-08-05 18:19:01 +08:00
|
|
|
|
mkdir -p src/plugins/<name>/router
|
|
|
|
|
|
mkdir -p src/plugins/<name>/models
|
|
|
|
|
|
touch src/plugins/<name>/__init__.py
|
|
|
|
|
|
touch src/plugins/<name>/global_var.py
|
|
|
|
|
|
touch src/plugins/<name>/router/__init__.py
|
2026-07-27 14:28:07 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 第二步:注册插件
|
|
|
|
|
|
|
2026-08-14 16:04:39 +08:00
|
|
|
|
在 `config/workers/<name>/config.toml` 中通过 `[workers]` 节注册(网关启动时自动扫描发现):
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
|
|
|
|
|
```toml
|
2026-08-14 16:04:39 +08:00
|
|
|
|
# config/workers/<name>/config.toml
|
|
|
|
|
|
[workers]
|
|
|
|
|
|
type = "<name>" # 插件类型(worker type)
|
|
|
|
|
|
module = "plugins.<name>" # Python 模块路径
|
|
|
|
|
|
factory = "create_workers" # 工厂函数名(Plugin 基类绑定的方法)
|
|
|
|
|
|
cleanup = "cleanup" # 清理函数名
|
|
|
|
|
|
count = 3 # Worker 副本数
|
|
|
|
|
|
|
|
|
|
|
|
# 运行时配置(展平后传给 create_workers 的 config 参数)
|
2026-07-27 14:28:07 +08:00
|
|
|
|
[workers.config]
|
2026-08-05 18:19:01 +08:00
|
|
|
|
data_dir = "./data"
|
2026-07-27 14:28:07 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-14 16:04:39 +08:00
|
|
|
|
> 旧式全局注册文件 `config/workers/config.toml`(`[[workers]]` 数组)仅作兜底兼容,新插件请使用各插件自己的 `config.toml`。
|
|
|
|
|
|
|
2026-07-27 14:28:07 +08:00
|
|
|
|
### 第三步:编写端点 TOML 配置
|
|
|
|
|
|
|
2026-08-05 18:19:01 +08:00
|
|
|
|
创建 `config/workers/<name>/config.toml`(分组配置):
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
|
|
|
|
|
```toml
|
|
|
|
|
|
[worker]
|
|
|
|
|
|
defaults = { timeout = 3.0, auth_required = true, cacheable = false, internal = false }
|
|
|
|
|
|
|
|
|
|
|
|
[worker.registration]
|
|
|
|
|
|
router_prefix = "/<prefix>"
|
2026-08-05 18:19:01 +08:00
|
|
|
|
tags = ["<name>"]
|
2026-07-27 14:28:07 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-05 18:19:01 +08:00
|
|
|
|
创建每个操作的 `.toml` 文件,如 `config/workers/<name>/my_action.toml`:
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
|
|
|
|
|
```toml
|
|
|
|
|
|
[POST]
|
|
|
|
|
|
summary = "我的操作"
|
|
|
|
|
|
auth_required = true
|
|
|
|
|
|
|
|
|
|
|
|
[POST.response_model]
|
|
|
|
|
|
result = { type = "str", required = true }
|
|
|
|
|
|
message = { type = "str", default = "" }
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 第四步:生成 Pydantic 模型
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-08-20 16:51:00 +08:00
|
|
|
|
uv run gen_models
|
2026-07-27 14:28:07 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-05 18:19:01 +08:00
|
|
|
|
自动在 `src/plugins/<name>/models/` 下生成 `pydantic.py` 和 `__init__.py`。
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
|
|
|
|
|
### 第五步:实现 RPC 端点
|
|
|
|
|
|
|
2026-08-05 18:19:01 +08:00
|
|
|
|
`src/plugins/<name>/router/my_action.py`:
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
from chongming_worker.decorators import rpc_method
|
|
|
|
|
|
from ..models.pydantic import MyActionPostRequest, MyActionPostResponse
|
|
|
|
|
|
|
|
|
|
|
|
@rpc_method(
|
2026-08-05 18:19:01 +08:00
|
|
|
|
subject="<name>.my_action",
|
2026-07-27 14:28:07 +08:00
|
|
|
|
method="POST",
|
|
|
|
|
|
response_model=MyActionPostResponse,
|
|
|
|
|
|
)
|
|
|
|
|
|
async def my_action(request: MyActionPostRequest) -> MyActionPostResponse:
|
2026-08-05 18:19:01 +08:00
|
|
|
|
"""业务逻辑"""
|
2026-07-27 14:28:07 +08:00
|
|
|
|
result = await do_something(request)
|
|
|
|
|
|
return MyActionPostResponse(result="success", message="done")
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 第六步:更新 router 导出
|
|
|
|
|
|
|
2026-08-05 18:19:01 +08:00
|
|
|
|
`src/plugins/<name>/router/__init__.py`:
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
from . import my_action
|
|
|
|
|
|
__all__ = ["my_action"]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-12 10:24:32 +08:00
|
|
|
|
### 第七步:实现插件入口(推荐 Plugin 基类)
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
2026-08-12 10:24:32 +08:00
|
|
|
|
`src/plugins/<name>/__init__.py`(由 `gen_plugin` 脚手架生成):
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
|
|
|
|
|
```python
|
2026-08-12 10:24:32 +08:00
|
|
|
|
import logging
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
2026-08-12 10:24:32 +08:00
|
|
|
|
from chongming_worker.plugin import Plugin
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
2026-08-12 10:24:32 +08:00
|
|
|
|
from . import router # noqa: F401 触发 @rpc_method 注册
|
|
|
|
|
|
# from .routes import router # 可选:自定义 FastAPI 路由(网关自动发现)
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
2026-08-12 10:24:32 +08:00
|
|
|
|
logger = logging.getLogger(__name__)
|
2026-08-05 18:19:01 +08:00
|
|
|
|
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
2026-08-12 10:24:32 +08:00
|
|
|
|
class MyPlugin(Plugin):
|
|
|
|
|
|
type = "<name>"
|
|
|
|
|
|
db_name = "<name>.db" # 自动在 data_dir 下建 SQLite 库(不需要则置 None)
|
|
|
|
|
|
auto_bind = True # 首次初始化时自动 auto_bind_models("<name>")
|
|
|
|
|
|
|
|
|
|
|
|
async def on_init(self):
|
|
|
|
|
|
"""首次 create_workers 时调用一次:初始化资源、启动后台任务。"""
|
|
|
|
|
|
self.set_var("db", self.db) # 发布到 global_var 供业务代码读取
|
|
|
|
|
|
# self.spawn_task(self._background_loop())
|
|
|
|
|
|
|
|
|
|
|
|
async def on_cleanup(self):
|
|
|
|
|
|
"""cleanup 时调用:关闭自定义客户端等(后台任务与数据库由基类处理)。"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 暴露给 app lifespan 的模块级入口(bound method,签名与旧写法完全一致)
|
|
|
|
|
|
_plugin = MyPlugin()
|
|
|
|
|
|
create_workers = _plugin.create_workers
|
|
|
|
|
|
cleanup = _plugin.cleanup
|
2026-07-27 14:28:07 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-14 16:04:39 +08:00
|
|
|
|
基类自动处理:幂等(初始化锁)、统一日志、数据库建库/释放、后台任务取消、清理钩子、模型自动绑定、cleanup 时重置已发布的 global_var。详见 `utils/worker/README.md` 的「Plugin 基类」章节。
|
2026-08-12 10:24:32 +08:00
|
|
|
|
|
2026-08-14 16:04:39 +08:00
|
|
|
|
> **旧写法已废弃**:不依赖基类的传统 `create_workers` / `cleanup` 模块级函数写法仅框架层面保留兼容,现有插件(auth / doubao / aibox_sophon_adapter / example)均已迁移到 Plugin 基类,新插件请勿采用旧写法。
|
2026-08-05 18:19:01 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 自定义 FastAPI 路由(高级)
|
|
|
|
|
|
|
|
|
|
|
|
当端点无法用 TOML 描述时(如文件上传),插件可暴露自定义 `APIRouter`。
|
|
|
|
|
|
|
|
|
|
|
|
`src/plugins/<name>/routes.py`:
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
from fastapi import APIRouter, UploadFile, File, Form
|
|
|
|
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/<prefix>", tags=["<name>"])
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/upload")
|
|
|
|
|
|
async def upload(
|
|
|
|
|
|
file: UploadFile = File(...),
|
|
|
|
|
|
description: str = Form(...),
|
|
|
|
|
|
):
|
|
|
|
|
|
# 处理文件上传
|
|
|
|
|
|
return {"status": "ok"}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
在 `__init__.py` 中暴露 `router`:
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
from .routes import router # 网关通过 getattr 自动发现
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
网关在 `load_workers()` 阶段自动检测并注册该 router,无需修改网关代码。
|
|
|
|
|
|
|
2026-07-27 14:28:07 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 为现有插件添加新端点
|
|
|
|
|
|
|
2026-08-05 18:19:01 +08:00
|
|
|
|
1. 创建 `config/workers/<name>/<new_op>.toml`
|
2026-08-20 16:51:00 +08:00
|
|
|
|
2. 运行 `uv run gen_models`
|
2026-08-05 18:19:01 +08:00
|
|
|
|
3. 创建 `router/<new_op>.py`,引用生成的模型
|
2026-07-27 14:28:07 +08:00
|
|
|
|
4. 更新 `router/__init__.py`
|
|
|
|
|
|
5. 重启服务
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-08-12 10:24:32 +08:00
|
|
|
|
## 共享对象(global_var)在两种路由中的使用
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
2026-08-12 10:24:32 +08:00
|
|
|
|
### 数据流
|
|
|
|
|
|
|
|
|
|
|
|
共享对象(Database、API client 等)在 `Plugin.on_init` 中初始化,通过 `set_var` 发布到 `global_var` 模块;两种路由的业务代码(模块级函数)在函数体内 `from .global_var import ...` 读取。
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
flowchart LR
|
|
|
|
|
|
P["Plugin.on_init<br/>self.db = ..."] -->|"self.set_var('db', self.db)"| GV["global_var 模块<br/>db / require()"]
|
|
|
|
|
|
R1["router/*.py<br/>@rpc_method 函数"] -->|"from ..global_var import require"| GV
|
|
|
|
|
|
R2["routes.py<br/>APIRouter 处理器"] -->|"from .global_var import db"| GV
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> **为什么需要 `global_var.py`**:`@rpc_method` 函数和 APIRouter 处理器都是模块级函数,拿不到 Plugin 实例,只能通过模块级容器读取;`global_var.py` 独立成模块,可避免与 `__init__.py` 循环导入。`set_var()` 内部会调用 `global_var.set_global_var()`(白名单校验)完成发布。
|
|
|
|
|
|
|
|
|
|
|
|
### 注入:on_init → global_var
|
|
|
|
|
|
|
|
|
|
|
|
`src/plugins/<name>/__init__.py`:
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
|
|
|
|
|
```python
|
2026-08-12 10:24:32 +08:00
|
|
|
|
class MyPlugin(Plugin):
|
|
|
|
|
|
type = "<name>"
|
|
|
|
|
|
db_name = "<name>.db"
|
|
|
|
|
|
|
|
|
|
|
|
async def on_init(self):
|
|
|
|
|
|
self.set_var("db", self.db) # 发布到 global_var 模块
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 路由一:TOML 驱动标准路由(@rpc_method)
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
2026-08-12 10:24:32 +08:00
|
|
|
|
`router/<op>.py`(请求经 Zenoh → Worker Queryable → 调用函数):
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
from chongming_worker.decorators import rpc_method
|
|
|
|
|
|
from ..models.pydantic import OpGetRequest, OpGetResponse
|
2026-08-05 18:19:01 +08:00
|
|
|
|
|
2026-08-12 10:24:32 +08:00
|
|
|
|
@rpc_method(subject="<name>.op", method="GET", response_model=OpGetResponse)
|
|
|
|
|
|
async def op(request: OpGetRequest) -> OpGetResponse:
|
|
|
|
|
|
from ..global_var import require
|
|
|
|
|
|
db = require("db") # 未初始化立即抛清晰错误
|
|
|
|
|
|
async with db.get_session() as session:
|
|
|
|
|
|
...
|
|
|
|
|
|
return OpGetResponse(...)
|
2026-07-27 14:28:07 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-12 10:24:32 +08:00
|
|
|
|
### 路由二:插件自定义路由(APIRouter)
|
|
|
|
|
|
|
|
|
|
|
|
`routes.py`(FastAPI 直接调用,适合 multipart 上传等 TOML 无法描述的端点):
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
|
|
|
|
|
```python
|
2026-08-12 10:24:32 +08:00
|
|
|
|
from fastapi import APIRouter, UploadFile, File
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
2026-08-12 10:24:32 +08:00
|
|
|
|
router = APIRouter(prefix="/<name>", tags=["<name>"])
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/upload")
|
|
|
|
|
|
async def upload(file: UploadFile = File(...)):
|
|
|
|
|
|
from .global_var import db
|
|
|
|
|
|
if db is None: # 容忍未初始化,优雅降级
|
|
|
|
|
|
return {"error": "db 未初始化"}
|
2026-07-27 14:28:07 +08:00
|
|
|
|
async with db.get_session() as session:
|
|
|
|
|
|
...
|
2026-08-12 10:24:32 +08:00
|
|
|
|
return {"ok": True}
|
2026-07-27 14:28:07 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-12 10:24:32 +08:00
|
|
|
|
在 `__init__.py` 顶部暴露给网关:`from .routes import router`(网关通过 `getattr(module, "router")` 自动发现)。
|
|
|
|
|
|
|
|
|
|
|
|
### 两种路由对比
|
|
|
|
|
|
|
|
|
|
|
|
| | 路由一 `@rpc_method` | 路由二 `APIRouter` |
|
|
|
|
|
|
| ---------- | ----------------------------------------- | --------------------------------------------- |
|
|
|
|
|
|
| 文件位置 | `router/<op>.py` | `routes.py` |
|
|
|
|
|
|
| 入口发现 | 装饰器注册 → Worker Queryable | `__init__.py` 暴露 `router` 属性 |
|
|
|
|
|
|
| 函数签名 | `(request: Pydantic模型) -> Pydantic模型` | FastAPI 风格(`Request`/`UploadFile`/`Form`) |
|
|
|
|
|
|
| 适用场景 | 标准 JSON / query 端点 | multipart 上传、手动解析报文 |
|
|
|
|
|
|
| 读共享对象 | `from ..global_var import require` | `from .global_var import db` |
|
|
|
|
|
|
| 请求路径 | HTTP → Zenoh → Worker 回调 | FastAPI 直接调用 |
|
|
|
|
|
|
|
|
|
|
|
|
### 经验
|
|
|
|
|
|
|
|
|
|
|
|
- **在函数体内 import**(不要放文件顶部):模块 import 时 `create_workers` 尚未执行、`global_var` 还是空;请求到达时变量才注入完成。
|
|
|
|
|
|
- **两种读取风格**:`require("db")`(必须有,未初始化立即报错)vs `from .global_var import db` + `if db is None`(允许优雅降级)。
|
|
|
|
|
|
- **参考实现**:`aibox_sophon_adapter/routes.py` 的 `_save_alarm_record`(自定义路由中 `from .global_var import db` + `async with db.get_session()`)。
|
|
|
|
|
|
|
2026-07-27 14:28:07 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
2026-08-05 18:19:01 +08:00
|
|
|
|
## @rpc_method 装饰器
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
@rpc_method(
|
|
|
|
|
|
subject="<plugin>.<operation>", # Zenoh 主题(必需)
|
2026-08-05 18:19:01 +08:00
|
|
|
|
method="GET", # HTTP 方法:GET / POST / MQTT
|
2026-07-27 14:28:07 +08:00
|
|
|
|
response_model=ResponseCls, # Pydantic 响应模型类
|
|
|
|
|
|
)
|
|
|
|
|
|
async def handler(request: RequestCls) -> ResponseCls:
|
|
|
|
|
|
...
|
2026-07-21 16:14:02 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-14 16:04:39 +08:00
|
|
|
|
| method | 参数来源 | 请求绑定 |
|
|
|
|
|
|
| ------ | ------------------ | ---------------------------- |
|
|
|
|
|
|
| `GET` | URL query string | TOML `params` 定义 |
|
|
|
|
|
|
| `POST` | JSON body | 自动绑定到 Pydantic 请求模型 |
|
|
|
|
|
|
| `MQTT` | MQTT payload JSON | 同 POST |
|
|
|
|
|
|
|
|
|
|
|
|
**请求信封协议**:网关统一把请求组装为 `{"meta": {"method", "user_info"}, "data": {...}}`
|
|
|
|
|
|
发给 Worker;运行时按 `meta.method` 分派到对应 `@rpc_method`,`data` 经请求模型校验后
|
|
|
|
|
|
传给处理器。MQTT 客户端发布消息时需按信封格式(见主 README「请求负载格式」),
|
|
|
|
|
|
MQTT 专用端点要显式 `"meta": {"method": "MQTT"}`。
|
2026-08-05 18:19:01 +08:00
|
|
|
|
|
|
|
|
|
|
- 设置 `internal = true` 后,端点不注册 HTTP 路由,仅可通过 Zenoh RPC 或 MQTT 内部调用
|
|
|
|
|
|
- MQTT 方法返回 `None` 时,Bridge 自动跳过响应发布
|
2026-07-27 14:28:07 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-08-14 16:04:39 +08:00
|
|
|
|
## 启动契约校验
|
|
|
|
|
|
|
|
|
|
|
|
服务启动时(`load_workers` 之后、注册路由之前),网关自动核对三方一致性:
|
|
|
|
|
|
|
|
|
|
|
|
| 检查项 | 策略 |
|
|
|
|
|
|
| ---------------------------- | --------------------------- |
|
|
|
|
|
|
| TOML HTTP 路由缺 `@rpc_method` | **硬失败**,启动直接终止 |
|
|
|
|
|
|
| `[MQTT]` / `internal` 路由缺注册 | 警告日志 |
|
|
|
|
|
|
| 已注册方法无 TOML 路由 | 警告日志(反向漂移检测) |
|
|
|
|
|
|
| params/response_model 缺生成模型 | 警告日志(gen_models 过期) |
|
|
|
|
|
|
| 生成模型未被任何路由引用 | 警告日志(孤儿模型) |
|
|
|
|
|
|
|
|
|
|
|
|
新增端点后如果启动报错「缺少对应的 @rpc_method 注册」,检查 TOML 的 `subject`/方法节
|
|
|
|
|
|
与 `router/<op>.py` 中的 `@rpc_method(subject=..., method=...)` 是否一致。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-07-27 14:28:07 +08:00
|
|
|
|
## 命名约定
|
|
|
|
|
|
|
2026-08-05 18:19:01 +08:00
|
|
|
|
| 概念 | 约定 | 示例 |
|
|
|
|
|
|
| ------------- | ------------------------------ | ---------------------------- |
|
|
|
|
|
|
| 插件名 | 小写下划线 | `aibox_sophon_adapter` |
|
|
|
|
|
|
| 操作名 | 小写下划线 | `board_ping`, `list_devices` |
|
|
|
|
|
|
| Zenoh subject | `<插件>.<操作>` | `example.add` |
|
|
|
|
|
|
| TOML 文件名 | 与操作名一致 | `add.toml` |
|
|
|
|
|
|
| Python 文件名 | 与操作名一致 | `add.py` |
|
|
|
|
|
|
| 模型类名 | `<操作><方法>Request/Response` | `AddGetRequest` |
|
|
|
|
|
|
|
2026-07-27 14:28:07 +08:00
|
|
|
|
| 概念 | 约定 | 示例 |
|
|
|
|
|
|
| ----------------- | --------------- | ----------------------------------- |
|
|
|
|
|
|
| 插件名 | 小写下划线 | `doubao`, `auth`, `example` |
|
|
|
|
|
|
| 操作名 | 小写下划线 | `analyze`, `add`, `change_password` |
|
|
|
|
|
|
| Zenoh subject | `<插件>.<操作>` | `example.add`, `doubao.analyze` |
|
|
|
|
|
|
| TOML 文件名 | 与操作名一致 | `analyze.toml`, `add.toml` |
|
|
|
|
|
|
| Python 路由文件名 | 与操作名一致 | `analyze.py`, `add.py` |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 现有插件
|
2026-07-21 16:14:02 +08:00
|
|
|
|
|
2026-07-27 14:28:07 +08:00
|
|
|
|
| 插件 | 说明 | 文档 |
|
|
|
|
|
|
| ---------------------------- | -------------------------------------- | ---------------- |
|
|
|
|
|
|
| [auth](auth/README.md) | 用户注册、登录、JWT 鉴权、用户信息管理 | 含 SQLite 数据库 |
|
|
|
|
|
|
| [doubao](doubao/README.md) | 火山引擎豆包视觉理解模型集成 | 第三方 API 调用 |
|
|
|
|
|
|
| [example](example/README.md) | 四则运算示例 + 访问计数器 | 入门参考 |
|