Skip to content

提供者

什么是提供者(Providers)?

生活中的例子

想象你在一家咖啡店工作:

  • 咖啡师(控制器)负责接受顾客的订单
  • 但咖啡师不会自己种咖啡豆、不会自己制作杯子
  • 咖啡师需要咖啡豆供应商杯子供应商来提供材料
  • 这些供应商就是"提供者"

在 NestJS 中

提供者(Provider) 是一个带有 @Injectable() 装饰器的类,它可以:

  • 包含业务逻辑(比如数据库操作、计算等)
  • 被其他类"注入"和使用
  • 由 NestJS 框架自动管理生命周期

依赖注入(DI)基础概念

什么是依赖注入?

依赖注入是一种设计模式,简单来说:

  • 依赖:一个类需要使用另一个类的功能
  • 注入:框架自动把需要的类实例传递给你

传统方式 vs 依赖注入

typescript
// ❌ 传统方式 - 手动创建依赖
class UserController {
  private userService: UserService;

  constructor() {
    this.userService = new UserService(); // 手动创建
  }
}

// ✅ 依赖注入方式 - 框架自动提供
class UserController {
  constructor(private userService: UserService) {
    // userService 由框架自动注入
  }
}

依赖注入的优点:

  1. 松耦合:类之间的依赖关系更灵活
  2. 易测试:可以轻松替换成测试用的假对象
  3. 易维护:修改一个服务不会影响其他代码

基本的提供者

1. 创建一个简单的服务

typescript
// users.service.ts
import { Injectable } from '@nestjs/common';

@Injectable() // 这个装饰器告诉NestJS这是一个提供者
export class UsersService {
  private users = [
    { id: 1, name: '张三', email: 'zhang@example.com' },
    { id: 2, name: '李四', email: 'li@example.com' },
  ];

  // 获取所有用户
  findAll() {
    return this.users;
  }

  // 根据ID查找用户
  findById(id: number) {
    return this.users.find((user) => user.id === id);
  }

  // 创建新用户
  create(userData: { name: string; email: string }) {
    const newUser = {
      id: this.users.length + 1,
      ...userData,
    };
    this.users.push(newUser);
    return newUser;
  }

  // 删除用户
  delete(id: number) {
    const index = this.users.findIndex((user) => user.id === id);
    if (index !== -1) {
      return this.users.splice(index, 1)[0];
    }
    return null;
  }
}

2. 在控制器中使用服务

typescript
// users.controller.ts
import { Controller, Get, Post, Delete, Param, Body } from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
  // 通过构造函数注入服务
  constructor(private usersService: UsersService) {}

  @Get()
  getAllUsers() {
    return this.usersService.findAll();
  }

  @Get(':id')
  getUser(@Param('id') id: string) {
    return this.usersService.findById(parseInt(id));
  }

  @Post()
  createUser(@Body() userData: { name: string; email: string }) {
    return this.usersService.create(userData);
  }

  @Delete(':id')
  deleteUser(@Param('id') id: string) {
    return this.usersService.delete(parseInt(id));
  }
}

3. 在模块中注册提供者

typescript
// users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
  controllers: [UsersController],
  providers: [UsersService], // 在这里注册提供者
  exports: [UsersService], // 如果其他模块需要使用,就导出
})
export class UsersModule {}

提供者的不同类型

1. useClass - 类提供者

这是最常见的提供者类型:

typescript
// 简写形式
@Module({
  providers: [UsersService], // 这是简写
})

// 完整形式
@Module({
  providers: [
    {
      provide: UsersService,  // 提供者的标识符
      useClass: UsersService  // 使用的类
    }
  ],
})

实际应用场景:

typescript
// 开发环境和生产环境使用不同的服务
interface IEmailService {
  sendEmail(to: string, subject: string, body: string): void;
}

@Injectable()
class DevelopmentEmailService implements IEmailService {
  sendEmail(to: string, subject: string, body: string) {
    console.log(`开发环境 - 发送邮件到 ${to}: ${subject}`);
  }
}

@Injectable()
class ProductionEmailService implements IEmailService {
  sendEmail(to: string, subject: string, body: string) {
    // 实际发送邮件的逻辑
    console.log(`生产环境 - 发送邮件到 ${to}`);
  }
}

@Module({
  providers: [
    {
      provide: 'EMAIL_SERVICE',
      useClass:
        process.env.NODE_ENV === 'production'
          ? ProductionEmailService
          : DevelopmentEmailService,
    },
  ],
})
export class EmailModule {}

2. useValue - 值提供者

直接提供一个值,而不是类的实例:

typescript
// 配置对象
const databaseConfig = {
  host: 'localhost',
  port: 5432,
  database: 'myapp',
  username: 'admin',
  password: 'password',
};

@Module({
  providers: [
    {
      provide: 'DATABASE_CONFIG',
      useValue: databaseConfig,
    },
  ],
})
export class DatabaseModule {}

// 在服务中使用
@Injectable()
export class DatabaseService {
  constructor(@Inject('DATABASE_CONFIG') private config: any) {
    console.log(`连接数据库: ${config.host}:${config.port}`);
  }
}

更多 useValue 的例子:

typescript
// 提供常量
@Module({
  providers: [
    {
      provide: 'API_VERSION',
      useValue: 'v1',
    },
    {
      provide: 'MAX_RETRY_COUNT',
      useValue: 3,
    },
    {
      provide: 'SUPPORTED_LANGUAGES',
      useValue: ['zh', 'en', 'jp'],
    },
  ],
})
export class ConfigModule {}

// 在服务中使用
@Injectable()
export class ApiService {
  constructor(
    @Inject('API_VERSION') private version: string,
    @Inject('MAX_RETRY_COUNT') private maxRetries: number,
    @Inject('SUPPORTED_LANGUAGES') private languages: string[]
  ) {
    console.log(`API版本: ${version}`);
    console.log(`最大重试次数: ${maxRetries}`);
    console.log(`支持的语言: ${languages.join(', ')}`);
  }
}

3. useFactory - 工厂提供者

使用函数来创建提供者,适合需要动态创建的场景:

typescript
// 简单的工厂函数
@Module({
  providers: [
    {
      provide: 'RANDOM_NUMBER',
      useFactory: () => {
        return Math.random(); // 每次都生成不同的随机数
      },
    },
  ],
})
export class RandomModule {}

复杂的工厂函数示例:

typescript
// 数据库连接工厂
@Injectable()
export class ConfigService {
  get(key: string): any {
    const config = {
      database: {
        host: 'localhost',
        port: 5432,
        name: 'myapp',
      },
    };
    return config[key];
  }
}

@Module({
  providers: [
    ConfigService,
    {
      provide: 'DATABASE_CONNECTION',
      useFactory: (configService: ConfigService) => {
        const dbConfig = configService.get('database');

        // 根据配置创建不同的连接
        if (process.env.NODE_ENV === 'test') {
          return {
            type: 'memory',
            database: ':memory:',
          };
        }

        return {
          type: 'postgres',
          host: dbConfig.host,
          port: dbConfig.port,
          database: dbConfig.name,
        };
      },
      inject: [ConfigService], // 注入依赖
    },
  ],
})
export class DatabaseModule {}

异步工厂函数:

typescript
@Module({
  providers: [
    {
      provide: 'ASYNC_DATA',
      useFactory: async () => {
        // 异步获取数据
        const response = await fetch('https://api.example.com/config');
        const data = await response.json();
        return data;
      },
    },
  ],
})
export class AsyncModule {}

4. useExisting - 别名提供者

为已存在的提供者创建别名:

typescript
@Injectable()
export class LoggerService {
  log(message: string) {
    console.log(`[LOG] ${message}`);
  }
}

@Module({
  providers: [
    LoggerService,
    {
      provide: 'LOGGER',
      useExisting: LoggerService, // 为LoggerService创建别名
    },
  ],
})
export class LoggerModule {}

// 现在可以用两种方式注入同一个服务
@Injectable()
export class UserService {
  constructor(
    private logger: LoggerService, // 直接注入
    @Inject('LOGGER') private logger2: LoggerService // 通过别名注入
  ) {
    // logger 和 logger2 是同一个实例
  }
}

自定义提供者的高级用法

1. 接口作为提供者标识符

typescript
// 定义接口
interface IPaymentService {
  processPayment(amount: number): Promise<boolean>;
}

// 支付宝支付服务
@Injectable()
export class AlipayService implements IPaymentService {
  async processPayment(amount: number): Promise<boolean> {
    console.log(`使用支付宝支付 ${amount} 元`);
    return true;
  }
}

// 微信支付服务
@Injectable()
export class WechatPayService implements IPaymentService {
  async processPayment(amount: number): Promise<boolean> {
    console.log(`使用微信支付 ${amount} 元`);
    return true;
  }
}

// 根据条件选择支付方式
@Module({
  providers: [
    AlipayService,
    WechatPayService,
    {
      provide: 'PAYMENT_SERVICE',
      useFactory: () => {
        // 根据配置选择支付方式
        const paymentType = process.env.PAYMENT_TYPE || 'alipay';
        return paymentType === 'alipay'
          ? new AlipayService()
          : new WechatPayService();
      },
    },
  ],
})
export class PaymentModule {}

// 使用
@Injectable()
export class OrderService {
  constructor(
    @Inject('PAYMENT_SERVICE') private paymentService: IPaymentService
  ) {}

  async createOrder(amount: number) {
    const success = await this.paymentService.processPayment(amount);
    return success ? '订单创建成功' : '支付失败';
  }
}

2. 多个提供者的工厂函数

typescript
@Injectable()
export class DatabaseService {
  connect() {
    return 'database connected';
  }
}

@Injectable()
export class CacheService {
  connect() {
    return 'cache connected';
  }
}

@Module({
  providers: [
    DatabaseService,
    CacheService,
    {
      provide: 'CONNECTION_MANAGER',
      useFactory: (db: DatabaseService, cache: CacheService) => {
        return {
          initialize: async () => {
            const dbResult = db.connect();
            const cacheResult = cache.connect();
            return `初始化完成: ${dbResult}, ${cacheResult}`;
          },
          healthCheck: () => {
            return '所有服务正常';
          },
        };
      },
      inject: [DatabaseService, CacheService],
    },
  ],
})
export class ConnectionModule {}

提供者的作用域(Scope)

1. 单例模式(默认)

typescript
@Injectable() // 默认是单例
export class UserService {
  private counter = 0;

  getCounter() {
    return ++this.counter;
  }
}

// 无论在哪里注入,都是同一个实例
@Controller('test')
export class TestController {
  constructor(private userService: UserService) {}

  @Get('counter1')
  getCounter1() {
    return this.userService.getCounter(); // 返回 1
  }

  @Get('counter2')
  getCounter2() {
    return this.userService.getCounter(); // 返回 2
  }
}

2. 请求作用域

typescript
import { Injectable, Scope } from '@nestjs/common';

@Injectable({ scope: Scope.REQUEST })
export class RequestScopedService {
  private counter = 0;

  getCounter() {
    return ++this.counter;
  }
}

// 每个HTTP请求都会创建新的实例
@Controller('test')
export class TestController {
  constructor(private requestService: RequestScopedService) {}

  @Get('counter1')
  getCounter1() {
    return this.requestService.getCounter(); // 每次请求都返回 1
  }

  @Get('counter2')
  getCounter2() {
    return this.requestService.getCounter(); // 每次请求都返回 1
  }
}

3. 瞬时作用域

typescript
@Injectable({ scope: Scope.TRANSIENT })
export class TransientService {
  private id = Math.random();

  getId() {
    return this.id;
  }
}

// 每次注入都创建新实例
@Injectable()
export class MyService {
  constructor(
    private transient1: TransientService,
    private transient2: TransientService
  ) {
    console.log(transient1.getId()); // 随机数1
    console.log(transient2.getId()); // 随机数2,不同的实例
  }
}

可选依赖

有时候某个依赖可能不存在,可以使用 @Optional() 装饰器:

typescript
import { Injectable, Optional } from '@nestjs/common';

@Injectable()
export class UserService {
  constructor(@Optional() @Inject('LOGGER') private logger?: any) {
    if (this.logger) {
      this.logger.log('UserService 已创建');
    }
  }

  findAll() {
    // 安全地使用可选依赖
    this.logger?.log('查询所有用户');
    return [];
  }
}

循环依赖处理

问题示例

typescript
// ❌ 会导致循环依赖错误
@Injectable()
export class UserService {
  constructor(private postService: PostService) {}
}

@Injectable()
export class PostService {
  constructor(private userService: UserService) {}
}

解决方案 1:使用 forwardRef

typescript
import { Injectable, forwardRef, Inject } from '@nestjs/common';

@Injectable()
export class UserService {
  constructor(
    @Inject(forwardRef(() => PostService))
    private postService: PostService
  ) {}

  getUserPosts(userId: number) {
    return this.postService.findByUserId(userId);
  }
}

@Injectable()
export class PostService {
  constructor(
    @Inject(forwardRef(() => UserService))
    private userService: UserService
  ) {}

  findByUserId(userId: number) {
    const user = this.userService.findById(userId);
    return user ? [] : null;
  }
}

解决方案 2:重构设计

typescript
// 创建一个共享的数据服务
@Injectable()
export class DataService {
  private users = [];
  private posts = [];

  getUsers() {
    return this.users;
  }
  getPosts() {
    return this.posts;
  }
  addUser(user: any) {
    this.users.push(user);
  }
  addPost(post: any) {
    this.posts.push(post);
  }
}

@Injectable()
export class UserService {
  constructor(private dataService: DataService) {}

  findById(id: number) {
    return this.dataService.getUsers().find((u) => u.id === id);
  }
}

@Injectable()
export class PostService {
  constructor(private dataService: DataService) {}

  findByUserId(userId: number) {
    return this.dataService.getPosts().filter((p) => p.userId === userId);
  }
}

实际应用综合示例

让我们创建一个完整的博客系统示例:

typescript
// 1. 配置服务
@Injectable()
export class ConfigService {
  private config = {
    database: {
      host: process.env.DB_HOST || 'localhost',
      port: parseInt(process.env.DB_PORT) || 5432,
      name: process.env.DB_NAME || 'blog',
    },
    jwt: {
      secret: process.env.JWT_SECRET || 'secret',
      expiresIn: '24h',
    },
    upload: {
      maxSize: 5 * 1024 * 1024, // 5MB
      allowedTypes: ['image/jpeg', 'image/png'],
    },
  };

  get(key: string): any {
    return this.config[key];
  }
}

// 2. 数据库服务
@Injectable()
export class DatabaseService {
  constructor(private configService: ConfigService) {}

  async connect() {
    const dbConfig = this.configService.get('database');
    console.log(
      `连接数据库: ${dbConfig.host}:${dbConfig.port}/${dbConfig.name}`
    );
    return 'connected';
  }

  async query(sql: string, params: any[] = []) {
    console.log(`执行查询: ${sql}`, params);
    return [];
  }
}

// 3. 日志服务
@Injectable()
export class LoggerService {
  log(message: string) {
    console.log(`[${new Date().toISOString()}] ${message}`);
  }

  error(message: string, error?: any) {
    console.error(`[${new Date().toISOString()}] ERROR: ${message}`, error);
  }
}

// 4. 文件上传服务
@Injectable()
export class FileUploadService {
  constructor(
    private configService: ConfigService,
    private logger: LoggerService
  ) {}

  async uploadFile(file: any): Promise<string> {
    const uploadConfig = this.configService.get('upload');

    if (file.size > uploadConfig.maxSize) {
      this.logger.error(`文件太大: ${file.size} bytes`);
      throw new Error('文件太大');
    }

    if (!uploadConfig.allowedTypes.includes(file.mimetype)) {
      this.logger.error(`不支持的文件类型: ${file.mimetype}`);
      throw new Error('不支持的文件类型');
    }

    const filename = `${Date.now()}-${file.originalname}`;
    this.logger.log(`上传文件: ${filename}`);

    // 实际的文件上传逻辑
    return `/uploads/${filename}`;
  }
}

// 5. 用户服务
@Injectable()
export class UserService {
  constructor(private db: DatabaseService, private logger: LoggerService) {}

  async findById(id: number) {
    this.logger.log(`查找用户: ${id}`);
    const result = await this.db.query('SELECT * FROM users WHERE id = ?', [
      id,
    ]);
    return result[0];
  }

  async create(userData: any) {
    this.logger.log(`创建用户: ${userData.username}`);
    const result = await this.db.query(
      'INSERT INTO users (username, email) VALUES (?, ?)',
      [userData.username, userData.email]
    );
    return result;
  }
}

// 6. 文章服务
@Injectable()
export class PostService {
  constructor(
    private db: DatabaseService,
    private logger: LoggerService,
    private userService: UserService
  ) {}

  async findAll() {
    this.logger.log('获取所有文章');
    return await this.db.query('SELECT * FROM posts');
  }

  async create(postData: any) {
    this.logger.log(`创建文章: ${postData.title}`);

    // 验证作者存在
    const author = await this.userService.findById(postData.authorId);
    if (!author) {
      throw new Error('作者不存在');
    }

    return await this.db.query(
      'INSERT INTO posts (title, content, author_id) VALUES (?, ?, ?)',
      [postData.title, postData.content, postData.authorId]
    );
  }
}

// 7. 控制器
@Controller('posts')
export class PostController {
  constructor(
    private postService: PostService,
    private fileUploadService: FileUploadService,
    private logger: LoggerService
  ) {}

  @Get()
  async findAll() {
    return await this.postService.findAll();
  }

  @Post()
  async create(@Body() createPostDto: any) {
    this.logger.log('收到创建文章请求');
    return await this.postService.create(createPostDto);
  }

  @Post('upload')
  async uploadImage(@Body() file: any) {
    const imageUrl = await this.fileUploadService.uploadFile(file);
    return { url: imageUrl };
  }
}

// 8. 模块配置
@Module({
  providers: [
    ConfigService,
    DatabaseService,
    LoggerService,
    FileUploadService,
    UserService,
    PostService,

    // 使用工厂函数创建数据库连接
    {
      provide: 'DATABASE_CONNECTION',
      useFactory: async (configService: ConfigService) => {
        const dbConfig = configService.get('database');
        return `postgresql://${dbConfig.host}:${dbConfig.port}/${dbConfig.name}`;
      },
      inject: [ConfigService],
    },

    // 提供JWT配置
    {
      provide: 'JWT_CONFIG',
      useFactory: (configService: ConfigService) => {
        return configService.get('jwt');
      },
      inject: [ConfigService],
    },
  ],
  controllers: [PostController],
  exports: [UserService, PostService], // 导出给其他模块使用
})
export class BlogModule {}

最佳实践

1. 服务命名规范

typescript
// ✅ 好的命名
@Injectable()
export class UserService {} // 用户相关的业务逻辑

@Injectable()
export class EmailService {} // 邮件发送服务

@Injectable()
export class PaymentService {} // 支付处理服务

// ❌ 避免的命名
@Injectable()
export class UserManager {} // 太泛化

@Injectable()
export class Helper {} // 没有明确含义

2. 单一职责原则

typescript
// ✅ 好的做法 - 每个服务只负责一个功能
@Injectable()
export class UserService {
  findAll() {}
  findById() {}
  create() {}
  update() {}
  delete() {}
}

@Injectable()
export class EmailService {
  sendWelcomeEmail() {}
  sendResetPasswordEmail() {}
}

// ❌ 避免的做法 - 一个服务做太多事情
@Injectable()
export class UserService {
  findAll() {}
  create() {}
  sendEmail() {} // 应该由EmailService处理
  processPayment() {} // 应该由PaymentService处理
}

3. 依赖注入最佳实践

typescript
// ✅ 好的做法 - 通过构造函数注入
@Injectable()
export class UserService {
  constructor(private db: DatabaseService, private logger: LoggerService) {}
}

// ❌ 避免的做法 - 直接创建依赖
@Injectable()
export class UserService {
  private db = new DatabaseService(); // 不要这样做
  private logger = new LoggerService(); // 不要这样做
}

总结

NestJS 提供者是应用程序的核心组件,它们:

  1. 封装业务逻辑 - 将复杂的业务逻辑封装在服务中
  2. 支持依赖注入 - 通过依赖注入实现松耦合
  3. 提供多种创建方式 - useClass、useValue、useFactory、useExisting
  4. 支持不同作用域 - 单例、请求作用域、瞬时作用域
  5. 易于测试 - 可以轻松替换为测试用的模拟对象

关键要点:

  • 使用 @Injectable() 装饰器标记提供者
  • 在模块的 providers 数组中注册
  • 通过构造函数注入使用
  • 根据需要选择合适的提供者类型
  • 遵循单一职责原则

从简单的服务开始,逐步掌握高级特性,你就能构建出优秀的 NestJS 应用!