车牌识别系统(PlateRecApp)是一款基于 HyperLPR3 的实时车牌识别应用,运行在树莓派 5 (aarch64) 上,支持:
| 项目 | 要求 |
|---|---|
| 硬件 | 树莓派 5 8GB(推荐) |
| 系统 | Debian 13 (trixie) / Ubuntu 24.04 aarch64 |
| 依赖 | HyperLPR3 SDK、FFmpeg、libcurl、OpenSSL、cJSON、Mosquitto、Nginx |
| 内网穿透 | frpc(通过 systemctl 管理) |
| 协议分流 | Nginx stream + ssl_preread(TCP 层 HTTP/HTTPS 自动检测分流) |
外网客户端 frps 服务器 本机(树莓派5)
───────── ───────── ─────────────
┌─ TLS → nginx stream → C++ HTTPS:8080 (透传)
https://mqtt.zhonjin.com:40963/ ─TCP─→ discover.zhonjin.com:9443 ─TCP─→ frpc ─→ nginx:40963 (stream+ssl_preread)
http://mqtt.zhonjin.com:40963/ ─TCP─→ ↑ ─TCP─→ frpc ─→└─ HTTP → nginx:8081 (301→HTTPS)
https://mqtt.zhonjin.com:40964/ ─TCP─→ ↑ ─TCP─→ frpc ─→ nginx:8082 (HTTPS代理) → ttyd:7681
| 路径 | 说明 |
|---|---|
| PlateRecApp | 主程序二进制 |
| config.ini | 主配置文件 |
| config.txt | MD5 校验配置 |
| hyperlpr3/ | HyperLPR3 SDK 模型文件 |
| assets/web/ | Web 页面文件(HTML/CSS/JS) |
| data/ | 数据库目录(auth.db、upload_records.db、system_metrics.db) |
| photos/ | 抓拍图片存储目录 |
| scripts/ | 运维脚本(sudoers 配置、密码重置等) |
tar xzf fix24_vXX.tar.gzcd fix24/build && cmake .. && make -j4/opt/openAI/project/003.PlateRecAPP/)cp -r assets/web/ /安装目录/assets/web/sudo bash scripts/setup_frpc_sudoers.shsudo apt install libnginx-mod-streamconfig.ini 配置(参考下文各节)sudo systemctl restart nginx && sudo systemctl restart PlateRecAppPlateRecApp 服务文件路径:/etc/systemd/system/PlateRecApp.service
Unit] Description=PlateRecApp License Plate Recognition System After=network.target frpc.service [Service] User=stevenroc Group=stevenroc WorkingDirectory=/opt/openAI/project/003.PlateRecAPP ExecStartPre=/bin/sleep 10 ExecStart=/opt/openAI/project/003.PlateRecAPP/PlateRecApp TimeoutStartSec=30 TimeoutStopSec=10 Restart=on-failure RestartSec=5 StandardOutput=append:/opt/openAI/project/003.PlateRecAPP/PlateRecApp.log LimitNOFILE=1048576 [Install] WantedBy=multi-user.target
如忘记管理员密码,运行:python3 reset_passwords.py
安装 stream 模块并部署配置:
# 安装 stream 模块(必须) sudo apt install -y libnginx-mod-stream # 验证模块 nginx -V 2>&1 | grep stream # 替换 nginx.conf(详见第 12 章) sudo cp nginx-stream.conf /etc/nginx/nginx.conf # 验证配置 sudo nginx -t # 启动/重载 sudo systemctl enable nginx sudo systemctl restart nginx
配置文件路径:<安装目录>/config.ini,ini 格式,section 和 key 均不区分大小写。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| project_name | string | - | 项目名称 |
| point_number | string | - | 站点编号 |
| throughway | string | - | 通道名称 |
| rtsp_url_front_in | string | - | 前侧进场摄像头 RTSP 地址 |
| rtsp_url_front_out | string | - | 前侧出场摄像头 RTSP 地址 |
| rtsp_url_side_in | string | - | 侧面进场摄像头 RTSP 地址 |
| rtsp_url_side_out | string | - | 侧面出场摄像头 RTSP 地址 |
| app_api | string | - | 云端上传 API 地址 |
| app_key | string | - | API 认证 Key |
| app_secret | string | - | API 认证 Secret |
| DEBUG_LOG | int | 0 | 调试日志开关(0=关闭,1=开启) |
| TestFlag | int | 0 | 测试模式开关 |
| wType | int | 0 | 称重类型(0=禁用,1=启用) |
| TIME_WINDOW | int | 5 | 识别时间窗口(分钟),同一车牌在窗口内不重复上传 |
| MAX_PHOTO_GROUPS | int | 100 | 最大照片组数缓存 |
| in_out_interval | int | 5 | ⚠️ 单位是分钟!交替锁定间隔(同一车牌进出最短时间),内部转为秒存储 |
| PLATE_CONFIDENCE_THRESHOLD | float | 0.7 | 车牌识别置信度阈值(0~1),低于此值不上传 |
| PLATE_LOG_THRESHOLD | float | 0.5 | 日志记录置信度阈值 |
| AlternatingMerge | int | 0 | 交替合并开关(0=关闭,1=开启) |
| hw_decode_mode | string | auto | 硬件解码模式:auto/soft/drm |
| hw_decode_device | string | /dev/dri/renderD128 | 硬件解码设备路径 |
| rtsp_transport | string | tcp | RTSP 传输协议:tcp/udp |
| PhotoMaxCapacityMB | int | 1024 | 照片目录最大容量(MB),超限自动清理旧照片 |
| LogRetentionDays | int | 30 | 日志保留天数 |
| 参数 | 说明 |
|---|---|
| ip | Web 服务监听地址(含端口),如 0.0.0.0:8080。v2 方案下 PlateRecApp 提供 HTTPS 服务(SSL 自管理),nginx stream 透传 TLS 流量到此处 |
| 参数 | 默认值 | 说明 |
|---|---|---|
| MQTT_HOST | - | MQTT Broker 地址(填写后自动启用 MQTT) |
| MQTT_PORT | 1883 | MQTT Broker 端口 |
| MQTT_USER | - | MQTT 用户名 |
| MQTT_PASS | - | MQTT 密码 |
| MQTT_TOPIC | - | 推送主题 |
| MQTT_CLIENT_ID | - | 客户端 ID |
| PLATE_COLOR | - | 车牌颜色标识(随 MQTT 消息上报) |
| VEHICLE_TYPE | - | 车辆类型标识(随 MQTT 消息上报) |
| 参数 | 默认值 | 说明 |
|---|---|---|
| enabled | 1 | 认证开关(0=关闭,1=开启) |
| db_path | data/auth.db | 认证数据库路径 |
| session_idle_timeout | 1800 | Session 空闲超时(秒) |
| session_max_timeout | 28800 | Session 最大超时(秒) |
| session_remember_timeout | 604800 | 记住登录超时(秒) |
| max_failed_attempts | 5 | 最大登录失败次数 |
| lockout_duration | 900 | 锁定时长(秒) |
| 参数 | 默认值 | 说明 |
|---|---|---|
| enabled | 1 | HTTPS 开关。v2 方案下必须设为 1(C++ 应用直接处理 TLS) |
| cert_path | ssl/plate_fullchain.pem | SSL 证书路径(相对于安装目录) |
| key_path | ssl/plate_privkey.pem | SSL 私钥路径(相对于安装目录) |
enabled 必须为 1。| 参数 | 默认值 | 说明 |
|---|---|---|
| monitor_interval | 2 | 监控采集间隔(秒),delta 法计算 CPU 使用率 |
| temp_alert_threshold | 70 | 温度告警阈值(°C) |
| cpu_alert_threshold | 90 | CPU 告警阈值(%) |
| db_path | data/system_metrics.db | 监控数据库路径 |
| history_retention_days | 7 | 原始数据保留天数 |
| aggregation_retention_days | 365 | 聚合数据保留天数 |
| flush_time | 23:20 | 每日定时刷盘时间(HH:MM) |
| 参数 | 默认值 | 说明 |
|---|---|---|
| config_path | - | frpc.toml 配置文件路径 |
| protected_tunnels | - | 受保护隧道名称(逗号分隔) |
| auto_rollback | 1 | 重启失败自动回滚(0/1) |
| admin_addr | 127.0.0.1 | frpc Admin API 地址 |
| admin_port | 7400 | frpc Admin API 端口 |
| admin_user | - | frpc Admin 用户名 |
| admin_password | - | frpc Admin 密码 |
| 参数 | 默认值 | 说明 |
|---|---|---|
| enabled | 1 | 内存缓冲模式(1=内存缓冲,0=直写文件) |
| log_file | PlateRecApp.log | 日志文件路径 |
| buffer_size_mb | 20 | 内存缓冲区上限(MB) |
| flush_time | 23:20 | 每日定时刷盘时间(HH:MM) |
| retention_days | 30 | 日志文件保留天数 |
| redirect_stdout | 1 | 捕获 stdout/stderr(0/1) |
| auto_flush_on_exit | 1 | 退出时立即刷盘(0/1) |
| 参数 | 说明 |
|---|---|
| APP_ID | 飞书应用 App ID |
| APP_SECRET | 飞书应用 App Secret |
| CHAT_ID | 飞书群聊 ID |
| TIMEOUT_SECOND | 请求超时时间(秒) |
| WarningSigns | 抑制紧急报警(1=抑制,0=正常) |
| 参数 | 默认值 | 说明 |
|---|---|---|
| flagWeight | 0 | 称重系统开关(0/1) |
| weight_server_ip | - | 称重服务器 IP |
| weight_server_port | - | 称重服务器端口 |
| weight_threshold_in | 0 | 进场称重阈值(吨) |
| weight_threshold_out | 0 | 出场称重阈值(吨) |
| default_entry_weight | 0 | 默认进场重量 |
| default_exit_weight | 0 | 默认出场重量 |
| weight_detection_time | 5000 | 称重检测时间(毫秒) |
| tcp_connect_timeout | 3000 | TCP 连接超时(毫秒) |
| MAX_WEIGHT | 100 | 最大称重值(吨) |
| STABLE_SAMPLE_COUNT | 5 | 稳定采样次数 |
| STABLE_THRESHOLD_KG | 50 | 稳定判定阈值(公斤) |
| CANDIDATE_DATA_COUNT | 3 | 候选数据次数 |
| MAX_UPLOAD_RETRIES | 3 | 上传最大重试次数 |
| RETRY_DELAY_MS | 1000 | 重试延迟(毫秒) |
| 参数 | 默认值 | 说明 |
|---|---|---|
| ttyd_port | 7681 | ttyd Web 终端端口 |
| ttyd_credential | - | ttyd 登录凭据(user:password) |
| ttyd_max_clients | 5 | ttyd 最大客户端数 |
| ssh_keys_path | - | SSH 公钥存储路径 |
标准 RTSP 地址格式:
rtsp://用户名:密码@摄像头IP:554/stream
| 参数 | 方向 | 说明 |
|---|---|---|
| rtsp_url_front_in | 前→进 | 前方摄像头,捕捉进场车辆 |
| rtsp_url_front_out | 前→出 | 前方摄像头,捕捉出场车辆 |
| rtsp_url_side_in | 侧→进 | 侧面摄像头,捕捉进场车辆 |
| rtsp_url_side_out | 侧→出 | 侧面摄像头,捕捉出场车辆 |
| 模式 | 值 | 说明 |
|---|---|---|
| 自动 | auto | 优先硬件解码,不可用则回退软件 |
| 软件解码 | soft | CPU 解码,兼容性最好 |
| DRM 硬件 | drm | DRM 硬件加速(树莓派5推荐) |
hw_decode_mode=drm,hw_decode_device=/dev/dri/renderD128,rtsp_transport=tcpPLATE_CONFIDENCE_THRESHOLD 则丢弃TIME_WINDOW 分钟内不重复上传in_out_interval 分钟则锁定app_apiPlateJPG/ 目录下,文件名格式:车牌号_时间戳_方向.jpg(如 沪FQ7108_1787365677_side_out.jpg)PlateJPG/ 目录下所有 .jpg 文件总大小超过 PhotoMaxCapacityMB(默认 1024MB)时,按文件修改时间从旧到新逐个删除,直到总大小降至阈值以下[照片清理] PlateJPG/ 当前 1156 MB / 1024 MB 阈值,共 8234 个文件 [照片清理] 删除 1523 张照片(按时间从旧到新),释放 156 MB,当前 1000 MB [照片清理] 同步清空DB路径字段: 2847 个字段已更新
照片被物理删除后,系统同步清空 upload_records 表中对应的 lo_photo_path / hi_photo_path 字段(设为 NULL):
<img onerror> 也会自动替换为「已清理」db_purge_missing_photo_paths),修复历史遗留数据config.ini 中 PhotoMaxCapacityMB 的值并重启服务。例如设为 2048 表示允许 2GB 照片存储。在 [mqtt] 段中填写 MQTT_HOST 即自动启用 MQTT 推送。
[mqtt] MQTT_HOST=mqtt.example.com MQTT_PORT=1883 MQTT_USER=username MQTT_PASS=password MQTT_TOPIC=plate/recognition MQTT_CLIENT_ID=plate_rec_station_01
称重系统通过 TCP 协议与称重地磅通信,车牌识别成功后自动读取称重数据并关联上传。
STABLE_SAMPLE_COUNT 次读数变化 < STABLE_THRESHOLD_KG 公斤,视为稳定CANDIDATE_DATA_COUNT 次读数的中值作为最终重量MAX_WEIGHT 吨视为异常,丢弃weight_threshold_in:进场最低重量(吨),低于此值可能表示空车weight_threshold_out:出场最低重量(吨)default_entry_weight:无法获取称重数据时的默认进场重量default_exit_weight:无法获取称重数据时的默认出场重量weight_detection_time:称重检测时间(毫秒,默认 5000)tcp_connect_timeout:TCP 连接超时(毫秒,默认 3000)MAX_UPLOAD_RETRIES:上传最大重试次数(默认 3)RETRY_DELAY_MS:重试延迟(毫秒,默认 1000)[config] 段填写坐标值| 参数 | 说明 |
|---|---|
| roi_in_x/y/w/h | 进场 ROI 区域坐标和尺寸(像素) |
| roi_in_enabled | 1=启用进场 ROI |
| roi_out_x/y/w/h | 出场 ROI 区域坐标和尺寸 |
| roi_out_enabled | 1=启用出场 ROI |
| roi_debug_enabled | 1=在抓拍图上绘制 ROI 框(调试用) |
防止同一车牌短时间内进出频繁触发(如车辆在门口调头)。同一车牌从"进"切换到"出"必须间隔 in_out_interval 分钟以上。
| 参数 | 默认值 | 说明 |
|---|---|---|
| in_out_interval | 5 | 交替锁定间隔(⚠️ 单位分钟),小于此间隔的进/出切换会被锁定 |
| AlternatingMerge | 0 | 交替合并开关 |
in_out_interval 单位是分钟,不是秒!填 5 表示 5 分钟 = 300 秒。| 角色 | Role值 | 可访问页面 |
|---|---|---|
| 普通用户 | 0 | 首页、视频、称重、锁定管理 |
| 管理员 | 1 | 普通用户 + 系统配置 |
| 超级管理员 | 2 | 所有页面(含系统监控、frpc管理、SSH密钥、日志、帮助) |
| 用户名 | 密码 | 角色 |
|---|---|---|
| admin | zhongjin188A | 超级管理员 |
| zhonjin | zhonjin | 普通用户 |
frpc 配置文件路径由 config.ini 的 [frpc] config_path 指定,通常为安装目录下的 frpc.toml。
使用 nginx stream + ssl_preread 方案,只需一个公网端口 40963 即可同时处理 HTTP 和 HTTPS:
# frpc.toml — v2 方案(nginx stream 分流) serverAddr = "discover.zhonjin.com" serverPort = 9443 auth.method = "token" auth.token = "你的frps认证Token" transport.tls.enable = true clientID = "ssd_40960" # frpc Admin HTTP API webServer.addr = "127.0.0.1" webServer.port = 7400 webServer.user = "admin" webServer.password = "你的管理密码" # 隧道1:主服务(外网40963→本机nginx:40963,stream自动分流HTTP/HTTPS) [[proxies]] name = "plate_web" type = "tcp" localIP = "127.0.0.1" localPort = 40963 # ← 转发到 nginx stream,不是 C++ 的 8080 remotePort = 40963 # 隧道2:ttyd终端(外网40964→本机nginx:8082→ttyd:7681) [[proxies]] name = "ttyd_web" type = "tcp" localIP = "127.0.0.1" localPort = 8082 # ← nginx HTTPS 反向代理(SSL 终止 + WebSocket) remotePort = 40964
| 隧道名 | 外网端口 | 本机端口 | 用途 |
|---|---|---|---|
| plate_web | 40963 | nginx stream:40963 | HTTP/HTTPS 合一,stream 自动分流 |
| ttyd_web | 40964 | nginx HTTPS:8082 | Web 终端(nginx SSL→ttyd:7681) |
HTTP 流量: 公网:40963 → frpc → nginx:40963 (stream) → $ssl_preread="" → 127.0.0.1:8081 (nginx HTTP) → 301 重定向到 HTTPS HTTPS 流量: 公网:40963 → frpc → nginx:40963 (stream) → $ssl_preread≠"" → 127.0.0.1:8080 (C++ HTTPS) → 直接处理 Web SSH: 公网:40964 → frpc → nginx:8082 (HTTPS反向代理, SSL终止) → 127.0.0.1:7681 (ttyd HTTP)
https://mqtt.zhonjin.com:40963/。| 参数 | 说明 |
|---|---|
| serverAddr | frps 服务器域名或 IP |
| serverPort | frps 服务端口(默认 7000,本项目用 9443) |
| auth.method | 认证方式,固定为 token |
| auth.token | 与 frps 约定的认证 Token |
| webServer.addr/port | frpc Admin API 监听地址,PlateRecApp 通过此接口获取隧道状态 |
| webServer.user/password | Admin API 认证凭据(需与 config.ini [frpc] 段一致) |
| [[proxies]] name | 隧道唯一名称,PlateRecApp Web 界面按名称识别 |
| [[proxies]] type | 隧道类型,本项目全部使用 tcp(透传 TCP 流) |
| [[proxies]] localPort | 本机目标端口,frpc 将外网流量转发到此端口 |
| [[proxies]] remotePort | frps 服务器暴露的外网端口,客户端通过此端口访问 |
在 config.ini [frpc] 段配置受保护隧道,防止 Web 界面误删关键隧道:
protected_tunnels = plate_web,ttyd_web
protected_tunnels 字段。# 查看 frpc 状态 systemctl status frpc # 重启 frpc sudo systemctl restart frpc # 查看 frpc 日志 tail -f /var/log/frpc.log # 通过 Admin API 查看隧道状态 curl -u admin:密码 http://127.0.0.1:7400/api/status/tcp
v2 方案使用 nginx 的 stream 模块 + ssl_preread 功能,在 TCP 层面检测流量类型并自动分流:
文件路径:/etc/nginx/nginx.conf(替换整个文件)
# 加载 stream 动态模块
load_module modules/ngx_stream_module.so;
events {
worker_connections 1024;
}
# ============================================================
# stream 块 — TCP 层协议分流(在 http 块外面)
# ============================================================
stream {
# 根据 ssl_preread 检测结果选择后端
# $ssl_preread_protocol 的可能值:
# "" → HTTP 流量(非 TLS)
# "TLSv1.2" → TLS 1.2
# "TLSv1.3" → TLS 1.3
map $ssl_preread_protocol $backend {
"" http_backend; # HTTP → nginx HTTP 8081
default tls_backend; # TLS → C++ HTTPS 8080
}
# TLS 后端 — C++ HTTPS 服务
upstream tls_backend {
server 127.0.0.1:8080;
}
# HTTP 后端 — nginx HTTP 301 重定向
upstream http_backend {
server 127.0.0.1:8081;
}
# 主服务器 — 监听公网端口
server {
listen 40963;
proxy_pass $backend;
ssl_preread on; # 启用 SSL 预读检测协议
}
}
# ============================================================
# http 块 — HTTP → HTTPS 301 重定向
# ============================================================
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# HTTP → HTTPS 301 重定向
server {
listen 8081;
server_name _;
return 301 https://$host:40963$request_uri;
}
# ============================================================
# ttyd Web 终端 HTTPS 反向代理(端口 8082)
# ============================================================
# frpc 隧道:外网 40964 → nginx:8082 → ttyd:7681
# nginx 做 SSL 终止,再通过 HTTP 代理到 ttyd(含 WebSocket 支持)
server {
listen 8082 ssl;
server_name _;
ssl_certificate /etc/nginx/ssl/plate_fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/plate_privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5:!RC4;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL_Ttyd:5m;
ssl_session_timeout 1h;
location / {
proxy_pass http://127.0.0.1:7681;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket 支持(ttyd 核心通信方式)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# ttyd 长连接超时
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}
}
| 组件 | 作用 | 说明 |
|---|---|---|
| stream 块 | TCP 层代理 | 与 http 块平级,不处理 HTTP 协议,只做 TCP 转发 |
| ssl_preread on | SSL 预读 | 在 TLS 握手阶段读取 ClientHello,获取协议版本,不解密 |
| map $ssl_preread_protocol | 协议分流 | 空字符串 = HTTP,非空 = TLS |
| upstream tls_backend | TLS 后端 | 透传到 C++ 应用(8080),C++ 自己处理 TLS 握手 |
| upstream http_backend | HTTP 后端 | 转发到 nginx HTTP 8081,返回 301 重定向 |
| server 8082 ssl | ttyd HTTPS 代理 | SSL 终止后代理到 ttyd:7681,支持 WebSocket |
sudo apt install -y libnginx-mod-streamls -l /usr/lib/nginx/modules/ngx_stream_module.sosudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.baksudo cp nginx-stream.conf /etc/nginx/nginx.confsudo rm -f /etc/nginx/sites-enabled/defaultsudo nginx -tsudo systemctl enable nginx && sudo systemctl restart nginx| 项目 | 要求 |
|---|---|
| nginx 版本 | ≥ 1.11.5(支持 stream 模块) |
| stream 模块 | libnginx-mod-stream(Debian)或 nginx-mod-stream(CentOS) |
| C++ 应用 | 必须启用 HTTPS,监听 127.0.0.1:8080 |
| SSL 证书 | C++ 应用自行管理(config.ini 的 [ssl] 段) |
# 检查端口监听 ss -tlnp | grep -E '40963|8080|8081' # 预期输出: # LISTEN 0.0.0.0:40963 nginx (stream) # LISTEN 127.0.0.1:8081 nginx (http 301) # LISTEN 0.0.0.0:8080 PlateRecApp (HTTPS) # 测试 HTTP 重定向(应返回 301) curl -I http://localhost:40963/ # 预期:HTTP/1.1 301 Moved Permanently # Location: https://localhost:40963/ # 测试 HTTPS(应返回 200) curl -kI https://localhost:40963/ # 预期:HTTP/1.1 200 OK # 浏览器测试(清除缓存或用无痕模式) # 访问 http://mqtt.zhonjin.com:40963/ → 自动跳转到 https://mqtt.zhonjin.com:40963/
| 端口 | 服务 | 说明 |
|---|---|---|
| 40963 | nginx stream | 公网入口,TLS/HTTP 分流 |
| 8081 | nginx http | HTTP 301 重定向(仅内部) |
| 8080 | C++ HTTPS | PlateRecApp Web 服务(SSL 自管理) |
| 40964 | nginx HTTPS→ttyd | Web 终端(nginx:8082 SSL终止→ttyd:7681) |
| 8082 | nginx HTTPS 代理 | ttyd Web 终端 HTTPS 反向代理(仅内部) |
| 7400 | frpc Admin | 隧道状态查询(仅 127.0.0.1) |
[ssl] 段配置。nginx 不再需要 SSL 证书。[ssl] enabled=1 cert_path=ssl/plate_fullchain.pem key_path=ssl/plate_privkey.pem
ssl/ 子目录下(或任意自定义路径),不再需要 /etc/nginx/ssl/。本项目使用 frpc 内网穿透,无公网 80 端口,因此不能使用传统的 HTTP-01 验证。推荐使用 DNS-01 验证,通过阿里云 DNS API 自动完成,不需要任何端口。
| 验证方式 | 端口要求 | 通配符 | 自动续期 | 本项目适用 |
|---|---|---|---|---|
| HTTP-01 (standalone) | 公网 80 | ❌ | ✅ | ❌ 80 端口不可用 |
| TLS-ALPN-01 | 公网 443 | ❌ | ✅ | ❌ 443 端口不可用 |
| DNS-01 (dns-aliyun) | 无 | ✅ | ✅ | ✅ 推荐 |
| 手动 DNS | 无 | ✅ | ❌ 手动 | ⚠️ 每 90 天手动操作 |
# 1. 安装 certbot + 阿里云 DNS 插件
sudo apt update
sudo apt install -y certbot python3-pip
sudo pip3 install certbot-dns-aliyun
# 2. 配置阿里云 AccessKey(RAM 子账号,需 AliyunDNSFullAccess 权限)
sudo mkdir -p /etc/letsencrypt
sudo vim /etc/letsencrypt/aliyun.ini
# 内容:
# dns_aliyun_access_key = 你的AccessKeyID
# dns_aliyun_access_key_secret = 你的AccessKeySecret
sudo chmod 600 /etc/letsencrypt/aliyun.ini
# 3. 一键申请通配符证书(自动通过 DNS API 验证)
sudo certbot certonly \
--authenticator dns-aliyun \
--dns-aliyun-credentials /etc/letsencrypt/aliyun.ini \
--dns-aliyun-propagation-seconds 30 \
--server https://acme-v02.api.letsencrypt.org/directory \
--agree-tos --no-eff-email \
--email admin@zhonjin.com \
-d mqtt.zhonjin.com \
-d "*.zhonjin.com"
# 4. 部署证书到 PlateRecApp
mkdir -p ssl/
sudo cp /etc/letsencrypt/live/mqtt.zhonjin.com/fullchain.pem ssl/plate_fullchain.pem
sudo cp /etc/letsencrypt/live/mqtt.zhonjin.com/privkey.pem ssl/plate_privkey.pem
sudo chown stevenroc:stevenroc ssl/*.pem
chmod 644 ssl/plate_fullchain.pem
chmod 600 ssl/plate_privkey.pem
# 5. 配置自动续期(每天 3:00 检查,续期后自动部署)
(crontab -l 2>/dev/null; echo "0 3 * * * certbot renew --quiet --deploy-hook /opt/openAI/project/003.PlateRecAPP/scripts/renew_ssl.sh") | crontab -
# 6. 重启 PlateRecApp
sudo systemctl restart PlateRecApp
#!/bin/bash
# /opt/openAI/project/003.PlateRecAPP/scripts/renew_ssl.sh
DOMAIN="mqtt.zhonjin.com"
SSL_DIR="/opt/openAI/project/003.PlateRecAPP/ssl"
cp /etc/letsencrypt/live/${DOMAIN}/fullchain.pem ${SSL_DIR}/plate_fullchain.pem
cp /etc/letsencrypt/live/${DOMAIN}/privkey.pem ${SSL_DIR}/plate_privkey.pem
chown stevenroc:stevenroc ${SSL_DIR}/*.pem
chmod 644 ${SSL_DIR}/plate_fullchain.pem
chmod 600 ${SSL_DIR}/plate_privkey.pem
systemctl restart PlateRecApp
echo "$(date) SSL 证书续期部署成功" >> /opt/openAI/project/003.PlateRecAPP/ssl_renew.log
AliyunDNSFullAccess(云解析 DNS 管理)_acme-challenge.mqtt.zhonjin.com TXT 记录,Let's Encrypt CA 查询 DNS 验证域名所有权,全程无需开放任何端口。dns9.hichina.com)。如果 DNS 在其他服务商,需先迁移到阿里云,或改用对应插件(如 certbot-dns-cloudflare、certbot-dns-dnspod)。无公网域名时使用自签名证书,浏览器会显示警告但加密通信正常:
# 创建证书目录 mkdir -p ssl/ # 生成自签名证书(10年有效期) openssl req -x509 -newkey rsa:2048 -nodes \ -keyout ssl/plate_privkey.pem \ -out ssl/plate_fullchain.pem \ -days 3650 \ -subj "/CN=mqtt.zhonjin.com/O=PlateRecApp/C=CN" # 设置权限 chmod 600 ssl/plate_privkey.pem chmod 644 ssl/plate_fullchain.pem # 重启 PlateRecApp 加载证书 sudo systemctl restart PlateRecApp
| 文件 | 用途 | config.ini 参数 |
|---|---|---|
| plate_fullchain.pem | 完整证书链(含中间 CA) | cert_path |
| plate_privkey.pem | 私钥文件(严禁泄露) | key_path |
/etc/nginx/ssl/),nginx 做 HTTPS 终止后转发 HTTP 到 C++。/etc/nginx/ssl/ 目录。temp_alert_threshold 配置)cpu_alert_threshold 配置)数据来源:通过读取 /proc、/sys、statvfs 等系统接口实时采集,页面每 2 秒自动刷新。
系统自动采集并存储监控数据到 SQLite 数据库(data/system_metrics.db),支持历史趋势查询:
monitor_interval(默认 2 秒),delta 法计算 CPU 使用率,不阻塞采集线程flush_time(默认 23:20)将内存监控数据批量写入 system_metrics.db,减少 SD 卡写入次数(v38 起)history_retention_days 天(默认 7 天),聚合数据保留 aggregation_retention_days 天(默认 365 天)/api/monitor/history?range=1h|6h|24h|7d|30d,自动选择合适的数据粒度/api/monitor/stats 返回数据库记录数和运行状态PlateRecApp_YYYYMMDD.logenabled=1,默认):所有日志写入内存环形缓冲区,Web 日志页面直接从内存读取,零磁盘 I/Oenabled=0):传统模式,日志直接写入文件flush_time(默认 23:20)自动将内存日志批量写入 PlateRecApp_YYYYMMDD.logauto_flush_on_exit=1 时,收到 SIGTERM/SIGINT 信号立即刷盘,防止日志丢失redirect_stdout=1 时,stdout/stderr 通过 pipe 重定向到内存缓冲区统一收集retention_days 控制),支持手动触发刷盘flush_time 均配置为 23:20,分别在 [log] 和 [system] 段中设置,可独立调整。| 页面 | 路径 | 权限 | 说明 |
|---|---|---|---|
| 监控首页 | / | 所有用户 | 实时监控、识别记录、连接状态、ROI设置 |
| 视频预览 | /video | 所有用户 | 实时视频流 |
| 称重记录 | /weight | 所有用户 | 称重数据 |
| 锁定管理 | /locks | 所有用户 | 交替锁定记录 |
| 帮助手册 | /help | 超级管理员 | 本文档 |
| 系统配置 | /config | 管理员+ | config.ini 在线编辑 |
| 系统监控 | /system | 超级管理员 | CPU/内存/温度/磁盘 |
| frpc 管理 | /frpc | 超级管理员 | 内网穿透状态和配置 |
| SSH 密钥 | /ssh-keys | 超级管理员 | SSH 公钥管理 |
| 日志管理 | /logs | 超级管理员 | 系统日志查看 |
| 登录 | /login | 公开 | 用户登录页面 |
首页和称重页均改为 Tab 分离结构,自动刷新与手动查询完全独立:
| Tab | 数据来源 | 刷新方式 |
|---|---|---|
| 📡 实时数据 / 实时称重 | 固定最新 20 条,无条件加载 | 每 30 秒自动刷新 |
| 🔍 历史查询 | 按筛选条件查询,支持完整分页 | 手动触发,不自动刷新 |
两个 Tab 使用独立的状态和函数,互不干扰。切换 Tab 时自动加载对应数据;重试操作后根据当前 Tab 智能刷新对应数据源。
历史查询 Tab 支持多维度组合筛选(所有条件 AND 组合):
| 筛选条件 | 首页 | 称重页 | 说明 |
|---|---|---|---|
| 车牌号 | ✅ | ✅ | 模糊匹配,如"沪A" |
| 联单编号 | ✅ | ✅ | 模糊匹配 |
| 方向 | ✅ | ✅ | 全部 / 进站 / 出站 |
| 上传状态 | ✅ | ✅ | 首页:成功/失败;称重页多一个"待上传"(status=0) |
| 飞书状态 | ✅ | — | 全部 / 已发送 / 失败 / 未发送 |
| 日期范围 | ✅ | ✅ | 按抓拍时间过滤(YYYY-MM-DD) |
| 文件 | 路径 | 说明 |
|---|---|---|
| 认证数据库 | data/auth.db | 用户账号、密码、Session |
| 识别记录 | data/upload_records.db | 车牌识别上传记录 |
| 系统监控 | data/system_metrics.db | 性能监控时序数据(原始+小时聚合+日聚合) |
data/ 目录下。系统首次启动时自动将旧路径数据库迁移到新位置(旧文件保留为备份)。| 字段 | 说明 |
|---|---|
| id | 自增主键 |
| create_time | 记录创建时间 |
| capture_time | 抓拍时间(用于时间对齐) |
| plate_number | 车牌号 |
| tb_num | 联单编号 |
| station_type | 方向:1=进站,2=出站 |
| lo_photo_path / hi_photo_path | 照片文件名(相对 PlateJPG/),清理后为 NULL |
| lo_upload_status / hi_upload_status | 上传状态:0=待传,1=成功,2=失败 |
| feishu_status | 飞书通知:1=已发送,2=失败,3=未发送 |
| retry_count | 重试次数(上限 5 次) |
data/ 目录和 config.ini/etc/nginx/ssl/、/etc/nginx/conf.d/PhotoMaxCapacityMB 限制内自动管理,重要照片建议另行备份A: 检查以下几点:
nginx -V 2>&1 | grep streamsudo nginx -tss -tlnp | grep 8081plate_web 正常运行(localPort 为 40963)curl -I http://localhost:40963/,应返回 301A: v2 方案下 TLS 由 C++ 应用处理,检查:
systemctl status PlateRecAppconfig.ini [ssl] enabled=1ls -l ssl/plate_fullchain.pem ssl/plate_privkey.pemss -tlnp | grep 40963sudo tail -f /var/log/nginx/error.logunknown directive "stream"?A: stream 模块未加载。执行:sudo apt install -y libnginx-mod-stream,确认 nginx.conf 顶部有 load_module modules/ngx_stream_module.so;
A: 检查 frpc.toml 配置语法:
frpc verify -c /path/to/frpc.toml
常见错误:TOML 格式问题(缺少引号、缩进错误)。通过 Web 管理界面编辑配置时系统会自动校验。
A: 自签名证书无法消除浏览器警告(这是正常安全行为)。解决方案:
A: 确认 RTSP 地址格式正确,在 VLC 中测试播放。尝试 rtsp_transport=tcp。
A: 5 分钟。单位是分钟,系统内部自动 ×60 转为秒。
A: 这是正常现象。当 PlateJPG/ 目录总大小超过 PhotoMaxCapacityMB(默认 1024MB)时,系统每日凌晨 2:00 自动删除最旧的照片释放空间。被删除照片对应的数据库记录仍保留(业务数据不丢失),Web 页面显示灰色「已清理」占位。如需保留更多照片,调大该配置值。
[路径校验] 转换绝对路径失败 错误?A: 此错误在旧版本(v41 修复前)出现,原因是照片被物理删除后 DB 路径字段未同步清空,前端仍请求已删除文件。v41 已修复:删除照片时通过 basename 匹配同步清空 DB 路径字段,启动时自动扫描孤儿路径,且 /photo 接口对缺失文件静默返回 404 不再打 ERROR 日志。升级后此错误不再出现。
A: 修改 config.ini 的 [config] 段中 PhotoMaxCapacityMB 值并重启服务。例如:
[config] PhotoMaxCapacityMB=2048 # 调整为 2GB
A: 称重记录上传状态筛选比首页多一个「待上传」选项,对应数据库中 upload_status = 0 的记录,即称重数据尚未成功上传到云端 API,可通过此筛选快速定位需重试的记录。
cd /opt/openAI/project/003.PlateRecAPP python3 reset_passwords.py