/** * 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 // ✅ v40修复:出站无记录拦截追踪(用于告警,不自动放行) // 无进站记录出站保持严格拦截(fix16),仅追踪拦截时长用于告警通知 static std::map 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(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> temp_records; { std::lock_guard 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(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 expired_plates; { std::lock_guard 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(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(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(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 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 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 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(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(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 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 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 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(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 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 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(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 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(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(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(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 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 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 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 lock(g_station_cache_mtx); time_t now = time(NULL); // ✅ fix17: 超过2小时的记录清除(内存+数据库),未超时记录仅日志报告长等待 std::vector 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(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 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(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 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 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 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(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(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 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(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 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(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; }