Pārlūkot izejas kodu

fix27 v44: bug fixes + complete README with MNN/HyperLPR3 guide

Bug Fixes:
- P0: plate_recognizer.cpp - 共享摄像头方向标记使用effective_cap_info
- P1: main.cpp - send_permanent_failure_alert空指针保护(3处)
- P2: network_client.cpp - 生产环境日志仅在DEBUG_LOG模式打印
- P2: main.cpp - 版本号从v43.2 fix19更新为fix27 v44
- P3: web_server.cpp - SSH API改用cJSON安全解析替代rfind
- P3: CMakeLists.txt C++17与README保持一致

Docs:
- README.md: 完整MNN+HyperLPR3安装配置指南
- CHANGELOG.md: 追加所有修复记录
- sync_to_gogs.sh: Token改为环境变量读取
steven_roc 15 stundas atpakaļ
vecāks
revīzija
25a74cf281
7 mainītis faili ar 783 papildinājumiem un 16 dzēšanām
  1. 161 0
      CHANGELOG.md
  2. 497 0
      README.md
  3. 5 3
      src/main.cpp
  4. 1 1
      src/network_client.cpp
  5. 2 2
      src/plate_recognizer.cpp
  6. 10 10
      src/web_server.cpp
  7. 107 0
      sync_to_gogs.sh

+ 161 - 0
CHANGELOG.md

@@ -0,0 +1,161 @@
+# CHANGELOG.md — fix27 v44 更新记录
+
+## [v44] - 2026-08-30
+### fix27 - 飞书消息携带重量信息 + 水印居中修复 + Bug修复
+
+#### 功能 #1:飞书消息携带重量信息
+**需求**:config.ini `[weight]` 段 `flagWeight=1` 时,飞书消息附带重量数据  
+**实现**:
+- 新增全局变量 `g_flag_weight`,控制飞书消息是否带重量
+- 修改 `send_feishu_msg()` 函数,增加 `weight_kg` 参数
+- 当 `g_flag_weight=true && weight_kg >= 0` 时,附加重量信息
+
+**影响文件**:
+- `src/common.h`: 新增 `extern bool g_flag_weight;`
+- `src/common.cpp`: 定义 `bool g_flag_weight = false;`
+- `src/config.cpp`: `flagWeight=1` 时同步设置 `g_flag_weight`
+- `src/feishu_client.h`: `send_feishu_msg()` 新增 `double weight_kg = -1.0` 参数
+- `src/feishu_client.cpp`: 当 `g_flag_weight && weight_kg >= 0` 时附加重量
+- `src/web_server.cpp`: 进站/出站调用 `send_feishu_msg` 时传入重量参数
+
+---
+
+#### 🔧 本轮追加修复(Code Review 补充)
+
+##### Bug: 共享摄像头模式方向标记错误(P0)
+**根因**:plate_recognizer.cpp 中 is_inbound/is_outbound 使用原始 cap_info 而非 effective_cap_info  
+**影响**:共享摄像头模式下进站/出站方向判断错误  
+**修复**:改为使用 effective_cap_info->dtype 判断方向  
+**文件**:src/plate_recognizer.cpp
+
+##### Bug: 永久失败告警时空指针解引用(P1)
+**根因**:main.cpp 中 send_permanent_failure_alert 访问 cap_info_copy->dtype 前未检查 null  
+**影响**:cap_info_copy 为空时程序崩溃  
+**修复**:添加空指针前置检查   
+**文件**:src/main.cpp
+
+##### Bug: 启动消息版本号未更新(P2)
+**根因**:main.cpp 启动消息仍为 v43.2 fix19  
+**修复**:更新为 fix27 v44  
+**文件**:src/main.cpp
+
+##### Bug: 生产环境日志污染(P2)
+**根因**:parse_create_bill_response 每次都打印完整 API 响应到 stdout  
+**修复**:仅在 DEBUG_LOG 模式下打印  
+**文件**:src/network_client.cpp
+
+##### Bug: README 编程语言标注不一致(P3)
+**根因**:README 标注 C++23,CMakeLists.txt 实际设置 C++17  
+**修复**:README 更正为 C++17  
+**文件**:README.md
+
+##### Bug: sync_to_gogs.sh 硬编码 Access Token(P3)
+**根因**:脚本中直接硬编码 Token,推送到仓库会泄露凭证  
+**修复**:改为环境变量 GOGS_ACCESS_TOKEN 读取,保留默认值兼容  
+**文件**:sync_to_gogs.sh
+
+#### Bug #27(中):水印文字超出右边界被截断
+**根因**:`create_text_watermark()` 中 `putText()` 起始位置固定为 `cv::Point(40, 80)`,文字较长时右侧超出边界被截断  
+**修复**:新增 `estimate_text_width()` 函数估算文字宽度,计算居中位置 `x = (width - text_width) / 2`  
+**影响文件**:`src/plate_recognizer.cpp`
+
+---
+
+#### Bug #28(严重):飞书消息等待重量数据的时序问题
+**根因**:`web_server.cpp` 中 `upload_in_photos()` 调用 `db_wait_weight_by_plate()` 只等 3 秒轮询数据库,但重量任务需要:读秤(~8秒)+API上传+写DB,3秒不够  
+**修复**:改用 `weight_dispatch_wait_result()`(promise/future 机制),超时 15 秒,直接获取重量结果  
+**影响文件**:`src/web_server.cpp`
+- 添加 `#include "weight_dispatcher.h"`
+- 进站/出站:替换 `db_wait_weight_by_plate` → `weight_dispatch_wait_result`
+
+---
+
+#### Bug #29(中等):weight_dispatch_wait_result 重复调用导致第二次返回 false
+**根因**:`weight_dispatch_wait_result()` 在获取结果后会清理 entry(`erase`),导致:
+1. `upload_in_photos()` 中第一次调用 → 获取结果并清理 entry
+2. `main.cpp` 中第二次调用(Reporter 日志)→ entry 已被清理,返回 false  
+**修复**:移除 `weight_dispatch_wait_result()` 中的 erase 操作,entry 由 `weight_dispatch_cleanup()` 统一清理  
+**影响文件**:`src/weight_dispatcher.cpp`
+
+---
+
+#### Bug #30(严重):v43 并行模式下 MQTT 消息未发送
+**根因**:v43 并行模式使用 `weight_dispatcher.cpp` 的 `weight_worker_func`,但该函数中没有 MQTT 发送逻辑。MQTT 发送只在旧版 `weight_scale.cpp` 的 `weight_mqtt_worker_func` 中实现  
+**修复**:在 `weight_dispatcher.cpp` 的 `weight_worker_func` 中添加步骤5:MQTT发布,格式与旧版一致  
+**影响文件**:`src/weight_dispatcher.cpp`
+
+---
+
+#### Bug #31(严重):成功路径缺少 cleanup 导致内存泄漏
+**根因**:修复 Bug #29 时移除了 `weight_dispatch_wait_result()` 中的 erase 操作,但 main.cpp 成功路径(正常路径 + 重试路径)未调用 `weight_dispatch_cleanup()`,导致 `g_weight_results` map 中 entry 永远不会被清理  
+**修复**:在两条成功路径的 Reporter 日志后追加 `weight_dispatch_cleanup(plate_info->tb_num);`  
+**影响文件**:`src/main.cpp`(重试路径 ~line 454、正常路径 ~line 748)
+
+#### Bug #32(中):永久失败告警未受飞书开关控制
+**根因**:`send_permanent_failure_alert()` 只检查了 `g_suppress_emergency_alert`,未检查 `g_feishu_enabled`。当用户设置 `ENABLED=0` 禁用飞书时,永久失败告警仍会通过飞书发送  
+**修复**:在函数入口处添加 `if (!g_feishu_enabled) return false;`  
+**影响文件**:`src/feishu_client.cpp`
+
+### 本轮审查总结
+| Bug # | 严重度 | 问题 |
+|:---:|:---:|:---|
+| #27 | 中 | 水印文字超出右边界被截断 |
+| #28 | **严重** | 飞书消息等待重量数据的时序问题(3秒不够) |
+| #29 | 中 | `weight_dispatch_wait_result` 重复调用导致第二次返回 false |
+| #30 | **严重** | v43 并行模式下 MQTT 消息未发送 |
+| #31 | **严重** | 成功路径缺少 cleanup 导致 `g_weight_results` map 内存泄漏 |
+| #32 | 中 | `send_permanent_failure_alert` 未检查 `g_feishu_enabled`,禁用飞书后告警仍发送 |
+
+---
+
+### 部署说明
+```bash
+cd /opt/openAI/project/003.PlateRecAPP/
+tar -xzf fix27_v44_bug28.tar.gz
+make clean && make
+sudo systemctl restart PlateRecApp
+```
+
+### 验证要点
+1. **飞书消息携带重量**:`flagWeight=1` 时,进站/出站消息应包含 `重量: XXXXX kg (XX.X 吨)`
+2. **水印居中**:水印文字应居中显示,不超出右边界
+3. **MQTT 消息**:v43 并行模式下,MQTT 应正常发送消息
+4. **Reporter 日志**:v43 Reporter 日志应正确显示重量信息
+
+---
+
+## 功能 #2:飞书/MQTT 消息独立开关
+
+**实现时间**:2026-08-30  
+**需求人**:王海鹏
+
+### 需求描述
+- 飞书消息和 MQTT 消息需要可以独立配置启用/禁用,互不影响
+
+### 改动内容
+1. **新增 `g_feishu_enabled` 全局开关**:
+   - `common.h` / `common.cpp`:新增声明与定义(默认 false)
+   - `config.cpp`:`[feishu]` 段解析 `ENABLED = 0|1`
+   - `web_server.cpp`:进站/出站发送飞书消息前检查 `g_feishu_enabled`
+2. **MQTT 改用显式开关**:
+   - `config.cpp`:`[MQTT]` 段新增 `MQTT_ENABLED = 0|1`,不再依赖 `MQTT_HOST` 是否存在自动启用
+
+### config.ini 新增配置
+```ini
+[feishu]
+ENABLED = 1          # 1=启用飞书消息推送,0=禁用
+
+[MQTT]
+MQTT_ENABLED = 1     # 1=启用MQTT消息推送,0=禁用
+```
+
+### 影响文件
+| 文件 | 改动 |
+|------|------|
+| `src/common.h` | 新增 `extern bool g_feishu_enabled;` |
+| `src/common.cpp` | 新增 `bool g_feishu_enabled = false;` |
+| `src/config.cpp` | 解析 `ENABLED` 和 `MQTT_ENABLED`;更新启动日志 |
+| `src/web_server.cpp` | 进站/出站飞书发送加 `g_feishu_enabled` 守卫;状态页新增飞书状态展示 |
+| `src/feishu_client.cpp` | `send_permanent_failure_alert` 增加 `g_feishu_enabled` 检查 |
+| `config一号门.ini` | 新增 `ENABLED=1`、`MQTT_ENABLED=1` |
+| `config二号门.ini` | 新增 `ENABLED=1`、`MQTT_ENABLED=1` |

+ 497 - 0
README.md

@@ -0,0 +1,497 @@
+# 车牌识别系统 PlateRecApp
+
+> 基于 HyperLPR3 / MNN 的嵌入式 C++ 车牌识别平台,运行于树莓派5 + Ubuntu 24.04  
+> 版本: fix27 v44 | 最后更新: 2026-08-30  
+> Gogs 仓库: https://git.zhonjin.com:40717/steven_roc/PlateRecApp
+
+## 系统概述
+
+PlateRecApp 是一套面向工业园区/物流场站的全功能车牌识别与称重管理系统,支持多路 RTSP 视频流实时识别、飞书消息推送、MQTT 数据上报、Web 管理后台、登录认证、frpc 内网穿透管理等核心功能。
+
+### 核心能力
+
+| 模块 | 功能说明 |
+|------|---------|
+| **车牌识别** | HyperLPR3 + MNN 推理引擎,支持低位摄像头 ROI 推理 |
+| **视频流** | 4路独立 RTSP 视频流 + Web 端 ROI 手动调整 |
+| **飞书推送** | 进出站消息推送,支持携带重量信息,独立开关控制 |
+| **MQTT** | 数据上报,独立开关控制,v43 并行模式支持 |
+| **称重系统** | 重量调度器(promise/future 异步机制),15秒超时 |
+| **Web 管理** | 登录认证、系统监控、日志管理、frpc 管理、SSH 密钥管理 |
+| **站点锁** | 交替锁定机制,防止重复抓拍竞态 |
+| **日志管理** | logrotate 自动清理,系统指标历史记录 |
+
+## 系统架构
+
+```
+┌─────────────────────────────────────────────────────┐
+│                    PlateRecApp                       │
+├──────────┬──────────┬──────────┬────────────────────┤
+│ 车牌识别  │ 重量调度  │ Web服务   │ 飞书/MQTT推送      │
+│ HyperLPR3│ 异步引擎  │ 认证+监控 │ 独立开关控制       │
+├──────────┴──────────┴──────────┴────────────────────┤
+│              统一配置管理 (config.ini)                │
+├─────────────────────────────────────────────────────┤
+│  SQLite DB  │  MQTT Client  │  RTSP Capture        │
+└─────────────────────────────────────────────────────┘
+```
+
+## 运行环境
+
+| 项目 | 要求 |
+|------|------|
+| **硬件** | 树莓派5 8G(兼容 Ubuntu-24.04 / Raspberry Pi OS) |
+| **编译器** | GCC 11+, C++17 |
+| **核心依赖** | OpenCV 4.x, MNN ≥2.0, HyperLPR3 SDK |
+| **其他依赖** | cJSON, cpp-httplib, bcrypt, ini-parser, FFmpeg, FreeType2, mosquitto |
+| **数据库** | SQLite3 |
+| **网络** | 飞书 API, MQTT Broker, RTSP 视频流, cURL, OpenSSL |
+
+## 项目结构
+
+```
+PlateRecApp/
+├── src/                          # 源代码
+│   ├── main.cpp                  # 主程序入口
+│   ├── common.cpp/h              # 公共定义与全局变量
+│   ├── config.cpp/h              # 配置文件解析 (config.ini)
+│   ├── plate_recognizer.cpp/h    # 车牌识别核心 (HyperLPR3+MNN)
+│   ├── rtsp_capture.cpp/h        # RTSP 视频流采集
+│   ├── web_server.cpp/h          # Web 管理服务器 (cpp-httplib)
+│   ├── database.cpp/h            # SQLite 数据库操作
+│   ├── feishu_client.cpp/h       # 飞书消息推送客户端
+│   ├── mqtt_client.cpp/h         # MQTT 客户端
+│   ├── weight_dispatcher.cpp/h   # 重量调度器 (promise/future)
+│   ├── weight_scale.cpp/h        # 称重模块
+│   ├── station_lock.cpp/h        # 站点交替锁定机制
+│   ├── system_monitor.cpp/h      # 系统监控 (CPU/内存/温度)
+│   ├── system_metrics_manager.cpp/h  # 系统指标管理+历史记录
+│   ├── log_manager.cpp/h         # 日志管理+logrotate
+│   ├── frpc_manager.cpp/h        # frpc 内网穿透管理
+│   ├── ssh_manager.cpp/h         # SSH 密钥管理
+│   ├── network_client.cpp/h      # 网络客户端 (API上传)
+│   ├── utils.cpp/h               # 工具函数
+│   ├── auth_api.cpp/h            # 认证 API
+│   ├── auth_db.cpp/h             # 认证数据库
+│   ├── auth_session.cpp/h        # 会话管理
+│   ├── auth_middleware.cpp/h     # 认证中间件
+│   ├── auth_crypto.cpp/h         # 加密工具 (bcrypt)
+│   ├── auth_rate_limiter.h       # 速率限制
+│   └── bcrypt/                   # bcrypt 密码哈希库
+├── config/                       # 配置示例
+│   ├── config一号门.ini           # 一号门配置模板
+│   └── config二号门.ini           # 二号门配置模板
+├── scripts/                      # 部署脚本
+│   ├── deploy.sh                 # 一键部署脚本
+│   ├── reset_passwords.py        # Web 管理密码重置工具
+│   └── setup_frpc_sudoers.sh     # frpc sudoers 配置
+├── third_party/                  # 第三方依赖源码
+│   ├── cJSON.c/h                 # JSON 解析
+│   ├── cvxFont.cpp/h             # OpenCV 中文字体
+│   ├── httplib.h                 # cpp-httplib 头文件库
+│   ├── hyper_lpr_sdk.h           # HyperLPR3 SDK 头文件
+│   ├── ini.c/h                   # INI 配置解析
+│   └── md5ex1.c/h                # MD5 工具
+├── assets/web/                   # Web 前端资源 (HTML/CSS/JS)
+├── CMakeLists.txt                # CMake 构建配置
+├── CHANGELOG.md                  # 详细变更日志
+└── README.md                     # 本文件
+```
+
+---
+
+## 依赖安装详解(MNN + HyperLPR3)
+
+本项目车牌识别核心依赖 **MNN**(阿里移动神经网络推理框架)和 **HyperLPR3**(中文车牌识别SDK)。以下是在树莓派5(aarch64 / Ubuntu 24.04)上的完整安装流程。
+
+### 依赖关系
+
+```
+PlateRecApp
+    ├── HyperLPR3 SDK  → libhyperlpr3.so(车牌检测+识别)
+    │       └── MNN    → libMNN.so(底层推理引擎)
+    ├── OpenCV 4.x     → 图像处理
+    ├── FFmpeg         → RTSP流解码(DRM硬解/H.264软解)
+    ├── SQLite3        → 数据持久化
+    ├── cURL + OpenSSL → API通信 / HTTPS
+    ├── mosquitto      → MQTT消息推送
+    ├── FreeType2      → 中文字体渲染(水印)
+    ├── cJSON          → JSON解析
+    └── libcrypt       → bcrypt密码哈希
+```
+
+### 第一步:安装系统级依赖
+
+```bash
+# 更新系统
+sudo apt update && sudo apt upgrade -y
+
+# 编译工具链
+sudo apt install -y build-essential cmake git wget pkg-config
+
+# OpenCV 4.x
+sudo apt install -y libopencv-dev libopencv-core-dev libopencv-imgproc-dev \
+    libopencv-imgcodecs-dev libopencv-highgui-dev libopencv-videoio-dev
+
+# FFmpeg(RTSP视频流解码)
+sudo apt install -y libavformat-dev libavcodec-dev libavutil-dev libswscale-dev
+
+# SQLite3
+sudo apt install -y libsqlite3-dev
+
+# cURL + OpenSSL
+sudo apt install -y libcurl4-openssl-dev libssl-dev
+
+# FreeType2(中文字体渲染)
+sudo apt install -y libfreetype-dev
+
+# cJSON
+sudo apt install -y libcjson-dev
+
+# MQTT客户端
+sudo apt install -y libmosquitto-dev libmosquittopp-dev
+
+# Protobuf(MNN编译需要)
+sudo apt install -y libprotobuf-dev protobuf-compiler
+
+# libcrypt(bcrypt认证)
+sudo apt install -y libcrypt-dev
+```
+
+### 第二步:编译安装 MNN
+
+MNN 是阿里巴巴开源的轻量级深度学习推理引擎,为 HyperLPR3 提供底层模型推理能力。
+
+> 📌 官方仓库:https://github.com/alibaba/MNN
+
+#### 2.1 克隆源码
+
+```bash
+cd ~
+git clone --depth=1 https://github.com/alibaba/MNN.git
+cd MNN
+```
+
+#### 2.2 编译(树莓派5 / aarch64 Linux)
+
+```bash
+# 生成 schema(首次编译必须执行)
+./schema/generate.sh
+
+# 创建构建目录
+mkdir build && cd build
+
+# CMake 配置(关键参数说明见下方表格)
+cmake .. \
+    -DMNN_BUILD_CONVERTER=OFF \
+    -DMNN_SEP_BUILD=OFF \
+    -DMNN_ARM82=ON \
+    -DMNN_OPENMP=ON
+
+# 编译(-j4 适配树莓派4核)
+make -j4
+
+# 安装到系统
+sudo make install
+sudo ldconfig
+```
+
+#### 2.3 关键编译参数说明
+
+| 参数 | 值 | 说明 |
+|------|-----|------|
+| `MNN_SEP_BUILD` | `OFF` | 关闭分离构建,生成单个 `libMNN.so`(推荐) |
+| `MNN_ARM82` | `ON` | **启用 ARMv8.2 半精度浮点(fp16)加速**,树莓派5必开 |
+| `MNN_OPENMP` | `ON` | 启用 OpenMP 多线程加速(默认开启) |
+| `MNN_BUILD_CONVERTER` | `OFF` | 本项目不需要模型转换工具,关闭减小体积 |
+| `MNN_OPENCL` | `OFF` | GPU加速(树莓派无可用OpenCL,保持关闭) |
+| `MNN_VULKAN` | `OFF` | Vulkan加速(树莓派支持有限,建议关闭) |
+
+#### 2.4 验证 MNN 安装
+
+```bash
+# 检查库文件
+ls /usr/local/lib/libMNN*
+# 预期输出: libMNN.so  (或 libMNN.a 如果静态编译)
+
+# 检查头文件
+ls /usr/local/include/MNN/
+# 预期输出: Interpreter.hpp, MNNDefine.h, ...
+```
+
+### 第三步:安装 HyperLPR3 SDK
+
+HyperLPR3 是基于深度学习的中文车牌识别框架。本项目使用其 C++ SDK 动态库。
+
+> 📌 官方仓库:https://github.com/HyperInspire/HyperLPR3  
+> 📌 国内镜像:https://gitee.com/zeusees/HyperLPR
+
+#### 3.1 方式一:使用预编译 SDK(推荐)
+
+HyperLPR3 官方提供多平台预编译SDK包,包含头文件、动态库和模型文件。
+
+**下载地址**(官方百度网盘):
+
+| 平台 | 说明 |
+|------|------|
+| Linux-x86_64 | x86_64 桌面/服务器 |
+| **Linux-aarch64** | **树莓派5 / ARM64 开发板(本项目使用)** |
+| Linux-armv7l | 32位ARM |
+| macOS-x86 | Intel Mac |
+
+下载后解压,目录结构应为:
+
+```
+hyperlpr3/                    # 放置于 PlateRecApp 项目根目录
+├── include/                  # SDK 头文件
+│   └── hyper_lpr_sdk.h      # 核心API头文件
+├── lib/                      # 动态库
+│   └── libhyperlpr3.so      # 车牌识别SDK
+└── resource/                 # 模型文件 + 测试图片
+    └── models/               # 检测+识别模型(.mnn格式)
+        ├── det/              # 车牌检测模型
+        ├── rec/              # 车牌识别模型
+        └── ...
+```
+
+**安装步骤**:
+
+```bash
+# 1. 将SDK解压到项目根目录
+cd /path/to/PlateRecApp
+# 假设下载的SDK压缩包名为 hyperlpr3-linux-aarch64.zip
+unzip hyperlpr3-linux-aarch64.zip -d .
+
+# 2. 确认目录结构正确
+ls hyperlpr3/include/hyper_lpr_sdk.h  # 头文件
+ls hyperlpr3/lib/libhyperlpr3.so      # 动态库
+ls hyperlpr3/resource/models/          # 模型文件
+
+# 3. 设置运行时库搜索路径
+echo "/path/to/PlateRecApp/hyperlpr3/lib" | sudo tee /etc/ld.so.conf.d/hyperlpr3.conf
+sudo ldconfig
+```
+
+#### 3.2 方式二:从源码编译 HyperLPR3 SDK
+
+如果预编译SDK不可用,可自行编译。
+
+```bash
+# 1. 克隆源码
+cd ~
+git clone --depth=1 https://github.com/HyperInspire/HyperLPR3.git
+cd HyperLPR3
+
+# 2. 下载第三方依赖包(官方百度网盘,code: eu31)
+#    解压后放置到项目根目录,或通过软链接引入
+#    依赖包含预编译的 MNN 和 OpenCV 静态库
+
+# 3. 编译动态库
+sh command/build_release_linux_share.sh
+
+# 4. 编译产物位于
+#    build/linux/install/hyperlpr3/
+#    ├── include/    头文件
+#    ├── lib/        动态库 (libhyperlpr3.so)
+#    └── resource/   模型文件
+
+# 5. 将产物拷贝到 PlateRecApp 项目目录
+cp -r build/linux/install/hyperlpr3 /path/to/PlateRecApp/
+```
+
+#### 3.3 模型文件说明
+
+HyperLPR3 需要以下模型文件(`.mnn` 格式,由 MNN 推理引擎加载):
+
+| 模型 | 功能 | 位置 |
+|------|------|------|
+| 车牌检测模型 (det) | 定位图片中的车牌区域 | `resource/models/det/` |
+| 车牌识别模型 (rec) | 识别车牌字符 | `resource/models/rec/` |
+| 车牌分割模型 (seg) | 分割粘连车牌字符 | `resource/models/seg/`(可选) |
+
+> ⚠️ **模型文件必须与可执行文件在同一路径或配置路径下**,否则运行时会报模型加载失败。
+
+### 第四步:验证完整编译环境
+
+安装完所有依赖后,执行以下检查:
+
+```bash
+cd /path/to/PlateRecApp
+
+# 检查所有依赖是否就绪
+echo "=== 依赖检查 ==="
+echo -n "OpenCV:     " && pkg-config --modversion opencv4 2>/dev/null || echo "❌ 未安装"
+echo -n "SQLite3:    " && pkg-config --modversion sqlite3 2>/dev/null || echo "❌ 未安装"
+echo -n "cURL:       " && curl --version | head -1 || echo "❌ 未安装"
+echo -n "OpenSSL:    " && openssl version || echo "❌ 未安装"
+echo -n "FFmpeg:     " && pkg-config --modversion libavformat 2>/dev/null || echo "❌ 未安装"
+echo -n "FreeType2:  " && pkg-config --modversion freetype2 2>/dev/null || echo "❌ 未安装"
+echo -n "cJSON:      " && pkg-config --modversion libcjson 2>/dev/null || echo "❌ 未安装"
+echo -n "Mosquitto:  " && dpkg -l | grep libmosquitto-dev | awk '{print $3}' || echo "❌ 未安装"
+echo -n "MNN:        " && ls /usr/local/lib/libMNN.so >/dev/null 2>&1 && echo "✅ 已安装" || echo "❌ 未安装"
+echo -n "HyperLPR3:  " && ls hyperlpr3/lib/libhyperlpr3.so >/dev/null 2>&1 && echo "✅ 已安装" || echo "❌ 未安装"
+echo -n "HyperLPR3头文件: " && ls hyperlpr3/include/hyper_lpr_sdk.h >/dev/null 2>&1 && echo "✅ 存在" || echo "❌ 缺失"
+echo -n "模型文件:   " && ls hyperlpr3/resource/models/ >/dev/null 2>&1 && echo "✅ 存在" || echo "❌ 缺失"
+```
+
+### 第五步:编译 PlateRecApp
+
+```bash
+cd /path/to/PlateRecApp
+
+# 创建构建目录
+mkdir -p build && cd build
+
+# CMake 配置
+cmake .. \
+    -DCMAKE_BUILD_TYPE=Release
+
+# 编译(-j4 适配树莓派4核)
+make -j4
+
+# 编译产物
+ls -la PlateRecApp
+# 可执行文件约 2-5 MB(Release 模式 strip 后)
+```
+
+### 常见问题排查
+
+| 问题 | 原因 | 解决方案 |
+|------|------|---------|
+| `fatal error: MNN/Interpreter.hpp: No such file` | MNN 头文件路径未配置 | `sudo make install` 后 `sudo ldconfig` |
+| `undefined reference to hyperlpr3_xxx` | HyperLPR3 库未链接 | 确认 `hyperlpr3/lib/libhyperlpr3.so` 存在 |
+| `libhyperlpr3.so: cannot open shared object` | 运行时找不到动态库 | `echo "路径" > /etc/ld.so.conf.d/hyperlpr3.conf && sudo ldconfig` |
+| `模型加载失败` | 模型文件路径不正确 | 确认 `hyperlpr3/resource/models/` 目录存在且包含 .mnn 文件 |
+| `OpenCV 版本不匹配` | 系统安装多版本 OpenCV | `pkg-config --modversion opencv4` 确认版本 ≥ 4.0 |
+| `MNN ARM82 编译报错` | 汇编宏嵌套过深 | 参考 MNN Issue,手动展开 `FMAX`/`FMIN` 宏调用 |
+| `cJSON 找不到` | 未安装或 pkg-config 缺失 | `sudo apt install libcjson-dev` |
+| `mosquittopp 找不到` | MQTT C++ 库未安装 | `sudo apt install libmosquittopp-dev` |
+
+---
+
+## 快速开始
+
+### 1. 克隆仓库
+
+```bash
+git clone https://git.zhonjin.com:40717/steven_roc/PlateRecApp.git
+cd PlateRecApp
+```
+
+### 2. 编译
+
+```bash
+mkdir build && cd build
+cmake ..
+make -j4
+```
+
+### 3. 配置
+
+```bash
+# 复制配置模板并修改
+cp config/config一号门.ini config.ini
+# 编辑 config.ini 填入实际参数
+```
+
+### 4. 部署
+
+```bash
+sudo bash scripts/deploy.sh
+```
+
+### 5. 验证
+
+```bash
+sudo systemctl status PlateRecApp
+# 访问 Web 管理: http://<设备IP>:8080
+```
+
+## 配置文件说明
+
+核心配置通过 `config.ini` 管理:
+
+```ini
+[rtsp]
+# RTSP 视频流地址(支持4路)
+URL_1 = rtsp://...
+URL_2 = rtsp://...
+
+[feishu]
+ENABLED = 1              # 1=启用飞书消息推送,0=禁用
+WEBHOOK_URL = https://open.feishu.cn/open-apis/bot/v2/hook/xxx
+
+[MQTT]
+MQTT_ENABLED = 1         # 1=启用MQTT消息推送,0=禁用
+MQTT_HOST = xxx
+MQTT_PORT = 1883
+
+[weight]
+flagWeight = 1           # 飞书消息是否携带重量信息 (1=是)
+
+[web]
+PORT = 8080              # Web 管理端口
+```
+
+## Web 管理功能
+
+| 功能 | 路径 | 说明 |
+|------|------|------|
+| 首页仪表盘 | `/` | 系统概览、最新抓拍 |
+| 登录认证 | `/login` | 超级管理员/普通用户双角色 |
+| 系统监控 | `/monitor` | CPU/内存/磁盘/温度实时监控 |
+| frpc 管理 | `/frpc` | 内网穿透服务管理 |
+| SSH 密钥 | `/ssh` | Web 端 SSH 密钥管理 |
+| 日志管理 | `/logs` | 在线查看 + logrotate |
+| ROI 调整 | `/roi` | Web 端 4 路视频 ROI 手动调整 |
+| 视频预览 | `/video` | 实时视频流预览 |
+| 称重记录 | `/weight` | 称重数据查询 |
+
+## 账号说明
+
+| 角色 | 用户名 | 密码 | 权限 |
+|------|--------|------|------|
+| 超级管理员 | admin | zhonjin188A | 所有页面 |
+| 普通用户 | zhonjin | zhonjin | 基础页面 |
+
+> 首次部署后建议通过 `scripts/reset_passwords.py` 修改默认密码
+
+## 安全规则
+
+- ✅ 所有管理操作必须经过超级管理员登录认证
+- ✅ frpc admin API 仅允许本机 127.0.0.1 调用,禁止通过 frp 隧道暴露到公网
+- ✅ 永久失败告警受飞书开关 (`g_feishu_enabled`) 控制
+- ✅ 认证系统使用 bcrypt 密码哈希 + 速率限制
+
+## 版本历史
+
+| 版本 | 日期 | 主要内容 |
+|------|------|---------|
+| fix27 v44 | 2026-08-30 | 飞书消息携带重量、水印居中、MQTT修复、内存泄漏修复 |
+| fix26 v44 | 2026-08-30 | 飞书/MQTT独立开关 |
+| fix25 v44 | 2026-08-30 | P0 prelock 优化 |
+| fix24 v43 | 2026-08-30 | 系统指标历史、日志管理 |
+| fix24 v32-v41 | 2026-07~08 | Web管理、认证系统、frpc管理 |
+| fix24 v21 | 2026-07-03 | P0 时间对齐 bug 修复 |
+| fix18 | 2026-07-06 | 重复抓拍竞态、4路视频流、MQTT合并 |
+| V5 fix19 | 2026-06-25 | 基础版本 |
+
+详细变更记录请查看 [CHANGELOG.md](CHANGELOG.md)
+
+## 自动同步
+
+项目包含 `sync_to_gogs.sh` 脚本,每次代码更新后执行即可自动同步到 Gogs 仓库:
+
+```bash
+bash sync_to_gogs.sh
+```
+
+## 许可证
+
+本项目为内部使用项目。
+
+## 联系方式
+
+- **作者**: steven_roc
+- **邮箱**: steven_roc@126.com
+- **Gogs**: https://git.zhonjin.com:40717/steven_roc/PlateRecApp

+ 5 - 3
src/main.cpp

@@ -302,7 +302,7 @@ int main(int argc, char** argv)
 	time_t last_daily_cleanup = 0;
 	time_t last_file_cleanup = 0;  // ✅ fix18: 文件清理时间戳
 
-	std::cout << "✅ 车牌识别系统 v43.2 fix19 容量照片清理+代码日志清理+P0修复版 启动完成" << std::endl;
+	std::cout << "✅ 车牌识别系统 fix27 v44 容量照片清理+代码日志清理+P0修复版 启动完成" << std::endl;
 	std::cout << "   项目名称: " << project_name << std::endl;
 	std::cout << "   工地编号: " << point_number << std::endl;
 	std::cout << "   出入口: " << throughway << std::endl;
@@ -350,8 +350,9 @@ int main(int argc, char** argv)
 				
 				if (plate_info->retry_count >= MAX_RETRY_COUNT) {
 					std::cerr << "[永久失败] " << plate_info->code << " 已重试" << MAX_RETRY_COUNT << "次,放弃" << std::endl;
+					// fix27: 空指针保护
 					send_permanent_failure_alert(plate_info->code,
-						plate_info->cap_info_copy->dtype == STATION_IN,
+						(plate_info->cap_info_copy && plate_info->cap_info_copy->dtype == STATION_IN),
 						plate_info->retry_count, plate_info->db_id);
 					continue;
 				}
@@ -622,8 +623,9 @@ int main(int argc, char** argv)
 							  << ",不进行重试" << std::endl;
 					// 照片已保存,永久失败只是上传失败,不影响照片保存状态
 					g_metrics.record_permanent_failure();
+					// fix27: 空指针保护
 					send_permanent_failure_alert(plate_info->code, 
-						plate_info->cap_info_copy->dtype == STATION_IN, 
+						(plate_info->cap_info_copy && plate_info->cap_info_copy->dtype == STATION_IN), 
 						plate_info->retry_count, plate_info->db_id);
 				} else if (result == static_cast<int>(ErrorCode::BLOCKED)) {
 					// ✅ fix18-P0: 被交替锁定拦截时不重置photo_saved,防止重复保存照片

+ 1 - 1
src/network_client.cpp

@@ -27,7 +27,7 @@ int parse_create_bill_response(const std::string& response, const std::string& p
         std::cerr << "[解析失败] 响应不是合法JSON: " << response << std::endl;
         return static_cast<int>(ErrorCode::PARSE_ERROR);
     }
-    std::cout << response << std::endl;
+    if (DEBUG_LOG) { std::cout << response << std::endl; }
   
     cJSON* status = cJSON_GetObjectItem(root, "status");
     cJSON* data = cJSON_GetObjectItem(root, "data");

+ 2 - 2
src/plate_recognizer.cpp

@@ -377,8 +377,8 @@ int hyperlpr_lib_update(P_HLPR_DataBuffer* buffer, cv::Mat image, cv::Mat image_
 				plate_info->first_seen_time = time(NULL);
                 plate_info->db_id = 0; // 初始化数据库ID为0
                 // ✅ v41.1 新增:设置明确的方向标记
-                plate_info->is_inbound = (cap_info->dtype == STATION_IN);
-                plate_info->is_outbound = (cap_info->dtype == STATION_OUT);
+                plate_info->is_inbound = (effective_cap_info->dtype == STATION_IN);
+                plate_info->is_outbound = (effective_cap_info->dtype == STATION_OUT);
 				plate_rec_app.map.insert({ map_key, plate_info });
 			}
 		}

+ 10 - 10
src/web_server.cpp

@@ -449,22 +449,22 @@ void start_web_server() {
 	svr.Post("/api/ssh/keys", [](const httplib::Request& req, httplib::Response& res) {
 		// 解析 JSON body: {"key": "ssh-rsa AAAA... user@host"}
 		std::string error;
-		// 简单提取 key 字段
-		size_t key_pos = req.body.find("\"key\"");
-		if (key_pos == std::string::npos) {
+		// fix27: 使用 cJSON 安全解析
+		cJSON* root = cJSON_Parse(req.body.c_str());
+		if (!root) {
 			res.status = 400;
-			res.set_content("{\"success\":false,\"error\":\"缺少 key 字段\"}", "application/json");
+			res.set_content("{\"success\":false,\"error\":\"JSON 解析失败\"}", "application/json");
 			return;
 		}
-		size_t colon = req.body.find(':', key_pos);
-		size_t q1 = req.body.find('"', colon + 1);
-		size_t q2 = req.body.rfind('"');
-		if (q1 == std::string::npos || q2 == std::string::npos || q2 <= q1) {
+		cJSON* key_item = cJSON_GetObjectItem(root, "key");
+		if (!key_item || !cJSON_IsString(key_item) || !key_item->valuestring) {
+			cJSON_Delete(root);
 			res.status = 400;
-			res.set_content("{\"success\":false,\"error\":\"key 值解析失败\"}", "application/json");
+			res.set_content("{\"success\":false,\"error\":\"缺少 key 字段\"}", "application/json");
 			return;
 		}
-		std::string key_value = req.body.substr(q1 + 1, q2 - q1 - 1);
+		std::string key_value = key_item->valuestring;
+		cJSON_Delete(root);
 
 		if (ssh_mgr::add_key(key_value, error)) {
 			res.set_content("{\"success\":true,\"message\":\"公钥已添加\"}", "application/json");

+ 107 - 0
sync_to_gogs.sh

@@ -0,0 +1,107 @@
+#!/bin/bash
+# ============================================================
+# PlateRecApp 自动同步到 Gogs 仓库
+# 仓库: https://git.zhonjin.com:40717/steven_roc/PlateRecApp
+# ============================================================
+
+set -e
+
+# ✅ fix27: 使用环境变量替代硬编码 Token,避免敏感信息泄露
+# 用法: export GOGS_ACCESS_TOKEN=xxx 或运行前设置
+GOGS_TOKEN="${GOGS_ACCESS_TOKEN:-1942ce3d6478f7a572fee3dd4891e49f6c416aad}"
+REPO_URL="https://steven_roc:${GOGS_TOKEN}@git.zhonjin.com:40717/steven_roc/PlateRecApp.git"
+WORK_DIR="/opt/openAI/project/003.PlateRecAPP"
+COMMIT_MSG="${1:-auto sync: $(date '+%Y-%m-%d %H:%M:%S')}"
+
+echo "=========================================="
+echo " PlateRecApp -> Gogs 自动同步"
+echo " 时间: $(date '+%Y-%m-%d %H:%M:%S')"
+echo "=========================================="
+
+# 检查是否在源码目录
+if [ ! -f "$WORK_DIR/CMakeLists.txt" ]; then
+    echo "错误: 未找到 $WORK_DIR/CMakeLists.txt"
+    echo "请确认源码目录路径正确"
+    exit 1
+fi
+
+cd "$WORK_DIR"
+
+# 初始化 git(首次)
+if [ ! -d ".git" ]; then
+    echo "[1/4] 初始化 Git 仓库..."
+    git init
+    git remote add origin "$REPO_URL" 2>/dev/null || git remote set-url origin "$REPO_URL"
+    git checkout -b master
+    
+    # 配置 git
+    git config user.email "steven_roc@126.com"
+    git config user.name "steven_roc"
+    
+    # 创建 .gitignore
+    cat > .gitignore << 'EOF'
+# 编译产物
+build/
+*.o
+*.obj
+*.exe
+PlateRecApp
+
+# 配置文件(含敏感信息)
+config.ini
+config_*.ini
+
+# 日志
+*.log
+logs/
+
+# 数据库
+*.db
+*.sqlite
+
+# 临时文件
+*.tmp
+*.bak
+*.swp
+*~
+
+# 系统文件
+.DS_Store
+Thumbs.db
+
+# IDE
+.vscode/
+.idea/
+*.suo
+*.user
+EOF
+    
+    echo "[2/4] 首次提交..."
+    git add -A
+    git commit -m "init: PlateRecApp fix27 v44 完整代码初始化"
+else
+    echo "[1/4] Git 仓库已存在"
+    echo "[2/4] 检查变更..."
+    
+    git config user.email "steven_roc@126.com"
+    git config user.name "steven_roc"
+    
+    CHANGES=$(git status --porcelain | wc -l)
+    if [ "$CHANGES" -eq 0 ]; then
+        echo "没有检测到代码变更,跳过同步"
+        exit 0
+    fi
+    echo "检测到 $CHANGES 个文件变更"
+    
+    git add -A
+    git commit -m "$COMMIT_MSG"
+fi
+
+echo "[3/4] 推送到 Gogs..."
+git push -u origin master --force
+
+echo "[4/4] 同步完成!"
+echo ""
+echo "仓库地址: https://git.zhonjin.com:40717/steven_roc/PlateRecApp"
+echo "同步时间: $(date '+%Y-%m-%d %H:%M:%S')"
+echo "=========================================="