程序员生存指南04-为什么AI能写70%的代码但取代不了你2026年程序员核心价值转变不是写代码而是设计系统-CSDN博客AI面试高频问题及原理01- 搞不清AI Agent和LLM的区别3分钟让你彻底明白-CSDN博客目录开篇那些年我们踩过的API坑一、RESTful到底是什么别再用RPC思维了二、URL设计别再让前端看不懂你的接口三、HTTP方法GET、POST、PUT、PATCH、DELETE到底怎么用四、状态码2xx、3xx、4xx、5xx的正确打开方式五、版本管理URL路径 vs Header选哪个六、分页、过滤、排序别让API变成数据黑洞七、错误响应统一格式拒绝黑盒八、互动挑战检查你的API是否符合规范九、源码获取与思考题十、系列预告开篇那些年我们踩过的API坑还记得刚入行那会儿我写了一个/getUserInfo?id123的接口 senior 看了直摇头“你这APIRESTful 了个寂寞。”我当时一脸懵这不就是获取用户信息吗有什么问题后来踩坑多了才明白RESTful 不是简单的用URL传参数而是一套完整的设计哲学。今天这篇文章我会把这些年踩过的坑、看过的烂代码、以及大厂的最佳实践一次性分享给你。读完本文你将获得一套可直接落地的 RESTful API 设计规范10个常见错误的避坑指南可直接复用的代码模板一、RESTful到底是什么别再用RPC思维了1.1 RESTful vs RPC两种思维模式的碰撞很多开发者包括当年的我写 API 时下意识用的是RPC 思维RPC 风格 /getUserInfo ← 动作 对象 /createOrder ← 动作 对象 /updateProduct ← 动作 对象而 RESTful 的核心思想是“资源导向”RESTful 风格 GET /users/123 ← 获取资源 POST /orders ← 创建资源 PUT /products/456 ← 更新资源 DELETE /comments/789 ← 删除资源1.2 RESTful 架构图┌─────────────────────────────────────────────────────────────┐ │ RESTful 架构 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ HTTP Methods ┌──────────────────┐ │ │ │ │ ─────────────────→ │ │ │ │ │ Client │ GET/POST/PUT │ Server │ │ │ │ │ ←───────────────── │ (Resources) │ │ │ └──────────┘ Status Codes └──────────────────┘ │ │ ↑ │ │ │ │ ↓ │ │ ┌──────────┐ ┌──────────┐ │ │ │ Stateless│ │ Resource │ │ │ │ Cacheable│ │ /users │ │ │ └──────────┘ │ /orders │ │ │ └──────────┘ │ │ │ │ 核心原则 │ │ • 资源是核心名词 │ │ • HTTP方法定义操作 │ │ • 无状态通信 │ │ • 统一接口 │ └─────────────────────────────────────────────────────────────┘1.3 RESTful 的六个核心约束约束说明实际意义Client-Server客户端-服务器分离前后端解耦独立演进Stateless无状态每个请求独立服务端不保存客户端状态Cacheable可缓存响应明确是否可缓存提升性能Uniform Interface统一接口资源标识、自描述消息、HATEOASLayered System分层系统客户端不知道是否直连服务器Code on Demand按需代码可选服务端可下发代码如JS二、URL设计别再让前端看不懂你的接口2.1 黄金法则名词复数不用动词❌错误示范/getUsers /createOrder /updateProductInfo /deleteCommentById✅正确姿势GET /users # 获取用户列表 POST /orders # 创建订单 PUT /products/{id} # 更新产品 DELETE /comments/{id} # 删除评论2.2 层级关系用URL表达资源关联/users/{userId}/orders # 某用户的所有订单 /users/{userId}/orders/{orderId} # 某用户的特定订单 /products/{id}/reviews # 某产品的所有评价层级关系图┌─────────────┐ │ API │ │ Root │ └──────┬──────┘ │ ┌───────────────┼───────────────┐ ↓ ↓ ↓ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ /users │ │/products│ │ /orders │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ ┌─────┴─────┐ ┌────┴────┐ ┌────┴────┐ ↓ ↓ ↓ ↓ ↓ ↓ ┌───────┐ ┌───────┐ │ ┌────────┐ │ ┌────────┐ │/users │ │/users │ │ │/orders │ │ │/orders │ │ /123 │ │ /456 │ └────│/{id} │ └────│/{id} │ └───┬───┘ └───────┘ └───┬────┘ └───┬────┘ │ │ │ ┌───┴───┐ ┌────┴────┐ ┌────┴────┐ │/orders│ │/reviews │ │/items │ └───────┘ └─────────┘ └─────────┘2.3 URL设计Checklist规则正确错误使用小写字母/users/Users使用连字符分隔/user-profiles/user_profiles不用下划线/order-items/order_items不用文件扩展名/users/users.json名词复数/orders/order无动作动词POST /orders/createOrder三、HTTP方法GET、POST、PUT、PATCH、DELETE到底怎么用3.1 HTTP方法语义对照表┌─────────────┬─────────────────┬────────────────────────────────┐ │ Method │ 语义 │ 使用场景 │ ├─────────────┼─────────────────┼────────────────────────────────┤ │ GET │ 获取资源 │ 查询、读取无副作用 │ │ POST │ 创建资源 │ 新建数据服务器分配ID │ │ PUT │ 全量更新 │ 替换整个资源提供完整资源 │ │ PATCH │ 局部更新 │ 修改部分字段提供变更字段 │ │ DELETE │ 删除资源 │ 删除指定资源 │ │ HEAD │ 获取元数据 │ 检查资源是否存在不返回body │ │ OPTIONS │ 获取支持方法 │ CORS预检请求 │ └─────────────┴─────────────────┴────────────────────────────────┘3.2 实战代码示例# Flask 示例 from flask import Flask, request, jsonify app Flask(__name__) # GET获取资源 app.route(/users, methods[GET]) def get_users(): 获取用户列表支持分页、过滤 page request.args.get(page, 1, typeint) size request.args.get(size, 10, typeint) # ... 查询逻辑 return jsonify({ data: users, pagination: { page: page, size: size, total: total } }) app.route(/users/int:user_id, methods[GET]) def get_user(user_id): 获取单个用户 user find_user_by_id(user_id) if not user: return jsonify({error: User not found}), 404 return jsonify(user) # POST创建资源 app.route(/users, methods[POST]) def create_user(): 创建新用户 data request.get_json() # 参数校验 if not data or name not in data: return jsonify({error: Name is required}), 400 new_user create_user_in_db(data) return jsonify(new_user), 201 # 201 Created # PUT全量更新 app.route(/users/int:user_id, methods[PUT]) def update_user(user_id): 全量更新用户信息 data request.get_json() # PUT 要求提供完整资源 required_fields [name, email, age] for field in required_fields: if field not in data: return jsonify({error: f{field} is required for PUT}), 400 updated_user replace_user(user_id, data) return jsonify(updated_user) # PATCH局部更新 app.route(/users/int:user_id, methods[PATCH]) def patch_user(user_id): 局部更新用户信息 data request.get_json() # PATCH 只更新提供的字段 updated_user partial_update_user(user_id, data) return jsonify(updated_user) # DELETE删除资源 app.route(/users/int:user_id, methods[DELETE]) def delete_user(user_id): 删除用户 delete_user_by_id(user_id) return , 204 # 204 No Content if __name__ __main__: app.run(debugTrue)3.3 PUT vs PATCH到底选哪个场景更新用户信息name, email, age PUT /users/123 { name: 张三, email: zhangsanexample.com, age: 25 } → 必须提供所有字段未提供的字段会被清空或设为默认值 PATCH /users/123 { age: 26 } → 只更新 age 字段其他字段保持不变建议前端提供完整表单 → 用 PUT前端只修改个别字段 → 用 PATCH不确定时 → 优先用 PATCH更安全四、状态码2xx、3xx、4xx、5xx的正确打开方式4.1 状态码速查表┌─────────┬────────────────────────────────────────────────────────────┐ │ 2xx │ 成功 │ ├─────────┼────────────────────────────────────────────────────────────┤ │ 200 │ OK - 请求成功返回所请求的数据 │ │ 201 │ Created - 资源创建成功POST请求 │ │ 202 │ Accepted - 请求已接受异步处理中 │ │ 204 │ No Content - 成功但无返回内容DELETE常用 │ ├─────────┼────────────────────────────────────────────────────────────┤ │ 3xx │ 重定向 │ ├─────────┼────────────────────────────────────────────────────────────┤ │ 301 │ Moved Permanently - 永久重定向 │ │ 302 │ Found - 临时重定向 │ │ 304 │ Not Modified - 资源未修改使用缓存 │ ├─────────┼────────────────────────────────────────────────────────────┤ │ 4xx │ 客户端错误 │ ├─────────┼────────────────────────────────────────────────────────────┤ │ 400 │ Bad Request - 请求参数错误 │ │ 401 │ Unauthorized - 未认证需要登录 │ │ 403 │ Forbidden - 无权限已登录但无权限 │ │ 404 │ Not Found - 资源不存在 │ │ 409 │ Conflict - 资源冲突如重复创建 │ │ 422 │ Unprocessable Entity - 语义错误如邮箱格式不对 │ │ 429 │ Too Many Requests - 请求过于频繁 │ ├─────────┼────────────────────────────────────────────────────────────┤ │ 5xx │ 服务端错误 │ ├─────────┼────────────────────────────────────────────────────────────┤ │ 500 │ Internal Server Error - 服务器内部错误 │ │ 502 │ Bad Gateway - 网关错误 │ │ 503 │ Service Unavailable - 服务不可用维护中/过载 │ │ 504 │ Gateway Timeout - 网关超时 │ └─────────┴────────────────────────────────────────────────────────────┘4.2 常见错误401 vs 403这是最容易混淆的一对状态码┌─────────────────────────────────────────────────────────────────────┐ │ 401 vs 403 区别 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 401 Unauthorized 403 Forbidden │ │ ─────────────── ───────────── │ │ │ │ 你是谁我不认识你 我知道你是谁但你没权限 │ │ │ │ • 未提供认证信息 • 已提供认证信息 │ │ • Token 无效/过期 • Token 有效 │ │ • 需要登录 • 权限不足 │ │ │ │ 示例 示例 │ │ GET /admin/users GET /admin/users │ │ Header: 无/Token无效 Header: Bearer {普通用户Token} │ │ → 401 → 403 │ │ │ └─────────────────────────────────────────────────────────────────────┘4.3 状态码使用示例# 400 Bad Request - 参数错误 app.route(/users, methods[POST]) def create_user(): data request.get_json() if not data.get(email): return jsonify({ error: Bad Request, message: Email is required, code: MISSING_FIELD }), 400 # 401 Unauthorized - 未认证 app.route(/profile, methods[GET]) def get_profile(): token request.headers.get(Authorization) if not token: return jsonify({ error: Unauthorized, message: Authentication required, code: NO_TOKEN }), 401 # 403 Forbidden - 无权限 app.route(/admin/users, methods[DELETE]) def delete_user(user_id): current_user get_current_user() if not current_user.is_admin: return jsonify({ error: Forbidden, message: Admin permission required, code: INSUFFICIENT_PERMISSION }), 403 # 404 Not Found - 资源不存在 app.route(/users/int:user_id, methods[GET]) def get_user(user_id): user find_user(user_id) if not user: return jsonify({ error: Not Found, message: fUser {user_id} not found, code: USER_NOT_FOUND }), 404 # 409 Conflict - 资源冲突 app.route(/users, methods[POST]) def create_user(): data request.get_json() if user_exists(data[email]): return jsonify({ error: Conflict, message: Email already registered, code: EMAIL_EXISTS }), 409 # 422 Unprocessable Entity - 语义错误 app.route(/users, methods[POST]) def create_user(): data request.get_json() if not is_valid_email(data[email]): return jsonify({ error: Unprocessable Entity, message: Invalid email format, code: INVALID_EMAIL }), 422五、版本管理URL路径 vs Header选哪个5.1 三种版本管理策略对比┌──────────────────────────────────────────────────────────────────────┐ │ API 版本管理策略对比 │ ├──────────────────────────────────────────────────────────────────────┤ │ │ │ 1. URL 路径版本推荐 │ │ ───────────────────── │ │ /v1/users │ │ /v2/users │ │ │ │ ✅ 优点直观、易于调试、可直接在浏览器测试 │ │ ❌ 缺点URL 变更破坏 REST 资源不变原则 │ │ │ │ 2. Header 版本 │ │ ──────────────── │ │ /users │ │ Accept: application/vnd.api.v1json │ │ │ │ ✅ 优点URL 不变符合 REST 原则 │ │ ❌ 缺点调试麻烦需要工具设置 Header │ │ │ │ 3. 查询参数版本 │ │ ──────────────── │ │ /users?version1 │ │ │ │ ✅ 优点简单 │ │ ❌ 缺点不标准容易被忽略 │ │ │ └──────────────────────────────────────────────────────────────────────┘5.2 推荐方案URL 路径版本# Flask 蓝图实现版本控制 from flask import Blueprint # v1 版本 v1_bp Blueprint(api_v1, __name__, url_prefix/v1) v1_bp.route(/users, methods[GET]) def get_users_v1(): v1 版本返回简单用户信息 return jsonify({ users: [ {id: 1, name: 张三} ] }) # v2 版本 v2_bp Blueprint(api_v2, __name__, url_prefix/v2) v2_bp.route(/users, methods[GET]) def get_users_v2(): v2 版本返回更详细的用户信息 return jsonify({ data: { users: [ { id: 1, name: 张三, profile: {...}, meta: {...} } ] }, pagination: {...} }) # 注册蓝图 app.register_blueprint(v1_bp) app.register_blueprint(v2_bp)六、分页、过滤、排序别让API变成数据黑洞6.1 分页设计GET /users?page1size20app.route(/users, methods[GET]) def get_users(): # 参数解析 page request.args.get(page, 1, typeint) size request.args.get(size, 20, typeint) # 限制最大页大小防止性能问题 size min(size, 100) # 查询数据 users, total query_users(pagepage, sizesize) # 计算分页信息 total_pages (total size - 1) // size return jsonify({ data: users, pagination: { page: page, size: size, total: total, total_pages: total_pages, has_next: page total_pages, has_prev: page 1 } })6.2 过滤设计GET /users?statusactiveroleadmincreated_after2024-01-01app.route(/users, methods[GET]) def get_users(): # 构建查询条件 filters {} if status in request.args: filters[status] request.args.get(status) if role in request.args: filters[role] request.args.get(role) if created_after in request.args: filters[created_at__gte] request.args.get(created_after) users query_users_with_filters(filters) return jsonify({data: users})6.3 排序设计GET /users?sort-created_at,name # - 表示降序 表示升序可省略app.route(/users, methods[GET]) def get_users(): sort_param request.args.get(sort, created_at) order_by [] for field in sort_param.split(,): if field.startswith(-): order_by.append((field[1:], desc)) elif field.startswith(): order_by.append((field[1:], asc)) else: order_by.append((field, asc)) users query_users(order_byorder_by) return jsonify({data: users})6.4 完整示例GET /api/v1/users?page1size20statusactivesort-created_at{ data: [ { id: 100, name: 张三, status: active, created_at: 2024-01-15T08:30:00Z }, { id: 99, name: 李四, status: active, created_at: 2024-01-14T16:45:00Z } ], pagination: { page: 1, size: 20, total: 156, total_pages: 8, has_next: true, has_prev: false }, filters_applied: { status: active }, sort_applied: [-created_at] }七、错误响应统一格式拒绝黑盒7.1 错误响应格式设计{ error: { code: VALIDATION_ERROR, message: Request validation failed, details: [ { field: email, code: INVALID_FORMAT, message: Invalid email format }, { field: age, code: OUT_OF_RANGE, message: Age must be between 18 and 120 } ], timestamp: 2024-01-15T08:30:00Z, request_id: req_abc123xyz, documentation_url: https://api.example.com/docs/errors/VALIDATION_ERROR } }7.2 错误处理中间件Flaskfrom flask import Flask, jsonify from werkzeug.exceptions import HTTPException app Flask(__name__) class APIError(Exception): 自定义API错误 def __init__(self, code, message, status_code400, detailsNone): self.code code self.message message self.status_code status_code self.details details or [] app.errorhandler(APIError) def handle_api_error(error): 处理自定义API错误 response { error: { code: error.code, message: error.message, details: error.details, timestamp: datetime.utcnow().isoformat() Z, request_id: g.get(request_id, unknown) } } return jsonify(response), error.status_code app.errorhandler(404) def handle_not_found(error): 处理404错误 return jsonify({ error: { code: RESOURCE_NOT_FOUND, message: The requested resource does not exist, timestamp: datetime.utcnow().isoformat() Z } }), 404 app.errorhandler(500) def handle_internal_error(error): 处理500错误 # 记录详细错误日志 logger.error(fInternal error: {error}, exc_infoTrue) return jsonify({ error: { code: INTERNAL_ERROR, message: An internal error occurred. Please try again later., timestamp: datetime.utcnow().isoformat() Z, request_id: g.get(request_id, unknown) } }), 500 # 使用示例 app.route(/users, methods[POST]) def create_user(): data request.get_json() errors [] if not data.get(email): errors.append({field: email, code: REQUIRED, message: Email is required}) elif not is_valid_email(data[email]): errors.append({field: email, code: INVALID_FORMAT, message: Invalid email format}) if not data.get(name): errors.append({field: name, code: REQUIRED, message: Name is required}) if errors: raise APIError( codeVALIDATION_ERROR, messageRequest validation failed, status_code422, detailserrors ) # ... 创建用户逻辑八、互动挑战检查你的API是否符合规范来做个小测试看看你的API设计能得多少分检查项符合不符合URL 使用名词复数/users 而非 /getUsers☐☐URL 使用小写字母和连字符☐☐正确使用 HTTP 方法GET/POST/PUT/PATCH/DELETE☐☐状态码使用准确200/201/204/400/401/403/404/422等☐☐错误响应格式统一☐☐支持分页、过滤、排序☐☐有 API 版本管理策略☐☐使用 HTTPS☐☐实现了限流保护☐☐有完善的接口文档☐☐得分10分RESTful 大师7-9分还不错继续优化4-6分还有很大提升空间0-3分建议重读本文 九、源码获取与思考题9.1 完整源码获取本文所有代码示例已整理成完整项目包含Flask RESTful API 完整实现错误处理中间件分页/过滤/排序工具类单元测试GitHub 地址https://github.com/example/restful-api-best-practices9.2 思考题场景题如果你的 API 需要支持批量删除一次删除多个用户你会如何设计是用DELETE /users?ids1,2,3还是POST /users/batch-delete为什么进阶题RESTful 原则中提到 HATEOAS超媒体作为应用状态引擎你了解吗在实际项目中你会使用吗实战题设计一个文件上传接口要求支持断点续传你会如何设计 URL 和 HTTP 方法欢迎在评论区分享你的答案十、系列预告本文是《后端架构设计》系列的第 20 篇后续还将更新主题21API 安全设计认证、鉴权、防刷最佳实践主题22GraphQL vs REST如何选择主题23API 性能优化缓存、压缩、限流实战主题24微服务架构下的 API 网关设计主题25OpenAPI 规范自动生成文档和 SDK关注不迷路点赞收藏防丢失总结RESTful API 设计看似简单但细节决定成败。记住这 10 个核心规范资源导向用名词不用动词URL 规范小写、复数、连字符分隔HTTP 方法GET/POST/PUT/PATCH/DELETE 各司其职状态码准确使用 2xx/3xx/4xx/5xx版本管理推荐 URL 路径方式分页过滤别让 API 变成数据黑洞错误格式统一、详细、可追溯HTTPS强制使用安全第一限流保护防止滥用接口文档及时更新方便协作好的 API 设计是工程师的基本功也是产品的门面。标签RESTful, API设计, HTTP, 后端开发, 接口规范, Web开发, 架构设计互动你的 API 设计得多少分评论区聊聊你踩过的坑
后端技术20-你的RESTful API可能并不RESTful!这10个规范你遵守了吗?从混乱到规范:RESTful API设计的完整 checklist
发布时间:2026/6/12 17:02:53
程序员生存指南04-为什么AI能写70%的代码但取代不了你2026年程序员核心价值转变不是写代码而是设计系统-CSDN博客AI面试高频问题及原理01- 搞不清AI Agent和LLM的区别3分钟让你彻底明白-CSDN博客目录开篇那些年我们踩过的API坑一、RESTful到底是什么别再用RPC思维了二、URL设计别再让前端看不懂你的接口三、HTTP方法GET、POST、PUT、PATCH、DELETE到底怎么用四、状态码2xx、3xx、4xx、5xx的正确打开方式五、版本管理URL路径 vs Header选哪个六、分页、过滤、排序别让API变成数据黑洞七、错误响应统一格式拒绝黑盒八、互动挑战检查你的API是否符合规范九、源码获取与思考题十、系列预告开篇那些年我们踩过的API坑还记得刚入行那会儿我写了一个/getUserInfo?id123的接口 senior 看了直摇头“你这APIRESTful 了个寂寞。”我当时一脸懵这不就是获取用户信息吗有什么问题后来踩坑多了才明白RESTful 不是简单的用URL传参数而是一套完整的设计哲学。今天这篇文章我会把这些年踩过的坑、看过的烂代码、以及大厂的最佳实践一次性分享给你。读完本文你将获得一套可直接落地的 RESTful API 设计规范10个常见错误的避坑指南可直接复用的代码模板一、RESTful到底是什么别再用RPC思维了1.1 RESTful vs RPC两种思维模式的碰撞很多开发者包括当年的我写 API 时下意识用的是RPC 思维RPC 风格 /getUserInfo ← 动作 对象 /createOrder ← 动作 对象 /updateProduct ← 动作 对象而 RESTful 的核心思想是“资源导向”RESTful 风格 GET /users/123 ← 获取资源 POST /orders ← 创建资源 PUT /products/456 ← 更新资源 DELETE /comments/789 ← 删除资源1.2 RESTful 架构图┌─────────────────────────────────────────────────────────────┐ │ RESTful 架构 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ HTTP Methods ┌──────────────────┐ │ │ │ │ ─────────────────→ │ │ │ │ │ Client │ GET/POST/PUT │ Server │ │ │ │ │ ←───────────────── │ (Resources) │ │ │ └──────────┘ Status Codes └──────────────────┘ │ │ ↑ │ │ │ │ ↓ │ │ ┌──────────┐ ┌──────────┐ │ │ │ Stateless│ │ Resource │ │ │ │ Cacheable│ │ /users │ │ │ └──────────┘ │ /orders │ │ │ └──────────┘ │ │ │ │ 核心原则 │ │ • 资源是核心名词 │ │ • HTTP方法定义操作 │ │ • 无状态通信 │ │ • 统一接口 │ └─────────────────────────────────────────────────────────────┘1.3 RESTful 的六个核心约束约束说明实际意义Client-Server客户端-服务器分离前后端解耦独立演进Stateless无状态每个请求独立服务端不保存客户端状态Cacheable可缓存响应明确是否可缓存提升性能Uniform Interface统一接口资源标识、自描述消息、HATEOASLayered System分层系统客户端不知道是否直连服务器Code on Demand按需代码可选服务端可下发代码如JS二、URL设计别再让前端看不懂你的接口2.1 黄金法则名词复数不用动词❌错误示范/getUsers /createOrder /updateProductInfo /deleteCommentById✅正确姿势GET /users # 获取用户列表 POST /orders # 创建订单 PUT /products/{id} # 更新产品 DELETE /comments/{id} # 删除评论2.2 层级关系用URL表达资源关联/users/{userId}/orders # 某用户的所有订单 /users/{userId}/orders/{orderId} # 某用户的特定订单 /products/{id}/reviews # 某产品的所有评价层级关系图┌─────────────┐ │ API │ │ Root │ └──────┬──────┘ │ ┌───────────────┼───────────────┐ ↓ ↓ ↓ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ /users │ │/products│ │ /orders │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ ┌─────┴─────┐ ┌────┴────┐ ┌────┴────┐ ↓ ↓ ↓ ↓ ↓ ↓ ┌───────┐ ┌───────┐ │ ┌────────┐ │ ┌────────┐ │/users │ │/users │ │ │/orders │ │ │/orders │ │ /123 │ │ /456 │ └────│/{id} │ └────│/{id} │ └───┬───┘ └───────┘ └───┬────┘ └───┬────┘ │ │ │ ┌───┴───┐ ┌────┴────┐ ┌────┴────┐ │/orders│ │/reviews │ │/items │ └───────┘ └─────────┘ └─────────┘2.3 URL设计Checklist规则正确错误使用小写字母/users/Users使用连字符分隔/user-profiles/user_profiles不用下划线/order-items/order_items不用文件扩展名/users/users.json名词复数/orders/order无动作动词POST /orders/createOrder三、HTTP方法GET、POST、PUT、PATCH、DELETE到底怎么用3.1 HTTP方法语义对照表┌─────────────┬─────────────────┬────────────────────────────────┐ │ Method │ 语义 │ 使用场景 │ ├─────────────┼─────────────────┼────────────────────────────────┤ │ GET │ 获取资源 │ 查询、读取无副作用 │ │ POST │ 创建资源 │ 新建数据服务器分配ID │ │ PUT │ 全量更新 │ 替换整个资源提供完整资源 │ │ PATCH │ 局部更新 │ 修改部分字段提供变更字段 │ │ DELETE │ 删除资源 │ 删除指定资源 │ │ HEAD │ 获取元数据 │ 检查资源是否存在不返回body │ │ OPTIONS │ 获取支持方法 │ CORS预检请求 │ └─────────────┴─────────────────┴────────────────────────────────┘3.2 实战代码示例# Flask 示例 from flask import Flask, request, jsonify app Flask(__name__) # GET获取资源 app.route(/users, methods[GET]) def get_users(): 获取用户列表支持分页、过滤 page request.args.get(page, 1, typeint) size request.args.get(size, 10, typeint) # ... 查询逻辑 return jsonify({ data: users, pagination: { page: page, size: size, total: total } }) app.route(/users/int:user_id, methods[GET]) def get_user(user_id): 获取单个用户 user find_user_by_id(user_id) if not user: return jsonify({error: User not found}), 404 return jsonify(user) # POST创建资源 app.route(/users, methods[POST]) def create_user(): 创建新用户 data request.get_json() # 参数校验 if not data or name not in data: return jsonify({error: Name is required}), 400 new_user create_user_in_db(data) return jsonify(new_user), 201 # 201 Created # PUT全量更新 app.route(/users/int:user_id, methods[PUT]) def update_user(user_id): 全量更新用户信息 data request.get_json() # PUT 要求提供完整资源 required_fields [name, email, age] for field in required_fields: if field not in data: return jsonify({error: f{field} is required for PUT}), 400 updated_user replace_user(user_id, data) return jsonify(updated_user) # PATCH局部更新 app.route(/users/int:user_id, methods[PATCH]) def patch_user(user_id): 局部更新用户信息 data request.get_json() # PATCH 只更新提供的字段 updated_user partial_update_user(user_id, data) return jsonify(updated_user) # DELETE删除资源 app.route(/users/int:user_id, methods[DELETE]) def delete_user(user_id): 删除用户 delete_user_by_id(user_id) return , 204 # 204 No Content if __name__ __main__: app.run(debugTrue)3.3 PUT vs PATCH到底选哪个场景更新用户信息name, email, age PUT /users/123 { name: 张三, email: zhangsanexample.com, age: 25 } → 必须提供所有字段未提供的字段会被清空或设为默认值 PATCH /users/123 { age: 26 } → 只更新 age 字段其他字段保持不变建议前端提供完整表单 → 用 PUT前端只修改个别字段 → 用 PATCH不确定时 → 优先用 PATCH更安全四、状态码2xx、3xx、4xx、5xx的正确打开方式4.1 状态码速查表┌─────────┬────────────────────────────────────────────────────────────┐ │ 2xx │ 成功 │ ├─────────┼────────────────────────────────────────────────────────────┤ │ 200 │ OK - 请求成功返回所请求的数据 │ │ 201 │ Created - 资源创建成功POST请求 │ │ 202 │ Accepted - 请求已接受异步处理中 │ │ 204 │ No Content - 成功但无返回内容DELETE常用 │ ├─────────┼────────────────────────────────────────────────────────────┤ │ 3xx │ 重定向 │ ├─────────┼────────────────────────────────────────────────────────────┤ │ 301 │ Moved Permanently - 永久重定向 │ │ 302 │ Found - 临时重定向 │ │ 304 │ Not Modified - 资源未修改使用缓存 │ ├─────────┼────────────────────────────────────────────────────────────┤ │ 4xx │ 客户端错误 │ ├─────────┼────────────────────────────────────────────────────────────┤ │ 400 │ Bad Request - 请求参数错误 │ │ 401 │ Unauthorized - 未认证需要登录 │ │ 403 │ Forbidden - 无权限已登录但无权限 │ │ 404 │ Not Found - 资源不存在 │ │ 409 │ Conflict - 资源冲突如重复创建 │ │ 422 │ Unprocessable Entity - 语义错误如邮箱格式不对 │ │ 429 │ Too Many Requests - 请求过于频繁 │ ├─────────┼────────────────────────────────────────────────────────────┤ │ 5xx │ 服务端错误 │ ├─────────┼────────────────────────────────────────────────────────────┤ │ 500 │ Internal Server Error - 服务器内部错误 │ │ 502 │ Bad Gateway - 网关错误 │ │ 503 │ Service Unavailable - 服务不可用维护中/过载 │ │ 504 │ Gateway Timeout - 网关超时 │ └─────────┴────────────────────────────────────────────────────────────┘4.2 常见错误401 vs 403这是最容易混淆的一对状态码┌─────────────────────────────────────────────────────────────────────┐ │ 401 vs 403 区别 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 401 Unauthorized 403 Forbidden │ │ ─────────────── ───────────── │ │ │ │ 你是谁我不认识你 我知道你是谁但你没权限 │ │ │ │ • 未提供认证信息 • 已提供认证信息 │ │ • Token 无效/过期 • Token 有效 │ │ • 需要登录 • 权限不足 │ │ │ │ 示例 示例 │ │ GET /admin/users GET /admin/users │ │ Header: 无/Token无效 Header: Bearer {普通用户Token} │ │ → 401 → 403 │ │ │ └─────────────────────────────────────────────────────────────────────┘4.3 状态码使用示例# 400 Bad Request - 参数错误 app.route(/users, methods[POST]) def create_user(): data request.get_json() if not data.get(email): return jsonify({ error: Bad Request, message: Email is required, code: MISSING_FIELD }), 400 # 401 Unauthorized - 未认证 app.route(/profile, methods[GET]) def get_profile(): token request.headers.get(Authorization) if not token: return jsonify({ error: Unauthorized, message: Authentication required, code: NO_TOKEN }), 401 # 403 Forbidden - 无权限 app.route(/admin/users, methods[DELETE]) def delete_user(user_id): current_user get_current_user() if not current_user.is_admin: return jsonify({ error: Forbidden, message: Admin permission required, code: INSUFFICIENT_PERMISSION }), 403 # 404 Not Found - 资源不存在 app.route(/users/int:user_id, methods[GET]) def get_user(user_id): user find_user(user_id) if not user: return jsonify({ error: Not Found, message: fUser {user_id} not found, code: USER_NOT_FOUND }), 404 # 409 Conflict - 资源冲突 app.route(/users, methods[POST]) def create_user(): data request.get_json() if user_exists(data[email]): return jsonify({ error: Conflict, message: Email already registered, code: EMAIL_EXISTS }), 409 # 422 Unprocessable Entity - 语义错误 app.route(/users, methods[POST]) def create_user(): data request.get_json() if not is_valid_email(data[email]): return jsonify({ error: Unprocessable Entity, message: Invalid email format, code: INVALID_EMAIL }), 422五、版本管理URL路径 vs Header选哪个5.1 三种版本管理策略对比┌──────────────────────────────────────────────────────────────────────┐ │ API 版本管理策略对比 │ ├──────────────────────────────────────────────────────────────────────┤ │ │ │ 1. URL 路径版本推荐 │ │ ───────────────────── │ │ /v1/users │ │ /v2/users │ │ │ │ ✅ 优点直观、易于调试、可直接在浏览器测试 │ │ ❌ 缺点URL 变更破坏 REST 资源不变原则 │ │ │ │ 2. Header 版本 │ │ ──────────────── │ │ /users │ │ Accept: application/vnd.api.v1json │ │ │ │ ✅ 优点URL 不变符合 REST 原则 │ │ ❌ 缺点调试麻烦需要工具设置 Header │ │ │ │ 3. 查询参数版本 │ │ ──────────────── │ │ /users?version1 │ │ │ │ ✅ 优点简单 │ │ ❌ 缺点不标准容易被忽略 │ │ │ └──────────────────────────────────────────────────────────────────────┘5.2 推荐方案URL 路径版本# Flask 蓝图实现版本控制 from flask import Blueprint # v1 版本 v1_bp Blueprint(api_v1, __name__, url_prefix/v1) v1_bp.route(/users, methods[GET]) def get_users_v1(): v1 版本返回简单用户信息 return jsonify({ users: [ {id: 1, name: 张三} ] }) # v2 版本 v2_bp Blueprint(api_v2, __name__, url_prefix/v2) v2_bp.route(/users, methods[GET]) def get_users_v2(): v2 版本返回更详细的用户信息 return jsonify({ data: { users: [ { id: 1, name: 张三, profile: {...}, meta: {...} } ] }, pagination: {...} }) # 注册蓝图 app.register_blueprint(v1_bp) app.register_blueprint(v2_bp)六、分页、过滤、排序别让API变成数据黑洞6.1 分页设计GET /users?page1size20app.route(/users, methods[GET]) def get_users(): # 参数解析 page request.args.get(page, 1, typeint) size request.args.get(size, 20, typeint) # 限制最大页大小防止性能问题 size min(size, 100) # 查询数据 users, total query_users(pagepage, sizesize) # 计算分页信息 total_pages (total size - 1) // size return jsonify({ data: users, pagination: { page: page, size: size, total: total, total_pages: total_pages, has_next: page total_pages, has_prev: page 1 } })6.2 过滤设计GET /users?statusactiveroleadmincreated_after2024-01-01app.route(/users, methods[GET]) def get_users(): # 构建查询条件 filters {} if status in request.args: filters[status] request.args.get(status) if role in request.args: filters[role] request.args.get(role) if created_after in request.args: filters[created_at__gte] request.args.get(created_after) users query_users_with_filters(filters) return jsonify({data: users})6.3 排序设计GET /users?sort-created_at,name # - 表示降序 表示升序可省略app.route(/users, methods[GET]) def get_users(): sort_param request.args.get(sort, created_at) order_by [] for field in sort_param.split(,): if field.startswith(-): order_by.append((field[1:], desc)) elif field.startswith(): order_by.append((field[1:], asc)) else: order_by.append((field, asc)) users query_users(order_byorder_by) return jsonify({data: users})6.4 完整示例GET /api/v1/users?page1size20statusactivesort-created_at{ data: [ { id: 100, name: 张三, status: active, created_at: 2024-01-15T08:30:00Z }, { id: 99, name: 李四, status: active, created_at: 2024-01-14T16:45:00Z } ], pagination: { page: 1, size: 20, total: 156, total_pages: 8, has_next: true, has_prev: false }, filters_applied: { status: active }, sort_applied: [-created_at] }七、错误响应统一格式拒绝黑盒7.1 错误响应格式设计{ error: { code: VALIDATION_ERROR, message: Request validation failed, details: [ { field: email, code: INVALID_FORMAT, message: Invalid email format }, { field: age, code: OUT_OF_RANGE, message: Age must be between 18 and 120 } ], timestamp: 2024-01-15T08:30:00Z, request_id: req_abc123xyz, documentation_url: https://api.example.com/docs/errors/VALIDATION_ERROR } }7.2 错误处理中间件Flaskfrom flask import Flask, jsonify from werkzeug.exceptions import HTTPException app Flask(__name__) class APIError(Exception): 自定义API错误 def __init__(self, code, message, status_code400, detailsNone): self.code code self.message message self.status_code status_code self.details details or [] app.errorhandler(APIError) def handle_api_error(error): 处理自定义API错误 response { error: { code: error.code, message: error.message, details: error.details, timestamp: datetime.utcnow().isoformat() Z, request_id: g.get(request_id, unknown) } } return jsonify(response), error.status_code app.errorhandler(404) def handle_not_found(error): 处理404错误 return jsonify({ error: { code: RESOURCE_NOT_FOUND, message: The requested resource does not exist, timestamp: datetime.utcnow().isoformat() Z } }), 404 app.errorhandler(500) def handle_internal_error(error): 处理500错误 # 记录详细错误日志 logger.error(fInternal error: {error}, exc_infoTrue) return jsonify({ error: { code: INTERNAL_ERROR, message: An internal error occurred. Please try again later., timestamp: datetime.utcnow().isoformat() Z, request_id: g.get(request_id, unknown) } }), 500 # 使用示例 app.route(/users, methods[POST]) def create_user(): data request.get_json() errors [] if not data.get(email): errors.append({field: email, code: REQUIRED, message: Email is required}) elif not is_valid_email(data[email]): errors.append({field: email, code: INVALID_FORMAT, message: Invalid email format}) if not data.get(name): errors.append({field: name, code: REQUIRED, message: Name is required}) if errors: raise APIError( codeVALIDATION_ERROR, messageRequest validation failed, status_code422, detailserrors ) # ... 创建用户逻辑八、互动挑战检查你的API是否符合规范来做个小测试看看你的API设计能得多少分检查项符合不符合URL 使用名词复数/users 而非 /getUsers☐☐URL 使用小写字母和连字符☐☐正确使用 HTTP 方法GET/POST/PUT/PATCH/DELETE☐☐状态码使用准确200/201/204/400/401/403/404/422等☐☐错误响应格式统一☐☐支持分页、过滤、排序☐☐有 API 版本管理策略☐☐使用 HTTPS☐☐实现了限流保护☐☐有完善的接口文档☐☐得分10分RESTful 大师7-9分还不错继续优化4-6分还有很大提升空间0-3分建议重读本文 九、源码获取与思考题9.1 完整源码获取本文所有代码示例已整理成完整项目包含Flask RESTful API 完整实现错误处理中间件分页/过滤/排序工具类单元测试GitHub 地址https://github.com/example/restful-api-best-practices9.2 思考题场景题如果你的 API 需要支持批量删除一次删除多个用户你会如何设计是用DELETE /users?ids1,2,3还是POST /users/batch-delete为什么进阶题RESTful 原则中提到 HATEOAS超媒体作为应用状态引擎你了解吗在实际项目中你会使用吗实战题设计一个文件上传接口要求支持断点续传你会如何设计 URL 和 HTTP 方法欢迎在评论区分享你的答案十、系列预告本文是《后端架构设计》系列的第 20 篇后续还将更新主题21API 安全设计认证、鉴权、防刷最佳实践主题22GraphQL vs REST如何选择主题23API 性能优化缓存、压缩、限流实战主题24微服务架构下的 API 网关设计主题25OpenAPI 规范自动生成文档和 SDK关注不迷路点赞收藏防丢失总结RESTful API 设计看似简单但细节决定成败。记住这 10 个核心规范资源导向用名词不用动词URL 规范小写、复数、连字符分隔HTTP 方法GET/POST/PUT/PATCH/DELETE 各司其职状态码准确使用 2xx/3xx/4xx/5xx版本管理推荐 URL 路径方式分页过滤别让 API 变成数据黑洞错误格式统一、详细、可追溯HTTPS强制使用安全第一限流保护防止滥用接口文档及时更新方便协作好的 API 设计是工程师的基本功也是产品的门面。标签RESTful, API设计, HTTP, 后端开发, 接口规范, Web开发, 架构设计互动你的 API 设计得多少分评论区聊聊你踩过的坑