Files
java-template/docs/alert_rules_comprehensive_implementation_plan.md
T
BrunoGaoandClaude e402548e45 fix: 修复customer_id字段缺失问题,完成数据库schema同步
数据库修复:
- t_user_health_data_daily: 添加customer_id字段(租户ID)
- t_user_health_data_weekly: 添加customer_id字段(租户ID)

ljwx-boot同步更新:
- TUserHealthDataDailyMapper.xml: 添加customer_id字段映射
- TUserHealthDataWeeklyMapper.xml: 添加customer_id字段映射

其他改进:
- 告警规则缓存优化
- Redis缓存性能提升
- 健康数据处理性能优化
- 添加完整的文档和工具脚本

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2025-09-10 13:49:59 +08:00

2609 lines
81 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 告警规则系统全流程可落地实施方案
## 📋 方案概述
基于现有系统架构和优化文档,整合告警规则、缓存同步、消息发布等所有优化策略,提供一个完整的、可直接落地的实施方案。
### 🎯 核心目标
- **统一规则管理**: 支持单体征、复合、复杂三类告警规则
- **高效缓存同步**: Redis三层缓存架构,TTL延长至24小时
- **消息发布优化**: 统一消息模型,支持多渠道分发
- **高并发处理**: 异步队列+批量处理,性能提升80%以上
- **管理界面友好**: 可视化规则配置向导
## 🏗️ 整体架构设计
### 系统架构图
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ ljwx-admin │ │ ljwx-boot │ │ ljwx-bigscreen │
│ 规则管理界面 │ ←→ │ 规则引擎+缓存 │ ←→ │ 告警生成+处理 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Vue3配置界面 │ │ Redis缓存同步 │ │ 异步队列处理 │
│ 可视化规则向导 │ │ DB=1 ←pub/sub→ │ │ 三层缓存优化 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
### 数据流向
```
告警规则配置 → ljwx-admin → ljwx-boot API → MySQL数据库
↓
Redis发布订阅通知
↓
ljwx-bigscreen订阅更新 → 本地缓存刷新
↓
健康数据上传 → 规则评估引擎
↓
复合条件评估 → 告警生成
↓
消息分发 → 微信/短信/大屏推送
```
## 🗄️ 数据库设计
### 1. 告警规则表优化 (t_alert_rules)
#### 1.1 向下兼容的表结构增强
```sql
-- 保持现有字段,新增扩展字段
ALTER TABLE `t_alert_rules`
ADD COLUMN `rule_category` ENUM('SINGLE', 'COMPOSITE', 'COMPLEX') DEFAULT 'SINGLE' COMMENT '规则类型:单体征/复合/复杂',
ADD COLUMN `condition_expression` JSON COMMENT '复合条件表达式',
ADD COLUMN `time_window_seconds` INT DEFAULT 300 COMMENT '时间窗口(秒)',
ADD COLUMN `cooldown_seconds` INT DEFAULT 600 COMMENT '告警冷却期(秒)',
ADD COLUMN `priority_level` INT DEFAULT 3 COMMENT '优先级(1-5,数字越小优先级越高)',
ADD COLUMN `rule_tags` JSON COMMENT '规则标签,便于分类管理',
ADD COLUMN `effective_time_start` TIME COMMENT '生效开始时间',
ADD COLUMN `effective_time_end` TIME COMMENT '生效结束时间',
ADD COLUMN `effective_days` VARCHAR(20) DEFAULT '1,2,3,4,5,6,7' COMMENT '生效星期(1-7)',
ADD COLUMN `version` BIGINT DEFAULT 1 COMMENT '规则版本号',
ADD COLUMN `enabled_channels` JSON COMMENT '启用的通知渠道',
ADD INDEX idx_customer_category (`customer_id`, `rule_category`),
ADD INDEX idx_priority (`priority_level`),
ADD INDEX idx_physical_sign_active (`physical_sign`, `is_active`);
```
#### 1.2 规则条件表达式设计
```json
-- 单体征规则 (现有兼容)
{
"rule_type": "single",
"physical_sign": "heart_rate",
"threshold_min": 60,
"threshold_max": 120,
"trend_duration": 3
}
-- 复合规则 (新增)
{
"rule_type": "composite",
"conditions": [
{
"physical_sign": "heart_rate",
"operator": ">",
"threshold": 120,
"duration_seconds": 180
},
{
"physical_sign": "blood_oxygen",
"operator": "<",
"threshold": 90,
"duration_seconds": 60
}
],
"logical_operator": "AND",
"evaluation_window": 300
}
-- 复杂规则 (高级)
{
"rule_type": "complex",
"expression": "((heart_rate > 120 AND blood_oxygen < 90) OR (temperature > 38.5)) AND time_of_day BETWEEN '09:00' AND '17:00'",
"variables": {
"heart_rate": "health_data.heart_rate",
"blood_oxygen": "health_data.blood_oxygen",
"temperature": "health_data.temperature",
"time_of_day": "CURRENT_TIME"
}
}
```
### 2. 告警记录表增强 (t_alert_info)
```sql
ALTER TABLE `t_alert_info`
ADD COLUMN `rule_version` BIGINT COMMENT '规则版本',
ADD COLUMN `trigger_conditions` JSON COMMENT '触发条件详情',
ADD COLUMN `evaluation_context` JSON COMMENT '评估上下文',
ADD COLUMN `suppression_key` VARCHAR(128) COMMENT '抑制键',
ADD COLUMN `escalation_level` INT DEFAULT 0 COMMENT '升级级别',
ADD COLUMN `ack_required` BOOLEAN DEFAULT FALSE COMMENT '是否需要确认',
ADD COLUMN `auto_resolve` BOOLEAN DEFAULT FALSE COMMENT '是否自动恢复',
ADD INDEX idx_suppression (`suppression_key`),
ADD INDEX idx_escalation (`escalation_level`),
ADD INDEX idx_rule_version (`rule_id`, `rule_version`);
```
### 3. 缓存同步表 (新增)
```sql
CREATE TABLE `t_alert_cache_sync` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`customer_id` BIGINT NOT NULL,
`cache_key` VARCHAR(255) NOT NULL,
`version` BIGINT NOT NULL DEFAULT 1,
`last_sync_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
`sync_status` ENUM('pending', 'synced', 'failed') DEFAULT 'pending',
`error_message` TEXT,
UNIQUE KEY uk_customer_cache (`customer_id`, `cache_key`),
INDEX idx_sync_status (`sync_status`),
INDEX idx_last_sync (`last_sync_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='告警规则缓存同步状态表';
```
## 🚀 后端实现 (ljwx-boot)
### 1. 规则引擎核心类
```java
@Service
@Slf4j
public class AlertRuleEngineService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private TAlertRulesMapper alertRulesMapper;
/**
* 规则评估主入口
*/
public List<AlertResult> evaluateRules(HealthDataEvent healthData) {
String customerId = healthData.getCustomerId();
// 1. 获取缓存的规则
List<TAlertRules> rules = getCachedRules(customerId);
// 2. 规则预编译和分组
CompiledRuleSet compiledRules = compileRules(rules);
// 3. 并行评估
return evaluateRulesParallel(healthData, compiledRules);
}
/**
* 三层缓存获取规则
*/
private List<TAlertRules> getCachedRules(String customerId) {
String cacheKey = "alert_rules:" + customerId;
// L1: JVM缓存 (5分钟)
List<TAlertRules> rules = localCache.get(cacheKey);
if (rules != null) {
return rules;
}
// L2: Redis缓存 (24小时)
rules = (List<TAlertRules>) redisTemplate.opsForValue().get(cacheKey);
if (rules != null) {
localCache.put(cacheKey, rules);
return rules;
}
// L3: 数据库
rules = alertRulesMapper.selectByCustomerId(Long.valueOf(customerId));
// 回填缓存
redisTemplate.opsForValue().set(cacheKey, rules, Duration.ofHours(24));
localCache.put(cacheKey, rules);
return rules;
}
/**
* 规则预编译
*/
private CompiledRuleSet compileRules(List<TAlertRules> rules) {
CompiledRuleSet compiledRules = new CompiledRuleSet();
for (TAlertRules rule : rules) {
if (!rule.getIsActive()) continue;
switch (rule.getRuleCategory()) {
case SINGLE:
compiledRules.addSingleRule(compileSingleRule(rule));
break;
case COMPOSITE:
compiledRules.addCompositeRule(compileCompositeRule(rule));
break;
case COMPLEX:
compiledRules.addComplexRule(compileComplexRule(rule));
break;
}
}
return compiledRules;
}
/**
* 并行规则评估
*/
private List<AlertResult> evaluateRulesParallel(HealthDataEvent healthData, CompiledRuleSet compiledRules) {
List<CompletableFuture<List<AlertResult>>> futures = new ArrayList<>();
// 单体征规则并行评估
futures.add(CompletableFuture.supplyAsync(() ->
evaluateSingleRules(healthData, compiledRules.getSingleRules())));
// 复合规则并行评估
futures.add(CompletableFuture.supplyAsync(() ->
evaluateCompositeRules(healthData, compiledRules.getCompositeRules())));
// 复杂规则并行评估
futures.add(CompletableFuture.supplyAsync(() ->
evaluateComplexRules(healthData, compiledRules.getComplexRules())));
// 合并结果
return futures.stream()
.map(CompletableFuture::join)
.flatMap(List::stream)
.collect(Collectors.toList());
}
}
```
### 2. 缓存同步增强版
```java
@Component
@Slf4j
public class AlertRulesCacheManager {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private TAlertCacheSyncMapper cacheSyncMapper;
/**
* 更新告警规则缓存 - 增强版本
*/
@Transactional
public void updateAlertRulesCache(Long customerId) {
String lockKey = "alert_rules_lock_" + customerId;
// 分布式锁
Boolean lockAcquired = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "locked", Duration.ofMinutes(5));
if (!lockAcquired) {
log.warn("获取分布式锁失败: {}", lockKey);
return;
}
try {
// 1. 查询最新规则
List<TAlertRules> rules = alertRulesMapper.selectByCustomerId(customerId);
// 2. 增加版本号
Long version = redisTemplate.opsForValue().increment("alert_rules_version_" + customerId);
// 3. 构建缓存数据
CacheData cacheData = CacheData.builder()
.version(version)
.rules(rules)
.updateTime(System.currentTimeMillis())
.customerId(customerId)
.build();
// 4. 更新Redis缓存
String cacheKey = "alert_rules_" + customerId;
redisTemplate.opsForValue().set(cacheKey, cacheData, Duration.ofHours(24));
// 5. 发布更新通知
String message = String.format("update:%s:%s", customerId, version);
redisTemplate.convertAndSend("alert_rules_channel", message);
// 6. 记录同步状态
updateSyncStatus(customerId, cacheKey, version, "synced", null);
log.info("告警规则缓存更新成功: customerId={}, version={}", customerId, version);
} catch (Exception e) {
log.error("告警规则缓存更新失败: customerId={}", customerId, e);
updateSyncStatus(customerId, "alert_rules_" + customerId, 0L, "failed", e.getMessage());
throw e;
} finally {
redisTemplate.delete(lockKey);
}
}
/**
* 批量更新缓存 - 性能优化
*/
public void batchUpdateCache(List<Long> customerIds) {
// 分批处理,避免阻塞
int batchSize = 10;
for (int i = 0; i < customerIds.size(); i += batchSize) {
List<Long> batch = customerIds.subList(i, Math.min(i + batchSize, customerIds.size()));
CompletableFuture.runAsync(() -> {
batch.parallelStream().forEach(this::updateAlertRulesCache);
});
}
}
private void updateSyncStatus(Long customerId, String cacheKey, Long version, String status, String errorMessage) {
TAlertCacheSync syncRecord = TAlertCacheSync.builder()
.customerId(customerId)
.cacheKey(cacheKey)
.version(version)
.lastSyncTime(LocalDateTime.now())
.syncStatus(status)
.errorMessage(errorMessage)
.build();
cacheSyncMapper.insertOrUpdate(syncRecord);
}
}
```
### 3. 统一消息发布器
```java
@Service
@Slf4j
public class UnifiedMessagePublisher {
@Autowired
private WeChatNotifier weChatNotifier;
@Autowired
private SmsNotifier smsNotifier;
@Autowired
private WebSocketNotifier webSocketNotifier;
@Autowired
private MessageQueueProducer messageProducer;
/**
* 统一消息发布入口
*/
public void publishAlert(AlertMessage alertMessage) {
// 1. 消息预处理
UnifiedMessage unifiedMessage = buildUnifiedMessage(alertMessage);
// 2. 渠道路由
List<NotificationChannel> channels = routeChannels(alertMessage);
// 3. 并行发送
List<CompletableFuture<NotificationResult>> futures = channels.stream()
.map(channel -> CompletableFuture.supplyAsync(() ->
sendToChannel(unifiedMessage, channel)))
.collect(Collectors.toList());
// 4. 结果汇总
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> {
List<NotificationResult> results = futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
logResults(alertMessage, results);
});
}
/**
* 构建统一消息模型
*/
private UnifiedMessage buildUnifiedMessage(AlertMessage alertMessage) {
return UnifiedMessage.builder()
.messageId(generateMessageId())
.messageType("alert")
.subType(alertMessage.getAlertType())
.title(buildTitle(alertMessage))
.content(buildContent(alertMessage))
.priority(mapPriority(alertMessage.getSeverityLevel()))
.urgency(mapUrgency(alertMessage.getSeverityLevel()))
.sender(buildSender())
.target(buildTarget(alertMessage))
.delivery(buildDelivery(alertMessage))
.metadata(buildMetadata(alertMessage))
.build();
}
/**
* 渠道智能路由
*/
private List<NotificationChannel> routeChannels(AlertMessage alertMessage) {
List<NotificationChannel> channels = new ArrayList<>();
// 基于严重程度决定渠道
String severity = alertMessage.getSeverityLevel();
List<String> enabledChannels = alertMessage.getEnabledChannels();
if (enabledChannels.contains("wechat")) {
channels.add(NotificationChannel.WECHAT);
}
if (enabledChannels.contains("message")) {
channels.add(NotificationChannel.INTERNAL_MESSAGE);
}
// Critical级别强制WebSocket推送
if ("critical".equals(severity)) {
channels.add(NotificationChannel.WEBSOCKET);
}
// 高优先级添加短信通道
if (Arrays.asList("critical", "high").contains(severity) &&
enabledChannels.contains("sms")) {
channels.add(NotificationChannel.SMS);
}
return channels;
}
/**
* 发送到指定渠道
*/
private NotificationResult sendToChannel(UnifiedMessage message, NotificationChannel channel) {
try {
switch (channel) {
case WECHAT:
return weChatNotifier.send(message);
case SMS:
return smsNotifier.send(message);
case WEBSOCKET:
return webSocketNotifier.send(message);
case INTERNAL_MESSAGE:
return insertInternalMessage(message);
default:
return NotificationResult.failed("未知渠道");
}
} catch (Exception e) {
log.error("发送消息失败: channel={}, messageId={}", channel, message.getMessageId(), e);
return NotificationResult.failed(e.getMessage());
}
}
}
```
## 🔧 前端实现 (ljwx-bigscreen优化)
### 1. 缓存订阅管理器增强
```python
class AlertRulesCacheSubscriber:
"""增强版Redis缓存订阅管理器"""
def __init__(self):
self.redis_bigscreen = RedisHelper() # DB=0
self.redis_boot = redis.Redis(host='127.0.0.1', port=6379, password='123456', db=1) # DB=1
self.local_cache = {}
self.cache_stats = {
'hits': 0,
'misses': 0,
'updates': 0,
'errors': 0
}
self.running = True
def start_subscriber(self):
"""启动缓存订阅器"""
def subscriber_thread():
pubsub = self.redis_bigscreen.pubsub()
pubsub.subscribe('alert_rules_channel')
logger.info("告警规则缓存订阅器已启动")
for message in pubsub.listen():
if not self.running:
break
if message['type'] == 'message':
self.handle_cache_update(message['data'])
Thread(target=subscriber_thread, daemon=True).start()
def handle_cache_update(self, message):
"""处理缓存更新消息 - 增强版本"""
try:
# 解析消息: "update:customer_id:version"
parts = message.decode('utf-8').split(':')
if len(parts) >= 2 and parts[0] == 'update':
customer_id = parts[1]
version = int(parts[2]) if len(parts) > 2 else 0
# 检查是否需要更新
if self.should_update_cache(customer_id, version):
success = self.refresh_local_cache(customer_id)
if success:
self.cache_stats['updates'] += 1
logger.info(f"缓存更新成功: customer_id={customer_id}, version={version}")
else:
self.cache_stats['errors'] += 1
except Exception as e:
self.cache_stats['errors'] += 1
logger.error(f"处理缓存更新失败: {e}")
def get_alert_rules(self, customer_id):
"""三层缓存获取告警规则 - 性能优化"""
cache_key = f"rules:{customer_id}"
# L1: 内存缓存
if cache_key in self.local_cache:
cached_data = self.local_cache[cache_key]
if time.time() - cached_data['timestamp'] < 300: # 5分钟有效
self.cache_stats['hits'] += 1
return cached_data['rules']
self.cache_stats['misses'] += 1
# L2: ljwx-boot Redis缓存
try:
boot_cache_key = f"alert_rules_{customer_id}"
cached_data = self.redis_boot.get(boot_cache_key)
if cached_data:
data = json.loads(cached_data)
rules = data.get('rules', [])
# 更新L1缓存
self.local_cache[cache_key] = {
'rules': rules,
'timestamp': time.time(),
'version': data.get('version', 0)
}
return rules
except Exception as e:
logger.error(f"从ljwx-boot Redis获取规则失败: {e}")
# L3: 数据库兜底
try:
return self.get_rules_from_database(customer_id)
except Exception as e:
logger.error(f"数据库兜底查询失败: {e}")
return []
def get_cache_stats(self):
"""获取缓存统计信息"""
total_requests = self.cache_stats['hits'] + self.cache_stats['misses']
hit_rate = (self.cache_stats['hits'] / total_requests * 100) if total_requests > 0 else 0
return {
'hit_rate': round(hit_rate, 2),
'total_hits': self.cache_stats['hits'],
'total_misses': self.cache_stats['misses'],
'cache_updates': self.cache_stats['updates'],
'cache_errors': self.cache_stats['errors'],
'local_cache_size': len(self.local_cache)
}
```
### 2. 高性能告警生成器
```python
class HighPerformanceAlertGenerator:
"""高性能告警生成器 - 异步队列处理"""
def __init__(self):
self.alert_queue = asyncio.Queue(maxsize=10000)
self.batch_size = 100
self.max_wait_time = 2.0
self.workers = []
self.rule_engine = AlertRuleEngine()
self.cache_subscriber = AlertRulesCacheSubscriber()
# 性能统计
self.performance_stats = {
'total_processed': 0,
'total_generated': 0,
'avg_processing_time': 0.0,
'queue_size': 0,
'cache_hit_rate': 0.0
}
async def start_workers(self, worker_count=4):
"""启动异步工作者"""
for i in range(worker_count):
worker = asyncio.create_task(self._worker_loop(f"worker-{i}"))
self.workers.append(worker)
# 启动缓存订阅器
self.cache_subscriber.start_subscriber()
logger.info(f"告警生成器已启动 {worker_count} 个工作者")
async def submit_health_data(self, health_data, health_data_id=None):
"""提交健康数据到处理队列"""
try:
item = {
'health_data': health_data,
'health_data_id': health_data_id,
'timestamp': time.time(),
'device_sn': health_data.get('deviceSn', '')
}
await self.alert_queue.put(item)
return True
except asyncio.QueueFull:
logger.warning("告警处理队列已满,丢弃请求")
return False
async def _worker_loop(self, worker_name):
"""工作者主循环"""
batch = []
last_process_time = time.time()
while True:
try:
# 收集批次
while len(batch) < self.batch_size and (time.time() - last_process_time) < self.max_wait_time:
try:
item = await asyncio.wait_for(self.alert_queue.get(), timeout=0.5)
batch.append(item)
except asyncio.TimeoutError:
break
# 处理批次
if batch:
await self._process_batch(batch, worker_name)
batch.clear()
last_process_time = time.time()
except Exception as e:
logger.error(f"{worker_name} 异常: {e}")
batch.clear()
async def _process_batch(self, health_data_batch, worker_name):
"""批量处理健康数据"""
start_time = time.time()
processed_count = 0
generated_alerts = 0
try:
# 按customer_id分组批次
customer_batches = defaultdict(list)
for item in health_data_batch:
customer_id = item['health_data'].get('customer_id') or item['health_data'].get('customerId')
customer_batches[customer_id].append(item)
# 并行处理每个客户的数据
tasks = []
for customer_id, customer_data in customer_batches.items():
task = self._process_customer_batch(customer_id, customer_data)
tasks.append(task)
results = await asyncio.gather(*tasks, return_exceptions=True)
# 统计结果
for result in results:
if isinstance(result, dict):
processed_count += result.get('processed', 0)
generated_alerts += result.get('generated', 0)
except Exception as e:
logger.error(f"{worker_name} 批量处理异常: {e}")
# 更新性能统计
processing_time = time.time() - start_time
self.performance_stats['total_processed'] += processed_count
self.performance_stats['total_generated'] += generated_alerts
self.performance_stats['avg_processing_time'] = (
self.performance_stats['avg_processing_time'] * 0.9 + processing_time * 0.1
)
self.performance_stats['queue_size'] = self.alert_queue.qsize()
logger.info(f"{worker_name} 批量处理完成: 处理{processed_count}条, 生成告警{generated_alerts}条, 耗时{processing_time:.3f}s")
async def _process_customer_batch(self, customer_id, health_data_list):
"""处理单个客户的健康数据批次"""
try:
# 1. 批量获取规则(一次查询)
rules = self.cache_subscriber.get_alert_rules(customer_id)
if not rules:
return {'processed': len(health_data_list), 'generated': 0}
# 2. 编译规则(一次编译)
compiled_rules = self.rule_engine.compile_rules(rules)
# 3. 批量评估
generated_alerts = 0
for item in health_data_list:
alerts = await self.rule_engine.evaluate_rules_async(
item['health_data'],
item['health_data_id'],
compiled_rules
)
generated_alerts += len(alerts)
return {'processed': len(health_data_list), 'generated': generated_alerts}
except Exception as e:
logger.error(f"处理客户批次失败: customer_id={customer_id}, error={e}")
return {'processed': 0, 'generated': 0}
def get_performance_stats(self):
"""获取性能统计"""
cache_stats = self.cache_subscriber.get_cache_stats()
self.performance_stats['cache_hit_rate'] = cache_stats['hit_rate']
return {
**self.performance_stats,
'cache_stats': cache_stats
}
```
### 3. 复合规则引擎
```python
class AlertRuleEngine:
"""告警规则引擎 - 支持复合条件"""
def __init__(self):
self.condition_cache = {}
self.evaluation_history = defaultdict(list)
def compile_rules(self, rules):
"""规则预编译"""
compiled_rules = {
'single_rules': defaultdict(list),
'composite_rules': [],
'complex_rules': [],
'priority_index': defaultdict(list)
}
for rule in rules:
if not rule.get('is_active', True):
continue
rule_category = rule.get('rule_category', 'SINGLE')
priority = rule.get('priority_level', 5)
if rule_category == 'SINGLE':
physical_sign = rule.get('physical_sign')
if physical_sign:
compiled_rule = self._compile_single_rule(rule)
compiled_rules['single_rules'][physical_sign].append(compiled_rule)
compiled_rules['priority_index'][priority].append(compiled_rule)
elif rule_category == 'COMPOSITE':
compiled_rule = self._compile_composite_rule(rule)
if compiled_rule:
compiled_rules['composite_rules'].append(compiled_rule)
compiled_rules['priority_index'][priority].append(compiled_rule)
elif rule_category == 'COMPLEX':
compiled_rule = self._compile_complex_rule(rule)
if compiled_rule:
compiled_rules['complex_rules'].append(compiled_rule)
compiled_rules['priority_index'][priority].append(compiled_rule)
return compiled_rules
async def evaluate_rules_async(self, health_data, health_data_id, compiled_rules):
"""异步规则评估"""
triggered_alerts = []
device_sn = health_data.get('deviceSn', '')
try:
# 1. 按优先级评估
for priority in sorted(compiled_rules['priority_index'].keys()):
priority_rules = compiled_rules['priority_index'][priority]
for rule in priority_rules:
# 检查生效时间
if not self._is_rule_effective(rule):
continue
# 根据规则类型评估
alert = None
if rule['type'] == 'single':
alert = await self._evaluate_single_rule_async(rule, health_data, health_data_id)
elif rule['type'] == 'composite':
alert = await self._evaluate_composite_rule_async(rule, health_data, health_data_id)
elif rule['type'] == 'complex':
alert = await self._evaluate_complex_rule_async(rule, health_data, health_data_id)
if alert:
# 检查告警抑制
if not self._is_suppressed(alert, rule):
triggered_alerts.append(alert)
# 设置冷却期
self._set_cooldown(alert, rule)
# 2. 批量保存告警
if triggered_alerts:
await self._save_alerts_batch(triggered_alerts)
except Exception as e:
logger.error(f"规则评估异常: device_sn={device_sn}, error={e}")
return triggered_alerts
async def _evaluate_composite_rule_async(self, rule, health_data, health_data_id):
"""复合规则异步评估"""
try:
condition_expr = rule.get('condition_expression', {})
conditions = condition_expr.get('conditions', [])
logical_op = condition_expr.get('logical_operator', 'AND')
if not conditions:
return None
# 并行评估所有条件
condition_tasks = []
for condition in conditions:
task = self._evaluate_condition_async(condition, health_data)
condition_tasks.append(task)
condition_results = await asyncio.gather(*condition_tasks)
# 逻辑组合
if logical_op == 'AND':
final_result = all(condition_results)
elif logical_op == 'OR':
final_result = any(condition_results)
else:
final_result = False
if final_result:
return await self._create_alert_async(rule, health_data, health_data_id, {
'condition_results': condition_results,
'trigger_type': 'composite'
})
except Exception as e:
logger.error(f"复合规则评估失败: rule_id={rule.get('id')}, error={e}")
return None
async def _evaluate_condition_async(self, condition, health_data):
"""异步条件评估"""
try:
physical_sign = condition['physical_sign']
operator = condition['operator']
threshold = condition['threshold']
duration = condition.get('duration_seconds', 0)
value = health_data.get(physical_sign)
if value is None:
return False
# 基础条件检查
condition_met = self._check_condition(float(value), operator, threshold)
# 持续时间检查
if condition_met and duration > 0:
condition_met = await self._check_duration_async(
health_data, physical_sign, operator, threshold, duration
)
return condition_met
except Exception as e:
logger.error(f"条件评估失败: condition={condition}, error={e}")
return False
def _check_condition(self, value, operator, threshold):
"""检查单个条件"""
if operator == '>':
return value > threshold
elif operator == '<':
return value < threshold
elif operator == '>=':
return value >= threshold
elif operator == '<=':
return value <= threshold
elif operator == '==':
return value == threshold
else:
return False
async def _check_duration_async(self, health_data, physical_sign, operator, threshold, duration):
"""异步检查持续时间条件"""
device_sn = health_data.get('deviceSn', '')
condition_key = f"{device_sn}:{physical_sign}:{operator}:{threshold}"
# 获取历史评估记录
history = self.evaluation_history[condition_key]
current_time = time.time()
# 清理过期记录
history[:] = [t for t in history if current_time - t < duration]
# 添加当前时间
history.append(current_time)
# 检查是否满足持续时间要求
return len(history) >= 3 # 至少3次连续满足条件
async def _save_alerts_batch(self, alerts):
"""批量保存告警"""
try:
for alert in alerts:
alert_info = AlertInfo(
rule_id=alert['rule_id'],
alert_type=alert['alert_type'],
device_sn=alert['device_sn'],
alert_desc=alert['alert_desc'],
severity_level=alert['severity_level'],
alert_status='pending',
alert_timestamp=get_now(),
health_id=alert.get('health_data_id'),
user_id=alert.get('user_id'),
org_id=alert.get('org_id'),
latitude=alert.get('latitude'),
longitude=alert.get('longitude'),
trigger_conditions=json.dumps(alert.get('trigger_conditions', {})),
evaluation_context=json.dumps(alert.get('evaluation_context', {}))
)
db.session.add(alert_info)
db.session.commit()
logger.info(f"批量保存告警成功: {len(alerts)}条")
except Exception as e:
db.session.rollback()
logger.error(f"批量保存告警失败: {e}")
```
## 🎨 前端管理界面 (ljwx-admin)
### 1. 规则配置向导组件
```vue
<template>
<div class="alert-rule-wizard">
<!-- 步骤导航 -->
<el-steps :active="currentStep" align-center>
<el-step title="基础信息" />
<el-step title="规则类型" />
<el-step title="条件配置" />
<el-step title="通知设置" />
<el-step title="生效时间" />
<el-step title="预览确认" />
</el-steps>
<!-- 基础信息步骤 -->
<div v-show="currentStep === 0" class="step-content">
<el-card header="基础信息">
<el-form :model="ruleConfig" label-width="120px">
<el-form-item label="规则名称" required>
<el-input v-model="ruleConfig.ruleName" placeholder="请输入规则名称" />
</el-form-item>
<el-form-item label="规则描述">
<el-input type="textarea" v-model="ruleConfig.ruleDescription"
placeholder="请描述此规则的用途" />
</el-form-item>
<el-form-item label="优先级">
<el-select v-model="ruleConfig.priorityLevel">
<el-option label="最高 (1)" :value="1" />
<el-option label="高 (2)" :value="2" />
<el-option label="中 (3)" :value="3" />
<el-option label="低 (4)" :value="4" />
<el-option label="最低 (5)" :value="5" />
</el-select>
</el-form-item>
<el-form-item label="规则标签">
<el-tag v-for="tag in ruleConfig.ruleTags" :key="tag" closable
@close="removeTag(tag)">{{ tag }}</el-tag>
<el-input v-model="newTag" @keyup.enter="addTag"
placeholder="输入标签按回车添加" style="width: 200px; margin-left: 10px;" />
</el-form-item>
</el-form>
</el-card>
</div>
<!-- 规则类型选择 -->
<div v-show="currentStep === 1" class="step-content">
<el-card header="选择规则类型">
<div class="rule-type-grid">
<div class="rule-type-card"
:class="{ active: ruleConfig.ruleCategory === 'SINGLE' }"
@click="selectRuleType('SINGLE')">
<el-icon><Monitor /></el-icon>
<h3>单体征规则</h3>
<p>基于单个生理指标的阈值告警</p>
<div class="example">例:心率 > 120 连续3次</div>
</div>
<div class="rule-type-card"
:class="{ active: ruleConfig.ruleCategory === 'COMPOSITE' }"
@click="selectRuleType('COMPOSITE')">
<el-icon><Connection /></el-icon>
<h3>复合规则</h3>
<p>多个生理指标的组合条件</p>
<div class="example">例:心率 > 120 且 血氧 < 90</div>
</div>
<div class="rule-type-card"
:class="{ active: ruleConfig.ruleCategory === 'COMPLEX' }"
@click="selectRuleType('COMPLEX')">
<el-icon><Setting /></el-icon>
<h3>复杂规则</h3>
<p>高级逻辑表达式和自定义公式</p>
<div class="example">例:自定义算法判断</div>
</div>
</div>
</el-card>
</div>
<!-- 单体征条件配置 -->
<div v-show="currentStep === 2 && ruleConfig.ruleCategory === 'SINGLE'" class="step-content">
<el-card header="单体征条件配置">
<el-form :model="ruleConfig" label-width="120px">
<el-form-item label="生理指标">
<el-select v-model="ruleConfig.physicalSign" placeholder="选择生理指标">
<el-option v-for="sign in physicalSigns" :key="sign.value"
:label="sign.label" :value="sign.value">
<span style="float: left">{{ sign.label }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ sign.unit }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="正常范围">
<div class="threshold-range">
<el-input-number v-model="ruleConfig.thresholdMin" placeholder="最小值" />
<span class="range-separator">至</span>
<el-input-number v-model="ruleConfig.thresholdMax" placeholder="最大值" />
<span class="unit">{{ getSelectedSignUnit() }}</span>
</div>
</el-form-item>
<el-form-item label="连续异常次数">
<el-input-number v-model="ruleConfig.trendDuration" :min="1" />
<span class="help-text">连续超出阈值多少次后触发告警</span>
</el-form-item>
<el-form-item label="时间窗口">
<el-input-number v-model="ruleConfig.timeWindowSeconds" :min="60" />
<span class="help-text">秒,在此时间窗口内统计异常次数</span>
</el-form-item>
</el-form>
</el-card>
</div>
<!-- 复合条件配置 -->
<div v-show="currentStep === 2 && ruleConfig.ruleCategory === 'COMPOSITE'" class="step-content">
<el-card header="复合条件配置">
<div class="composite-conditions">
<div v-for="(condition, index) in compositeConditions" :key="index"
class="condition-item">
<div class="condition-row">
<el-select v-model="condition.physicalSign" placeholder="生理指标">
<el-option v-for="sign in physicalSigns" :key="sign.value"
:label="sign.label" :value="sign.value" />
</el-select>
<el-select v-model="condition.operator" placeholder="运算符">
<el-option label="大于 >" value=">" />
<el-option label="小于 <" value="<" />
<el-option label="等于 =" value="=" />
<el-option label="大于等于 >=" value=">=" />
<el-option label="小于等于 <=" value="<=" />
</el-select>
<el-input-number v-model="condition.threshold" placeholder="阈值" />
<el-input-number v-model="condition.durationSeconds"
placeholder="持续时间(秒)" :min="0" />
<el-button @click="removeCondition(index)" type="danger"
icon="el-icon-delete" circle />
</div>
<!-- 逻辑连接符 -->
<div v-if="index < compositeConditions.length - 1" class="logic-connector">
<el-radio-group v-model="logicalOperator">
<el-radio-button label="AND">并且</el-radio-button>
<el-radio-button label="OR">或者</el-radio-button>
</el-radio-group>
</div>
</div>
<el-button @click="addCondition" type="primary" icon="el-icon-plus">
添加条件
</el-button>
</div>
</el-card>
</div>
<!-- 通知设置 -->
<div v-show="currentStep === 3" class="step-content">
<el-card header="通知设置">
<el-form :model="ruleConfig" label-width="120px">
<el-form-item label="严重程度">
<el-select v-model="ruleConfig.severityLevel">
<el-option label="低 (Low)" value="low" />
<el-option label="中 (Medium)" value="medium" />
<el-option label="高 (High)" value="high" />
<el-option label="紧急 (Critical)" value="critical" />
</el-select>
</el-form-item>
<el-form-item label="通知渠道">
<el-checkbox-group v-model="ruleConfig.enabledChannels">
<el-checkbox label="wechat">微信通知</el-checkbox>
<el-checkbox label="message">内部消息</el-checkbox>
<el-checkbox label="sms">短信通知</el-checkbox>
<el-checkbox label="email">邮件通知</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="告警消息模板">
<el-input type="textarea" v-model="ruleConfig.alertMessage"
placeholder="可使用变量: {user_name}, {device_sn}, {value}, {threshold}" />
</el-form-item>
<el-form-item label="冷却期">
<el-input-number v-model="ruleConfig.cooldownSeconds" :min="0" />
<span class="help-text">秒,避免重复告警的冷却时间</span>
</el-form-item>
</el-form>
</el-card>
</div>
<!-- 生效时间 -->
<div v-show="currentStep === 4" class="step-content">
<el-card header="生效时间设置">
<el-form :model="ruleConfig" label-width="120px">
<el-form-item label="生效时间段">
<el-time-picker v-model="effectiveTimeRange" is-range
range-separator="至" format="HH:mm" />
</el-form-item>
<el-form-item label="生效星期">
<el-checkbox-group v-model="ruleConfig.effectiveDays">
<el-checkbox label="1">周一</el-checkbox>
<el-checkbox label="2">周二</el-checkbox>
<el-checkbox label="3">周三</el-checkbox>
<el-checkbox label="4">周四</el-checkbox>
<el-checkbox label="5">周五</el-checkbox>
<el-checkbox label="6">周六</el-checkbox>
<el-checkbox label="7">周日</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="立即生效">
<el-switch v-model="ruleConfig.isActive" />
</el-form-item>
</el-form>
</el-card>
</div>
<!-- 预览确认 -->
<div v-show="currentStep === 5" class="step-content">
<el-card header="规则预览">
<div class="rule-preview">
<div class="preview-section">
<h4>基础信息</h4>
<p><strong>规则名称:</strong>{{ ruleConfig.ruleName }}</p>
<p><strong>规则类型:</strong>{{ getRuleTypeText() }}</p>
<p><strong>优先级:</strong>{{ getPriorityText() }}</p>
</div>
<div class="preview-section">
<h4>触发条件</h4>
<div class="condition-preview">{{ generateConditionText() }}</div>
</div>
<div class="preview-section">
<h4>通知配置</h4>
<p><strong>严重程度:</strong>{{ ruleConfig.severityLevel }}</p>
<p><strong>通知渠道:</strong>{{ ruleConfig.enabledChannels.join(', ') }}</p>
<p><strong>告警消息:</strong>{{ ruleConfig.alertMessage }}</p>
</div>
<div class="preview-section">
<h4>生效时间</h4>
<p><strong>时间段:</strong>{{ formatEffectiveTime() }}</p>
<p><strong>生效日期:</strong>{{ formatEffectiveDays() }}</p>
</div>
</div>
</el-card>
</div>
<!-- 操作按钮 -->
<div class="wizard-actions">
<el-button @click="previousStep" :disabled="currentStep === 0">上一步</el-button>
<el-button @click="nextStep" type="primary"
:disabled="!canNextStep" v-if="currentStep < 5">下一步</el-button>
<el-button @click="saveRule" type="success"
v-if="currentStep === 5" :loading="saving">保存规则</el-button>
</div>
</div>
</template>
<script setup>
import { ref, computed, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { Monitor, Connection, Setting } from '@element-plus/icons-vue'
// 响应式数据
const currentStep = ref(0)
const saving = ref(false)
const newTag = ref('')
// 规则配置
const ruleConfig = reactive({
ruleName: '',
ruleDescription: '',
ruleCategory: 'SINGLE',
physicalSign: '',
thresholdMin: null,
thresholdMax: null,
trendDuration: 1,
timeWindowSeconds: 300,
priorityLevel: 3,
severityLevel: 'medium',
alertMessage: '',
enabledChannels: ['message'],
cooldownSeconds: 600,
effectiveTimeStart: null,
effectiveTimeEnd: null,
effectiveDays: ['1','2','3','4','5','6','7'],
ruleTags: [],
isActive: true
})
// 复合条件
const compositeConditions = ref([
{ physicalSign: '', operator: '>', threshold: 0, durationSeconds: 60 }
])
const logicalOperator = ref('AND')
// 生理指标选项
const physicalSigns = ref([
{ label: '心率', value: 'heart_rate', unit: 'bpm' },
{ label: '血氧', value: 'blood_oxygen', unit: '%' },
{ label: '体温', value: 'temperature', unit: '℃' },
{ label: '收缩压', value: 'pressure_high', unit: 'mmHg' },
{ label: '舒张压', value: 'pressure_low', unit: 'mmHg' },
{ label: '步数', value: 'step', unit: '步' },
{ label: '卡路里', value: 'calorie', unit: 'kcal' },
{ label: '距离', value: 'distance', unit: 'km' },
{ label: '压力指数', value: 'stress', unit: '分' }
])
// 计算属性
const canNextStep = computed(() => {
switch (currentStep.value) {
case 0: return ruleConfig.ruleName.trim() !== ''
case 1: return ruleConfig.ruleCategory !== ''
case 2:
if (ruleConfig.ruleCategory === 'SINGLE') {
return ruleConfig.physicalSign !== ''
} else if (ruleConfig.ruleCategory === 'COMPOSITE') {
return compositeConditions.value.every(c =>
c.physicalSign && c.operator && c.threshold !== null)
}
return true
case 3: return ruleConfig.severityLevel && ruleConfig.enabledChannels.length > 0
case 4: return true
default: return true
}
})
// 方法
const selectRuleType = (type) => {
ruleConfig.ruleCategory = type
}
const addCondition = () => {
compositeConditions.value.push({
physicalSign: '',
operator: '>',
threshold: 0,
durationSeconds: 60
})
}
const removeCondition = (index) => {
if (compositeConditions.value.length > 1) {
compositeConditions.value.splice(index, 1)
}
}
const addTag = () => {
if (newTag.value && !ruleConfig.ruleTags.includes(newTag.value)) {
ruleConfig.ruleTags.push(newTag.value)
newTag.value = ''
}
}
const removeTag = (tag) => {
const index = ruleConfig.ruleTags.indexOf(tag)
if (index > -1) {
ruleConfig.ruleTags.splice(index, 1)
}
}
const nextStep = () => {
if (canNextStep.value && currentStep.value < 5) {
currentStep.value++
}
}
const previousStep = () => {
if (currentStep.value > 0) {
currentStep.value--
}
}
const saveRule = async () => {
saving.value = true
try {
// 构建保存数据
const saveData = {
...ruleConfig,
conditionExpression: ruleConfig.ruleCategory === 'COMPOSITE' ? {
conditions: compositeConditions.value,
logicalOperator: logicalOperator.value
} : null
}
// 调用API保存
await saveAlertRule(saveData)
ElMessage.success('告警规则保存成功')
// 返回规则列表页面
} catch (error) {
ElMessage.error('保存失败:' + error.message)
} finally {
saving.value = false
}
}
// 格式化方法
const getRuleTypeText = () => {
const types = {
'SINGLE': '单体征规则',
'COMPOSITE': '复合规则',
'COMPLEX': '复杂规则'
}
return types[ruleConfig.ruleCategory] || ''
}
const getPriorityText = () => {
const priorities = { 1: '最高', 2: '高', 3: '中', 4: '低', 5: '最低' }
return priorities[ruleConfig.priorityLevel] || ''
}
const generateConditionText = () => {
if (ruleConfig.ruleCategory === 'SINGLE') {
const sign = physicalSigns.value.find(s => s.value === ruleConfig.physicalSign)
return `${sign?.label || ''} 在 ${ruleConfig.thresholdMin} - ${ruleConfig.thresholdMax} 范围外连续 ${ruleConfig.trendDuration} 次`
} else if (ruleConfig.ruleCategory === 'COMPOSITE') {
return compositeConditions.value.map(c => {
const sign = physicalSigns.value.find(s => s.value === c.physicalSign)
return `${sign?.label || ''} ${c.operator} ${c.threshold}`
}).join(` ${logicalOperator.value} `)
}
return ''
}
const getSelectedSignUnit = () => {
const sign = physicalSigns.value.find(s => s.value === ruleConfig.physicalSign)
return sign?.unit || ''
}
const formatEffectiveTime = () => {
// 实现时间格式化
return '全天'
}
const formatEffectiveDays = () => {
const dayNames = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
return ruleConfig.effectiveDays.map(d => dayNames[parseInt(d) - 1]).join(', ')
}
// API调用
const saveAlertRule = async (ruleData) => {
// 实现API调用
console.log('保存规则数据:', ruleData)
}
</script>
<style scoped>
.alert-rule-wizard {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.step-content {
margin: 20px 0;
min-height: 400px;
}
.rule-type-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin: 20px 0;
}
.rule-type-card {
border: 2px solid #e4e7ed;
border-radius: 8px;
padding: 30px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
}
.rule-type-card:hover {
border-color: #409eff;
box-shadow: 0 2px 12px rgba(64, 158, 255, 0.2);
}
.rule-type-card.active {
border-color: #409eff;
background-color: #f0f9ff;
}
.rule-type-card .el-icon {
font-size: 48px;
color: #409eff;
margin-bottom: 15px;
}
.rule-type-card h3 {
margin: 15px 0 10px;
color: #303133;
}
.rule-type-card p {
color: #606266;
margin-bottom: 15px;
}
.example {
background-color: #f5f7fa;
padding: 8px;
border-radius: 4px;
font-size: 12px;
color: #909399;
}
.threshold-range {
display: flex;
align-items: center;
gap: 10px;
}
.range-separator {
color: #606266;
}
.unit {
color: #909399;
font-size: 12px;
}
.help-text {
color: #909399;
font-size: 12px;
margin-left: 10px;
}
.condition-item {
border: 1px solid #e4e7ed;
border-radius: 6px;
padding: 15px;
margin-bottom: 15px;
}
.condition-row {
display: flex;
gap: 15px;
align-items: center;
}
.logic-connector {
text-align: center;
margin: 10px 0;
}
.rule-preview {
background-color: #f8f9fa;
border-radius: 6px;
padding: 20px;
}
.preview-section {
margin-bottom: 20px;
}
.preview-section h4 {
color: #303133;
margin-bottom: 10px;
border-bottom: 1px solid #e4e7ed;
padding-bottom: 5px;
}
.condition-preview {
background-color: #fff;
border: 1px solid #e4e7ed;
border-radius: 4px;
padding: 10px;
font-family: 'Monaco', 'Menlo', monospace;
color: #606266;
}
.wizard-actions {
text-align: center;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #e4e7ed;
}
.wizard-actions .el-button {
margin: 0 10px;
min-width: 100px;
}
</style>
```
### 2. 规则列表管理页面
```vue
<template>
<div class="alert-rules-management">
<!-- 页面头部 -->
<div class="page-header">
<div class="header-left">
<h2>告警规则管理</h2>
<p>管理和配置系统告警规则,支持单体征、复合和复杂规则</p>
</div>
<div class="header-right">
<el-button type="primary" @click="createRule">
<el-icon><Plus /></el-icon>
新建规则
</el-button>
</div>
</div>
<!-- 搜索和过滤 -->
<el-card class="filter-card">
<el-row :gutter="20">
<el-col :span="6">
<el-input v-model="searchForm.keyword" placeholder="搜索规则名称" clearable>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</el-col>
<el-col :span="4">
<el-select v-model="searchForm.ruleCategory" placeholder="规则类型" clearable>
<el-option label="单体征规则" value="SINGLE" />
<el-option label="复合规则" value="COMPOSITE" />
<el-option label="复杂规则" value="COMPLEX" />
</el-select>
</el-col>
<el-col :span="4">
<el-select v-model="searchForm.severityLevel" placeholder="严重程度" clearable>
<el-option label="低" value="low" />
<el-option label="中" value="medium" />
<el-option label="高" value="high" />
<el-option label="紧急" value="critical" />
</el-select>
</el-col>
<el-col :span="4">
<el-select v-model="searchForm.isActive" placeholder="状态" clearable>
<el-option label="启用" :value="true" />
<el-option label="禁用" :value="false" />
</el-select>
</el-col>
<el-col :span="6">
<el-button type="primary" @click="searchRules">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
<el-button type="success" @click="batchEnable" :disabled="!selectedRules.length">
批量启用
</el-button>
<el-button type="warning" @click="batchDisable" :disabled="!selectedRules.length">
批量禁用
</el-button>
</el-col>
</el-row>
</el-card>
<!-- 规则列表 -->
<el-card class="table-card">
<el-table :data="rulesList" v-loading="loading"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange">
<el-table-column type="selection" width="50" />
<el-table-column prop="ruleName" label="规则名称" min-width="200">
<template #default="{ row }">
<div class="rule-name-cell">
<span class="rule-name">{{ row.ruleName }}</span>
<div class="rule-tags">
<el-tag v-for="tag in row.ruleTags" :key="tag" size="small">
{{ tag }}
</el-tag>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="ruleCategory" label="规则类型" width="120">
<template #default="{ row }">
<el-tag :type="getRuleTypeTagType(row.ruleCategory)">
{{ getRuleTypeText(row.ruleCategory) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="physicalSign" label="监测指标" width="120">
<template #default="{ row }">
<span v-if="row.ruleCategory === 'SINGLE'">
{{ getPhysicalSignText(row.physicalSign) }}
</span>
<span v-else-if="row.ruleCategory === 'COMPOSITE'">
{{ getCompositeSignsText(row.conditionExpression) }}
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="severityLevel" label="严重程度" width="100">
<template #default="{ row }">
<el-tag :type="getSeverityTagType(row.severityLevel)">
{{ getSeverityText(row.severityLevel) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="priorityLevel" label="优先级" width="80" sortable>
<template #default="{ row }">
<el-rate v-model="row.priorityLevel" disabled show-score
text-color="#ff9900" :max="5" />
</template>
</el-table-column>
<el-table-column prop="enabledChannels" label="通知渠道" width="150">
<template #default="{ row }">
<div class="channels">
<el-icon v-if="row.enabledChannels.includes('wechat')" class="channel-icon wechat">
<ChatDotRound />
</el-icon>
<el-icon v-if="row.enabledChannels.includes('message')" class="channel-icon message">
<Message />
</el-icon>
<el-icon v-if="row.enabledChannels.includes('sms')" class="channel-icon sms">
<Phone />
</el-icon>
<el-icon v-if="row.enabledChannels.includes('email')" class="channel-icon email">
<Monitor />
</el-icon>
</div>
</template>
</el-table-column>
<el-table-column prop="isActive" label="状态" width="80">
<template #default="{ row }">
<el-switch v-model="row.isActive" @change="toggleRuleStatus(row)" />
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="160" sortable>
<template #default="{ row }">
{{ formatTime(row.updateTime) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button size="small" @click="viewRule(row)">详情</el-button>
<el-button size="small" type="primary" @click="editRule(row)">编辑</el-button>
<el-button size="small" @click="duplicateRule(row)">复制</el-button>
<el-popconfirm title="确定删除此规则吗?" @confirm="deleteRule(row.id)">
<template #reference>
<el-button size="small" type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-wrapper">
<el-pagination v-model:current-page="pagination.current"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange" />
</div>
</el-card>
<!-- 规则详情对话框 -->
<el-dialog v-model="detailDialogVisible" title="规则详情" width="800px">
<div v-if="selectedRule" class="rule-detail">
<!-- 规则详情内容 -->
<el-descriptions :column="2" border>
<el-descriptions-item label="规则名称">{{ selectedRule.ruleName }}</el-descriptions-item>
<el-descriptions-item label="规则类型">{{ getRuleTypeText(selectedRule.ruleCategory) }}</el-descriptions-item>
<el-descriptions-item label="严重程度">{{ getSeverityText(selectedRule.severityLevel) }}</el-descriptions-item>
<el-descriptions-item label="优先级">{{ selectedRule.priorityLevel }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="selectedRule.isActive ? 'success' : 'danger'">
{{ selectedRule.isActive ? '启用' : '禁用' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="通知渠道">{{ selectedRule.enabledChannels.join(', ') }}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{ formatTime(selectedRule.createTime) }}</el-descriptions-item>
</el-descriptions>
<!-- 条件详情 -->
<div class="condition-detail" v-if="selectedRule.ruleCategory === 'COMPOSITE'">
<h4>复合条件</h4>
<div class="composite-condition">
<!-- 显示复合条件详情 -->
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search, ChatDotRound, Message, Phone, Monitor } from '@element-plus/icons-vue'
// 响应式数据
const loading = ref(false)
const rulesList = ref([])
const selectedRules = ref([])
const detailDialogVisible = ref(false)
const selectedRule = ref(null)
// 搜索表单
const searchForm = reactive({
keyword: '',
ruleCategory: '',
severityLevel: '',
isActive: null
})
// 分页
const pagination = reactive({
current: 1,
pageSize: 20,
total: 0
})
// 排序
const sortConfig = reactive({
prop: '',
order: ''
})
// 生命周期
onMounted(() => {
loadRulesList()
})
// 方法
const loadRulesList = async () => {
loading.value = true
try {
const params = {
...searchForm,
page: pagination.current,
pageSize: pagination.pageSize,
sortProp: sortConfig.prop,
sortOrder: sortConfig.order
}
const response = await fetchAlertRules(params)
rulesList.value = response.data.list
pagination.total = response.data.total
} catch (error) {
ElMessage.error('加载规则列表失败:' + error.message)
} finally {
loading.value = false
}
}
const createRule = () => {
// 跳转到规则创建页面
router.push('/alert-rules/create')
}
const editRule = (rule) => {
// 跳转到规则编辑页面
router.push(`/alert-rules/edit/${rule.id}`)
}
const viewRule = (rule) => {
selectedRule.value = rule
detailDialogVisible.value = true
}
const duplicateRule = async (rule) => {
try {
await ElMessageBox.confirm('确定复制此规则吗?', '确认操作')
const duplicatedRule = {
...rule,
id: undefined,
ruleName: rule.ruleName + ' (副本)',
isActive: false
}
await saveAlertRule(duplicatedRule)
ElMessage.success('规则复制成功')
loadRulesList()
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('复制失败:' + error.message)
}
}
}
const deleteRule = async (ruleId) => {
try {
await deleteAlertRule(ruleId)
ElMessage.success('删除成功')
loadRulesList()
} catch (error) {
ElMessage.error('删除失败:' + error.message)
}
}
const toggleRuleStatus = async (rule) => {
try {
await updateRuleStatus(rule.id, rule.isActive)
ElMessage.success(rule.isActive ? '规则已启用' : '规则已禁用')
} catch (error) {
rule.isActive = !rule.isActive // 回滚状态
ElMessage.error('状态更新失败:' + error.message)
}
}
const batchEnable = async () => {
try {
const ruleIds = selectedRules.value.map(rule => rule.id)
await batchUpdateRuleStatus(ruleIds, true)
ElMessage.success('批量启用成功')
loadRulesList()
} catch (error) {
ElMessage.error('批量启用失败:' + error.message)
}
}
const batchDisable = async () => {
try {
const ruleIds = selectedRules.value.map(rule => rule.id)
await batchUpdateRuleStatus(ruleIds, false)
ElMessage.success('批量禁用成功')
loadRulesList()
} catch (error) {
ElMessage.error('批量禁用失败:' + error.message)
}
}
const searchRules = () => {
pagination.current = 1
loadRulesList()
}
const resetSearch = () => {
Object.keys(searchForm).forEach(key => {
searchForm[key] = key === 'isActive' ? null : ''
})
searchRules()
}
const handleSelectionChange = (selection) => {
selectedRules.value = selection
}
const handleSortChange = ({ prop, order }) => {
sortConfig.prop = prop
sortConfig.order = order
loadRulesList()
}
const handleSizeChange = (size) => {
pagination.pageSize = size
pagination.current = 1
loadRulesList()
}
const handleCurrentChange = (page) => {
pagination.current = page
loadRulesList()
}
// 格式化方法
const getRuleTypeText = (category) => {
const types = {
'SINGLE': '单体征',
'COMPOSITE': '复合',
'COMPLEX': '复杂'
}
return types[category] || ''
}
const getRuleTypeTagType = (category) => {
const types = {
'SINGLE': '',
'COMPOSITE': 'success',
'COMPLEX': 'warning'
}
return types[category] || ''
}
const getSeverityText = (level) => {
const levels = {
'low': '低',
'medium': '中',
'high': '高',
'critical': '紧急'
}
return levels[level] || ''
}
const getSeverityTagType = (level) => {
const types = {
'low': 'info',
'medium': '',
'high': 'warning',
'critical': 'danger'
}
return types[level] || ''
}
const getPhysicalSignText = (sign) => {
const signs = {
'heart_rate': '心率',
'blood_oxygen': '血氧',
'temperature': '体温',
'pressure_high': '收缩压',
'pressure_low': '舒张压'
}
return signs[sign] || sign
}
const getCompositeSignsText = (expression) => {
if (!expression || !expression.conditions) return '-'
return expression.conditions.map(c => getPhysicalSignText(c.physicalSign)).join(', ')
}
const formatTime = (time) => {
if (!time) return '-'
return new Date(time).toLocaleString('zh-CN')
}
// API 调用方法
const fetchAlertRules = async (params) => {
// 实现API调用
console.log('获取规则列表:', params)
return { data: { list: [], total: 0 } }
}
const saveAlertRule = async (rule) => {
// 实现API调用
console.log('保存规则:', rule)
}
const deleteAlertRule = async (ruleId) => {
// 实现API调用
console.log('删除规则:', ruleId)
}
const updateRuleStatus = async (ruleId, isActive) => {
// 实现API调用
console.log('更新规则状态:', ruleId, isActive)
}
const batchUpdateRuleStatus = async (ruleIds, isActive) => {
// 实现API调用
console.log('批量更新规则状态:', ruleIds, isActive)
}
</script>
<style scoped>
.alert-rules-management {
padding: 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.header-left h2 {
margin: 0 0 5px 0;
color: #303133;
}
.header-left p {
margin: 0;
color: #606266;
font-size: 14px;
}
.filter-card {
margin-bottom: 20px;
}
.table-card {
margin-bottom: 20px;
}
.rule-name-cell {
.rule-name {
font-weight: 500;
color: #303133;
}
.rule-tags {
margin-top: 5px;
.el-tag {
margin-right: 5px;
}
}
}
.channels {
display: flex;
gap: 8px;
.channel-icon {
font-size: 16px;
&.wechat { color: #1aad19; }
&.message { color: #409eff; }
&.sms { color: #e6a23c; }
&.email { color: #909399; }
}
}
.pagination-wrapper {
margin-top: 20px;
text-align: center;
}
.rule-detail {
.condition-detail {
margin-top: 20px;
h4 {
margin-bottom: 10px;
color: #303133;
}
}
}
</style>
```
## 📊 性能监控与统计
### 1. 性能监控接口
```python
@app.route('/api/alert_rules/performance_stats', methods=['GET'])
def get_alert_rules_performance_stats():
"""获取告警规则系统性能统计"""
try:
# 获取高性能生成器统计
generator_stats = high_performance_generator.get_performance_stats()
# 获取缓存统计
cache_subscriber = get_alert_rules_cache_subscriber()
cache_stats = cache_subscriber.get_cache_stats()
# 获取数据库统计
db_stats = get_database_performance_stats()
# 获取告警统计
alert_stats = get_alert_generation_stats()
return jsonify({
'success': True,
'data': {
'performance': {
'generator': generator_stats,
'cache': cache_stats,
'database': db_stats,
'alerts': alert_stats
},
'timestamp': time.time()
}
})
except Exception as e:
logger.error(f"获取性能统计失败: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 500
def get_database_performance_stats():
"""获取数据库性能统计"""
return {
'connection_pool_size': db.engine.pool.size(),
'connection_pool_checked_out': db.engine.pool.checkedout(),
'query_count': getattr(db.engine, '_query_count', 0),
'avg_query_time': getattr(db.engine, '_avg_query_time', 0)
}
def get_alert_generation_stats():
"""获取告警生成统计"""
today = datetime.now().date()
# 今日告警统计
today_alerts = db.session.query(AlertInfo).filter(
func.date(AlertInfo.alert_timestamp) == today
).count()
# 按严重程度统计
severity_stats = db.session.query(
AlertInfo.severity_level,
func.count(AlertInfo.id)
).filter(
func.date(AlertInfo.alert_timestamp) == today
).group_by(AlertInfo.severity_level).all()
# 按规则类型统计
rule_type_stats = db.session.query(
AlertRules.rule_type,
func.count(AlertInfo.id)
).join(
AlertInfo, AlertRules.id == AlertInfo.rule_id
).filter(
func.date(AlertInfo.alert_timestamp) == today
).group_by(AlertRules.rule_type).all()
return {
'today_total': today_alerts,
'severity_distribution': dict(severity_stats),
'rule_type_distribution': dict(rule_type_stats)
}
```
### 2. 监控仪表板页面
```vue
<template>
<div class="performance-dashboard">
<el-row :gutter="20">
<!-- 整体性能卡片 -->
<el-col :span="6" v-for="metric in overallMetrics" :key="metric.key">
<el-card class="metric-card">
<div class="metric-content">
<div class="metric-icon" :style="{ color: metric.color }">
<component :is="metric.icon" />
</div>
<div class="metric-info">
<div class="metric-value">{{ metric.value }}</div>
<div class="metric-label">{{ metric.label }}</div>
<div class="metric-trend" :class="metric.trendClass">
{{ metric.trend }}
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px;">
<!-- 缓存性能图表 -->
<el-col :span="12">
<el-card title="缓存性能">
<div ref="cacheChartRef" style="height: 300px;"></div>
</el-card>
</el-col>
<!-- 告警生成趋势 -->
<el-col :span="12">
<el-card title="告警生成趋势">
<div ref="alertTrendChartRef" style="height: 300px;"></div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px;">
<!-- 规则执行性能 -->
<el-col :span="24">
<el-card title="规则执行性能">
<el-table :data="rulePerformanceData" max-height="400">
<el-table-column prop="ruleName" label="规则名称" />
<el-table-column prop="ruleType" label="规则类型" />
<el-table-column prop="avgExecutionTime" label="平均执行时间(ms)" />
<el-table-column prop="totalExecutions" label="执行次数" />
<el-table-column prop="successRate" label="成功率" />
<el-table-column prop="lastExecution" label="最后执行时间" />
</el-table>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'
import { TrendChartUp, Timer, CheckCircle, Warning } from '@element-plus/icons-vue'
// 响应式数据
const cacheChartRef = ref(null)
const alertTrendChartRef = ref(null)
const rulePerformanceData = ref([])
// 性能指标
const overallMetrics = ref([
{
key: 'processing_speed',
label: '处理速度',
value: '1,250/s',
trend: '+15%',
trendClass: 'trend-up',
color: '#67c23a',
icon: TrendChartUp
},
{
key: 'avg_response_time',
label: '平均响应时间',
value: '45ms',
trend: '-8%',
trendClass: 'trend-down',
color: '#409eff',
icon: Timer
},
{
key: 'success_rate',
label: '成功率',
value: '99.8%',
trend: '+0.2%',
trendClass: 'trend-up',
color: '#67c23a',
icon: CheckCircle
},
{
key: 'cache_hit_rate',
label: '缓存命中率',
value: '94.2%',
trend: '+12%',
trendClass: 'trend-up',
color: '#e6a23c',
icon: Warning
}
])
let cacheChart = null
let alertTrendChart = null
let refreshTimer = null
// 生命周期
onMounted(() => {
initCharts()
loadPerformanceData()
// 定时刷新
refreshTimer = setInterval(loadPerformanceData, 30000) // 30秒刷新
})
onUnmounted(() => {
if (cacheChart) cacheChart.dispose()
if (alertTrendChart) alertTrendChart.dispose()
if (refreshTimer) clearInterval(refreshTimer)
})
// 方法
const initCharts = () => {
// 初始化缓存性能图表
cacheChart = echarts.init(cacheChartRef.value)
const cacheOption = {
title: { text: '缓存命中率', left: 'center' },
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: [] },
yAxis: { type: 'value', max: 100 },
series: [
{
name: 'L1缓存',
type: 'line',
data: [],
smooth: true,
itemStyle: { color: '#409eff' }
},
{
name: 'L2缓存',
type: 'line',
data: [],
smooth: true,
itemStyle: { color: '#67c23a' }
},
{
name: 'L3数据库',
type: 'line',
data: [],
smooth: true,
itemStyle: { color: '#e6a23c' }
}
]
}
cacheChart.setOption(cacheOption)
// 初始化告警趋势图表
alertTrendChart = echarts.init(alertTrendChartRef.value)
const alertOption = {
title: { text: '告警生成趋势', left: 'center' },
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: [] },
yAxis: { type: 'value' },
series: [
{
name: '总告警数',
type: 'bar',
data: [],
itemStyle: { color: '#409eff' }
},
{
name: '紧急告警',
type: 'line',
data: [],
itemStyle: { color: '#f56c6c' }
}
]
}
alertTrendChart.setOption(alertOption)
}
const loadPerformanceData = async () => {
try {
// 获取性能统计数据
const response = await fetch('/api/alert_rules/performance_stats')
const data = await response.json()
if (data.success) {
updateMetrics(data.data.performance)
updateCharts(data.data.performance)
updateRulePerformanceTable(data.data.performance)
}
} catch (error) {
console.error('加载性能数据失败:', error)
}
}
const updateMetrics = (performanceData) => {
// 更新整体性能指标
const generator = performanceData.generator
const cache = performanceData.cache
overallMetrics.value[0].value = `${Math.round(generator.total_processed / generator.avg_processing_time)}/s`
overallMetrics.value[1].value = `${Math.round(generator.avg_processing_time * 1000)}ms`
overallMetrics.value[2].value = `${(generator.total_processed / (generator.total_processed + generator.total_failed) * 100).toFixed(1)}%`
overallMetrics.value[3].value = `${cache.hit_rate}%`
}
const updateCharts = (performanceData) => {
// 更新图表数据(示例数据)
const timeLabels = generateTimeLabels()
// 更新缓存图表
cacheChart.setOption({
xAxis: { data: timeLabels },
series: [
{ data: generateRandomData(24, 85, 95) },
{ data: generateRandomData(24, 75, 85) },
{ data: generateRandomData(24, 60, 75) }
]
})
// 更新告警趋势图表
alertTrendChart.setOption({
xAxis: { data: timeLabels },
series: [
{ data: generateRandomData(24, 50, 200) },
{ data: generateRandomData(24, 0, 20) }
]
})
}
const updateRulePerformanceTable = (performanceData) => {
// 更新规则性能表格(示例数据)
rulePerformanceData.value = [
{
ruleName: '心率异常规则',
ruleType: '单体征',
avgExecutionTime: 12,
totalExecutions: 1250,
successRate: '99.8%',
lastExecution: '2025-09-10 14:30:25'
},
{
ruleName: '综合健康评估',
ruleType: '复合',
avgExecutionTime: 28,
totalExecutions: 680,
successRate: '99.5%',
lastExecution: '2025-09-10 14:30:18'
}
]
}
// 工具函数
const generateTimeLabels = () => {
const labels = []
for (let i = 23; i >= 0; i--) {
const time = new Date()
time.setHours(time.getHours() - i)
labels.push(time.getHours() + ':00')
}
return labels
}
const generateRandomData = (count, min, max) => {
return Array.from({ length: count }, () =>
Math.floor(Math.random() * (max - min + 1)) + min
)
}
</script>
<style scoped>
.performance-dashboard {
padding: 20px;
}
.metric-card {
height: 120px;
}
.metric-content {
display: flex;
align-items: center;
height: 100%;
}
.metric-icon {
font-size: 36px;
margin-right: 15px;
}
.metric-info {
flex: 1;
}
.metric-value {
font-size: 24px;
font-weight: bold;
color: #303133;
margin-bottom: 5px;
}
.metric-label {
font-size: 14px;
color: #606266;
margin-bottom: 5px;
}
.metric-trend {
font-size: 12px;
font-weight: 500;
}
.trend-up {
color: #67c23a;
}
.trend-down {
color: #409eff;
}
</style>
```
## 📋 实施计划与部署
### 阶段一:基础设施准备 (1周)
1. **数据库表结构升级**
- 执行表结构变更SQL
- 数据迁移和验证
- 索引优化
2. **Redis配置优化**
- TTL调整为24小时
- 发布订阅通道配置
- 连接池优化
3. **基础设施监控**
- 性能基线建立
- 监控指标配置
### 阶段二:后端核心实现 (2周)
1. **规则引擎开发**
- 单体征规则兼容
- 复合规则引擎
- 规则预编译优化
2. **缓存同步优化**
- 三层缓存架构
- 跨DB订阅机制
- 容错和恢复
3. **消息发布统一**
- 统一消息模型
- 多渠道路由
- 异步处理队列
### 阶段三:前端界面开发 (2周)
1. **规则配置向导**
- 步骤式配置界面
- 实时预览功能
- 条件可视化编辑
2. **规则管理页面**
- 列表展示和筛选
- 批量操作功能
- 性能监控面板
3. **性能监控仪表板**
- 实时性能图表
- 告警统计分析
- 系统健康状态
### 阶段四:性能优化和测试 (1周)
1. **性能压力测试**
- 1000并发处理验证
- 响应时间优化
- 内存使用优化
2. **功能集成测试**
- 端到端流程测试
- 异常场景验证
- 兼容性测试
### 阶段五:生产部署 (1周)
1. **灰度发布**
- 小流量验证
- 性能监控
- 问题快速响应
2. **全量发布**
- 配置迁移
- 数据同步
- 性能调优
## 🎯 预期效果
### 性能提升指标
- **规则评估速度**: 提升80%以上
- **并发处理能力**: 支持1000+并发
- **内存使用**: 减少40%
- **响应时间**: 平均50ms以下
### 功能增强效果
- **规则表达能力**: 支持复杂的多体征关联
- **管理效率**: 可视化配置,降低80%配置时间
- **系统稳定性**: 告警抑制,减少90%重复告警
- **运维便利性**: 完整监控体系,问题快速定位
### 业务价值
- **告警准确性**: 减少误报85%以上
- **响应速度**: 告警延迟降至秒级
- **运维成本**: 减少人工干预60%
- **用户体验**: 更精准的健康监护服务
这个完整的实施方案整合了所有优化策略,提供了从数据库设计、后端实现、前端界面到部署监控的全流程解决方案,可以直接按照计划逐步实施落地。