本文是当前项目的部署配置手册,面向管理系统部署人员和边缘端联调人员。内容覆盖:
边缘端协议字段和 Topic 的完整定义见 doc/边缘端自动与手工接入对接指南.md。本文只说明管理系统需要改哪些文件、填什么值。
开发运行使用项目根目录的 config.yaml:
D:\lq\Smart Parking\lc_garage\config.yaml
执行 wails dev 或 go run 时,程序优先从当前工作目录读取该文件。
生产程序使用启动参数 -c 指定配置;未指定时,程序从 Gin 运行模式对应的配置文件读取,并在当前目录找不到时尝试可执行文件目录。因此生产部署必须把配置文件放在 exe 同目录:
build\bin\smart-parking.exe
build\bin\config.yaml
也可以明确指定:
.\build\bin\smart-parking.exe -c .\build\bin\config.yaml
internal/config.yaml 是旧模板/服务端兼容配置,不是 Wails 桌面程序默认编辑的文件。除非启动参数明确指定它,否则不要在两个文件之间来回修改。
internal/config/mqtt.go、internal/config/camera.go 只是 YAML 映射结构,通常不需要填写。只有新增配置字段或协议变化时才修改 Go 代码;日常部署只改 config.yaml。
下面配置适用于“管理系统和内嵌 MQTT Broker 在同一台 Windows 主机、边缘设备通过局域网连接、摄像头由系统端转换”的场景。把 192.168.110.164 换成边缘设备实际能访问的管理主机 IPv4 地址。
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 表示使用数据库中的边缘播放地址。 |
只需要在 config.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
设备收到配置后连接:
tcp://192.168.110.164:1883
advertised-host 的填写规则:
ipconfig,选择与边缘设备同一网络、且边缘设备可路由到的 IPv4 地址;localhost;外部 Broker 场景下,broker 是管理系统连接地址,advertised-host/port 是边缘端连接地址,两者可以相同,也可以不同:
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 管理员还必须配置:
parking/lot/+/booth/+/channel/+/gate/state、gate/lwt、gate/cmd/ack 和 camera/event;gate/state、gate/lwt、gate/cmd/ack、camera/event,只订阅自己的 gate/cmd;tls-enabled 改成 true 就获得 TLS。在管理主机以管理员 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。
验证监听:
Get-NetTCPConnection -LocalPort 8888,1883 -State Listen
Test-NetConnection 192.168.110.164 -Port 1883
系统端没有额外的 YAML 开关。管理员在“设备接入”页面点击自动发现,系统会:
discovery.request.v1,目标端口固定 31001,发送 3 次;request_id、nonce、设备永久 ID、设备签名、公钥指纹、响应来源 IP 和 HTTPS 配置地址;GET /api/v1/identity 再次读取设备身份和服务器证书指纹;POST /api/v1/provision 下发 MQTT、业务路由和图片上传地址;gate.state.v1,收到后才显示“在线”。自动发现响应被丢弃时,重点看后端日志中的 收到 UDP 发现响应 和 丢弃 UDP 发现响应,常见原因是公钥指纹不匹配、报文来源 IP 与响应 ip 不一致、签名错误或 config_url 不是 HTTPS。
管理员在页面输入边缘设备 IP 和 HTTPS 端口(端口为空时后端使用 8443)。系统端请求:
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 组播、主机名和非法端口。
以下 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 验证后端。
设备绑定成功后,系统下发的图片地址由 mqtt.advertised-host 和 system.addr 生成:
http://<advertised-host>:<system.addr>/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。
config.yamlcamera:
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 占用越高。
ffmpeg -version
Test-Path 'C:\ffmpeg\bin\ffmpeg.exe'
如果系统找不到 FFmpeg,把绝对路径写入 ffmpeg-path,修改后重启后端。摄像头原始地址必须能从管理主机访问,例如:
rtsp://用户名:密码@192.168.1.50:554/live
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:
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):
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', '', '', '系统端转换');
更新已有记录:
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 返回短期播放地址:
/api/parking/camera/stream/CAM-IN-01/<token>
浏览器以 <img> 显示 MJPEG;每次页面重新加载会获得新的短期令牌。后端断开浏览器连接后自动结束 FFmpeg 进程。
camera:
conversion-mode: edge
然后更新数据库:
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 地址。
默认 sqlite.path 为空,数据库实际位置为:
C:\Users\<当前 Windows 用户>\AppData\Roaming\smart-parking\lc_garage.db
启动时会自动执行 AutoMigrate,包括 uhf_reader、device_discoveries、device_images 和 camera 表。
检查已绑定设备:
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。
项目已提供统一管理脚本:
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 桌面开发模式使用:
.\scripts\manage-project.ps1 start -Mode wails
基础验证:
Invoke-WebRequest http://127.0.0.1:8888/health
Test-NetConnection <advertised-host> -Port 1883
管理员登录后验证自动/手工接入 API,再查询状态接口。日志重点关注:
内嵌 MQTT broker 已启动、MQTT 连接已恢复;收到 UDP 发现响应、丢弃 UDP 发现响应;HTTPS 身份读取失败、证书指纹不匹配;设备配置下发失败;边缘设备图片上传结果、边缘设备图片上传失败;收到车辆识别 MQTT 事件、车辆识别事件图片引用校验成功。| 现象 | 检查项 |
|---|---|
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 地址必须能从管理主机访问。 |
config.yaml 已填写 MQTT、系统端口和摄像头模式。mqtt.advertised-host 是边缘设备可达的管理主机或外部 Broker IP。tls-enabled: false;外部 TLS Broker 已由运维完成证书和监听配置。8888、内嵌 MQTT TCP 1883 和设备 HTTPS 8443 路由/防火墙已放行。31001;不满足网络条件时已准备手工 IP 接入。device_id、证书指纹和配对码。channel_id。image_id 的 MQTT 事件。provision 后能连接 MQTT,并发布 retained gate.state.v1。