Skip to content

第二十三章 二次开发与功能扩展

作者

谭策 — 独立开发者 | AIOps 领域探索者

IT Online 微信公众号

许可证

MPL-2.0 © 谭策

本章导读

ITOps Agent Platform 采用模块化架构设计,前后端分离、路由与业务逻辑分层、数据库迁移体系完善,为二次开发提供了良好的基础。本章将以实际操作为导向,手把手教你如何在现有代码基础上添加新功能,涵盖从前端页面到后端接口、从数据库表到自定义 Agent 的完整开发流程。

学习目标

  • 掌握前端新增页面的完整流程(路由 + 组件 + API 调用)
  • 掌握后端新增 API 端点的完整流程(路由 + Service + 数据库操作)
  • 掌握数据库表的新增与迁移机制
  • 学会创建自定义 Agent 和自定义工作流
  • 学会扩展通知渠道和 LLM Provider
  • 学会添加告警源适配器
  • 理解项目的扩展点与插件化设计思路

核心内容

23.1 添加前端新页面

以添加一个 "系统状态监控" 页面为例。

步骤 1:创建页面组件

typescript
// frontend/src/pages/SystemStatus.tsx
import { useQuery } from '@tanstack/react-query';
import { Activity, Server, Database, Shield } from 'lucide-react';
import api from '../lib/api';

interface HealthStatus {
  status: string;
  uptime: number;
  memory: NodeJS.MemoryUsage;
  database: string;
}

export default function SystemStatus() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['systemStatus'],
    queryFn: () => api.get('/api/health/summary').then(r => r.data.data),
    refetchInterval: 30000,
  });

  if (isLoading) return <div>加载中...</div>;
  if (error) return <div>加载失败</div>;

  return (
    <div className="p-6">
      <h1 className="text-2xl font-bold mb-6">系统状态监控</h1>

      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
        <StatusCard
          icon={<Activity />}
          title="系统状态"
          value={data.status}
          color={data.status === 'healthy' ? 'green' : 'red'}
        />
        <StatusCard
          icon={<Server />}
          title="运行时间"
          value={formatUptime(data.uptime)}
          color="blue"
        />
        <StatusCard
          icon={<Database />}
          title="数据库"
          value={data.database}
          color={data.database === 'connected' ? 'green' : 'red'}
        />
        <StatusCard
          icon={<Shield />}
          title="内存使用"
          value={formatBytes(data.memory.rss)}
          color="purple"
        />
      </div>
    </div>
  );
}

function StatusCard({ icon, title, value, color }: any) {
  const colorMap: Record<string, string> = {
    green: 'text-green-500',
    red: 'text-red-500',
    blue: 'text-blue-500',
    purple: 'text-purple-500',
  };

  return (
    <div className="bg-card rounded-lg p-4 border border-border">
      <div className={`w-8 h-8 mb-2 ${colorMap[color]}`}>{icon}</div>
      <p className="text-sm text-text-secondary">{title}</p>
      <p className="text-xl font-semibold text-text-primary">{value}</p>
    </div>
  );
}

function formatUptime(seconds: number): string {
  const days = Math.floor(seconds / 86400);
  const hours = Math.floor((seconds % 86400) / 3600);
  return `${days}天 ${hours}小时`;
}

function formatBytes(bytes: number): string {
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

步骤 2:添加路由

typescript
// frontend/src/App.tsx — 在现有路由中添加
import SystemStatus from './pages/SystemStatus';

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <BrowserRouter>
        <AuthProvider>
          <ErrorBoundary>
            <Routes>
              <Route path="/login" element={<Login />} />
              <Route element={<ProtectedRoute />}>
                <Route element={<Layout />}>
                  <Route path="/dashboard" element={<Dashboard />} />
                  <Route path="/system-status" element={<SystemStatus />} />  {/* 新增 */}
                  {/* ... 其他路由 ... */}
                </Route>
              </Route>
            </Routes>
          </ErrorBoundary>
        </AuthProvider>
      </BrowserRouter>
    </QueryClientProvider>
  );
}

步骤 3:添加到导航菜单

typescript
// frontend/src/components/layout/Layout.tsx — 在 navigation 数组中添加
import { Activity } from 'lucide-react';

const navigation = [
  { name: '仪表盘', href: '/dashboard', icon: LayoutDashboard },
  { name: '系统状态', href: '/system-status', icon: Activity },  // 新增
  { name: '服务器管理', href: '/servers', icon: Server },
  // ...
];

前端页面开发流程总结:

1. 创建页面组件 (src/pages/xxx.tsx)

2. 添加路由 (App.tsx)

3. 添加到导航菜单 (Layout.tsx)

4. 调用后端 API (api.get/post/...)

5. 使用 React Query 管理数据状态

6. 测试页面功能

23.2 添加后端 API 端点

以添加一个 "系统信息" 接口为例。

步骤 1:创建路由文件

typescript
// backend/src/routes/systemStatusRoutes.ts
import { Router, Request, Response } from 'express';
import { requireRole } from '../middleware/auth';
import { db } from '../models/database';
import { logger } from '../utils/logger';

const router = Router();

// 获取系统信息
router.get('/info', requireRole('admin'), async (_req: Request, res: Response) => {
  try {
    const memUsage = process.memoryUsage();
    const uptime = process.uptime();

    res.json({
      success: true,
      data: {
        nodeVersion: process.version,
        platform: process.platform,
        pid: process.pid,
        uptime,
        memory: {
          rss: memUsage.rss,
          heapUsed: memUsage.heapUsed,
          heapTotal: memUsage.heapTotal,
        },
        timestamp: new Date().toISOString(),
      },
    });
  } catch (error) {
    logger.error('获取系统信息失败', error as Error);
    res.status(500).json({ success: false, error: '获取系统信息失败' });
  }
});

// 获取数据库统计
router.get('/database-stats', requireRole('admin'), async (_req: Request, res: Response) => {
  try {
    const stats = db.prepare(`
      SELECT name,
        (SELECT COUNT(*) FROM sqlite_master WHERE type='table') as tableCount
    `).get();

    const tables = db.prepare(
      "SELECT name, (SELECT COUNT(*) FROM sqlite_master WHERE name = ?) as existsFlag FROM sqlite_master WHERE type='table'"
    ).all();

    const tableSizes: Array<{ name: string; count: number }> = [];
    for (const t of tables as Array<{ name: string }>) {
      const count = db.prepare(`SELECT COUNT(*) as count FROM ${t.name}`).get() as { count: number };
      tableSizes.push({ name: t.name, count: count.count });
    }

    res.json({ success: true, data: { tables: tableSizes } });
  } catch (error) {
    logger.error('获取数据库统计失败', error as Error);
    res.status(500).json({ success: false, error: '获取数据库统计失败' });
  }
});

export default router;

步骤 2:在 app.ts 中注册路由

typescript
// backend/src/app.ts — 添加 import 和路由注册
import systemStatusRoutes from './routes/systemStatusRoutes';

// 在 authenticateToken 之后添加
app.use(authenticateToken);

app.use('/api/system-status', rateLimiter, systemStatusRoutes);  // 新增

后端 API 开发流程总结:

1. 创建路由文件 (src/routes/xxxRoutes.ts)

2. 定义路由处理函数 (router.get/post/put/delete)

3. 添加权限控制 (requireRole)

4. 实现业务逻辑 (Service 层或直接 db 操作)

5. 错误处理 (try-catch + logger)

6. 在 app.ts 注册路由

7. 测试 API (curl 或 Postman)

23.3 添加数据库表与迁移

方式一:在 initializeDatabase 中直接创建(新项目或开发环境)

typescript
// backend/src/models/database.ts — 在 initializeDatabase 函数中添加
export function initializeDatabase() {
  db.exec(`
    -- ... 现有表定义 ...

    -- 新增: 自定义监控指标表
    CREATE TABLE IF NOT EXISTS custom_metrics (
      id TEXT PRIMARY KEY,
      name TEXT NOT NULL,
      metric_type TEXT NOT NULL,         -- 'gauge', 'counter', 'histogram'
      value REAL NOT NULL,
      labels TEXT,                        -- JSON 格式的标签
      source TEXT,                        -- 数据来源
      collected_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    );

    CREATE INDEX IF NOT EXISTS idx_metrics_name ON custom_metrics(name);
    CREATE INDEX IF NOT EXISTS idx_metrics_collected ON custom_metrics(collected_at);
    CREATE INDEX IF NOT EXISTS idx_metrics_source ON custom_metrics(source);
  `);

  runMigrations();
  // ...
}

方式二:通过迁移系统(生产环境推荐)

typescript
// backend/src/models/migrations.ts
interface Migration {
  id: string;
  up: (db: Database.Database) => void;
}

const migrations: Migration[] = [
  // ... 已有迁移 ...
  {
    id: '2024-01-15-add-custom-metrics',
    up: (db) => {
      db.exec(`
        CREATE TABLE IF NOT EXISTS custom_metrics (
          id TEXT PRIMARY KEY,
          name TEXT NOT NULL,
          metric_type TEXT NOT NULL,
          value REAL NOT NULL,
          labels TEXT,
          source TEXT,
          collected_at DATETIME DEFAULT CURRENT_TIMESTAMP,
          created_at DATETIME DEFAULT CURRENT_TIMESTAMP
        );
        CREATE INDEX IF NOT EXISTS idx_metrics_name ON custom_metrics(name);
        CREATE INDEX IF NOT EXISTS idx_metrics_collected ON custom_metrics(collected_at);
      `);
    },
  },
];

export function runMigrations(): void {
  db.exec(`
    CREATE TABLE IF NOT EXISTS migrations (
      id TEXT PRIMARY KEY,
      applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
    );
  `);

  for (const migration of migrations) {
    const existing = db.prepare('SELECT 1 FROM migrations WHERE id = ?').get(migration.id);
    if (!existing) {
      logger.info(`Applying migration: ${migration.id}`);
      migration.up(db);
      db.prepare('INSERT INTO migrations (id) VALUES (?)').run(migration.id);
    }
  }
}

数据库迁移流程:

  应用启动


  initializeDatabase()

      ├── 创建/更新所有表 (CREATE TABLE IF NOT EXISTS)

      └── runMigrations()

              ├── 创建 migrations 跟踪表

              └── 遍历迁移列表

                      ├── 已执行 → 跳过

                      └── 未执行 → 执行 up() + 记录到 migrations 表

23.4 创建自定义 Agent

项目已内置多个预设 Agent(如告警分析 Agent、合规检查 Agent)。创建自定义 Agent 有两种方式:通过 UI 界面或通过 API 直接创建。

通过 API 创建自定义 Agent:

typescript
// 调用现有 API 接口
POST /api/agents

// 请求体
{
  "name": "日志分析 Agent",
  "avatar": "📊",
  "role": "日志分析专家",
  "category": "日志分析",
  "description": "分析系统日志,识别异常模式和潜在问题",
  "system_prompt": "你是一个专业的日志分析专家。你的任务是:\n1. 解析给定的日志内容\n2. 识别错误、警告和异常模式\n3. 分析可能的根本原因\n4. 提供处理建议\n\n请按照以下格式输出:\n## 日志摘要\n## 发现的问题\n## 根本原因分析\n## 处理建议",
  "model": "doubao-4o",
  "temperature": 0.5,
  "enabled": 1
}

Agent 执行流程:

用户输入 Prompt


┌──────────────────────────────────┐
│  Agent 执行引擎                   │
│                                  │
│  1. 加载 Agent 配置               │
│     - system_prompt              │
│     - model                      │
│     - temperature                │
│                                  │
│  2. 构建 LLM 请求                 │
│     - system: system_prompt      │
│     - user: 用户输入              │
│     - 参数: temperature, etc.     │
│                                  │
│  3. 调用 LLM API                 │
│     - Doubao / OpenAI / LocalAI  │
│                                  │
│  4. 解析 LLM 响应                 │
│                                  │
│  5. 记录执行结果                  │
│     - agent_executions 表        │
│     - 执行时间、Token 数          │
│                                  │
│  6. 返回结果                     │
└──────────────────────────────────┘


输出 Markdown 格式的分析结果

自定义 Agent 最佳实践:

要点说明示例
System Prompt明确角色、任务、输出格式"你是一个...你的任务是...请按以下格式输出..."
Temperature控制创造性:低=精确,高=灵活分析类 0.3-0.5,创作类 0.7-0.9
Model 选择根据任务复杂度选择模型简单任务用小模型,复杂分析用大模型
Category合理分类,方便用户查找"日志分析"、"安全审计"、"性能诊断"

23.5 创建自定义工作流

工作流(Workflow)由节点(Nodes)和边(Edges)组成,支持多个 Agent 协作执行复杂任务。

工作流节点类型:

节点类型说明适用场景
Agent 节点调用 LLM Agent 执行任务分析、生成、判断
条件节点根据条件分支执行if-else 逻辑
并行节点多个分支同时执行并发检查
服务器命令节点在服务器上执行命令巡检、修复操作

通过 API 创建自定义工作流:

typescript
POST /api/workflows

{
  "name": "服务器异常诊断工作流",
  "description": "当服务器出现异常时,自动执行诊断流程",
  "nodes": [
    {
      "id": "node-1",
      "type": "agent",
      "agentId": "告警分析 Agent ID",
      "label": "分析告警内容",
      "position": { "x": 100, "y": 100 }
    },
    {
      "id": "node-2",
      "type": "server-command",
      "command": "top -bn1 | head -20",
      "label": "检查 CPU 使用率",
      "position": { "x": 400, "y": 100 }
    },
    {
      "id": "node-3",
      "type": "condition",
      "condition": "cpu_usage > 90",
      "label": "CPU 是否过载?",
      "position": { "x": 700, "y": 100 }
    },
    {
      "id": "node-4",
      "type": "agent",
      "agentId": "修复建议 Agent ID",
      "label": "生成修复建议",
      "position": { "x": 1000, "y": 100 }
    }
  ],
  "edges": [
    { "from": "node-1", "to": "node-2" },
    { "from": "node-2", "to": "node-3" },
    { "from": "node-3", "to": "node-4", "condition": "true" }
  ]
}

工作流执行流程:

触发工作流


┌────────────────────────────────────┐
│  Task 引擎                          │
│                                    │
│  1. 加载工作流定义 (nodes + edges)  │
│                                    │
│  2. 按拓扑排序确定执行顺序          │
│                                    │
│  3. 逐个执行节点:                  │
│     ├── Agent 节点 → 调用 LLM API  │
│     ├── 命令节点 → SSH 执行命令     │
│     ├── 条件节点 → 评估条件表达式   │
│     └── 并行节点 → 并发执行分支     │
│                                    │
│  4. 节点间通过 context 传递数据     │
│                                    │
│  5. 实时更新任务状态 (tasks 表)     │
│                                    │
│  6. 通过 WebSocket 推送进度         │
│                                    │
│  7. 生成执行报告                    │
└────────────────────────────────────┘


任务完成/失败

23.6 添加通知渠道

项目已支持 Webhook、邮件、企业微信、钉钉等通知渠道。添加新的通知渠道需要以下步骤:

步骤 1:扩展通知配置

typescript
// backend/src/services/notificationService.ts
// 在通知服务中添加新的渠道

interface NotificationConfig {
  // 现有渠道
  webhook_enabled?: boolean;
  email_enabled?: boolean;
  wechat_enabled?: boolean;
  dingtalk_enabled?: boolean;
  // 新增:飞书
  feishu_enabled?: boolean;
  feishu_config?: {
    webhook_url: string;
    secret?: string;
  };
}

步骤 2:实现通知发送逻辑

typescript
// backend/src/services/notificationService.ts
// 在 sendNotification 方法中添加飞书渠道

async sendFeishuNotification(
  config: { webhook_url: string; secret?: string },
  title: string,
  content: string
): Promise<void> {
  const payload = {
    msg_type: 'interactive',
    card: {
      header: {
        title: { tag: 'plain_text', content: title },
      },
      elements: [
        {
          tag: 'markdown',
          content: content,
        },
      ],
    },
  };

  if (config.secret) {
    // 添加签名验证
    const timestamp = Date.now();
    const sign = this.generateFeishuSign(timestamp, config.secret);
    (payload as any).timestamp = timestamp;
    (payload as any).sign = sign;
  }

  const response = await fetch(config.webhook_url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });

  if (!response.ok) {
    throw new Error(`飞书通知发送失败: ${response.status}`);
  }
}

步骤 3:更新数据库和 API

typescript
// database.ts - 更新 notification_config 表
CREATE TABLE IF NOT EXISTS notification_config (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  webhook_enabled INTEGER DEFAULT 1,
  feishu_enabled INTEGER DEFAULT 0,           -- 新增
  feishu_config TEXT,                          -- 新增,JSON 格式
  -- ... 其他现有字段 ...
);

通知渠道架构:

┌──────────────────────────────────────┐
│       NotificationService            │
│                                      │
│  sendNotification(notification)      │
│         │                            │
│    ┌────┼────┬────┬────┐            │
│    ▼    ▼    ▼    ▼    ▼            │
│ Webhook 邮件 企微  钉钉  飞书(新)    │
│    │    │    │    │    │            │
│    ▼    ▼    ▼    ▼    ▼            │
│ HTTP  SMTP API  API  API            │
└──────────────────────────────────────┘

23.7 集成新的 LLM Provider

项目已支持 Doubao、OpenAI 和本地 AI(Ollama 等)三种 Provider。集成新的 LLM Provider(如 Anthropic Claude)的步骤:

步骤 1:扩展环境变量配置

typescript
// backend/src/utils/env.ts
interface EnvConfig {
  // 新增 Claude 配置
  ANTHROPIC_API_KEY?: string;
  ANTHROPIC_API_BASE?: string;
  ANTHROPIC_MODEL?: string;
  // ...
}

// 在 validateEnv 函数中读取
const env = validateEnv();
// ...
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
ANTHROPIC_API_BASE: process.env.ANTHROPIC_API_BASE || 'https://api.anthropic.com/v1',
ANTHROPIC_MODEL: process.env.ANTHROPIC_MODEL || 'claude-3-sonnet-20240229',

步骤 2:在 LLM 服务中添加 Provider

typescript
// backend/src/services/llmService.ts

interface LLMProviderConfig {
  providerName: string;
  apiKeySetting: string;
  apiKeyEnv: string;
  apiBaseSetting: string;
  apiBaseEnv: string;
  defaultApiBase: string;
  modelSetting: string;
  modelEnv: string;
  defaultModel: string;
  placeholderKey: string;
}

// 新增 Anthropic 配置
const ANTHROPIC_CONFIG: LLMProviderConfig = {
  providerName: 'Anthropic',
  apiKeySetting: 'ANTHROPIC_API_KEY',
  apiKeyEnv: 'ANTHROPIC_API_KEY',
  apiBaseSetting: 'ANTHROPIC_API_BASE',
  apiBaseEnv: 'ANTHROPIC_API_BASE',
  defaultApiBase: 'https://api.anthropic.com/v1',
  modelSetting: 'ANTHROPIC_MODEL',
  modelEnv: 'ANTHROPIC_MODEL',
  defaultModel: 'claude-3-sonnet-20240229',
  placeholderKey: 'your-anthropic-api-key-here'
};

// 在 sendMessage 函数中添加 Anthropic 分支
async function sendMessage(provider: string, messages: Array<{role: string, content: string}>, options: any): Promise<string> {
  switch (provider) {
    case 'doubao':
      return callOpenAICompatible(DOUBAO_CONFIG, messages, options);
    case 'openai':
      return callOpenAICompatible(OPENAI_CONFIG, messages, options);
    case 'local':
      return callOpenAICompatible(LOCAL_AI_CONFIG, messages, options);
    case 'anthropic':
      return callAnthropic(messages, options);  // 新增
    default:
      throw new Error(`Unsupported provider: ${provider}`);
  }
}

// 实现 Anthropic API 调用
async function callAnthropic(messages: Array<{role: string, content: string}>, options: any): Promise<string> {
  const apiKey = env.ANTHROPIC_API_KEY;
  const apiBase = env.ANTHROPIC_API_BASE;
  const model = env.ANTHROPIC_MODEL;

  // Anthropic 使用不同的消息格式
  const systemMessage = messages.find(m => m.role === 'system');
  const userMessages = messages.filter(m => m.role !== 'system');

  const response = await fetch(`${apiBase}/messages`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': apiKey,
      'anthropic-version': '2023-06-01',
    },
    body: JSON.stringify({
      model,
      system: systemMessage?.content,
      messages: userMessages,
      max_tokens: options.maxTokens || 4096,
      temperature: options.temperature || 0.7,
    }),
  });

  if (!response.ok) {
    throw new Error(`Anthropic API error: ${response.status}`);
  }

  const data = await response.json();
  return data.content[0].text;
}

LLM Provider 扩展架构:

┌─────────────────────────────────────────────┐
│            LLM Service                      │
│                                             │
│  sendMessage(provider, messages, options)   │
│         │                                   │
│    ┌────┼────┬────┬──────────┐             │
│    ▼    ▼    ▼    ▼          ▼             │
│ Doubao OpenAI LocalAI Anthropic(新)         │
│    │    │    │    │                         │
│    ▼    ▼    ▼    ▼                         │
│ OpenAI OpenAI OpenAI Anthropic              │
│ Compatible Compatible Compatible API        │
└─────────────────────────────────────────────┘

步骤 3:在设置页面添加配置选项

typescript
// frontend/src/pages/Settings.tsx — 在 LLM 配置区域添加
<div className="space-y-4">
  <h4 className="font-medium">Anthropic Claude</h4>
  <div>
    <label>API Key</label>
    <input type="password" value={anthropicApiKey} onChange={...} />
  </div>
  <div>
    <label>API Base URL</label>
    <input value={anthropicApiBase} onChange={...} />
  </div>
  <div>
    <label>Model</label>
    <select value={anthropicModel} onChange={...}>
      <option value="claude-3-sonnet-20240229">Claude 3 Sonnet</option>
      <option value="claude-3-opus-20240229">Claude 3 Opus</option>
      <option value="claude-3-haiku-20240307">Claude 3 Haiku</option>
    </select>
  </div>
</div>

23.8 添加告警源适配器

项目已支持 Zabbix、Prometheus 等告警源通过 Webhook 推送告警。添加新的告警源(如 Grafana Alerting):

步骤 1:扩展 Webhook 路由

typescript
// backend/src/routes/webhookRoutes.ts

// Grafana Webhook
router.post('/grafana', async (req, res) => {
  // 验证签名
  if (!verifyWebhookSignature(req, 'grafana')) {
    return res.status(401).json({ success: false, error: 'Invalid signature' });
  }

  try {
    const grafanaAlert = req.body;

    // 转换为平台内部告警格式
    const alert = {
      source: 'grafana',
      severity: mapGrafanaSeverity(grafanaAlert.status),
      title: grafanaAlert.labels?.alertname || grafanaAlert.message,
      content: grafanaAlert.annotations?.description || '',
      metadata: JSON.stringify({
        instance: grafanaAlert.labels?.instance,
        job: grafanaAlert.labels?.job,
        grafanaUrl: grafanaAlert.ruleUrl,
        ...grafanaAlert.labels,
      }),
    };

    // 插入告警表
    db.prepare(`
      INSERT INTO alerts (id, source, severity, title, content, metadata, status)
      VALUES (?, ?, ?, ?, ?, ?, 'new')
    `).run(
      randomUUID(),
      alert.source,
      alert.severity,
      alert.title,
      alert.content,
      alert.metadata
    );

    // 触发告警处理服务
    alertService.processNewAlert(alert);

    res.json({ success: true });
  } catch (error) {
    logger.error('处理 Grafana 告警失败', error as Error);
    res.status(500).json({ success: false, error: '处理失败' });
  }
});

function mapGrafanaSeverity(status: string): string {
  switch (status) {
    case 'Alerting': return 'critical';
    case 'Resolved': return 'info';
    case 'Pending': return 'warning';
    default: return 'warning';
  }
}

步骤 2:添加告警源配置

typescript
// 在告警工作流映射中添加 Grafana 源
POST /api/alert-mappings

{
  "alert_source": "grafana",
  "alert_severity": "critical",
  "alert_title_pattern": ".*CPU.*",
  "workflow_id": "CPU异常处理工作流ID",
  "enabled": true
}

告警源适配器架构:

外部告警源                    平台内部
┌──────────┐
│ Zabbix   │ ─── Webhook ───▶ /api/webhooks/zabbix
│          │                  │
│ Prometheus│── Webhook ───▶ /api/webhooks/prometheus
│          │                  │
│ Grafana  │ ─── Webhook ───▶ /api/webhooks/grafana (新)
│          │                  │
│ Custom   │ ─── Webhook ───▶ /api/webhooks/custom
└──────────┘                  │

                    ┌─────────────────┐
                    │ 告警格式转换器    │
                    │ → 统一告警格式   │
                    └────────┬────────┘


                    ┌─────────────────┐
                    │ AlertService    │
                    │ - 插入 alerts 表 │
                    │ - 匹配工作流映射 │
                    │ - 触发自动处理   │
                    └─────────────────┘

本章小结

本章以实际操作为导向,系统讲解了 ITOps Agent Platform 的二次开发流程:

  1. 前端扩展:页面组件 → 路由注册 → 导航菜单 → API 调用
  2. 后端扩展:路由文件 → 业务逻辑 → 权限控制 → app.ts 注册
  3. 数据库扩展:initializeDatabase 直接创建 + 迁移系统
  4. Agent 扩展:通过 API 创建自定义 Agent,配置 System Prompt
  5. 工作流扩展:节点 + 边定义,多 Agent 协作
  6. 通知渠道扩展:配置 → 实现 → 数据库 → API
  7. LLM Provider 扩展:环境变量 → 服务实现 → 设置页面
  8. 告警源扩展:Webhook 路由 → 格式转换 → 告警服务

掌握这些扩展技能,你将能够根据实际业务需求,灵活地定制和增强平台功能。

本章练习

基础练习

  1. 添加前端页面:创建一个 "脚本库" 详情页面,展示单个脚本的内容、执行历史和关联的服务器。使用 React Query 获取数据,Markdown 组件渲染脚本内容。

  2. 添加后端 API:实现一个 GET /api/servers/:id/metrics 接口,返回指定服务器的 CPU、内存、磁盘使用率的最近 24 小时数据(模拟数据即可)。

  3. 创建自定义 Agent:通过 API 创建一个 "SQL 审核 Agent",System Prompt 要求它检查 SQL 语句的性能问题、安全风险,并给出优化建议。测试该 Agent 的执行效果。

进阶练习

  1. 实现告警降噪规则:添加一个告警降噪功能,当同一告警在 1 小时内出现超过 10 次时,自动合并为一条告警并标记为 "噪声告警"。实现对应的数据库表、API 和前端展示。

  2. 集成第三方 LLM:选择一个当前项目未支持的 LLM Provider(如 Google Gemini、Mistral 等),完成从环境变量配置到 API 调用的完整集成。

  3. 实现定时任务持久化:当前调度器(schedulerService)的定时任务仅保存在内存中。将其改造为基于数据库的持久化调度,支持服务重启后任务不丢失。

思考题

  1. 项目的路由注册方式是硬编码在 app.ts 中的(逐个 import + app.use())。在大型项目中,这种方式的维护成本会越来越高。请设计一种自动注册路由的方案(如基于目录扫描或装饰器模式),并分析其优缺点。

  2. 当前 LLM Provider 的集成方式是 Switch-Case 分支。如果要实现 "插件化" 的 LLM Provider 架构(支持热插拔、动态加载),应该如何设计?请画出架构图并说明关键技术点。

延伸阅读

基于 MPL-2.0 许可证发布