PHP项目怎么实现用户自定义告警?

wen java案例 1

本文目录导读:

PHP项目怎么实现用户自定义告警?

  1. 整体架构设计
  2. 数据库表结构设计(MySQL)
  3. 核心代码实现
  4. 用户端配置页面(简单示例)
  5. 扩展建议
  6. 如何集成到现有项目

实现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>

扩展建议

  1. 防重复告警

    • 记录每条规则最后一次告警时间,加上最小告警间隔(如 5 分钟)。
    • 或者使用 alert_events 表判断同规则 5 分钟内是否已触发。
  2. 批量处理

    • 建议使用队列(如 Redis + Laravel Queue)异步处理通知,避免阻塞主流程。
    • 使用 chunk 分页加载规则,防止规则过多内存溢出。
  3. 聚合告警

    • 支持多个条件组合(AND/OR),需要将条件存储在子表中。
    • 支持 “一次告警” / “恢复告警” 两种状态。
  4. 安全考虑

    • 用户输入的 Webhook URL 应该做白名单或限制协议(仅 HTTPS)。
    • 对用户输入的所有字段做权限校验(防止越权创建他人规则)。
  5. 监控同步

    如果数据点来自外部系统(Prometheus、CloudWatch等),可以开发适配器,将外部指标推送到该告警引擎。


如何集成到现有项目

步骤 操作
1 执行SQL创建上述两张表
2 复制 AlertEngine.phpNotificationFactory.php、各通知实现类到项目中
3 编写用户管理告警规则的 CRUD API和前端页面
4 在数据采集或定时任务中调用 $engine->processDataPoint(...)
5 定期清理过期告警事件(可选)

这套方案覆盖了80%的Web项目自定义告警需求,你可以根据你的监控数据的频率(实时 / 每分钟 / 每天)选择同步推送或定时批量执行。

抱歉,评论功能暂时关闭!