外观
二、一个请求的完整链路(重点)
理解请求链路是学后端最关键的一步。 就像你在前端 DevTools 的 Network 标签里看请求一样,后端的每个请求也有一条清晰的生命线。
2.1 以"用户登录"为例(同步流程)
我们跟踪一个 POST /api/user/login 请求从发出到返回的每一步:
text
① 客户端发送 POST /api/user/login
│
▼
② ┌───── API 网关 ─────┐
│ AuthFilter 拦截 │
│ → 检查路径白名单 │ ← 登录接口在白名单中,不需要 token
│ → 生成 requestId │ ← 类似前端给每个请求加 traceId
│ → 附加到 headers │
│ → 路由到用户服务 │
└─────────────────────┘
│
▼
③ ┌───── 用户服务 Controller ─────┐
│ UserController.login() │
│ → @Validated 校验参数 │ ← 类似前端 zod/yup 校验
│ → 根据登录方式分发 │
└───────────────────────────────┘
│
▼
④ ┌───── 用户服务 Service ─────────────────┐
│ LoginRegisterServiceImpl.login() │
│ → 验证短信验证码(调用短信服务) │
│ → 查询用户是否存在(查数据库) │
│ → 若不存在 → 自动注册并创建用户 │
│ → 调用认证服务获取 token(Feign 调用) │
└────────────────────────────────────────┘
│ ← 这里发生了一次 服务间 HTTP 调用
▼
⑤ ┌───── 认证服务 Controller ─────┐
│ OauthController.token() │
│ → 验证客户端身份 │
│ → 生成 access_token │
│ → 生成 refresh_token │
│ → 存储到数据库 │
│ → 返回 token 给用户服务 │
└───────────────────────────────┘
│
▼
⑥ 用户服务组装最终响应
→ 包含 token + 用户基本信息
→ 统一包装为标准响应结构
→ 返回给客户端几个关键知识点
- 白名单:网关维护了一份"不需要登录就能访问"的路径列表,登录接口本身就在里面
- requestId:贯穿整条链路的唯一标识,方便在日志里查完整链路(类似前端里给每个请求加的
X-Request-Id) - Feign 调用:用户服务调认证服务获取 token,就像前端用
axios调另一个接口一样 - 统一响应格式:后端统一返回类似
{ code: 0, data: {...}, message: "success" }的结构
2.2 以"图片生成"为例(异步 MQ 流程)
图片生成是耗时操作(可能要几秒到几十秒),所以用异步方式——先返回任务 ID,后台慢慢处理。
text
① 客户端: POST /api/canvas/v1/image/create
│
▼
② ┌───── API 网关 ──────┐
│ AuthFilter │
│ → 验证 token │ ← 这次需要登录了
│ → 提取 uid │ ← 从 token 中解析出用户 ID
│ → 附加到 headers │ ← 后续服务从 header 中读取 uid
│ → 路由到画布服务 │
└──────────────────────┘
│
▼
③ ┌───── 画布服务 Controller ─────┐
│ ImageController │
│ → 从 header 中取出 uid │
│ → 委托给 ImageTaskService │
└───────────────────────────────┘
│
▼
④ ┌───── 画布服务 Service ────────────────────────┐
│ ImageTaskServiceImpl.createImageTask() │
│ → 用策略模式选择生成策略 │ ← 设计模式,后面章节详解
│ → Feign 调用用户服务检查并扣除配额 │ ← 又一次服务间调用
│ → 创建任务记录 → 保存到 MongoDB │
│ → 把任务消息发送到 RocketMQ │ ← 关键:发消息而不是直接调用
│ → 立即返回任务 ID 给客户端 │ ← 不等图片生成完成!
└────────────────────────────────────────────────┘
│
▼ (到这里接口已经返回了,后面全是异步)
│
▼
⑤ ┌───── AI 服务(消费者)──────────────────────┐
│ ImageGenerateTaskPullConsumer │
│ → 从 RocketMQ 拉取任务消息 │
│ → 按 AI 模型类型分配到不同线程池 │
│ → 调用外部 AI API 生成图片 │ ← 这一步最耗时
│ → 生成完成后把结果消息发回 RocketMQ │
└─────────────────────────────────────────────┘
│
▼ (又是异步,通过消息队列)
│
⑥ ┌───── 画布服务(结果消费者)──────────────────┐
│ ImageResultConsumer │
│ → 接收图片生成结果 │
│ → Feign 调用存储服务上传图片到云 OSS │
│ → 更新 MongoDB 中的任务状态为"已完成" │
└─────────────────────────────────────────────┘
│
▼
⑦ 客户端通过轮询 GET /api/canvas/v1/image/status/{taskId}
→ 不断查询任务状态
→ 直到状态变为"完成",拿到图片 URL同步 vs 异步的核心区别
| 特点 | 同步流程(登录) | 异步流程(图片生成) |
|---|---|---|
| 接口返回时机 | 所有逻辑执行完才返回 | 提交任务后立即返回任务 ID |
| 客户端获取结果 | 直接在响应体中 | 需要轮询 / WebSocket 通知 |
| 适用场景 | 快速操作(< 1~2 秒) | 耗时操作(几秒 ~ 几分钟) |
| 通信方式 | HTTP(Feign) | HTTP + 消息队列(RocketMQ) |
💡 前端类比:
- 同步 =
const result = await axios.post('/login'),马上就有结果- 异步 = 你上传了一个视频到 B 站,它先告诉你"已提交转码",然后你不断刷新页面看进度
2.3 请求链路中的数据传递
前端工程师特别需要注意,在后端微服务中,数据是通过 HTTP Headers 在服务间传递的:
text
客户端
│ headers: { Authorization: "Bearer xxx" }
▼
网关
│ 解析 token → 提取 uid
│ headers: { uid: "12345", requestId: "abc-def-ghi" }
▼
用户服务
│ @RequestHeader("uid") Long uid ← 从 header 直接读取
│ 如果需要调其他服务,Feign 拦截器自动把 uid 和 requestId 透传
▼
认证服务
│ 同样能拿到 uid 和 requestId这就好比前端的 axios 拦截器自动给每个请求加 Authorization header 一样。后端的 Feign 也有拦截器,自动把关键信息传递下去。
