NestJS重定向时如何简便保留原请求的查询参数?
问题描述
我是NestJS开发新手,需要实现跨控制器的旧端点到新端点的重定向,做旧接口兼容,要求重定向过程完整保留动态查询参数。
典型场景:访问 /old-namespace/getStuff?foo=bar 时自动跳转到 /new-namespace/getStuff?foo=bar,因为查询参数完全动态,不想手动拼接重建查询字符串。
框架自带的@Redirect装饰器使用很便捷,基础用法示例如下:
@Get('docs') @Redirect('/docs', 302) getDocs(@Query('version') version) { if (version && version === '5') { return { url: '/docs/v5/' }; } }
但实际使用时这个装饰器重定向后会清空request.query,属于框架预期行为。之前尝试过编写中间件、拦截器、自定义装饰器解决,要么效果不稳定,要么实现太笨重侵入性强,想要更轻量的方案——最好能达到类似给@Redirect增加retainQuery = true配置项的简便使用体验。
最低侵入实现方案
完全不需要改动全局逻辑,两种方案按需选,都不需要手动拼接query:
- 单端点少量重定向场景:直接复用原生
@Redirect的动态url返回能力,加个10行以内的通用工具函数即可
import { Request } from 'express'; // 用Fastify的话替换对应Request类型即可,逻辑无差异 const appendQueryToPath = (targetPath: string, req: Request) => { const search = new URLSearchParams(req.query as Record<string, string>).toString(); return search ? `${targetPath}?${search}` : targetPath; };
在旧端点上正常使用@Redirect,url留空,方法内直接调用工具函数返回目标地址即可:
@Get('old-namespace/getStuff') @Redirect('', 301) getOldStuff(@Req() req: Request) { return { url: appendQueryToPath('/new-namespace/getStuff', req) }; }
- 多端点批量重定向场景:封装一个和原生
@Redirect使用体验一致的自定义装饰器,内置query透传逻辑,一次封装到处用,完全不需要在每个路由方法里写重复逻辑
import { Redirect, ExecutionContext, CallHandler, NestInterceptor, UseInterceptors, SetMetadata, applyDecorators, HttpStatus } from '@nestjs/common'; import { Request } from 'express'; const REDIRECT_TARGET_META = 'redirect_with_query_target'; const REDIRECT_STATUS_META = 'redirect_with_query_status'; class RedirectQueryInterceptor implements NestInterceptor { intercept(context: ExecutionContext) { const handler = context.getHandler(); const targetPath = Reflect.getMetadata(REDIRECT_TARGET_META, handler); const statusCode = Reflect.getMetadata(REDIRECT_STATUS_META, handler); const req = context.switchToHttp().getRequest<Request>(); const res = context.switchToHttp().getResponse(); const search = new URLSearchParams(req.query as Record<string, string>).toString(); const finalUrl = search ? `${targetPath}?${search}` : targetPath; res.redirect(statusCode, finalUrl); } } export const RedirectRetainQuery = ( targetPath: string, statusCode: number = HttpStatus.FOUND ) => { return applyDecorators( SetMetadata(REDIRECT_TARGET_META, targetPath), SetMetadata(REDIRECT_STATUS_META, statusCode), UseInterceptors(RedirectQueryInterceptor) ); };
封装完成后使用方式和原生装饰器完全一致,不需要写额外逻辑:
// 单个方法使用 @Get('old-namespace/getStuff') @RedirectRetainQuery('/new-namespace/getStuff', HttpStatus.MOVED_PERMANENTLY) getOldStuff() { // 方法内不需要写任何重定向相关代码,自动透传所有query参数 } // 整个控制器下的路由批量重定向时,直接把装饰器加在控制器类上即可 @Controller('old-namespace') @RedirectRetainQuery('/new-namespace', 301) export class OldController { // 该控制器下所有路由都会自动把query透传到新命名空间对应路径 }
这个方案比全局中间件轻量很多,只会在你加了装饰器的路由上生效,不会误伤其他正常业务路由,也不需要维护复杂的路径匹配规则,侵入性几乎为零。
内容的提问来源于stack exchange,提问作者beepboop928347
相关产品推荐
相关产品推荐

