最近 AI Agent 这块的热度确实挡不住,尤其是 OpenClaw 这一套带控制台、带多渠道接入的一键部署方案出来以后,群里天天有人讨论怎么挂微信、怎么挂飞书、怎么把本地模型接进去。我看了下它的架构思路,第一反应倒不是去部署,而是觉得:这东西本质上就是一个 AI Agent Gateway——消息从各个入口进来,网关识别渠道、路由给具体 Agent,Agent 调模型,再把结果流式返回。既然我一直在 Java 后端这个圈子,Spring Boot 又熟,干脆就用 Java 全栈自己实现了一个同类的 Gateway。
这篇文章我会把整体架构、各个核心模块的职责拆分、关键代码怎么写,以及我在实操里踩过的那些坑(502、WebSocket 连不上、模型路由配置报错、内存溢出之类)全部讲一遍。如果你正在做 AI Agent 项目、想把 Agent 接入 IM 渠道,或者想用 Java 写一个完整的全栈 AI 项目,这篇内容应该能让你少走不少弯路。
1. AI Agent Gateway 到底解决什么问题
1.1 从 OpenClaw 说起的项目背景
OpenClaw 在社区里火起来,不完全是靠“AI Agent”这个词本身,更关键的是它把部署体验做到了零门槛:装好以后,你的 Agent 可以同时出现在微信、飞书这些日常 IM 里,还能通过一个 Web 控制台去做可视化管理。很多人第一次跑起来的时候,感叹的是“原来接入一个对话机器人可以这么简单”,但项目背后真正复杂的地方,恰恰是那一层看不见的 Gateway。
我最初也是从“跑通 OpenClaw 部署”这个角度开始的,结果越是看文档越觉得,如果只是照着一键部署脚本点下一步,能学到的东西有限。于是我把项目反过来拆:我自己用 Java 实现一个同样职责的 Gateway,把消息接入、Agent 路由、模型调用、控制台管理这几条链路全部自己写一遍。这么做的好处是,你不再是被部署脚本遮住眼睛的使用者,而是能看清每一层数据在做什么的开发者。
1.2 网关在 Agent 链路里到底干了什么
一个 AI Agent 系统,如果直接连接模型 API,形态其实是比较笨的:每个入口都要单独对接一次模型,微信接一遍,飞书接一遍,网页再接一遍,重复代码满天飞。网关的价值就是把“入口”和“模型调用”解耦开,统一在中间层做四件事:
- 协议转换:微信、飞书、WebSocket、REST 各自的数据格式完全不同,网关负责把它们解析成统一的内部消息结构。
- 会话路由:根据用户 ID、Agent 配置、模型别名,把一条请求分发到正确的 Agent 实例或者模型路由上。
- 状态维护:多轮对话需要记忆上下文,网关统一管理 session 生命周期,包括超时、归档、恢复。
- 流式转发:模型输出是流式的,网关要把这些流数据转成目标渠道可以理解的格式,再推给用户。
所以在我的实现里,Gateway 不是一个可有可无的转发层,而是整个系统的“交换中枢”。只要接入层和模型层都遵守网关定义的协议,扩展一个新渠道或者新模型,成本会被压得很低。
1.3 直接调模型 API 和加一层网关的差别
有人会问,我直接在后端写个接口调 OpenAI 不就行了,为什么要绕一圈加个网关?我实际对比过这个差别,最核心的感受是三个:
- 渠道参数解耦。如果直接调模型,那么每条渠道的鉴权、消息格式解析、错误码转换都要写在上游业务代码里。微信多一个字段、飞书改一次事件签名,你就得动业务代码。有了网关,这些差异全部收敛在适配器里,业务侧只需要面对统一的 AgentRequest。
- 模型路由可配置。直接调模型时,模型名是写死的。有了网关的路由表,你可以在配置中心或控制台里动态调整某个 Agent 用哪个模型、哪个 Base URL、哪个 Key,不用改代码重新发布。
- 流式和会话的统一。IM 渠道天然适合流式打字机效果,但每个渠道的流式封装不一样。网关统一用 SSE 和 WebSocket 做内部流式协议,再在适配器层转换成微信、飞书各自能识别的消息,复杂度被限制在局部了。
这套思路跑起来之后,我最大的感受是:网关不是给系统加复杂度,而是把复杂度从每个业务端点挪到一个可管理的地方。后面所有模块设计,都围绕这个目标展开。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 整体架构与技术选型拆解
2.1 模块划分:接入层、路由层、模型层、控制面
我实现的这个 Java 版 Agent Gateway,整体分成四个平面,各干各的活,互不干扰:
接入层(Channel Layer)
接入层面对的是外部世界。微信、飞书、Web 控制台、移动端 H5、甚至纯粹的 HTTP API 调用,都是在这一层被接收下来。接入层不关心 Agent 逻辑,只负责三件事:接收消息、验签鉴权、把消息标准化成网关内部结构。
路由层(Routing Layer)
路由层是网关的“大脑”。拿到标准化消息之后,根据会话 ID 找到对应的 Agent 配置,再根据 Agent 配置里的模型别名查路由表,确定要走哪个模型供应商。这一层还负责限流、重试、超时控制、审计日志。
模型层(Model Layer)
模型层把不同厂商的 API 差异屏蔽掉。OpenAI、Anthropic、DeepSeek、Ollama 本地模型,都通过同一个 ModelClient 接口暴露给上层。模型层内部实现流式解析、错误码映射、token 消耗统计。
控制面(Control Plane)
控制面就是管理端。我用 Vue 3 写了一个 Control UI,背后是管理 API。Agent 的创建、模型路由的配置、Key 池的管理、会话的查看和终止,都在这一层完成。控制面修改配置之后,通过事件总线通知路由层刷新缓存,不用重启网关。
这四个平面合在一起,就是一个完整可运行的 AI Agent Gateway。任何一层发生变更,都只影响该层自己的实现,不会波及其他模块。
2.2 为什么选 Java 全栈而不是 Python
现在 AI 生态里 Python 的声量很大,很多 Agent 框架也都是 Python 写的,但我选 Java 全栈是认真的考虑过的,不是不会 Python,而是几个实际因素:
- 长连接并发能力。网关最重要的负载是长连接:WebSocket 连接、SSE 流式输出、IM 长轮询,这些在 Java 的 Netty 生态里处理非常成熟。Spring WebFlux 从底层就是响应式模型,不需要像传统 Servlet 那样一个请求占一个线程,连接数上来之后内存和线程开销要小很多。
- 企业环境容易落地。很多公司现有技术栈就是 Java,尤其是后端基础设施、监控、配置中心、网关体系都是 Java 系。这时候你引入一个 Java 写的 Agent Gateway,运维和开发都能快速接手,不需要额外养一套 Python 技术栈。
- 生态组件现成。Redis、PostgreSQL、Nacos、Sentinel 这些基础设施在 Java 圈都很成熟,限流、缓存、配置管理的代码写起来非常顺手。
当然 Python 在模型调用和数据处理上有优势,但网关的核心职责是连接管理、路由、协议转换,这个场景 Java 不但不弱,反而更稳。
2.3 流式通信的底层选型:WebFlux 与 Netty
网关对外要支持 WebSocket 和 SSE 两种主要的流式协议,对内要转发模型的流式输出,这个场景天然适合响应式编程。我最后选了 Spring Boot 3 + Spring WebFlux,底层由 Reactor Netty 驱动。
选 WebFlux 而不是 Spring MVC,核心原因是线程模型。Spring MVC 一个请求占用一个 Tomcat 工作线程,如果大量还是 WebSocket 长连接,线程资源会非常紧张。WebFlux 的事件驱动模型里,少量的 Netty 线程就可以支撑大量空闲连接,真正有数据到达时才消耗计算资源。
实际做下来,网关内部的消息流是这么走的:
- 渠道侧收到消息,接入层解析出 AgentRequest。
- 路由层根据配置把 AgentRequest 交给对应的 Agent 执行器。
- Agent 执行器调用模型层,模型层返回的是 Flux
流式数据。 - 网关把流式数据转换成 ServerSentEvent 或者 WebSocketMessage,直接推给客户端。
整条链路从头到尾是响应式的,没有一个环节会阻塞在等待模型响应上。这也是我建议做 Agent 网关的朋友认真考虑 WebFlux 的原因——它和 AI 流式输出是天然匹配的。
3. 核心细节解析与实操要点
3.1 渠道适配器设计:微信、飞书、Web 是怎么统一进网关的
渠道适配是网关最容易写成一团乱麻的地方,因为每个平台的协议差异真的很大。微信的消息是 XML 格式,还有签名校验、被动回复的限制;飞书走的是事件订阅,签名算法不同,消息回传要通过业务链接口;Web 端则可以用标准的 WebSocket 或 SSE 直接连。
我在设计时用了一个 ChannelAdapter 接口来收敛差异:
java复制public interface ChannelAdapter {
String channelType();
Mono<AgentRequest> parseInbound(Object rawMessage);
Mono<Void> sendOutbound(AgentResponse response);
Mono<Void> verifySignature(Map<String, String> headers, String body);
}
每个渠道实现一个 Adapter,注册到 Spring 容器里。网关在收到请求时,从请求头或者 URL 路径里提取渠道标识,从 Spring Context 里找到对应的 Adapter 实例,然后统一调用 parseInbound 把消息解析成 AgentRequest。
需要注意几个细节:
- 验签必须前置。微信和飞书的回调都会带签名,如果验签不通过,不能进入后续逻辑。这里要特别小心消息体读一次的坑:一旦在验签时读了整个 Body,后面解析就取不到内容了。我建议用 WebFlux 的缓存请求体封装,或者在 Filter 层一次性读取再放到上下文里。
- 回包协议差异。微信的被动回复有 5 秒限制,超时后只能通过客服消息补发。飞书的交互卡片回复是独立的接口,需要拿到用户 open_id 和 message_id。适配器里必须各自处理这些差异,不然会出现“机器人已收到但没回复”的诡异现象。
我实现前三个渠道(微信、飞书、WebSocket)大概花了两天,大部分时间不是在写业务逻辑,而是在处理协议细节。所以如果你也要做这块,建议把渠道适配器好好抽象,后面每接一个渠道都是在填新的 Adapter 实现,而不是改主链路。
3.2 Agent 会话与状态存储
AI Agent 的多轮对话,关键在会话管理。网关里我定义了一个很简单的 Session 模型:
java复制public record AgentSession(
String sessionId,
String agentId,
String userId,
String channel,
List<ChatMessage> history,
Instant expireAt
) {}
会话的存储我选了 Redis,底层用 Hash 结构保存会话元数据,用 List 保存历史消息。每次请求进来时,网关从 Redis 里把会话对象拉出来,追加用户消息,调模型,再把模型回复追加到历史里,最后对整个列表做截断(比如最多保留 20 条),控制 token 消耗。
用 Redis 而不是 JVM 内存 Map,主要原因有三个:
- 网关实例可以水平扩展,多个实例共享同一份会话数据。
- 支持 TTL 自动过期,长时间不活跃的会话可以被回收。
- 断线重连后会话不丢,体验更稳定。
还有一个常见的坑:多轮历史一起发给模型时,如果不加截断,上下文会越来越长,token 费用直接失控。我建议根据 Agent 配置的 maxContextLength 来做截断,优先保留最近的几条,同时把最早的消息压缩成一句摘要。这个逻辑虽然不复杂,但能实际省下不少成本。
3.3 模型路由与多供应商适配
模型路由是网关里最能体现“配置化思维”的部分。我的做法是给每个 Agent 配置一个 model alias,比如 gpt-4o-mini、deepseek-chat、local-qwen,然后由路由表决定这个 alias 实际指向哪个供应商、哪个模型、哪个 Base URL。
路由配置是这样的:
yaml复制openclaw:
routes:
- alias: gpt-4o-mini
provider: openai
model: gpt-4o-mini
baseUrl: https://api.openai.com/v1
apiKey: ${OPENAI_API_KEY}
- alias: deepseek-chat
provider: openai-compatible
model: deepseek-chat
baseUrl: https://api.deepseek.com/v1
apiKey: ${DEEPSEEK_API_KEY}
- alias: local-qwen
provider: ollama
model: qwen2.5:7b
baseUrl: http://127.0.0.1:11434
apiKey: unused
路由层拿到 alias,查这个表,然后实例化对应的 ModelClient。ModelClient 是核心抽象:
java复制public interface ModelClient {
Flux<ModelChunk> chatStream(ChatRequest request);
Mono<ModelResponse> chat(ChatRequest request);
}
不同供应商的差异都被封装在实现类里。OpenAI 和 DeepSeek 因为兼容 OpenAI 协议,可以直接复用同一个实现,只是换 baseUrl 和 apiKey。Ollama 本地模型是独立实现,但它也提供了 OpenAI 兼容端点,所以实际上也可以用同一套客户端,只是 baseUrl 指向本地端口。
我在实操中发现一个很有用的技巧:路由表不要只放 alias 到 provider 的映射,还要带上“最大并发数”和“超时时间”。比如本地模型用 Ollama 跑 7B 模型,响应慢,超时要放大到 120 秒;云端模型快,但贵,超时控制在 30 秒就行。这些细节都通过路由配置下发,网关就能针对不同模型做差异化的负载控制。
3.4 Control UI 与配置热更新
控制面是我这个项目里“全栈”属性最强的一块。我用了 Vue 3 + Element Plus 写了一个管理界面,功能包括:
- Agent 的增删改查,每个 Agent 绑定一个模型 alias、一个系统提示词、一组工具白名单。
- 路由表管理,可视化配置 alias 与供应商的映射。
- 会话管理,查看在线会话、强制终止异常会话。
- 运行时监控,展示每个供应商的调用量、失败率、平均响应时长。
控制面修改配置后,通过一个内部事件总线的 publish 操作通知路由层刷新缓存。这样配置变更秒级生效,不需要重启网关进程,也不需要重新发布前端。
这里有个容易忽略的问题:刷新缓存的时候,正在执行中的长对话怎么办。我的做法是事件里带一个 version 字段,路由层在拉取新配置时只对“尚未开始的新会话”生效,已经开始的会话继续使用旧版本配置直到结束。避免用户聊到一半,模型突然被切换了,导致上下文格式不一致。
4. 实操过程:从零开始搭一个可跑的 Agent Gateway
4.1 创建项目与依赖选择
我用 Spring Initializr 创建项目,JDK 17,Spring Boot 3.2。核心依赖选择如下:
- spring-boot-starter-webflux:提供 WebFlux 和 Netty 运行时。
- spring-boot-starter-data-redis-reactive:响应式 Redis 客户端,做会话存储和限流。
- spring-boot-starter-validation:参数校验。
- spring-boot-starter-actuator:健康检查,网关的存活探针。
- flyway-core + r2dbc-postgresql:Agent 配置、路由表的持久化存储。
- vue3 + axios + element-plus:Control UI 前端。
主配置里我单独开了两个端口:核心网关监听在 8080 端口,WebSocket 服务独立监听在 18789 端口(这个设计是为了后面服务的独立扩缩容,也方便定位连接问题)。Control UI 的管理 API 放在 8081 端口。
这个拆分在后面的实操中确实帮我省了不少事。WebSocket 连接和普通的 HTTP 接口混在一起时,一旦出现连接风暴,很难排查是接口问题还是长连接问题。拆开之后,两个服务的监控数据完全隔离,日志也各自独立。
4.2 实现核心消息通道:REST + SSE + WebSocket
核心链路里,我先实现了一个最基础的 Chat API,支持两种模式:普通 JSON 返回和 SSE 流式返回。
普通模式:
java复制@PostMapping("/v1/chat")
public Mono<AgentResponse> chat(@RequestBody AgentRequest request) {
return agentRouter.route(request).flatMap(r -> r.execute());
}
流式模式用 WebFlux 的 ServerSentEvent:
java复制@PostMapping(value = "/v1/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<AgentEvent>> chatStream(@RequestBody AgentRequest request) {
return agentRouter.route(request)
.flatMapMany(AgentExecutor::executeStream)
.map(data -> ServerSentEvent.<AgentEvent>builder()
.event("message")
.data(data)
.build());
}
WebSocket 部分,我实现了 WebSocketHandler,把消息解析复用 ChannelAdapter 的逻辑,保证 HTTP 和 WebSocket 进来的消息走同一套处理链路:
java复制@Component
public class AgentGatewayWebSocketHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
return session.receive()
.map(WebSocketMessage::getPayloadAsText)
.flatMap(message -> agentRouter.route(parseRequest(message)))
.flatMap(executor -> executor.executeStream()
.map(chunk -> session.textMessage(serialize(chunk))))
.doOnNext(session::send)
.then();
}
}
这个阶段实操下来,最需要注意的问题是流式数据没有及时 flush。如果用了代理层或者开了输出编码缓冲,SSE 数据会攒在缓冲区里,用户会感觉模型“半天才蹦一个字”。我在本地测试时用的是 curl -N,代理层用的是 Nginx,必须关闭代理缓冲(proxy_buffering off)才能保证打字机效果。
4.3 把飞书机器人接进来:事件订阅与消息回复
飞书接入是一个很好的渠道示例,因为它的机制比较典型:应用通过事件订阅收到消息,需要先做 URL 验证,然后在回调里解析事件,再通过开放接口主动发送消息。
回调接口的实现:
java复制@PostMapping("/channel/feishu/webhook")
public Mono<Object> feishuWebhook(@RequestBody String body, @RequestHeader Map<String, String> headers) {
return feishuAdapter.verifySignature(headers, body).flatMap(verified -> {
if (!verified) {
return Mono.error(new AuthenticationException("signature mismatch"));
}
return feishuAdapter.parseInbound(body)
.flatMap(request -> agentRouter.route(request))
.flatMap(AgentExecutor::execute)
.thenReturn(Map.of("code", 0));
});
}
飞书的 URL 验证返回的是一个 challenge 字符串,如果这个处理不对,应用根本没法完成创建。这也算是接入飞书的一个经典坑:回调接口必须同时处理验证请求和真正的消息事件,很多新手只实现了消息解析,结果在配置阶段就卡住了。
消息回复部分,我没有走飞书的单聊 API,而是用了“机器人消息卡片”接口,构造一条 JSON 卡片消息回传。好处是消息格式统一,而且支持富文本。坏处是如果消息里带有 Markdown,转成卡片 JSON 时要做一层转义,否则飞书端会展示原始字符串。
4.4 接一个本地模型:Ollama 最小路由配置
本地模型对很多项目来说,最大的价值是隐私和成本。我用 Ollama 跑 Qwen2.5 7B 作为测试模型,接入方式就是修改路由配置,不需要新增任何代码。
启动 Ollama 之后,用命令行确认模型已下载:
bash复制ollama pull qwen2.5:7b
ollama run qwen2.5:7b
然后在路由配置里加一条:
yaml复制- alias: local-qwen
provider: openai
model: qwen2.5:7b
baseUrl: http://127.0.0.1:11434/v1
apiKey: unused
注意,Ollama 其实提供了 OpenAI 兼容端点 /v1,所以 provider 层面直接复用 openai 的实现就行。我刚开始不知道这点,专门写了一个 ollama 的独立实现,后来发现完全是多余的工作量。这个教训价值挺大的:新模型接入时,先看它有没有提供 OpenAI 兼容协议,有就几乎零成本接入,别急着写专属客户端。
5. 常见问题与排查技巧实录
5.1 502 Bad Gateway 到底是谁的错误
这个报错在我刚开始打通链路时频繁出现,尤其典型的是:
text复制unexpected status 502 bad gateway: unknown error, url: http://127.0.0.1:1572
这个问题的根源在于网关把请求转发到了一个内部服务,这个内部服务可能根本没启动、端口被占用、或者上游响应超时。排查 502 时,我建议按下面的顺序来:
- 先确认目标地址的端口是否在监听。用
lsof -i :1572查端口,如果没输出,说明上游服务没起来。 - 再确认网关配置的 URL 是不是写错了。尤其是用 localhost 还是 127.0.0.1,虽然大多时候等价,但在某些环境下(比如容器化部署)是不同的。
- 最后看上游服务的响应时间。如果上游服务本身处理很慢,代理层超时设置太短也会导致 502。可以在网关的 HttpClient 配置里把 connectTimeout 和 requestTimeout 调大一点试试。
还有一类要注意:WebFlux 的默认缓冲区大小。如果请求体特别大,超过 max-in-memory-size,网关还没转发就直接拒绝了,表面上看起来像 502。我在配置里显式设了一下:
yaml复制spring:
codec:
max-in-memory-size: 16MB
5.2 WebSocket 网关连不上,链路环节排查
我在接入 Web 前端时遇到过这个报错:
text复制gateway: not reachable at ws://127.0.0.1:18789
这个现象是前端 WebSocket 客户端根本连不上网关。排查链路如下:
- 先测试 WebSocket 服务本身是否活着。我用 wscat 连一下:
wscat -c ws://127.0.0.1:18789/ws,如果连接成功,说明服务正常,问题出在前端或代理层。 - 如果连不上,查服务的日志和端口监听。Spring Boot 的 WebFlux 独立端口配置偶尔会被忽略,一定要确认
server.port和自定义 WebSocket 服务端口不冲突。 - 还要看有没有安全过滤器拦截了握手请求。我项目里一开始加了 Spring Security,默认会拦截 WebSocket 的 HTTP Upgrade 请求,导致连不上,后来放行了
/ws/**路径才解决。
WebSocket 长连接还有个隐藏坑:空闲连接被中间的网络设备断开。我的方案是在前端做 30 秒心跳,网关侧收到 ping 后返回 pong,保持连接活跃。否则用户挂机几分钟后,连接其实已经断了,但前端还不知道,要等下一次发送才报错。
5.3 堆外内存溢出与连接膨胀
WebFlux 的响应式模型下,内存问题跟传统 Spring MVC 不太一样。有一次压力测试跑着跑着,进程直接报:
text复制java.lang.OutOfMemoryError: Direct buffer memory
这个是因为 Netty 在处理请求时使用了堆外内存(Direct Memory),如果流式响应的数据量很大,并且没有被及时释放,堆外内存会涨满。排查和解决我是这样做的:
- 用
-XX:MaxDirectMemorySize=512m限制堆外内存,避免无上限占用。 - 检查代码里有没有对流式数据做不必要的缓存,比如把一个 Flux 完整 collectList 到内存再下发,这就非常危险。
- 关注连接数。WebSocket 连接如果不加上限,服务端文件句柄和内存都会膨胀。我在网关里用 Semaphore 做了最大连接数限制,超过限制直接拒绝新连接。
还有一个经验是,本地跑长连接压测时,留意观察 free -m 里的 buff/cache 变化,如果很高,说明 Netty 缓冲区没有正常回收,这时候要检查是不是有些流没有正确订阅或者没有 cancel。
5.4 模型路由配置报错的排查
模型路由报错典型长这样:
text复制doesn't look like an anthropic model: expected a gateway model route reference
这个乍一看像模型问题,其实是路由解析失败。网关发现配置里的模型名不是它认识的路由 alias,于是往上游模型供应商的原始名字去解析了,结果自然对不上。
排查步骤:
- 检查 Agent 配置里填的 model 字段是否是路由表中存在的 alias。
- 检查路由表配置是否被控制面成功刷新到了缓存中,有时改了配置但缓存没更新,看起来就像是没配一样。
- 检查 provider 的匹配逻辑。如果 provider 解析失败,网关默认把 alias 当作模型名,这种“回退”行为很容易掩盖配置错误。我最后在路由层加了一个启动时校验,路由表里所有 alias 必须唯一并且指向存在的 provider,否则直接启动失败,把错误暴露在第一时间。
这块给我最大的启发是:网关类的系统一定要在启动或者配置变更时做严格校验,宁可拒绝启动,也不要带着残缺配置跑,否则线上问题排查成本会非常高。
最后:这个项目后续还能怎么扩展
做到这个程度,网关的基本能力已经完整了,但距离生产级还有不少路要走。我自己的计划是把插件机制做出来:让 Agent 通过工具调用去操作外部 API,比如查天气、查库存、发邮件。网关层只需要预留一个 tool dispatcher 的接口,把模型请求里的 function call 解析出来,再路由到对应的插件实现即可。这个方向业务价值很大,也是现在 Agent 生态里最热门的落地点之一。
另外,如果想把项目做成真正能被团队复用的基础设施,多租户隔离和计费能力是绕不开的。每个租户有独立的 Key 池、独立的会话空间、独立的配额限制,这些在现在的架构里都已经预留了扩展点,只是还没填充完整。
这套 Java 全栈的 AI Agent Gateway 做下来,我自己最大的收获不是某个具体技术,而是搞明白了 Agent 系统里“接入、路由、模型、控制”这四个面之间的关系。以后不管是用现成的方案还是自己造轮子,至少知道问题会出在哪一层、日志该去哪一层看、配置该去哪一层改。如果你也在做类似的中间层服务,希望这篇内容能帮你把路径理顺一些。
