提供者
什么是提供者(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. 创建一个简单的服务
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 提供者是应用程序的核心组件,它们:
- 封装业务逻辑 - 将复杂的业务逻辑封装在服务中
- 支持依赖注入 - 通过依赖注入实现松耦合
- 提供多种创建方式 - useClass、useValue、useFactory、useExisting
- 支持不同作用域 - 单例、请求作用域、瞬时作用域
- 易于测试 - 可以轻松替换为测试用的模拟对象
关键要点:
- 使用
@Injectable()
装饰器标记提供者 - 在模块的
providers
数组中注册 - 通过构造函数注入使用
- 根据需要选择合适的提供者类型
- 遵循单一职责原则
从简单的服务开始,逐步掌握高级特性,你就能构建出优秀的 NestJS 应用!