22 KiB
Chongming-Zenoh
基于 FastAPI + Zenoh + MQTT 的配置驱动微服务网关平台。
用 TOML 定义 RPC 端点,自动生成 Pydantic 模型与 FastAPI 路由,同时暴露 HTTP 与 MQTT 双通道;底层经 Zenoh 做 Worker 服务发现与 RPC 调用。插件(Worker)可独立开发、 按副本数水平扩展,既可跑在网关进程内(inline),也可作为独立进程/容器部署(external)。
核心特性
| 特性 | 说明 |
|---|---|
| 双通道接入 | HTTP(FastAPI,自动 OpenAPI 文档)与 MQTT(内嵌或外部 Broker) |
| 配置驱动路由 | 扫描 config/workers/*/*.toml 自动注册 HTTP 路由与 Pydantic 模型 |
| Zenoh RPC | 基于 liveliness 的 Worker 发现与调用:负载均衡、超时重试、实例定向 |
| 混合部署 | inline(网关进程内)与 external(独立进程)按插件粒度共存 |
| Worker 框架 | @rpc_method 装饰器 + Plugin 基类生命周期 + 启动契约校验 |
| 流式端点 | @rpc_method 返回生成器即得 SSE(text/event-stream)逐块输出 |
| Worker 运维 | REST/前端管理:副本重启、配置热更新、扩缩容(全局对称 / 逐实例) |
| 鉴权与安全 | 网关内置登录与 JWT 签发(Rust 实现);Rust Argon2id 密码哈希 |
| 插件脚手架 | gen_plugin 生成统一骨架,gen_models 生成模型,build_plugin 打包进程插件 |
| 交付打包 | PyInstaller / Nuitka 一键打独立可执行文件;.mbank 模块银行支持插件加密与热替换 |
| Vue 3 前端 | 管理台(Worker 管理 + 登录)与自动生成的 API 客户端 |
快速开始
环境要求
- Python >= 3.12、uv
- Rust 工具链(编译
jwt/password扩展) - Node.js >= 22(前端开发)
- ffmpeg(可选,
stream_manager需要)
首次克隆
git clone http://124.71.68.6:3000/chongming/chongming-core.git chongming-zenoh
cd chongming-zenoh
git clone http://124.71.68.6:3000/chongming/chongming-plugins.git src/plugins
git clone http://124.71.68.6:3000/chongming/chongming-config.git config
git clone http://124.71.68.6:3000/chongming/chongming-web.git src/chongming-web
uv sync # 网关(chongming-gateway)与 utils/* 工具包按 pyproject 源自动安装
安装与启动
uv sync # 同步依赖(自动编译 Rust 扩展)
uv run serve # 启动服务(开发模式,热重载)
启动后:
| 通道 | 地址 | 说明 |
|---|---|---|
| HTTP | http://localhost:8000 |
REST API |
| Swagger | http://localhost:8000/docs |
OpenAPI 交互文档(仅调试模式开启) |
| MQTT | mqtt://localhost:1883 |
内嵌 Broker(use_embedded_broker 控制) |
服务器可切换(uv run gunicorn / uv run granian,或设 APP_SERVER 环境变量),
启动参数统一读 config.toml 的 [<env>.server] 段。
前端开发
cd src/chongming-web
npm install
npm run dev # http://localhost:5173
项目结构
chongming-zenoh/
├── config.toml # 应用主配置(环境、服务器、MQTT、缓存等)
├── pyproject.toml # 宿主项目依赖与脚本入口
├── plugins_deps.toml # 内置插件专属依赖(打包收集用)
├── config/ # 配置子仓库(chongming-config)
│ ├── zenoh_config.json5 # Zenoh 会话配置
│ └── workers/ # 插件注册 + 路由定义(TOML)
│ ├── <type>/config.toml # 插件注册与运行时配置
│ └── <type>/<op>.toml # 端点定义(每个端点一个文件)
├── src/
│ ├── plugins/ # 内置插件源码(chongming-plugins 子仓库)
│ └── chongming-web/ # Vue 3 前端(chongming-web 子仓库)
├── utils/ # 独立工具包(各自独立仓库,uv 安装)
│ ├── gateway/ # 网关本体(chongming-gateway,可独立发包)
│ ├── cli/ # chongming-cli:build / gen_plugin / build_plugin ...
│ ├── worker/ # Worker 框架(chongming-worker)
│ └── ... # config / database / jwt / password / request / ...
├── workers/ # 进程插件本地开发目录(不入库;生产在独立容器)
├── public/ # 可执行文件打包骨架(main.py / server.py / 打包资源)
├── resources/ # 模块银行产物(plugins.mbank 等)
├── data/ logs/ # 数据库、上传文件、日志
└── docs/ # 专题文档(见文末「更多文档」)
使用指南
插件:两种运行模式
按插件粒度在 config/workers/<type>/config.toml 的 [workers] 节声明:
load_mode = "inline" # 默认:插件在网关进程内加载
load_mode = "external" # 独立进程运行,经 Zenoh 注册路由
| 模式 | 源码位置 | 加载方式 | 适用 |
|---|---|---|---|
| inline | src/plugins/<name>/ |
网关进程内 import,TOML 盘扫生成路由 | 与网关同生命周期的业务插件 |
| external | workers/<name>/(自包含项目) |
独立进程,经 Zenoh 注册路由 | 独立扩缩、独立依赖/语言、跨容器部署 |
- external 插件与网关零导入关系:只依赖
chongming-workerSDK + zenoh,代码结构与 inline 完全一致;本地调试cd workers/<name> && uv run chongming-worker --type <name>。 - 两种模式可同时存在(混合部署),网关对二者的路由挂载、服务发现与运维指令一致。
开发指南:内置插件开发 | 进程插件开发(external,从开发到部署) | Rust 插件
端点定义与路由
| 节类型 | HTTP | OpenAPI | MQTT | 模型命名 |
|---|---|---|---|---|
[GET] |
✅ | ✅ | ✅ | XxxGetRequest / XxxGetResponse |
[POST] |
✅ | ✅ | ✅ | XxxPostRequest / XxxPostResponse |
[MQTT] |
❌ | ❌ | ✅ | XxxMqttRequest |
方式一:TOML 动态路由(标准 JSON 端点)——config/workers/<type>/*.toml:
# config/workers/example/add.toml
[GET]
summary = "加法运算"
params = ["a: float", "b: float"]
[GET.response_model]
result = { type = "float", required = true }
方式二:插件自定义路由(无法用 TOML 描述时,如 multipart 上传)——插件暴露
APIRouter,网关自动发现:
# src/plugins/<name>/routes.py
from fastapi import APIRouter, UploadFile, File
router = APIRouter(prefix="/<prefix>", tags=["<worker_type>"])
@router.post("/upload")
async def upload(file: UploadFile = File(...)):
...
约定:插件
__init__.py导入该router实例,网关通过getattr(module, "router", None)发现。
流式端点(SSE)
方法节声明 stream = true,@rpc_method(..., stream=True) 返回生成器即可逐块输出:
[POST]
summary = "模拟 AI 流式对话(SSE,逐 token 输出)"
stream = true
timeout = 300.0 # 流总寿命上限(秒);流式端点必须配大
stream_idle_timeout = 10.0 # 连续无数据块的最大间隔(秒)
params = ["prompt: str", "tokens: int"]
curl -N -X POST localhost:8000/api/v1/calc/stream_chat \
-H 'Content-Type: application/json' -d '{"prompt":"你好","tokens":3}'
# data: {"token":"你好·1","index":0}
# data: {"token":"你好·2","index":1}
# data: {"done":true}
- 请求与普通端点一致(含鉴权);响应
text/event-stream,每块一个data: {json}事件。 - 错误语义:无可用 worker 时 HTTP 503;流中异常/空闲超时以
data: {"error": "..."}收尾; 客户端断开即终止迭代。 - 经 nginx 反代时需
proxy_buffering off;,否则失去逐块即时性。 - 流式能力由
chongming-worker提供,需 venv 中为支持stream的版本 (本地改框架后按「仓库结构与开发工作流」刷新 pin)。
Worker 管理与扩缩容
网关提供 REST 管理端点与 Vue 管理页(需超级管理员),inline 与 external 插件共用同一套
管理协议(Zenoh worker/<type>/<worker_id>/__admin__)。
| 方法 | 路径 | 说明 |
|---|---|---|
GET |
/api/v1/admin/workers |
盘点全部副本(inline 本地注册 + external liveliness 发现) |
POST |
/api/v1/admin/workers/restart |
重启单个副本(worker_type / worker_id) |
POST |
/api/v1/admin/workers/<type>/stop |
停止单个副本 / 某插件全部副本 |
GET POST |
/api/v1/admin/workers/<type>/config |
读取 / 更新 [workers.config](成功即下发并重启副本) |
POST |
/api/v1/admin/workers/<type>/scale |
调整该类型副本数(全局对称分摊) |
GET |
/api/v1/admin/workers/<type>/instances |
按进程实例分组查看副本(供逐实例扩缩) |
POST |
/api/v1/admin/workers/<type>/scale_instance |
逐实例精细扩缩(只调指定实例) |
要点:
- 扩缩容持久化到插件配置;缩容走优雅排空(先摘除服务发现隔绝新请求,等在途请求完成后退出)。
- 多网关共享同一
[cache]时,副本数按在线网关自动分摊,任一台操作即全局生效(需redis或共享卷sqlite;CHONGMING_GATEWAY_ID用于区分同宿主多网关)。 - 无状态插件默认逐请求随机负载均衡;需要固定到某个进程实例的插件(本地状态/设备连接)
可用实例定向调用:HTTP 头
X-Chongming-Instance: <实例id>,或 MQTT 信封顶层target_instance;实例离线时严格报错,不回退。
curl -H 'X-Chongming-Instance: edge-01' http://127.0.0.1:8000/api/v1/demo/state
细节(管理指令语义、多实例分摊与自愈、领导选举、逐实例扩缩)见 Worker 管理与多实例运维。
MQTT 通道
| 方向 | Topic | 说明 |
|---|---|---|
| 请求 | chongming/<type>/<op> |
MQTT → Worker |
| 响应 | chongming/<type>/<op>/response |
Worker → MQTT(无响应时跳过) |
[development.mqtt]
enabled = true
host = "127.0.0.1" # 内嵌 Broker 时为监听地址;外部 Broker 时为对方地址
port = 1883
use_embedded_broker = true # true(默认)网关内嵌 amqtt Broker;false 连接外部 Broker
# username = "chongming" # 可选:Broker 认证(内嵌/外部均生效)
# password = "change-me"
请求负载为信封结构(控制字段与业务数据分离),非信封格式会被丢弃并告警:
{
"meta": { "method": "GET", "timeout": 30, "cacheable": false },
"data": { "a": 3, "b": 5 }
}
自定义订阅主题:把外部 MQTT 主题桥接到插件,推荐写在插件自己的配置里:
# config/workers/aibox_sophon_adapter/config.toml
[[workers.mqtt_topics]]
topic = "box/aibox_sophon/board_ping" # 支持 + / # 通配符
subject = "aibox_sophon_adapter.board_ping" # 可选,{1} {2}... 对应捕获值
response_topic = "/board_ping/response" # 可选,默认 "<实际主题>/response"
- 多条自定义主题按顺序匹配,先匹配者优先;内置
chongming/...接口最后兜底。 - 全局
[<env>.mqtt].custom_topics可作通用兜底(不推荐,优先用插件配置)。
多实例部署要点(细节见 Worker 管理与多实例运维):
- 单机:内嵌 Broker + 本地
sqlite缓存即可。 - 多实例:外部 Broker(必须) + 共享
[cache]后端(redis或共享卷sqlite)。 共享缓存是收敛前提:MQTT Bridge 全局单例、副本数分摊、inline 领导选举都跑在它上面。
手动联调:
uv run serve # 终端 1:启动服务
uv run python test/test_mqtt_e2e.py # 终端 2:端到端
uv run python test/publisher.py example add '{"a":3,"b":5}' # 单次调用
uv run python test/subscriber.py # 监听所有响应
配置
- 应用配置:根
config.toml(环境[default].env→[development]/[production]选段;服务器、安全、MQTT、缓存等)。 - 插件注册:
config/workers/<type>/config.toml的[workers]节——加载模式、count副本数、router_prefix、[workers.config]运行时配置。 - 路由定义:
config/workers/<type>/*.toml,每个端点一个文件。 - Zenoh:
config/zenoh_config.json5。
字段速查(完整说明见 配置说明):
| 字段 | 位置 | 说明 |
|---|---|---|
count |
<插件>/config.toml |
Worker 副本数([workers] 节) |
load_mode |
<插件>/config.toml |
inline(默认)/ external |
router_prefix |
<插件>/config.toml |
API 路由前缀 |
defaults.timeout / defaults.cacheable |
<插件>/config.toml |
端点默认超时与是否缓存 |
params |
<操作>.toml |
参数列表("name: type") |
auth_required |
<操作>.toml |
是否需要 JWT 鉴权 |
internal |
<操作>.toml |
仅限内部 RPC,不注册 HTTP 路由 |
cacheable |
<操作>.toml |
端点是否可缓存 |
stream / stream_idle_timeout |
<操作>.toml |
流式输出(SSE)与其空闲超时 |
launch_worker_count |
根 config.toml [cache] |
副本数/运行时配置以 toml(默认)还是 cache 为准 |
打包与部署
构建可执行文件
uv run build pyinstaller # PyInstaller 打包
uv run build nuitka # Nuitka 打包(Linux 需宿主 patchelf,已在 dev 依赖)
uv run build --encrypt --license # 对外分发:加密模块(PyArmor 混淆)+ 注入授权公钥
uv run build --only mbank # 只重新生成 plugins.mbank(插件更新只需替换该文件)
uv run build --only mbank,prepare,vue # 只跑指定步骤
构建流程:模块打包(plugins.mbank)→ 资源复制 → PyInstaller/Nuitka 打包 → 数据库初始化
→ 前端编译 → VFS 挂载。--only 可选步骤:mbank / prepare / obfuscate / pack /
clean / license_guard / vue。
网关更新与插件更新的路径不同:
- 网关本体(
chongming-gateway包)是普通依赖,由 PyInstaller/Nuitka 从 venv 收集进 exe → 改网关必须重新uv run build; - 插件代码在
plugins.mbank里 →uv run build --only mbank后替换该文件即可热更新。
打包产物的依赖收集
exe 内的依赖来自三处,新增第三方包时对号入座:
| 依赖归属 | 声明位置 |
|---|---|
网关本体(fastapi / granian / chongming_* 等) |
chongming-gateway 包自己的 pyproject.toml(消费者自动获得,打包沿静态导入收集) |
动态导入、静态分析看不到的(granian 子模块、amqtt 插件、sqlite_vfs.contrib、uvloop)+ 骨架自身 |
根 config.toml 的 [pyinstaller_build_import] |
内置插件专属(如 doubao 的 openai / bytedance) |
根 plugins_deps.toml,按插件分节 |
新增插件依赖时,venv 安装清单(根 pyproject.toml)与 plugins_deps.toml 需同步维护;
依赖变更后必须重跑 pack(--only mbank 带不动第三方依赖)。
--encrypt / --license 默认都关(不加参数即内部调试产物:明文模块、不校验授权);
对外分发必须同时加这两个开关。
进程插件打包与容器部署
uv run build_plugin demo # → dist/demo.zip(开发目录即产物)
uv run migrate_plugin <name> # 内置插件一键迁移为进程插件
zip 根即容器工作目录:插件代码 + config/ + pyproject.toml + start.sh
(uv sync + chongming-worker --type <name>);容器内 unzip <name>.zip -d /app && cd /app && ./start.sh。
生产部署时进程插件与网关分别打包到不同容器:网关容器装 chongming-gateway,
插件容器装插件代码 + chongming-worker。
仓库结构与开发工作流
本仓库(chongming-core)是宿主项目:骨架(public/)、打包配置、构建与交付;
业务资产分布在独立仓库中,通过 uv 依赖与子目录 clone 组合:
| 目录 / 包 | 仓库 | 说明 |
|---|---|---|
utils/gateway/ |
chongming-gateway |
网关本体(独立包,发布到私有源;本仓按发布版依赖) |
utils/template/ |
项目模板(独立维护) | 新项目模板:python utils/template/init.py <目录> 初始化出「骨架 + 插件 + 配置」的网关项目 |
utils/<name>/ |
chongming-utils-* |
工具包(config / worker / jwt / cache / ...) |
utils/cli/ |
chongming-utils-cli |
chongming-cli:build / gen_plugin / build_plugin / migrate_plugin |
src/plugins/ |
chongming-plugins |
内置插件源码 |
config/ |
chongming-config |
应用与 Worker 路由配置 |
src/chongming-web/ |
chongming-web |
Vue 3 前端 |
修改 utils/<name> / utils/gateway(可发布包):
- 在包目录内提交并 push,
uv build后用 twine 发布新版本到私有源 - 回主仓库
uv lock --upgrade-package <包名>刷新 pin,uv sync - 主仓库提交
uv.lock
⚠️ 打包陷阱:
uv run build会先按锁文件同步环境,本地uv pip install -e utils/<name>的可编辑安装会被替换回发布版——「改了包但产物行为没变」几乎都是它。打包前确认 venv 指向目标版本:python -c "import chongming_worker, inspect; print(inspect.getsourcefile(chongming_worker))"。
修改 src/plugins/ / config/ / src/chongming-web/:在对应子仓库内提交并 push,
回主仓库启动验证。
发布前总检查:
- 所有子仓库
git status干净且已 push;uv.lock与各源一致 - 打包前确认 venv 内网关与工具包为目标版本(见上方打包陷阱)
- 配置中无硬编码凭据——密钥走环境变量:
VOLC_ACCESSKEY(豆包 Ark Key)、AIBOX_MQTT_URI(AI Box 外部 Broker URI) - 相关测试通过(见
test/,如uv run python test/test_mqtt_e2e.py)
内置内容一览
内置插件(chongming-plugins 仓库,inline)
| 插件 | HTTP 前缀 | MQTT 主题前缀 | 说明 |
|---|---|---|---|
| auth | /auth |
auth.* |
用户管理:注册/资料/改密(登录与 token 签发内置网关) |
| auth_oauth | /auth/oauth |
auth_oauth.* |
身份源示例:GitHub OAuth + 手机验证码登录 |
| doubao | /doubao |
doubao.* |
豆包视觉理解(支持 AICC 安全通道) |
| aibox_sophon_adapter | /aibox |
aibox_sophon_adapter.* |
AI Box 设备心跳、离线检测、告警、视频上传 |
| example | /calc |
example.* |
四则运算 + 心跳演示(含 MQTT 端点示例) |
| demo(进程插件示例) | — | — | workers/demo/(本地自包含项目,external 模式) |
工具包(uv 依赖,各自独立仓库)
| 包 | 路径 | 语言 | 说明 |
|---|---|---|---|
| chongming-gateway | utils/gateway/ |
Python | 网关本体(FastAPI + Zenoh) |
| chongming-auth | utils/auth/ |
Python | auth 域共享契约:Users 模型、建库/种子、登录校验与 JWT 签发 |
| chongming-config | utils/config/ |
Python | TOML 配置加载(TypedDict 类型安全) |
| chongming-database | utils/database/ |
Python | aiosqlite + SQLModel 异步数据库封装 |
| chongming-jwt | utils/jwt/ |
Rust + PyO3 | JWT 编解码(HS/RS 系列) |
| chongming-password | utils/password/ |
Rust + PyO3 | Argon2id 密码哈希与验证 |
| chongming-request | utils/request/ |
Python | Zenoh RPC 直连客户端 |
| chongming-worker | utils/worker/ |
Python | Worker 框架:装饰器、运行时、契约校验、独立进程入口 |
| chongming-stream-manager | utils/stream_manager/ |
Python | ffmpeg + MediaMTX RTSP 流代理管理 |
| bytedance | utils/bytedance/ |
Python | 字节跳动 AICC 安全通信 SDK |
常用命令
uv run serve # 启动开发服务
uv run granian # 或 uv run gunicorn(换服务器,参数仍读 config.toml)
uv run gen_plugin <name> # 生成插件骨架(--process 生成进程插件项目)
uv run gen_models # 按 TOML 生成 Pydantic 模型
uv run build [pyinstaller|nuitka] # 打包可执行文件
uv run build_plugin <name> # 打包进程插件 → dist/<name>.zip
uv run migrate_plugin <name> # 内置插件迁移为进程插件
uv run chongming-worker --type <name> # 独立 Worker 进程(external 插件本地调试/容器入口)
cd utils/gateway && uv build # 构建网关包 wheel(发版用)
python utils/template/init.py ../new-proj # 用模板初始化一个新项目