93b27cf227
Co-Authored-By: Claude <noreply@anthropic.com>
OPC 智能园区后端(FastAPI + MQTT)
独立后端服务:所有数据通过 REST API 提供,页面/媒体控制通过 MQTT 下发,媒体资源由后端统一存储与返回。
架构
┌──────────────┐ REST(全部数据/媒体/设置) ┌────────────────┐
│ Tauri 大屏 │ ◄────────────────────────── │ FastAPI :10085 │
│ (React 前端) │ └───────┬────────┘
│ │ MQTT 订阅 opc/display/command │ MQTT 发布
│ 账号: dpm │ ◄────────────────────────────────────│ 账号: dpmserver
└──────┬───────┘ MQTT Broker (EMQX) │
│ 192.168.1.3:1883 │
└───────────────────────────────────────────────┘
- 数据:
GET /api/dashboard/snapshot(每 2.2s 刷新,模拟引擎在后端运行)等 - 控制:
POST /api/display/command→ MQTTopc/display/command→ 所有大屏订阅同步响应 - 媒体:上传/列表/播放列表/
/file静态返回(图片/视频统一由后端托管) - AI:通义千问(DashScope)+ 工具调用(切页/控制/卡片/通知,经 MQTT 广播);语音识别用阿里云 paraformer
启动
cd backend
uv sync # 安装依赖(fastapi/uvicorn/paho-mqtt/dashscope/jinja2 等)
cp .env.example .env # 按需修改(含 DashScope Key、MQTT 账号)
uv run python main.py # 或 .venv/bin/python main.py
启动后:API 与前端页面均在 http://0.0.0.0:10085(浏览器直接打开即大屏)。
环境变量(backend/.env)
| 变量 | 说明 |
|---|---|
DPM_HOST / DPM_PORT(或 FASTAPI_HOST/FASTAPI_PORT) |
监听地址,默认 0.0.0.0:10085 |
DPM_MQTT_HOST / DPM_MQTT_PORT |
MQTT broker 地址,默认读 MQTT_BROKER_HOST/PORT |
MQTT_USERNAME / MQTT_PASSWORD |
服务端发布账号:dpmserver / 123456 |
DASHSCOPE_API_KEY |
阿里云 DashScope(LLM + 语音识别) |
DPM_LLM_MODEL |
默认 qwen-plus |
DPM_ASR_MODEL |
默认 paraformer-realtime-v2 |
前端连接配置在项目根
.env.local(已 gitignore):VITE_API_BASE、VITE_MQTT_URL、VITE_MQTT_USERNAME=dpm、VITE_MQTT_PASSWORD=123456。
MQTT 主题
| 主题 | 方向 | 说明 |
|---|---|---|
opc/display/command |
后端→前端 | {cmd_id, ts, action, params},action: navigate / navigate_rel / play / pause / next / prev / alert / show_card / set_mode |
opc/dashboard/tick |
后端→前端 | 数据快照({ts, snapshot}) |
opc/display/ack |
前端→后端 | 指令回执(预留) |
控制示例
# 切换页面(数据大屏 / 数字孪生 / AI 助手 / 媒体轮播)
curl -X POST http://localhost:10085/api/display/command \
-H "Content-Type: application/json" \
-d '{"action":"navigate","params":{"page":"/twin"}}'
# 媒体控制
curl -X POST http://localhost:10085/api/display/command \
-d '{"action":"next","params":{}}'
# 大屏通知
curl -X POST http://localhost:10085/api/display/command \
-d '{"action":"alert","params":{"title":"园区通知","content":"..."}}'
关键接口
GET /api/dashboard/snapshot— 大屏全量数据快照GET /api/park/companies·GET /api/park/zones— 企业分布POST /api/ai/chat— AI 对话({messages:[{role,content}]}→{reply, tools, mqtt_published})POST /api/ai/asr— 语音识别(multipart 上传录音 +format参数)GET/POST /api/playlist·POST /upload·GET /media— 媒体与播放列表POST /api/display/command— 管理端控制指令(转 MQTT 广播)GET /api/events— SSE 兼容通道(MQTT 不可用时的前端回退)
说明
- 大屏数据由后端模拟引擎产生(
sim_engine.py,与原前端parkData.js逻辑一致);前端离线时也有本地兜底 - 媒体文件存放于
backend/media/,/file/...直接返回 - 安全:
backend/.env与根.env.local已加入.gitignore,含阿里云密钥与 MQTT 凭据,勿提交
Docker 部署(推荐)
一键构建并启动 后端 + EMQX Broker:
# 1) (可选)配置部署参数
export MQTT_PUBLIC_HOST=192.168.1.50 # 展播端可访问的服务器局域网 IP(Broker WebSocket)
export EMQX_USER=dpmserver
export EMQX_PASS=你的密码
export DASHSCOPE_API_KEY=sk-xxx # AI/语音密钥(未配置时 AI 走本地规则引擎)
export DPM_S2S_ENABLED=1 # 实时语音对话开关
# 2) 构建并启动
docker compose up -d --build
# 3) 验证
curl http://192.168.1.9:10085/api/health # {"ok":true,...}
docker compose logs -f backend
- 端口:
10085(API/后台/媒体)、8765(s2s 语音)、1883/8083/18083(EMQX) - 数据卷:
dpm_media(媒体)、dpm_knowledge(知识库 park.md,可热更新)、dpm_data(设置/播放列表) - 展播端(Tauri 大屏)启动时经
GET /api/config自动获取 MQTT WebSocket 地址 - 常用命令:
docker compose down停止;docker compose up -d --build backend仅重建后端;docker compose ps查看状态
单独构建镜像:
docker build -f backend/Dockerfile -t dpm-backend .