NestJS工厂模式实现多类型登录遇signin调用错误求助
多类型登录实现问题修复与优化方案
一、当前错误原因分析
你遇到的loginFactory.signin is not a function错误,核心原因是工厂函数返回的是服务类的引用,而非实例:
authFactory.ts中loginFactoryTypes存储的是CustomerAuthService、UserAuthService这些类本身,不是通过依赖注入创建的实例signin是类的实例方法,必须通过类的实例才能调用,直接用类引用调用自然会报错
二、快速修复方案
方案1:改造工厂函数返回实例(需手动处理依赖,不推荐用于NestJS)
如果需要快速临时修复,可以在工厂函数中手动实例化类,但要注意类中的依赖(比如customersService、jwtService)需要手动传入,这种方式会脱离NestJS的DI容器管理,后续维护成本高:
// authFactory.ts import { CustomersService } from '../customers/customers.service'; import { JwtService } from '@nestjs/jwt'; const loginFactoryTypes = { [LOGIN_TYPE.Customer]: new CustomerAuthService(new CustomersService(), new JwtService({})), [LOGIN_TYPE.User]: new UserAuthService(...), // 同理传入对应依赖 }; export const createLoginFactory = (type: string) => { return loginFactoryTypes[type]; };
方案2:利用NestJS的ModuleRef获取实例(推荐)
在NestJS中,服务实例由DI容器管理,正确的做法是通过ModuleRef动态获取服务实例,自动处理依赖注入:
- 在
AuthModule中注册所有登录服务:
// auth.module.ts @Module({ providers: [CustomerAuthService, UserAuthService, JwtService], controllers: [AuthController], }) export class AuthModule {}
- 在控制器中注入
ModuleRef,改造工厂逻辑:
// auth.controller.ts import { ModuleRef, BadRequestException } from '@nestjs/core'; @Controller('auth') export class AuthController { constructor(private readonly moduleRef: ModuleRef) {} @Post('/signin') async signInFactory(@Body() signInDto: SignInUserDto | SignInCustomerDto): Promise<unknown> { const { type } = signInDto; // 映射登录类型到对应服务类 const serviceMap = { [LOGIN_TYPE.Customer]: CustomerAuthService, [LOGIN_TYPE.User]: UserAuthService, }; // 通过ModuleRef获取DI容器中的服务实例 const authService = this.moduleRef.get(serviceMap[type], { strict: false }); if (!authService) { throw new BadRequestException('不支持的登录类型'); } return authService.signin(signInDto); } }
三、更优实现方案:策略模式+依赖注入
在NestJS中,处理多类型登录更优雅的方式是使用策略模式,将不同登录逻辑封装为独立策略,通过统一入口调度:
1. 定义抽象登录策略接口
// auth-strategy.interface.ts export interface AuthStrategy { type: LOGIN_TYPE; signin(dto: any): Promise<{ token: string; user: any }>; }
2. 实现具体登录策略
// customer-auth.strategy.ts @Injectable() export class CustomerAuthStrategy implements AuthStrategy { type = LOGIN_TYPE.Customer; constructor( private readonly customersService: CustomersService, private readonly jwtService: JwtService, ) {} async signin(signInDto: SignInCustomerDto) { const customer = await this.customersService.findCustomerByEmail(signInDto); const token = this.jwtService.sign({ id: customer.id, email: customer.email }); return { token, customer }; } } // user-auth.strategy.ts(用户登录策略同理实现) @Injectable() export class UserAuthStrategy implements AuthStrategy { type = LOGIN_TYPE.User; constructor(...) {} async signin(signInDto: SignInUserDto) { // 用户登录逻辑实现 } }
3. 创建策略管理器
// auth-strategy-manager.ts @Injectable() export class AuthStrategyManager { private strategyMap: Map<string, AuthStrategy> = new Map(); // 自动注入所有实现AuthStrategy的策略类 constructor(strategies: AuthStrategy[]) { strategies.forEach(strategy => { this.strategyMap.set(strategy.type, strategy); }); } getStrategy(type: string): AuthStrategy { const strategy = this.strategyMap.get(type); if (!strategy) { throw new BadRequestException('不支持的登录类型'); } return strategy; } }
4. 控制器中使用策略管理器
// auth.controller.ts @Controller('auth') export class AuthController { constructor(private readonly strategyManager: AuthStrategyManager) {} @Post('/signin') async signIn(@Body() signInDto: SignInUserDto | SignInCustomerDto): Promise<unknown> { const { type } = signInDto; const strategy = this.strategyManager.getStrategy(type); return strategy.signin(signInDto); } }
5. 在Module中注册相关类
// auth.module.ts @Module({ providers: [ CustomerAuthStrategy, UserAuthStrategy, AuthStrategyManager, CustomersService, JwtService, ], controllers: [AuthController], }) export class AuthModule {}
这种方案的优势:
- 符合开闭原则:新增登录类型只需新增策略类,无需修改原有代码
- 依赖注入完全由NestJS容器管理,避免手动处理依赖的问题
- 代码结构清晰,各职责分离,便于维护和测试
四、DTO优化建议
当前用SignInUserDto & SignInCustomerDto会导致DTO包含所有字段,不够严谨,建议改为联合类型并抽象基础字段:
// base-signin.dto.ts export class BaseSignInDto { @IsString() type: LOGIN_TYPE; @IsEmail() email: string; @IsString() password: string; } // customer-signin.dto.ts export class CustomerSignInDto extends BaseSignInDto { @IsString() store: string; } // user-signin.dto.ts export class UserSignInDto extends BaseSignInDto { // 用户登录特有的字段 } // 控制器中使用联合类型 @Post('/signin') async signIn(@Body() signInDto: CustomerSignInDto | UserSignInDto): Promise<unknown> { // ... }
内容的提问来源于stack exchange,提问作者Terra Tor
相关产品推荐
相关产品推荐

