当前位置: 首页 > article >正文

从装饰器原理到实战:手把手教你用TypeScript为NestJS方法实现一个‘网络代理’

从装饰器原理到实战手把手教你用TypeScript为NestJS方法实现一个‘网络代理’在Node.js生态中装饰器Decorator作为一种元编程工具正逐渐从实验性特性转变为现代框架的核心支柱。NestJS正是这一趋势的典型代表——它巧妙利用装饰器语法糖将复杂的HTTP路由、依赖注入等机制简化为直观的Get()、Controller()等注解。但当我们频繁使用这些魔法时是否思考过它们背后的运作原理本文将带您从零实现一个能代理外部API请求的Get装饰器通过造轮子的方式揭开NestJS部分底层设计的神秘面纱。1. 装饰器基础从语法到执行机制装饰器本质上是一个特殊的高阶函数它能够修改类、方法、访问器或属性的行为。TypeScript通过experimentalDecorators编译器选项提供了对装饰器的实验性支持。让我们先看一个最简单的方法装饰器示例function LogExecutionTime(target: any, key: string, descriptor: PropertyDescriptor) { const originalMethod descriptor.value; descriptor.value function(...args: any[]) { const start Date.now(); const result originalMethod.apply(this, args); console.log(方法 ${key} 执行耗时: ${Date.now() - start}ms); return result; }; } class Example { LogExecutionTime calculate() { // 模拟耗时操作 for(let i 0; i 1000000000; i) {} } }当调用new Example().calculate()时控制台会输出方法执行时间。这里有几个关键点需要注意参数结构方法装饰器接收三个参数target对于静态方法是类的构造函数对于实例方法是类的原型key被装饰方法的名称字符串或Symboldescriptor属性描述符核心是其中的value属性指向原始方法执行时机装饰器在类定义时立即执行而非实例化或方法调用时。上例中LogExecutionTime在Example类被加载时就会运行。描述符替换通过修改descriptor.value我们可以完全替换原始方法实现。这正是实现API代理装饰器的理论基础。2. 构建装饰器工厂动态配置代理URL直接使用装饰器虽然简单但缺乏灵活性。装饰器工厂模式返回装饰器的函数可以让我们传入配置参数const Get (baseUrl: string): MethodDecorator { return (target, key, descriptor) { // 装饰器实现将放在这里 }; };这种模式在NestJS中随处可见比如Get(/path)中的路径参数就是通过工厂传递的。现在我们可以这样使用自定义装饰器class VideoController { Get(https://api.example.com/videos) fetchVideos() {} }3. 实现HTTP代理整合Axios与异步处理核心挑战在于如何让装饰器自动完成网络请求并将结果注入原始方法。以下是关键实现步骤import axios, { AxiosResponse, AxiosError } from axios; const Get (url: string): MethodDecorator { return (target, key, descriptor: PropertyDescriptor) { const originalMethod descriptor.value; descriptor.value async function(...args: any[]) { try { const response await axios.get(url); return originalMethod.apply(this, [response.data, ...args]); } catch (error) { // 错误处理逻辑 if (axios.isAxiosError(error)) { return originalMethod.apply(this, [error.response?.data, ...args]); } throw error; } }; }; };这段代码实现了几个重要特性异步处理使用async/await处理Axios的Promise保持代码线性可读结果注入将API响应数据作为第一个参数传递给原始方法错误边界区分网络错误和业务错误防止未捕获的异常实际应用时控制器方法可以这样定义class VideoController { Get(https://api.apiopen.top/api/getHaoKanVideo) async getVideoList(data: any) { return { status: 200, videos: data.result.list }; } }4. 类型安全增强泛型与响应类型推断上述实现存在明显的类型缺陷——data参数被定义为any失去了TypeScript的类型优势。我们可以通过泛型进行改进interface ApiResponseT any { code: number; message: string; result: T; } const Get T(url: string): MethodDecorator { return (target, key, descriptor: TypedPropertyDescriptor(data: ApiResponseT) void) { const originalMethod descriptor.value; descriptor.value async function(...args: any[]) { const response await axios.getApiResponseT(url); return originalMethod!.call(this, response.data); }; }; };现在可以精确指定响应数据类型interface Video { id: string; title: string; cover: string; } class VideoController { GetVideo[](https://api.apiopen.top/api/getHaoKanVideo) getVideoList(data: ApiResponseVideo[]) { // data.result现在被正确推断为Video[]类型 return data.result.map(video video.title); } }5. 高级技巧元数据存储与组合装饰器真正的NestJS装饰器通常会做更多工作比如存储路由元数据供框架后续使用。我们可以通过reflect-metadata库模拟这一机制import reflect-metadata; const Get (path: string): MethodDecorator { return (target, key, descriptor) { // 存储路由信息 Reflect.defineMetadata(path, path, target, key); Reflect.defineMetadata(method, GET, target, key); // 保留原始代理逻辑 const originalMethod descriptor.value; descriptor.value async function(...args) { const response await axios.get(path); return originalMethod!.call(this, response.data); }; }; };更进一步我们可以将功能拆分为多个装饰器然后组合使用function HttpMethod(method: string) { return (path: string): MethodDecorator { return (target, key, descriptor) { Reflect.defineMetadata(path, path, target, key); Reflect.defineMetadata(method, method, target, key); }; }; } const Get HttpMethod(GET); const Post HttpMethod(POST); // 使用示例 class UserController { Get(/users) listUsers() {} Post(/users) createUser() {} }这种架构设计正是NestJS灵活性的来源——每个装饰器只关注单一职责通过组合实现复杂功能。6. 实战优化请求拦截与缓存策略生产环境中的装饰器通常需要更多实用功能。以下是两个常见增强点请求拦截器集成const Get (url: string): MethodDecorator { return (target, key, descriptor) { const originalMethod descriptor.value; // 创建带拦截器的实例 const instance axios.create({ baseURL: url, timeout: 5000 }); instance.interceptors.request.use(config { console.log(请求 ${url} 开始); return config; }); descriptor.value async function(...args) { const response await instance.get(); return originalMethod!.call(this, response.data); }; }; };缓存策略实现const cache new Mapstring, any(); const GetWithCache (url: string, ttl: number 30000): MethodDecorator { return (target, key, descriptor) { const originalMethod descriptor.value; descriptor.value async function(...args) { if (cache.has(url)) { const { data, expires } cache.get(url); if (Date.now() expires) { return originalMethod!.call(this, data); } } const response await axios.get(url); cache.set(url, { data: response.data, expires: Date.now() ttl }); return originalMethod!.call(this, response.data); }; }; };7. 测试策略确保装饰器可靠性为装饰器编写测试需要特殊技巧因为它们在类定义阶段就执行。推荐使用Jest的describeit组合describe(Get装饰器, () { let mockAxios: jest.Mockedtypeof axios; beforeEach(() { mockAxios axios as jest.Mockedtypeof axios; jest.clearAllMocks(); }); it(应该正确代理GET请求, async () { const mockData { result: test }; mockAxios.get.mockResolvedValue({ data: mockData }); class TestClass { Get(https://api.example.com) method(data: any) { expect(data).toEqual(mockData); } } await new TestClass().method(); expect(mockAxios.get).toHaveBeenCalledWith(https://api.example.com); }); it(应该处理请求错误, async () { mockAxios.get.mockRejectedValue(new Error(Network error)); class TestClass { Get(https://api.example.com) method(data: any) { expect(data).toBeUndefined(); } } await expect(new TestClass().method()).rejects.toThrow(Network error); }); });关键测试点应包括正确发起请求并传递参数响应数据正确注入错误处理符合预期类型系统正常工作8. 性能考量与最佳实践装饰器虽然强大但不当使用可能带来性能问题避免过度装饰每个装饰器都会增加初始化开销慎用同步操作装饰器在类定义时同步执行耗时操作会拖慢启动速度内存泄漏闭包中引用大对象会导致内存无法释放推荐的最佳实践包括保持装饰器轻量将复杂逻辑延迟到方法调用时执行使用装饰器组合而非创建多功能巨型装饰器明确类型约束避免any泛滥破坏类型安全提供文档注释说明装饰器的用途和参数含义/** * 自动发起GET请求并将响应注入方法 * param url - 请求地址 * param config - 可选Axios配置 */ function Get(url: string, config?: AxiosRequestConfig): MethodDecorator { // 实现... }在NestJS实际开发中虽然我们通常会直接使用框架提供的装饰器但理解其实现原理对于调试复杂问题和编写高级功能至关重要。当您下次使用Get()定义路由时不妨思考一下它背后可能进行的操作——这正是区分普通开发者和框架专家的关键所在。

相关文章:

从装饰器原理到实战:手把手教你用TypeScript为NestJS方法实现一个‘网络代理’

从装饰器原理到实战:手把手教你用TypeScript为NestJS方法实现一个‘网络代理’ 在Node.js生态中,装饰器(Decorator)作为一种元编程工具,正逐渐从实验性特性转变为现代框架的核心支柱。NestJS正是这一趋势的典型代表—…...

Pixelle-Video完整指南:5分钟掌握AI全自动短视频制作

Pixelle-Video完整指南:5分钟掌握AI全自动短视频制作 【免费下载链接】Pixelle-Video 🚀 AI 全自动短视频引擎 | AI Fully Automated Short Video Engine 项目地址: https://gitcode.com/GitHub_Trending/pi/Pixelle-Video Pixelle-Video是一款革…...

别再被‘pip不是内部命令’搞懵了!Python新手必看的pip安装与修复保姆级教程(附ensurepip用法)

Python包管理革命:从pip失效到ensurepip的深度实践指南 为什么你的pip命令突然"罢工"了? 刚接触Python的新手们常常会遇到一个令人抓狂的问题——昨天还能正常使用的pip命令,今天突然提示"不是内部或外部命令"。这就像突…...

GHelper:华硕笔记本性能调优的终极解决方案

GHelper:华硕笔记本性能调优的终极解决方案 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, Zenbook, Expertbook, …...

IC617保姆级教程:用ADEXL和Calculator两步搞定CMOS晶体管的gmid设计曲线

IC617高效设计指南:ADEXL与Calculator协同生成CMOS晶体管gmid曲线的实战解析 在模拟集成电路设计中,gmid曲线作为评估晶体管工作状态的核心工具,直接影响着放大器的增益、噪声和功耗等关键指标。传统方法往往需要反复切换多个工具界面&#x…...

深度解析AI游戏瞄准辅助:从YOLOv10模型到实时视觉识别的完整技术架构

深度解析AI游戏瞄准辅助:从YOLOv10模型到实时视觉识别的完整技术架构 【免费下载链接】yolov8_aimbot Aim-bot based on AI for all FPS games 项目地址: https://gitcode.com/gh_mirrors/yo/yolov8_aimbot 在当今FPS游戏竞技领域,AI瞄准辅助技术…...

告别臃肿:Win11Debloat让你的Windows 11系统焕然一新

告别臃肿:Win11Debloat让你的Windows 11系统焕然一新 【免费下载链接】Win11Debloat A simple, lightweight PowerShell script that allows you to remove pre-installed apps, disable telemetry, as well as perform various other changes to declutter and cus…...

别再复制粘贴了!手把手教你用Git命令将本地项目一键推送到GitLab仓库(含常见错误解决)

从零掌握Git命令流:本地项目高效同步GitLab全指南 在代码协作开发中,Git已成为不可或缺的版本控制工具。尽管现代IDE提供了便捷的图形化Git操作界面,但真正理解并熟练运用Git命令行,才是开发者摆脱"界面依赖症"、深入掌…...

从一颗2N5551看懂半导体散热:热阻Rja、Rjc到底怎么测?对我们选型有啥用?

从一颗2N5551看懂半导体散热:热阻Rja、Rjc到底怎么测?对我们选型有啥用? 拆开一颗塑料封装的2N5551三极管,你会看到指甲盖大小的黑色环氧树脂包裹着不到1平方毫米的硅晶片。这个微型结构在工作时产生的热量,可能让芯片…...

别再傻等!解决conda install nb_conda卡在solving environment的3个高效方法(附清华源配置)

彻底解决conda install卡在solving environment的终极指南 当你满怀期待地在终端输入conda install nb_conda准备为Jupyter Notebook添加环境管理功能时,却发现进度条永远卡在"solving environment"这一步,这种体验就像在高速公路上遇到无休止…...

3步构建专业级无人机应用:DJI Android SDK V5实战指南

3步构建专业级无人机应用:DJI Android SDK V5实战指南 【免费下载链接】Mobile-SDK-Android-V5 MSDK V5 Sample 项目地址: https://gitcode.com/gh_mirrors/mo/Mobile-SDK-Android-V5 想要快速开发功能完善的无人机应用?DJI Android SDK V5为开发…...

Debian 12.9 最小化安装后,我这样配置成了一台全能家庭服务器(含桌面、DNS、Cockpit)

Debian 12.9 家庭服务器全栈配置指南:从零构建智能家居中枢 在数字化生活日益普及的今天,家庭服务器正逐渐成为现代智能家居的核心枢纽。一台经过精心配置的Debian服务器不仅能满足文件存储、媒体共享等基础需求,更能通过DNS解析、Web化管理等…...

Wayback Machine 浏览器扩展:一键穿越互联网历史的终极免费工具

Wayback Machine 浏览器扩展:一键穿越互联网历史的终极免费工具 【免费下载链接】wayback-machine-webextension A web browser extension for Chrome, Firefox, Edge, and Safari 14. 项目地址: https://gitcode.com/gh_mirrors/wa/wayback-machine-webextension…...

宝塔面板301重定向保姆级教程:从WWW跳转到Nginx/Apache配置文件修改,一篇搞定

宝塔面板301重定向深度实战:Nginx与Apache配置文件高阶玩法 当你发现宝塔面板的图形界面无法满足某些特殊重定向需求时,直接修改服务器配置文件才是真正的解决方案。本文将带你深入Nginx和Apache的配置世界,摆脱图形界面的限制,实…...

tRPC-Go 框架 01:tRPC-Go 总览与核心架构

tRPC-Go 框架 01:tRPC-Go 总览与核心架构 tRPC 是腾讯开源的多语言 RPC 框架,tRPC-Go 是其 Go 语言实现,已在腾讯内部支撑了海量服务(视频、音乐、新闻、广告等),日均调用量万亿级。本篇我们站高一点&…...

观察Taotoken用量看板如何帮助控制月度API支出

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 观察Taotoken用量看板如何帮助控制月度API支出 在项目开发中,大模型API的调用成本往往是预算管理的重要一环。如果缺乏…...

银河麒麟V10找不到应用商店?手把手教你从源码编译安装录屏神器Capture(附ffmpeg配置避坑)

银河麒麟V10系统下从源码构建专业录屏工具Capture的全流程指南 在国产操作系统银河麒麟V10上,许多用户发现系统默认没有提供应用商店,导致无法直接安装常用的录屏工具。本文将详细介绍如何从源码编译安装功能强大的录屏软件Capture,并解决ARM…...

RPC 核心概念 05:超时、重试、熔断与限流

RPC 核心概念 05:超时、重试、熔断与限流 如果说服务发现是 RPC 的"基础设施",那么超时、重试、熔断、限流就是 RPC 的安全气囊——决定了系统在故障来临时还能否站立。本篇讲清楚这四件套的边界、配合与陷阱。 一、为什么需要这些&#xff1f…...

用C#手搓ABB IRB 2600机器人正逆运动学(附完整代码与避坑指南)

从零实现ABB IRB 2600机器人运动学:C#实战与工业级代码优化 在工业机器人编程领域,能够将教科书上的数学公式转化为可靠的生产线代码是一项核心技能。ABB IRB 2600作为经典的六轴工业机器人,其运动学实现过程中存在诸多教科书不会提及的工程细…...

别再手动画图了!WPS PPT里这个‘转智能图形’功能,3秒让文字变高级图示

WPS PPT智能图形进阶指南:3秒实现专业级视觉表达 在快节奏的职场环境中,演示文档的视觉呈现往往决定着信息传递的效率。传统PPT制作中,将文字列表转换为可视化图形需要经历形状绘制、文字排版、配色调整等多道工序,耗时且难以保证…...

PX4固件编译避坑指南:自定义机型后如何正确生成airframe_metadata并更新QGC

PX4固件编译避坑指南:自定义机型后如何正确生成airframe_metadata并更新QGC 当你花费数小时精心设计了一个全新的无人机机型,修改完所有参数并准备在QGroundControl(QGC)中测试时,却发现地面站无法识别你的自定义机型—…...

Photoshop图层批量导出终极指南:告别手动操作的时代

Photoshop图层批量导出终极指南:告别手动操作的时代 【免费下载链接】Photoshop-Export-Layers-to-Files-Fast This script allows you to export your layers as individual files at a speed much faster than the built-in script from Adobe. 项目地址: https…...

别再死记硬背二进制转换了!用Python写个自动转换工具,顺便搞懂CPU是怎么算的

用Python打造二进制转换工具:从代码实践理解CPU运算本质 当我们在编程中遇到需要处理二进制数据时,是否曾对背后的计算机原理产生好奇?本文将通过构建一个Python数制转换工具,带你穿透代码表层,深入理解CPU如何处理二…...

宇视云团队模式访客预约操作流程

宇视云团队模式访客预约操作流程 本文将从创建访客邀请、来访信息登记、线上审批操作到最终多种方式通行,为您提供一步步的详细图文说明,引导企业内部员工和外部来访人员轻松上手,让访客接待更高效、更安全。 第一步:受访者创建“…...

iPhone升级iOS 16.1后WiFi总掉线?别急着重置,试试这个2.4GHz频段切换法

iPhone升级iOS 16.1后WiFi频繁断连的深度解决方案 最近不少iPhone用户在升级到iOS 16.1后反馈WiFi连接不稳定,尤其是在使用笔记本热点或双频路由器时频繁掉线。这个问题不仅影响工作效率,也让人倍感困扰。本文将深入分析问题根源,并提供一套…...

YOLO26 ONNX Runtime 部署实战:告别NMS后处理,边缘推理新标杆

🚀 YOLO26 ONNX Runtime 部署实战:告别NMS后处理,边缘推理新标杆 摘要: Ultralytics 重磅推出的 YOLO26 不仅在精度上实现了代际飞跃,更在架构层面进行了颠覆性革新——彻底移除了传统的 NMS(非极大值抑制)后处理环节。本文将带你深入了解 YOLO26 的核心优势,并基于 …...

宇视出入口相机抓拍调试速成方法

宇视出入口相机抓拍调试速成方法一、背景概述智慧园区、停车场等场景已普及宇视出入口抓拍相机,用于车辆出入管控与车牌识别。现场易受光照、车速、车道环境等影响,常出现漏抓、识别率低、画面模糊等问题。传统调试无标准、耗时长、上手门槛高&#xff0…...

别再死记硬背占空比了!用STM32 HAL库驱动MG90S舵机,我总结了这份避坑指南

STM32 HAL库驱动MG90S舵机:从参数计算到实战调试的全方位指南 刚接触STM32和舵机的新手们,是否曾被PWM配置中的各种参数搞得晕头转向?明明按照教程设置了占空比,舵机却纹丝不动;或者角度总是偏差几度,调试…...

如何在Java中极速处理百万级Excel数据?FastExcel高性能读写实战指南

如何在Java中极速处理百万级Excel数据?FastExcel高性能读写实战指南 【免费下载链接】fastexcel Generate and read big Excel files quickly 项目地址: https://gitcode.com/gh_mirrors/fas/fastexcel 面对海量Excel数据处理时,你是否曾因内存溢…...

Pure Live完整指南:3分钟掌握跨平台纯净直播聚合工具

Pure Live完整指南:3分钟掌握跨平台纯净直播聚合工具 【免费下载链接】pure_live A Flutter project can make you watch live with ease. 项目地址: https://gitcode.com/gh_mirrors/pu/pure_live 在当今数字娱乐时代,直播已成为人们日常娱乐的重…...