CodeRoad Map 程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。
由 程序员柏风 独立维护 · 关于作者
学习 更多
方法与语义
这一课你将能做到
说出 GET / POST / PUT / PATCH / DELETE 各自「对资源做什么」,并为本站 /api/tracks、/admin/tracks 一类路径配上正确方法
用自己的话区分安全方法 与幂等 ,并判断「连点两次提交按钮」会不会重复创建数据
用 curl 对同一 URL 发错方法,观察 405 Method Not Allowed,并说出重试策略与方法幂等性的关系
为什么要学
前端把「查列表」写成 fetch(url, { method: 'POST' })、后端只开了 @GetMapping,就会出现「接口文档能看、实际 405」。REST 与主流框架的约定是:URL 点名资源,方法点名动作 。方法选错,连锁反应在后面:网关不敢缓存你的 POST、重试机制可能重复下单、爬虫和浏览器预取可能误触副作用——把「扣积分」做成 GET 是真实存在过的生产事故模式。
面试中「GET 和 POST 的区别」几乎是必考题,答「一个获取一个提交」只能及格,答出安全性与幂等性才算懂行。本课对齐 MDN / RFC 9110 的日常语义,不背全书。
先建立图像
方法 人话 幂等? 安全? 典型 body GET 只读拿资源 是 是 通常无 HEAD 只要头、不要 body 是 是 无 POST 创建资源或触发一次动作 否 否 常有 JSON PUT 用这份内容整体替换 资源 是 否 有 PATCH 改一部分 字段视设计 否 有 DELETE 删掉资源 是 否 常无 OPTIONS 问「允许哪些方法/CORS」 是 是 常无
安全 ≈ 「只读,不改服务器状态」(GET/HEAD/OPTIONS)
幂等 ≈ 「同样请求做 N 次,资源终态和做 1 次一样」
GET 两次 → 还是同一份课表(幂等)
POST 创建两次 → 两条记录(非幂等!)
PUT 替换两次 → 还是同一份内容(幂等)
DELETE 两次 → 第二次常 404,但资源终态仍是「已删除」(视为幂等)
跟着做
步骤 1:把上一课的 Spring Boot 接口补全
在 HelloController 同级加一个演示用的资源控制器(用内存 Map 代替 MySQL,语义与真实 CRUD 一致):
@RestController
@RequestMapping("/api/notes")
public class NoteController {
private final Map<Long, String> store = new ConcurrentHashMap<>();
private final AtomicLong idGen = new AtomicLong();
@GetMapping // 读集合
public Map<Long, String> list() { return store; }
@GetMapping("/{id}") // 读单个
public ResponseEntity<String> get(@PathVariable Long id) {
return store.containsKey(id)
? ResponseEntity.ok(store.get(id))
: ResponseEntity.notFound().build();
}
@PostMapping // 创建
public ResponseEntity<Long> create(@RequestBody String content) {
Long id = idGen.incrementAndGet();
store.put(id, content);
return ResponseEntity.created(URI.create("/api/notes/" + id)).body(id);
}
@PutMapping("/{id}") // 整体替换
public String replace(@PathVariable Long id, @RequestBody String content) {
store.put(id, content);
return "replaced";
}
@DeleteMapping("/{id}") // 删除
public ResponseEntity<Void> remove(@PathVariable Long id) {
store.remove(id);
return ResponseEntity.noContent().build();
}
}
步骤 2:GET 连发两次,体会幂等 curl -s http://localhost:8080/api/notes
curl -s http://localhost:8080/api/notes
两次结果完全一致——无论发多少次,服务器状态不变,这就是幂等 + 安全的直观感受。
步骤 3:POST 连发两次,观察重复创建 curl -s -X POST http://localhost:8080/api/notes -H "Content-Type: application/json" -d "first"
curl -s -X POST http://localhost:8080/api/notes -H "Content-Type: application/json" -d "first"
curl -s http://localhost:8080/api/notes
注意返回了两个不同的 id ——同一内容创建了两次。这就是「POST 非幂等」:用户双击提交按钮、网络超时自动重试,都会放大这个问题。生产上支付/下单类接口会加幂等键 (客户端生成唯一 ID,服务端去重)。
步骤 4:对只读接口误用错误方法,看 405 # 对集合发 DELETE?我们的 NoteController 没有 DELETE /api/notes 映射
curl -i -X DELETE http://localhost:8080/api/notes
会看到 405 Method Not Allowed,响应头里可能带 Allow: GET, POST——服务器在告诉你「资源在,但这个动词不行」。对照状态码课:405 ≠ 404 ,前者是方法错,后者是资源无。
步骤 5:PUT 与幂等验证 # 同一条替换请求发两次
curl -s -X PUT http://localhost:8080/api/notes/1 -H "Content-Type: application/json" -d "v2"
curl -s -X PUT http://localhost:8080/api/notes/1 -H "Content-Type: application/json" -d "v2"
curl -s http://localhost:8080/api/notes/1
发 10 次结果都是 v2——「替换」天然幂等,所以客户端超时重试 PUT 是安全的;这也是 HTTP 客户端库默认只自动重试幂等请求的原因。
步骤 6:对照 Spring 注解心智 HTTP 方法 Spring 注解 本站课表示例 GET @GetMappingGET /api/tracks 读课表POST @PostMapping + @RequestBodyPOST /admin/tracks 新建 trackPUT @PutMappingPUT /admin/tracks/{id} 全量更新PATCH @PatchMappingPATCH /admin/tracks/{id} 只改 titleDELETE @DeleteMappingDELETE /admin/tracks/{id} 下架
设计口诀:读 GET,建 POST,整换 PUT,局部 PATCH,删 DELETE 。
步骤 7:想一遍「副作用藏在 GET 里」的事故 假设有人把「领取积分」实现成 GET /api/points/claim?userId=1:浏览器会预取链接、爬虫会抓取、代理可能重试、用户刷新页面会再领一次。正确做法是 POST /api/points/claim,GET 只用于 GET /api/points/{userId} 查余额。把这条写进团队代码评审清单,比事后追幂等键便宜得多。
讲清楚要点
因为方法表达意图,所以网关和缓存可以按方法做不同策略 ——GET/HEAD 可被缓存与预取,POST 默认不被任何一层缓存;把查询做成 POST 等于主动放弃整条缓存链路。
因为 GET/HEAD/OPTIONS 被定义为安全方法,所以浏览器、爬虫、代理敢于自动发起或重试它们 ——永远不要把写操作藏在 GET 里。
因为 PUT/DELETE 幂等,客户端超时重试相对安全;重试 POST 可能重复下单 ——支付类接口必须显式设计幂等(幂等键、唯一索引、状态机),不能指望协议兜底。
因为 URL 应是名词资源,所以少写 /doCreateTrack 这类动词路径 ——动作放在方法上,REST 课展开。
因为 405 表示「资源在、方法不对」,所以排错时先确认后端是否映射了该动词 ——对照 @XxxMapping 与 Allow 响应头,别一律当 404 处理。
因为 PATCH 语义不如 PUT 统一(补丁格式需约定,如 JSON Merge Patch),所以团队要么统一用 PUT + 全量 DTO,要么明确定义 PATCH 格式 ——最怕两种混用还没文档。
你可能会懵的地方
PUT 和 PATCH 到底用哪个?
整份文档替换 → PUT;只改 title 一个字段 → PATCH。很多内部 API 只用 PUT + 全量对象,也完全可以——关键是前后端一致,别让 PUT 实际只收部分字段。
POST 一定表示「创建」吗?
不必须。POST 是最泛化的「处理这个数据」:登录(POST /api/auth/login)、触发导出、大条件搜索(POST /api/tracks/search)都合法。创建集合成员只是它最常见的用法。
幂等是要求「响应字节级相同」吗?
不是。幂等看的是资源终态 :第二次 DELETE 返回 404,但「资源不存在」这个终态没变,语义上仍幂等。
HTML 表单只支持 GET/POST,怎么发 PUT/DELETE?
这是 HTML 表单的历史限制,不是 HTTP 的限制。现代前端用 fetch/axios 可以发任何方法。别用「表单只支持两种」来约束 API 设计。
OPTIONS 平时用不到,为什么要认识它?
你不主动发,浏览器会替你发——CORS 预检就是 OPTIONS(CORS 课细讲)。后端没正确响应 OPTIONS,前端会表现为「接口假死」;另外它也可用于探活:curl -i -X OPTIONS -H "Origin: https://x.com" url 看 Allow 头。
错法对照 错法 问题 正解 用 GET 带 body 做复杂查询 缓存/代理/日志对 GET body 的处理不确定,且可能被预取重复执行 简单过滤用查询参数;超大条件定义 POST /search 端点并写进文档 所有写操作都叫 POST /api/update 方法语义丢失,网关无法区分创建/更新/删除 PUT/PATCH/DELETE /api/tracks/{id},动作归方法管前端请求超时就自动重试所有 POST 可能重复创建/重复扣款 只自动重试幂等方法;POST 重试需幂等键 出现 405 只会重启服务 405 说明方法没映射上,重启无效 查 Allow 响应头 + 对照 Controller 注解 把「查询」做成 POST 理由是「参数复杂」 放弃了缓存、预取、可收藏性,日志排查也变难 查询参数承载过滤;确实巨大再用 POST 搜索端点
卡点急救
POST 报 415 Unsupported Media Type :请求头缺 Content-Type: application/json,Spring 的 @RequestBody 不知道怎么反序列化。curl 补 -H "Content-Type: application/json";浏览器 fetch 里 headers: { 'Content-Type': 'application/json' }。
DELETE/PUT 返回 405,但代码里明明写了注解 :确认类上 @RequestMapping("/api/notes") 与方法上 @DeleteMapping("/{id}") 拼接后路径是否真的一致;再看是否被安全框架(如 Spring Security)拦截后转成了别的响应。开 Debug 日志 logging.level.org.springframework.web=DEBUG 看实际映射。
前端连点按钮创建了多条数据 :短期方案按钮置灰 + 防抖;根治是后端幂等——数据库唯一约束或幂等键表,客户端为每次业务操作生成固定 Idempotency-Key 头,服务端见重就返回第一次的结果。
面试常问
GET 和 POST 的区别?
分三层答:① 语义:GET 读资源、安全幂等;POST 提交数据、非幂等。② 报文:GET 参数在 URL 查询串,body 通常为空;POST 数据在 body,需 Content-Type 声明格式。③ 工程后果:GET 可被缓存/收藏/预取,POST 不会;GET 参数进服务器访问日志和浏览器历史,敏感数据别放 GET。
什么是幂等性?哪些 HTTP 方法幂等?
同一请求执行一次与执行多次对资源状态的影响相同。GET/HEAD/OPTIONS/PUT/DELETE 幂等,POST 不幂等(PATCH 视实现)。应用价值:客户端重试策略、消息队列重复消费去重、接口防重都建立在幂等之上。
为什么浏览器表单只支持 GET/POST,而 HTTP 定义了那么多方法?
HTML 表单规范是 90 年代定下的子集,一直没扩展。HTTP 方法是协议层概念,任何 HTTP 客户端(fetch、axios、curl、OkHttp)都能发全。API 设计按协议语义来,不要被表单限制绑架。
自测三问
「安全」与「幂等」各用一句话解释,并各举两个方法。
为什么创建资源常用 POST 到集合路径而不是 PUT?什么情况下 PUT 到确定 URL 也合理?
对只读的 /api/notes 误发 DELETE,期望什么状态码?如何用 curl 验证并从响应头里找到线索?
小结一句话 方法是资源上的动词:读用 GET、建用 POST、整换用 PUT、局部改用 PATCH、删用 DELETE;安全与幂等决定缓存与重试能不能帮你。
延伸