Skip to content

二、一个请求的完整链路(重点)

理解请求链路是学后端最关键的一步。 就像你在前端 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 也有拦截器,自动把关键信息传递下去。