如何处理 NestJS 中的 TypeORM 实体字段唯一验证错误?
How to handle TypeORM entity field unique validation error in NestJS?
我在我的 TypeORM 实体字段电子邮件上设置了一个自定义的唯一验证器装饰器。 NestJS有依赖注入,但是服务没有注入
错误是:
TypeError: Cannot read property 'findByEmail' of undefined
对实现自定义电子邮件验证器有什么帮助吗?
user.entity.ts
:
@Column()
@Validate(CustomEmail, {
message: "Title is too short or long!"
})
@IsEmail()
email: string;
我的 CustomEmail
验证器是
import {ValidatorConstraint, ValidatorConstraintInterface,
ValidationArguments} from "class-validator";
import {UserService} from "./user.service";
@ValidatorConstraint({ name: "customText", async: true })
export class CustomEmail implements ValidatorConstraintInterface {
constructor(private userService: UserService) {}
async validate(text: string, args: ValidationArguments) {
const user = await this.userService.findByEmail(text);
return !user;
}
defaultMessage(args: ValidationArguments) {
return "Text ($value) is too short or too long!";
}
}
我知道我可以在 Column
选项中设置 unique
@Column({
unique: true
})
但这会引发 mysql 错误和 ExceptionsHandler
导致我的应用程序崩溃,所以我无法自己处理...
谢谢!
我已经修改了我的代码。我正在检查用户服务中 username/email 的唯一性(而不是自定义验证器)和 return HttpExcetion 以防用户已插入数据库。
我可以在这里提出 2 种不同的方法,第一种在本地捕获约束违反错误而无需额外请求,第二种使用全局错误过滤器,在整个应用程序中捕获此类错误。我个人使用后者。
本地无db请求解决方案
不需要额外的数据库请求。您可以捕获违反唯一约束的错误并向客户端抛出您想要的任何 HttpException
。在 users.service.ts
:
public create(newUser: Partial<UserEntity>): Promise<UserEntity> {
return this.usersRepository.save(newUser).catch((e) => {
if (/(email)[\s\S]+(already exists)/.test(e.detail)) {
throw new BadRequestException(
'Account with this email already exists.',
);
}
return e;
});
}
哪个 return:
全局错误过滤解决方案
或者甚至创建一个全局 QueryErrorFilter:
@Catch(QueryFailedError)
export class QueryErrorFilter extends BaseExceptionFilter {
public catch(exception: any, host: ArgumentsHost): any {
const detail = exception.detail;
if (typeof detail === 'string' && detail.includes('already exists')) {
const messageStart = exception.table.split('_').join(' ') + ' with';
throw new BadRequestException(
exception.detail.replace('Key', messageStart),
);
}
return super.catch(exception, host);
}
}
然后在 main.ts
:
async function bootstrap() {
const app = await NestFactory.create(/**/);
/* ... */
const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(new QueryErrorFilter(httpAdapter));
/* ... */
await app.listen(3000);
}
bootstrap();
这将给出通用的 $table entity with ($field)=($value) already exists.
错误消息。示例:
最简单的解决方案!
@Entity()
export class MyEntity extends BaseEntity{
@Column({unique:true}) name:string;
}
export abstract class BaseDataService<T> {
constructor(protected readonly repo: Repository<T>) {}
private async isUnique(t: any) {
const uniqueColumns = this.repo.metadata.uniques.map(
(e) => e.givenColumnNames[0]
);
for (const u of uniqueColumns) {
const count = await this.repo.count({ [u]: t[u] });
if (count > 0) {
throw new UnprocessableEntityException(`${u} must be unique!`);
}
}
}
async save(body: DeepPartial<T>) {
await this.isUnique(body);
try {
return await this.repo.save(body);
} catch (err) {
throw new UnprocessableEntityException(err.message);
}
}
async update(id: number, updated: QueryDeepPartialEntity<T>) {
await this.isUnique(updated)
try {
return await this.repo.update(id, updated);
} catch (err) {
throw new UnprocessableEntityException(err.message);
}
}
}
试试这个解决方案。
UniqueValidation.ts
import { HttpException, HttpStatus } from '@nestjs/common';
import { Repository } from 'typeorm';
export async function emailUnique(
newV: any,
repo: Repository<any>,
updateMode = false,
oldV: any = null,
) {
if (!updateMode) {
const unique = await repo.find({
where: [{ email: newV.email }],
});
if (unique.length !== 0) {
throw new HttpException(`email must be unique!`, HttpStatus.BAD_REQUEST);
}
return false;
} else {
const subquery = await repo
.createQueryBuilder('user')
.select('user.email')
.where('user.email != :oldEmail')
.andWhere('user.user_id_pk != :id ')
.setParameters({
id: oldV.id,
oldEmail: oldV.email,
})
.getMany();
console.log(subquery);
if (subquery.findIndex((item) => item.email === newV.email) > -1) {
throw new HttpException(`email must be unique!`, HttpStatus.BAD_REQUEST);
}
return false;
}
}
user_service.ts 文件:
async createUser(createUserDto: CreateUserDto) {
await emailUnique(createUserDto, this.userRepository);
//code
});
this.userRepository.save(newUser);
return true;
}
async updateUser(id: number, updateUserDto: UpdateUserDto)
{
const user = await this.getUserById(id);
await emailUnique(updateUserDto, this.userRepository, true,
user);
}
我在我的 TypeORM 实体字段电子邮件上设置了一个自定义的唯一验证器装饰器。 NestJS有依赖注入,但是服务没有注入
错误是:
TypeError: Cannot read property 'findByEmail' of undefined
对实现自定义电子邮件验证器有什么帮助吗?
user.entity.ts
:
@Column()
@Validate(CustomEmail, {
message: "Title is too short or long!"
})
@IsEmail()
email: string;
我的 CustomEmail
验证器是
import {ValidatorConstraint, ValidatorConstraintInterface,
ValidationArguments} from "class-validator";
import {UserService} from "./user.service";
@ValidatorConstraint({ name: "customText", async: true })
export class CustomEmail implements ValidatorConstraintInterface {
constructor(private userService: UserService) {}
async validate(text: string, args: ValidationArguments) {
const user = await this.userService.findByEmail(text);
return !user;
}
defaultMessage(args: ValidationArguments) {
return "Text ($value) is too short or too long!";
}
}
我知道我可以在 Column
选项中设置 unique
@Column({
unique: true
})
但这会引发 mysql 错误和 ExceptionsHandler
导致我的应用程序崩溃,所以我无法自己处理...
谢谢!
我已经修改了我的代码。我正在检查用户服务中 username/email 的唯一性(而不是自定义验证器)和 return HttpExcetion 以防用户已插入数据库。
我可以在这里提出 2 种不同的方法,第一种在本地捕获约束违反错误而无需额外请求,第二种使用全局错误过滤器,在整个应用程序中捕获此类错误。我个人使用后者。
本地无db请求解决方案
不需要额外的数据库请求。您可以捕获违反唯一约束的错误并向客户端抛出您想要的任何 HttpException
。在 users.service.ts
:
public create(newUser: Partial<UserEntity>): Promise<UserEntity> {
return this.usersRepository.save(newUser).catch((e) => {
if (/(email)[\s\S]+(already exists)/.test(e.detail)) {
throw new BadRequestException(
'Account with this email already exists.',
);
}
return e;
});
}
哪个 return:
全局错误过滤解决方案
或者甚至创建一个全局 QueryErrorFilter:
@Catch(QueryFailedError)
export class QueryErrorFilter extends BaseExceptionFilter {
public catch(exception: any, host: ArgumentsHost): any {
const detail = exception.detail;
if (typeof detail === 'string' && detail.includes('already exists')) {
const messageStart = exception.table.split('_').join(' ') + ' with';
throw new BadRequestException(
exception.detail.replace('Key', messageStart),
);
}
return super.catch(exception, host);
}
}
然后在 main.ts
:
async function bootstrap() {
const app = await NestFactory.create(/**/);
/* ... */
const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(new QueryErrorFilter(httpAdapter));
/* ... */
await app.listen(3000);
}
bootstrap();
这将给出通用的 $table entity with ($field)=($value) already exists.
错误消息。示例:
最简单的解决方案!
@Entity()
export class MyEntity extends BaseEntity{
@Column({unique:true}) name:string;
}
export abstract class BaseDataService<T> {
constructor(protected readonly repo: Repository<T>) {}
private async isUnique(t: any) {
const uniqueColumns = this.repo.metadata.uniques.map(
(e) => e.givenColumnNames[0]
);
for (const u of uniqueColumns) {
const count = await this.repo.count({ [u]: t[u] });
if (count > 0) {
throw new UnprocessableEntityException(`${u} must be unique!`);
}
}
}
async save(body: DeepPartial<T>) {
await this.isUnique(body);
try {
return await this.repo.save(body);
} catch (err) {
throw new UnprocessableEntityException(err.message);
}
}
async update(id: number, updated: QueryDeepPartialEntity<T>) {
await this.isUnique(updated)
try {
return await this.repo.update(id, updated);
} catch (err) {
throw new UnprocessableEntityException(err.message);
}
}
}
试试这个解决方案。
UniqueValidation.ts
import { HttpException, HttpStatus } from '@nestjs/common';
import { Repository } from 'typeorm';
export async function emailUnique(
newV: any,
repo: Repository<any>,
updateMode = false,
oldV: any = null,
) {
if (!updateMode) {
const unique = await repo.find({
where: [{ email: newV.email }],
});
if (unique.length !== 0) {
throw new HttpException(`email must be unique!`, HttpStatus.BAD_REQUEST);
}
return false;
} else {
const subquery = await repo
.createQueryBuilder('user')
.select('user.email')
.where('user.email != :oldEmail')
.andWhere('user.user_id_pk != :id ')
.setParameters({
id: oldV.id,
oldEmail: oldV.email,
})
.getMany();
console.log(subquery);
if (subquery.findIndex((item) => item.email === newV.email) > -1) {
throw new HttpException(`email must be unique!`, HttpStatus.BAD_REQUEST);
}
return false;
}
}
user_service.ts 文件:
async createUser(createUserDto: CreateUserDto) {
await emailUnique(createUserDto, this.userRepository);
//code
});
this.userRepository.save(newUser);
return true;
}
async updateUser(id: number, updateUserDto: UpdateUserDto)
{
const user = await this.getUserById(id);
await emailUnique(updateUserDto, this.userRepository, true,
user);
}