# PlateRecApp 技术方案文档 > **版本**: v43.2 fix24 > **作者**: steven_roc > **仓库**: https://git.zhonjin.com:40717/steven_roc/PlateRecApp > **最后更新**: 2026-07-06 --- ## 1. 项目概述 ### 1.1 项目名称 **PlateRecApp**(HyperLPR3-LinuxAPP) ### 1.2 系统定位 面向渣土车/工程车辆管理的**车牌识别+称重+联单上传**一体化边缘计算系统。部署在工地出入口的树莓派5上,通过4路RTSP摄像头实时捕获车牌,结合称重仪表数据,自动完成: - 车牌识别(HyperLPR3 + MNN推理引擎) - 进站/出站交替锁定管理 - 照片水印叠加与平台上传 - 飞书实时通知推送 - MQTT消息发布 - 耀华称重仪表数据采集与上报 - Web管理后台(监控/视频/配置/锁定管理) - frpc内网穿透管理 - SSH密钥管理 - 系统资源监控 ### 1.3 目标场景 工地/码头/回填点出入口,单点双向(进站+出站),支持4个摄像头(前低位×2 + 侧高位×2),支持共享摄像头模式。 --- ## 2. 软件环境 ### 2.1 目标硬件 | 参数 | 规格 | |------|------| | 主板 | 树莓派5 8GB | | CPU | BCM2712, Quad-core Arm Cortex-A76 | | GPU | VideoCore VII | | 内存 | 8GB LPDDR4X | ### 2.2 操作系统 | 参数 | 值 | |------|-----| | OS | Ubuntu 24.04 LTS | | 架构 | aarch64 (ARM64) | | 内核 | Linux 6.x | ### 2.3 编译器与构建工具 | 工具 | 版本/标准 | |------|-----------| | GCC | 11+ (aarch64-linux-gnu) | | C++ 标准 | C++17 (CMAKE_CXX_STANDARD 17) | | CMake | 3.25.1+ | | 构建类型 | Release (默认) / Debug / MinSizeRel / RelWithDebInfo | ### 2.4 依赖库清单 | 库名 | 用途 | 查找方式 | |------|------|----------| | OpenCV | 图像处理、JPEG编解码、水印渲染 | `find_package(OpenCV REQUIRED)` | | HyperLPR3 SDK | 车牌检测+识别(MNN推理) | 头文件 `hyper_lpr_sdk.h`,库 `libhyperlpr3` | | SQLite3 | 本地数据库(记录存储、认证、监控) | `pkg_check_modules(SQLITE3 REQUIRED sqlite3)` | | cURL | HTTP客户端(API上传、飞书消息) | `find_package(CURL REQUIRED)` | | OpenSSL | HTTPS/SSL、MD5计算 | `find_package(OpenSSL REQUIRED)` | | FreeType2 | 中文字体渲染(水印文字) | `pkg_check_modules(FREETYPE REQUIRED freetype2)` | | FFmpeg (libavformat/libavcodec/libavutil/libswscale) | RTSP流解码、DRM硬件加速 | `pkg_check_modules` | | cJSON | JSON解析与生成 | 手动查找 + pkg-config 回退 | | mosquitto / mosquittopp | MQTT客户端通信 | 直接链接 | | libcrypt | bcrypt密码哈希(`crypt_r`) | `find_library(CRYPT_LIBRARY crypt)` | | inih | INI配置文件解析 | 第三方头文件 `ini.h` | | httplib (cpp-httplib) | 轻量级HTTP/HTTPS服务器 | 第三方头文件 `httplib.h` | | cvxFont | OpenCV中文字体绘制 | 第三方库 `cvxFont.h` | --- ## 3. 系统架构 ### 3.1 模块划分图 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ main.cpp (主调度) │ │ 配置加载 → 数据库初始化 → HyperLPR初始化 → 摄像头启动 → 主循环 │ └──────┬──────────┬──────────┬──────────┬──────────┬─────────────────┘ │ │ │ │ │ ┌────▼───┐ ┌───▼────┐ ┌──▼───┐ ┌───▼────┐ ┌───▼──────┐ │ config │ │database│ │web │ │feishu │ │ MQTT + │ │ .cpp │ │ .cpp │ │server│ │client │ │ Weight │ └────────┘ └────────┘ └──────┘ └────────┘ │ Scale │ └──────────┘ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ RTSP │ │ Plate │ │ Station │ │ Network │ │ Capture │ │Recognize │ │ Lock │ │ Client │ │ (×4) │ │ r.cpp │ │ .cpp │ │ .cpp │ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ Auth │ │ frpc │ │ SSH │ │ System │ │ System │ │ Manager │ │ Manager │ │ Monitor │ │(*.cpp) │ │ .cpp │ │ .cpp │ │ .cpp │ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ ┌──────────┐ ┌──────────────────────────────────────────┐ │ Log │ │ common.h (全局类型/结构体/指标) │ │ Manager │ │ MonitorMetrics / ROIConfig / ... │ └──────────┘ └──────────────────────────────────────────┘ ``` ### 3.2 模块间依赖关系 ``` common.h ← 所有模块(全局类型定义) config.cpp ← main.cpp(配置加载) rtsp_capture.cpp ← plate_recognizer.cpp(帧采集) plate_recognizer.cpp ← main.cpp(识别主逻辑) station_lock.cpp ← plate_recognizer.cpp(交替锁定) database.cpp ← plate_recognizer.cpp, web_server.cpp(数据持久化) network_client.cpp ← plate_recognizer.cpp, weight_scale.cpp(HTTP API) feishu_client.cpp ← plate_recognizer.cpp(飞书通知) mqtt_client.cpp ← weight_scale.cpp(MQTT发布) weight_scale.cpp ← main.cpp(称重数据采集) web_server.cpp ← main.cpp(Web服务线程) auth_*.cpp ← web_server.cpp(认证中间件) frpc_manager.cpp ← web_server.cpp(frpc管理API) ssh_manager.cpp ← web_server.cpp(SSH密钥管理) log_manager.cpp ← main.cpp(日志缓冲) system_monitor.cpp ← web_server.cpp(系统监控) ``` ### 3.3 数据流图 ``` RTSP摄像头(×4) │ ▼ FFmpeg解码(软解/DRM硬解) 帧队列(deque, 最大10帧) │ ▼ getFrame() 取出最新帧 ROI裁剪(可选, 按配置区域) │ ▼ HyperLPR3推理(HLPR_UpdateImage) 车牌检测结果(车牌号+置信度+类型) │ ▼ 帧级去重(2秒内同车牌跳过) │ ├─── 置信度 ≥ LOG_THRESHOLD? ──→ 保存照片(加水印) → 插入SQLite │ ▼ 置信度 ≥ CONFIDENCE_THRESHOLD? │ ├─── 交替锁定检查(try_lock_in/out_station) │ │ BLOCKED → 跳过 │ ▼ ALLOWED │ ├─── 创建联单(lib_curl_create_bill_request) → 获取tb_num │ ├─── 上传照片(低位+高位×2组) → 平台API │ ├─── 飞书通知(send_feishu_msg) → 飞书开放API │ └─── 称重+MQTT(weight_scale → mqtt_publish) → MQTT Broker ``` --- ## 4. 核心模块技术方案 ### 4.1 车牌识别引擎(HyperLPR3 + MNN) #### 4.1.1 初始化流程 ```cpp // hyperlpr_lib_init() — plate_recognizer.cpp HLPR_ContextConfiguration configuration = { 0 }; configuration.models_path = (char*)model.c_str(); // hyperlpr3/resource/models/r2_mobile configuration.max_num = 2; // 每帧最多检测2个车牌 configuration.det_level = DETECT_LEVEL_HIGH; // 高精度检测模式 configuration.use_half = false; // 不使用FP16半精度 configuration.nms_threshold = 0.5f; // NMS非极大值抑制阈值 configuration.rec_confidence_threshold = 0.6f; // 识别置信度阈值 configuration.box_conf_threshold = 0.30f; // 检测框置信度阈值 configuration.threads = 1; // 推理线程数 plate_rec_app.hlpr_ctx = HLPR_CreateContext(&configuration); ``` #### 4.1.2 推理流程 1. `capture_do_work_thread()` 主循环每500ms执行一次 2. 从4路RTSPCapture获取最新帧 3. ROI裁剪(如启用)→ `HLPR_CreateDataBuffer` → `HLPR_UpdateImage` 4. 遍历检测结果,按置信度更新 `CarPlateInfo` 5. 置信度 ≥ `PLATE_LOG_THRESHOLD` → 保存照片到磁盘 6. 置信度 ≥ `PLATE_CONFIDENCE_THRESHOLD` → 触发上传流程 #### 4.1.3 ROI裁剪策略 ```cpp struct ROIConfig { int x, y, width, height; // 裁剪区域 bool enabled; // 是否启用 cv::Rect getRect(int img_w, int img_h); // 安全边界裁剪 }; // 进站ROI: g_roi_in, 出站ROI: g_roi_out // 支持通过Web界面手动绘制调整,写回config.ini ``` #### 4.1.4 车牌类型映射 ```cpp static const std::vector TYPES = { "蓝牌", "黄牌", "白牌单层", "绿牌", "黑牌港澳", "香港单层", "香港双层", "澳门单层", "澳门双层", "黄牌双层" }; ``` #### 4.1.5 关键结构体 ```cpp struct CarPlateInfo { std::string code; // 车牌号 float confidence; // 识别置信度 std::string type; // 车牌类型(蓝牌/黄牌等) cv::Mat image_front; // 前向照片 cv::Mat image_side; // 侧向照片 int db_id; // 数据库记录ID bool is_inbound/outbound; // 进站/出站标记 time_t db_capture_time; // 照片保存时固定的capture_time std::atomic upload_in_progress{false}; // P0: 上传竞态保护 }; ``` ### 4.2 RTSP视频采集 #### 4.2.1 类设计 ```cpp class RTSPCapture { std::string rtspUrl_; DecodeMode decodeMode_; // AUTO / SOFT / DRM std::string drmDevice_; // 默认 /dev/dri/renderD128 std::string transport_; // tcp / udp AVFormatContext* fmtCtx_; AVCodecContext* codecCtx_; AVBufferRef* hwDeviceCtx_; // DRM硬件设备上下文 SwsContext* swsCtx_; // 像素格式转换上下文 std::deque frameQueue_; // 帧队列(最大10帧) std::thread captureThread_; }; ``` #### 4.2.2 FFmpeg解码流程 1. `avformat_open_input()` — 打开RTSP流 2. `avformat_find_stream_info()` — 获取流信息 3. `av_find_best_stream()` — 查找视频流 4. 根据codec判断:HEVC → 尝试DRM硬解,否则软解 5. `avcodec_open2()` — 打开解码器(thread_count=4) 6. `captureLoop()` 循环读取帧 → `decodeFrameToBGR()` → 入队 #### 4.2.3 DRM硬件加速配置 ```cpp bool initDRMHwAccel() { AVHWDeviceType type = av_hwdevice_find_type_by_name("drm"); av_hwdevice_ctx_create(&hwDeviceCtx_, type, "/dev/dri/renderD128", nullptr, 0); codecCtx_->hw_device_ctx = av_buffer_ref(hwDeviceCtx_); } // 解码后检测 AV_PIX_FMT_DRM_PRIME 格式 → av_hwframe_transfer_data 转CPU ``` #### 4.2.4 帧队列管理 - 队列容量:最大10帧,超出时丢弃最旧帧 - `getFrame()` — 取出并移除最新帧(供识别线程使用) - `peekFrame()` — 只读取不移除(供MJPEG视频流使用,避免与识别线程竞争) - 解码线程与消费线程通过 `std::mutex + condition_variable` 同步 #### 4.2.5 重连策略 - 读取失败(`av_read_frame` 返回错误)→ `cleanupFFmpeg()` → 等待3秒 → 重新 `initFFmpeg()` - 连接超时:5秒(`timeout=5000000`微秒) - RTSP传输:默认TCP(可配置UDP) - 低延迟优化:`fflags=nobuffer+genpts`, `flags=low_delay` #### 4.2.6 FFmpeg选项 ```cpp av_dict_set(&opts, "rtsp_transport", "tcp", 0); av_dict_set(&opts, "timeout", "5000000", 0); // 5秒超时 av_dict_set(&opts, "fflags", "nobuffer+genpts", 0); av_dict_set(&opts, "flags", "low_delay", 0); av_dict_set(&opts, "avoid_negative_ts", "make_zero", 0); av_dict_set(&opts, "err_detect", "ignore_err", 0); ``` ### 4.3 交替锁定机制 #### 4.3.1 核心规则(fix17版) | 状态转换 | 等待时间 | 超时行为 | |----------|----------|----------| | 进站完成 → 允许出站 | `in_out_interval` (默认300秒) | 等待≤2小时,超时清零 | | 出站完成 → 允许进站 | `in_out_interval + TIME_WINDOW` (默认600秒) | 等待≤2小时,超时清零 | | 出站无进站记录 | 永远拦截 | 必须先进站才能出站 | | 同方向连续操作 | 永远拦截 | 必须先完成对端操作 | #### 4.3.2 状态机 ``` INBOUND_ALLOWED → 上次出站完成,等待 in_out_interval+TIME_WINDOW 后允许进站 OUTBOUND_ALLOWED → 上次进站完成,等待 in_out_interval 后允许出站 无记录出站 → 永远拦截(必须先进站) 锁定超2小时 → 自动清零释放(视为车辆已离开) ``` #### 4.3.3 关键函数 ```cpp bool try_lock_in_station(const std::string& plate); // 原子锁定进站 bool try_lock_out_station(const std::string&plate); // 原子锁定出站 void lock_in_station(const std::string& plate); // 确认进站锁定 void lock_out_station(const std::string& plate); // 确认出站锁定 void unlock_rollback_station(const std::string& plate, bool is_in); // 失败回滚 void cleanup_station_cache(); // 2小时超时清理 void load_station_cache_from_db(); // 重启恢复 ``` #### 4.3.4 持久化 - 数据库表:`station_lock_cache` - 字段:`plate_number`, `last_in_time`, `last_out_time`, `current_mode` - 重启后自动从数据库恢复锁定状态,超时记录直接删除 ### 4.4 称重调度器 #### 4.4.1 耀华仪表通信协议 ``` 帧格式: 0x02 + sign(1字节) + digits(8字节) + status(1字节) + 0x03 帧长度: 12字节 数据解析: weight_kg = digits_value / 100.0 (单位: 分→公斤) 有效状态: 排除 status='G'/'H' 的帧 重量范围: g_weight_min_kg ~ g_weight_max_kg ``` #### 4.4.2 TCP连接管理 - 连接超时:`tcp_connect_timeout`(可配置) - 读取间隔:200ms - 断线重连:5秒延迟自动重连 - 接收缓冲区:1024字节 #### 4.4.3 稳定读数检测 ```cpp // 稳定性判定:连续 g_weight_stable_samples 个样本波动 ≤ g_weight_stable_threshold g_stability_samples.push_back(weight_kg); if (size > g_weight_stable_samples) g_stability_samples.erase(begin); // 候选数据:收集 g_weight_candidate_count 个有效读数 ``` #### 4.4.4 称重+MQTT异步任务队列 ```cpp struct WeightMqttTask { std::string plate; int station_type; // 1=进站, 2=出站 std::string tb_num; }; // 去重机制:30秒内同车牌只发送一次MQTT消息 static std::unordered_map g_mqtt_dedup_map; static const int MQTT_DEDUP_INTERVAL_SEC = 30; ``` ### 4.5 Web管理后台 #### 4.5.1 框架与配置 - **HTTP框架**: cpp-httplib(header-only) - **SSL支持**: OpenSSL,通过 `httplib::SSLServer` 创建HTTPS服务器 - **默认端口**: 8080(从config.ini `[server] ip` 字段解析) - **HTTP重定向**: 可选HTTP→HTTPS自动跳转(fix24-v19) #### 4.5.2 SSL性能优化 ```cpp // SSL会话缓存:10分钟有效期 SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_SERVER); SSL_CTX_set_timeout(ssl_ctx, 600); // 优先使用ECDHE+AESGCM/ChaCha20密码套件 SSL_CTX_set_cipher_list(ssl_ctx, "ECDHE-ECDSA-AES128-GCM-SHA256:" "ECDHE-RSA-AES128-GCM-SHA256:" "ECDHE-ECDSA-CHACHA20-POLY1305:..."); SSL_CTX_set_ecdh_auto(ssl_ctx, 1); // 优先X25519 ``` #### 4.5.3 路由表 | 路径 | 方法 | 功能 | 认证要求 | |------|------|------|----------| | `/` | GET | 监控首页 | 公开/基础 | | `/video` | GET | 4路视频预览 | 公开/基础 | | `/login` | GET | 登录页面 | 公开 | | `/config` | GET | 系统配置 | 管理员 | | `/locks` | GET | 锁定管理 | 公开/基础 | | `/system` | GET | 系统监控 | 公开/基础 | | `/api/monitor` | GET | 监控指标JSON | 公开 | | `/api/messages` | GET | 记录列表JSON | 公开 | | `/api/system/status` | GET | 系统资源状态 | 公开 | | `/api/frpc/status` | GET | frpc隧道状态 | 管理员 | | `/api/frpc/config` | GET/POST | frpc配置读写 | 超级管理员 | | `/api/auth/login` | POST | 登录认证 | 公开 | | `/api/auth/logout` | POST | 登出 | 已登录 | | `/api/auth/status` | GET | 会话状态 | 公开 | | `/api/station_locks` | GET | 锁定记录列表 | 公开 | | `/api/clear_lock` | POST | 清除锁定 | 管理员 | | `/api/video/{stream}` | GET | MJPEG视频流 | 公开 | | `/api/photo/{path}` | GET | 照片访问 | 公开 | #### 4.5.4 认证系统 **架构**: bcrypt密码哈希 + 内存+SQLite双层Session + CSRF Token + IP速率限制 ```cpp // auth_db.h — 数据库层 struct User { int id; std::string username; std::string password_hash; int role; // 0=user, 1=admin, 2=superadmin int enabled; int failed_attempts; time_t locked_until; }; struct Session { std::string session_id; // 64字符hex std::string csrf_token; // 64字符hex int user_id; std::string username; time_t created_at, last_active, expires_at; int remember; // 1=延长会话(7天) int user_role; }; // 数据库表: users, sessions, audit_log // WAL模式 + busy_timeout=5000ms ``` **Session管理**: - 空闲超时: 30分钟 (1800秒) - 绝对超时: 8小时 (28800秒) - "记住我": 7天 (604800秒) - 内存缓存 + SQLite持久化双层架构 **速率限制**: - 滑动窗口: 5分钟 (300秒) - 最大尝试: 5次/IP - 超限返回 HTTP 429 **CSRF保护**: - 每个Session生成64字符hex CSRF Token - 状态变更请求(POST/PUT/DELETE)必须携带 `X-CSRF-Token` 头 **安全响应头**: - `X-Content-Type-Options: nosniff` - `X-Frame-Options: DENY` - `X-XSS-Protection: 1; mode=block` - `Content-Security-Policy: default-src 'self'` ### 4.6 飞书/MQTT推送 #### 4.6.1 飞书消息 **API**: `POST https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id` **消息格式**: ```json { "receive_id": "{chat_id}", "msg_type": "text", "content": "{\"text\":\"工程名: xxx\\n车牌号: xxx\\n时间: xxx\\n...\"}" } ``` **重试策略**: - 最大重试: 4次(初始请求 + 3次重试) - 退让策略: 指数退让 2^n 秒 (2s, 4s, 8s) - 网络超时/连接失败 → 重试;业务错误 → 直接终止 **Token管理**: - `g_tenant_token`: 飞书租户访问令牌 - 过期时间: `g_token_expire_time` - 刷新缓冲: 300秒 (`TOKEN_REFRESH_BUFFER_SECONDS`) #### 4.6.2 MQTT客户端 **架构**: 基于 `mosquittopp` 类,继承回调机制 ```cpp class MqttClient : public mosqpp::mosquittopp { std::atomic connected{false}; std::atomic current_state{DISCONNECTED}; static const int MAX_RECONNECT_ATTEMPTS = 30; }; ``` **连接管理**: - 异步连接: `connect_async(host, port, keepalive=60)` - 状态机: DISCONNECTED → CONNECTING → CONNECTED → STABLE (5秒后) - 最大重连: 30次 - ClientID格式: `{client_id}_{pid}_{timestamp}` **消息发布**: - QoS: 1 (至少一次) - 发布后等待ACK确认 (`acked` 原子变量) - 连接断开后自动重连 **消息内容**: 车牌号 + 车牌颜色 + 车型 + 联单编号 + 进出站类型 ### 4.7 数据库操作 #### 4.7.1 SQLite表结构 **主表: `upload_records`** ```sql CREATE TABLE upload_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, create_time DATETIME DEFAULT (datetime('now', 'localtime')), capture_time INTEGER, -- fix24-v13: 抓拍时刻Unix时间戳 plate_number TEXT NOT NULL, tb_num TEXT NOT NULL, station_type INTEGER, -- 1=进站, 2=出站 lo_photo_path TEXT, hi_photo_path TEXT, lo_upload_status INTEGER DEFAULT 0, hi_upload_status INTEGER DEFAULT 0, feishu_status INTEGER DEFAULT 0, retry_count INTEGER DEFAULT 0 ); -- 索引 CREATE INDEX idx_plate_number ON upload_records(plate_number); CREATE INDEX idx_tb_num ON upload_records(tb_num); CREATE INDEX idx_create_time ON upload_records(create_time); CREATE INDEX idx_plate_station_time_status ON upload_records( plate_number, station_type, create_time, lo_upload_status, hi_upload_status); ``` **交替锁定表: `station_lock_cache`** ```sql CREATE TABLE station_lock_cache ( plate_number TEXT PRIMARY KEY, last_in_time INTEGER, last_out_time INTEGER, current_mode INTEGER -- 0=INBOUND_ALLOWED, 1=OUTBOUND_ALLOWED ); ``` **TIME_WINDOW缓存表: `time_window_cache`** ```sql -- 存储进站/出站工单配额缓存 CREATE TABLE time_window_cache (...); ``` **认证数据库表** (独立SQLite文件): ```sql CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, -- bcrypt hash role INTEGER DEFAULT 0, enabled INTEGER DEFAULT 1, failed_attempts INTEGER DEFAULT 0, locked_until INTEGER DEFAULT 0, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ); CREATE TABLE sessions (...); CREATE TABLE audit_log (...); ``` **系统监控数据库** (独立SQLite文件): - 路径: `data/system_metrics.db` - 用途: CPU/内存/温度/磁盘历史数据 #### 4.7.2 数据库配置 ```cpp sqlite3_busy_timeout(g_db, 100); // busy超时100ms SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX // WAL模式(认证数据库) // 认证数据库: busy_timeout=5000ms ``` #### 4.7.3 缓存策略 - TIME_WINDOW缓存: 内存 + SQLite 双层 - 交替锁定缓存: 内存(`g_plate_station_cache`) + SQLite 双层 - 联单缓存: `g_in_bill_cache` / `g_out_bill_cache` (内存) - 数据库自动清理: 每日23:10执行 `db_cleanup_old_records` + `db_vacuum` - 数据库健康检查: 每60秒 `SELECT 1` 探活,失败自动重连 - DB容量阈值: 500MB触发清理 (`DB_CLEANUP_THRESHOLD_MB`) ### 4.8 日志管理 #### 4.8.1 内存日志缓冲架构(fix24-v36) ``` stdout/stderr → pipe() → 读取线程 → 内存buffer(vector) │ ├── Web API读取(零磁盘I/O) └── 定时刷盘(默认23:20) → 磁盘文件 ``` #### 4.8.2 关键配置 | 参数 | 默认值 | 说明 | |------|--------|------| | `enabled` | true | 1=内存缓冲模式, 0=直写文件 | | `buffer_size_mb` | 20MB | 内存缓冲区上限 | | `flush_time` | 23:20 | 每日刷盘时间 | | `retention_days` | 30天 | 日志保留天数 | | `redirect_stdout` | true | 是否重定向stdout/stderr | | `auto_flush_on_exit` | true | 程序退出时自动刷盘 | #### 4.8.3 刷盘策略 - 定时刷盘: 到达 `flush_time` 时将内存buffer全量写入磁盘 - 退出刷盘: 程序优雅退出时执行最后一次刷盘 - 容量控制: 超过 `buffer_size_mb` 时丢弃最旧日志 - 文件清理: 凌晨2:00按 `retention_days` 清理过期日志文件 ### 4.9 frpc内网穿透管理 #### 4.9.1 状态获取 通过 frpc admin HTTP API 获取隧道状态: ```cpp std::string call_admin_api(const std::string &endpoint) { // URL: http://{admin_addr}:{admin_port}/api/serverinfo // Basic Auth: g_frpc_admin_user : g_frpc_admin_password // 超时: connect=1s, total=2s } ``` #### 4.9.2 进程检测(三重验证) 1. `systemctl is-active frpc` — systemd状态 2. `pgrep -x frpc` — 进程存在性 3. frpc admin API可达性 #### 4.9.3 配置热更新 - 读取: 直接读取 `frpc.toml` 文件内容 - 保存: 自动备份旧文件到 `backup_path` → 写入新配置 - 重启: `systemctl restart frpc` → 等待 `restart_check_timeout` 秒 - 自动回滚: 重启后检测进程存活,失败则恢复备份配置 #### 4.9.4 安全限制 - **admin API仅允许本机127.0.0.1调用** - 禁止通过frp隧道暴露到公网 - 受保护隧道列表: `g_frpc_protected_tunnels`(不可删除) ### 4.10 SSH密钥管理 #### 4.10.1 功能 - 读取/添加/删除 `authorized_keys` 文件中的SSH公钥 - 支持密钥类型验证: ssh-rsa, ssh-dss, ssh-ed25519, ecdsa-sha2-* #### 4.10.2 密钥格式验证 ```cpp bool validate_key_format(const std::string &key_line) { // 格式: type base64_data [comment] // type必须是已知SSH密钥类型 // base64数据至少20字符 } ``` #### 4.10.3 配置 - 密钥文件路径: `g_ssh_keys_path` (默认 `~/.ssh/authorized_keys`) - 通过Web管理界面操作 --- ## 5. 配置文件详解 ### 5.1 config.ini 完整配置段 #### [config] — 核心业务配置 | 参数 | 默认值 | 说明 | |------|--------|------| | `project_name` | (必填) | 工程名 | | `point_number` | (必填) | 点位编号 | | `throughway` | (必填) | 出入口编号 | | `rtsp_url_front_in` | (必填) | 进站前向摄像头RTSP URL | | `rtsp_url_front_out` | (必填) | 出站前向摄像头RTSP URL | | `rtsp_url_side_in` | (必填) | 进站侧向摄像头RTSP URL | | `rtsp_url_side_out` | (必填) | 出站侧向摄像头RTSP URL | | `app_api` | (必填) | 平台API地址 | | `app_key` | (必填) | 平台API Key | | `app_secret` | (必填) | 平台API Secret | | `DEBUG_LOG` | 0 | 调试日志(1=开启, 0=正式) | | `TestFlag` | 0 | 测试环境标记 | | `wType` | 1 | 工地类型(1=工地, 2=码头, 3=回填) | | `TIME_WINDOW` | 10 | 时间窗口(分钟),控制进站/出站配额 | | `MAX_PHOTO_GROUPS` | 2 | 每侧最大照片组数 | | `in_out_interval` | 5 | 进出站间隔(分钟),转换为秒: ×60 | | `PLATE_CONFIDENCE_THRESHOLD` | 0.8 | 上传触发置信度阈值 | | `PLATE_LOG_THRESHOLD` | 0.5 | 日志记录置信度阈值 | | `AlternatingMerge` | 0 | 交替锁定开关(1=启用) | | `roi_in_x/y/w/h` | 0 | 进站ROI区域坐标 | | `roi_in_enabled` | 0 | 进站ROI启用(1=启用) | | `roi_out_x/y/w/h` | 0 | 出站ROI区域坐标 | | `roi_out_enabled` | 0 | 出站ROI启用 | | `roi_debug_enabled` | 0 | ROI调试模式 | | `hw_decode_mode` | auto | 解码模式(auto/soft/drm) | | `hw_decode_device` | /dev/dri/renderD128 | DRM设备路径 | | `rtsp_transport` | tcp | RTSP传输协议(tcp/udp) | | `PhotoMaxCapacityMB` | 1024 | 照片存储容量阈值(MB) | | `LogRetentionDays` | 30 | 日志保留天数 | #### [server] — Web服务配置 | 参数 | 默认值 | 说明 | |------|--------|------| | `ip` | http://0.0.0.0:8080 | 服务地址(端口从此字段解析) | #### [ssl] — HTTPS/SSL配置 | 参数 | 默认值 | 说明 | |------|--------|------| | `enabled` | 0 | SSL启用(1=启用) | | `cert_path` | (空) | SSL证书文件路径 | | `key_path` | (空) | SSL私钥文件路径 | #### [auth] — 认证系统配置 | 参数 | 默认值 | 说明 | |------|--------|------| | `enabled` | 0 | 认证启用(1=启用) | | `db_path` | data/auth.db | 认证数据库路径(相对pathComm) | #### [feishu] — 飞书通知配置 | 参数 | 默认值 | 说明 | |------|--------|------| | `APP_ID` | (必填) | 飞书应用ID | | `APP_SECRET` | (必填) | 飞书应用密钥 | | `CHAT_ID` | (必填) | 飞书群聊ID | | `TIMEOUT_SECOND` | 10 | HTTP超时(秒) | | `WarningSigns` | 0 | 抑制紧急告警(1=抑制) | #### [weight] — 称重系统配置 | 参数 | 默认值 | 说明 | |------|--------|------| | `flagWeight` | 0 | 称重启用(1=启用) | | `weight_threshold_in` | (必填) | 进站重量阈值(公斤) | | `weight_threshold_out` | (必填) | 出站重量阈值(公斤) | | `default_entry_weight` | 0 | 默认进站重量 | | `default_exit_weight` | 0 | 默认出站重量 | | `weight_detection_time` | 10 | 称重检测时间(秒) | | `tcp_connect_timeout` | 5 | TCP连接超时(秒) | | `weight_server_ip` | (必填) | 仪表IP地址 | | `weight_server_port` | (必填) | 仪表端口 | | `MAX_WEIGHT` | 100000 | 最大有效重量(公斤) | | `STABLE_SAMPLE_COUNT` | 5 | 稳定判定样本数 | | `STABLE_THRESHOLD_KG` | 50 | 稳定波动阈值(公斤) | | `CANDIDATE_DATA_COUNT` | 10 | 候选数据数量 | | `MAX_UPLOAD_RETRIES` | 3 | 最大重试次数 | | `RETRY_DELAY_MS` | 1000 | 重试延迟(毫秒) | #### [MQTT] — MQTT配置 | 参数 | 默认值 | 说明 | |------|--------|------| | `MQTT_HOST` | (空) | MQTT Broker地址 | | `MQTT_PORT` | 1883 | MQTT端口 | | `MQTT_USER` | (空) | 用户名 | | `MQTT_PASS` | (空) | 密码 | | `MQTT_TOPIC` | (必填) | 发布主题 | | `MQTT_CLIENT_ID` | PlateRecApp | 客户端ID | | `PLATE_COLOR` | 黄色 | 默认车牌颜色 | | `VEHICLE_TYPE` | 渣土车 | 默认车型 | #### [system] — 系统监控配置 | 参数 | 默认值 | 说明 | |------|--------|------| | `monitor_interval` | 30 | 采样间隔(秒) | | `temp_alert_threshold` | 80 | 温度告警阈值(°C) | | `cpu_alert_threshold` | 90 | CPU告警阈值(%) | | `db_path` | data/system_metrics.db | 监控数据库路径 | | `history_retention_days` | 30 | 全量数据保留天数 | | `aggregation_retention_days` | 365 | 聚合数据保留天数 | | `flush_time` | 23:20 | 每日刷盘时间 | #### [frpc] — frpc管理配置 | 参数 | 默认值 | 说明 | |------|--------|------| | `config_path` | /etc/frp/frpc.toml | frpc配置文件路径 | | `backup_path` | (自动) | 备份文件路径 | | `protected_tunnels` | (空) | 受保护隧道列表(逗号分隔) | | `restart_check_timeout` | 10 | 重启后等待检测时间(秒) | | `auto_rollback` | 1 | 失败自动回滚(1=启用) | | `web_enabled` | 1 | Web管理页面开关 | | `admin_addr` | 127.0.0.1 | frpc admin API地址 | | `admin_port` | 7400 | frpc admin API端口 | | `admin_user` | (空) | admin用户名 | | `admin_password` | (空) | admin密码 | #### [terminal] — 终端/SSH配置 | 参数 | 默认值 | 说明 | |------|--------|------| | `ttyd_port` | 7681 | ttyd Web终端端口 | | `ttyd_credential` | (空) | ttyd认证凭证(user:pass) | | `ttyd_max_clients` | 5 | 最大客户端数 | | `ssh_keys_path` | ~/.ssh/authorized_keys | SSH公钥文件路径 | #### [log] — 日志管理配置 | 参数 | 默认值 | 说明 | |------|--------|------| | `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 | | `auto_flush_on_exit` | 1 | 退出时自动刷盘 | --- ## 6. 构建系统 ### 6.1 CMakeLists.txt 详细解析 ```cmake cmake_minimum_required(VERSION 3.25.1) project(HyperLPR3-LinuxAPP VERSION 1.0.0 LANGUAGES CXX C) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) ``` **构建类型默认**: Release ### 6.2 编译优化标志 | 标志 | 作用 | |------|------| | `-O3` | 最高级别速度优化 | | `-s` | 去除符号表(strip) | | `--as-needed` | 只链接实际使用的符号 | | `--gc-sections` | 删除未使用的段 | | POST_BUILD strip | 编译后strip进一步减小体积 | **可选优化**(注释状态): - `-Os`: 体积优先优化 - `-flto`: 链接时优化(减小10-30%体积) - `-fvisibility=hidden`: 隐藏符号 ### 6.3 依赖库查找策略 1. **pkg-config优先**: FreeType2, SQLite3, FFmpeg各模块 2. **CMake find_package**: OpenCV, CURL, OpenSSL 3. **手动查找回退**: cJSON(先pkg-config,再find_library,最后硬编码路径) 4. **动态链接优先**: `BUILD_SHARED_LIBS ON`,可通过 `-DUSE_STATIC_LIBS=ON` 切换静态 ### 6.4 源码文件列表 ``` src/ ├── main.cpp # 主入口 + 调度 ├── common.cpp/h # 全局类型定义 ├── config.cpp/h # 配置解析 ├── plate_recognizer.cpp/h # 车牌识别引擎 ├── rtsp_capture.cpp/h # RTSP视频采集 ├── web_server.cpp/h # Web管理后台 ├── database.cpp/h # SQLite数据库 ├── network_client.cpp/h # HTTP API通信 ├── feishu_client.cpp/h # 飞书推送 ├── mqtt_client.cpp/h # MQTT客户端 ├── weight_scale.cpp/h # 称重系统 ├── station_lock.cpp/h # 交替锁定 ├── log_manager.cpp/h # 日志管理 ├── frpc_manager.cpp/h # frpc管理 ├── ssh_manager.cpp/h # SSH管理 ├── system_monitor.cpp/h # 系统监控 ├── system_metrics_manager.cpp/h # 监控历史数据 ├── monitor.cpp/h # 指标打印 ├── utils.cpp/h # 工具函数 ├── auth_api.cpp/h # 认证API ├── auth_crypto.cpp/h # bcrypt + 安全随机 ├── auth_db.cpp/h # 认证数据库 ├── auth_session.cpp/h # 会话管理 ├── auth_middleware.cpp/h # 认证中间件 ├── auth_rate_limiter.h # 速率限制(header-only) └── bcrypt/ # bcrypt实现 ``` --- ## 7. 部署方案 ### 7.1 目录结构 ``` /opt/openAI/project/003.PlateRecAPP/ # pathComm (可执行文件所在目录) ├── PlateRecApp # 可执行文件 ├── config.ini # 主配置文件 ├── config.txt # MD5校验配置 ├── simsun.ttc # 中文字体文件 ├── hyperlpr3/ │ └── resource/models/r2_mobile/ # HyperLPR3模型文件 ├── assets/web/ # Web前端HTML页面 │ ├── login.html │ ├── system.html │ └── ... ├── data/ # 数据库目录 │ ├── upload_records.db # 主数据库 │ ├── auth.db # 认证数据库 │ └── system_metrics.db # 监控数据库 ├── PlateJPG/ # 照片存储目录 ├── third_party/ # 第三方依赖 └── sync_to_gogs.sh # Gogs同步脚本 ``` ### 7.2 systemd service 配置 ```ini [Unit] Description=PlateRecApp License Plate Recognition After=network.target [Service] Type=simple WorkingDirectory=/opt/openAI/project/003.PlateRecAPP ExecStart=/opt/openAI/project/003.PlateRecAPP/PlateRecApp config.ini Restart=always RestartSec=5 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target ``` ### 7.3 文件权限 - 可执行文件: `755` - 配置文件: `644`(含敏感信息,不可公开) - 数据库目录: `755` - 照片目录: `755` ### 7.4 启动流程 ``` 1. 解析可执行文件路径 → 确定 pathComm 2. 读取CPU序列号 → MD5三重校验 → 验证config.txt 3. 解析config.ini → 加载所有配置 4. 计算时区偏移 → 初始化cURL 5. 初始化SQLite数据库 → 加载缓存 6. 初始化HyperLPR3模型 7. 创建4路RTSPCapture → 启动采集线程 8. 启动识别工作线程 9. 初始化认证系统 → 启动Web服务器 10. 启动称重系统 → 启动MQTT客户端 11. 进入主循环(100ms间隔) ``` --- ## 8. 安全策略 ### 8.1 认证机制 - **bcrypt密码哈希**: 使用系统libcrypt的`crypt_r`,成本因子自动调整 - **Session管理**: 64字符hex随机session_id,内存+SQLite双层 - **CSRF防护**: 每个Session独立CSRF Token,状态变更请求必须验证 - **IP速率限制**: 滑动窗口5分钟/5次,超限返回429 - **角色权限**: user(0) / admin(1) / superadmin(2),不同页面/API按角色控制 - **账户锁定**: 连续失败登录自动锁定(`failed_attempts` + `locked_until`) - **审计日志**: 所有登录/登出/失败/超时事件记录到 `audit_log` 表 ### 8.2 密码存储 - 算法: bcrypt(通过 `crypt_r` 系统调用) - 自动salt生成,每次哈希结果不同 - 验证使用 `secure_compare` 时间安全比较(防时序攻击) ### 8.3 HTTPS/SSL - 可选启用(config.ini `[ssl] enabled=1`) - 证书+私钥 PEM格式 - SSL会话缓存(10分钟) - 优先ECDHE+AESGCM/ChaCha20密码套件 - HTTP→HTTPS自动重定向(可选) ### 8.4 frpc admin API 限制 - **仅允许本机127.0.0.1调用** - 禁止通过frp隧道暴露到公网 - Basic Auth认证保护 - 超时严格限制(connect=1s, total=2s) ### 8.5 路径安全 - Web文件访问: `is_safe_file_path()` 防路径穿越 - 禁止 `..` / `/` / `\` 字符 - `realpath()` 验证绝对路径在基础目录内 ### 8.6 配置文件校验 - CPU序列号 + MD5三重校验 → 防止未授权设备运行 - config.ini 含敏感信息,.gitignore 已排除 --- ## 9. 版本变更记录 详见 [CHANGELOG.md](CHANGELOG.md) 主要版本里程碑: - **v43**: RTSP ROI裁剪、DRM硬件解码、4路独立视频流 - **v43.2 fix11-fix17**: 交替锁定机制完善(非对称等待、2小时超时清零) - **fix18**: 称重+MQTT主进程合并、日志logrotate - **fix19**: 文件清理重构(照片按容量、日志按天数) - **fix20**: 推理耗时追踪、MQTT完整迁移 - **fix21-fix23**: P0时间对齐bug修复、抓拍时间全链路一致 - **fix24**: 登录认证系统、Web管理功能(系统监控+frpc+SSH+日志)、HTTPS/SSL --- ## 10. 待改进项 ### 10.1 技术债务 1. **配置热更新不完整**: ROI可热写回config.ini,但其他配置修改后需重启 2. **认证系统JSON解析**: 使用手写简单解析而非cJSON,鲁棒性待提升 3. **全局变量过多**: 大量 `extern` 全局变量,耦合度高,应逐步迁移到单例/依赖注入 4. **Web前端内嵌**: HTML直接写在C++字符串中,维护困难,应迁移到独立模板 5. **缺少单元测试**: 无自动化测试框架覆盖核心逻辑 6. **数据库迁移**: 旧数据库需手动cp迁移,缺少自动迁移工具 7. **日志内存缓冲**: 超过buffer_size时丢弃旧日志,可能丢失关键信息 ### 10.2 改进方向 1. **gRPC替代HTTP**: 平台API通信改用gRPC提升效率 2. **TensorRT替代MNN**: 利用NPU/GPU进一步加速推理 3. **WebSocket替代轮询**: Web监控数据改为实时推送 4. **Docker化部署**: 简化依赖安装和环境配置 5. **OTA远程更新**: 结合frpc实现远程固件升级 6. **多站点支持**: 单进程管理多个出入口点位 --- > **本文档持续更新** > 最后更新: 2026-07-06 | 基于 PlateRecApp v43.2 fix24 源码