You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 07:44:55