help20260828.html 60 KB

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