尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
第 5 章 网关层与通信基础
本章目标理解网关层的作用学会设计统一的通信抽象层⭐⭐⭐ 难度进阶基础 | ⏱️ 预计时间1.5 小时5.1 为什么需要网关层先想一个问题如果让你直接在组件里调用接口你可能会这么写function AgentList() { const [agents, setAgents] useState([]); useEffect(() { // 直接用 axios 发请求 axios.get(/api/agents).then(res { setAgents(res.data); }); }, []); // ... }看起来没问题对吧但是项目变大之后你会遇到这些问题问题说明接口满天飞每个组件自己发请求到处都是 axios 调用数据格式不统一这个接口返回{ code, data }那个返回{ status, result }通信方式多样有的接口走 HTTP有的走 WebSocket还有的要轮询难测试组件直接依赖 axios写测试要 mock 半天换接口费劲后端改了接口路径要到处找哪里调用了网关层就是来解决这些问题的。它就像一个「翻译官」把各种乱七八糟的通信方式翻译成统一的、好用的接口给上层用。5.2 网关层设计思想门面模式Facade Pattern网关层用的是门面模式——对外提供一个统一的大接口内部封装了复杂的子系统。上层代码Store / 组件 │ ▼ ┌───────────────────┐ │ Gateway 门面 │ ← 统一的、干净的接口 │ getAgents()│ │ sendMessage()│ │... │ └─────────┬─────────┘ │ ┌─────┴─────┐ ▼ ▼ HTTP WebSocket(axios)(原生WS)上层代码只知道「我可以调用这些方法」不用管底层是 HTTP 还是 WS不用管请求地址是什么不用管返回格式是什么。接口先行先定义契约好的做法是先定义接口interface再写实现。// 先定义网关应该提供哪些能力interfaceWorkbenchGateway{// 获取 Agent 列表getAgents():PromiseAgent[];// 获取会话列表getConversations(agentId:number):PromiseConversation[];// 发送消息sendMessage(params:SendMessageParams):PromiseSendMessageResult;// 订阅实时事件subscribeEvents(callback:(event:RealtimeEvent)void):()void;// ...更多方法}核心概念接口interface是契约。上层只依赖契约不依赖具体实现。这样以后换实现比如从 HTTP 换成 mock从 WS 换成 SSE上层代码一行都不用改。5.3 动手实现第一个网关安装 axiosnpminstallaxios# 或者yarnaddaxios创建 HTTP 客户端创建src/services/http.tsimportaxiosfromaxios;// 创建 axios 实例统一配置consthttpaxios.create({baseURL:/api,// 接口前缀timeout:30000,// 超时时间 30 秒});// 请求拦截器统一加 token 等http.interceptors.request.use(config{consttokenlocalStorage.getItem(token);if(token){config.headers.AuthorizationBearer${token};}returnconfig;});// 响应拦截器统一处理错误http.interceptors.response.use(response{// 统一返回 data 部分returnresponse.data;},error{console.error(请求出错,error);returnPromise.reject(error);});exportdefaulthttp;小贴士axios 拦截器是个好东西。所有请求统一加 token、所有响应统一处理错误不用每个请求都写一遍。定义类型和接口创建src/services/gateway/types.ts// 领域类型 exportinterfaceAgent{id:number;name:string;description:string;online:boolean;avatar?:string;}exportinterfaceConversation{id:string;title:string;agentId:number;lastMessage?:string;lastMessageAt?:string;unreadCount:number;pinned:boolean;createdAt:string;updatedAt:string;}exportinterfaceMessage{id:string;role:user|assistant;content:string;createdAt:string;status?:pending|sending|sent|error;}// 网关接口 exportinterfaceSendMessageParams{agentId:number;conversationId:string|null;// null 表示新建对话content:string;}exportinterfaceSendMessageResult{messageId:string;conversationId:string;accepted:boolean;}exportinterfaceWorkbenchGateway{// Agent 相关getAgents():PromiseAgent[];// 会话相关getConversations(agentId:number):PromiseConversation[];getHistory(conversationId:string):PromiseMessage[];// 消息相关sendMessage(params:SendMessageParams):PromiseSendMessageResult;}划重点类型定义是网关层的灵魂。所有进出网关的数据都有明确的类型。上层不用猜接口返回什么。实现 HTTP 网关创建src/services/gateway/HttpGateway.tsimporthttpfrom../http;importtype{Agent,Conversation,Message,WorkbenchGateway,SendMessageParams,SendMessageResult,}from./types;exportclassHttpGatewayimplementsWorkbenchGateway{// Agent 相关 asyncgetAgents():PromiseAgent[]{// 后端返回格式可能是 { code: 0, data: [...] }// 我们在这里统一解析返回干净的领域类型constresawaithttp.get(/agents);returnres.data;}// 会话相关 asyncgetConversations(agentId:number):PromiseConversation[]{constresawaithttp.get(/agents/${agentId}/conversations);returnres.data;}asyncgetHistory(conversationId:string):PromiseMessage[]{constresawaithttp.get(/conversations/${conversationId}/messages);returnres.data;}// 消息相关 asyncsendMessage(params:SendMessageParams):PromiseSendMessageResult{constresawaithttp.post(/messages/send,params);returnres.data;}}创建工厂函数创建src/services/gateway/index.tsimport{HttpGateway}from./HttpGateway;importtype{WorkbenchGateway}from./types;// 工厂函数创建网关实例exportfunctioncreateGateway():WorkbenchGateway{// 以后可以根据环境切换// if (import.meta.env.MODE mock) {// return new MockGateway();// }returnnewHttpGateway();}在 Store 中使用修改src/store/RootStore.tsimport{makeAutoObservable,runInAction}frommobx;import{createGateway}from/services/gateway;importtype{WorkbenchGateway}from/services/gateway/types;import{AgentStore}from./AgentStore;import{ConversationStore}from./ConversationStore;// 我们下面会写exportclassRootStore{readonlyagentStorenewAgentStore();readonlyconversationStorenewConversationStore();privategateway:WorkbenchGateway;constructor(){this.gatewaycreateGateway();makeAutoObservable(this,{gateway:false},{autoBind:true});}// 初始化asyncbootstrap(){this.agentStore.setLoading(true);try{// 通过网关获取数据constagentsawaitthis.gateway.getAgents();runInAction((){this.agentStore.setAgents(agents);});}finally{runInAction((){this.agentStore.setLoading(false);});}}}小贴士runInAction是 MobX 的工具函数。异步操作之后修改状态要用它包裹一下这样 MobX 才能正确追踪。简单理解同步代码里改状态MobX 自动识别是 action异步回调里改状态需要手动用runInAction告诉 MobX。5.4 网关层的好处我们来总结一下加了网关层之后带来了什么好处好处具体体现统一数据格式不管后端返回什么格式网关层都解析成统一的类型屏蔽传输差异HTTP / WS / 轮询上层不用关心便于测试可以写 MockGateway前端开发不依赖后端便于切换以后换后端、换接口协议只改网关层集中管理错误处理、loading 状态、重试逻辑都可以在网关层统一做代码清晰Store 层只关注业务逻辑不操心接口细节5.5 会话列表 Store 实战让我们用刚学的知识写一个会话列表的 Store。创建 ConversationStore创建src/store/ConversationStore.tsimport{makeAutoObservable}frommobx;importtype{Conversation}from/services/gateway/types;classConversationStore{// 按 agentId 分组存储会话privateconversationsByAgentnewMapnumber,Conversation[]();// 每个 Agent 当前选中的会话privateselectedByAgentnewMapnumber,string|null();// 加载状态loadingAgentId:number|nullnull;constructor(){makeAutoObservable(this);}// 查询方法 getConversations(agentId:number):Conversation[]{returnthis.conversationsByAgent.get(agentId)??[];}getSelectedConversationId(agentId:number):string|null{returnthis.selectedByAgent.get(agentId)??null;}getSelectedConversation(agentId:number):Conversation|null{constselectedIdthis.getSelectedConversationId(agentId);if(!selectedId)returnnull;returnthis.getConversations(agentId).find(cc.idselectedId)??null;}// 修改方法 setConversations(agentId:number,conversations:Conversation[]){this.conversationsByAgent.set(agentId,conversations);// 如果还没有选中的默认选第一个if(!this.selectedByAgent.has(agentId)conversations.length0){this.selectedByAgent.set(agentId,conversations[0].id);}}selectConversation(agentId:number,conversationId:string|null){this.selectedByAgent.set(agentId,conversationId);}setLoadingAgent(agentId:number|null){this.loadingAgentIdagentId;}// 局部更新某个会话patchConversation(agentId:number,conversationId:string,patch:PartialConversation){constconversationsthis.conversationsByAgent.get(agentId);if(!conversations)return;constconversationconversations.find(cc.idconversationId);if(conversation){Object.assign(conversation,patch);}}}exportconstconversationStorenewConversationStore();在 RootStore 中加加载方法修改src/store/RootStore.ts加上切换 Agent 时加载会话列表的逻辑exportclassRootStore{// ...// 选择 AgentasyncselectAgent(agentId:number){this.agentStore.selectAgent(agentId);this.conversationStore.setLoadingAgent(agentId);try{constconversationsawaitthis.gateway.getConversations(agentId);runInAction((){this.conversationStore.setConversations(agentId,conversations);});}finally{runInAction((){this.conversationStore.setLoadingAgent(null);});}}}看到了吗业务逻辑选 Agent → 加载会话写在根 Store 里数据操作分散在子 Store 里网络请求通过网关走。每一层都只关心自己的事代码结构非常清晰。5.6 Mock 网关前端独立开发网关层还有一个超大好处可以写一个 Mock 版本前端不依赖后端就能开发和测试。创建src/services/gateway/MockGateway.tsimporttype{WorkbenchGateway,Agent,Conversation,Message}from./types;// Mock 数据constmockAgents:Agent[][{id:1,name:小运,description:运维助手帮你查服务器状态,online:true},{id:2,name:小办,description:办公助手帮你写文档排日程,online:true},{id:3,name:小审,description:审核助手帮你做内容审核,online:false},];constmockConversations:Recordnumber,Conversation[]{1:[{id:conv-1,title:CPU 使用率高的问题,agentId:1,lastMessage:CPU 使用率是 35%一切正常,lastMessageAt:2024-01-15T10:30:00Z,unreadCount:0,pinned:true,createdAt:2024-01-10T09:00:00Z,updatedAt:2024-01-15T10:30:00Z,},{id:conv-2,title:磁盘空间清理,agentId:1,lastMessage:已经清理了 2.5GB 空间,lastMessageAt:2024-01-14T16:00:00Z,unreadCount:2,pinned:false,createdAt:2024-01-12T14:00:00Z,updatedAt:2024-01-14T16:00:00Z,},],};exportclassMockGatewayimplementsWorkbenchGateway{asyncgetAgents():PromiseAgent[]{// 模拟网络延迟awaitdelay(300);returnmockAgents;}asyncgetConversations(agentId:number):PromiseConversation[]{awaitdelay(200);returnmockConversations[agentId]??[];}asyncgetHistory(conversationId:string):PromiseMessage[]{awaitdelay(200);return[{id:msg-1,role:user,content:帮我看看 CPU 使用率,createdAt:2024-01-15T10:00:00Z,},{id:msg-2,role:assistant,content:好的我来查一下... CPU 使用率是 35%一切正常。,createdAt:2024-01-15T10:00:05Z,},];}asyncsendMessage(params:any):Promiseany{awaitdelay(100);return{messageId:msg-Date.now(),conversationId:params.conversationId||conv-new,accepted:true,};}}// 辅助函数模拟延迟functiondelay(ms:number){returnnewPromise(resolvesetTimeout(resolve,ms));}然后在工厂函数里切换exportfunctioncreateGateway():WorkbenchGateway{if(import.meta.env.VITE_USE_MOCKtrue){returnnewMockGateway();}returnnewHttpGateway();}太方便了后端接口还没写好没关系用 Mock 网关先把前端做起来。等后端好了一行代码切换过去。5.7 本章小结你学到了什么✅ 理解了为什么需要网关层统一接口、屏蔽差异、便于测试✅ 学会了门面模式的设计思想✅ 学会了先定义接口契约再写实现✅ 实现了 HTTP 网关和 axios 封装✅ 写了 ConversationStore 并和网关层配合✅ 学会了用 Mock 网关实现前端独立开发核心概念回顾概念一句话解释网关层前后端之间的翻译官统一通信接口门面模式对外一个大接口内部封装复杂子系统接口契约interface 定义了能力上层只依赖接口不依赖实现Mock 网关假的网关实现用于前端独立开发和测试runInActionMobX 中异步修改状态时需要用的工具函数✏️ 动手练习跟着本章步骤把 HTTP 网关和 Mock 网关都实现一下新增一个SessionStore管理当前会话的消息列表有loadHistory()方法从网关加载历史消息在页面上展示 Agent 列表点击某个 Agent 能切换并显示它的会话列表思考一下如果有的接口走 WebSocket网关层应该怎么设计才能让上层不用管是 HTTP 还是 WS下一章是重点中的重点我们会学习 WebSocket 实时通信实现 AI 流式回复的效果——这是 AI 产品最核心的体验。
RELATED

相关推荐

多谐振荡器从仿真到实物:Multivibrator.zip 设计避坑指南

多谐振荡器从仿真到实物:Multivibrator.zip 设计避坑指南

简介:这份资源是面向电子工程学习者与电路设计初学者的AD20多谐振荡器完整工程包,围绕无稳态与单稳态多谐振荡器的原理验证与仿真实践展开,帮助读者在真实项目中理解周期性脉冲信号的产生与参数调节方法。压缩包共17个文件,约201K…

📅 2026/10/16 12:14:12
HotSpot JVM核心机制:内存布局、垃圾回收与JIT编译调优

HotSpot JVM核心机制:内存布局、垃圾回收与JIT编译调优

很多写 Java 的开发者,其实和 HotSpot 的关系相当微妙——每天写的代码最终都跑到这个虚拟机里,但大多数人几乎没有认真想过:它为什么叫 HotSpot?解释执行和机器码执行的分界线到底在哪里?调参数的时候,-Xm…

📅 2026/10/16 12:14:11
堆排序算法实验报告:Java实现、复杂度分析与避坑指南

堆排序算法实验报告:Java实现、复杂度分析与避坑指南

简介:本资源是一份面向算法初学者与计算机专业学生的堆排序算法综合实验报告,以Java语言实现,帮助读者系统掌握堆排序的建堆、调整与排序全过程。压缩包内共1个doc文档,约63KB,内容涵盖算法流程图、关键代码、复杂度分…

📅 2026/10/16 12:14:11
MORE NEWS

更多资讯

📰

K8s部署Redis生产级实战:持久化、高可用与Helm一站式避坑

聊到k8s安装redis,很多人第一反应是“拉个镜像、起个Deployment不就完事了”。真上手操作你会发现,光是把Pod跑起来只算幼儿园水平,数据不丢、重启不挂、主从能自动切换才是真正的考验。去年某项目上线前,我们所有服务共用一个Red…

📰

QCustomPlot瀑布图绘制详解:从QCPColorMap到实时滚动实现

简介:基于Qt与QCustomPlot的瀑布图实现demo,主要面向需要在Qt界面中展示数据变化过程的C开发者。demo通过随机数生成数据,完整演示了初始化绘图控件、配置坐标轴与刻度、创建QCPGraph对象、绘制矩形及连接线、调用replot刷新视图等关键流程&a…

📰

公共文化场馆携程评论爬取实战:反爬应对与文旅数据应用

1. 项目概述:为什么一个城市图书馆的评论数据值得被系统性采集?“广州图书馆——携程评论爬取”这个标题乍看有点违和——图书馆不是政务服务平台或公共文化场馆吗?怎么会出现在携程这样的旅游预订平台上?但正是这种“反常识”的组…

📰

用Go实现GitLab pre-receive钩子:强制规范commit message

简介:这是一份采用 Go 语言编写的 GitLab pre-receive(预接收)钩子示例资源,面向 GitLab 管理员、DevOps 工程师及对服务端钩子机制感兴趣的开发者。它演示了如何在代码推送前拦截提交,通过检查最新提交消息是否包含指…

📰

PySOT评估必装pysot-toolkit:Ubuntu 16.04编译安装与评测原理详解

1. 为什么必须单独安装 pysot-toolkit:PySOT 主库与评估工具的职责边界 很多人在跑通 PySOT 训练流程后,会自然地认为“模型训出来了,接下来直接 eval 就行”——结果在终端敲下 python tools/test.py 时,十有八九会遇到 Modul…

📰

福克斯刷机避坑:ELM327改AS-Built配置与备份恢复全攻略

简介:面向福特福克斯车主与汽车电子爱好者的电子控制单元刷机工具包,以ELM327诊断接口为硬件基础,可读取车辆电脑数据、分析参数并修改固件。压缩包共33个文件,集成FoCCCus主程序、19个vbf车型固件、12个动态链接库及1个xml配置说…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬