史前史就是这个问题。
我修改了validationMiddleware
方法如下:
export async function validationMiddleware<T extends object>(
dtoClass: new () => T,
validateTarget: string,
req: Request,
res: Response,
next: NextFunction,
) {
const dtoInstance = new dtoClass();
const request = {
body: req.body,
query: req.query,
params: req.params,
};
Object.assign(dtoInstance, request[validateTarget as keyof typeof request]);
//request[validateTarget as keyof typeof request] = dtoInstance;
const errors: ValidationError[] = await validate(dtoInstance, {
forbidUnknownValues: true,
whitelist: true,
forbidNonWhitelisted: true,
});
if (errors.length > 0) {
const validationErrors = errors.map((error) => ({
[error.property]: Object.values(error.constraints || {}),
}));
const errorMessage = 'Invalid data provided';
const errorResponse = { message: errorMessage, errors: validationErrors };
return res.status(422).json(errorResponse);
}
next();
}
然后在路由器中我这样做:
userRoutes.post('/users/signup', (req, res, next) => {
validationMiddleware(CreateUserDto, 'body', req, res, next);
userController.signUp(req, res);
});
Cannot set headers after they are sent to the client
但是,如果我想向此路由发送消息,并且发送的对象没有验证错误,则会收到错误。如果有验证错误,我会在 Postman 中收到错误,但在这种情况下,控制器方法也会被执行。
我做了研究,发现我发送了两次响应。一次在validationMiddleware
方法中,一次在signUp
方法中。
为了解决这个问题,我尝试了以下方法:
- 解析
validate
结果以避免出现异步函数和同步流 - 引入一个
hasError
变量,并返回该变量,如果不为真,则调用控制器方法 - 如果没有错误,则在
else
语句中调用next()
return
发送响应后使用以终止中间件
没有成功。那么问题是什么呢?
编辑
现在,我重新设计了这个功能:
export function validationMiddleware<T extends object>(
dtoClass: new () => T,
validateTarget: string,
req: Request,
res: Response,
next: NextFunction,
) {
console.log('validation');
let hasErrors = false;
const dtoInstance = new dtoClass();
const request = {
body: req.body,
query: req.query,
params: req.params,
};
Object.assign(dtoInstance, request[validateTarget as keyof typeof request]);
//request[validateTarget as keyof typeof request] = dtoInstance;
//const errors: ValidationError[] = [];
validate(dtoInstance, {
forbidUnknownValues: true,
whitelist: true,
forbidNonWhitelisted: true,
}).then((errors) => {
if (errors.length > 0) {
const validationErrors = errors.map((error) => ({
[error.property]: Object.values(error.constraints || {}),
}));
const errorMessage = 'Invalid data provided';
const errorResponse = { message: errorMessage, errors: validationErrors };
res.status(422).json(errorResponse);
} else {
next();
}
});
}
没有更多的async
和await
。我读取承诺的值,如果有任何错误,则将其发送给客户端,否则转到下一个中间件(控制器)。
但在这种情况下,控制器也会执行。如果这是错误的方法,我该如何解决?我想要实现的是:
- 验证传入的请求
- 如果有错误,则将其发送给客户端
- 否则执行控制器逻辑
我可以将验证逻辑写入控制器,但我认为这不太干净,这就是为什么我尝试在控制器外部使用单独的函数。
问题在于,验证(解决承诺)发生在调用控制器方法之后。我知道这一点,但该如何解决呢?validate
该库的函数class-validator
返回一个Promise
。
编辑2:
事实证明,该validate
函数也有一个同步变量。因此:
const dtoInstance = new dtoClass();
const request = {
body: req.body,
query: req.query,
params: req.params,
};
Object.assign(dtoInstance, request[validateTarget as keyof typeof request]);
//request[validateTarget as keyof typeof request] = dtoInstance;
const errors: ValidationError[] = validateSync(dtoInstance, {
forbidUnknownValues: true,
whitelist: true,
forbidNonWhitelisted: true,
});
if (errors.length > 0) {
console.log('validation');
const validationErrors = errors.map((error) => ({
[error.property]: Object.values(error.constraints || {}),
}));
const errorMessage = 'Invalid data provided';
const errorResponse = { message: errorMessage, errors: validationErrors };
res.status(422).json(errorResponse);
}
return errors.length > 0;
在路线中:
userRoutes.post('/users/signup', (req, res, next) => {
if (!validationMiddleware(CreateUserDto, 'body', req, res, next))
userController.signUp(req, res);
});
现在它已按预期运行。
这看起来像是设计问题。就像上一篇文章
validationMiddleware
中提到的那样,它从 Express 中间件转换为某种混合中间件,虽然它本身不是中间件,但仍然依赖中间件参数。在这种情况下,没有必要将验证设置为同步。使用原始的 Promise 代替 可以解决类型问题,但缺乏一致的错误处理。async..await
一种更简洁的方法是将执行顺序委托给 Express 中间件堆栈,之前的变体
validationMiddleware
需要修复才能与 Express TypeScript 类型兼容:因此
next
,signUp
如果出现错误,就不会被调用。另一种方法是全局实现错误处理,并实现类似于此的自定义错误类。验证函数可以完全从中间件转换而来,并且与中间件的实现细节无关:
这依赖于Express 5 处理因拒绝的承诺而产生的错误的能力。