本文目录导读:

实现PHP项目的用户自定义告警功能,通常需要覆盖规则配置、数据采集、逻辑判断、通知推送四个核心环节,下面提供一套完整的实现方案和代码示例。
整体架构设计
graph TD
A[用户端] -->|配置告警规则| B[规则存储]
B -->|读取规则| C[告警引擎]
D[数据源] -->|推送数据| C
C -->|触发条件| E[通知渠道]
E -->|发送消息| F[用户]
数据库表结构设计(MySQL)
告警规则表
CREATE TABLE `alert_rules` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '用户ID',
`rule_name` varchar(100) NOT NULL COMMENT '规则名称',
`metric` varchar(50) NOT NULL COMMENT '监控指标(如 cpu_usage, memory_usage, order_amount)',
`operator` enum('>','<','>=','<=','==','!=') NOT NULL COMMENT '比较运算符',
`threshold` decimal(20,6) NOT NULL COMMENT '阈值',
`duration` int(11) DEFAULT 0 COMMENT '持续次数 (N次持续触发才告警)',
`notification_method` json NOT NULL COMMENT '通知方式 ["email","sms","dingtalk","webhook"]',
`notification_target` json NOT NULL COMMENT '通知目标 {"email":"user@example.com","webhook":"https://.../"}',
`enabled` tinyint(1) DEFAULT 1 COMMENT '启用状态',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_metric` (`metric`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
告警事件记录表
CREATE TABLE `alert_events` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `rule_id` int(11) NOT NULL, `user_id` int(11) NOT NULL, `metric` varchar(50) NOT NULL, `triggered_value` decimal(20,6) NOT NULL COMMENT '触发时的实际值', `message` text COMMENT '告警消息内容', `notified` tinyint(1) DEFAULT 0 COMMENT '是否已通知', `notified_at` datetime DEFAULT NULL, `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_rule_id` (`rule_id`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
核心代码实现
告警引擎类(AlertEngine.php)
<?php
namespace App\Service;
use App\Model\AlertRule;
use App\Model\AlertEvent;
use App\Notification\NotificationFactory;
class AlertEngine
{
private $thresholdCounter = []; // 存储连续满足条件次数
/**
* 处理新的监控数据点
* @param string $metric 指标名称
* @param float $value 当前值
* @param int $userId 数据所属用户(可选,如果不传则遍历所有规则)
*/
public function processDataPoint(string $metric, float $value, ?int $userId = null): void
{
// 1. 查询所有匹配该指标且启用的规则
$rules = AlertRule::query()
->where('metric', $metric)
->where('enabled', 1);
if ($userId !== null) {
$rules->where('user_id', $userId);
}
// 2. 逐条规则判断
foreach ($rules->cursor() as $rule) {
$this->evaluateRule($rule, $value);
}
}
/**
* 评估单条规则
*/
private function evaluateRule(AlertRule $rule, float $currentValue): void
{
// 对比运算
$triggered = false;
switch ($rule->operator) {
case '>':
$triggered = $currentValue > $rule->threshold;
break;
case '<':
$triggered = $currentValue < $rule->threshold;
break;
case '>=':
$triggered = $currentValue >= $rule->threshold;
break;
case '<=':
$triggered = $currentValue <= $rule->threshold;
break;
case '==':
$triggered = abs($currentValue - $rule->threshold) < 0.000001; // 浮点数比较
break;
case '!=':
$triggered = abs($currentValue - $rule->threshold) >= 0.000001;
break;
}
// 取连续触发计数器(以规则ID为key)
$key = $rule->id;
if (!isset($this->thresholdCounter[$key])) {
$this->thresholdCounter[$key] = 0;
}
if ($triggered) {
$this->thresholdCounter[$key]++;
// 连续触发达到设定次数
if ($this->thresholdCounter[$key] >= $rule->duration) {
$this->sendAlert($rule, $currentValue);
// 重置计数器,避免重复告警(可按需设计重试间隔)
$this->thresholdCounter[$key] = 0;
}
} else {
// 条件不满足则重置计数器
$this->thresholdCounter[$key] = 0;
}
}
/**
* 执行告警通知
*/
private function sendAlert(AlertRule $rule, float $value): void
{
// 1. 记录告警事件
$event = AlertEvent::create([
'rule_id' => $rule->id,
'user_id' => $rule->user_id,
'metric' => $rule->metric,
'triggered_value'=> $value,
'message' => "{$rule->rule_name} 触发告警: {$rule->metric} = {$value} {$rule->operator} {$rule->threshold}"
]);
// 2. 发送通知
$methods = json_decode($rule->notification_method, true) ?? [];
$targets = json_decode($rule->notification_target, true) ?? [];
foreach ($methods as $index => $method) {
$target = $targets[$index] ?? '';
if (empty($target)) continue;
$notifier = NotificationFactory::create($method);
$notifier->send(
$target,
$event->message,
[
'rule' => $rule->toArray(),
'triggered_value' => $value
]
);
}
// 3. 标记事件已通知
$event->update(['notified' => 1, 'notified_at' => now()]);
}
}
通知工厂(NotificationFactory.php)
<?php
namespace App\Notification;
class NotificationFactory
{
public static function create(string $type): NotificationInterface
{
switch ($type) {
case 'email':
return app(EmailNotifier::class);
case 'sms':
return app(SmsNotifier::class);
case 'dingtalk':
return app(DingTalkNotifier::class);
case 'webhook':
return app(WebhookNotifier::class);
default:
throw new \InvalidArgumentException("不支持的告警方式: {$type}");
}
}
}
// 通知接口
interface NotificationInterface
{
public function send(string $target, string $message, array $context = []): bool;
}
一个纯PHP的Webhook通知示例
<?php
namespace App\Notification;
class WebhookNotifier implements NotificationInterface
{
public function send(string $target, string $message, array $context = []): bool
{
$payload = [
'msgtype' => 'text',
'text' => [
'content' => $message
],
'context' => $context
];
$ch = curl_init($target);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $httpCode === 200;
}
}
数据采集入口(可放在队列Job或定时任务中)
<?php
// 示例:每天定时检查数据库里的订单异常、或从Redis接收实时指标
use App\Service\AlertEngine;
// 在控制器里或队列消费者里调用
$engine = new AlertEngine();
// 场景1:推送实时数据点(例如从MQTT、Redis订阅接收)
$engine->processDataPoint('cpu_usage', 85.5, userId: 123);
// 场景2:批量处理(从数据库统计)
$metrics = [
['metric' => 'login_fail_count', 'value' => 10, 'user_id' => 456],
['metric' => 'disk_usage', 'value' => 92.3, 'user_id' => 789],
];
foreach ($metrics as $point) {
$engine->processDataPoint($point['metric'], $point['value'], $point['user_id']);
}
用户端配置页面(简单示例)
使用Bootstrap + axios 实现一个快速规则配置表单:
<form id="alertRuleForm">
<div class="form-group">
<label>规则名称</label>
<input type="text" name="rule_name" class="form-control" required>
</div>
<div class="form-group">
<label>监控指标</label>
<select name="metric" class="form-control">
<option value="cpu_usage">CPU使用率 (%)</option>
<option value="memory_usage">内存使用率 (%)</option>
<option value="api_error_rate">API错误率 (%)</option>
<option value="daily_order_amount">日订单金额</option>
</select>
</div>
<div class="form-group row">
<div class="col-4">
<label>操作符</label>
<select name="operator" class="form-control">
<option value=">">大于 ></option>
<option value="<">小于 <</option>
<option value=">=">大于等于 >=</option>
<option value="<=">小于等于 <=</option>
</select>
</div>
<div class="col-4">
<label>阈值</label>
<input type="number" name="threshold" step="0.01" class="form-control">
</div>
<div class="col-4">
<label>持续次数</label>
<input type="number" name="duration" min="0" class="form-control" value="1">
</div>
</div>
<div class="form-group">
<label>通知方式</label>
<div>
<label class="checkbox-inline"><input type="checkbox" name="methods[]" value="email"> 邮件</label>
<label class="checkbox-inline"><input type="checkbox" name="methods[]" value="dingtalk"> 钉钉</label>
<label class="checkbox-inline"><input type="checkbox" name="methods[]" value="webhook"> Webhook</label>
</div>
</div>
<div class="form-group">
<label>通知目标(根据方式填写邮箱/钉钉Token/Webhook URL)</label>
<textarea name="targets" class="form-control" rows="2"
placeholder='如: {"email":"admin@example.com", "webhook":"https://hook.xxx/abc"}'></textarea>
</div>
<button type="submit" class="btn btn-primary">保存规则</button>
</form>
<script>
document.getElementById('alertRuleForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const data = {
rule_name: formData.get('rule_name'),
metric: formData.get('metric'),
operator: formData.get('operator'),
threshold: parseFloat(formData.get('threshold')),
duration: parseInt(formData.get('duration')),
notification_method: formData.getAll('methods[]'),
notification_target: JSON.parse(formData.get('targets') || '{}'),
enabled: 1
};
// 调后端API保存
axios.post('/api/alert-rules', data).then(res => {
alert('保存成功');
});
});
</script>
扩展建议
-
防重复告警:
- 记录每条规则最后一次告警时间,加上最小告警间隔(如 5 分钟)。
- 或者使用
alert_events表判断同规则 5 分钟内是否已触发。
-
批量处理:
- 建议使用队列(如 Redis + Laravel Queue)异步处理通知,避免阻塞主流程。
- 使用
chunk分页加载规则,防止规则过多内存溢出。
-
聚合告警:
- 支持多个条件组合(AND/OR),需要将条件存储在子表中。
- 支持 “一次告警” / “恢复告警” 两种状态。
-
安全考虑:
- 用户输入的 Webhook URL 应该做白名单或限制协议(仅 HTTPS)。
- 对用户输入的所有字段做权限校验(防止越权创建他人规则)。
-
监控同步:
如果数据点来自外部系统(Prometheus、CloudWatch等),可以开发适配器,将外部指标推送到该告警引擎。
如何集成到现有项目
| 步骤 | 操作 |
|---|---|
| 1 | 执行SQL创建上述两张表 |
| 2 | 复制 AlertEngine.php、NotificationFactory.php、各通知实现类到项目中 |
| 3 | 编写用户管理告警规则的 CRUD API和前端页面 |
| 4 | 在数据采集或定时任务中调用 $engine->processDataPoint(...) |
| 5 | 定期清理过期告警事件(可选) |
这套方案覆盖了80%的Web项目自定义告警需求,你可以根据你的监控数据的频率(实时 / 每分钟 / 每天)选择同步推送或定时批量执行。