station_lock.cpp 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. /**
  2. * station_lock.cpp — 交替锁定实现
  3. *
  4. * v43.2 fix17: 2小时超时清零(锁定记录超2小时自动清除)
  5. *
  6. * fix17核心:交替锁定记录等待超过2小时(7200秒)后自动清零释放
  7. * fix17修改:
  8. * 1. cleanup_station_cache: 增加超过2小时的记录清除(内存+数据库)
  9. * 2. load_station_cache_from_db: 重启恢复时跳过并删除超过2小时的过期记录
  10. * 3. common.h: 新增STATION_LOCK_EXPIRE_SEC=7200常量
  11. * 4. main.cpp: 启动信息更新为fix17+"2小时超时清零"
  12. *
  13. * fix17业务规则:
  14. * 进站完成后→等in_out_interval(300秒)→允许出站;如无出站→一直等待(但不超过2小时)
  15. * 出站完成后→等in_out_interval+TIME_WINDOW(600秒)→允许进站;如无进站→一直等待(但不超过2小时)
  16. * 出站无进站记录→永久拦截(不受2小时限制,必须先进站才能出站)
  17. * 交替锁定记录等待超过2小时→自动清零释放(视为车辆已离开不再回来)
  18. * 系统重启后:未超时保持锁定,超时清零
  19. *
  20. * v43.2 fix15: 非对称等待时间+cleanup超时修正
  21. * v43.2 fix14: AlternatingMerge=1禁用TIME_WINDOW独立限流+交替锁定独占时序
  22. * v43.2 fix12: 修复 in_out_interval=TIME_WINDOW=5 时的交互Bug
  23. * v43.2 fix11: 修复交替锁定"超时放行"导致连续进站P0 Bug
  24. *
  25. * 交替锁定规则(AlternatingMerge=1, fix17版):
  26. * 1. 进站完成后,状态变为OUTBOUND_ALLOWED,等待 in_out_interval(300秒) 后允许出站
  27. * 2. 出站完成后,状态变为INBOUND_ALLOWED,等待 in_out_interval+TIME_WINDOW(600秒) 后允许进站
  28. * 3. 同方向操作永远被拦截(进站后不能再进站,必须先出站)
  29. * 4. 同方向操作永远被拦截(出站后不能再出站,必须先进站)
  30. * 5. ✅ fix16: 出站无进站记录永远拦截,必须先进站才能出站(无超时放行)
  31. * 6. ✅ fix17: 交替锁定记录等待超过2小时自动清零释放
  32. * 7. ✅ fix15: 非对称等待时间,出站→进站比进站→出站多等TIME_WINDOW
  33. * 8. ✅ fix14: AlternatingMerge=1时TIME_WINDOW独立限流禁用
  34. *
  35. * 状态机(非对称等待+2小时超时清零):
  36. * INBOUND_ALLOWED → 上次出站完成,等待 in_out_interval+TIME_WINDOW(600秒) 后允许进站;如无进站→一直等待(≤2小时)
  37. * OUTBOUND_ALLOWED → 上次进站完成,等待 in_out_interval(300秒) 后允许出站;如无出站→一直等待(≤2小时)
  38. * 无记录出站 → 永远拦截(必须先进站,不受2小时限制)
  39. * ⚠️ 同方向操作永远被拦截(必须先完成对端操作)
  40. * ⚠️ 锁定记录等待超过2小时 → 自动清零释放(视为车辆已离开)
  41. */
  42. #include "station_lock.h"
  43. #include "database.h"
  44. #include <iostream>
  45. // ✅ v40修复:出站无记录拦截追踪(用于告警,不自动放行)
  46. // 无进站记录出站保持严格拦截(fix16),仅追踪拦截时长用于告警通知
  47. static std::map<std::string, time_t> g_norecord_block_start;
  48. static const int NORECORD_ALERT_SEC = 600; // 无记录拦截超过10分钟触发告警
  49. static void _save_station_cache_to_db_unlocked(const std::string& plate, const PlateStationState& state) {
  50. if (!g_db) return;
  51. const char* sql = "INSERT OR REPLACE INTO station_lock_cache "
  52. "(plate_number, last_in_time, last_out_time, current_mode) VALUES (?, ?, ?, ?);";
  53. sqlite3_stmt* stmt = nullptr;
  54. if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
  55. sqlite3_bind_text(stmt, 1, plate.c_str(), -1, SQLITE_TRANSIENT);
  56. sqlite3_bind_int64(stmt, 2, state.last_in_time);
  57. sqlite3_bind_int64(stmt, 3, state.last_out_time);
  58. sqlite3_bind_int(stmt, 4, static_cast<int>(state.current_mode));
  59. sqlite3_step(stmt);
  60. sqlite3_finalize(stmt);
  61. }
  62. }
  63. // 不获取锁的数据库删除版本
  64. static void _delete_station_cache_from_db_unlocked(const std::string& plate) {
  65. if (!g_db) return;
  66. const char* sql = "DELETE FROM station_lock_cache WHERE plate_number=?;";
  67. sqlite3_stmt* stmt = nullptr;
  68. if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
  69. sqlite3_bind_text(stmt, 1, plate.c_str(), -1, SQLITE_TRANSIENT);
  70. sqlite3_step(stmt);
  71. sqlite3_finalize(stmt);
  72. }
  73. }
  74. // 从数据库加载交替锁定状态(重启恢复)
  75. void load_station_cache_from_db() {
  76. if (!g_alternating_merge_enabled) return;
  77. // 先在 g_db_mtx 保护下读取所有数据到临时容器
  78. std::vector<std::tuple<std::string, time_t, time_t, StationMode>> temp_records;
  79. {
  80. std::lock_guard<std::mutex> lock(g_db_mtx);
  81. if (!g_db) return;
  82. const char* sql = "SELECT plate_number, last_in_time, last_out_time, current_mode FROM station_lock_cache;";
  83. sqlite3_stmt* stmt;
  84. if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
  85. while (sqlite3_step(stmt) == SQLITE_ROW) {
  86. std::string plate = (const char*)sqlite3_column_text(stmt, 0);
  87. time_t last_in = sqlite3_column_int64(stmt, 1);
  88. time_t last_out = sqlite3_column_int64(stmt, 2);
  89. StationMode mode = static_cast<StationMode>(sqlite3_column_int(stmt, 3));
  90. temp_records.emplace_back(plate, last_in, last_out, mode);
  91. }
  92. sqlite3_finalize(stmt);
  93. }
  94. }
  95. // ✅ fix17: 在 g_station_cache_mtx 保护下处理数据,超2小时的记录不恢复并删除
  96. time_t now = time(NULL);
  97. std::vector<std::string> expired_plates;
  98. {
  99. std::lock_guard<std::mutex> lock(g_station_cache_mtx);
  100. for (auto& rec : temp_records) {
  101. const std::string& plate = std::get<0>(rec);
  102. time_t last_in = std::get<1>(rec);
  103. time_t last_out = std::get<2>(rec);
  104. StationMode mode = std::get<3>(rec);
  105. // 计算最后活跃时间
  106. time_t last_time = std::max(last_in, last_out);
  107. if (last_time == 0) {
  108. // last_in和last_out都为0的异常记录,仍恢复
  109. PlateStationState state;
  110. state.plate = plate;
  111. state.last_in_time = last_in;
  112. state.last_out_time = last_out;
  113. state.current_mode = mode;
  114. g_plate_station_cache[plate] = state;
  115. std::cout << "[重启恢复] " << plate << " 异常记录(无时间戳),已恢复" << std::endl;
  116. continue;
  117. }
  118. // ✅ fix17: 超过2小时的记录不恢复,标记删除
  119. int elapsed = static_cast<int>(difftime(now, last_time));
  120. if (elapsed >= STATION_LOCK_EXPIRE_SEC) {
  121. std::cout << "[重启清零] " << plate << " 等待已" << elapsed
  122. << "秒(超过" << STATION_LOCK_EXPIRE_SEC << "秒),清零释放" << std::endl;
  123. expired_plates.push_back(plate);
  124. continue;
  125. }
  126. // 未超时记录,恢复到内存
  127. PlateStationState state;
  128. state.plate = plate;
  129. state.last_in_time = last_in;
  130. state.last_out_time = last_out;
  131. state.current_mode = mode;
  132. g_plate_station_cache[plate] = state;
  133. // 计算当前状态下的等待剩余时间(日志报告)
  134. if (mode == StationMode::OUTBOUND_ALLOWED) {
  135. time_t ref = last_in;
  136. int wait_remaining = g_in_out_interval_sec - static_cast<int>(difftime(now, ref));
  137. if (wait_remaining > 0) {
  138. std::cout << "[重启恢复] " << plate << " 进站中,出站还需等待"
  139. << wait_remaining << "秒" << std::endl;
  140. } else {
  141. std::cout << "[重启恢复] " << plate << " 进站等待已满,可出站" << std::endl;
  142. }
  143. } else {
  144. time_t ref = last_out;
  145. int in_wait_sec = g_in_out_interval_sec + g_time_window_min * 60;
  146. int wait_remaining = in_wait_sec - static_cast<int>(difftime(now, ref));
  147. if (wait_remaining > 0) {
  148. std::cout << "[重启恢复] " << plate << " 出站中,进站还需等待"
  149. << wait_remaining << "秒(共需" << in_wait_sec << "秒)" << std::endl;
  150. } else {
  151. std::cout << "[重启恢复] " << plate << " 出站等待已满,可进站" << std::endl;
  152. }
  153. }
  154. }
  155. }
  156. // ✅ fix17: 从数据库中删除重启时发现的过期记录
  157. if (!expired_plates.empty()) {
  158. std::lock_guard<std::mutex> lock_db(g_db_mtx);
  159. for (const auto& plate : expired_plates) {
  160. if (g_db) {
  161. const char* sql = "DELETE FROM station_lock_cache WHERE plate_number = ?;";
  162. sqlite3_stmt* stmt;
  163. if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
  164. sqlite3_bind_text(stmt, 1, plate.c_str(), -1, SQLITE_TRANSIENT);
  165. sqlite3_step(stmt);
  166. sqlite3_finalize(stmt);
  167. }
  168. }
  169. }
  170. std::cout << "[重启清零] 清除 " << expired_plates.size() << " 条超过2小时的锁定记录" << std::endl;
  171. }
  172. std::cout << "[配置] 从数据库恢复 " << g_plate_station_cache.size()
  173. << " 条站口锁定记录" << std::endl;
  174. }
  175. /**
  176. * 原子操作:尝试进站锁定(检查+锁定一步完成,消除竞态条件)
  177. *
  178. * 返回 true 表示锁定成功,可以继续上传;返回 false 表示被拦截
  179. *
  180. * 逻辑(fix11: 严格交替,无同方向超时放行):
  181. * - 无记录 → 允许(首次进站),立即锁定为 OUTBOUND_ALLOWED
  182. * - OUTBOUND_ALLOWED(上次进站后)→ 永远拦截(必须先出站才能再进站)
  183. * ✅ fix11: 移除超时放行,严格交替。超时清理由cleanup_station_cache处理
  184. * - INBOUND_ALLOWED(上次出站后)→ 必须等待 in_out_interval 秒后才允许进站
  185. * - 未到时间 → 拦截(强制等待间隔)
  186. * - 已到时间 → 允许进站,立即锁定为 OUTBOUND_ALLOWED
  187. */
  188. bool try_lock_in_station(const std::string& plate) {
  189. if (!g_alternating_merge_enabled) return true;
  190. std::lock_guard<std::mutex> lock(g_station_cache_mtx);
  191. time_t now = time(NULL);
  192. auto it = g_plate_station_cache.find(plate);
  193. // 无记录 → 可以进站(首次出现),立即锁定
  194. if (it == g_plate_station_cache.end()) {
  195. PlateStationState state;
  196. state.plate = plate;
  197. state.last_in_time = now;
  198. state.last_out_time = 0;
  199. state.current_mode = StationMode::OUTBOUND_ALLOWED; // 进站完成→等待出站
  200. g_plate_station_cache[plate] = state;
  201. std::lock_guard<std::mutex> lock_db(g_db_mtx);
  202. _save_station_cache_to_db_unlocked(plate, state);
  203. // ✅ v40: 清除无记录拦截追踪(车辆已有进站记录)
  204. g_norecord_block_start.erase(plate);
  205. g_metrics.record_station_lock(true);
  206. std::cout << "[进站锁定] " << plate << " 首次进站,需等待"
  207. << g_in_out_interval_sec << "秒后才允许出站" << std::endl;
  208. return true;
  209. }
  210. PlateStationState& state = it->second;
  211. // OUTBOUND_ALLOWED → 上次进站完成,必须先出站才能再进站(严格交替,无超时放行)
  212. // ✅ fix11: 移除"超时放行",OUTBOUND_ALLOWED状态下进站必须被拦截
  213. // 旧逻辑:进站后5分钟未出站→超时放行→允许重新进站 → 导致连续进站Bug
  214. // 新逻辑:进站后必须出站,才能再进站。超时清理由cleanup_station_cache处理
  215. if (state.current_mode == StationMode::OUTBOUND_ALLOWED) {
  216. time_t ref_time = state.last_in_time;
  217. if (ref_time > 0) {
  218. int elapsed = static_cast<int>(difftime(now, ref_time));
  219. std::cout << "[进站拦截] " << plate << " 上次进站后仅" << elapsed
  220. << "秒,必须先出站才能再进站,还需等待出站" << std::endl;
  221. } else {
  222. std::cout << "[进站拦截] " << plate << " 当前为出站等待状态,必须先出站" << std::endl;
  223. }
  224. g_metrics.record_station_block(true);
  225. return false;
  226. }
  227. // INBOUND_ALLOWED → 上次出站后,需等待 in_out_interval+TIME_WINDOW 才允许进站(非对称间隔)
  228. // ✅ fix15: 出站→进站等待 in_out_interval+TIME_WINDOW(如5+5=10分钟600秒)
  229. // 进站→出站等待仅 in_out_interval(如5分钟300秒),非对称设计
  230. if (state.current_mode == StationMode::INBOUND_ALLOWED) {
  231. time_t ref_time = state.last_out_time;
  232. if (ref_time > 0) {
  233. int elapsed = static_cast<int>(difftime(now, ref_time));
  234. int in_wait_sec = g_in_out_interval_sec + g_time_window_min * 60; // 出站→进站: 300+300=600秒
  235. if (elapsed >= in_wait_sec) {
  236. // 等待时间已满,允许进站,立即锁定
  237. state.last_in_time = now;
  238. state.last_out_time = ref_time; // 保留出站时间
  239. state.current_mode = StationMode::OUTBOUND_ALLOWED;
  240. std::lock_guard<std::mutex> lock_db(g_db_mtx);
  241. _save_station_cache_to_db_unlocked(plate, state);
  242. g_metrics.record_station_lock(true);
  243. std::cout << "[进站放行] " << plate << " 出站后已等待" << elapsed
  244. << "秒(需" << in_wait_sec << "秒),允许进站,已锁定" << std::endl;
  245. return true;
  246. }
  247. std::cout << "[进站等待] " << plate << " 出站后需等待" << in_wait_sec
  248. << "秒才能进站,还需等待"
  249. << (in_wait_sec - elapsed) << "秒" << std::endl;
  250. g_metrics.record_station_block(true);
  251. return false;
  252. }
  253. // last_out_time == 0 异常情况,允许进站,锁定
  254. state.last_in_time = now;
  255. state.current_mode = StationMode::OUTBOUND_ALLOWED;
  256. std::lock_guard<std::mutex> lock_db(g_db_mtx);
  257. _save_station_cache_to_db_unlocked(plate, state);
  258. g_metrics.record_station_lock(true);
  259. return true;
  260. }
  261. return true;
  262. }
  263. /**
  264. * 原子操作:尝试出站锁定(检查+锁定一步完成,消除竞态条件)
  265. *
  266. * 返回 true 表示锁定成功,可以继续上传;返回 false 表示被拦截
  267. *
  268. * 逻辑(fix16: 严格交替,出站无进站记录永远拦截):
  269. * - 无记录 → 永远拦截(必须先进站才能出站,无超时放行)
  270. * ✅ fix16: 移除OUT_NO_RECORD_TIMEOUT_SEC超时放行,必须先进站
  271. * - OUTBOUND_ALLOWED(上次进站后)→ 必须等待 in_out_interval 秒后才允许出站
  272. * - 未到时间 → 拦截(强制等待间隔)
  273. * - 已到时间 → 允许出站,立即锁定为 INBOUND_ALLOWED
  274. * - INBOUND_ALLOWED(上次出站后)→ 永远拦截(必须先进站才能再出站)
  275. */
  276. bool try_lock_out_station(const std::string& plate) {
  277. if (!g_alternating_merge_enabled) return true;
  278. std::lock_guard<std::mutex> lock(g_station_cache_mtx);
  279. time_t now = time(NULL);
  280. auto it = g_plate_station_cache.find(plate);
  281. // ✅ fix16: 无记录 → 永远拦截(必须先进站才能出站,无超时放行)
  282. // 旧逻辑:等待OUT_NO_RECORD_TIMEOUT_SEC(600秒)后允许出站 → 破坏"必须先进站"规则
  283. // 新逻辑:出站无进站记录永远拦截,一直等待进站记录
  284. if (it == g_plate_station_cache.end()) {
  285. std::cout << "[出站拦截] " << plate << " 无进站记录,必须先进站才能出站" << std::endl;
  286. g_metrics.record_station_block(false);
  287. return false;
  288. }
  289. PlateStationState& state = it->second;
  290. // OUTBOUND_ALLOWED → 上次进站后,需等待 in_out_interval 才允许出站(强制间隔)
  291. if (state.current_mode == StationMode::OUTBOUND_ALLOWED) {
  292. time_t ref_time = state.last_in_time;
  293. if (ref_time > 0) {
  294. int elapsed = static_cast<int>(difftime(now, ref_time));
  295. if (elapsed >= g_in_out_interval_sec) {
  296. // 等待时间已满,允许出站,立即锁定
  297. state.last_out_time = now;
  298. state.last_in_time = ref_time; // 保留进站时间
  299. state.current_mode = StationMode::INBOUND_ALLOWED;
  300. std::lock_guard<std::mutex> lock_db(g_db_mtx);
  301. _save_station_cache_to_db_unlocked(plate, state);
  302. g_metrics.record_station_lock(false);
  303. std::cout << "[出站放行] " << plate << " 进站后已等待" << elapsed
  304. << "秒,允许出站,已锁定" << std::endl;
  305. return true;
  306. }
  307. std::cout << "[出站等待] " << plate << " 进站后需等待" << g_in_out_interval_sec
  308. << "秒才能出站,还需等待"
  309. << (g_in_out_interval_sec - elapsed) << "秒" << std::endl;
  310. g_metrics.record_station_block(false);
  311. return false;
  312. }
  313. // last_in_time == 0 异常情况,允许出站,锁定
  314. state.last_out_time = now;
  315. state.current_mode = StationMode::INBOUND_ALLOWED;
  316. std::lock_guard<std::mutex> lock_db(g_db_mtx);
  317. _save_station_cache_to_db_unlocked(plate, state);
  318. g_metrics.record_station_lock(false);
  319. return true;
  320. }
  321. // INBOUND_ALLOWED → 上次出站完成,必须先进站才能再出站(严格交替,无超时放行)
  322. // ✅ fix11: 移除"超时放行",INBOUND_ALLOWED状态下出站必须被拦截
  323. // 旧逻辑:出站后5分钟未进站→超时放行→允许重新出站 → 导致连续出站
  324. // 新逻辑:出站后必须进站,才能再出站。超时清理由cleanup_station_cache处理
  325. if (state.current_mode == StationMode::INBOUND_ALLOWED) {
  326. time_t ref_time = state.last_out_time;
  327. if (ref_time > 0) {
  328. int elapsed = static_cast<int>(difftime(now, ref_time));
  329. std::cout << "[出站拦截] " << plate << " 上次出站后仅" << elapsed
  330. << "秒,必须先进站才能再出站,还需等待进站" << std::endl;
  331. } else {
  332. std::cout << "[出站拦截] " << plate << " 当前为进站等待状态,必须先进站" << std::endl;
  333. }
  334. g_metrics.record_station_block(false);
  335. return false;
  336. }
  337. return false;
  338. }
  339. /**
  340. * 交替锁定预检:检查交替锁定是否允许操作(不修改任何状态)
  341. *
  342. * ✅ fix12新增:解决 in_out_interval=TIME_WINDOW=5 时的交互Bug
  343. *
  344. * 问题:当 photo_saved=true 且 TIME_WINDOW(5分钟) 到期时,主循环直接重置 photo_saved=false,
  345. * 但交替锁定仍会阻止同方向操作。导致照片保存→上传被拦截→photo_count递增无实际上传→重复浪费循环。
  346. *
  347. * 修复:在保存照片前先调用此函数预检,如果交替锁定会拦截,跳过照片保存和计数器递增。
  348. *
  349. * 与 try_lock_in/out_station 的区别:
  350. * - try_lock: 原子操作(检查+锁定),修改状态,用于上传流程
  351. * - peek: 只读查询(仅检查),不修改状态,用于保存照片前的预判
  352. *
  353. * 返回 true 表示交替锁定允许该操作,false 表示会被拦截
  354. */
  355. bool peek_station_lock(const std::string& plate, bool is_in) {
  356. if (!g_alternating_merge_enabled) return true;
  357. std::lock_guard<std::mutex> lock(g_station_cache_mtx);
  358. time_t now = time(NULL);
  359. auto it = g_plate_station_cache.find(plate);
  360. // 无记录
  361. if (it == g_plate_station_cache.end()) {
  362. if (is_in) {
  363. return true; // 首次进站允许
  364. } else {
  365. // ✅ fix16: 出站无进站记录严格拦截(必须先进站才能出站,不自动放行)
  366. // ✅ v40: 追踪拦截时长,超10分钟触发告警通知管理员手动处理
  367. auto block_it = g_norecord_block_start.find(plate);
  368. if (block_it == g_norecord_block_start.end()) {
  369. g_norecord_block_start[plate] = now;
  370. std::cout << "[出站拦截-无记录] " << plate
  371. << " 无进站记录,必须先进站才能出站" << std::endl;
  372. return false;
  373. }
  374. int blocked_elapsed = static_cast<int>(difftime(now, block_it->second));
  375. if (blocked_elapsed >= NORECORD_ALERT_SEC && blocked_elapsed % NORECORD_ALERT_SEC < 35) {
  376. // 每10分钟重复告警一次
  377. std::cerr << "[⚠️出站告警-无记录] " << plate
  378. << " 无进站记录已拦截" << blocked_elapsed << "秒"
  379. << ",需管理员手动处理(Web页面清除锁定或确认车辆已进站)" << std::endl;
  380. } else {
  381. std::cout << "[出站拦截-无记录] " << plate
  382. << " 无进站记录,已拦截" << blocked_elapsed << "秒" << std::endl;
  383. }
  384. return false;
  385. }
  386. }
  387. const PlateStationState& state = it->second;
  388. if (is_in) {
  389. // OUTBOUND_ALLOWED → 上次进站完成,必须先出站才能再进站(严格交替)
  390. if (state.current_mode == StationMode::OUTBOUND_ALLOWED) {
  391. std::cout << "[进站拦截] " << plate << " 必须先出站才能再进站,等待出站操作" << std::endl;
  392. return false;
  393. }
  394. // INBOUND_ALLOWED → 上次出站后,需等待 in_out_interval+TIME_WINDOW 才允许进站(非对称)
  395. // ✅ fix15: 出站→进站等待 in_out_interval+TIME_WINDOW(如5+5=10分钟600秒)
  396. if (state.current_mode == StationMode::INBOUND_ALLOWED) {
  397. if (state.last_out_time > 0) {
  398. int elapsed = static_cast<int>(difftime(now, state.last_out_time));
  399. int in_wait_sec = g_in_out_interval_sec + g_time_window_min * 60;
  400. if (elapsed >= in_wait_sec) {
  401. return true;
  402. }
  403. // ✅ fix24-v7: 输出剩余等待时间
  404. int remaining = in_wait_sec - elapsed;
  405. std::cout << "[进站等待] " << plate << " 出站后需等待" << in_wait_sec
  406. << "秒才能进站,已等待" << elapsed << "秒,还需等待"
  407. << remaining << "秒" << std::endl;
  408. return false;
  409. }
  410. return true; // last_out_time == 0 异常情况
  411. }
  412. } else {
  413. // INBOUND_ALLOWED → 上次出站完成,必须先进站才能再出站(严格交替)
  414. if (state.current_mode == StationMode::INBOUND_ALLOWED) {
  415. std::cout << "[出站拦截] " << plate << " 必须先进站才能再出站,等待进站操作" << std::endl;
  416. return false;
  417. }
  418. // OUTBOUND_ALLOWED → 上次进站后,需等待 in_out_interval 才允许出站
  419. if (state.current_mode == StationMode::OUTBOUND_ALLOWED) {
  420. if (state.last_in_time > 0) {
  421. int elapsed = static_cast<int>(difftime(now, state.last_in_time));
  422. if (elapsed >= g_in_out_interval_sec) {
  423. return true;
  424. }
  425. // ✅ fix24-v7: 输出剩余等待时间
  426. int remaining = g_in_out_interval_sec - elapsed;
  427. std::cout << "[出站等待] " << plate << " 进站后需等待" << g_in_out_interval_sec
  428. << "秒才能出站,已等待" << elapsed << "秒,还需等待"
  429. << remaining << "秒" << std::endl;
  430. return false;
  431. }
  432. return true; // last_in_time == 0 异常情况
  433. }
  434. }
  435. return true;
  436. }
  437. /**
  438. * 解锁回滚(上传失败时调用,恢复锁定状态允许重试)
  439. *
  440. * 进站失败 → 回滚:删除锁定记录,恢复到 INBOUND_ALLOWED(允许重新进站)
  441. * 出站失败 → 回滚:恢复到 OUTBOUND_ALLOWED(允许重新出站)
  442. */
  443. void unlock_rollback_station(const std::string& plate, bool was_in) {
  444. if (!g_alternating_merge_enabled) return;
  445. std::lock_guard<std::mutex> lock(g_station_cache_mtx);
  446. auto it = g_plate_station_cache.find(plate);
  447. if (it == g_plate_station_cache.end()) return;
  448. PlateStationState& state = it->second;
  449. if (was_in) {
  450. // 进站失败回滚:恢复到 INBOUND_ALLOWED(上次出站完成的状态),允许重新进站
  451. // 保留 last_out_time,清除 last_in_time
  452. state.last_in_time = 0;
  453. state.current_mode = StationMode::INBOUND_ALLOWED;
  454. std::lock_guard<std::mutex> lock_db(g_db_mtx);
  455. _save_station_cache_to_db_unlocked(plate, state);
  456. std::cout << "[进站回滚] " << plate << " 进站失败,回滚锁定状态,允许重新进站" << std::endl;
  457. } else {
  458. // 出站失败回滚:恢复到 OUTBOUND_ALLOWED(上次进站完成的状态),允许重新出站
  459. // 保留 last_in_time,清除 last_out_time
  460. state.last_out_time = 0;
  461. state.current_mode = StationMode::OUTBOUND_ALLOWED;
  462. std::lock_guard<std::mutex> lock_db(g_db_mtx);
  463. _save_station_cache_to_db_unlocked(plate, state);
  464. std::cout << "[出站回滚] " << plate << " 出站失败,回滚锁定状态,允许重新出站" << std::endl;
  465. }
  466. }
  467. /**
  468. * 进站锁定确认(上传成功后调用,仅打印日志,锁定已在try_lock中完成)
  469. */
  470. void lock_in_station(const std::string& plate) {
  471. if (!g_alternating_merge_enabled) return;
  472. // 锁定已在 try_lock_in_station 中完成,此处仅用于日志确认
  473. std::cout << "[进站确认] " << plate << " 进站上传成功,锁定已生效" << std::endl;
  474. }
  475. /**
  476. * 出站锁定确认(上传成功后调用,仅打印日志,锁定已在try_lock中完成)
  477. */
  478. void lock_out_station(const std::string& plate) {
  479. if (!g_alternating_merge_enabled) return;
  480. // 锁定已在 try_lock_out_station 中完成,此处仅用于日志确认
  481. std::cout << "[出站确认] " << plate << " 出站上传成功,锁定已生效" << std::endl;
  482. }
  483. // 交替锁定缓存清理(定时调用)
  484. // ✅ fix17: 超过2小时的记录清除(内存+数据库),未超时记录仅日志报告长等待
  485. // 交替锁定记录在对端操作完成前保持,超过2小时自动清零
  486. void cleanup_station_cache() {
  487. if (!g_alternating_merge_enabled) return;
  488. std::lock_guard<std::mutex> lock(g_station_cache_mtx);
  489. time_t now = time(NULL);
  490. // ✅ fix17: 超过2小时的记录清除(内存+数据库),未超时记录仅日志报告长等待
  491. std::vector<std::string> expired_plates;
  492. for (auto it = g_plate_station_cache.begin(); it != g_plate_station_cache.end(); ) {
  493. PlateStationState& state = it->second;
  494. time_t last_time = std::max(state.last_in_time, state.last_out_time);
  495. if (last_time > 0) {
  496. int elapsed = static_cast<int>(difftime(now, last_time));
  497. // ✅ fix17: 超过2小时(7200秒)的记录自动清零释放
  498. if (elapsed >= STATION_LOCK_EXPIRE_SEC) {
  499. std::cout << "[锁定清零] " << it->first << " 等待已" << elapsed
  500. << "秒(超过" << STATION_LOCK_EXPIRE_SEC << "秒),自动清零释放" << std::endl;
  501. expired_plates.push_back(it->first);
  502. it = g_plate_station_cache.erase(it);
  503. continue;
  504. }
  505. // 超过30分钟的长等待记录,周期性报告
  506. if (elapsed >= 1800 && elapsed % 1800 < STATUS_CLEANUP_INTERVAL_SEC) {
  507. if (state.current_mode == StationMode::OUTBOUND_ALLOWED) {
  508. std::cout << "[长等待] " << it->first << " 进站" << elapsed
  509. << "秒仍未出站,继续等待(2小时后清零)" << std::endl;
  510. } else {
  511. std::cout << "[长等待] " << it->first << " 出站" << elapsed
  512. << "秒仍未进站,继续等待(2小时后清零)" << std::endl;
  513. }
  514. }
  515. }
  516. ++it;
  517. }
  518. // 从数据库中删除已过期的记录
  519. if (!expired_plates.empty()) {
  520. std::lock_guard<std::mutex> lock_db(g_db_mtx);
  521. for (const auto& plate : expired_plates) {
  522. if (g_db) {
  523. const char* sql = "DELETE FROM station_lock_cache WHERE plate_number = ?;";
  524. sqlite3_stmt* stmt;
  525. if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
  526. sqlite3_bind_text(stmt, 1, plate.c_str(), -1, SQLITE_TRANSIENT);
  527. sqlite3_step(stmt);
  528. sqlite3_finalize(stmt);
  529. }
  530. }
  531. }
  532. std::cout << "[锁定清零] 清除 " << expired_plates.size() << " 条超过2小时的锁定记录" << std::endl;
  533. }
  534. // ✅ v40: 同步清理无记录拦截追踪中已无意义的条目(超30分钟)
  535. for (auto it = g_norecord_block_start.begin(); it != g_norecord_block_start.end(); ) {
  536. int elapsed = static_cast<int>(difftime(now, it->second));
  537. if (elapsed >= 1800) {
  538. it = g_norecord_block_start.erase(it);
  539. } else {
  540. ++it;
  541. }
  542. }
  543. }
  544. // ==================== 交替锁定核心函数结束 ====================
  545. // ✅ fix24: 手动清除指定车牌的锁定记录
  546. bool clear_station_lock(const std::string& plate) {
  547. if (!g_alternating_merge_enabled) return false;
  548. std::lock_guard<std::mutex> lock(g_station_cache_mtx);
  549. auto it = g_plate_station_cache.find(plate);
  550. if (it == g_plate_station_cache.end()) {
  551. std::cout << "[锁定清除] " << plate << " 无锁定记录" << std::endl;
  552. return false;
  553. }
  554. const PlateStationState& state = it->second;
  555. std::string mode_str = (state.current_mode == StationMode::OUTBOUND_ALLOWED) ? "等待出站" : "等待进站";
  556. std::cout << "[锁定清除] " << plate << " 原状态:" << mode_str
  557. << " last_in=" << state.last_in_time << " last_out=" << state.last_out_time
  558. << " → 已清除" << std::endl;
  559. g_plate_station_cache.erase(it);
  560. // ✅ v40: 同步清除无记录拦截追踪
  561. g_norecord_block_start.erase(plate);
  562. // 同步删除数据库记录
  563. std::lock_guard<std::mutex> lock_db(g_db_mtx);
  564. if (g_db) {
  565. const char* sql = "DELETE FROM station_lock_cache WHERE plate_number = ?;";
  566. sqlite3_stmt* stmt;
  567. if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
  568. sqlite3_bind_text(stmt, 1, plate.c_str(), -1, SQLITE_TRANSIENT);
  569. sqlite3_step(stmt);
  570. sqlite3_finalize(stmt);
  571. }
  572. }
  573. return true;
  574. }
  575. // ✅ fix24-v7: 获取剩余等待时间
  576. int get_remaining_wait_time(const std::string& plate, bool is_in) {
  577. if (!g_alternating_merge_enabled) return 0;
  578. std::lock_guard<std::mutex> lock(g_station_cache_mtx);
  579. auto it = g_plate_station_cache.find(plate);
  580. if (it == g_plate_station_cache.end()) {
  581. // 无记录:进站允许(返回0),出站永久拦截(返回-1)
  582. return is_in ? 0 : -1;
  583. }
  584. const PlateStationState& state = it->second;
  585. time_t now = time(NULL);
  586. if (is_in) {
  587. if (state.current_mode == StationMode::OUTBOUND_ALLOWED) {
  588. return -1; // 严格拦截:必须先出站
  589. }
  590. if (state.current_mode == StationMode::INBOUND_ALLOWED && state.last_out_time > 0) {
  591. int elapsed = static_cast<int>(difftime(now, state.last_out_time));
  592. int in_wait_sec = g_in_out_interval_sec + g_time_window_min * 60;
  593. int remaining = in_wait_sec - elapsed;
  594. return remaining > 0 ? remaining : 0;
  595. }
  596. return 0;
  597. } else {
  598. if (state.current_mode == StationMode::INBOUND_ALLOWED) {
  599. return -1; // 严格拦截:必须先进站
  600. }
  601. if (state.current_mode == StationMode::OUTBOUND_ALLOWED && state.last_in_time > 0) {
  602. int elapsed = static_cast<int>(difftime(now, state.last_in_time));
  603. int remaining = g_in_out_interval_sec - elapsed;
  604. return remaining > 0 ? remaining : 0;
  605. }
  606. return 0;
  607. }
  608. }
  609. // ==================== 旧函数兼容实现(已弃用,新代码应使用 try_lock 系列) ====================
  610. bool can_create_in_bill(const std::string& plate) {
  611. // 旧接口:仅检查不锁定,存在竞态条件
  612. // ⚠️ 此函数不再推荐使用,请使用 try_lock_in_station
  613. // ✅ fix11: 与try_lock_in_station保持一致,OUTBOUND_ALLOWED时永远返回false
  614. if (!g_alternating_merge_enabled) return true;
  615. std::lock_guard<std::mutex> lock(g_station_cache_mtx);
  616. time_t now = time(NULL);
  617. auto it = g_plate_station_cache.find(plate);
  618. if (it == g_plate_station_cache.end()) return true;
  619. PlateStationState& state = it->second;
  620. // OUTBOUND_ALLOWED → 必须先出站才能再进站(严格交替)
  621. if (state.current_mode == StationMode::OUTBOUND_ALLOWED) {
  622. return false;
  623. }
  624. // INBOUND_ALLOWED → 需等待 in_out_interval+TIME_WINDOW 才允许进站(非对称)
  625. // ✅ fix15: 出站→进站等待 in_out_interval+TIME_WINDOW(如5+5=10分钟600秒)
  626. if (state.current_mode == StationMode::INBOUND_ALLOWED) {
  627. time_t ref_time = state.last_out_time;
  628. if (ref_time > 0) {
  629. int elapsed = static_cast<int>(difftime(now, ref_time));
  630. if (elapsed >= g_in_out_interval_sec + g_time_window_min * 60) return true;
  631. }
  632. return false;
  633. }
  634. return true;
  635. }
  636. bool can_create_out_bill(const std::string& plate) {
  637. // ⚠️ 此函数不再推荐使用,请使用 try_lock_out_station
  638. // ✅ fix11: 与try_lock_out_station保持一致,INBOUND_ALLOWED时永远返回false
  639. if (!g_alternating_merge_enabled) return true;
  640. std::lock_guard<std::mutex> lock(g_station_cache_mtx);
  641. time_t now = time(NULL);
  642. auto it = g_plate_station_cache.find(plate);
  643. if (it == g_plate_station_cache.end()) {
  644. // ✅ fix16: 出站无进站记录永远返回false(必须先进站才能出站)
  645. return false;
  646. }
  647. PlateStationState& state = it->second;
  648. // OUTBOUND_ALLOWED → 需等待 in_out_interval 才允许出站
  649. if (state.current_mode == StationMode::OUTBOUND_ALLOWED) {
  650. time_t ref_time = state.last_in_time;
  651. if (ref_time > 0) {
  652. int elapsed = static_cast<int>(difftime(now, ref_time));
  653. if (elapsed >= g_in_out_interval_sec) return true;
  654. }
  655. return false;
  656. }
  657. // INBOUND_ALLOWED → 必须先进站才能再出站(严格交替)
  658. if (state.current_mode == StationMode::INBOUND_ALLOWED) {
  659. return false;
  660. }
  661. return true;
  662. }