help.html 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width,initial-scale=1">
  6. <title>帮助手册 - 车牌识别系统</title>
  7. <style>
  8. *{box-sizing:border-box;margin:0;padding:0}
  9. body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#f0f2f5;color:#333}
  10. .navbar{background:#1a1a2e;color:#fff;padding:12px 20px;display:flex;align-items:center;gap:20px;box-shadow:0 2px 8px rgba(0,0,0,0.15)}
  11. .navbar a{color:#ccc;text-decoration:none;padding:6px 12px;border-radius:4px;font-size:14px}
  12. .navbar a:hover,.navbar a.active{background:rgba(255,255,255,0.15);color:#fff}
  13. .navbar .brand{font-size:16px;font-weight:bold;color:#4fc3f7;margin-right:10px}
  14. .container{max-width:960px;margin:20px auto;padding:0 16px}
  15. .card{background:#fff;border-radius:8px;margin-bottom:16px;box-shadow:0 1px 4px rgba(0,0,0,0.08);overflow:hidden}
  16. .card-title{background:#263238;color:#fff;padding:10px 16px;font-size:14px;font-weight:600;display:flex;align-items:center;justify-content:space-between;cursor:pointer;user-select:none}
  17. .card-title:hover{background:#37474f}
  18. .card-title .arrow{transition:transform 0.2s;font-size:12px}
  19. .card-title.collapsed .arrow{transform:rotate(-90deg)}
  20. .card-body{padding:16px 20px;line-height:1.8;font-size:14px}
  21. .card-body.hidden{display:none}
  22. h3{color:#1a1a2e;margin:16px 0 8px;font-size:15px;border-left:3px solid #667eea;padding-left:10px}
  23. h4{color:#333;margin:12px 0 6px;font-size:14px}
  24. table{width:100%;border-collapse:collapse;margin:8px 0 16px;font-size:13px}
  25. th{background:#f5f5f5;padding:8px 10px;text-align:left;font-weight:600;border-bottom:2px solid #e0e0e0;white-space:nowrap}
  26. td{padding:8px 10px;border-bottom:1px solid #f0f0f0;vertical-align:top}
  27. td:first-child{font-family:monospace;color:#d32f2f;white-space:nowrap;font-size:12px}
  28. code{background:#f5f5f5;padding:2px 6px;border-radius:3px;font-size:12px;color:#d32f2f;font-family:monospace}
  29. pre{background:#1e1e2e;color:#cdd6f4;padding:14px 18px;border-radius:6px;font-size:12px;overflow-x:auto;margin:8px 0 16px;line-height:1.6}
  30. pre code{background:none;color:#cdd6f4;padding:0;font-size:12px}
  31. .tip{background:#e8f5e9;border-left:4px solid #4caf50;padding:10px 14px;margin:8px 0;border-radius:0 4px 4px 0;font-size:13px}
  32. .warn{background:#fff3e0;border-left:4px solid #ff9800;padding:10px 14px;margin:8px 0;border-radius:0 4px 4px 0;font-size:13px}
  33. .note{background:#e3f2fd;border-left:4px solid #2196f3;padding:10px 14px;margin:8px 0;border-radius:0 4px 4px 0;font-size:13px}
  34. ul{padding-left:20px;margin:6px 0}
  35. li{margin:4px 0;font-size:13px;line-height:1.7}
  36. .toc{background:#fff;border-radius:8px;padding:16px 24px;margin-bottom:16px;box-shadow:0 1px 4px rgba(0,0,0,0.08)}
  37. .toc h2{font-size:16px;color:#1a1a2e;margin-bottom:10px;border-bottom:2px solid #667eea;padding-bottom:6px}
  38. .toc ol{padding-left:20px;columns:2;column-gap:30px}
  39. .toc li{margin:4px 0;font-size:13px;break-inside:avoid}
  40. .toc a{color:#667eea;text-decoration:none}
  41. .toc a:hover{text-decoration:underline}
  42. @media(max-width:768px){
  43. .navbar{flex-wrap:wrap;gap:8px;padding:10px 14px}
  44. .navbar .brand{font-size:14px;width:100%}
  45. .navbar a{font-size:12px;padding:4px 8px}
  46. .container{padding:0 10px;margin:10px auto}
  47. .toc ol{columns:1}
  48. table{font-size:12px;display:block;overflow-x:auto}
  49. th,td{padding:6px 8px}
  50. td:first-child{font-size:11px}
  51. }
  52. @media(max-width:480px){
  53. .navbar .brand{font-size:13px}
  54. .navbar a{font-size:11px;padding:3px 6px}
  55. .card-body{padding:12px 14px}
  56. }
  57. </style>
  58. </head>
  59. <body>
  60. <nav class="navbar">
  61. <span class="brand">🚛 车牌识别系统</span>
  62. <a href="/">监控首页</a>
  63. <a href="/video">视频预览</a>
  64. <a href="/locks">锁定管理</a>
  65. <a href="/help" class="active">帮助手册</a>
  66. </nav>
  67. <div class="container">
  68. <div class="toc">
  69. <h2>📋 目录</h2>
  70. <ol>
  71. <li><a href="#sec1">系统概述</a></li>
  72. <li><a href="#sec2">快速部署指南</a></li>
  73. <li><a href="#sec3">config.ini 完整配置说明</a></li>
  74. <li><a href="#sec4">RTSP 摄像头配置</a></li>
  75. <li><a href="#sec5">车牌识别上传流程</a></li>
  76. <li><a href="#sec6">MQTT 数据上报配置</a></li>
  77. <li><a href="#sec7">称重系统对接</a></li>
  78. <li><a href="#sec8">ROI 区域识别配置</a></li>
  79. <li><a href="#sec9">交替锁定机制</a></li>
  80. <li><a href="#sec10">登录认证与权限</a></li>
  81. <li><a href="#sec11">frpc.toml 内网穿透配置详解</a></li>
  82. <li><a href="#sec12">Nginx 反向代理配置详解</a></li>
  83. <li><a href="#sec13">SSL/HTTPS 证书配置</a></li>
  84. <li><a href="#sec14">系统监控与日志</a></li>
  85. <li><a href="#sec15">Web 管理功能</a></li>
  86. <li><a href="#sec16">数据库与备份</a></li>
  87. <li><a href="#sec17">常见问题 FAQ</a></li>
  88. </ol>
  89. </div>
  90. <!-- 1. 系统概述 -->
  91. <div class="card" id="sec1">
  92. <div class="card-title" onclick="toggleCard(this)">1. 系统概述 <span class="arrow">▼</span></div>
  93. <div class="card-body">
  94. <h3>系统简介</h3>
  95. <p>车牌识别系统(PlateRecApp)是一款基于 HyperLPR3 的实时车牌识别应用,运行在树莓派 5 (aarch64) 上,支持:</p>
  96. <ul>
  97. <li>多路 RTSP 摄像头实时抓拍识别(进场/出场/侧面进/侧面出)</li>
  98. <li>车牌识别结果自动上传至云端 API</li>
  99. <li>称重系统对接(TCP 协议),自动关联车牌与称重数据</li>
  100. <li>MQTT 协议实时推送识别结果</li>
  101. <li>飞书群消息通知(异常报警)</li>
  102. <li>Web 管理界面(监控首页、视频预览、系统配置、锁定管理等)</li>
  103. <li>frpc 内网穿透管理 + Nginx stream + ssl_preread 协议分流(HTTP 自动跳 HTTPS)</li>
  104. <li>登录认证与角色权限控制</li>
  105. <li>SD 卡写入寿命保护(内存缓冲 + 每日定时写盘)</li>
  106. </ul>
  107. <h3>运行环境</h3>
  108. <table>
  109. <tr><th>项目</th><th>要求</th></tr>
  110. <tr><td>硬件</td><td>树莓派 5 8GB(推荐)</td></tr>
  111. <tr><td>系统</td><td>Debian 13 (trixie) / Ubuntu 24.04 aarch64</td></tr>
  112. <tr><td>依赖</td><td>HyperLPR3 SDK、FFmpeg、libcurl、OpenSSL、cJSON、Mosquitto、Nginx</td></tr>
  113. <tr><td>内网穿透</td><td>frpc(通过 systemctl 管理)</td></tr>
  114. <tr><td>协议分流</td><td>Nginx stream + ssl_preread(TCP 层 HTTP/HTTPS 自动检测分流)</td></tr>
  115. </table>
  116. <h3>网络架构总览(v2 — nginx stream + ssl_preread 方案)</h3>
  117. <pre>外网客户端 frps 服务器 本机(树莓派5)
  118. ───────── ───────── ─────────────
  119. ┌─ TLS → nginx stream → C++ HTTPS:8080 (透传)
  120. https://mqtt.zhonjin.com:40963/ ─TCP─→ discover.zhonjin.com:9443 ─TCP─→ frpc ─→ nginx:40963 (stream+ssl_preread)
  121. http://mqtt.zhonjin.com:40963/ ─TCP─→ ↑ ─TCP─→ frpc ─→└─ HTTP → nginx:8081 (301→HTTPS)
  122. https://mqtt.zhonjin.com:40964/ ─TCP─→ ↑ ─TCP─→ frpc ─→ nginx:8082 (HTTPS代理) → ttyd:7681</pre>
  123. <div class="tip"><strong>v2 方案优势:</strong>公网只需一个端口 40963,nginx stream 在 TCP 层自动检测协议类型并分流。HTTP 自动跳 HTTPS,HTTPS 透传到 C++ 应用处理。用户只需记住一个地址。</div>
  124. <h3>目录结构</h3>
  125. <table>
  126. <tr><th>路径</th><th>说明</th></tr>
  127. <tr><td>PlateRecApp</td><td>主程序二进制</td></tr>
  128. <tr><td>config.ini</td><td>主配置文件</td></tr>
  129. <tr><td>config.txt</td><td>MD5 校验配置</td></tr>
  130. <tr><td>hyperlpr3/</td><td>HyperLPR3 SDK 模型文件</td></tr>
  131. <tr><td>assets/web/</td><td>Web 页面文件(HTML/CSS/JS)</td></tr>
  132. <tr><td>data/</td><td>数据库目录(auth.db、upload_records.db、system_metrics.db)</td></tr>
  133. <tr><td>photos/</td><td>抓拍图片存储目录</td></tr>
  134. <tr><td>scripts/</td><td>运维脚本(sudoers 配置、密码重置等)</td></tr>
  135. </table>
  136. </div>
  137. </div>
  138. <!-- 2. 快速部署指南 -->
  139. <div class="card" id="sec2">
  140. <div class="card-title" onclick="toggleCard(this)">2. 快速部署指南 <span class="arrow">▼</span></div>
  141. <div class="card-body">
  142. <h3>编译部署</h3>
  143. <ol>
  144. <li>解压代码包:<code>tar xzf fix24_vXX.tar.gz</code></li>
  145. <li>编译:<code>cd fix24/build && cmake .. && make -j4</code></li>
  146. <li>部署文件到安装目录(如 <code>/opt/openAI/project/003.PlateRecAPP/</code>)</li>
  147. <li>复制 HTML 文件:<code>cp -r assets/web/ /安装目录/assets/web/</code></li>
  148. <li>配置 sudoers(frpc 管理需要):<code>sudo bash scripts/setup_frpc_sudoers.sh</code></li>
  149. <li>安装 nginx stream 模块:<code>sudo apt install libnginx-mod-stream</code></li>
  150. <li>部署 nginx stream 配置文件(详见第 12 章)</li>
  151. <li>部署 SSL 证书(C++ 应用自管理,详见第 13 章)</li>
  152. <li>修改 <code>config.ini</code> 配置(参考下文各节)</li>
  153. <li>启动服务:<code>sudo systemctl restart nginx && sudo systemctl restart PlateRecApp</code></li>
  154. </ol>
  155. <h3>一键部署脚本(推荐)</h3>
  156. <p>代码包内置 <code>scripts/deploy.sh</code>,覆盖编译→备份→部署→服务安装→启动→健康检查全流程,默认部署目录 <code>/opt/openAI/project/003.PlateRecAPP</code>、运行用户 <code>stevenroc</code>、服务名 <code>PlateRecApp</code>(均可用环境变量 <code>APP_DIR</code>/<code>RUN_USER</code>/<code>SVC_NAME</code> 覆盖):</p>
  157. <pre># 首次:安装编译/运行依赖(需 root)
  158. sudo bash scripts/deploy.sh deps
  159. # 一键编译+部署+重启(部署前自动备份当前版本到 backup/,保留最近 5 个)
  160. sudo bash scripts/deploy.sh deploy # 或直接 sudo bash scripts/deploy.sh
  161. # 日常运维
  162. bash scripts/deploy.sh status # 服务状态 + 8080/8079 端口 + 进程 + 磁盘
  163. bash scripts/deploy.sh logs # 跟踪 PlateRecApp.log
  164. bash scripts/deploy.sh journal # 跟踪 systemd journal
  165. bash scripts/deploy.sh restart # 重启服务
  166. sudo bash scripts/deploy.sh rollback # 回滚到上一次备份
  167. sudo bash scripts/deploy.sh sudoers # 配置 frpc 管理 sudoers 权限</pre>
  168. <div class="tip">脚本安全策略:<b>config.ini 已存在时永不覆盖</b>(首次部署才从模板生成);每次部署自动备份二进制+配置+Web资源+SDK;服务文件由脚本自动安装到 /etc/systemd/system/ 并设置开机自启。</div>
  169. <h3>systemd 服务配置</h3>
  170. <p>PlateRecApp 服务文件路径:<code>/etc/systemd/system/PlateRecApp.service</code>(由 deploy.sh 自动生成,内容等价于):</p>
  171. <pre>Unit]
  172. Description=PlateRecApp License Plate Recognition System
  173. After=network.target frpc.service
  174. [Service]
  175. User=stevenroc
  176. Group=stevenroc
  177. WorkingDirectory=/opt/openAI/project/003.PlateRecAPP
  178. ExecStartPre=/bin/sleep 10
  179. ExecStart=/opt/openAI/project/003.PlateRecAPP/PlateRecApp
  180. TimeoutStartSec=30
  181. TimeoutStopSec=10
  182. Restart=on-failure
  183. RestartSec=5
  184. StandardOutput=append:/opt/openAI/project/003.PlateRecAPP/PlateRecApp.log
  185. LimitNOFILE=1048576
  186. [Install]
  187. WantedBy=multi-user.target</pre>
  188. <div class="tip">PlateRecApp 以非 root 用户运行(stevenroc),frpc 管理通过 sudoers 授权。日志输出由 v36 内存缓冲系统接管,systemd 的 StandardOutput 仅作备份。</div>
  189. <h3>密码重置</h3>
  190. <p>如忘记管理员密码,运行:<code>python3 reset_passwords.py</code></p>
  191. <h3>Nginx 服务配置(v2 — stream 方案)</h3>
  192. <p>安装 stream 模块并部署配置:</p>
  193. <pre># 安装 stream 模块(必须)
  194. sudo apt install -y libnginx-mod-stream
  195. # 验证模块
  196. nginx -V 2>&amp;1 | grep stream
  197. # 替换 nginx.conf(详见第 12 章)
  198. sudo cp nginx-stream.conf /etc/nginx/nginx.conf
  199. # 验证配置
  200. sudo nginx -t
  201. # 启动/重载
  202. sudo systemctl enable nginx
  203. sudo systemctl restart nginx</pre>
  204. </div>
  205. </div>
  206. <!-- 3. config.ini 完整配置 -->
  207. <div class="card" id="sec3">
  208. <div class="card-title" onclick="toggleCard(this)">3. config.ini 完整配置说明 <span class="arrow">▼</span></div>
  209. <div class="card-body">
  210. <p>配置文件路径:<code>&lt;安装目录&gt;/config.ini</code>,ini 格式,section 和 key 均不区分大小写。</p>
  211. <h3>[config] 核心识别配置</h3>
  212. <table>
  213. <tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
  214. <tr><td>project_name</td><td>string</td><td>-</td><td>项目名称</td></tr>
  215. <tr><td>point_number</td><td>string</td><td>-</td><td>站点编号</td></tr>
  216. <tr><td>throughway</td><td>string</td><td>-</td><td>通道名称</td></tr>
  217. <tr><td>rtsp_url_front_in</td><td>string</td><td>-</td><td>前侧进场摄像头 RTSP 地址</td></tr>
  218. <tr><td>rtsp_url_front_out</td><td>string</td><td>-</td><td>前侧出场摄像头 RTSP 地址</td></tr>
  219. <tr><td>rtsp_url_side_in</td><td>string</td><td>-</td><td>侧面进场摄像头 RTSP 地址</td></tr>
  220. <tr><td>rtsp_url_side_out</td><td>string</td><td>-</td><td>侧面出场摄像头 RTSP 地址</td></tr>
  221. <tr><td>app_api</td><td>string</td><td>-</td><td>云端上传 API 地址</td></tr>
  222. <tr><td>app_key</td><td>string</td><td>-</td><td>API 认证 Key</td></tr>
  223. <tr><td>app_secret</td><td>string</td><td>-</td><td>API 认证 Secret</td></tr>
  224. <tr><td>DEBUG_LOG</td><td>int</td><td>0</td><td>调试日志开关(0=关闭,1=开启)</td></tr>
  225. <tr><td>TestFlag</td><td>int</td><td>0</td><td>测试模式开关</td></tr>
  226. <tr><td>wType</td><td>int</td><td>0</td><td>称重类型(0=禁用,1=启用)</td></tr>
  227. <tr><td>TIME_WINDOW</td><td>int</td><td>5</td><td>识别时间窗口(分钟),同一车牌在窗口内不重复上传</td></tr>
  228. <tr><td>MAX_PHOTO_GROUPS</td><td>int</td><td>100</td><td>最大照片组数缓存</td></tr>
  229. <tr><td>in_out_interval</td><td>int</td><td>5</td><td>⚠️ 单位是<strong>分钟</strong>!交替锁定间隔(同一车牌进出最短时间),内部转为秒存储</td></tr>
  230. <tr><td>PLATE_CONFIDENCE_THRESHOLD</td><td>float</td><td>0.7</td><td>车牌识别置信度阈值(0~1),低于此值不上传</td></tr>
  231. <tr><td>PLATE_LOG_THRESHOLD</td><td>float</td><td>0.5</td><td>日志记录置信度阈值</td></tr>
  232. <tr><td>AlternatingMerge</td><td>int</td><td>0</td><td>交替合并开关(0=关闭,1=开启)</td></tr>
  233. <tr><td>hw_decode_mode</td><td>string</td><td>auto</td><td>硬件解码模式:auto/soft/drm</td></tr>
  234. <tr><td>hw_decode_device</td><td>string</td><td>/dev/dri/renderD128</td><td>硬件解码设备路径</td></tr>
  235. <tr><td>rtsp_transport</td><td>string</td><td>tcp</td><td>RTSP 传输协议:tcp/udp</td></tr>
  236. <tr><td>PhotoMaxCapacityMB</td><td>int</td><td>1024</td><td>照片目录最大容量(MB),超限自动清理旧照片</td></tr>
  237. <tr><td>LogRetentionDays</td><td>int</td><td>30</td><td>日志保留天数</td></tr>
  238. </table>
  239. <h3>[server] Web 服务配置</h3>
  240. <table>
  241. <tr><th>参数</th><th>说明</th></tr>
  242. <tr><td>ip</td><td>Web 服务监听地址(含端口),如 <code>0.0.0.0:8080</code>。v2 方案下 PlateRecApp 提供 HTTPS 服务(SSL 自管理),nginx stream 透传 TLS 流量到此处</td></tr>
  243. </table>
  244. <h3>[mqtt] MQTT 上报配置</h3>
  245. <table>
  246. <tr><th>参数</th><th>默认值</th><th>说明</th></tr>
  247. <tr><td>MQTT_HOST</td><td>-</td><td>MQTT Broker 地址(填写后自动启用 MQTT)</td></tr>
  248. <tr><td>MQTT_PORT</td><td>1883</td><td>MQTT Broker 端口</td></tr>
  249. <tr><td>MQTT_USER</td><td>-</td><td>MQTT 用户名</td></tr>
  250. <tr><td>MQTT_PASS</td><td>-</td><td>MQTT 密码</td></tr>
  251. <tr><td>MQTT_TOPIC</td><td>-</td><td>推送主题</td></tr>
  252. <tr><td>MQTT_CLIENT_ID</td><td>-</td><td>客户端 ID</td></tr>
  253. <tr><td>PLATE_COLOR</td><td>-</td><td>车牌颜色标识(随 MQTT 消息上报)</td></tr>
  254. <tr><td>VEHICLE_TYPE</td><td>-</td><td>车辆类型标识(随 MQTT 消息上报)</td></tr>
  255. </table>
  256. <h3>[auth] 登录认证配置</h3>
  257. <table>
  258. <tr><th>参数</th><th>默认值</th><th>说明</th></tr>
  259. <tr><td>enabled</td><td>1</td><td>认证开关(0=关闭,1=开启)</td></tr>
  260. <tr><td>db_path</td><td>data/auth.db</td><td>认证数据库路径</td></tr>
  261. <tr><td>session_idle_timeout</td><td>1800</td><td>Session 空闲超时(秒)</td></tr>
  262. <tr><td>session_max_timeout</td><td>28800</td><td>Session 最大超时(秒)</td></tr>
  263. <tr><td>session_remember_timeout</td><td>604800</td><td>记住登录超时(秒)</td></tr>
  264. <tr><td>max_failed_attempts</td><td>5</td><td>最大登录失败次数</td></tr>
  265. <tr><td>lockout_duration</td><td>900</td><td>锁定时长(秒)</td></tr>
  266. </table>
  267. <h3>[ssl] HTTPS 配置(v2 — C++ 应用自管理)</h3>
  268. <table>
  269. <tr><th>参数</th><th>默认值</th><th>说明</th></tr>
  270. <tr><td>enabled</td><td>1</td><td>HTTPS 开关。v2 方案下必须设为 1(C++ 应用直接处理 TLS)</td></tr>
  271. <tr><td>cert_path</td><td>ssl/plate_fullchain.pem</td><td>SSL 证书路径(相对于安装目录)</td></tr>
  272. <tr><td>key_path</td><td>ssl/plate_privkey.pem</td><td>SSL 私钥路径(相对于安装目录)</td></tr>
  273. </table>
  274. <div class="note">v2 方案变更:nginx stream 只做 TCP 分流,不解密 TLS。SSL 证书由 C++ 应用自己管理,<code>enabled</code> 必须为 1。</div>
  275. <h3>[sync] 车牌称重并行同步配置(v43 新增)</h3>
  276. <table>
  277. <tr><th>参数</th><th>默认值</th><th>说明</th></tr>
  278. <tr><td>enabled</td><td>0</td><td>并行同步开关。<code>0</code>=串行v41模式(重量在照片上传成功后入队);<code>1</code>=并行v43模式(tb_num确定后立即并行触发重量读取+上传)</td></tr>
  279. <tr><td>reporter_timeout_sec</td><td>30</td><td>重量上报API超时(秒),最小5</td></tr>
  280. <tr><td>weight_retry_max</td><td>5</td><td>重量上传最大重试次数,最小0</td></tr>
  281. <tr><td>weight_retry_interval_sec</td><td>10</td><td>重量上传重试间隔(秒),最小1</td></tr>
  282. <tr><td>tb_num_dedup_sec</td><td>300</td><td>同联单编号去重窗口(秒),最小10。同一 tb_num 在此时间内只触发一次重量读取</td></tr>
  283. </table>
  284. <div class="tip"><b>v43 核心变更</b>:并行模式下,车牌识别成功并获取到 tb_num 后,照片上传和重量读取<strong>同时进行</strong>,互不阻塞。重量上报延迟从 v41 的 25~50s 降至 ≤5s,捕获率从 72% 提升至 ≥98%。</div>
  285. <div class="warn"><b>灰度策略</b>:首次部署建议保持 <code>enabled=0</code>(完全兼容v41行为),验证系统稳定后再改为 <code>enabled=1</code> 开启并行模式。修改 enabled 无需重启,秒级生效。</div>
  286. <h3>[system] 系统监控配置(v38 内存缓冲)</h3>
  287. <table>
  288. <tr><th>参数</th><th>默认值</th><th>说明</th></tr>
  289. <tr><td>monitor_interval</td><td>2</td><td>监控采集间隔(秒),delta 法计算 CPU 使用率</td></tr>
  290. <tr><td>temp_alert_threshold</td><td>70</td><td>温度告警阈值(°C)</td></tr>
  291. <tr><td>cpu_alert_threshold</td><td>90</td><td>CPU 告警阈值(%)</td></tr>
  292. <tr><td>db_path</td><td>data/system_metrics.db</td><td>监控数据库路径</td></tr>
  293. <tr><td>history_retention_days</td><td>7</td><td>原始数据保留天数</td></tr>
  294. <tr><td>aggregation_retention_days</td><td>365</td><td>聚合数据保留天数</td></tr>
  295. <tr><td>flush_time</td><td>23:20</td><td>每日定时刷盘时间(HH:MM)</td></tr>
  296. </table>
  297. <h3>[frpc] 内网穿透配置</h3>
  298. <table>
  299. <tr><th>参数</th><th>默认值</th><th>说明</th></tr>
  300. <tr><td>config_path</td><td>-</td><td>frpc.toml 配置文件路径</td></tr>
  301. <tr><td>protected_tunnels</td><td>-</td><td>受保护隧道名称(逗号分隔)</td></tr>
  302. <tr><td>auto_rollback</td><td>1</td><td>重启失败自动回滚(0/1)</td></tr>
  303. <tr><td>admin_addr</td><td>127.0.0.1</td><td>frpc Admin API 地址</td></tr>
  304. <tr><td>admin_port</td><td>7400</td><td>frpc Admin API 端口</td></tr>
  305. <tr><td>admin_user</td><td>-</td><td>frpc Admin 用户名</td></tr>
  306. <tr><td>admin_password</td><td>-</td><td>frpc Admin 密码</td></tr>
  307. </table>
  308. <h3>[log] 日志配置(v36 内存缓冲)</h3>
  309. <table>
  310. <tr><th>参数</th><th>默认值</th><th>说明</th></tr>
  311. <tr><td>enabled</td><td>1</td><td>内存缓冲模式(1=内存缓冲,0=直写文件)</td></tr>
  312. <tr><td>log_file</td><td>PlateRecApp.log</td><td>日志文件路径</td></tr>
  313. <tr><td>buffer_size_mb</td><td>20</td><td>内存缓冲区上限(MB)</td></tr>
  314. <tr><td>flush_time</td><td>23:20</td><td>每日定时刷盘时间(HH:MM)</td></tr>
  315. <tr><td>retention_days</td><td>30</td><td>日志文件保留天数</td></tr>
  316. <tr><td>redirect_stdout</td><td>1</td><td>捕获 stdout/stderr(0/1)</td></tr>
  317. <tr><td>auto_flush_on_exit</td><td>1</td><td>退出时立即刷盘(0/1)</td></tr>
  318. </table>
  319. <h3>[feishu] 飞书通知配置</h3>
  320. <table>
  321. <tr><th>参数</th><th>说明</th></tr>
  322. <tr><td>APP_ID</td><td>飞书应用 App ID</td></tr>
  323. <tr><td>APP_SECRET</td><td>飞书应用 App Secret</td></tr>
  324. <tr><td>CHAT_ID</td><td>飞书群聊 ID</td></tr>
  325. <tr><td>TIMEOUT_SECOND</td><td>请求超时时间(秒)</td></tr>
  326. <tr><td>WarningSigns</td><td>抑制紧急报警(1=抑制,0=正常)</td></tr>
  327. </table>
  328. <h3>[weight] 称重系统配置</h3>
  329. <table>
  330. <tr><th>参数</th><th>默认值</th><th>说明</th></tr>
  331. <tr><td>flagWeight</td><td>0</td><td>称重系统开关(0/1)</td></tr>
  332. <tr><td>weight_server_ip</td><td>-</td><td>称重服务器 IP</td></tr>
  333. <tr><td>weight_server_port</td><td>-</td><td>称重服务器端口</td></tr>
  334. <tr><td>weight_threshold_in</td><td>0</td><td>进场称重阈值(吨)</td></tr>
  335. <tr><td>weight_threshold_out</td><td>0</td><td>出场称重阈值(吨)</td></tr>
  336. <tr><td>default_entry_weight</td><td>0</td><td>默认进场重量</td></tr>
  337. <tr><td>default_exit_weight</td><td>0</td><td>默认出场重量</td></tr>
  338. <tr><td>weight_detection_time</td><td>5000</td><td>称重检测时间(毫秒)</td></tr>
  339. <tr><td>tcp_connect_timeout</td><td>3000</td><td>TCP 连接超时(毫秒)</td></tr>
  340. <tr><td>MAX_WEIGHT</td><td>100</td><td>最大称重值(吨)</td></tr>
  341. <tr><td>STABLE_SAMPLE_COUNT</td><td>5</td><td>稳定采样次数</td></tr>
  342. <tr><td>STABLE_THRESHOLD_KG</td><td>50</td><td>稳定判定阈值(公斤)</td></tr>
  343. <tr><td>CANDIDATE_DATA_COUNT</td><td>3</td><td>候选数据次数</td></tr>
  344. <tr><td>MAX_UPLOAD_RETRIES</td><td>3</td><td>上传最大重试次数</td></tr>
  345. <tr><td>RETRY_DELAY_MS</td><td>1000</td><td>重试延迟(毫秒)</td></tr>
  346. </table>
  347. <h3>[terminal] 终端/SSH 配置</h3>
  348. <table>
  349. <tr><th>参数</th><th>默认值</th><th>说明</th></tr>
  350. <tr><td>ttyd_port</td><td>7681</td><td>ttyd Web 终端端口</td></tr>
  351. <tr><td>ttyd_credential</td><td>-</td><td>ttyd 登录凭据(user:password)</td></tr>
  352. <tr><td>ttyd_max_clients</td><td>5</td><td>ttyd 最大客户端数</td></tr>
  353. <tr><td>ssh_keys_path</td><td>-</td><td>SSH 公钥存储路径</td></tr>
  354. </table>
  355. </div>
  356. </div>
  357. <!-- 4. RTSP 摄像头配置 -->
  358. <div class="card" id="sec4">
  359. <div class="card-title" onclick="toggleCard(this)">4. RTSP 摄像头配置 <span class="arrow">▼</span></div>
  360. <div class="card-body">
  361. <h3>摄像头地址格式</h3>
  362. <p>标准 RTSP 地址格式:</p>
  363. <pre>rtsp://用户名:密码@摄像头IP:554/stream</pre>
  364. <h3>四路摄像头说明</h3>
  365. <table>
  366. <tr><th>参数</th><th>方向</th><th>说明</th></tr>
  367. <tr><td>rtsp_url_front_in</td><td>前→进</td><td>前方摄像头,捕捉进场车辆</td></tr>
  368. <tr><td>rtsp_url_front_out</td><td>前→出</td><td>前方摄像头,捕捉出场车辆</td></tr>
  369. <tr><td>rtsp_url_side_in</td><td>侧→进</td><td>侧面摄像头,捕捉进场车辆</td></tr>
  370. <tr><td>rtsp_url_side_out</td><td>侧→出</td><td>侧面摄像头,捕捉出场车辆</td></tr>
  371. </table>
  372. <h3>硬件解码(树莓派5推荐配置)</h3>
  373. <table>
  374. <tr><th>模式</th><th>值</th><th>说明</th></tr>
  375. <tr><td>自动</td><td>auto</td><td>优先硬件解码,不可用则回退软件</td></tr>
  376. <tr><td>软件解码</td><td>soft</td><td>CPU 解码,兼容性最好</td></tr>
  377. <tr><td>DRM 硬件</td><td>drm</td><td>DRM 硬件加速(树莓派5推荐)</td></tr>
  378. </table>
  379. <div class="tip">树莓派 5 推荐:<code>hw_decode_mode=drm</code>,<code>hw_decode_device=/dev/dri/renderD128</code>,<code>rtsp_transport=tcp</code></div>
  380. </div>
  381. </div>
  382. <!-- 5. 车牌识别上传流程 -->
  383. <div class="card" id="sec5">
  384. <div class="card-title" onclick="toggleCard(this)">5. 车牌识别上传完整流程 <span class="arrow">▼</span></div>
  385. <div class="card-body">
  386. <h3>识别流程</h3>
  387. <ol>
  388. <li><strong>摄像头抓拍</strong>:RTSP 视频流实时解码,检测到车辆运动时触发抓拍</li>
  389. <li><strong>车牌识别</strong>:HyperLPR3 引擎识别车牌号,返回车牌文本 + 置信度</li>
  390. <li><strong>置信度过滤</strong>:低于 <code>PLATE_CONFIDENCE_THRESHOLD</code> 则丢弃</li>
  391. <li><strong>时间窗口去重</strong>:同一车牌在 <code>TIME_WINDOW</code> 分钟内不重复上传</li>
  392. <li><strong>交替锁定检查</strong>:同一车牌进出间隔低于 <code>in_out_interval</code> 分钟则锁定</li>
  393. <li><strong>ROI 过滤</strong>:如启用 ROI,只识别指定区域内的车牌</li>
  394. <li><strong>数据上传</strong>:HTTP POST 将识别结果 + 照片上传至 <code>app_api</code></li>
  395. <li><strong>MQTT 推送</strong>:如启用 MQTT,同步推送识别结果</li>
  396. <li><strong>飞书通知</strong>:异常报警发送飞书群消息</li>
  397. </ol>
  398. <h3>照片存储与自动清理</h3>
  399. <ul>
  400. <li>照片保存在 <code>PlateJPG/</code> 目录下,文件名格式:<code>车牌号_时间戳_方向.jpg</code>(如 <code>沪FQ7108_1787365677_side_out.jpg</code>)</li>
  401. <li>系统每日凌晨 <strong>2:00</strong> 自动执行照片清理任务(间隔 24 小时)</li>
  402. <li>清理策略:当 <code>PlateJPG/</code> 目录下所有 .jpg 文件总大小超过 <code>PhotoMaxCapacityMB</code>(默认 1024MB)时,按文件修改时间从旧到新逐个删除,直到总大小降至阈值以下</li>
  403. <li>删除照片后递归清理空子目录</li>
  404. <li>清理日志输出示例:
  405. <pre>[照片清理] PlateJPG/ 当前 1156 MB / 1024 MB 阈值,共 8234 个文件
  406. [照片清理] 删除 1523 张照片(按时间从旧到新),释放 156 MB,当前 1000 MB
  407. [照片清理] 同步清空DB路径字段: 2847 个字段已更新</pre>
  408. </li>
  409. </ul>
  410. <h4>照片清理与数据库同步(v41 修复)</h4>
  411. <p>照片被物理删除后,系统同步清空 <code>upload_records</code> 表中对应的 <code>lo_photo_path</code> / <code>hi_photo_path</code> 字段(设为 NULL):</p>
  412. <ul>
  413. <li>业务记录(车牌号、联单编号、抓拍时间、上传状态等)<strong>完整保留</strong>,仅清空路径字段</li>
  414. <li>Web 前端路径为 NULL 时显示灰色斜体「已清理」占位,不再显示 broken image</li>
  415. <li>即使 DB 清理与页面加载存在竞态,<code>&lt;img onerror&gt;</code> 也会自动替换为「已清理」</li>
  416. <li>程序启动时自动执行一次孤儿路径扫描(<code>db_purge_missing_photo_paths</code>),修复历史遗留数据</li>
  417. </ul>
  418. <div class="tip">💡 调整容量阈值:修改 <code>config.ini</code> 中 <code>PhotoMaxCapacityMB</code> 的值并重启服务。例如设为 2048 表示允许 2GB 照片存储。</div>
  419. </div>
  420. </div>
  421. <!-- 6. MQTT -->
  422. <div class="card" id="sec6">
  423. <div class="card-title" onclick="toggleCard(this)">6. MQTT 数据上报配置 <span class="arrow">▼</span></div>
  424. <div class="card-body">
  425. <h3>启用条件</h3>
  426. <p>在 <code>[mqtt]</code> 段中填写 <code>MQTT_HOST</code> 即自动启用 MQTT 推送。</p>
  427. <h3>配置示例</h3>
  428. <pre>[mqtt]
  429. MQTT_HOST=mqtt.example.com
  430. MQTT_PORT=1883
  431. MQTT_USER=username
  432. MQTT_PASS=password
  433. MQTT_TOPIC=plate/recognition
  434. MQTT_CLIENT_ID=plate_rec_station_01</pre>
  435. <div class="warn">MQTT 连接失败时系统会自动重连,不影响识别和上传功能。</div>
  436. </div>
  437. </div>
  438. <!-- 7. 称重系统 -->
  439. <div class="card" id="sec7">
  440. <div class="card-title" onclick="toggleCard(this)">7. 称重系统对接 <span class="arrow">▼</span></div>
  441. <div class="card-body">
  442. <h3>工作原理</h3>
  443. <p>称重系统通过 TCP 协议与称重地磅通信,车牌识别成功后自动读取称重数据并关联上传。</p>
  444. <h3>重量判定逻辑</h3>
  445. <ul>
  446. <li>连续 <code>STABLE_SAMPLE_COUNT</code> 次读数变化 &lt; <code>STABLE_THRESHOLD_KG</code> 公斤,视为稳定</li>
  447. <li>稳定后取 <code>CANDIDATE_DATA_COUNT</code> 次读数的中值作为最终重量</li>
  448. <li>重量超过 <code>MAX_WEIGHT</code> 吨视为异常,丢弃</li>
  449. </ul>
  450. <h3>阈值说明</h3>
  451. <ul>
  452. <li><code>weight_threshold_in</code>:进场最低重量(吨),低于此值可能表示空车</li>
  453. <li><code>weight_threshold_out</code>:出场最低重量(吨)</li>
  454. <li><code>default_entry_weight</code>:无法获取称重数据时的默认进场重量</li>
  455. <li><code>default_exit_weight</code>:无法获取称重数据时的默认出场重量</li>
  456. <li><code>weight_detection_time</code>:称重检测时间(毫秒,默认 5000)</li>
  457. <li><code>tcp_connect_timeout</code>:TCP 连接超时(毫秒,默认 3000)</li>
  458. <li><code>MAX_UPLOAD_RETRIES</code>:上传最大重试次数(默认 3)</li>
  459. <li><code>RETRY_DELAY_MS</code>:重试延迟(毫秒,默认 1000)</li>
  460. </ul>
  461. </div>
  462. </div>
  463. <!-- 8. ROI -->
  464. <div class="card" id="sec8">
  465. <div class="card-title" onclick="toggleCard(this)">8. ROI 区域识别配置 <span class="arrow">▼</span></div>
  466. <div class="card-body">
  467. <h3>配置方式</h3>
  468. <ul>
  469. <li><strong>Web 可视化</strong>:首页连接状态面板中拖拽设置 ROI 区域</li>
  470. <li><strong>手动配置</strong>:在 config.ini 的 <code>[config]</code> 段填写坐标值</li>
  471. </ul>
  472. <h3>参数说明</h3>
  473. <table>
  474. <tr><th>参数</th><th>说明</th></tr>
  475. <tr><td>roi_in_x/y/w/h</td><td>进场 ROI 区域坐标和尺寸(像素)</td></tr>
  476. <tr><td>roi_in_enabled</td><td>1=启用进场 ROI</td></tr>
  477. <tr><td>roi_out_x/y/w/h</td><td>出场 ROI 区域坐标和尺寸</td></tr>
  478. <tr><td>roi_out_enabled</td><td>1=启用出场 ROI</td></tr>
  479. <tr><td>roi_debug_enabled</td><td>1=在抓拍图上绘制 ROI 框(调试用)</td></tr>
  480. </table>
  481. </div>
  482. </div>
  483. <!-- 9. 交替锁定 -->
  484. <div class="card" id="sec9">
  485. <div class="card-title" onclick="toggleCard(this)">9. 交替锁定机制 <span class="arrow">▼</span></div>
  486. <div class="card-body">
  487. <h3>机制说明</h3>
  488. <p>防止同一车牌短时间内进出频繁触发(如车辆在门口调头)。同一车牌从"进"切换到"出"必须间隔 <code>in_out_interval</code> 分钟以上。</p>
  489. <h3>工作流程</h3>
  490. <ol>
  491. <li>车牌 A 被识别为"进"方向</li>
  492. <li>5 分钟内(默认)车牌 A 再次被识别为"出"方向</li>
  493. <li>系统判定为异常,创建交替锁定记录</li>
  494. <li>锁定期间该车牌不会被上传</li>
  495. <li>超过 2 小时锁定自动清零,或手动在"锁定管理"页面清除</li>
  496. </ol>
  497. <table>
  498. <tr><th>参数</th><th>默认值</th><th>说明</th></tr>
  499. <tr><td>in_out_interval</td><td>5</td><td>交替锁定间隔(⚠️ 单位分钟),小于此间隔的进/出切换会被锁定</td></tr>
  500. <tr><td>AlternatingMerge</td><td>0</td><td>交替合并开关</td></tr>
  501. </table>
  502. <div class="warn"><code>in_out_interval</code> 单位是<strong>分钟</strong>,不是秒!填 5 表示 5 分钟 = 300 秒。</div>
  503. </div>
  504. </div>
  505. <!-- 10. 认证与权限 -->
  506. <div class="card" id="sec10">
  507. <div class="card-title" onclick="toggleCard(this)">10. 登录认证与权限 <span class="arrow">▼</span></div>
  508. <div class="card-body">
  509. <h3>角色定义</h3>
  510. <table>
  511. <tr><th>角色</th><th>Role值</th><th>可访问页面</th></tr>
  512. <tr><td>普通用户</td><td>0</td><td>首页、视频、称重、锁定管理</td></tr>
  513. <tr><td>管理员</td><td>1</td><td>普通用户 + 系统配置</td></tr>
  514. <tr><td>超级管理员</td><td>2</td><td>所有页面(含系统监控、frpc管理、SSH密钥、日志、帮助)</td></tr>
  515. </table>
  516. <h3>默认账号</h3>
  517. <table>
  518. <tr><th>用户名</th><th>密码</th><th>角色</th></tr>
  519. <tr><td>admin</td><td>zhongjin188A</td><td>超级管理员</td></tr>
  520. <tr><td>zhonjin</td><td>zhonjin</td><td>普通用户</td></tr>
  521. </table>
  522. <h3>安全特性</h3>
  523. <ul>
  524. <li>密码 bcrypt 加密存储(cost=12)</li>
  525. <li>连续登录失败锁定账号</li>
  526. <li>CSRF Token 防护</li>
  527. <li>Session 超时自动失效</li>
  528. <li>登录速率限制</li>
  529. </ul>
  530. </div>
  531. </div>
  532. <!-- 11. frpc.toml 详解 -->
  533. <div class="card" id="sec11">
  534. <div class="card-title" onclick="toggleCard(this)">11. frpc.toml 内网穿透配置详解 <span class="arrow">▼</span></div>
  535. <div class="card-body">
  536. <h3>配置文件位置</h3>
  537. <p>frpc 配置文件路径由 <code>config.ini</code> 的 <code>[frpc] config_path</code> 指定,通常为安装目录下的 <code>frpc.toml</code>。</p>
  538. <h3>v2 方案 — nginx stream 分流(推荐)</h3>
  539. <p>使用 nginx stream + ssl_preread 方案,<strong>只需一个公网端口 40963</strong> 即可同时处理 HTTP 和 HTTPS:</p>
  540. <pre># frpc.toml — v2 方案(nginx stream 分流)
  541. serverAddr = "discover.zhonjin.com"
  542. serverPort = 9443
  543. auth.method = "token"
  544. auth.token = "你的frps认证Token"
  545. transport.tls.enable = true
  546. clientID = "ssd_40960"
  547. # frpc Admin HTTP API
  548. webServer.addr = "127.0.0.1"
  549. webServer.port = 7400
  550. webServer.user = "admin"
  551. webServer.password = "你的管理密码"
  552. # 隧道1:主服务(外网40963→本机nginx:40963,stream自动分流HTTP/HTTPS)
  553. [[proxies]]
  554. name = "plate_web"
  555. type = "tcp"
  556. localIP = "127.0.0.1"
  557. localPort = 40963 # ← 转发到 nginx stream,不是 C++ 的 8080
  558. remotePort = 40963
  559. # 隧道2:ttyd终端(外网40964→本机nginx:8082→ttyd:7681)
  560. [[proxies]]
  561. name = "ttyd_web"
  562. type = "tcp"
  563. localIP = "127.0.0.1"
  564. localPort = 8082 # ← nginx HTTPS 反向代理(SSL 终止 + WebSocket)
  565. remotePort = 40964</pre>
  566. <h3>隧道说明(v2)</h3>
  567. <table>
  568. <tr><th>隧道名</th><th>外网端口</th><th>本机端口</th><th>用途</th></tr>
  569. <tr><td>plate_web</td><td>40963</td><td>nginx stream:40963</td><td>HTTP/HTTPS 合一,stream 自动分流</td></tr>
  570. <tr><td>ttyd_web</td><td>40964</td><td>nginx HTTPS:8082</td><td>Web 终端(nginx SSL→ttyd:7681)</td></tr>
  571. </table>
  572. <h3>v2 数据流向</h3>
  573. <pre>HTTP 流量: 公网:40963 → frpc → nginx:40963 (stream) → $ssl_preread="" → 127.0.0.1:8081 (nginx HTTP) → 301 重定向到 HTTPS
  574. HTTPS 流量: 公网:40963 → frpc → nginx:40963 (stream) → $ssl_preread≠"" → 127.0.0.1:8080 (C++ HTTPS) → 直接处理
  575. Web SSH: 公网:40964 → frpc → nginx:8082 (HTTPS反向代理, SSL终止) → 127.0.0.1:7681 (ttyd HTTP)</pre>
  576. <div class="tip"><strong>v1→v2 迁移注意:</strong>不再需要 40965 端口。HTTPS 主服务统一使用 <code>https://mqtt.zhonjin.com:40963/</code>。</div>
  577. <h3>参数详解</h3>
  578. <table>
  579. <tr><th>参数</th><th>说明</th></tr>
  580. <tr><td>serverAddr</td><td>frps 服务器域名或 IP</td></tr>
  581. <tr><td>serverPort</td><td>frps 服务端口(默认 7000,本项目用 9443)</td></tr>
  582. <tr><td>auth.method</td><td>认证方式,固定为 <code>token</code></td></tr>
  583. <tr><td>auth.token</td><td>与 frps 约定的认证 Token</td></tr>
  584. <tr><td>webServer.addr/port</td><td>frpc Admin API 监听地址,PlateRecApp 通过此接口获取隧道状态</td></tr>
  585. <tr><td>webServer.user/password</td><td>Admin API 认证凭据(需与 config.ini [frpc] 段一致)</td></tr>
  586. <tr><td>[[proxies]] name</td><td>隧道唯一名称,PlateRecApp Web 界面按名称识别</td></tr>
  587. <tr><td>[[proxies]] type</td><td>隧道类型,本项目全部使用 <code>tcp</code>(透传 TCP 流)</td></tr>
  588. <tr><td>[[proxies]] localPort</td><td>本机目标端口,frpc 将外网流量转发到此端口</td></tr>
  589. <tr><td>[[proxies]] remotePort</td><td>frps 服务器暴露的外网端口,客户端通过此端口访问</td></tr>
  590. </table>
  591. <h3>隧道保护</h3>
  592. <p>在 <code>config.ini [frpc]</code> 段配置受保护隧道,防止 Web 界面误删关键隧道:</p>
  593. <pre>protected_tunnels = plate_web,ttyd_web</pre>
  594. <div class="warn">受保护的隧道无法通过 Web 管理界面删除。如需删除,必须先修改 config.ini 中的 <code>protected_tunnels</code> 字段。</div>
  595. <h3>frpc 管理命令</h3>
  596. <pre># 查看 frpc 状态
  597. systemctl status frpc
  598. # 重启 frpc
  599. sudo systemctl restart frpc
  600. # 查看 frpc 日志
  601. tail -f /var/log/frpc.log
  602. # 通过 Admin API 查看隧道状态
  603. curl -u admin:密码 http://127.0.0.1:7400/api/status/tcp</pre>
  604. </div>
  605. </div>
  606. <!-- 12. Nginx 反向代理详解 -->
  607. <div class="card" id="sec12">
  608. <div class="card-title" onclick="toggleCard(this)">12. Nginx stream + ssl_preread 配置详解(v2) <span class="arrow">▼</span></div>
  609. <div class="card-body">
  610. <h3>架构说明</h3>
  611. <p>v2 方案使用 nginx 的 <code>stream</code> 模块 + <code>ssl_preread</code> 功能,在 <strong>TCP 层面</strong>检测流量类型并自动分流:</p>
  612. <ul>
  613. <li><strong>端口 40963</strong>:nginx stream 监听,检测协议类型(TLS / HTTP)</li>
  614. <li><strong>TLS 流量</strong> → 透传到 C++ HTTPS 服务(127.0.0.1:8080)</li>
  615. <li><strong>HTTP 流量</strong> → nginx HTTP 模块(127.0.0.1:8081)→ 301 重定向到 HTTPS</li>
  616. </ul>
  617. <div class="tip"><strong>关键区别:</strong>stream 模块是 TCP 层代理,不解密 TLS 流量。SSL 证书由 C++ 应用自己管理,nginx 只做协议检测和分流。</div>
  618. <h3>nginx.conf 完整配置</h3>
  619. <p>文件路径:<code>/etc/nginx/nginx.conf</code>(替换整个文件)</p>
  620. <pre># 加载 stream 动态模块
  621. load_module modules/ngx_stream_module.so;
  622. events {
  623. worker_connections 1024;
  624. }
  625. # ============================================================
  626. # stream 块 — TCP 层协议分流(在 http 块外面)
  627. # ============================================================
  628. stream {
  629. # 根据 ssl_preread 检测结果选择后端
  630. # $ssl_preread_protocol 的可能值:
  631. # "" → HTTP 流量(非 TLS)
  632. # "TLSv1.2" → TLS 1.2
  633. # "TLSv1.3" → TLS 1.3
  634. map $ssl_preread_protocol $backend {
  635. "" http_backend; # HTTP → nginx HTTP 8081
  636. default tls_backend; # TLS → C++ HTTPS 8080
  637. }
  638. # TLS 后端 — C++ HTTPS 服务
  639. upstream tls_backend {
  640. server 127.0.0.1:8080;
  641. }
  642. # HTTP 后端 — nginx HTTP 301 重定向
  643. upstream http_backend {
  644. server 127.0.0.1:8081;
  645. }
  646. # 主服务器 — 监听公网端口
  647. server {
  648. listen 40963;
  649. proxy_pass $backend;
  650. ssl_preread on; # 启用 SSL 预读检测协议
  651. }
  652. }
  653. # ============================================================
  654. # http 块 — HTTP → HTTPS 301 重定向
  655. # ============================================================
  656. http {
  657. sendfile on;
  658. tcp_nopush on;
  659. tcp_nodelay on;
  660. keepalive_timeout 65;
  661. include /etc/nginx/mime.types;
  662. default_type application/octet-stream;
  663. access_log /var/log/nginx/access.log;
  664. error_log /var/log/nginx/error.log;
  665. # HTTP → HTTPS 301 重定向
  666. server {
  667. listen 8081;
  668. server_name _;
  669. return 301 https://$host:40963$request_uri;
  670. }
  671. # ============================================================
  672. # ttyd Web 终端 HTTPS 反向代理(端口 8082)
  673. # ============================================================
  674. # frpc 隧道:外网 40964 → nginx:8082 → ttyd:7681
  675. # nginx 做 SSL 终止,再通过 HTTP 代理到 ttyd(含 WebSocket 支持)
  676. server {
  677. listen 8082 ssl;
  678. server_name _;
  679. ssl_certificate /etc/nginx/ssl/plate_fullchain.pem;
  680. ssl_certificate_key /etc/nginx/ssl/plate_privkey.pem;
  681. ssl_protocols TLSv1.2 TLSv1.3;
  682. ssl_ciphers HIGH:!aNULL:!MD5:!RC4;
  683. ssl_prefer_server_ciphers on;
  684. ssl_session_cache shared:SSL_Ttyd:5m;
  685. ssl_session_timeout 1h;
  686. location / {
  687. proxy_pass http://127.0.0.1:7681;
  688. proxy_set_header Host $host;
  689. proxy_set_header X-Real-IP $remote_addr;
  690. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  691. proxy_set_header X-Forwarded-Proto $scheme;
  692. # WebSocket 支持(ttyd 核心通信方式)
  693. proxy_http_version 1.1;
  694. proxy_set_header Upgrade $http_upgrade;
  695. proxy_set_header Connection "upgrade";
  696. # ttyd 长连接超时
  697. proxy_read_timeout 86400s;
  698. proxy_send_timeout 86400s;
  699. }
  700. }
  701. }</pre>
  702. <h3>核心原理</h3>
  703. <table>
  704. <tr><th>组件</th><th>作用</th><th>说明</th></tr>
  705. <tr><td>stream 块</td><td>TCP 层代理</td><td>与 http 块平级,不处理 HTTP 协议,只做 TCP 转发</td></tr>
  706. <tr><td>ssl_preread on</td><td>SSL 预读</td><td>在 TLS 握手阶段读取 ClientHello,获取协议版本,不解密</td></tr>
  707. <tr><td>map $ssl_preread_protocol</td><td>协议分流</td><td>空字符串 = HTTP,非空 = TLS</td></tr>
  708. <tr><td>upstream tls_backend</td><td>TLS 后端</td><td>透传到 C++ 应用(8080),C++ 自己处理 TLS 握手</td></tr>
  709. <tr><td>upstream http_backend</td><td>HTTP 后端</td><td>转发到 nginx HTTP 8081,返回 301 重定向</td></tr>
  710. <tr><td>server 8082 ssl</td><td>ttyd HTTPS 代理</td><td>SSL 终止后代理到 ttyd:7681,支持 WebSocket</td></tr>
  711. </table>
  712. <h3>部署步骤</h3>
  713. <ol>
  714. <li>安装 stream 模块:<code>sudo apt install -y libnginx-mod-stream</code></li>
  715. <li>验证模块:<code>ls -l /usr/lib/nginx/modules/ngx_stream_module.so</code></li>
  716. <li>备份原配置:<code>sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak</code></li>
  717. <li>替换配置:<code>sudo cp nginx-stream.conf /etc/nginx/nginx.conf</code></li>
  718. <li>删除默认站点(如有):<code>sudo rm -f /etc/nginx/sites-enabled/default</code></li>
  719. <li>验证配置:<code>sudo nginx -t</code></li>
  720. <li>启动 nginx:<code>sudo systemctl enable nginx && sudo systemctl restart nginx</code></li>
  721. </ol>
  722. <h3>前置条件</h3>
  723. <table>
  724. <tr><th>项目</th><th>要求</th></tr>
  725. <tr><td>nginx 版本</td><td>≥ 1.11.5(支持 stream 模块)</td></tr>
  726. <tr><td>stream 模块</td><td>libnginx-mod-stream(Debian)或 nginx-mod-stream(CentOS)</td></tr>
  727. <tr><td>C++ 应用</td><td>必须启用 HTTPS,监听 127.0.0.1:8080</td></tr>
  728. <tr><td>SSL 证书</td><td>C++ 应用自行管理(config.ini 的 [ssl] 段)</td></tr>
  729. </table>
  730. <h3>验证方法</h3>
  731. <pre># 检查端口监听
  732. ss -tlnp | grep -E '40963|8080|8081'
  733. # 预期输出:
  734. # LISTEN 0.0.0.0:40963 nginx (stream)
  735. # LISTEN 127.0.0.1:8081 nginx (http 301)
  736. # LISTEN 0.0.0.0:8080 PlateRecApp (HTTPS)
  737. # 测试 HTTP 重定向(应返回 301)
  738. curl -I http://localhost:40963/
  739. # 预期:HTTP/1.1 301 Moved Permanently
  740. # Location: https://localhost:40963/
  741. # 测试 HTTPS(应返回 200)
  742. curl -kI https://localhost:40963/
  743. # 预期:HTTP/1.1 200 OK
  744. # 浏览器测试(清除缓存或用无痕模式)
  745. # 访问 http://mqtt.zhonjin.com:40963/ → 自动跳转到 https://mqtt.zhonjin.com:40963/</pre>
  746. <h3>端口规划总结</h3>
  747. <table>
  748. <tr><th>端口</th><th>服务</th><th>说明</th></tr>
  749. <tr><td><strong>40963</strong></td><td>nginx stream</td><td>公网入口,TLS/HTTP 分流</td></tr>
  750. <tr><td>8081</td><td>nginx http</td><td>HTTP 301 重定向(仅内部)</td></tr>
  751. <tr><td>8080</td><td>C++ HTTPS</td><td>PlateRecApp Web 服务(SSL 自管理)</td></tr>
  752. <tr><td><strong>40964</strong></td><td>nginx HTTPS→ttyd</td><td>Web 终端(nginx:8082 SSL终止→ttyd:7681)</td></tr>
  753. <tr><td>8082</td><td>nginx HTTPS 代理</td><td>ttyd Web 终端 HTTPS 反向代理(仅内部)</td></tr>
  754. <tr><td>7400</td><td>frpc Admin</td><td>隧道状态查询(仅 127.0.0.1)</td></tr>
  755. </table>
  756. </div>
  757. </div>
  758. <!-- 13. SSL/HTTPS 证书配置 -->
  759. <div class="card" id="sec13">
  760. <div class="card-title" onclick="toggleCard(this)">13. SSL/HTTPS 证书配置(v2 — C++ 应用自管理) <span class="arrow">▼</span></div>
  761. <div class="card-body">
  762. <div class="note"><strong>v2 方案变更:</strong>nginx stream 只做 TCP 层协议分流,不解密 TLS 流量。SSL 证书由 C++ 应用(PlateRecApp)自己管理,通过 config.ini 的 <code>[ssl]</code> 段配置。nginx 不再需要 SSL 证书。</div>
  763. <h3>config.ini [ssl] 段配置</h3>
  764. <pre>[ssl]
  765. enabled=1
  766. cert_path=ssl/plate_fullchain.pem
  767. key_path=ssl/plate_privkey.pem</pre>
  768. <div class="tip">v2 方案下 SSL 证书文件放在安装目录的 <code>ssl/</code> 子目录下(或任意自定义路径),不再需要 <code>/etc/nginx/ssl/</code>。</div>
  769. <h3>方式一:Let's Encrypt 证书 — 阿里云 DNS 验证(推荐)</h3>
  770. <p>本项目使用 frpc 内网穿透,<strong>无公网 80 端口</strong>,因此不能使用传统的 HTTP-01 验证。推荐使用 <strong>DNS-01 验证</strong>,通过阿里云 DNS API 自动完成,<strong>不需要任何端口</strong>。</p>
  771. <h4>方案对比</h4>
  772. <table>
  773. <tr><th>验证方式</th><th>端口要求</th><th>通配符</th><th>自动续期</th><th>本项目适用</th></tr>
  774. <tr><td>HTTP-01 (standalone)</td><td>公网 80</td><td>❌</td><td>✅</td><td>❌ 80 端口不可用</td></tr>
  775. <tr><td>TLS-ALPN-01</td><td>公网 443</td><td>❌</td><td>✅</td><td>❌ 443 端口不可用</td></tr>
  776. <tr><td><strong>DNS-01 (dns-aliyun)</strong></td><td><strong>无</strong></td><td><strong>✅</strong></td><td><strong>✅</strong></td><td><strong>✅ 推荐</strong></td></tr>
  777. <tr><td>手动 DNS</td><td>无</td><td>✅</td><td>❌ 手动</td><td>⚠️ 每 90 天手动操作</td></tr>
  778. </table>
  779. <h4>一键部署</h4>
  780. <pre># 1. 安装 certbot + 阿里云 DNS 插件
  781. sudo apt update
  782. sudo apt install -y certbot python3-pip
  783. sudo pip3 install certbot-dns-aliyun
  784. # 2. 配置阿里云 AccessKey(RAM 子账号,需 AliyunDNSFullAccess 权限)
  785. sudo mkdir -p /etc/letsencrypt
  786. sudo vim /etc/letsencrypt/aliyun.ini
  787. # 内容:
  788. # dns_aliyun_access_key = 你的AccessKeyID
  789. # dns_aliyun_access_key_secret = 你的AccessKeySecret
  790. sudo chmod 600 /etc/letsencrypt/aliyun.ini
  791. # 3. 一键申请通配符证书(自动通过 DNS API 验证)
  792. sudo certbot certonly \
  793. --authenticator dns-aliyun \
  794. --dns-aliyun-credentials /etc/letsencrypt/aliyun.ini \
  795. --dns-aliyun-propagation-seconds 30 \
  796. --server https://acme-v02.api.letsencrypt.org/directory \
  797. --agree-tos --no-eff-email \
  798. --email admin@zhonjin.com \
  799. -d mqtt.zhonjin.com \
  800. -d "*.zhonjin.com"
  801. # 4. 部署证书到 PlateRecApp
  802. mkdir -p ssl/
  803. sudo cp /etc/letsencrypt/live/mqtt.zhonjin.com/fullchain.pem ssl/plate_fullchain.pem
  804. sudo cp /etc/letsencrypt/live/mqtt.zhonjin.com/privkey.pem ssl/plate_privkey.pem
  805. sudo chown stevenroc:stevenroc ssl/*.pem
  806. chmod 644 ssl/plate_fullchain.pem
  807. chmod 600 ssl/plate_privkey.pem
  808. # 5. 配置自动续期(每天 3:00 检查,续期后自动部署)
  809. (crontab -l 2>/dev/null; echo "0 3 * * * certbot renew --quiet --deploy-hook /opt/openAI/project/003.PlateRecAPP/scripts/renew_ssl.sh") | crontab -
  810. # 6. 重启 PlateRecApp
  811. sudo systemctl restart PlateRecApp</pre>
  812. <h4>续期部署脚本 renew_ssl.sh</h4>
  813. <pre>#!/bin/bash
  814. # /opt/openAI/project/003.PlateRecAPP/scripts/renew_ssl.sh
  815. DOMAIN="mqtt.zhonjin.com"
  816. SSL_DIR="/opt/openAI/project/003.PlateRecAPP/ssl"
  817. cp /etc/letsencrypt/live/${DOMAIN}/fullchain.pem ${SSL_DIR}/plate_fullchain.pem
  818. cp /etc/letsencrypt/live/${DOMAIN}/privkey.pem ${SSL_DIR}/plate_privkey.pem
  819. chown stevenroc:stevenroc ${SSL_DIR}/*.pem
  820. chmod 644 ${SSL_DIR}/plate_fullchain.pem
  821. chmod 600 ${SSL_DIR}/plate_privkey.pem
  822. systemctl restart PlateRecApp
  823. echo "$(date) SSL 证书续期部署成功" >> /opt/openAI/project/003.PlateRecAPP/ssl_renew.log</pre>
  824. <h4>阿里云 RAM 权限配置</h4>
  825. <ol>
  826. <li>登录 <a href="https://ram.console.aliyun.com/" target="_blank">RAM 控制台</a> → 用户 → 创建用户</li>
  827. <li>勾选 <strong>OpenAPI 调用访问</strong>,保存 AccessKey ID 和 Secret</li>
  828. <li>添加权限:<code>AliyunDNSFullAccess</code>(云解析 DNS 管理)</li>
  829. </ol>
  830. <div class="tip">DNS 验证原理:certbot 通过阿里云 API 自动添加 <code>_acme-challenge.mqtt.zhonjin.com</code> TXT 记录,Let's Encrypt CA 查询 DNS 验证域名所有权,全程无需开放任何端口。</div>
  831. <div class="warn"><strong>前提:</strong>域名 mqtt.zhonjin.com 的 DNS 解析必须使用阿里云(<code>dns9.hichina.com</code>)。如果 DNS 在其他服务商,需先迁移到阿里云,或改用对应插件(如 <code>certbot-dns-cloudflare</code>、<code>certbot-dns-dnspod</code>)。</div>
  832. <h3>方式二:自签名证书</h3>
  833. <p>无公网域名时使用自签名证书,浏览器会显示警告但加密通信正常:</p>
  834. <pre># 创建证书目录
  835. mkdir -p ssl/
  836. # 生成自签名证书(10年有效期)
  837. openssl req -x509 -newkey rsa:2048 -nodes \
  838. -keyout ssl/plate_privkey.pem \
  839. -out ssl/plate_fullchain.pem \
  840. -days 3650 \
  841. -subj "/CN=mqtt.zhonjin.com/O=PlateRecApp/C=CN"
  842. # 设置权限
  843. chmod 600 ssl/plate_privkey.pem
  844. chmod 644 ssl/plate_fullchain.pem
  845. # 重启 PlateRecApp 加载证书
  846. sudo systemctl restart PlateRecApp</pre>
  847. <h3>证书文件说明</h3>
  848. <table>
  849. <tr><th>文件</th><th>用途</th><th>config.ini 参数</th></tr>
  850. <tr><td>plate_fullchain.pem</td><td>完整证书链(含中间 CA)</td><td>cert_path</td></tr>
  851. <tr><td>plate_privkey.pem</td><td>私钥文件(严禁泄露)</td><td>key_path</td></tr>
  852. </table>
  853. <h3>v1→v2 迁移说明</h3>
  854. <div class="warn"><strong>v1 方案:</strong>SSL 证书由 nginx 管理(<code>/etc/nginx/ssl/</code>),nginx 做 HTTPS 终止后转发 HTTP 到 C++。<br>
  855. <strong>v2 方案:</strong>SSL 证书由 C++ 应用管理,nginx stream 只做 TCP 分流,不解密 TLS。不再需要 <code>/etc/nginx/ssl/</code> 目录。</div>
  856. </div>
  857. </div>
  858. <!-- 14. 系统监控与日志 -->
  859. <div class="card" id="sec14">
  860. <div class="card-title" onclick="toggleCard(this)">14. 系统监控与日志 <span class="arrow">▼</span></div>
  861. <div class="card-body">
  862. <h3>系统监控(实时)</h3>
  863. <ul>
  864. <li>CPU 使用率(delta 法计算,含每核使用率、负载均值)</li>
  865. <li>内存使用率(总量/已用/可用)</li>
  866. <li>磁盘使用率(根分区容量/已用/可用)</li>
  867. <li>CPU 温度监控,超阈值告警(阈值由 <code>temp_alert_threshold</code> 配置)</li>
  868. <li>CPU 使用率告警(阈值由 <code>cpu_alert_threshold</code> 配置)</li>
  869. <li>PlateRecApp 进程资源占用(RSS 内存、CPU 占用)</li>
  870. <li>frpc 运行状态、MQTT 连接状态、称重系统连接状态</li>
  871. </ul>
  872. <p>数据来源:通过读取 <code>/proc</code>、<code>/sys</code>、<code>statvfs</code> 等系统接口实时采集,页面每 2 秒自动刷新。</p>
  873. <h3>历史监控数据(v37+)</h3>
  874. <p>系统自动采集并存储监控数据到 SQLite 数据库(<code>data/system_metrics.db</code>),支持历史趋势查询:</p>
  875. <ul>
  876. <li><strong>采集间隔</strong>:<code>monitor_interval</code>(默认 2 秒),delta 法计算 CPU 使用率,不阻塞采集线程</li>
  877. <li><strong>定时刷盘</strong>:每日 <code>flush_time</code>(默认 23:20)将内存监控数据批量写入 <code>system_metrics.db</code>,减少 SD 卡写入次数(v38 起)</li>
  878. <li><strong>三级存储</strong>:原始数据(2秒间隔)→ 小时聚合 → 日聚合,自动降采样</li>
  879. <li><strong>自动清理</strong>:原始数据保留 <code>history_retention_days</code> 天(默认 7 天),聚合数据保留 <code>aggregation_retention_days</code> 天(默认 365 天)</li>
  880. <li><strong>历史查询 API</strong>:<code>/api/monitor/history?range=1h|6h|24h|7d|30d</code>,自动选择合适的数据粒度</li>
  881. <li><strong>统计接口</strong>:<code>/api/monitor/stats</code> 返回数据库记录数和运行状态</li>
  882. </ul>
  883. <h3>SD 卡写入保护策略(v38)</h3>
  884. <ul>
  885. <li><strong>系统监控</strong>:内存缓冲,每日 23:20 一次写入,SD 卡写入从 2880 次/天降至 1 次</li>
  886. <li><strong>运行日志</strong>:内存环形缓冲区,Web 日志页面零磁盘 I/O,每日 23:20 写入 <code>PlateRecApp_YYYYMMDD.log</code></li>
  887. <li><strong>旧版日志清理</strong>:内存模式启用时自动跳过旧版 copytruncate,避免无意义读写</li>
  888. <li><strong>退出安全刷盘</strong>:SIGTERM/SIGINT 时立即执行最后一次刷盘</li>
  889. </ul>
  890. <h3>日志管理(v36 内存缓冲)</h3>
  891. <ul>
  892. <li><strong>内存缓冲模式</strong>(<code>enabled=1</code>,默认):所有日志写入内存环形缓冲区,Web 日志页面直接从内存读取,零磁盘 I/O</li>
  893. <li><strong>直写文件模式</strong>(<code>enabled=0</code>):传统模式,日志直接写入文件</li>
  894. <li><strong>定时刷盘</strong>:每日 <code>flush_time</code>(默认 23:20)自动将内存日志批量写入 <code>PlateRecApp_YYYYMMDD.log</code></li>
  895. <li><strong>退出刷盘</strong>:<code>auto_flush_on_exit=1</code> 时,收到 SIGTERM/SIGINT 信号立即刷盘,防止日志丢失</li>
  896. <li><strong>stdout 捕获</strong>:<code>redirect_stdout=1</code> 时,stdout/stderr 通过 pipe 重定向到内存缓冲区统一收集</li>
  897. <li>Web 界面实时查看日志(支持 Tail 模式),显示内存使用率和缓冲状态</li>
  898. <li>超期日志文件自动清理(由 <code>retention_days</code> 控制),支持手动触发刷盘</li>
  899. </ul>
  900. <div class="note">两个子系统的 <code>flush_time</code> 均配置为 23:20,分别在 <code>[log]</code> 和 <code>[system]</code> 段中设置,可独立调整。</div>
  901. </div>
  902. </div>
  903. <!-- 15. Web管理 -->
  904. <div class="card" id="sec15">
  905. <div class="card-title" onclick="toggleCard(this)">15. Web 管理功能 <span class="arrow">▼</span></div>
  906. <div class="card-body">
  907. <table>
  908. <tr><th>页面</th><th>路径</th><th>权限</th><th>说明</th></tr>
  909. <tr><td>监控首页</td><td>/</td><td>所有用户</td><td>实时监控、识别记录、连接状态、ROI设置</td></tr>
  910. <tr><td>视频预览</td><td>/video</td><td>所有用户</td><td>实时视频流</td></tr>
  911. <tr><td>称重记录</td><td>/weight</td><td>所有用户</td><td>称重数据</td></tr>
  912. <tr><td>锁定管理</td><td>/locks</td><td>所有用户</td><td>交替锁定记录</td></tr>
  913. <tr><td>帮助手册</td><td>/help</td><td>超级管理员</td><td>本文档</td></tr>
  914. <tr><td>系统配置</td><td>/config</td><td>管理员+</td><td>config.ini 在线编辑</td></tr>
  915. <tr><td>系统监控</td><td>/system</td><td>超级管理员</td><td>CPU/内存/温度/磁盘</td></tr>
  916. <tr><td>frpc 管理</td><td>/frpc</td><td>超级管理员</td><td>内网穿透状态和配置</td></tr>
  917. <tr><td>SSH 密钥</td><td>/ssh-keys</td><td>超级管理员</td><td>SSH 公钥管理</td></tr>
  918. <tr><td>日志管理</td><td>/logs</td><td>超级管理员</td><td>系统日志查看</td></tr>
  919. <tr><td>登录</td><td>/login</td><td>公开</td><td>用户登录页面</td></tr>
  920. </table>
  921. <h3>Tab 分离:实时数据 + 历史查询(v41 新增)</h3>
  922. <p>首页和称重页均改为 Tab 分离结构,自动刷新与手动查询完全独立:</p>
  923. <table>
  924. <tr><th>Tab</th><th>数据来源</th><th>刷新方式</th></tr>
  925. <tr><td>📡 实时数据 / 实时称重</td><td>固定最新 20 条,无条件加载</td><td>每 30 秒自动刷新</td></tr>
  926. <tr><td>🔍 历史查询</td><td>按筛选条件查询,支持完整分页</td><td>手动触发,不自动刷新</td></tr>
  927. </table>
  928. <p>两个 Tab 使用独立的状态和函数,互不干扰。切换 Tab 时自动加载对应数据;重试操作后根据当前 Tab 智能刷新对应数据源。</p>
  929. <h3>分类查询筛选条件(v41)</h3>
  930. <p>历史查询 Tab 支持多维度组合筛选(所有条件 AND 组合):</p>
  931. <table>
  932. <tr><th>筛选条件</th><th>首页</th><th>称重页</th><th>说明</th></tr>
  933. <tr><td>车牌号</td><td>✅</td><td>✅</td><td>模糊匹配,如"沪A"</td></tr>
  934. <tr><td>联单编号</td><td>✅</td><td>✅</td><td>模糊匹配</td></tr>
  935. <tr><td>方向</td><td>✅</td><td>✅</td><td>全部 / 进站 / 出站</td></tr>
  936. <tr><td>上传状态</td><td>✅</td><td>✅</td><td>首页:成功/失败;称重页多一个"待上传"(status=0)</td></tr>
  937. <tr><td>飞书状态</td><td>✅</td><td>—</td><td>全部 / 已发送 / 失败 / 未发送</td></tr>
  938. <tr><td>日期范围</td><td>✅</td><td>✅</td><td>按抓拍时间过滤(YYYY-MM-DD)</td></tr>
  939. </table>
  940. <h3>分页控件(v41)</h3>
  941. <ul>
  942. <li><strong>每页条数</strong>:可选 20 / 50 / 100 条</li>
  943. <li><strong>页码导航</strong>:首页 / 上一页 / 页码(当前页前后各 2 页,含首末页和省略号)/ 下一页 / 末页</li>
  944. <li><strong>跳转</strong>:输入页码 + GO 按钮快速跳转</li>
  945. <li><strong>信息栏</strong>:显示"第 X/Y 页 · 共 Z 条"</li>
  946. </ul>
  947. </div>
  948. </div>
  949. <!-- 16. 数据库 -->
  950. <div class="card" id="sec16">
  951. <div class="card-title" onclick="toggleCard(this)">16. 数据库与备份 <span class="arrow">▼</span></div>
  952. <div class="card-body">
  953. <h3>数据库文件</h3>
  954. <table>
  955. <tr><th>文件</th><th>路径</th><th>说明</th></tr>
  956. <tr><td>认证数据库</td><td>data/auth.db</td><td>用户账号、密码、Session</td></tr>
  957. <tr><td>识别记录</td><td>data/upload_records.db</td><td>车牌识别上传记录</td></tr>
  958. <tr><td>系统监控</td><td>data/system_metrics.db</td><td>性能监控时序数据(原始+小时聚合+日聚合)</td></tr>
  959. </table>
  960. <div class="tip">v29 版本起,所有数据库统一存放在 <code>data/</code> 目录下。系统首次启动时自动将旧路径数据库迁移到新位置(旧文件保留为备份)。</div>
  961. <h3>upload_records 表结构</h3>
  962. <table>
  963. <tr><th>字段</th><th>说明</th></tr>
  964. <tr><td>id</td><td>自增主键</td></tr>
  965. <tr><td>create_time</td><td>记录创建时间</td></tr>
  966. <tr><td>capture_time</td><td>抓拍时间(用于时间对齐)</td></tr>
  967. <tr><td>plate_number</td><td>车牌号</td></tr>
  968. <tr><td>tb_num</td><td>联单编号</td></tr>
  969. <tr><td>station_type</td><td>方向:1=进站,2=出站</td></tr>
  970. <tr><td>lo_photo_path / hi_photo_path</td><td>照片文件名(相对 PlateJPG/),清理后为 NULL</td></tr>
  971. <tr><td>lo_upload_status / hi_upload_status</td><td>上传状态:0=待传,1=成功,2=失败</td></tr>
  972. <tr><td>feishu_status</td><td>飞书通知:1=已发送,2=失败,3=未发送</td></tr>
  973. <tr><td>retry_count</td><td>重试次数(上限 5 次)</td></tr>
  974. </table>
  975. <h3>备份建议</h3>
  976. <ul>
  977. <li>定期备份 <code>data/</code> 目录和 <code>config.ini</code></li>
  978. <li>nginx SSL 证书和配置文件备份:<code>/etc/nginx/ssl/</code>、<code>/etc/nginx/conf.d/</code></li>
  979. <li>frpc 配置修改时系统自动备份</li>
  980. <li>照片数据在 <code>PhotoMaxCapacityMB</code> 限制内自动管理,重要照片建议另行备份</li>
  981. </ul>
  982. </div>
  983. </div>
  984. <!-- 17. FAQ -->
  985. <div class="card" id="sec17">
  986. <div class="card-title" onclick="toggleCard(this)">17. 常见问题 FAQ <span class="arrow">▼</span></div>
  987. <div class="card-body">
  988. <h3>Q: HTTP 访问没有跳转到 HTTPS?</h3>
  989. <p>A: 检查以下几点:</p>
  990. <ul>
  991. <li>确认 nginx stream 模块已安装:<code>nginx -V 2>&amp;1 | grep stream</code></li>
  992. <li>确认 nginx 配置正确:<code>sudo nginx -t</code></li>
  993. <li>确认 8081 端口正在监听:<code>ss -tlnp | grep 8081</code></li>
  994. <li>确认 frpc 隧道 <code>plate_web</code> 正常运行(localPort 为 40963)</li>
  995. <li>浏览器可能缓存了旧的重定向,尝试清除缓存或无痕模式</li>
  996. <li>测试本机重定向:<code>curl -I http://localhost:40963/</code>,应返回 301</li>
  997. </ul>
  998. <h3>Q: HTTPS 访问返回错误?</h3>
  999. <p>A: v2 方案下 TLS 由 C++ 应用处理,检查:</p>
  1000. <ul>
  1001. <li>PlateRecApp 是否正在运行:<code>systemctl status PlateRecApp</code></li>
  1002. <li>PlateRecApp 是否启用了 HTTPS:<code>config.ini [ssl] enabled=1</code></li>
  1003. <li>SSL 证书文件是否存在:<code>ls -l ssl/plate_fullchain.pem ssl/plate_privkey.pem</code></li>
  1004. <li>nginx stream 是否正常监听:<code>ss -tlnp | grep 40963</code></li>
  1005. <li>nginx 错误日志:<code>sudo tail -f /var/log/nginx/error.log</code></li>
  1006. </ul>
  1007. <h3>Q: <code>unknown directive "stream"</code>?</h3>
  1008. <p>A: stream 模块未加载。执行:<code>sudo apt install -y libnginx-mod-stream</code>,确认 nginx.conf 顶部有 <code>load_module modules/ngx_stream_module.so;</code></p>
  1009. <h3>Q: frpc 重启后隧道不生效?</h3>
  1010. <p>A: 检查 frpc.toml 配置语法:</p>
  1011. <pre>frpc verify -c /path/to/frpc.toml</pre>
  1012. <p>常见错误:TOML 格式问题(缺少引号、缩进错误)。通过 Web 管理界面编辑配置时系统会自动校验。</p>
  1013. <h3>Q: 自签名证书浏览器警告怎么消除?</h3>
  1014. <p>A: 自签名证书无法消除浏览器警告(这是正常安全行为)。解决方案:</p>
  1015. <ul>
  1016. <li>方式1:使用 Let's Encrypt 免费证书(需域名解析到公网 IP)</li>
  1017. <li>方式2:在客户端将自签名 CA 证书添加到受信任的根证书列表</li>
  1018. <li>方式3:忽略警告,继续使用(加密通信正常,仅证书不受信任)</li>
  1019. </ul>
  1020. <h3>Q: 摄像头连接失败怎么办?</h3>
  1021. <p>A: 确认 RTSP 地址格式正确,在 VLC 中测试播放。尝试 <code>rtsp_transport=tcp</code>。</p>
  1022. <h3>Q: in_out_interval 填 5 是 5 秒还是 5 分钟?</h3>
  1023. <p>A: <strong>5 分钟</strong>。单位是分钟,系统内部自动 ×60 转为秒。</p>
  1024. <h3>Q: 照片被自动清理后,Web 页面看到"已清理"怎么办?</h3>
  1025. <p>A: 这是正常现象。当 <code>PlateJPG/</code> 目录总大小超过 <code>PhotoMaxCapacityMB</code>(默认 1024MB)时,系统每日凌晨 2:00 自动删除最旧的照片释放空间。被删除照片对应的数据库记录仍保留(业务数据不丢失),Web 页面显示灰色「已清理」占位。如需保留更多照片,调大该配置值。</p>
  1026. <h3>Q: 日志中出现 <code>[路径校验] 转换绝对路径失败</code> 错误?</h3>
  1027. <p>A: 此错误在旧版本(v41 修复前)出现,原因是照片被物理删除后 DB 路径字段未同步清空,前端仍请求已删除文件。v41 已修复:删除照片时通过 basename 匹配同步清空 DB 路径字段,启动时自动扫描孤儿路径,且 <code>/photo</code> 接口对缺失文件静默返回 404 不再打 ERROR 日志。升级后此错误不再出现。</p>
  1028. <h3>Q: 如何调整照片存储上限?</h3>
  1029. <p>A: 修改 <code>config.ini</code> 的 <code>[config]</code> 段中 <code>PhotoMaxCapacityMB</code> 值并重启服务。例如:</p>
  1030. <pre>[config]
  1031. PhotoMaxCapacityMB=2048 # 调整为 2GB</pre>
  1032. <h3>Q: 称重页的"待上传"状态是什么意思?</h3>
  1033. <p>A: 称重记录上传状态筛选比首页多一个「待上传」选项,对应数据库中 <code>upload_status = 0</code> 的记录,即称重数据尚未成功上传到云端 API,可通过此筛选快速定位需重试的记录。</p>
  1034. <h3>Q: 如何重置管理员密码?</h3>
  1035. <pre>cd /opt/openAI/project/003.PlateRecAPP
  1036. python3 reset_passwords.py</pre>
  1037. </div>
  1038. </div>
  1039. </div>
  1040. <script>
  1041. function toggleCard(el) {
  1042. el.classList.toggle('collapsed');
  1043. var body = el.nextElementSibling;
  1044. body.classList.toggle('hidden');
  1045. }
  1046. </script>
  1047. </body>
  1048. </html>