📋 目录

  1. 系统概述
  2. 快速部署指南
  3. config.ini 完整配置说明
  4. RTSP 摄像头配置
  5. 车牌识别上传流程
  6. MQTT 数据上报配置
  7. 称重系统对接
  8. ROI 区域识别配置
  9. 交替锁定机制
  10. 登录认证与权限
  11. frpc.toml 内网穿透配置详解
  12. Nginx 反向代理配置详解
  13. SSL/HTTPS 证书配置
  14. 系统监控与日志
  15. Web 管理功能
  16. 数据库与备份
  17. 常见问题 FAQ
1. 系统概述 ▼

系统简介

车牌识别系统(PlateRecApp)是一款基于 HyperLPR3 的实时车牌识别应用,运行在树莓派 5 (aarch64) 上,支持:

  • 多路 RTSP 摄像头实时抓拍识别(进场/出场/侧面进/侧面出)
  • 车牌识别结果自动上传至云端 API
  • 称重系统对接(TCP 协议),自动关联车牌与称重数据
  • MQTT 协议实时推送识别结果
  • 飞书群消息通知(异常报警)
  • Web 管理界面(监控首页、视频预览、系统配置、锁定管理等)
  • frpc 内网穿透管理 + Nginx stream + ssl_preread 协议分流(HTTP 自动跳 HTTPS)
  • 登录认证与角色权限控制
  • SD 卡写入寿命保护(内存缓冲 + 每日定时写盘)

运行环境

项目要求
硬件树莓派 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 自动检测分流)

网络架构总览(v2 — nginx stream + ssl_preread 方案)

外网客户端                        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
v2 方案优势:公网只需一个端口 40963,nginx stream 在 TCP 层自动检测协议类型并分流。HTTP 自动跳 HTTPS,HTTPS 透传到 C++ 应用处理。用户只需记住一个地址。

目录结构

路径说明
PlateRecApp主程序二进制
config.ini主配置文件
config.txtMD5 校验配置
hyperlpr3/HyperLPR3 SDK 模型文件
assets/web/Web 页面文件(HTML/CSS/JS)
data/数据库目录(auth.db、upload_records.db、system_metrics.db)
photos/抓拍图片存储目录
scripts/运维脚本(sudoers 配置、密码重置等)
2. 快速部署指南 ▼

编译部署

  1. 解压代码包:tar xzf fix24_vXX.tar.gz
  2. 编译:cd fix24/build && cmake .. && make -j4
  3. 部署文件到安装目录(如 /opt/openAI/project/003.PlateRecAPP/)
  4. 复制 HTML 文件:cp -r assets/web/ /安装目录/assets/web/
  5. 配置 sudoers(frpc 管理需要):sudo bash scripts/setup_frpc_sudoers.sh
  6. 安装 nginx stream 模块:sudo apt install libnginx-mod-stream
  7. 部署 nginx stream 配置文件(详见第 12 章)
  8. 部署 SSL 证书(C++ 应用自管理,详见第 13 章)
  9. 修改 config.ini 配置(参考下文各节)
  10. 启动服务:sudo systemctl restart nginx && sudo systemctl restart PlateRecApp

一键部署脚本(推荐)

代码包内置 scripts/deploy.sh,覆盖编译→备份→部署→服务安装→启动→健康检查全流程,默认部署目录 /opt/openAI/project/003.PlateRecAPP、运行用户 stevenroc、服务名 PlateRecApp(均可用环境变量 APP_DIR/RUN_USER/SVC_NAME 覆盖):

# 首次:安装编译/运行依赖(需 root)
sudo bash scripts/deploy.sh deps

# 一键编译+部署+重启(部署前自动备份当前版本到 backup/,保留最近 5 个)
sudo bash scripts/deploy.sh deploy      # 或直接 sudo bash scripts/deploy.sh

# 日常运维
bash scripts/deploy.sh status           # 服务状态 + 8080/8079 端口 + 进程 + 磁盘
bash scripts/deploy.sh logs             # 跟踪 PlateRecApp.log
bash scripts/deploy.sh journal          # 跟踪 systemd journal
bash scripts/deploy.sh restart          # 重启服务
sudo bash scripts/deploy.sh rollback    # 回滚到上一次备份
sudo bash scripts/deploy.sh sudoers     # 配置 frpc 管理 sudoers 权限
脚本安全策略:config.ini 已存在时永不覆盖(首次部署才从模板生成);每次部署自动备份二进制+配置+Web资源+SDK;服务文件由脚本自动安装到 /etc/systemd/system/ 并设置开机自启。

systemd 服务配置

PlateRecApp 服务文件路径:/etc/systemd/system/PlateRecApp.service(由 deploy.sh 自动生成,内容等价于):

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
PlateRecApp 以非 root 用户运行(stevenroc),frpc 管理通过 sudoers 授权。日志输出由 v36 内存缓冲系统接管,systemd 的 StandardOutput 仅作备份。

密码重置

如忘记管理员密码,运行:python3 reset_passwords.py

Nginx 服务配置(v2 — stream 方案)

安装 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
3. config.ini 完整配置说明 ▼

配置文件路径:<安装目录>/config.ini,ini 格式,section 和 key 均不区分大小写。

[config] 核心识别配置

参数类型默认值说明
project_namestring-项目名称
point_numberstring-站点编号
throughwaystring-通道名称
rtsp_url_front_instring-前侧进场摄像头 RTSP 地址
rtsp_url_front_outstring-前侧出场摄像头 RTSP 地址
rtsp_url_side_instring-侧面进场摄像头 RTSP 地址
rtsp_url_side_outstring-侧面出场摄像头 RTSP 地址
app_apistring-云端上传 API 地址
app_keystring-API 认证 Key
app_secretstring-API 认证 Secret
DEBUG_LOGint0调试日志开关(0=关闭,1=开启)
TestFlagint0测试模式开关
wTypeint0称重类型(0=禁用,1=启用)
TIME_WINDOWint5识别时间窗口(分钟),同一车牌在窗口内不重复上传
MAX_PHOTO_GROUPSint100最大照片组数缓存
in_out_intervalint5⚠️ 单位是分钟!交替锁定间隔(同一车牌进出最短时间),内部转为秒存储
PLATE_CONFIDENCE_THRESHOLDfloat0.7车牌识别置信度阈值(0~1),低于此值不上传
PLATE_LOG_THRESHOLDfloat0.5日志记录置信度阈值
AlternatingMergeint0交替合并开关(0=关闭,1=开启)
hw_decode_modestringauto硬件解码模式:auto/soft/drm
hw_decode_devicestring/dev/dri/renderD128硬件解码设备路径
rtsp_transportstringtcpRTSP 传输协议:tcp/udp
PhotoMaxCapacityMBint1024照片目录最大容量(MB),超限自动清理旧照片
LogRetentionDaysint30日志保留天数

[server] Web 服务配置

参数说明
ipWeb 服务监听地址(含端口),如 0.0.0.0:8080。v2 方案下 PlateRecApp 提供 HTTPS 服务(SSL 自管理),nginx stream 透传 TLS 流量到此处

[mqtt] MQTT 上报配置

参数默认值说明
MQTT_HOST-MQTT Broker 地址(填写后自动启用 MQTT)
MQTT_PORT1883MQTT Broker 端口
MQTT_USER-MQTT 用户名
MQTT_PASS-MQTT 密码
MQTT_TOPIC-推送主题
MQTT_CLIENT_ID-客户端 ID
PLATE_COLOR-车牌颜色标识(随 MQTT 消息上报)
VEHICLE_TYPE-车辆类型标识(随 MQTT 消息上报)

[auth] 登录认证配置

参数默认值说明
enabled1认证开关(0=关闭,1=开启)
db_pathdata/auth.db认证数据库路径
session_idle_timeout1800Session 空闲超时(秒)
session_max_timeout28800Session 最大超时(秒)
session_remember_timeout604800记住登录超时(秒)
max_failed_attempts5最大登录失败次数
lockout_duration900锁定时长(秒)

[ssl] HTTPS 配置(v2 — C++ 应用自管理)

参数默认值说明
enabled1HTTPS 开关。v2 方案下必须设为 1(C++ 应用直接处理 TLS)
cert_pathssl/plate_fullchain.pemSSL 证书路径(相对于安装目录)
key_pathssl/plate_privkey.pemSSL 私钥路径(相对于安装目录)
v2 方案变更:nginx stream 只做 TCP 分流,不解密 TLS。SSL 证书由 C++ 应用自己管理,enabled 必须为 1。

[sync] 车牌称重并行同步配置(v43 新增)

参数默认值说明
enabled0并行同步开关。0=串行v41模式(重量在照片上传成功后入队);1=并行v43模式(tb_num确定后立即并行触发重量读取+上传)
reporter_timeout_sec30重量上报API超时(秒),最小5
weight_retry_max5重量上传最大重试次数,最小0
weight_retry_interval_sec10重量上传重试间隔(秒),最小1
tb_num_dedup_sec300同联单编号去重窗口(秒),最小10。同一 tb_num 在此时间内只触发一次重量读取
v43 核心变更:并行模式下,车牌识别成功并获取到 tb_num 后,照片上传和重量读取同时进行,互不阻塞。重量上报延迟从 v41 的 25~50s 降至 ≤5s,捕获率从 72% 提升至 ≥98%。
灰度策略:首次部署建议保持 enabled=0(完全兼容v41行为),验证系统稳定后再改为 enabled=1 开启并行模式。修改 enabled 无需重启,秒级生效。

[system] 系统监控配置(v38 内存缓冲)

参数默认值说明
monitor_interval2监控采集间隔(秒),delta 法计算 CPU 使用率
temp_alert_threshold70温度告警阈值(°C)
cpu_alert_threshold90CPU 告警阈值(%)
db_pathdata/system_metrics.db监控数据库路径
history_retention_days7原始数据保留天数
aggregation_retention_days365聚合数据保留天数
flush_time23:20每日定时刷盘时间(HH:MM)

[frpc] 内网穿透配置

参数默认值说明
config_path-frpc.toml 配置文件路径
protected_tunnels-受保护隧道名称(逗号分隔)
auto_rollback1重启失败自动回滚(0/1)
admin_addr127.0.0.1frpc Admin API 地址
admin_port7400frpc Admin API 端口
admin_user-frpc Admin 用户名
admin_password-frpc Admin 密码

[log] 日志配置(v36 内存缓冲)

参数默认值说明
enabled1内存缓冲模式(1=内存缓冲,0=直写文件)
log_filePlateRecApp.log日志文件路径
buffer_size_mb20内存缓冲区上限(MB)
flush_time23:20每日定时刷盘时间(HH:MM)
retention_days30日志文件保留天数
redirect_stdout1捕获 stdout/stderr(0/1)
auto_flush_on_exit1退出时立即刷盘(0/1)

[feishu] 飞书通知配置

参数说明
APP_ID飞书应用 App ID
APP_SECRET飞书应用 App Secret
CHAT_ID飞书群聊 ID
TIMEOUT_SECOND请求超时时间(秒)
WarningSigns抑制紧急报警(1=抑制,0=正常)

[weight] 称重系统配置

参数默认值说明
flagWeight0称重系统开关(0/1)
weight_server_ip-称重服务器 IP
weight_server_port-称重服务器端口
weight_threshold_in0进场称重阈值(吨)
weight_threshold_out0出场称重阈值(吨)
default_entry_weight0默认进场重量
default_exit_weight0默认出场重量
weight_detection_time5000称重检测时间(毫秒)
tcp_connect_timeout3000TCP 连接超时(毫秒)
MAX_WEIGHT100最大称重值(吨)
STABLE_SAMPLE_COUNT5稳定采样次数
STABLE_THRESHOLD_KG50稳定判定阈值(公斤)
CANDIDATE_DATA_COUNT3候选数据次数
MAX_UPLOAD_RETRIES3上传最大重试次数
RETRY_DELAY_MS1000重试延迟(毫秒)

[terminal] 终端/SSH 配置

参数默认值说明
ttyd_port7681ttyd Web 终端端口
ttyd_credential-ttyd 登录凭据(user:password)
ttyd_max_clients5ttyd 最大客户端数
ssh_keys_path-SSH 公钥存储路径
4. RTSP 摄像头配置 ▼

摄像头地址格式

标准 RTSP 地址格式:

rtsp://用户名:密码@摄像头IP:554/stream

四路摄像头说明

参数方向说明
rtsp_url_front_in前→进前方摄像头,捕捉进场车辆
rtsp_url_front_out前→出前方摄像头,捕捉出场车辆
rtsp_url_side_in侧→进侧面摄像头,捕捉进场车辆
rtsp_url_side_out侧→出侧面摄像头,捕捉出场车辆

硬件解码(树莓派5推荐配置)

模式值说明
自动auto优先硬件解码,不可用则回退软件
软件解码softCPU 解码,兼容性最好
DRM 硬件drmDRM 硬件加速(树莓派5推荐)
树莓派 5 推荐:hw_decode_mode=drm,hw_decode_device=/dev/dri/renderD128,rtsp_transport=tcp
5. 车牌识别上传完整流程 ▼

识别流程

  1. 摄像头抓拍:RTSP 视频流实时解码,检测到车辆运动时触发抓拍
  2. 车牌识别:HyperLPR3 引擎识别车牌号,返回车牌文本 + 置信度
  3. 置信度过滤:低于 PLATE_CONFIDENCE_THRESHOLD 则丢弃
  4. 时间窗口去重:同一车牌在 TIME_WINDOW 分钟内不重复上传
  5. 交替锁定检查:同一车牌进出间隔低于 in_out_interval 分钟则锁定
  6. ROI 过滤:如启用 ROI,只识别指定区域内的车牌
  7. 数据上传:HTTP POST 将识别结果 + 照片上传至 app_api
  8. MQTT 推送:如启用 MQTT,同步推送识别结果
  9. 飞书通知:异常报警发送飞书群消息

照片存储与自动清理

  • 照片保存在 PlateJPG/ 目录下,文件名格式:车牌号_时间戳_方向.jpg(如 沪FQ7108_1787365677_side_out.jpg)
  • 系统每日凌晨 2:00 自动执行照片清理任务(间隔 24 小时)
  • 清理策略:当 PlateJPG/ 目录下所有 .jpg 文件总大小超过 PhotoMaxCapacityMB(默认 1024MB)时,按文件修改时间从旧到新逐个删除,直到总大小降至阈值以下
  • 删除照片后递归清理空子目录
  • 清理日志输出示例:
    [照片清理] PlateJPG/ 当前 1156 MB / 1024 MB 阈值,共 8234 个文件
    [照片清理] 删除 1523 张照片(按时间从旧到新),释放 156 MB,当前 1000 MB
    [照片清理] 同步清空DB路径字段: 2847 个字段已更新

照片清理与数据库同步(v41 修复)

照片被物理删除后,系统同步清空 upload_records 表中对应的 lo_photo_path / hi_photo_path 字段(设为 NULL):

  • 业务记录(车牌号、联单编号、抓拍时间、上传状态等)完整保留,仅清空路径字段
  • Web 前端路径为 NULL 时显示灰色斜体「已清理」占位,不再显示 broken image
  • 即使 DB 清理与页面加载存在竞态,<img onerror> 也会自动替换为「已清理」
  • 程序启动时自动执行一次孤儿路径扫描(db_purge_missing_photo_paths),修复历史遗留数据
💡 调整容量阈值:修改 config.ini 中 PhotoMaxCapacityMB 的值并重启服务。例如设为 2048 表示允许 2GB 照片存储。
6. MQTT 数据上报配置 ▼

启用条件

在 [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
MQTT 连接失败时系统会自动重连,不影响识别和上传功能。
7. 称重系统对接 ▼

工作原理

称重系统通过 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)
8. ROI 区域识别配置 ▼

配置方式

  • Web 可视化:首页连接状态面板中拖拽设置 ROI 区域
  • 手动配置:在 config.ini 的 [config] 段填写坐标值

参数说明

参数说明
roi_in_x/y/w/h进场 ROI 区域坐标和尺寸(像素)
roi_in_enabled1=启用进场 ROI
roi_out_x/y/w/h出场 ROI 区域坐标和尺寸
roi_out_enabled1=启用出场 ROI
roi_debug_enabled1=在抓拍图上绘制 ROI 框(调试用)
9. 交替锁定机制 ▼

机制说明

防止同一车牌短时间内进出频繁触发(如车辆在门口调头)。同一车牌从"进"切换到"出"必须间隔 in_out_interval 分钟以上。

工作流程

  1. 车牌 A 被识别为"进"方向
  2. 5 分钟内(默认)车牌 A 再次被识别为"出"方向
  3. 系统判定为异常,创建交替锁定记录
  4. 锁定期间该车牌不会被上传
  5. 超过 2 小时锁定自动清零,或手动在"锁定管理"页面清除
参数默认值说明
in_out_interval5交替锁定间隔(⚠️ 单位分钟),小于此间隔的进/出切换会被锁定
AlternatingMerge0交替合并开关
in_out_interval 单位是分钟,不是秒!填 5 表示 5 分钟 = 300 秒。
10. 登录认证与权限 ▼

角色定义

角色Role值可访问页面
普通用户0首页、视频、称重、锁定管理
管理员1普通用户 + 系统配置
超级管理员2所有页面(含系统监控、frpc管理、SSH密钥、日志、帮助)

默认账号

用户名密码角色
adminzhongjin188A超级管理员
zhonjinzhonjin普通用户

安全特性

  • 密码 bcrypt 加密存储(cost=12)
  • 连续登录失败锁定账号
  • CSRF Token 防护
  • Session 超时自动失效
  • 登录速率限制
11. frpc.toml 内网穿透配置详解 ▼

配置文件位置

frpc 配置文件路径由 config.ini 的 [frpc] config_path 指定,通常为安装目录下的 frpc.toml。

v2 方案 — nginx stream 分流(推荐)

使用 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

隧道说明(v2)

隧道名外网端口本机端口用途
plate_web40963nginx stream:40963HTTP/HTTPS 合一,stream 自动分流
ttyd_web40964nginx HTTPS:8082Web 终端(nginx SSL→ttyd:7681)

v2 数据流向

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)
v1→v2 迁移注意:不再需要 40965 端口。HTTPS 主服务统一使用 https://mqtt.zhonjin.com:40963/。

参数详解

参数说明
serverAddrfrps 服务器域名或 IP
serverPortfrps 服务端口(默认 7000,本项目用 9443)
auth.method认证方式,固定为 token
auth.token与 frps 约定的认证 Token
webServer.addr/portfrpc Admin API 监听地址,PlateRecApp 通过此接口获取隧道状态
webServer.user/passwordAdmin API 认证凭据(需与 config.ini [frpc] 段一致)
[[proxies]] name隧道唯一名称,PlateRecApp Web 界面按名称识别
[[proxies]] type隧道类型,本项目全部使用 tcp(透传 TCP 流)
[[proxies]] localPort本机目标端口,frpc 将外网流量转发到此端口
[[proxies]] remotePortfrps 服务器暴露的外网端口,客户端通过此端口访问

隧道保护

在 config.ini [frpc] 段配置受保护隧道,防止 Web 界面误删关键隧道:

protected_tunnels = plate_web,ttyd_web
受保护的隧道无法通过 Web 管理界面删除。如需删除,必须先修改 config.ini 中的 protected_tunnels 字段。

frpc 管理命令

# 查看 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
12. Nginx stream + ssl_preread 配置详解(v2) ▼

架构说明

v2 方案使用 nginx 的 stream 模块 + ssl_preread 功能,在 TCP 层面检测流量类型并自动分流:

  • 端口 40963:nginx stream 监听,检测协议类型(TLS / HTTP)
  • TLS 流量 → 透传到 C++ HTTPS 服务(127.0.0.1:8080)
  • HTTP 流量 → nginx HTTP 模块(127.0.0.1:8081)→ 301 重定向到 HTTPS
关键区别:stream 模块是 TCP 层代理,不解密 TLS 流量。SSL 证书由 C++ 应用自己管理,nginx 只做协议检测和分流。

nginx.conf 完整配置

文件路径:/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 onSSL 预读在 TLS 握手阶段读取 ClientHello,获取协议版本,不解密
map $ssl_preread_protocol协议分流空字符串 = HTTP,非空 = TLS
upstream tls_backendTLS 后端透传到 C++ 应用(8080),C++ 自己处理 TLS 握手
upstream http_backendHTTP 后端转发到 nginx HTTP 8081,返回 301 重定向
server 8082 sslttyd HTTPS 代理SSL 终止后代理到 ttyd:7681,支持 WebSocket

部署步骤

  1. 安装 stream 模块:sudo apt install -y libnginx-mod-stream
  2. 验证模块:ls -l /usr/lib/nginx/modules/ngx_stream_module.so
  3. 备份原配置:sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
  4. 替换配置:sudo cp nginx-stream.conf /etc/nginx/nginx.conf
  5. 删除默认站点(如有):sudo rm -f /etc/nginx/sites-enabled/default
  6. 验证配置:sudo nginx -t
  7. 启动 nginx:sudo 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/

端口规划总结

端口服务说明
40963nginx stream公网入口,TLS/HTTP 分流
8081nginx httpHTTP 301 重定向(仅内部)
8080C++ HTTPSPlateRecApp Web 服务(SSL 自管理)
40964nginx HTTPS→ttydWeb 终端(nginx:8082 SSL终止→ttyd:7681)
8082nginx HTTPS 代理ttyd Web 终端 HTTPS 反向代理(仅内部)
7400frpc Admin隧道状态查询(仅 127.0.0.1)
13. SSL/HTTPS 证书配置(v2 — C++ 应用自管理) ▼
v2 方案变更:nginx stream 只做 TCP 层协议分流,不解密 TLS 流量。SSL 证书由 C++ 应用(PlateRecApp)自己管理,通过 config.ini 的 [ssl] 段配置。nginx 不再需要 SSL 证书。

config.ini [ssl] 段配置

[ssl]
enabled=1
cert_path=ssl/plate_fullchain.pem
key_path=ssl/plate_privkey.pem
v2 方案下 SSL 证书文件放在安装目录的 ssl/ 子目录下(或任意自定义路径),不再需要 /etc/nginx/ssl/。

方式一:Let's Encrypt 证书 — 阿里云 DNS 验证(推荐)

本项目使用 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

续期部署脚本 renew_ssl.sh

#!/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

阿里云 RAM 权限配置

  1. 登录 RAM 控制台 → 用户 → 创建用户
  2. 勾选 OpenAPI 调用访问,保存 AccessKey ID 和 Secret
  3. 添加权限:AliyunDNSFullAccess(云解析 DNS 管理)
DNS 验证原理:certbot 通过阿里云 API 自动添加 _acme-challenge.mqtt.zhonjin.com TXT 记录,Let's Encrypt CA 查询 DNS 验证域名所有权,全程无需开放任何端口。
前提:域名 mqtt.zhonjin.com 的 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

v1→v2 迁移说明

v1 方案:SSL 证书由 nginx 管理(/etc/nginx/ssl/),nginx 做 HTTPS 终止后转发 HTTP 到 C++。
v2 方案:SSL 证书由 C++ 应用管理,nginx stream 只做 TCP 分流,不解密 TLS。不再需要 /etc/nginx/ssl/ 目录。
14. 系统监控与日志 ▼

系统监控(实时)

  • CPU 使用率(delta 法计算,含每核使用率、负载均值)
  • 内存使用率(总量/已用/可用)
  • 磁盘使用率(根分区容量/已用/可用)
  • CPU 温度监控,超阈值告警(阈值由 temp_alert_threshold 配置)
  • CPU 使用率告警(阈值由 cpu_alert_threshold 配置)
  • PlateRecApp 进程资源占用(RSS 内存、CPU 占用)
  • frpc 运行状态、MQTT 连接状态、称重系统连接状态

数据来源:通过读取 /proc、/sys、statvfs 等系统接口实时采集,页面每 2 秒自动刷新。

历史监控数据(v37+)

系统自动采集并存储监控数据到 SQLite 数据库(data/system_metrics.db),支持历史趋势查询:

  • 采集间隔:monitor_interval(默认 2 秒),delta 法计算 CPU 使用率,不阻塞采集线程
  • 定时刷盘:每日 flush_time(默认 23:20)将内存监控数据批量写入 system_metrics.db,减少 SD 卡写入次数(v38 起)
  • 三级存储:原始数据(2秒间隔)→ 小时聚合 → 日聚合,自动降采样
  • 自动清理:原始数据保留 history_retention_days 天(默认 7 天),聚合数据保留 aggregation_retention_days 天(默认 365 天)
  • 历史查询 API:/api/monitor/history?range=1h|6h|24h|7d|30d,自动选择合适的数据粒度
  • 统计接口:/api/monitor/stats 返回数据库记录数和运行状态

SD 卡写入保护策略(v38)

  • 系统监控:内存缓冲,每日 23:20 一次写入,SD 卡写入从 2880 次/天降至 1 次
  • 运行日志:内存环形缓冲区,Web 日志页面零磁盘 I/O,每日 23:20 写入 PlateRecApp_YYYYMMDD.log
  • 旧版日志清理:内存模式启用时自动跳过旧版 copytruncate,避免无意义读写
  • 退出安全刷盘:SIGTERM/SIGINT 时立即执行最后一次刷盘

日志管理(v36 内存缓冲)

  • 内存缓冲模式(enabled=1,默认):所有日志写入内存环形缓冲区,Web 日志页面直接从内存读取,零磁盘 I/O
  • 直写文件模式(enabled=0):传统模式,日志直接写入文件
  • 定时刷盘:每日 flush_time(默认 23:20)自动将内存日志批量写入 PlateRecApp_YYYYMMDD.log
  • 退出刷盘:auto_flush_on_exit=1 时,收到 SIGTERM/SIGINT 信号立即刷盘,防止日志丢失
  • stdout 捕获:redirect_stdout=1 时,stdout/stderr 通过 pipe 重定向到内存缓冲区统一收集
  • Web 界面实时查看日志(支持 Tail 模式),显示内存使用率和缓冲状态
  • 超期日志文件自动清理(由 retention_days 控制),支持手动触发刷盘
两个子系统的 flush_time 均配置为 23:20,分别在 [log] 和 [system] 段中设置,可独立调整。
15. Web 管理功能 ▼
页面路径权限说明
监控首页/所有用户实时监控、识别记录、连接状态、ROI设置
视频预览/video所有用户实时视频流
称重记录/weight所有用户称重数据
锁定管理/locks所有用户交替锁定记录
帮助手册/help超级管理员本文档
系统配置/config管理员+config.ini 在线编辑
系统监控/system超级管理员CPU/内存/温度/磁盘
frpc 管理/frpc超级管理员内网穿透状态和配置
SSH 密钥/ssh-keys超级管理员SSH 公钥管理
日志管理/logs超级管理员系统日志查看
登录/login公开用户登录页面

Tab 分离:实时数据 + 历史查询(v41 新增)

首页和称重页均改为 Tab 分离结构,自动刷新与手动查询完全独立:

Tab数据来源刷新方式
📡 实时数据 / 实时称重固定最新 20 条,无条件加载每 30 秒自动刷新
🔍 历史查询按筛选条件查询,支持完整分页手动触发,不自动刷新

两个 Tab 使用独立的状态和函数,互不干扰。切换 Tab 时自动加载对应数据;重试操作后根据当前 Tab 智能刷新对应数据源。

分类查询筛选条件(v41)

历史查询 Tab 支持多维度组合筛选(所有条件 AND 组合):

筛选条件首页称重页说明
车牌号✅✅模糊匹配,如"沪A"
联单编号✅✅模糊匹配
方向✅✅全部 / 进站 / 出站
上传状态✅✅首页:成功/失败;称重页多一个"待上传"(status=0)
飞书状态✅—全部 / 已发送 / 失败 / 未发送
日期范围✅✅按抓拍时间过滤(YYYY-MM-DD)

分页控件(v41)

  • 每页条数:可选 20 / 50 / 100 条
  • 页码导航:首页 / 上一页 / 页码(当前页前后各 2 页,含首末页和省略号)/ 下一页 / 末页
  • 跳转:输入页码 + GO 按钮快速跳转
  • 信息栏:显示"第 X/Y 页 · 共 Z 条"
16. 数据库与备份 ▼

数据库文件

文件路径说明
认证数据库data/auth.db用户账号、密码、Session
识别记录data/upload_records.db车牌识别上传记录
系统监控data/system_metrics.db性能监控时序数据(原始+小时聚合+日聚合)
v29 版本起,所有数据库统一存放在 data/ 目录下。系统首次启动时自动将旧路径数据库迁移到新位置(旧文件保留为备份)。

upload_records 表结构

字段说明
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
  • nginx SSL 证书和配置文件备份:/etc/nginx/ssl/、/etc/nginx/conf.d/
  • frpc 配置修改时系统自动备份
  • 照片数据在 PhotoMaxCapacityMB 限制内自动管理,重要照片建议另行备份
17. 常见问题 FAQ ▼

Q: HTTP 访问没有跳转到 HTTPS?

A: 检查以下几点:

  • 确认 nginx stream 模块已安装:nginx -V 2>&1 | grep stream
  • 确认 nginx 配置正确:sudo nginx -t
  • 确认 8081 端口正在监听:ss -tlnp | grep 8081
  • 确认 frpc 隧道 plate_web 正常运行(localPort 为 40963)
  • 浏览器可能缓存了旧的重定向,尝试清除缓存或无痕模式
  • 测试本机重定向:curl -I http://localhost:40963/,应返回 301

Q: HTTPS 访问返回错误?

A: v2 方案下 TLS 由 C++ 应用处理,检查:

  • PlateRecApp 是否正在运行:systemctl status PlateRecApp
  • PlateRecApp 是否启用了 HTTPS:config.ini [ssl] enabled=1
  • SSL 证书文件是否存在:ls -l ssl/plate_fullchain.pem ssl/plate_privkey.pem
  • nginx stream 是否正常监听:ss -tlnp | grep 40963
  • nginx 错误日志:sudo tail -f /var/log/nginx/error.log

Q: unknown directive "stream"?

A: stream 模块未加载。执行:sudo apt install -y libnginx-mod-stream,确认 nginx.conf 顶部有 load_module modules/ngx_stream_module.so;

Q: frpc 重启后隧道不生效?

A: 检查 frpc.toml 配置语法:

frpc verify -c /path/to/frpc.toml

常见错误:TOML 格式问题(缺少引号、缩进错误)。通过 Web 管理界面编辑配置时系统会自动校验。

Q: 自签名证书浏览器警告怎么消除?

A: 自签名证书无法消除浏览器警告(这是正常安全行为)。解决方案:

  • 方式1:使用 Let's Encrypt 免费证书(需域名解析到公网 IP)
  • 方式2:在客户端将自签名 CA 证书添加到受信任的根证书列表
  • 方式3:忽略警告,继续使用(加密通信正常,仅证书不受信任)

Q: 摄像头连接失败怎么办?

A: 确认 RTSP 地址格式正确,在 VLC 中测试播放。尝试 rtsp_transport=tcp。

Q: in_out_interval 填 5 是 5 秒还是 5 分钟?

A: 5 分钟。单位是分钟,系统内部自动 ×60 转为秒。

Q: 照片被自动清理后,Web 页面看到"已清理"怎么办?

A: 这是正常现象。当 PlateJPG/ 目录总大小超过 PhotoMaxCapacityMB(默认 1024MB)时,系统每日凌晨 2:00 自动删除最旧的照片释放空间。被删除照片对应的数据库记录仍保留(业务数据不丢失),Web 页面显示灰色「已清理」占位。如需保留更多照片,调大该配置值。

Q: 日志中出现 [路径校验] 转换绝对路径失败 错误?

A: 此错误在旧版本(v41 修复前)出现,原因是照片被物理删除后 DB 路径字段未同步清空,前端仍请求已删除文件。v41 已修复:删除照片时通过 basename 匹配同步清空 DB 路径字段,启动时自动扫描孤儿路径,且 /photo 接口对缺失文件静默返回 404 不再打 ERROR 日志。升级后此错误不再出现。

Q: 如何调整照片存储上限?

A: 修改 config.ini 的 [config] 段中 PhotoMaxCapacityMB 值并重启服务。例如:

[config]
PhotoMaxCapacityMB=2048   # 调整为 2GB

Q: 称重页的"待上传"状态是什么意思?

A: 称重记录上传状态筛选比首页多一个「待上传」选项,对应数据库中 upload_status = 0 的记录,即称重数据尚未成功上传到云端 API,可通过此筛选快速定位需重试的记录。

Q: 如何重置管理员密码?

cd /opt/openAI/project/003.PlateRecAPP
python3 reset_passwords.py