TypeORM+NestJS应用中用户模型持久化时自动哈希密码的实现
在NestJS + TypeORM中自动哈希密码的解决方案
你遇到的问题根源在于:TypeORM的insert()方法是直接生成批量插入的SQL语句,不会实例化实体类,因此实体上的@BeforeInsert()这类监听器不会被触发。下面是几种可行的解决办法:
方案1:改用save()方法(最直接)
save()方法会实例化实体并触发所有实体监听器,适合单条用户创建的场景。
步骤1:改造用户实体
在实体中区分明文密码(仅用于接收输入)和哈希密码(存储到数据库),并通过监听器自动哈希:
import { Entity, PrimaryGeneratedColumn, Column, BeforeInsert, BeforeUpdate } from 'typeorm'; import * as bcrypt from 'bcrypt'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() username: string; // 明文密码:不持久化到数据库,仅用于接收前端输入 @Column({ select: false, insert: false, update: false }) password: string; // 数据库中存储的哈希密码列 @Column() hashedPassword: string; @BeforeInsert() @BeforeUpdate() async hashPassword() { // 仅当明文密码有值时才哈希(避免更新其他字段时重复哈希) if (this.password) { this.hashedPassword = await bcrypt.hash(this.password, 10); } } }
步骤2:在Service中使用save()
import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from './user.entity'; import { CreateUserDto } from './dto/create-user.dto'; @Injectable() export class UserService { constructor( @InjectRepository(User) private readonly userRepository: Repository<User>, ) {} async createUser(createUserDto: CreateUserDto) { const user = this.userRepository.create(createUserDto); // 实例化实体 return this.userRepository.save(user); // 触发@BeforeInsert } }
方案2:手动哈希后使用insert()(适合批量插入)
如果必须使用insert()(比如批量创建用户),可以在Service层先手动哈希密码,再执行插入:
import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from './user.entity'; import { CreateUserDto } from './dto/create-user.dto'; import * as bcrypt from 'bcrypt'; @Injectable() export class UserService { constructor( @InjectRepository(User) private readonly userRepository: Repository<User>, ) {} async batchCreateUsers(createUserDtos: CreateUserDto[]) { // 批量处理密码哈希 const usersWithHashedPassword = await Promise.all( createUserDtos.map(async (dto) => ({ ...dto, hashedPassword: await bcrypt.hash(dto.password, 10), password: undefined, // 移除明文密码,避免插入数据库 })), ); return this.userRepository.insert(usersWithHashedPassword); } }
方案3:自定义Repository封装哈希逻辑
可以创建自定义Repository,封装带有密码哈希的插入方法,让业务层调用更简洁:
步骤1:创建自定义Repository
import { EntityRepository, Repository } from 'typeorm'; import { User } from './user.entity'; import { CreateUserDto } from './dto/create-user.dto'; import * as bcrypt from 'bcrypt'; @EntityRepository(User) export class UserRepository extends Repository<User> { async createUserWithHash(createUserDto: CreateUserDto) { const hashedPassword = await bcrypt.hash(createUserDto.password, 10); return this.insert({ ...createUserDto, hashedPassword, }); } async batchCreateUsersWithHash(createUserDtos: CreateUserDto[]) { const usersWithHashed = await Promise.all( createUserDtos.map(async (dto) => ({ ...dto, hashedPassword: await bcrypt.hash(dto.password, 10), })), ); return this.insert(usersWithHashed); } }
步骤2:在Module中注册自定义Repository
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UserService } from './user.service'; import { UserRepository } from './user.repository'; @Module({ imports: [TypeOrmModule.forFeature([UserRepository])], providers: [UserService], }) export class UserModule {}
步骤3:在Service中调用自定义方法
import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { UserRepository } from './user.repository'; import { CreateUserDto } from './dto/create-user.dto'; @Injectable() export class UserService { constructor( @InjectRepository(UserRepository) private readonly userRepository: UserRepository, ) {} async createUser(createUserDto: CreateUserDto) { return this.userRepository.createUserWithHash(createUserDto); } }
方案4:使用TypeORM Subscriber(全局监听)
创建实体订阅者,全局监听用户实体的插入/更新事件,自动处理密码哈希。注意:这种方式仅在通过save()方法操作实体时生效,insert()方法仍然不会触发订阅者事件。
步骤1:创建Subscriber
import { EntitySubscriberInterface, EventSubscriber, InsertEvent, UpdateEvent } from 'typeorm'; import { User } from './user.entity'; import * as bcrypt from 'bcrypt'; @EventSubscriber() export class UserSubscriber implements EntitySubscriberInterface<User> { listenTo() { return User; } async beforeInsert(event: InsertEvent<User>) { if (event.entity.password) { event.entity.hashedPassword = await bcrypt.hash(event.entity.password, 10); } } async beforeUpdate(event: UpdateEvent<User>) { if (event.entity.password) { event.entity.hashedPassword = await bcrypt.hash(event.entity.password, 10); } } }
步骤2:在TypeORM配置中注册Subscriber
在app.module.ts的TypeORM配置中添加subscribers:
TypeOrmModule.forRoot({ // 其他配置... subscribers: [UserSubscriber], }),
内容的提问来源于stack exchange,提问作者hunglt.ee
相关产品推荐
相关产品推荐

