44 分钟
部署与基础设施工程

API 设计、跨域 CORS 与实时通信:轮询、SSE、WebSocket 怎么选

从 RESTful 接口设计原则讲到跨域 CORS 预检机制,再对比轮询、SSE、WebSocket 三种实时方案的取舍,并讲清 Webhook(支付回调为什么是服务器主动找你),为后续支付与大模型接入打基础

  • 掌握 RESTful 接口的资源化设计、幂等性、状态码与版本、分页约定
  • 彻底搞懂跨域 CORS 为什么会报错、预检 OPTIONS 在做什么、该在哪一层配
  • 能根据场景在轮询、SSE、WebSocket 之间做正确选择
  • 理解 Webhook 的反向调用思想,看懂支付/第三方异步通知为什么这样设计

接口不是能通就行,要让人和机器都看得懂

当你的 App 要和后端、后端要和第三方打交道,接口怎么设计决定了后续好不好维护、出问题好不好查。这一课讲四件部署与集成时绕不开的事:REST 风格、跨域 CORS、实时通信三方案、Webhook 反向回调。它们都是稳定的 Web 工程基础,且直接对应朋友好学里的大模型流式对话、TTS、支付、OTA 检查等真实功能。

RESTful:把一切抽象成「资源」,用方法表达动作

REST 的核心思想是:URL 用名词表示资源(如 /api/lessons、/api/orders/123),用 HTTP 方法表达对资源做什么,用状态码表达结果,而不是在 URL 里堆动词。比如删除第 123 单,规范写法是 DELETE /api/orders/123,而不是 GET /api/deleteOrder?id=123。

示例
RESTful 约定(以课程资源为例)
GET    /api/lessons          列表(带分页参数 ?page=1&size=20)
GET    /api/lessons/s1-m1-l1 取单个
POST   /api/lessons          新建
PUT    /api/lessons/x        整体替换
PATCH  /api/lessons/x        局部更新
DELETE /api/lessons/x        删除

统一响应体便于客户端处理:
{ "ok": true, "data": {...} }
{ "ok": false, "error": { "code": "RATE_LIMITED", "message": "..." } }
💡幂等:重试安全的前提

幂等指「同一个请求执行一次和执行 N 次结果相同」。GET/PUT/DELETE 天然应幂等,POST 默认不幂等。网络不稳时客户端可能重发,所以下单、支付、扣额度这类关键操作必须做成幂等——客户端为每次操作生成唯一请求号,服务端见过同一个号就直接返回上次结果、绝不重复执行。这是上一课 TCP 重传思想在业务层的延续。

跨域 CORS:浏览器的安全策略,不是后端坏了

浏览器有「同源策略」:协议、域名、端口三者完全相同才算同源,网页脚本默认只能请求同源接口;跨源请求要靠服务器明确放行,这套机制叫 CORS(跨源资源共享)。注意CORS 限制的是浏览器里的网页脚本,用 curl、用服务器发请求根本不受它限制——这也是为什么把大模型密钥放在服务器端中继,既安全又没有跨域问题。

跨域请求分两种:简单请求直接发、服务器用 Access-Control-Allow-Origin 头部表态放不放行;带自定义头(如 Authorization)或 JSON 的请求会先发一个 OPTIONS「预检」请求,问服务器「我等下要用 POST、带这些头,可以吗」,服务器点头后浏览器才发真正的请求。

示例
# 浏览器先发预检
OPTIONS /api/chat HTTP/1.1
Origin: https://pygood.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization,content-type

# 服务器同意,浏览器才发真正的 POST
Access-Control-Allow-Origin: https://pygood.com
Access-Control-Allow-Methods: GET,POST,OPTIONS
Access-Control-Allow-Headers: authorization,content-type
Access-Control-Max-Age: 86400   # 预检结果缓存一天,少发 OPTIONS
⚠️CORS 该在哪一层配、最常见的坑

生产中通常在 Nginx 或后端框架统一配 CORS,别到处重复加头(重复的 Allow-Origin 头反而会让浏览器报错)。坑点:带 Cookie/凭证时 Allow-Origin 不能写 *,必须写具体来源并配 Allow-Credentials;别忘了放行 OPTIONS 方法本身,否则预检直接 405;CORS 报错先看浏览器控制台是「预检失败」还是「真请求被拒」,两者修法不同。

实时通信三方案:轮询、SSE、WebSocket

很多功能需要「服务器有新数据就尽快给客户端」:大模型逐字吐字、TTS 边合成边播、进度通知。有三条技术路线,复杂度和能力递增:

配对题实时方案与它的特点/适用场景配对

朋友好学的大模型对话用的是 SSE:它复用普通 HTTP、跑在 TCP 上、自动重连,服务器生成一点就推一个 data: 块,客户端逐字渲染,不需要 WebSocket 那样复杂的双向通道。WebSocket 更适合双方都要实时发消息的场景(如在线聊天室、协同编辑、对战游戏)。选型原则:只需要服务器→客户端单向流式,优先 SSE;必须双向高频实时才上 WebSocket;更新很慢、实时性要求低才用轮询。

示例
# SSE 的响应就是一个「不断开、持续追加」的 HTTP 流
Content-Type: text/event-stream
Cache-Control: no-store

data: {"delta":"变量"}

data: {"delta":"是给"}

data: [DONE]     # 自定义结束标记,客户端收到后收尾

Webhook:反过来,由服务器主动「找你」

前面都是客户端主动请求服务器,但有些事情你无法预知发生时刻:用户到底付没付款、第三方审核结果何时出、物流状态何时变。总不能让你的服务器一直去问。Webhook(网络钩子)的思路是反过来:你提前把一个自己的接口地址登记给第三方,事件发生时由第三方服务器主动 POST 通知你。支付的异步通知(支付宝/微信/Stripe 都叫 notify/webhook)就是最典型的 Webhook。

🐍资深杂谈:为什么支付结果绝不能只信客户端回调

用户付完款,App 端也会收到一个「支付成功」的界面回调,但这个回调可以被篡改、可以断网丢失,绝不能拿它当发货依据。正确链路是:用户支付→第三方支付公司在其服务器确认扣款→第三方服务器调用你登记的 Webhook→你的服务器验签(确认通知确实来自支付公司、没被伪造)→幂等地更新订单、发货→回一个规定格式的 ACK。客户端界面只负责展示,真正的账本以服务器收到并验签通过的 Webhook 为准。这套「服务端到服务端 + 验签 + 幂等」是所有支付接入的铁律,Stage15-m7 会带你走完整链路。

选择题

要实现「大模型一个字一个字往外蹦」的流式对话,且只需要服务器向客户端单向推送,最合适、最省成本的方案是?

本节小结

REST 把万物抽象为资源:URL 用名词、方法表动作、状态码表结果,关键操作靠唯一请求号做幂等。CORS 是浏览器同源策略、靠服务器返回 Allow-* 头放行,非简单请求先 OPTIONS 预检,curl/服务器间不受限、生产在 Nginx 或框架统一配,带凭证不能用 *。实时三方案:轮询简单但浪费、SSE 单向流式(大模型/TTS 首选)、WebSocket 双向全双工(聊天/协同/游戏)。Webhook 是事件发生时第三方服务器反向 POST 通知你,支付结果必须以服务端收到、验签通过、幂等处理的 Webhook 为准,不能只信客户端回调。

资深工程师加餐

底层原理 · 大厂视角 · 工程经验,点卡片展开

把会话、上传文件、定时任务状态从应用进程里挪到 Redis、对象存储或数据库后,任何一台应用实例都能处理任何请求,扩容就是多开几个实例挂到负载均衡后面。反过来,只要状态留在某台机器的内存里,它就既无法横向扩容,也无法滚动更新(一重启用户就掉线)。面试答「如何支撑更高并发」,先讲无状态化,再谈加机器和缓存。