feat: 拆出 FastAPI 独立后端 + MQTT 控制 + 阿里云 AI

后端(backend/,FastAPI :10085):
- 全部数据 REST 接口:dashboard 快照 / 企业分区 / 播放列表 / 媒体 / 设置
- MQTT 控制通道:opc/display/command(切页/播放/卡片/通知),
  管理端 POST /api/display/command → MQTT 广播 → 所有大屏同步响应
- 阿里云 DashScope:通义千问 LLM + 函数调用(工具经 MQTT 广播),
  paraformer-realtime-v2 语音识别 /api/ai/asr
- 媒体资源统一由后端存储返回(上传/列表/静态服务)
- SSE /api/events 保留作 MQTT 不可用时的兼容回退

前端:
- config.js + .env.local 配置后端地址与 MQTT 账号(前端 dpm / 服务端 dpmserver)
- mqtt.js 客户端 + useMqttControl(MQTT 驱动切页/媒体/卡片/通知)
- DpmOverlays 全局覆盖层(通知 toast + 企业/分区/总览卡片)
- useParkSim 改为后端 API 数据源(离线回退本地模拟)
- AiChatPanel:对话走后端 LLM(工具调用),语音走本地录音 + 后端 ASR
- MediaScreen:媒体控制走 MQTT(保留 SSE 回退),修复 useEffect TDZ

Rust:lib.rs 移除内嵌 HTTP 服务器,只保留薄壳(窗口/权限/自启/npc 隧道)
安全:backend/.env、.env.local、media、data.json 已 gitignore
This commit is contained in:
Pine
2026-08-17 21:25:46 +08:00
parent 7f1c2ac4f0
commit 856ff88440
34 changed files with 3639 additions and 378 deletions
+87
View File
@@ -0,0 +1,87 @@
# 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` → MQTT `opc/display/command` → 所有大屏订阅同步响应
- **媒体**:上传/列表/播放列表/`/file` 静态返回(图片/视频统一由后端托管)
- **AI**:通义千问(DashScope)+ 工具调用(切页/控制/卡片/通知,经 MQTT 广播);语音识别用阿里云 paraformer
## 启动
```bash
cd backend
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
cp .env.example .env # 按需修改(含 DashScope Key、MQTT 账号)
.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` | 阿里云 DashScopeLLM + 语音识别) |
| `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` | 前端→后端 | 指令回执(预留) |
## 控制示例
```bash
# 切换页面(数据大屏 / 数字孪生 / 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 凭据,勿提交