| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759 |
- /**
- * station_lock.cpp — 交替锁定实现
- *
- * v43.2 fix17: 2小时超时清零(锁定记录超2小时自动清除)
- *
- * fix17核心:交替锁定记录等待超过2小时(7200秒)后自动清零释放
- * fix17修改:
- * 1. cleanup_station_cache: 增加超过2小时的记录清除(内存+数据库)
- * 2. load_station_cache_from_db: 重启恢复时跳过并删除超过2小时的过期记录
- * 3. common.h: 新增STATION_LOCK_EXPIRE_SEC=7200常量
- * 4. main.cpp: 启动信息更新为fix17+"2小时超时清零"
- *
- * fix17业务规则:
- * 进站完成后→等in_out_interval(300秒)→允许出站;如无出站→一直等待(但不超过2小时)
- * 出站完成后→等in_out_interval+TIME_WINDOW(600秒)→允许进站;如无进站→一直等待(但不超过2小时)
- * 出站无进站记录→永久拦截(不受2小时限制,必须先进站才能出站)
- * 交替锁定记录等待超过2小时→自动清零释放(视为车辆已离开不再回来)
- * 系统重启后:未超时保持锁定,超时清零
- *
- * v43.2 fix15: 非对称等待时间+cleanup超时修正
- * v43.2 fix14: AlternatingMerge=1禁用TIME_WINDOW独立限流+交替锁定独占时序
- * v43.2 fix12: 修复 in_out_interval=TIME_WINDOW=5 时的交互Bug
- * v43.2 fix11: 修复交替锁定"超时放行"导致连续进站P0 Bug
- *
- * 交替锁定规则(AlternatingMerge=1, fix17版):
- * 1. 进站完成后,状态变为OUTBOUND_ALLOWED,等待 in_out_interval(300秒) 后允许出站
- * 2. 出站完成后,状态变为INBOUND_ALLOWED,等待 in_out_interval+TIME_WINDOW(600秒) 后允许进站
- * 3. 同方向操作永远被拦截(进站后不能再进站,必须先出站)
- * 4. 同方向操作永远被拦截(出站后不能再出站,必须先进站)
- * 5. ✅ fix16: 出站无进站记录永远拦截,必须先进站才能出站(无超时放行)
- * 6. ✅ fix17: 交替锁定记录等待超过2小时自动清零释放
- * 7. ✅ fix15: 非对称等待时间,出站→进站比进站→出站多等TIME_WINDOW
- * 8. ✅ fix14: AlternatingMerge=1时TIME_WINDOW独立限流禁用
- *
- * 状态机(非对称等待+2小时超时清零):
- * INBOUND_ALLOWED → 上次出站完成,等待 in_out_interval+TIME_WINDOW(600秒) 后允许进站;如无进站→一直等待(≤2小时)
- * OUTBOUND_ALLOWED → 上次进站完成,等待 in_out_interval(300秒) 后允许出站;如无出站→一直等待(≤2小时)
- * 无记录出站 → 永远拦截(必须先进站,不受2小时限制)
- * ⚠️ 同方向操作永远被拦截(必须先完成对端操作)
- * ⚠️ 锁定记录等待超过2小时 → 自动清零释放(视为车辆已离开)
- */
- #include "station_lock.h"
- #include "database.h"
- #include <iostream>
- // ✅ v40修复:出站无记录拦截追踪(用于告警,不自动放行)
- // 无进站记录出站保持严格拦截(fix16),仅追踪拦截时长用于告警通知
- static std::map<std::string, time_t> g_norecord_block_start;
- static const int NORECORD_ALERT_SEC = 600; // 无记录拦截超过10分钟触发告警
- static void _save_station_cache_to_db_unlocked(const std::string& plate, const PlateStationState& state) {
- if (!g_db) return;
-
- const char* sql = "INSERT OR REPLACE INTO station_lock_cache "
- "(plate_number, last_in_time, last_out_time, current_mode) VALUES (?, ?, ?, ?);";
- sqlite3_stmt* stmt = nullptr;
- if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
- sqlite3_bind_text(stmt, 1, plate.c_str(), -1, SQLITE_TRANSIENT);
- sqlite3_bind_int64(stmt, 2, state.last_in_time);
- sqlite3_bind_int64(stmt, 3, state.last_out_time);
- sqlite3_bind_int(stmt, 4, static_cast<int>(state.current_mode));
- sqlite3_step(stmt);
- sqlite3_finalize(stmt);
- }
- }
- // 不获取锁的数据库删除版本
- static void _delete_station_cache_from_db_unlocked(const std::string& plate) {
- if (!g_db) return;
-
- const char* sql = "DELETE FROM station_lock_cache WHERE plate_number=?;";
- sqlite3_stmt* stmt = nullptr;
- if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
- sqlite3_bind_text(stmt, 1, plate.c_str(), -1, SQLITE_TRANSIENT);
- sqlite3_step(stmt);
- sqlite3_finalize(stmt);
- }
- }
- // 从数据库加载交替锁定状态(重启恢复)
- void load_station_cache_from_db() {
- if (!g_alternating_merge_enabled) return;
- // 先在 g_db_mtx 保护下读取所有数据到临时容器
- std::vector<std::tuple<std::string, time_t, time_t, StationMode>> temp_records;
-
- {
- std::lock_guard<std::mutex> lock(g_db_mtx);
- if (!g_db) return;
-
- const char* sql = "SELECT plate_number, last_in_time, last_out_time, current_mode FROM station_lock_cache;";
- sqlite3_stmt* stmt;
-
- if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
- while (sqlite3_step(stmt) == SQLITE_ROW) {
- std::string plate = (const char*)sqlite3_column_text(stmt, 0);
- time_t last_in = sqlite3_column_int64(stmt, 1);
- time_t last_out = sqlite3_column_int64(stmt, 2);
- StationMode mode = static_cast<StationMode>(sqlite3_column_int(stmt, 3));
- temp_records.emplace_back(plate, last_in, last_out, mode);
- }
- sqlite3_finalize(stmt);
- }
- }
-
- // ✅ fix17: 在 g_station_cache_mtx 保护下处理数据,超2小时的记录不恢复并删除
- time_t now = time(NULL);
- std::vector<std::string> expired_plates;
-
- {
- std::lock_guard<std::mutex> lock(g_station_cache_mtx);
-
- for (auto& rec : temp_records) {
- const std::string& plate = std::get<0>(rec);
- time_t last_in = std::get<1>(rec);
- time_t last_out = std::get<2>(rec);
- StationMode mode = std::get<3>(rec);
-
- // 计算最后活跃时间
- time_t last_time = std::max(last_in, last_out);
- if (last_time == 0) {
- // last_in和last_out都为0的异常记录,仍恢复
- PlateStationState state;
- state.plate = plate;
- state.last_in_time = last_in;
- state.last_out_time = last_out;
- state.current_mode = mode;
- g_plate_station_cache[plate] = state;
- std::cout << "[重启恢复] " << plate << " 异常记录(无时间戳),已恢复" << std::endl;
- continue;
- }
-
- // ✅ fix17: 超过2小时的记录不恢复,标记删除
- int elapsed = static_cast<int>(difftime(now, last_time));
- if (elapsed >= STATION_LOCK_EXPIRE_SEC) {
- std::cout << "[重启清零] " << plate << " 等待已" << elapsed
- << "秒(超过" << STATION_LOCK_EXPIRE_SEC << "秒),清零释放" << std::endl;
- expired_plates.push_back(plate);
- continue;
- }
-
- // 未超时记录,恢复到内存
- PlateStationState state;
- state.plate = plate;
- state.last_in_time = last_in;
- state.last_out_time = last_out;
- state.current_mode = mode;
- g_plate_station_cache[plate] = state;
-
- // 计算当前状态下的等待剩余时间(日志报告)
- if (mode == StationMode::OUTBOUND_ALLOWED) {
- time_t ref = last_in;
- int wait_remaining = g_in_out_interval_sec - static_cast<int>(difftime(now, ref));
- if (wait_remaining > 0) {
- std::cout << "[重启恢复] " << plate << " 进站中,出站还需等待"
- << wait_remaining << "秒" << std::endl;
- } else {
- std::cout << "[重启恢复] " << plate << " 进站等待已满,可出站" << std::endl;
- }
- } else {
- time_t ref = last_out;
- int in_wait_sec = g_in_out_interval_sec + g_time_window_min * 60;
- int wait_remaining = in_wait_sec - static_cast<int>(difftime(now, ref));
- if (wait_remaining > 0) {
- std::cout << "[重启恢复] " << plate << " 出站中,进站还需等待"
- << wait_remaining << "秒(共需" << in_wait_sec << "秒)" << std::endl;
- } else {
- std::cout << "[重启恢复] " << plate << " 出站等待已满,可进站" << std::endl;
- }
- }
- }
- }
-
- // ✅ fix17: 从数据库中删除重启时发现的过期记录
- if (!expired_plates.empty()) {
- std::lock_guard<std::mutex> lock_db(g_db_mtx);
- for (const auto& plate : expired_plates) {
- if (g_db) {
- const char* sql = "DELETE FROM station_lock_cache WHERE plate_number = ?;";
- sqlite3_stmt* stmt;
- if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
- sqlite3_bind_text(stmt, 1, plate.c_str(), -1, SQLITE_TRANSIENT);
- sqlite3_step(stmt);
- sqlite3_finalize(stmt);
- }
- }
- }
- std::cout << "[重启清零] 清除 " << expired_plates.size() << " 条超过2小时的锁定记录" << std::endl;
- }
-
- std::cout << "[配置] 从数据库恢复 " << g_plate_station_cache.size()
- << " 条站口锁定记录" << std::endl;
- }
- /**
- * 原子操作:尝试进站锁定(检查+锁定一步完成,消除竞态条件)
- *
- * 返回 true 表示锁定成功,可以继续上传;返回 false 表示被拦截
- *
- * 逻辑(fix11: 严格交替,无同方向超时放行):
- * - 无记录 → 允许(首次进站),立即锁定为 OUTBOUND_ALLOWED
- * - OUTBOUND_ALLOWED(上次进站后)→ 永远拦截(必须先出站才能再进站)
- * ✅ fix11: 移除超时放行,严格交替。超时清理由cleanup_station_cache处理
- * - INBOUND_ALLOWED(上次出站后)→ 必须等待 in_out_interval 秒后才允许进站
- * - 未到时间 → 拦截(强制等待间隔)
- * - 已到时间 → 允许进站,立即锁定为 OUTBOUND_ALLOWED
- */
- bool try_lock_in_station(const std::string& plate) {
- if (!g_alternating_merge_enabled) return true;
- std::lock_guard<std::mutex> lock(g_station_cache_mtx);
- time_t now = time(NULL);
-
- auto it = g_plate_station_cache.find(plate);
-
- // 无记录 → 可以进站(首次出现),立即锁定
- if (it == g_plate_station_cache.end()) {
- PlateStationState state;
- state.plate = plate;
- state.last_in_time = now;
- state.last_out_time = 0;
- state.current_mode = StationMode::OUTBOUND_ALLOWED; // 进站完成→等待出站
- g_plate_station_cache[plate] = state;
-
- std::lock_guard<std::mutex> lock_db(g_db_mtx);
- _save_station_cache_to_db_unlocked(plate, state);
-
- // ✅ v40: 清除无记录拦截追踪(车辆已有进站记录)
- g_norecord_block_start.erase(plate);
-
- g_metrics.record_station_lock(true);
- std::cout << "[进站锁定] " << plate << " 首次进站,需等待"
- << g_in_out_interval_sec << "秒后才允许出站" << std::endl;
- return true;
- }
-
- PlateStationState& state = it->second;
-
- // OUTBOUND_ALLOWED → 上次进站完成,必须先出站才能再进站(严格交替,无超时放行)
- // ✅ fix11: 移除"超时放行",OUTBOUND_ALLOWED状态下进站必须被拦截
- // 旧逻辑:进站后5分钟未出站→超时放行→允许重新进站 → 导致连续进站Bug
- // 新逻辑:进站后必须出站,才能再进站。超时清理由cleanup_station_cache处理
- if (state.current_mode == StationMode::OUTBOUND_ALLOWED) {
- time_t ref_time = state.last_in_time;
- if (ref_time > 0) {
- int elapsed = static_cast<int>(difftime(now, ref_time));
- std::cout << "[进站拦截] " << plate << " 上次进站后仅" << elapsed
- << "秒,必须先出站才能再进站,还需等待出站" << std::endl;
- } else {
- std::cout << "[进站拦截] " << plate << " 当前为出站等待状态,必须先出站" << std::endl;
- }
- g_metrics.record_station_block(true);
- return false;
- }
-
- // INBOUND_ALLOWED → 上次出站后,需等待 in_out_interval+TIME_WINDOW 才允许进站(非对称间隔)
- // ✅ fix15: 出站→进站等待 in_out_interval+TIME_WINDOW(如5+5=10分钟600秒)
- // 进站→出站等待仅 in_out_interval(如5分钟300秒),非对称设计
- if (state.current_mode == StationMode::INBOUND_ALLOWED) {
- time_t ref_time = state.last_out_time;
- if (ref_time > 0) {
- int elapsed = static_cast<int>(difftime(now, ref_time));
- int in_wait_sec = g_in_out_interval_sec + g_time_window_min * 60; // 出站→进站: 300+300=600秒
- if (elapsed >= in_wait_sec) {
- // 等待时间已满,允许进站,立即锁定
- state.last_in_time = now;
- state.last_out_time = ref_time; // 保留出站时间
- state.current_mode = StationMode::OUTBOUND_ALLOWED;
-
- std::lock_guard<std::mutex> lock_db(g_db_mtx);
- _save_station_cache_to_db_unlocked(plate, state);
-
- g_metrics.record_station_lock(true);
- std::cout << "[进站放行] " << plate << " 出站后已等待" << elapsed
- << "秒(需" << in_wait_sec << "秒),允许进站,已锁定" << std::endl;
- return true;
- }
- std::cout << "[进站等待] " << plate << " 出站后需等待" << in_wait_sec
- << "秒才能进站,还需等待"
- << (in_wait_sec - elapsed) << "秒" << std::endl;
- g_metrics.record_station_block(true);
- return false;
- }
- // last_out_time == 0 异常情况,允许进站,锁定
- state.last_in_time = now;
- state.current_mode = StationMode::OUTBOUND_ALLOWED;
-
- std::lock_guard<std::mutex> lock_db(g_db_mtx);
- _save_station_cache_to_db_unlocked(plate, state);
-
- g_metrics.record_station_lock(true);
- return true;
- }
-
- return true;
- }
- /**
- * 原子操作:尝试出站锁定(检查+锁定一步完成,消除竞态条件)
- *
- * 返回 true 表示锁定成功,可以继续上传;返回 false 表示被拦截
- *
- * 逻辑(fix16: 严格交替,出站无进站记录永远拦截):
- * - 无记录 → 永远拦截(必须先进站才能出站,无超时放行)
- * ✅ fix16: 移除OUT_NO_RECORD_TIMEOUT_SEC超时放行,必须先进站
- * - OUTBOUND_ALLOWED(上次进站后)→ 必须等待 in_out_interval 秒后才允许出站
- * - 未到时间 → 拦截(强制等待间隔)
- * - 已到时间 → 允许出站,立即锁定为 INBOUND_ALLOWED
- * - INBOUND_ALLOWED(上次出站后)→ 永远拦截(必须先进站才能再出站)
- */
- bool try_lock_out_station(const std::string& plate) {
- if (!g_alternating_merge_enabled) return true;
- std::lock_guard<std::mutex> lock(g_station_cache_mtx);
- time_t now = time(NULL);
-
- auto it = g_plate_station_cache.find(plate);
-
- // ✅ fix16: 无记录 → 永远拦截(必须先进站才能出站,无超时放行)
- // 旧逻辑:等待OUT_NO_RECORD_TIMEOUT_SEC(600秒)后允许出站 → 破坏"必须先进站"规则
- // 新逻辑:出站无进站记录永远拦截,一直等待进站记录
- if (it == g_plate_station_cache.end()) {
- std::cout << "[出站拦截] " << plate << " 无进站记录,必须先进站才能出站" << std::endl;
- g_metrics.record_station_block(false);
- return false;
- }
-
- PlateStationState& state = it->second;
-
- // OUTBOUND_ALLOWED → 上次进站后,需等待 in_out_interval 才允许出站(强制间隔)
- if (state.current_mode == StationMode::OUTBOUND_ALLOWED) {
- time_t ref_time = state.last_in_time;
- if (ref_time > 0) {
- int elapsed = static_cast<int>(difftime(now, ref_time));
- if (elapsed >= g_in_out_interval_sec) {
- // 等待时间已满,允许出站,立即锁定
- state.last_out_time = now;
- state.last_in_time = ref_time; // 保留进站时间
- state.current_mode = StationMode::INBOUND_ALLOWED;
-
- std::lock_guard<std::mutex> lock_db(g_db_mtx);
- _save_station_cache_to_db_unlocked(plate, state);
-
- g_metrics.record_station_lock(false);
- std::cout << "[出站放行] " << plate << " 进站后已等待" << elapsed
- << "秒,允许出站,已锁定" << std::endl;
- return true;
- }
- std::cout << "[出站等待] " << plate << " 进站后需等待" << g_in_out_interval_sec
- << "秒才能出站,还需等待"
- << (g_in_out_interval_sec - elapsed) << "秒" << std::endl;
- g_metrics.record_station_block(false);
- return false;
- }
- // last_in_time == 0 异常情况,允许出站,锁定
- state.last_out_time = now;
- state.current_mode = StationMode::INBOUND_ALLOWED;
-
- std::lock_guard<std::mutex> lock_db(g_db_mtx);
- _save_station_cache_to_db_unlocked(plate, state);
-
- g_metrics.record_station_lock(false);
- return true;
- }
-
- // INBOUND_ALLOWED → 上次出站完成,必须先进站才能再出站(严格交替,无超时放行)
- // ✅ fix11: 移除"超时放行",INBOUND_ALLOWED状态下出站必须被拦截
- // 旧逻辑:出站后5分钟未进站→超时放行→允许重新出站 → 导致连续出站
- // 新逻辑:出站后必须进站,才能再出站。超时清理由cleanup_station_cache处理
- if (state.current_mode == StationMode::INBOUND_ALLOWED) {
- time_t ref_time = state.last_out_time;
- if (ref_time > 0) {
- int elapsed = static_cast<int>(difftime(now, ref_time));
- std::cout << "[出站拦截] " << plate << " 上次出站后仅" << elapsed
- << "秒,必须先进站才能再出站,还需等待进站" << std::endl;
- } else {
- std::cout << "[出站拦截] " << plate << " 当前为进站等待状态,必须先进站" << std::endl;
- }
- g_metrics.record_station_block(false);
- return false;
- }
-
- return false;
- }
- /**
- * 交替锁定预检:检查交替锁定是否允许操作(不修改任何状态)
- *
- * ✅ fix12新增:解决 in_out_interval=TIME_WINDOW=5 时的交互Bug
- *
- * 问题:当 photo_saved=true 且 TIME_WINDOW(5分钟) 到期时,主循环直接重置 photo_saved=false,
- * 但交替锁定仍会阻止同方向操作。导致照片保存→上传被拦截→photo_count递增无实际上传→重复浪费循环。
- *
- * 修复:在保存照片前先调用此函数预检,如果交替锁定会拦截,跳过照片保存和计数器递增。
- *
- * 与 try_lock_in/out_station 的区别:
- * - try_lock: 原子操作(检查+锁定),修改状态,用于上传流程
- * - peek: 只读查询(仅检查),不修改状态,用于保存照片前的预判
- *
- * 返回 true 表示交替锁定允许该操作,false 表示会被拦截
- */
- bool peek_station_lock(const std::string& plate, bool is_in) {
- if (!g_alternating_merge_enabled) return true;
- std::lock_guard<std::mutex> lock(g_station_cache_mtx);
- time_t now = time(NULL);
-
- auto it = g_plate_station_cache.find(plate);
-
- // 无记录
- if (it == g_plate_station_cache.end()) {
- if (is_in) {
- return true; // 首次进站允许
- } else {
- // ✅ fix16: 出站无进站记录严格拦截(必须先进站才能出站,不自动放行)
- // ✅ v40: 追踪拦截时长,超10分钟触发告警通知管理员手动处理
- auto block_it = g_norecord_block_start.find(plate);
- if (block_it == g_norecord_block_start.end()) {
- g_norecord_block_start[plate] = now;
- std::cout << "[出站拦截-无记录] " << plate
- << " 无进站记录,必须先进站才能出站" << std::endl;
- return false;
- }
- int blocked_elapsed = static_cast<int>(difftime(now, block_it->second));
- if (blocked_elapsed >= NORECORD_ALERT_SEC && blocked_elapsed % NORECORD_ALERT_SEC < 35) {
- // 每10分钟重复告警一次
- std::cerr << "[⚠️出站告警-无记录] " << plate
- << " 无进站记录已拦截" << blocked_elapsed << "秒"
- << ",需管理员手动处理(Web页面清除锁定或确认车辆已进站)" << std::endl;
- } else {
- std::cout << "[出站拦截-无记录] " << plate
- << " 无进站记录,已拦截" << blocked_elapsed << "秒" << std::endl;
- }
- return false;
- }
- }
-
- const PlateStationState& state = it->second;
-
- if (is_in) {
- // OUTBOUND_ALLOWED → 上次进站完成,必须先出站才能再进站(严格交替)
- if (state.current_mode == StationMode::OUTBOUND_ALLOWED) {
- std::cout << "[进站拦截] " << plate << " 必须先出站才能再进站,等待出站操作" << std::endl;
- return false;
- }
- // INBOUND_ALLOWED → 上次出站后,需等待 in_out_interval+TIME_WINDOW 才允许进站(非对称)
- // ✅ fix15: 出站→进站等待 in_out_interval+TIME_WINDOW(如5+5=10分钟600秒)
- if (state.current_mode == StationMode::INBOUND_ALLOWED) {
- if (state.last_out_time > 0) {
- int elapsed = static_cast<int>(difftime(now, state.last_out_time));
- int in_wait_sec = g_in_out_interval_sec + g_time_window_min * 60;
- if (elapsed >= in_wait_sec) {
- return true;
- }
- // ✅ fix24-v7: 输出剩余等待时间
- int remaining = in_wait_sec - elapsed;
- std::cout << "[进站等待] " << plate << " 出站后需等待" << in_wait_sec
- << "秒才能进站,已等待" << elapsed << "秒,还需等待"
- << remaining << "秒" << std::endl;
- return false;
- }
- return true; // last_out_time == 0 异常情况
- }
- } else {
- // INBOUND_ALLOWED → 上次出站完成,必须先进站才能再出站(严格交替)
- if (state.current_mode == StationMode::INBOUND_ALLOWED) {
- std::cout << "[出站拦截] " << plate << " 必须先进站才能再出站,等待进站操作" << std::endl;
- return false;
- }
- // OUTBOUND_ALLOWED → 上次进站后,需等待 in_out_interval 才允许出站
- if (state.current_mode == StationMode::OUTBOUND_ALLOWED) {
- if (state.last_in_time > 0) {
- int elapsed = static_cast<int>(difftime(now, state.last_in_time));
- if (elapsed >= g_in_out_interval_sec) {
- return true;
- }
- // ✅ fix24-v7: 输出剩余等待时间
- int remaining = g_in_out_interval_sec - elapsed;
- std::cout << "[出站等待] " << plate << " 进站后需等待" << g_in_out_interval_sec
- << "秒才能出站,已等待" << elapsed << "秒,还需等待"
- << remaining << "秒" << std::endl;
- return false;
- }
- return true; // last_in_time == 0 异常情况
- }
- }
-
- return true;
- }
- /**
- * 解锁回滚(上传失败时调用,恢复锁定状态允许重试)
- *
- * 进站失败 → 回滚:删除锁定记录,恢复到 INBOUND_ALLOWED(允许重新进站)
- * 出站失败 → 回滚:恢复到 OUTBOUND_ALLOWED(允许重新出站)
- */
- void unlock_rollback_station(const std::string& plate, bool was_in) {
- if (!g_alternating_merge_enabled) return;
- std::lock_guard<std::mutex> lock(g_station_cache_mtx);
-
- auto it = g_plate_station_cache.find(plate);
- if (it == g_plate_station_cache.end()) return;
-
- PlateStationState& state = it->second;
-
- if (was_in) {
- // 进站失败回滚:恢复到 INBOUND_ALLOWED(上次出站完成的状态),允许重新进站
- // 保留 last_out_time,清除 last_in_time
- state.last_in_time = 0;
- state.current_mode = StationMode::INBOUND_ALLOWED;
-
- std::lock_guard<std::mutex> lock_db(g_db_mtx);
- _save_station_cache_to_db_unlocked(plate, state);
-
- std::cout << "[进站回滚] " << plate << " 进站失败,回滚锁定状态,允许重新进站" << std::endl;
- } else {
- // 出站失败回滚:恢复到 OUTBOUND_ALLOWED(上次进站完成的状态),允许重新出站
- // 保留 last_in_time,清除 last_out_time
- state.last_out_time = 0;
- state.current_mode = StationMode::OUTBOUND_ALLOWED;
-
- std::lock_guard<std::mutex> lock_db(g_db_mtx);
- _save_station_cache_to_db_unlocked(plate, state);
-
- std::cout << "[出站回滚] " << plate << " 出站失败,回滚锁定状态,允许重新出站" << std::endl;
- }
- }
- /**
- * 进站锁定确认(上传成功后调用,仅打印日志,锁定已在try_lock中完成)
- */
- void lock_in_station(const std::string& plate) {
- if (!g_alternating_merge_enabled) return;
- // 锁定已在 try_lock_in_station 中完成,此处仅用于日志确认
- std::cout << "[进站确认] " << plate << " 进站上传成功,锁定已生效" << std::endl;
- }
- /**
- * 出站锁定确认(上传成功后调用,仅打印日志,锁定已在try_lock中完成)
- */
- void lock_out_station(const std::string& plate) {
- if (!g_alternating_merge_enabled) return;
- // 锁定已在 try_lock_out_station 中完成,此处仅用于日志确认
- std::cout << "[出站确认] " << plate << " 出站上传成功,锁定已生效" << std::endl;
- }
- // 交替锁定缓存清理(定时调用)
- // ✅ fix17: 超过2小时的记录清除(内存+数据库),未超时记录仅日志报告长等待
- // 交替锁定记录在对端操作完成前保持,超过2小时自动清零
- void cleanup_station_cache() {
- if (!g_alternating_merge_enabled) return;
- std::lock_guard<std::mutex> lock(g_station_cache_mtx);
- time_t now = time(NULL);
-
- // ✅ fix17: 超过2小时的记录清除(内存+数据库),未超时记录仅日志报告长等待
- std::vector<std::string> expired_plates;
-
- for (auto it = g_plate_station_cache.begin(); it != g_plate_station_cache.end(); ) {
- PlateStationState& state = it->second;
- time_t last_time = std::max(state.last_in_time, state.last_out_time);
-
- if (last_time > 0) {
- int elapsed = static_cast<int>(difftime(now, last_time));
-
- // ✅ fix17: 超过2小时(7200秒)的记录自动清零释放
- if (elapsed >= STATION_LOCK_EXPIRE_SEC) {
- std::cout << "[锁定清零] " << it->first << " 等待已" << elapsed
- << "秒(超过" << STATION_LOCK_EXPIRE_SEC << "秒),自动清零释放" << std::endl;
- expired_plates.push_back(it->first);
- it = g_plate_station_cache.erase(it);
- continue;
- }
-
- // 超过30分钟的长等待记录,周期性报告
- if (elapsed >= 1800 && elapsed % 1800 < STATUS_CLEANUP_INTERVAL_SEC) {
- if (state.current_mode == StationMode::OUTBOUND_ALLOWED) {
- std::cout << "[长等待] " << it->first << " 进站" << elapsed
- << "秒仍未出站,继续等待(2小时后清零)" << std::endl;
- } else {
- std::cout << "[长等待] " << it->first << " 出站" << elapsed
- << "秒仍未进站,继续等待(2小时后清零)" << std::endl;
- }
- }
- }
- ++it;
- }
-
- // 从数据库中删除已过期的记录
- if (!expired_plates.empty()) {
- std::lock_guard<std::mutex> lock_db(g_db_mtx);
- for (const auto& plate : expired_plates) {
- if (g_db) {
- const char* sql = "DELETE FROM station_lock_cache WHERE plate_number = ?;";
- sqlite3_stmt* stmt;
- if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
- sqlite3_bind_text(stmt, 1, plate.c_str(), -1, SQLITE_TRANSIENT);
- sqlite3_step(stmt);
- sqlite3_finalize(stmt);
- }
- }
- }
- std::cout << "[锁定清零] 清除 " << expired_plates.size() << " 条超过2小时的锁定记录" << std::endl;
- }
-
- // ✅ v40: 同步清理无记录拦截追踪中已无意义的条目(超30分钟)
- for (auto it = g_norecord_block_start.begin(); it != g_norecord_block_start.end(); ) {
- int elapsed = static_cast<int>(difftime(now, it->second));
- if (elapsed >= 1800) {
- it = g_norecord_block_start.erase(it);
- } else {
- ++it;
- }
- }
- }
- // ==================== 交替锁定核心函数结束 ====================
- // ✅ fix24: 手动清除指定车牌的锁定记录
- bool clear_station_lock(const std::string& plate) {
- if (!g_alternating_merge_enabled) return false;
- std::lock_guard<std::mutex> lock(g_station_cache_mtx);
-
- auto it = g_plate_station_cache.find(plate);
- if (it == g_plate_station_cache.end()) {
- std::cout << "[锁定清除] " << plate << " 无锁定记录" << std::endl;
- return false;
- }
-
- const PlateStationState& state = it->second;
- std::string mode_str = (state.current_mode == StationMode::OUTBOUND_ALLOWED) ? "等待出站" : "等待进站";
- std::cout << "[锁定清除] " << plate << " 原状态:" << mode_str
- << " last_in=" << state.last_in_time << " last_out=" << state.last_out_time
- << " → 已清除" << std::endl;
-
- g_plate_station_cache.erase(it);
-
- // ✅ v40: 同步清除无记录拦截追踪
- g_norecord_block_start.erase(plate);
-
- // 同步删除数据库记录
- std::lock_guard<std::mutex> lock_db(g_db_mtx);
- if (g_db) {
- const char* sql = "DELETE FROM station_lock_cache WHERE plate_number = ?;";
- sqlite3_stmt* stmt;
- if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
- sqlite3_bind_text(stmt, 1, plate.c_str(), -1, SQLITE_TRANSIENT);
- sqlite3_step(stmt);
- sqlite3_finalize(stmt);
- }
- }
-
- return true;
- }
- // ✅ fix24-v7: 获取剩余等待时间
- int get_remaining_wait_time(const std::string& plate, bool is_in) {
- if (!g_alternating_merge_enabled) return 0;
- std::lock_guard<std::mutex> lock(g_station_cache_mtx);
-
- auto it = g_plate_station_cache.find(plate);
- if (it == g_plate_station_cache.end()) {
- // 无记录:进站允许(返回0),出站永久拦截(返回-1)
- return is_in ? 0 : -1;
- }
-
- const PlateStationState& state = it->second;
- time_t now = time(NULL);
-
- if (is_in) {
- if (state.current_mode == StationMode::OUTBOUND_ALLOWED) {
- return -1; // 严格拦截:必须先出站
- }
- if (state.current_mode == StationMode::INBOUND_ALLOWED && state.last_out_time > 0) {
- int elapsed = static_cast<int>(difftime(now, state.last_out_time));
- int in_wait_sec = g_in_out_interval_sec + g_time_window_min * 60;
- int remaining = in_wait_sec - elapsed;
- return remaining > 0 ? remaining : 0;
- }
- return 0;
- } else {
- if (state.current_mode == StationMode::INBOUND_ALLOWED) {
- return -1; // 严格拦截:必须先进站
- }
- if (state.current_mode == StationMode::OUTBOUND_ALLOWED && state.last_in_time > 0) {
- int elapsed = static_cast<int>(difftime(now, state.last_in_time));
- int remaining = g_in_out_interval_sec - elapsed;
- return remaining > 0 ? remaining : 0;
- }
- return 0;
- }
- }
- // ==================== 旧函数兼容实现(已弃用,新代码应使用 try_lock 系列) ====================
- bool can_create_in_bill(const std::string& plate) {
- // 旧接口:仅检查不锁定,存在竞态条件
- // ⚠️ 此函数不再推荐使用,请使用 try_lock_in_station
- // ✅ fix11: 与try_lock_in_station保持一致,OUTBOUND_ALLOWED时永远返回false
- if (!g_alternating_merge_enabled) return true;
- std::lock_guard<std::mutex> lock(g_station_cache_mtx);
- time_t now = time(NULL);
-
- auto it = g_plate_station_cache.find(plate);
- if (it == g_plate_station_cache.end()) return true;
-
- PlateStationState& state = it->second;
-
- // OUTBOUND_ALLOWED → 必须先出站才能再进站(严格交替)
- if (state.current_mode == StationMode::OUTBOUND_ALLOWED) {
- return false;
- }
-
- // INBOUND_ALLOWED → 需等待 in_out_interval+TIME_WINDOW 才允许进站(非对称)
- // ✅ fix15: 出站→进站等待 in_out_interval+TIME_WINDOW(如5+5=10分钟600秒)
- if (state.current_mode == StationMode::INBOUND_ALLOWED) {
- time_t ref_time = state.last_out_time;
- if (ref_time > 0) {
- int elapsed = static_cast<int>(difftime(now, ref_time));
- if (elapsed >= g_in_out_interval_sec + g_time_window_min * 60) return true;
- }
- return false;
- }
-
- return true;
- }
- bool can_create_out_bill(const std::string& plate) {
- // ⚠️ 此函数不再推荐使用,请使用 try_lock_out_station
- // ✅ fix11: 与try_lock_out_station保持一致,INBOUND_ALLOWED时永远返回false
- if (!g_alternating_merge_enabled) return true;
- std::lock_guard<std::mutex> lock(g_station_cache_mtx);
- time_t now = time(NULL);
-
- auto it = g_plate_station_cache.find(plate);
- if (it == g_plate_station_cache.end()) {
- // ✅ fix16: 出站无进站记录永远返回false(必须先进站才能出站)
- return false;
- }
-
- PlateStationState& state = it->second;
-
- // OUTBOUND_ALLOWED → 需等待 in_out_interval 才允许出站
- if (state.current_mode == StationMode::OUTBOUND_ALLOWED) {
- time_t ref_time = state.last_in_time;
- if (ref_time > 0) {
- int elapsed = static_cast<int>(difftime(now, ref_time));
- if (elapsed >= g_in_out_interval_sec) return true;
- }
- return false;
- }
-
- // INBOUND_ALLOWED → 必须先进站才能再出站(严格交替)
- if (state.current_mode == StationMode::INBOUND_ALLOWED) {
- return false;
- }
-
- return true;
- }
|