<!-- title: Claude Code事件驱动架构：队列到异步Worker -->

# Claude Code事件驱动架构实战：消息队列到异步Worker全流程

**学习目标**：掌握使用Claude Code构建事件驱动架构（EDA）的完整流程，涵盖Redis消息队列、BullMQ任务系统、事件总线设计、异步Worker模式，以及生产级监控面板。读完本教程，你能独立用Claude Code搭建一个高可用的异步任务处理平台。

**适用人群**：有Node.js/TypeScript基础、希望用AI辅助构建分布式后端系统的中高级开发者。

---

## 一、前置条件

| 工具 | 版本要求 | 用途 |
|------|----------|------|
| Claude Code | 最新版 (≥1.0) | AI编程助手 |
| Node.js | ≥20.x | 运行时环境 |
| Redis | ≥7.0 | 消息队列与缓存 |
| Docker | ≥24.x | 容器化Redis |
| TypeScript | ≥5.3 | 类型安全 |

**确保已安装Claude Code并完成认证**：

```bash
claude --version
# 输出类似: 1.x.x
```

**启动Redis服务**：

```bash
docker run -d --name redis-eda \
  -p 6379:6379 \
  -v redis-data:/data \
  redis:7-alpine redis-server --appendonly yes
```

---

## 二、事件驱动架构核心概念

### 2.1 为什么需要事件驱动？

传统请求-响应模型在以下场景力不从心：

- **耗时操作**：视频转码、批量邮件发送、报表生成
- **系统解耦**：订单服务不应直接依赖通知服务
- **流量削峰**：秒杀场景下不能让数据库直接扛峰值写入
- **数据一致性**：跨服务的最终一致性需要事件保障

```mermaid
graph LR
    subgraph 传统同步模型
        A[客户端] -->|HTTP请求| B[API服务]
        B -->|同步调用| C[邮件服务]
        B -->|同步调用| D[报表服务]
        B -->|等待...| E[返回响应]
        style E fill:#f96,stroke:#333
    end

    subgraph 事件驱动模型
        F[客户端] -->|HTTP请求| G[API服务]
        G -->|发布事件| H[事件总线]
        H -->|异步| I[邮件Worker]
        H -->|异步| J[报表Worker]
        G -->|立即返回| K[响应✓]
        style K fill:#9f6,stroke:#333
    end
```

### 2.2 技术选型对比

| 方案 | 优势 | 劣势 | 适用场景 |
|------|------|------|----------|
| Redis Pub/Sub | 极低延迟、简单 | 不持久化、无ACK | 实时通知、缓存失效 |
| Redis Streams | 持久化、消费者组 | 运维复杂度中等 | 中小规模事件流 |
| BullMQ | 重试、延迟、优先级 | 依赖Redis | 任务队列（本教程重点） |
| Kafka | 高吞吐、分区 | 基础设施重 | 大规模事件流 |
| RabbitMQ | 灵活路由、ACK | 吞吐量中等 | 企业集成 |

**本教程选择BullMQ**：它在简单性和功能性之间取得最佳平衡，适合90%的中小型项目，且与Claude Code的配合最为顺畅。

---

## 三、项目初始化

### 3.1 创建项目骨架

```bash
mkdir event-driven-platform && cd event-driven-platform
claude init
```

在交互式引导中选择TypeScript项目，Claude Code会自动生成基础配置。

### 3.2 配置CLAUDE.md

在项目根目录创建`CLAUDE.md`，这是Claude Code理解项目上下文的关键：

```markdown
# Event-Driven Platform

## Architecture
- Event-driven architecture using BullMQ + Redis
- TypeScript strict mode
- Pattern: Producer → Queue → Worker → Event Bus

## Tech Stack
- Runtime: Node.js 20+ with ESM modules
- Queue: BullMQ 5.x backed by Redis 7
- Validation: Zod schemas
- Logging: Pino structured logging
- Testing: Vitest

## Project Structure
```
src/
├── producers/     # 事件生产者
├── workers/       # 异步Worker
├── events/        # 事件定义与总线
├── queues/        # 队列配置
├── monitoring/    # 监控面板
└── shared/        # 共享类型与工具
```

## Rules
- 所有事件必须使用Zod schema验证
- Worker必须实现优雅关闭（graceful shutdown）
- 使用structured logging，禁止console.log
- 错误处理使用Result模式，不抛异常
```

### 3.3 安装依赖

让Claude Code帮你安装：

```bash
claude "安装以下依赖：bullmq ioredis zod pino pino-pretty dotenv，以及开发依赖 typescript @types/node tsx vitest"
```

Claude Code会生成精确的安装命令并执行。

---

## 四、核心模块实现

### 4.1 共享类型与Redis连接

让Claude Code创建基础设施工件：

```bash
claude "创建 src/shared/redis.ts，使用ioredis建立Redis连接，支持环境变量配置，包含连接池和错误处理。同时创建 src/shared/types.ts 定义基础的Event和JobResult泛型类型。"
```

**预期输出 — `src/shared/redis.ts`**：

```typescript
import Redis, { type RedisOptions } from 'ioredis';
import { logger } from './logger.js';

const getRedisConfig = (): RedisOptions => ({
  host: process.env.REDIS_HOST ?? 'localhost',
  port: parseInt(process.env.REDIS_PORT ?? '6379'),
  password: process.env.REDIS_PASSWORD,
  maxRetriesPerRequest: null, // BullMQ要求
  enableReadyCheck: true,
  retryStrategy: (times) => Math.min(times * 200, 5000),
});

// 创建连接池 - BullMQ需要专用连接
export const createRedis = (label: string): Redis => {
  const redis = new Redis(getRedisConfig());

  redis.on('error', (err) =>
    logger.error({ err, label }, 'Redis connection error')
  );
  redis.on('connect', () =>
    logger.info({ label }, 'Redis connected')
  );

  return redis;
};
```

**预期输出 — `src/shared/types.ts`**：

```typescript
import { z } from 'zod';

// 基础事件类型
export interface BaseEvent<T = unknown> {
  id: string;
  type: string;
  timestamp: number;
  payload: T;
  correlationId: string;
}

// 任务结果
export type JobResult<T = unknown> =
  | { success: true; data: T }
  | { success: false; error: string; retryable: boolean };

// 事件处理器签名
export type EventHandler<T> = (
  event: BaseEvent<T>
) => Promise<JobResult>;
```

### 4.2 事件定义（Zod Schema验证）

```bash
claude "创建 src/events/schemas.ts，用Zod定义三种事件：
1. EmailEvent - 包含 to, subject, body, template?
2. ReportEvent - 包含 reportType(daily/weekly/monthly), dateRange, recipients[]
3. WebhookEvent - 包含 url, method, headers, body, retryCount
每个schema都要包含uuid和timestamp的自动生成。"
```

**`src/events/schemas.ts`**：

```typescript
import { z } from 'zod';

const uuid = () => crypto.randomUUID();
const now = () => Date.now();

export const EmailEventSchema = z.object({
  id: z.string().uuid().default(uuid),
  type: z.literal('email.send'),
  timestamp: z.number().default(now),
  correlationId: z.string().uuid(),
  payload: z.object({
    to: z.string().email(),
    subject: z.string().min(1).max(200),
    body: z.string().min(1),
    template: z.enum(['welcome', 'notification', 'report']).optional(),
  }),
});

export const ReportEventSchema = z.object({
  id: z.string().uuid().default(uuid),
  type: z.literal('report.generate'),
  timestamp: z.number().default(now),
  correlationId: z.string().uuid(),
  payload: z.object({
    reportType: z.enum(['daily', 'weekly', 'monthly']),
    dateRange: z.object({
      start: z.string().datetime(),
      end: z.string().datetime(),
    }),
    recipients: z.array(z.string().email()).min(1),
  }),
});

export const WebhookEventSchema = z.object({
  id: z.string().uuid().default(uuid),
  type: z.literal('webhook.dispatch'),
  timestamp: z.number().default(now),
  correlationId: z.string().uuid(),
  payload: z.object({
    url: z.string().url(),
    method: z.enum(['POST', 'PUT', 'PATCH']),
    headers: z.record(z.string()).default({}),
    body: z.unknown(),
    retryCount: z.number().int().min(0).max(5).default(0),
  }),
});

// 类型推导
export type EmailEvent = z.infer<typeof EmailEventSchema>;
export type ReportEvent = z.infer<typeof ReportEventSchema>;
export type WebhookEvent = z.infer<typeof WebhookEventSchema>;

// 事件注册表 - 用于动态路由
export const EventRegistry = {
  'email.send': EmailEventSchema,
  'report.generate': ReportEventSchema,
  'webhook.dispatch': WebhookEventSchema,
} as const;
```

### 4.3 队列配置与生产者

```bash
claude "创建 src/queues/index.ts，定义三个BullMQ队列：email-queue、report-queue、webhook-queue。
配置合理的默认值：并发数3、尝试次数3、指数退避。
然后创建 src/producers/event-producer.ts，实现类型安全的事件发布接口。"
```

**`src/queues/index.ts`**：

```typescript
import { Queue, type QueueOptions } from 'bullmq';
import { createRedis } from '../shared/redis.js';

const defaultQueueOptions: QueueOptions = {
  connection: createRedis('queue'),
  defaultJobOptions: {
    removeOnComplete: { count: 1000 },   // 保留最近1000条成功记录
    removeOnFail: { count: 5000 },        // 保留最近5000条失败记录
    attempts: 3,
    backoff: {
      type: 'exponential',
      delay: 1000,  // 1s → 2s → 4s
    },
  },
};

export const emailQueue = new Queue('email-queue', {
  ...defaultQueueOptions,
  limiter: { max: 10, duration: 1000 }, // 每秒最多10封
});

export const reportQueue = new Queue('report-queue', {
  ...defaultQueueOptions,
  defaultJobOptions: {
    ...defaultQueueOptions.defaultJobOptions,
    attempts: 2, // 报表生成昂贵，少重试
  },
});

export const webhookQueue = new Queue('webhook-queue', {
  ...defaultQueueOptions,
  defaultJobOptions: {
    ...defaultQueueOptions.defaultJobOptions,
    backoff: {
      type: 'exponential',
      delay: 5000, // Webhook退避更保守
    },
  },
});

export const queues = { emailQueue, reportQueue, webhookQueue };
```

**`src/producers/event-producer.ts`**：

```typescript
import { queues } from '../queues/index.js';
import { EventRegistry } from '../events/schemas.js';
import { logger } from '../shared/logger.js';
import type { JobResult } from '../shared/types.js';

type EventType = keyof typeof EventRegistry;

export class EventProducer {
  async publish<T extends EventType>(
    type: T,
    rawPayload: z.infer<typeof EventRegistry[T]>
  ): Promise<JobResult<{ jobId: string }>> {
    const schema = EventRegistry[type];

    // Zod验证 - 第一道防线
    const parsed = schema.safeParse(rawPayload);
    if (!parsed.success) {
      logger.warn({ type, errors: parsed.error.flatten() }, 'Event validation failed');
      return {
        success: false,
        error: `Validation: ${parsed.error.message}`,
        retryable: false,
      };
    }

    const event = parsed.data;
    const queueMap = {
      'email.send': queues.emailQueue,
      'report.generate': queues.reportQueue,
      'webhook.dispatch': queues.webhookQueue,
    };

    const queue = queueMap[type];
    const job = await queue.add(type, event, {
      jobId: event.id,
      // 事件元数据存入job，方便Dashboard追踪
      opts: { priority: this.getPriority(type) },
    });

    logger.info(
      { eventType: type, jobId: job.id, correlationId: event.correlationId },
      'Event published'
    );

    return { success: true, data: { jobId: job.id! } };
  }

  private getPriority(type: EventType): number {
    // 数字越小优先级越高
    const priorities: Record<EventType, number> = {
      'webhook.dispatch': 1,  // Webhook实时性要求最高
      'email.send': 5,
      'report.generate': 10,  // 报表可以稍慢
    };
    return priorities[type];
  }
}

export const producer = new EventProducer();
```

### 4.4 Worker实现

```bash
claude "创建 src/workers/email-worker.ts，实现邮件发送Worker。
要求：
1. 使用Worker类，并发度3
2. 模拟邮件发送（随机延迟+成功率95%）
3. 实现优雅关闭（SIGTERM/SIGINT）
4. 成功/失败都要写structured log
5. 失败时返回retryable信息"
```

**`src/workers/email-worker.ts`**：

```typescript
import { Worker, type Job } from 'bullmq';
import { createRedis } from '../shared/redis.js';
import { logger } from '../shared/logger.js';
import { EmailEventSchema } from '../events/schemas.js';
import type { JobResult } from '../shared/types.js';

// 模拟邮件发送API
async function sendEmail(
  to: string,
  subject: string,
  body: string
): Promise<{ delivered: boolean; messageId: string }> {
  // 模拟网络延迟
  await new Promise((r) => setTimeout(r, 100 + Math.random() * 400));

  // 95%成功率
  if (Math.random() > 0.95) {
    throw new Error('SMTP connection refused');
  }

  return {
    delivered: true,
    messageId: `msg-${Date.now()}-${Math.random().toString(36).slice(2)}`,
  };
}

const processor = async (job: Job): Promise<JobResult> => {
  const { data } = job;

  // 运行时验证
  const parsed = EmailEventSchema.safeParse(data);
  if (!parsed.success) {
    return {
      success: false,
      error: `Invalid event data: ${parsed.error.message}`,
      retryable: false,
    };
  }

  const event = parsed.data;
  logger.info(
    { jobId: job.id, to: event.payload.to, attempt: job.attemptsStarted },
    'Processing email job'
  );

  try {
    const result = await sendEmail(
      event.payload.to,
      event.payload.subject,
      event.payload.body
    );

    logger.info(
      { jobId: job.id, messageId: result.messageId },
      'Email sent successfully'
    );

    return { success: true, data: result };
  } catch (err) {
    const errorMsg = err instanceof Error ? err.message : 'Unknown error';
    logger.warn({ jobId: job.id, error: errorMsg }, 'Email send failed');

    return {
      success: false,
      error: errorMsg,
      retryable: true, // SMTP错误可重试
    };
  }
};

// 创建Worker实例
const worker = new Worker('email-queue', processor, {
  connection: createRedis('email-worker'),
  concurrency: 3,
  limiter: { max: 5, duration: 1000 },
});

// 进度更新
worker.on('progress', (job, progress) => {
  logger.debug({ jobId: job.id, progress }, 'Job progress');
});

// 完成事件
worker.on('completed', (job) => {
  logger.info({ jobId: job.id }, 'Job completed');
});

// 失败事件
worker.on('failed', (job, err) => {
  logger.error({ jobId: job?.id, error: err.message }, 'Job failed permanently');
});

// 优雅关闭
const shutdown = async (signal: string) => {
  logger.info({ signal }, 'Shutting down email worker...');
  await worker.close(true); // true = 等待当前任务完成
  process.exit(0);
};

process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));

logger.info('Email worker started');
```

### 4.5 事件总线——系统枢纽

```bash
claude "创建 src/events/event-bus.ts，实现一个EventBus类：
1. 基于Redis Pub/Sub的跨进程事件广播
2. 支持subscribe/unsubscribe/publish
3. 内置事件日志（写入Redis Stream做审计追踪）
4. 提供getEventHistory方法查询历史"
```

**`src/events/event-bus.ts`**：

```typescript
import Redis from 'ioredis';
import { createRedis } from '../shared/redis.js';
import { logger } from '../shared/logger.js';
import type { BaseEvent, EventHandler } from '../shared/types.js';

type Listener<T = unknown> = {
  eventType: string;
  handler: EventHandler<T>;
};

export class EventBus {
  private publisher: Redis;
  private subscriber: Redis;
  private listeners: Map<string, Listener[]> = new Map();
  private streamKey = 'event-bus:history';

  constructor() {
    this.publisher = createRedis('event-bus-pub');
    this.subscriber = createRedis('event-bus-sub');

    this.subscriber.on('message', (channel, message) => {
      this.handleMessage(channel, message);
    });
  }

  async publish<T>(event: BaseEvent<T>): Promise<void> {
    const channel = `events:${event.type}`;
    const payload = JSON.stringify(event);

    // 广播给订阅者
    await this.publisher.publish(channel, payload);

    // 写入审计流
    await this.publisher.xadd(
      this.streamKey,
      '*',
      'id', event.id,
      'type', event.type,
      'payload', payload,
      'correlationId', event.correlationId
    );

    logger.info(
      { eventType: event.type, eventId: event.id, channel },
      'Event broadcasted'
    );
  }

  subscribe<T>(eventType: string, handler: EventHandler<T>): () => void {
    const channel = `events:${eventType}`;

    if (!this.listeners.has(channel)) {
      this.listeners.set(channel, []);
      this.subscriber.subscribe(channel);
    }

    const listener: Listener<T> = { eventType, handler };
    this.listeners.get(channel)!.push(listener as Listener);

    logger.info({ eventType, channel }, 'Subscribed to event');

    // 返回取消订阅函数
    return () => {
      const listeners = this.listeners.get(channel);
      if (listeners) {
        const idx = listeners.findIndex((l) => l.handler === handler);
        if (idx !== -1) listeners.splice(idx, 1);
        if (listeners.length === 0) {
          this.subscriber.unsubscribe(channel);
          this.listeners.delete(channel);
        }
      }
    };
  }

  private async handleMessage(channel: string, raw: string): Promise<void> {
    try {
      const event = JSON.parse(raw);
      const listeners = this.listeners.get(channel) ?? [];

      await Promise.allSettled(
        listeners.map(async ({ handler }) => {
          try {
            await handler(event);
          } catch (err) {
            logger.error(
              { err, eventType: event.type, eventId: event.id },
              'Event handler failed'
            );
          }
        })
      );
    } catch (err) {
      logger.error({ err, channel }, 'Failed to process event message');
    }
  }

  // 查询事件历史
  async getHistory(count = 50): Promise<BaseEvent[]> {
    const entries = await this.publisher.xrange(
      this.streamKey,
      '-',
      '+',
      'COUNT',
      count
    );

    return entries.map(([, fields]) => {
      const obj = Object.fromEntries(
        fields.reduce<string[]>((acc, val, i) => {
          if (i % 2 === 0) acc.push(val);
          else acc[acc.length - 1] = val;
          return acc;
        }, []).reduce<[string, string][]>((acc, _, i, arr) => {
          if (i % 2 === 0) acc.push([arr[i], arr[i + 1]]);
          return acc;
        }, [])
      );
      return JSON.parse(obj.payload);
    });
  }

  async disconnect(): Promise<void> {
    await this.subscriber.unsubscribe();
    await this.publisher.quit();
    await this.subscriber.quit();
  }
}

export const eventBus = new EventBus();
```

---

## 五、架构全景图

```mermaid
graph TB
    subgraph 客户端
        CLI[CLI/API调用]
        DASH[监控Dashboard]
    end

    subgraph 事件层
        EP[EventProducer<br/>Zod验证]
        EB[EventBus<br/>Redis Pub/Sub]
    end

    subgraph 队列层 - BullMQ
        EQ[email-queue]
        RQ[report-queue]
        WQ[webhook-queue]
    end

    subgraph Worker层
        EW[EmailWorker<br/>并发3]
        RW[ReportWorker<br/>并发2]
        WW[WebhookWorker<br/>并发5]
    end

    subgraph 存储
        REDIS[(Redis 7<br/>队列+Pub/Sub+Streams)]
    end

    CLI -->|发布事件| EP
    EP -->|验证+入队| EQ
    EP -->|验证+入队| RQ
    EP -->|验证+入队| WQ
    EP -->|广播| EB

    EB -->|审计日志| REDIS
    EQ -->|消费| EW
    RQ -->|消费| RW
    WQ -->|消费| WW

    EW -->|状态更新| REDIS
    RW -->|状态更新| REDIS
    WW -->|状态更新| REDIS

    DASH -->|读取状态| REDIS

    style EP fill:#6366f1,color:#fff
    style EB fill:#8b5cf6,color:#fff
    style REDIS fill:#dc2626,color:#fff
    style DASH fill:#059669,color:#fff
```

---

## 六、监控面板实现

### 6.1 REST API端点

```bash
claude "创建 src/monitoring/api.ts，使用Node.js原生http模块（不引入Express）创建监控API：
- GET /stats - 各队列的等待/活跃/完成/失败数
- GET /history - 最近事件历史
- GET /health - 健康检查（Redis连接状态）
端口从环境变量MONITOR_PORT读取，默认3001"
```

**`src/monitoring/api.ts`**：

```typescript
import { createServer } from 'node:http';
import { queues } from '../queues/index.js';
import { createRedis } from '../shared/redis.js';
import { logger } from '../shared/logger.js';

const PORT = parseInt(process.env.MONITOR_PORT ?? '3001');

const healthRedis = createRedis('monitor');

async function getStats() {
  const [email, report, webhook] = await Promise.all([
    queues.emailQueue.getJobCounts(),
    queues.reportQueue.getJobCounts(),
    queues.webhookQueue.getJobCounts(),
  ]);

  return {
    timestamp: new Date().toISOString(),
    queues: {
      email: email,
      report: report,
      webhook: webhook,
    },
  };
}

async function getHealth() {
  try {
    await healthRedis.ping();
    return { status: 'healthy', redis: 'connected', uptime: process.uptime() };
  } catch {
    return { status: 'unhealthy', redis: 'disconnected' };
  }
}

const server = createServer(async (req, res) => {
  res.setHeader('Content-Type', 'application/json');
  res.setHeader('Access-Control-Allow-Origin', '*');

  try {
    if (req.url === '/stats') {
      const stats = await getStats();
      res.writeHead(200);
      res.end(JSON.stringify(stats));
    } else if (req.url === '/health') {
      const health = await getHealth();
      res.writeHead(health.status === 'healthy' ? 200 : 503);
      res.end(JSON.stringify(health));
    } else {
      res.writeHead(404);
      res.end(JSON.stringify({ error: 'Not found' }));
    }
  } catch (err) {
    logger.error({ err }, 'Monitor API error');
    res.writeHead(500);
    res.end(JSON.stringify({ error: 'Internal error' }));
  }
});

server.listen(PORT, () => {
  logger.info({ port: PORT }, 'Monitor API started');
});
```

### 6.2 实时Dashboard页面

```bash
claude "创建 src/monitoring/dashboard.ts，用Node.js http模块提供一个单页面HTML dashboard。
页面功能：
1. 每2秒自动刷新队列状态（调用/stats API）
2. 用CSS Grid展示三个队列的状态卡片
3. 每个卡片显示：等待中、活跃、完成、失败的数字
4. 使用纯CSS动画，数字变化时有闪烁效果
5. 深色主题，适合运维大屏"
```

Claude Code会生成一个完整的HTML内联Dashboard。这里展示核心模板结构：

```typescript
// src/monitoring/dashboard.ts 的关键HTML模板
const dashboardHTML = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Event-Driven Platform Dashboard</title>
  <style>
    * { margin: 0; box-sizing: border-box; }
    body { background: #0f172a; color: #e2e8f0; font-family: system-ui; padding: 2rem; }
    .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem; }
    .card { background: #1e293b; border-radius: 12px; padding: 1.5rem;
            border: 1px solid #334155; transition: border-color 0.3s; }
    .card:hover { border-color: #6366f1; }
    .card h2 { font-size: 1.1rem; margin-bottom: 1rem; color: #94a3b8; }
    .metric { display: flex; justify-content: space-between; padding: 0.5rem 0;
              border-bottom: 1px solid #334155; }
    .metric:last-child { border: none; }
    .metric .value { font-weight: 700; font-size: 1.3rem; font-variant-numeric: tabular-nums; }
    .waiting .value { color: #facc15; }
    .active .value { color: #22d3ee; }
    .completed .value { color: #4ade80; }
    .failed .value { color: #f87171; }
    @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } }
    .flash { animation: pulse 0.5s ease; }
    .header { text-align: center; margin-bottom: 2rem; }
    .header h1 { font-size: 1.5rem; color: #f8fafc; }
    .header .ts { color: #64748b; font-size: 0.85rem; }
  </style>
</head>
<body>
  <div class="header">
    <h1>Event-Driven Platform Monitor</h1>
    <div class="ts" id="timestamp">--</div>
  </div>
  <div class="grid" id="queues"></div>
  <script>
    const API = location.port === '3002' ? 'http://localhost:3001' : '';
    async function refresh() {
      try {
        const res = await fetch(API + '/stats');
        const data = await res.json();
        document.getElementById('timestamp').textContent = data.timestamp;
        const container = document.getElementById('queues');
        const labels = { waiting: '等待中', active: '处理中',
                         completed: '已完成', failed: '已失败' };
        const cls = { waiting: 'waiting', active: 'active',
                      completed: 'completed', failed: 'failed' };
        container.innerHTML = Object.entries(data.queues).map(([name, counts]) => \`
          <div class="card">
            <h2>\${name.toUpperCase().replace('-', ' ')}</h2>
            \${Object.entries(labels).map(([key, label]) => \`
              <div class="metric \${cls[key]}">
                <span>\${label}</span>
                <span class="value flash">\${counts[key] ?? 0}</span>
              </div>
            \`).join('')}
          </div>
        \`).join('');
      } catch(e) { console.error('Fetch failed:', e); }
    }
    refresh();
    setInterval(refresh, 2000);
  </script>
</body>
</html>
`;
```

---

## 七、完整实战案例：异步通知平台

### 7.1 主入口编排

将所有模块串联为一个可运行的系统：

```bash
claude "创建 src/index.ts 作为系统入口，功能：
1. 加载dotenv配置
2. 启动所有Worker
3. 启动监控API和Dashboard
4. 提供一个交互式CLI，可以手动发送测试事件
5. 演示模式：启动后自动发送一批测试事件"
```

**`src/index.ts`**：

```typescript
import 'dotenv/config';
import { producer } from './producers/event-producer.js';
import { eventBus } from './events/event-bus.js';
import { logger } from './shared/logger.js';
import './workers/email-worker.js';
import './monitoring/api.js';

async function demo() {
  logger.info('=== Event-Driven Platform Demo ===');

  // 订阅所有事件做审计日志
  const eventTypes = ['email.send', 'report.generate', 'webhook.dispatch'];
  const unsubscribers = eventTypes.map((type) =>
    eventBus.subscribe(type, async (event) => {
      logger.info(
        { eventType: type, eventId: event.id },
        '[Audit] Event received via bus'
      );
      return { success: true, data: 'logged' };
    })
  );

  // 发送一批测试事件
  const correlationId = crypto.randomUUID();

  // 1. 邮件事件
  const emailResult = await producer.publish('email.send', {
    id: crypto.randomUUID(),
    type: 'email.send',
    timestamp: Date.now(),
    correlationId,
    payload: {
      to: 'user@example.com',
      subject: '欢迎使用事件驱动平台',
      body: '这是一封由BullMQ队列异步处理的邮件。',
      template: 'welcome',
    },
  });
  logger.info({ result: emailResult }, 'Email event published');

  // 2. 报表事件
  const reportResult = await producer.publish('report.generate', {
    id: crypto.randomUUID(),
    type: 'report.generate',
    timestamp: Date.now(),
    correlationId,
    payload: {
      reportType: 'daily',
      dateRange: {
        start: new Date(Date.now() - 86400000).toISOString(),
        end: new Date().toISOString(),
      },
      recipients: ['admin@example.com'],
    },
  });
  logger.info({ result: reportResult }, 'Report event published');

  // 3. Webhook事件
  const webhookResult = await producer.publish('webhook.dispatch', {
    id: crypto.randomUUID(),
    type: 'webhook.dispatch',
    timestamp: Date.now(),
    correlationId,
    payload: {
      url: 'https://httpbin.org/post',
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: { event: 'test', correlationId },
      retryCount: 0,
    },
  });
  logger.info({ result: webhookResult }, 'Webhook event published');

  logger.info('Demo events published. Workers are processing...');
  logger.info('Dashboard: http://localhost:3002');
  logger.info('API: http://localhost:3001/stats');
}

demo().catch((err) => {
  logger.fatal({ err }, 'Failed to start platform');
  process.exit(1);
});
```

### 7.2 启动与验证

```bash
# 终端1：启动Redis（如果还没启动）
docker start redis-eda

# 终端2：启动系统
npx tsx src/index.ts
```

**预期输出**：

```
[INFO] Redis connected (label="queue")
[INFO] Redis connected (label="email-worker")
[INFO] Redis connected (label="event-bus-pub")
[INFO] Redis connected (label="event-bus-sub")
[INFO] Email worker started
[INFO] Monitor API started (port=3001)
[INFO] === Event-Driven Platform Demo ===
[INFO] Event published (eventType="email.send", jobId="...")
[INFO] Event published (eventType="report.generate", jobId="...")
[INFO] Event published (eventType="webhook.dispatch", jobId="...")
[INFO] Processing email job (jobId="...", to="user@example.com")
[INFO] Email sent successfully (jobId="...", messageId="msg-...")
[INFO] Job completed (jobId="...")
```

### 7.3 验证监控面板

```bash
# 检查队列统计
curl http://localhost:3001/stats | jq .

# 输出：
# {
#   "timestamp": "2026-05-19T10:30:00.000Z",
#   "queues": {
#     "email": { "waiting": 0, "active": 0, "completed": 1, "failed": 0 },
#     "report": { "waiting": 0, "active": 1, "completed": 0, "failed": 0 },
#     "webhook": { "waiting": 0, "active": 0, "completed": 1, "failed": 0 }
#   }
# }

# 健康检查
curl http://localhost:3001/health | jq .
# { "status": "healthy", "redis": "connected", "uptime": 42.5 }
```

---

## 八、高级模式

### 8.1 死信队列（Dead Letter Queue）

当消息重试耗尽后，需要将其路由到死信队列做人工审查：

```bash
claude "在 src/workers/ 下创建 dead-letter-worker.ts，监听所有队列的failed事件，
将失败任务写入 Redis Sorted Set（按时间排序），
并提供查询接口。同时在 monitoring/api.ts 中添加 GET /dead-letters 端点。"
```

Claude Code会自动理解上下文并生成与现有架构一致的代码。核心模式如下：

```typescript
// Worker失败时自动触发
worker.on('failed', async (job, err) => {
  if (job && job.attemptsMade >= job.opts?.attempts!) {
    // 写入死信队列
    await deadLetterRedis.zadd(
      'dead-letters',
      Date.now(),
      JSON.stringify({
        jobId: job.id,
        queueName: 'email-queue',
        data: job.data,
        error: err.message,
        failedAt: new Date().toISOString(),
      })
    );
    logger.error({ jobId: job.id }, 'Job moved to dead letter queue');
  }
});
```

### 8.2 延迟任务与定时调度

```typescript
// 延迟发送 - 5分钟后
await emailQueue.add('email.send', eventPayload, {
  delay: 5 * 60 * 1000,
});

// 定时调度 - 使用BullMQ的Repeatable API
await reportQueue.add(
  'report.generate',
  { reportType: 'daily', /* ... */ },
  {
    repeat: { pattern: '0 6 * * *' }, // 每天早上6点
    jobId: 'daily-report',
  }
);
```

### 8.3 优先级队列与速率限制

```mermaid
graph TD
    IN[事件入口] --> P{优先级判断}
    P -->|urgent| H[高优先级队列<br/>priority=1]
    P -->|normal| M[普通队列<br/>priority=5]
    P -->|batch| L[批量队列<br/>priority=10]
    H --> W[Worker<br/>concurrency=5]
    M --> W
    L --> W
    W -->|超限| RL[速率限制器<br/>10 req/s]
    RL --> EXT[外部API]

    style H fill:#ef4444,color:#fff
    style M fill:#f59e0b,color:#fff
    style L fill:#6b7280,color:#fff
    style RL fill:#8b5cf6,color:#fff
```

```typescript
// 在Producer中动态设置优先级
async publishWithPriority(
  type: EventType,
  payload: unknown,
  priority: 'urgent' | 'normal' | 'batch'
) {
  const priorityMap = { urgent: 1, normal: 5, batch: 10 };
  return queue.add(type, payload, {
    priority: priorityMap[priority],
  });
}
```

---

## 九、测试策略

### 9.1 使用Vitest进行单元测试

```bash
claude "为 event-producer.ts 编写Vitest单元测试。
要求：
1. Mock BullMQ的Queue类
2. 测试正常发布流程
3. 测试Zod验证失败的场景
4. 测试优先级分配逻辑
5. 使用beforeEach/afterEach管理Mock"
```

**`src/producers/__tests__/event-producer.test.ts`**：

```typescript
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { producer } from '../event-producer.js';

// Mock BullMQ
vi.mock('../../queues/index.js', () => ({
  queues: {
    emailQueue: { add: vi.fn() },
    reportQueue: { add: vi.fn() },
    webhookQueue: { add: vi.fn() },
  },
}));

import { queues } from '../../queues/index.js';

describe('EventProducer', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should publish valid email event', async () => {
    const mockAdd = vi.mocked(queues.emailQueue.add).mockResolvedValue({
      id: 'job-123',
    } as any);

    const result = await producer.publish('email.send', {
      id: crypto.randomUUID(),
      type: 'email.send',
      timestamp: Date.now(),
      correlationId: crypto.randomUUID(),
      payload: {
        to: 'test@example.com',
        subject: 'Test',
        body: 'Test body',
      },
    });

    expect(result.success).toBe(true);
    if (result.success) {
      expect(result.data.jobId).toBe('job-123');
    }
    expect(mockAdd).toHaveBeenCalledOnce();
  });

  it('should reject invalid email', async () => {
    const result = await producer.publish('email.send', {
      id: crypto.randomUUID(),
      type: 'email.send',
      timestamp: Date.now(),
      correlationId: crypto.randomUUID(),
      payload: {
        to: 'not-an-email',
        subject: 'Test',
        body: 'Test body',
      },
    });

    expect(result.success).toBe(false);
    if (!result.success) {
      expect(result.retryable).toBe(false);
      expect(result.error).toContain('Validation');
    }
  });
});
```

### 9.2 运行测试

```bash
npx vitest run
#  ✓ event-producer.test.ts (2 tests) 15ms
#  Tests  2 passed (2)
#  Duration  320ms
```

---

## 十、生产部署清单

### 10.1 Docker Compose部署

```yaml
# docker-compose.yml
version: '3.8'

services:
  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru
    volumes:
      - redis-data:/data
    healthcheck:
      test: ['CMD', 'redis-cli', 'ping']
      interval: 5s
      retries: 5

  email-worker:
    build: .
    command: npx tsx src/workers/email-worker.ts
    depends_on:
      redis: { condition: service_healthy }
    environment:
      REDIS_HOST: redis
    deploy:
      replicas: 2  # 水平扩展
      resources:
        limits: { memory: 256M }

  report-worker:
    build: .
    command: npx tsx src/workers/report-worker.ts
    depends_on:
      redis: { condition: service_healthy }
    environment:
      REDIS_HOST: redis

  monitor:
    build: .
    command: npx tsx src/monitoring/api.ts
    depends_on:
      redis: { condition: service_healthy }
    ports:
      - '3001:3001'
    environment:
      REDIS_HOST: redis

volumes:
  redis-data:
```

### 10.2 关键配置参数

| 参数 | 开发环境 | 生产环境 | 说明 |
|------|----------|----------|------|
| Worker并发数 | 3 | 10-50 | 根据下游API承受能力调整 |
| 最大重试次数 | 3 | 3-5 | 根据任务重要性 |
| 退避策略 | exponential 1s | exponential 5s | 生产环境更保守 |
| Redis内存 | 无限制 | 512MB+ | 设定maxmemory防OOM |
| 连接池 | 5 | 20-50 | 每个Worker独立连接 |
| 任务TTL | 默认 | 24h | 防止僵尸任务堆积 |

### 10.3 健康检查与告警

```typescript
// 添加到 monitoring/api.ts 的告警逻辑
async function checkAlerts(): Promise<string[]> {
  const alerts: string[] = [];
  const emailCounts = await queues.emailQueue.getJobCounts();

  if (emailCounts.waiting > 1000) {
    alerts.push('email-queue堆积超过1000，需要扩容Worker');
  }
  if (emailCounts.failed > 100) {
    alerts.push('email-queue失败数超过100，检查SMTP服务');
  }

  const deadLetterCount = await deadLetterRedis.zcard('dead-letters');
  if (deadLetterCount > 50) {
    alerts.push(`死信队列中有${deadLetterCount}条消息待处理`);
  }

  return alerts;
}
```

---

## 十一、事件流转完整时序图

```mermaid
sequenceDiagram
    participant C as 客户端
    participant P as Producer
    participant Z as Zod验证
    participant Q as BullMQ Queue
    participant W as Worker
    participant BUS as EventBus
    participant R as Redis
    participant M as Monitor

    C->>P: publish('email.send', payload)
    P->>Z: safeParse(payload)

    alt 验证失败
        Z-->>P: ❌ Validation Error
        P-->>C: {success: false, retryable: false}
    else 验证通过
        Z-->>P: ✅ Parsed Event
        P->>Q: queue.add(type, event)
        Q->>R: LPUSH job data
        P->>BUS: broadcast(event)
        BUS->>R: XADD audit stream
        BUS->>R: PUBLISH events:email.send
        P-->>C: {success: true, jobId: '...'}
    end

    Note over Q,W: 异步处理
    Q->>W: BRPOP job
    W->>W: process(job)
    W->>R: 更新job状态(active)
    W->>W: 执行业务逻辑
    W->>R: 更新job状态(completed)

    M->>R: GET job counts
    R-->>M: stats data
    M-->>C: Dashboard展示
```

---

## 十二、常见问题与解决方案

| 问题 | 症状 | 解决方案 |
|------|------|----------|
| Worker启动后不消费 | 日志显示connected但无处理记录 | 检查Redis `maxRetriesPerRequest: null` 配置 |
| 任务堆积不处理 | waiting数持续增长 | 增加Worker并发数或`replicas` |
| 内存持续增长 | Redis内存使用量线性上升 | 配置`removeOnComplete`和`removeOnFail` |
| 重复消费 | 同一任务被两个Worker执行 | 确认Worker使用不同的`connection`实例 |
| 优雅关闭失败 | `docker stop`超时 | 检查`worker.close(true)`的timeout设置 |

---

## 延伸阅读

- [BullMQ官方文档 - 全面了解队列配置选项](https://docs.bullmq.io/)
- [Redis Streams详解 - 理解事件持久化底层机制](https://redis.io/docs/latest/develop/data-types/streams/)
- [事件驱动架构模式 - Martin Fowler的经典EDA论述](https://martinfowler.com/articles/201701-event-driven.html)
- [Claude Code官方Hooks文档 - 自动化工作流集成](https://docs.anthropic.com/en/docs/claude-code/hooks)

---

## 训练营适用性评估

| 维度 | 评估 |
|------|------|
| 适合训练营类型 | 后端架构训练营、Node.js进阶营、分布式系统实战营 |
| 教学时长 | 6-8小时（含动手实验） |
| 难度等级 | ⭐⭐⭐⭐ (中高级) |
| 实验环境要求 | 每人需Docker + Node.js 20 + Redis |
| 最佳分组 | 2-3人一组，每组实现一个Worker |
| 产出物 | 完整可运行的事件驱动平台 + Dashboard |

---

## 参考来源

1. [Designing Event-Driven Architecture with Claude Code](https://claudecode-lab.com/en/blog/claude-code-event-driven-architecture/) — 事件驱动架构设计模式与Claude Code实践指南
2. [Claude Code Job Queues and Async Processing](https://claudecode-lab.com/en/blog/claude-code-queue-system/) — BullMQ集成与异步任务系统实现教程
3. [BullMQ Job Queues in Node.js: Background Processing Done Right](https://dev.to/young_gao/bullmq-job-queues-background-processing-in-nodejs-done-right-5306) — BullMQ生产级最佳实践与配置参数详解
4. [Claude Event-Driven Agents (GitHub)](https://github.com/hwillGIT/claude-event-driven-agents) — 基于Redis消息队列的事件驱动Agent架构参考实现
5. [Scaling WebSockets with Claude Code: Redis Pub/Sub](https://dev.to/myougatheaxo/scaling-websockets-with-claude-code-redis-pubsub-and-socketio-adapter-2026-03-11-5666) — Redis Pub/Sub与WebSocket横向扩展实战

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
