fixed: 告警手动清除功能的bug修复 - 当删除告警配置后,迅速点击手动清除,会导致告警显示卡死
continuous-integration/drone/tag Build is passing Details

This commit is contained in:
GuanYuankai 2025-11-25 16:57:18 +08:00
parent 4211482650
commit f44c8e1ae9
1 changed files with 70 additions and 43 deletions

View File

@ -1,4 +1,4 @@
// alarm/alarm_service.cpp
#include "alarm_service.h"
#include <chrono>
@ -31,7 +31,7 @@ void AlarmService::stop() {
std::lock_guard<std::mutex> lock(m_tts_queue_mutex);
m_tts_running = false;
}
m_tts_cv.notify_one(); // 唤醒工作线程使其退出
m_tts_cv.notify_one();
}
bool AlarmService::load_rules(const std::string &config_path) {
@ -57,9 +57,7 @@ bool AlarmService::load_rules(const std::string &config_path) {
bool AlarmService::reload_rules() {
if (m_rules_config_path.empty()) {
spdlog::error(
"Cannot reload rules: config path not set (load_rules was never "
"called?).");
spdlog::error("Cannot reload rules: config path not set.");
return false;
}
@ -70,15 +68,51 @@ bool AlarmService::reload_rules() {
std::map<std::string, AlarmState> new_states;
if (!parse_rules_from_file(m_rules_config_path, new_rules, new_states)) {
spdlog::error(
"Failed to parse new rules file, aborting reload. Service continues "
"with old rules.");
spdlog::error("Failed to parse new rules file, aborting reload.");
return false;
}
try {
auto active_alarms = DataStorage::getInstance().getActiveAlarms();
for (const auto &alarm : active_alarms) {
bool rule_exists = false;
for (const auto &new_rule : new_rules) {
if (new_rule.rule_id == alarm.rule_id) {
rule_exists = true;
break;
}
}
if (!rule_exists) {
spdlog::info("Reload Cleanup: Removing zombie alarm for deleted rule "
"'{}' on device '{}'.",
alarm.rule_id, alarm.device_id);
AlarmEvent clear_event;
clear_event.rule_id = alarm.rule_id;
clear_event.device_id = alarm.device_id;
clear_event.status = AlarmEventStatus::CLEARED;
clear_event.message =
"Rule deleted from config. Auto-cleared by system.";
clear_event.level = "INFO";
clear_event.trigger_value = 0.0;
clear_event.timestamp_ms =
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
DataStorage::getInstance().storeAlarmEvent(clear_event);
}
}
} catch (const std::exception &e) {
spdlog::warn("Error during zombie alarm cleanup: {}", e.what());
}
{
std::lock_guard<std::mutex> lock(m_rules_mutex);
m_rules = std::move(new_rules);
m_alarm_states = std::move(new_states);
}
@ -117,7 +151,7 @@ void AlarmService::process_system_data(const std::string &system_data_json) {
if (!j_data.is_object())
return;
const std::string device_id = "proxy_system"; // 系统数据的固定ID
const std::string device_id = "proxy_system";
for (auto &rule : m_rules) {
if (rule.device_id == device_id) {
@ -271,15 +305,10 @@ void AlarmService::trigger_alarm_action(AlarmRule &rule, double value,
std::chrono::system_clock::now().time_since_epoch())
.count();
// 1. 语音播报 (加入队列)
// schedule_tts(message);
// 2. 发布到MQTT
std::string topic =
rule.alarm_mqtt_topic.empty() ? "proxy/alarms" : rule.alarm_mqtt_topic;
m_mqtt_client.publish(topic, AlarmEvent::toJson(event).dump(), 1, false);
// 3. 写入数据库
if (!DataStorage::getInstance().storeAlarmEvent(event)) {
spdlog::error("Failed to store ACTIVE alarm event for rule: {}",
rule.rule_id);
@ -290,9 +319,8 @@ void AlarmService::clear_alarm(AlarmRule &rule, double value,
const std::string &actual_device_id) {
spdlog::info("[ALARM CLEARED] (Rule: {})", rule.rule_id);
// 检查是否有解除消息模板
if (rule.clear_message_template.empty()) {
return; // 没有模板,安静地解除
return;
}
std::string message = format_message(rule, rule.clear_message_template, value,
@ -309,45 +337,40 @@ void AlarmService::clear_alarm(AlarmRule &rule, double value,
std::chrono::system_clock::now().time_since_epoch())
.count();
// 1. 语音播报 (加入队列)
schedule_tts(message);
// 2. 发布到MQTT
std::string topic =
rule.alarm_mqtt_topic.empty() ? "proxy/alarms" : rule.alarm_mqtt_topic;
m_mqtt_client.publish(topic, AlarmEvent::toJson(event).dump(), 1, false);
// 3. 写入数据库
if (!DataStorage::getInstance().storeAlarmEvent(event)) {
spdlog::error("Failed to store CLEARED alarm event for rule: {}",
rule.rule_id);
}
}
// --- TTS 队列和 Web API 实现 ---
void AlarmService::tts_worker() {
while (m_tts_running) {
std::string message_to_play;
{
std::unique_lock<std::mutex> lock(m_tts_queue_mutex);
// 等待队列中有消息 或 线程被通知停止
m_tts_cv.wait(lock,
[this] { return !m_tts_queue.empty() || !m_tts_running; });
if (!m_tts_running && m_tts_queue.empty()) {
break; // 退出
break;
}
if (!m_tts_queue.empty()) {
message_to_play = m_tts_queue.front();
m_tts_queue.pop();
}
} // 释放锁
}
if (!message_to_play.empty()) {
spdlog::debug("TTS worker playing: {}", message_to_play);
// 这是阻塞调用,但在独立线程中,不会影响主 io_context
m_tts_service.say_text_and_play(message_to_play);
}
}
@ -384,21 +407,25 @@ nlohmann::json AlarmService::getAlarmHistoryJson(int limit) {
bool AlarmService::manually_clear_alarm(const std::string &rule_id,
const std::string &device_id) {
auto it = m_alarm_states.find(rule_id);
if (it == m_alarm_states.end()) {
spdlog::warn(
"Cannot manually clear alarm: Rule '{}' not found in state map.",
rule_id);
return false;
{
auto it = m_alarm_states.find(rule_id);
if (it != m_alarm_states.end()) {
AlarmState &state = it->second;
state.current_state = AlarmStateType::NORMAL;
state.debounce_timer.cancel();
} else {
spdlog::warn("Manually clearing alarm for deleted/missing rule '{}'. "
"Cleaning up DB record only.",
rule_id);
}
}
AlarmState &state = it->second;
state.current_state = AlarmStateType::NORMAL;
state.debounce_timer.cancel();
spdlog::info("[ALARM MANUALLY CLEARED] (Rule: {}) for Device: {}", rule_id,
device_id);
// 2. 写入数据库日志
AlarmEvent event;
event.rule_id = rule_id;
event.device_id = device_id;
@ -409,15 +436,15 @@ bool AlarmService::manually_clear_alarm(const std::string &rule_id,
std::chrono::system_clock::now().time_since_epoch())
.count();
for (const auto &rule : m_rules) {
if (rule.rule_id == rule_id) {
event.level = AlarmRule::levelToString(rule.level);
break;
event.level = "INFO";
{
for (const auto &rule : m_rules) {
if (rule.rule_id == rule_id) {
event.level = AlarmRule::levelToString(rule.level);
break;
}
}
}
if (event.level.empty()) {
event.level = "INFO";
}
if (!DataStorage::getInstance().storeAlarmEvent(event)) {
spdlog::error("Failed to store MANUALLY CLEARED alarm event for rule: {}",