# 系统端设备与摄像头对接配置说明 本文是当前项目的部署配置手册,面向管理系统部署人员和边缘端联调人员。内容覆盖: - 自动 UDP 发现和手工 IP 接入; - MQTT 道闸控制、状态上报和图片 HTTP 上传; - 系统端将 RTSP/HTTP 摄像头流转换为浏览器可播放的 MJPEG; - 数据库、Windows 防火墙、启动、验证和故障排查。 边缘端协议字段和 Topic 的完整定义见 [`doc/边缘端自动与手工接入对接指南.md`](边缘端自动与手工接入对接指南.md)。本文只说明管理系统需要改哪些文件、填什么值。 ## 1. 配置文件位置 ### 1.1 开发运行 开发运行使用项目根目录的 `config.yaml`: ```text D:\lq\Smart Parking\lc_garage\config.yaml ``` 执行 `wails dev` 或 `go run` 时,程序优先从当前工作目录读取该文件。 ### 1.2 生产运行 生产程序使用启动参数 `-c` 指定配置;未指定时,程序从 Gin 运行模式对应的配置文件读取,并在当前目录找不到时尝试可执行文件目录。因此生产部署必须把配置文件放在 exe 同目录: ```text build\bin\smart-parking.exe build\bin\config.yaml ``` 也可以明确指定: ```powershell .\build\bin\smart-parking.exe -c .\build\bin\config.yaml ``` `internal/config.yaml` 是旧模板/服务端兼容配置,不是 Wails 桌面程序默认编辑的文件。除非启动参数明确指定它,否则不要在两个文件之间来回修改。 ### 1.3 代码配置结构 `internal/config/mqtt.go`、`internal/config/camera.go` 只是 YAML 映射结构,通常不需要填写。只有新增配置字段或协议变化时才修改 Go 代码;日常部署只改 `config.yaml`。 ## 2. 可直接复制的基础配置 下面配置适用于“管理系统和内嵌 MQTT Broker 在同一台 Windows 主机、边缘设备通过局域网连接、摄像头由系统端转换”的场景。把 `192.168.110.164` 换成边缘设备实际能访问的管理主机 IPv4 地址。 ```yaml system: db-type: sqlite oss-type: local router-prefix: "" addr: 8888 gate-simulator: false debug-routes: false sqlite: db-name: lc_garage path: "" mqtt: enabled: true embed-broker: true broker-listen: ":1883" broker: tcp://127.0.0.1:1883 advertised-host: "192.168.110.164" advertised-port: 1883 tls-enabled: false client-id: smart-parking username: "" password: "" camera: conversion-mode: system ffmpeg-path: ffmpeg mjpeg-fps: 8 jpeg-quality: 5 local: path: uploads/file store-path: uploads/file ``` 说明: | 配置 | 当前实现中的含义 | | --- | --- | | `system.addr` | 管理系统 HTTP 端口;图片上传地址会使用该端口。 | | `mqtt.broker-listen` | 内嵌 Broker 监听地址。`:1883` 表示监听本机所有 IPv4 接口。 | | `mqtt.broker` | 管理系统自身连接 Broker 的地址;内嵌 Broker 时保留 `127.0.0.1`。 | | `mqtt.advertised-host` | 下发给边缘端的 Broker 主机地址。内嵌 Broker 时必须是管理主机局域网 IP,不能是 `127.0.0.1`、`localhost` 或 `0.0.0.0`。 | | `mqtt.advertised-port` | 边缘端连接 Broker 的端口;必须与 `broker-listen` 的端口一致。 | | `mqtt.tls-enabled` | 下发给设备的 MQTT TLS 标志。当前内嵌 Broker 没有 TLS listener,因此内嵌模式必须填 `false`。 | | `mqtt.username/password` | 当前内嵌 Broker 允许匿名连接,可留空。若改用带认证的外部 Broker,系统连接和边缘端凭据必须由两端按同一版本协议配置。 | | `camera.conversion-mode` | `system` 表示管理端调用 FFmpeg;`edge` 表示使用数据库中的边缘播放地址。 | ## 3. MQTT Broker 配置 ### 3.1 内嵌 Broker(当前默认方案) 只需要在 `config.yaml` 填: ```yaml mqtt: enabled: true embed-broker: true broker-listen: ":1883" broker: tcp://127.0.0.1:1883 advertised-host: "192.168.110.164" advertised-port: 1883 tls-enabled: false ``` 设备收到配置后连接: ```text tcp://192.168.110.164:1883 ``` `advertised-host` 的填写规则: 1. 在管理主机执行 `ipconfig`,选择与边缘设备同一网络、且边缘设备可路由到的 IPv4 地址; 2. 不要填写浏览器地址栏中的 `localhost`; 3. 不要填写设备自身 IP; 4. 设备跨 VLAN 时填写管理网段的可达地址,并在三层防火墙放行 TCP 1883; 5. 管理主机 IP 变化时必须同步修改该值并重启/重新下发设备配置。 ### 3.2 外部 Broker 外部 Broker 场景下,`broker` 是管理系统连接地址,`advertised-host/port` 是边缘端连接地址,两者可以相同,也可以不同: ```yaml mqtt: enabled: true embed-broker: false broker-listen: ":1883" # 不启动内嵌 Broker 时该项不生效 broker: tcp://192.168.110.20:1883 advertised-host: "192.168.110.20" advertised-port: 1883 tls-enabled: false client-id: smart-parking username: parking-system password: "替换为外部 Broker 密码" ``` 外部 Broker 管理员还必须配置: - 允许管理主机和边缘设备访问对应 TCP 端口; - 为系统客户端和每台设备配置唯一 Client ID; - 授权系统订阅 `parking/lot/+/booth/+/channel/+/gate/state`、`gate/lwt`、`gate/cmd/ack` 和 `camera/event`; - 授权每台设备只发布自己的 `gate/state`、`gate/lwt`、`gate/cmd/ack`、`camera/event`,只订阅自己的 `gate/cmd`; - 若 Broker 使用 TLS,应使用外部 Broker 的 TLS 地址和证书策略。当前项目的内嵌 Broker 未配置证书,不能仅把 `tls-enabled` 改成 `true` 就获得 TLS。 ### 3.3 防火墙 在管理主机以管理员 PowerShell 执行(仅在确认端口未被其他服务占用时): ```powershell New-NetFirewallRule -DisplayName "Smart Parking HTTP 8888" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 8888 New-NetFirewallRule -DisplayName "Smart Parking MQTT 1883" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 1883 ``` 自动发现不需要管理系统固定 UDP 监听端口。系统从临时 UDP 端口向边缘端 `UDP/31001` 发送广播,边缘端响应到请求源 IP/源端口。跨 VLAN 或无线隔离时不要依赖该流程,改用手工 IP。 验证监听: ```powershell Get-NetTCPConnection -LocalPort 8888,1883 -State Listen Test-NetConnection 192.168.110.164 -Port 1883 ``` ## 4. 自动和手工接入时系统端需要做什么 ### 4.1 自动 UDP 发现 系统端没有额外的 YAML 开关。管理员在“设备接入”页面点击自动发现,系统会: 1. 向每个启用网卡的 IPv4 定向广播地址发送 `discovery.request.v1`,目标端口固定 `31001`,发送 3 次; 2. 接收并校验 `request_id`、`nonce`、设备永久 ID、设备签名、公钥指纹、响应来源 IP 和 HTTPS 配置地址; 3. 通过 HTTPS `GET /api/v1/identity` 再次读取设备身份和服务器证书指纹; 4. 管理员确认指纹/配对码后,选择停车场、岗亭、通道和方向; 5. 通过 HTTPS `POST /api/v1/provision` 下发 MQTT、业务路由和图片上传地址; 6. 等待设备发布 retained `gate.state.v1`,收到后才显示“在线”。 自动发现响应被丢弃时,重点看后端日志中的 `收到 UDP 发现响应` 和 `丢弃 UDP 发现响应`,常见原因是公钥指纹不匹配、报文来源 IP 与响应 `ip` 不一致、签名错误或 `config_url` 不是 HTTPS。 ### 4.2 手工 IP 接入 管理员在页面输入边缘设备 IP 和 HTTPS 端口(端口为空时后端使用 `8443`)。系统端请求: ```text GET https://<设备IP>:<端口>/api/v1/identity POST https://<设备IP>:<端口>/api/v1/pair # pairing_required=true 时 POST https://<设备IP>:<端口>/api/v1/provision ``` 手工 IP 是正式接入路径,适用于跨 VLAN、UDP 广播被禁、无线隔离、VPN 和不支持发现协议的设备。系统拒绝 `127.0.0.1`、`localhost`、`0.0.0.0`、IPv4 广播、IPv4/IPv6 组播、主机名和非法端口。 ### 4.3 管理 API(用于联调) 以下 API 均在 JWT、Casbin 和操作审计保护下,只有管理员权限(authority `888`)可调用。前端开发服务器地址为 `http://localhost:5173` 时,前缀仍是 `/api`: | 方法 | URL | 用途 | | --- | --- | --- | | `POST` | `/api/device-provisioning/discover` | 发起自动发现,可选 `{"broadcast_address":"192.168.110.255"}` | | `GET` | `/api/device-provisioning/discoveries` | 查询 10 分钟内的发现结果 | | `POST` | `/api/device-provisioning/manual/identity` | 手工 IP 读取身份,Body `{"ip":"192.168.110.218","port":8443}` | | `POST` | `/api/device-provisioning/{device_id}/identity` | 读取自动发现候选的 HTTPS 身份 | | `POST` | `/api/device-provisioning/{device_id}/verify` | Body `{"fingerprint":"SHA256:...","pairing_code":"..."}` | | `POST` | `/api/device-provisioning/{device_id}/provision` | 绑定并下发配置 | | `GET` | `/api/device-provisioning/{device_id}/status` | 查询 `provisioned/online/failed` 等状态 | 注意:`/api/device-provisioning/discoveries` 返回 404 时,通常是后端未重启、请求打到了错误端口,或请求 URL 少了 `/api` 代理前缀;先访问 `http://127.0.0.1:8888/health` 验证后端。 ## 5. 图片 HTTP 上传配置 设备绑定成功后,系统下发的图片地址由 `mqtt.advertised-host` 和 `system.addr` 生成: ```text http://:/device-images/upload ``` 当前版本按用户要求不使用上传令牌,依赖内网隔离和设备绑定校验。边缘端必须使用 `multipart/form-data`,字段为 `device_code`、`image_id`、可选 `event_id`/`direction` 和文件字段 `image`。单张图片限制 8 MB;图片上传成功后再发布 MQTT `camera.event.v1`,事件只引用 `image_id`。 因此必须放行管理主机 TCP `8888`,且 `mqtt.advertised-host` 不能填 `127.0.0.1`。图片上传路由是 `/device-images/upload`,不是 `/api/device-images/upload`。 ## 6. 摄像头配置(系统端转换) ### 6.1 `config.yaml` ```yaml camera: conversion-mode: system ffmpeg-path: C:\\ffmpeg\\bin\\ffmpeg.exe mjpeg-fps: 8 jpeg-quality: 5 ``` `ffmpeg-path` 可以填 `ffmpeg`(要求已加入 PATH),也可以填绝对路径。`mjpeg-fps` 建议 5-10;`jpeg-quality` 是 FFmpeg 的质量值,数值越小画质越好、带宽和 CPU 占用越高。 ### 6.2 安装和验证 FFmpeg ```powershell ffmpeg -version Test-Path 'C:\ffmpeg\bin\ffmpeg.exe' ``` 如果系统找不到 FFmpeg,把绝对路径写入 `ffmpeg-path`,修改后重启后端。摄像头原始地址必须能从管理主机访问,例如: ```text rtsp://用户名:密码@192.168.1.50:554/live ``` ### 6.3 `camera` 表字段 摄像头表由启动时 `AutoMigrate` 自动创建,不需要手工建表。当前没有独立摄像头配置页面,使用 SQLite 客户端写入/更新记录: | 字段 | 填写要求 | | --- | --- | | `device_code` | 摄像头唯一编码,例如 `CAM-IN-01`。 | | `device_name` | 页面显示名称。 | | `channel_id` | 与摄像头对应的道闸通道 ID;摄像头、道闸必须是同一通道。 | | `ip_address`/`port` | 摄像头管理地址和 RTSP/HTTP 端口。 | | `username`/`password` | 原始流凭据,仅后端使用,不返回前端。 | | `stream_protocol` | 系统转换填 `system-mjpeg`;边缘转换填 `webrtc` 或 `hls`。 | | `source_url` | 系统转换时填 RTSP/HTTP 原始地址。 | | `stream_url` | 系统转换留空;边缘转换填 WHEP/HLS 播放地址。 | | `is_active` | `1` 启用,`0` 停用。 | | `status` | `online`/`offline`,仅用于页面状态显示。 | 查询停车场、岗亭、通道 ID: ```sql SELECT id, lot_code, lot_name FROM parking_lot WHERE deleted_at IS NULL; SELECT id, booth_code, booth_name, parking_lot_id FROM booth WHERE deleted_at IS NULL; SELECT id, channel_code, channel_name, parking_lot_id, booth_id, direction FROM channel WHERE deleted_at IS NULL; ``` 插入一台系统转换摄像头(把 `8` 换成实际 `channel_id`): ```sql INSERT INTO camera (created_at, updated_at, device_code, device_name, channel_id, ip_address, port, username, password, channel, status, is_active, stream_protocol, source_url, stream_url, snapshot_url, description) VALUES (datetime('now'), datetime('now'), 'CAM-IN-01', '入口摄像头', 8, '192.168.1.50', 554, 'admin', '替换为摄像头密码', 1, 'offline', 1, 'system-mjpeg', 'rtsp://admin:替换为摄像头密码@192.168.1.50:554/live', '', '', '系统端转换'); ``` 更新已有记录: ```sql UPDATE camera SET channel_id = 8, stream_protocol = 'system-mjpeg', source_url = 'rtsp://admin:替换为摄像头密码@192.168.1.50:554/live', stream_url = '', is_active = 1, updated_at = datetime('now') WHERE device_code = 'CAM-IN-01'; ``` 系统转换时,接口 `GET /api/parking/camera/streams` 返回短期播放地址: ```text /api/parking/camera/stream/CAM-IN-01/ ``` 浏览器以 `` 显示 MJPEG;每次页面重新加载会获得新的短期令牌。后端断开浏览器连接后自动结束 FFmpeg 进程。 ### 6.4 切换边缘端转换 ```yaml camera: conversion-mode: edge ``` 然后更新数据库: ```sql UPDATE camera SET stream_protocol = 'webrtc', source_url = '', stream_url = 'https://边缘端或媒体网关地址/实时播放接口', updated_at = datetime('now') WHERE device_code = 'CAM-IN-01'; ``` 系统端当前不负责把 RTSP 转成 WHEP;在 `system` 模式下统一输出 MJPEG,未来切换到 `edge` 模式时由边缘端/媒体网关提供 WHEP 或 HLS 地址。 ## 7. SQLite 数据库位置和设备绑定 默认 `sqlite.path` 为空,数据库实际位置为: ```text C:\Users\<当前 Windows 用户>\AppData\Roaming\smart-parking\lc_garage.db ``` 启动时会自动执行 `AutoMigrate`,包括 `uhf_reader`、`device_discoveries`、`device_images` 和 `camera` 表。 检查已绑定设备: ```powershell sqlite3 "$env:APPDATA\smart-parking\lc_garage.db" ` "SELECT id, device_code, device_id, connect_type, channel_id, provision_status, status, deleted_at FROM uhf_reader ORDER BY id;" ``` MQTT 设备必须满足:`connect_type='mqtt'`、`is_active=1`、`channel_id` 指向正确通道。`device_code` 为数据库全局唯一字段;软删除记录仍占用唯一索引,重新绑定同一设备时系统会恢复原记录,不要直接重复 `INSERT`。 ## 8. 启动、重启和验证 项目已提供统一管理脚本: ```powershell cd 'D:\lq\Smart Parking\lc_garage' .\scripts\manage-project.ps1 start -Mode split .\scripts\manage-project.ps1 status .\scripts\manage-project.ps1 restart -Mode split .\scripts\manage-project.ps1 stop ``` `split` 模式后端是 `8888`、前端是 `5173`;Wails 桌面开发模式使用: ```powershell .\scripts\manage-project.ps1 start -Mode wails ``` 基础验证: ```powershell Invoke-WebRequest http://127.0.0.1:8888/health Test-NetConnection -Port 1883 ``` 管理员登录后验证自动/手工接入 API,再查询状态接口。日志重点关注: - `内嵌 MQTT broker 已启动`、`MQTT 连接已恢复`; - `收到 UDP 发现响应`、`丢弃 UDP 发现响应`; - `HTTPS 身份读取失败`、`证书指纹不匹配`; - `设备配置下发失败`; - `边缘设备图片上传结果`、`边缘设备图片上传失败`; - `收到车辆识别 MQTT 事件`、`车辆识别事件图片引用校验成功`。 ## 9. 常见问题 | 现象 | 检查项 | | --- | --- | | `advertised-host` 配置错误 | 必须是边缘端可达的单播 IP;修改后重启并重新下发。 | | 设备收到配置但 MQTT 不上线 | 检查 TCP 1883、防火墙、Broker 地址,以及 `tls-enabled` 是否与实际 listener 一致。内嵌 Broker 当前填 `false`。 | | 自动发现无结果 | 检查边缘端 UDP `31001`、Windows 网络类型/防火墙;跨 VLAN、无线隔离时使用手工 IP。 | | 日志提示公钥指纹不匹配 | `public_key_fingerprint` 必须是 `signing_public_key` 原始 32 字节 Ed25519 公钥的 SHA-256。 | | 日志提示 IP 与报文来源不一致 | UDP 响应 JSON 的 `ip` 必须等于实际发送报文的源 IP。 | | HTTPS 身份读取失败 | 检查设备 HTTPS 端口(默认 `8443`)、TLS 1.2+、设备路由和证书指纹。 | | 绑定时报 `UNIQUE constraint failed` | 用上面的 `Unscoped` 查询检查同 `device_code` 的软删除行;当前代码会恢复同一永久 `device_id` 的历史行,其他设备不能复用编码。 | | 图片上传 404 | 使用 `/device-images/upload`,不是 `/api/device-images/upload`;确认后端端口为 `8888` 且 `local.store-path`/对象存储可写。 | | 摄像头没有画面 | 检查 `is_active=1`、`channel_id`、FFmpeg 路径和 `source_url`;RTSP 地址必须能从管理主机访问。 | ## 10. 部署前检查清单 - [ ] 根目录或 exe 同目录的 `config.yaml` 已填写 MQTT、系统端口和摄像头模式。 - [ ] `mqtt.advertised-host` 是边缘设备可达的管理主机或外部 Broker IP。 - [ ] 内嵌 Broker 使用 `tls-enabled: false`;外部 TLS Broker 已由运维完成证书和监听配置。 - [ ] TCP `8888`、内嵌 MQTT TCP `1883` 和设备 HTTPS `8443` 路由/防火墙已放行。 - [ ] 自动发现设备监听 UDP `31001`;不满足网络条件时已准备手工 IP 接入。 - [ ] 每个设备有稳定的永久 `device_id`、证书指纹和配对码。 - [ ] 设备、道闸和摄像头绑定同一 `channel_id`。 - [ ] 边缘端先上传图片,再发布带 `image_id` 的 MQTT 事件。 - [ ] 设备收到 `provision` 后能连接 MQTT,并发布 retained `gate.state.v1`。 - [ ] 已用日志和状态 API 验证“配置已保存”和“设备在线”两个阶段。