npm package: @nest-packages/nestjs-alert-module
Description: A pluggable alert module for NestJS with support for multiple channels (Feishu, Sentry, etc.)
Author: huixisheng@gmail.com
Latest version: 0.0.1

# NestJS Alert Module

一个功能强大、插件化、可扩展的 NestJS 告警模块，支持多种告警渠道（飞书、Sentry、Logger 等）。

[![npm version](https://img.shields.io/npm/v/@libs/nestjs-alert-module.svg)](https://www.npmjs.com/package/@libs/nestjs-alert-module)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## ✨ 特性

- 🔌 **插件化架构** - 轻松扩展新的告警渠道
- 🎯 **多渠道支持** - 飞书（个人/群/Webhook）、Sentry、Logger
- 🛡️ **类型安全** - 完整的 TypeScript 类型定义
- 🚀 **开箱即用** - 简单的配置即可快速使用
- 🎨 **装饰器支持** - `@Alert()` 装饰器自动捕获异常
- 🔄 **智能重试** - 自动重试失败的告警
- 🚦 **限流控制** - 防止告警风暴
- 🔁 **去重机制** - 避免重复告警
- 💉 **依赖注入** - 完全使用 NestJS 依赖注入
- 🧪 **易于测试** - 良好的可测试性

## 📦 安装

```bash
npm install @libs/nestjs-alert-module
# 或
yarn add @libs/nestjs-alert-module
# 或
pnpm add @libs/nestjs-alert-module
```

### 依赖

```bash
npm install @nestjs/common @nestjs/core @nestjs/axios rxjs fast-safe-stringify
```

## 🚀 快速开始

### 1. 导入模块

```typescript
import { Module } from '@nestjs/common';
import { AlertModule, AlertChannelType } from '@libs/nestjs-alert-module';

@Module({
  imports: [
    // 同步配置
    AlertModule.forRoot({
      isGlobal: true,
      defaultChannels: [
        {
          type: AlertChannelType.FEISHU_WEBHOOK,
          config: {
            webhookUrl: 'https://open.feishu.cn/open-apis/bot/v2/hook/xxx',
          },
        },
      ],
      rateLimit: {
        enabled: true,
        maxRequests: 10,
        windowMs: 60000, // 1分钟
      },
      deduplication: {
        enabled: true,
        ttl: 300, // 5分钟
      },
      retry: {
        enabled: true,
        maxAttempts: 3,
        delay: 1000,
      },
    }),
    
    // 或异步配置
    AlertModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (configService: ConfigService) => ({
        isGlobal: true,
        defaultChannels: [
          {
            type: AlertChannelType.FEISHU_WEBHOOK,
            config: {
              webhookUrl: configService.get('FEISHU_WEBHOOK_URL'),
            },
          },
        ],
        feishu: {
          appId: configService.get('FEISHU_APP_ID'),
          appSecret: configService.get('FEISHU_APP_SECRET'),
        },
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}
```

### 2. 使用 AlertService

```typescript
import { Injectable } from '@nestjs/common';
import { AlertService, AlertLevel } from '@libs/nestjs-alert-module';

@Injectable()
export class UserService {
  constructor(private readonly alertService: AlertService) {}

  async createUser(data: any) {
    try {
      const user = await this.userRepository.save(data);
      
      // 发送成功告警
      await this.alertService.info('用户创建成功', {
        userId: user.id,
        username: user.username,
      });
      
      return user;
    } catch (error) {
      // 发送错误告警
      await this.alertService.error('用户创建失败', error, {
        inputData: data,
      });
      throw error;
    }
  }
}
```

### 3. 使用装饰器

```typescript
import { Injectable } from '@nestjs/common';
import { Alert, AlertLevel, AlertChannelType } from '@libs/nestjs-alert-module';

@Injectable()
export class PaymentService {
  @Alert({
    title: '支付处理',
    level: AlertLevel.ERROR,
    channels: [AlertChannelType.FEISHU_WEBHOOK, AlertChannelType.SENTRY],
    onlyOnError: true, // 只在异常时发送告警
  })
  async processPayment(orderId: string, amount: number) {
    // 如果方法抛出异常，会自动发送告警
    return await this.paymentGateway.charge(orderId, amount);
  }
}
```

## 📖 完整示例

### 发送到多个渠道

```typescript
await this.alertService.send({
  level: AlertLevel.CRITICAL,
  title: '系统严重错误',
  content: {
    service: 'payment-service',
    errorCode: 'PAYMENT_001',
    message: '支付网关连接失败',
  },
  channels: [
    {
      type: AlertChannelType.FEISHU_WEBHOOK,
      config: {
        webhookUrl: 'https://open.feishu.cn/open-apis/bot/v2/hook/xxx',
      },
    },
    {
      type: AlertChannelType.FEISHU_USER,
      config: {
        userId: 'ou_xxx', // 飞书用户 Open ID
      },
    },
    {
      type: AlertChannelType.FEISHU_GROUP,
      config: {
        chatId: 'oc_xxx', // 飞书群 ID
      },
    },
    {
      type: AlertChannelType.SENTRY,
      config: {},
    },
    {
      type: AlertChannelType.LOGGER,
      config: {},
    },
  ],
  metadata: {
    traceId: 'trace-123456',
    environment: 'production',
  },
  tags: ['payment', 'critical'],
});
```

## 📚 API 文档

### AlertService

#### `send(message: IAlertMessage): Promise<AlertResult[]>`

发送告警到指定渠道。

#### `error(title: string, error: Error | string, metadata?: Record<string, any>): Promise<AlertResult[]>`

发送错误告警。

#### `warning(title: string, content: string | Record<string, any>, metadata?: Record<string, any>): Promise<AlertResult[]>`

发送警告告警。

#### `info(title: string, content: string | Record<string, any>, metadata?: Record<string, any>): Promise<AlertResult[]>`

发送信息告警。

#### `critical(title: string, content: string | Record<string, any>, metadata?: Record<string, any>): Promise<AlertResult[]>`

发送严重告警。

### 告警级别

```typescript
enum AlertLevel {
  INFO = 'info',
  WARNING = 'warning',
  ERROR = 'error',
  CRITICAL = 'critical',
}
```

### 告警渠道类型

```typescript
enum AlertChannelType {
  FEISHU_USER = 'feishu_user',           // 飞书个人消息
  FEISHU_GROUP = 'feishu_group',         // 飞书群消息
  FEISHU_WEBHOOK = 'feishu_webhook',     // 飞书 Webhook
  SENTRY = 'sentry',                     // Sentry
  LOGGER = 'logger',                     // 日志
  EMAIL = 'email',                       // 邮件（预留）
  SMS = 'sms',                           // 短信（预留）
  CUSTOM = 'custom',                     // 自定义
}
```

## 🔌 自定义渠道插件

### 创建自定义渠道

```typescript
import { Injectable } from '@nestjs/common';
import { IAlertChannel, AlertChannelType, IAlertMessage, AlertResult } from '@libs/nestjs-alert-module';

@Injectable()
export class EmailAlertChannel implements IAlertChannel {
  readonly name = 'email';
  readonly type = AlertChannelType.EMAIL;

  async send(message: IAlertMessage): Promise<AlertResult> {
    // 实现邮件发送逻辑
    try {
      await this.sendEmail(message);
      return {
        success: true,
        channel: this.name,
        timestamp: new Date(),
      };
    } catch (error) {
      return {
        success: false,
        channel: this.name,
        error: error.message,
        timestamp: new Date(),
      };
    }
  }

  async validateConfig(config: any): Promise<boolean> {
    return !!config.to && !!config.subject;
  }

  private async sendEmail(message: IAlertMessage): Promise<void> {
    // 邮件发送实现
  }
}
```

### 创建插件

```typescript
import { Injectable } from '@nestjs/common';
import { IAlertPlugin, IAlertChannel } from '@libs/nestjs-alert-module';

@Injectable()
export class EmailAlertPlugin implements IAlertPlugin {
  readonly name = 'email-alert-plugin';
  readonly version = '1.0.0';

  constructor(private readonly emailChannel: EmailAlertChannel) {}

  register(): IAlertChannel {
    return this.emailChannel;
  }
}
```

### 注册插件

```typescript
@Module({
  imports: [
    AlertModule.forRoot({ /* ... */ }),
  ],
  providers: [
    EmailAlertChannel,
    EmailAlertPlugin,
  ],
})
export class AppModule {}
```

## ⚙️ 配置选项

```typescript
interface AlertModuleOptions {
  /**
   * 是否全局模块
   */
  isGlobal?: boolean;

  /**
   * 默认告警渠道
   */
  defaultChannels?: AlertChannelTarget[];

  /**
   * 飞书配置
   */
  feishu?: {
    appId?: string;
    appSecret?: string;
    webhooks?: Record<string, string>;
  };

  /**
   * Sentry 配置
   */
  sentry?: {
    dsn?: string;
    environment?: string;
  };

  /**
   * 限流配置
   */
  rateLimit?: {
    enabled: boolean;
    maxRequests: number;
    windowMs: number;
  };

  /**
   * 去重配置
   */
  deduplication?: {
    enabled: boolean;
    ttl: number;
  };

  /**
   * 重试配置
   */
  retry?: {
    enabled: boolean;
    maxAttempts: number;
    delay: number;
  };
}
```

## 🧪 测试

```typescript
import { Test } from '@nestjs/testing';
import { AlertService, AlertModule } from '@libs/nestjs-alert-module';

describe('AlertService', () => {
  let alertService: AlertService;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      imports: [
        AlertModule.forRoot({
          defaultChannels: [],
        }),
      ],
    }).compile();

    alertService = module.get<AlertService>(AlertService);
  });

  it('should send alert', async () => {
    const results = await alertService.info('Test Alert', 'Test content');
    expect(results).toBeDefined();
  });
});
```

## 📝 最佳实践

1. **使用全局模块** - 将 AlertModule 配置为全局模块，避免在每个模块中重复导入
2. **合理配置限流** - 根据实际情况配置限流参数，防止告警风暴
3. **启用去重** - 开启去重功能，避免短时间内重复发送相同告警
4. **分级告警** - 根据错误严重程度使用不同的告警级别
5. **添加元数据** - 在告警中添加 traceId、环境等元数据，便于问题追踪
6. **使用装饰器** - 对于需要监控的方法，使用 `@Alert()` 装饰器简化代码

## 🤝 贡献

欢迎提交 Issue 和 Pull Request！

## 📄 许可证

MIT

## 🔗 相关链接

- [NestJS 官方文档](https://nestjs.com/)
- [飞书开放平台](https://open.feishu.cn/)
- [Sentry 文档](https://docs.sentry.io/)

## 📮 联系方式

如有问题或建议，欢迎联系我们。