ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ECC api-design 技能详解:生产级 REST API 的设计规范、分页与版本化实践

ECC api-design 技能详解:生产级 REST API 的设计规范、分页与版本化实践 ECC api-design 技能详解生产级 REST API 的设计规范、分页与版本化实践【免费下载链接】ECCThe agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.项目地址: https://gitcode.com/GitHub_Trending/ev/ECC本篇基于 ECC 仓库中的api-design技能文档SKILL.md展开完整覆盖资源命名、HTTP 方法与状态码语义、响应信封、分页、过滤排序、鉴权、限流、版本化等生产级 REST API 设计要点并结合仓库中的模块清单与关联技能backend-patterns、contract-first说明其在 Agent 工作流中的定位。读完本文你可以直接套用该技能的命名规范、错误响应格式与 API 设计检查清单来设计或评审自己的 REST 接口。技能定位与激活时机api-design是 ECCThe agent harness performance optimization system提供的一项可被 Agent 自动调用的技能用于在设计或评审 REST 接口时给出一致的约定与最佳实践。其元数据定义在 SKILL.md 的 frontmatter 中name: api-designdescription涵盖资源命名、状态码、分页、过滤、错误响应、版本化与限流适用于设计或评审 REST 端点、资源名、状态码、分页或版本化的场景。与之配套的接口描述文件 agents/openai.yaml 进一步声明了该技能的展示名API Design、默认提示词Use $api-design to design production REST API resources and responses.以及策略allow_implicit_invocation: true意味着 Agent 在相关上下文中可以隐式激活该技能而无需用户显式点名。文档明确列出了激活时机设计新的 API 端点评审现有 API 契约为接口增加分页、过滤或排序实现 API 错误处理规划 API 版本化策略构建面向公开或合作伙伴的 API。从仓库的模块化安装配置看该技能被归入framework-language模块manifests/install-modules.json 中skills/api-design条目与backend-patterns、contract-first、coding-standards等核心框架/语言技能同属一个默认安装模块。在 skills/backend-patterns/SKILL.md 中也明确了分工后端层负责选择限流的集成点与错误形状HTTP 契约状态码、响应结构交给api-design滥用场景评审则交给security-reviewskills/contract-first/SKILL.md 则从契约先行角度补充说明——contract-first治理团队如何变更边界api-design治理一个好的 API 应该长什么样。两者配合使用是仓库推荐的 API 工程工作流。资源设计URL 结构与命名规则URL 结构技能文档要求资源是名词、复数、小写、kebab-case标准 CRUD 结构如下# 资源是名词、复数、小写、kebab-case GET /api/v1/users GET /api/v1/users/:id POST /api/v1/users PUT /api/v1/users/:id PATCH /api/v1/users/:id DELETE /api/v1/users/:id # 用子资源表达关系 GET /api/v1/users/:id/orders POST /api/v1/users/:id/orders # 无法映射到 CRUD 的动作动词要谨慎使用 POST /api/v1/orders/:id/cancel POST /api/v1/auth/login POST /api/v1/auth/refresh设计要点版本前缀/api/v1/直接出现在 URL 中对应后文版本化章节所有权关系用嵌套子资源表达只有确实无法映射到 CRUD 的动作如取消订单、登录、刷新令牌才允许在 URL 中出现动词路径段。命名规则好/坏对照# 好 /api/v1/team-members # 多词资源用 kebab-case /api/v1/orders?statusactive # 用查询参数做过滤 /api/v1/users/123/orders # 用嵌套资源表达所有权 # 坏 /api/v1/getUsers # URL 中出现了动词 /api/v1/user # 单数应使用复数 /api/v1/team_members # URL 中用了 snake_case /api/v1/users/123/getOrders # 嵌套资源里出现动词核心结论动词、单数、snake_case 是 URL 命名中的三类典型错误过滤语义一律下沉到查询参数而不是塞进路径。HTTP 方法与状态码语义方法语义表方法幂等安全用途GET是是读取资源POST否否创建资源、触发动作PUT是否完整替换资源PATCH否*否部分更新资源DELETE是否删除资源文档特别说明PATCH 在正确实现下可以做成幂等。状态码参考表# 成功 200 OK — GET、PUT、PATCH带响应体时 201 Created — POST需附带 Location 头 204 No Content — DELETE、PUT无响应体时 # 客户端错误 400 Bad Request — 校验失败、JSON 格式错误 401 Unauthorized — 缺失或无效的认证 403 Forbidden — 已认证但无权限 404 Not Found — 资源不存在 409 Conflict — 重复条目、状态冲突 422 Unprocessable Entity — 语义无效JSON 合法但数据有问题 429 Too Many Requests — 超出限流阈值 # 服务端错误 500 Internal Server Error — 未预期故障绝不暴露细节 502 Bad Gateway — 上游服务失败 503 Service Unavailable — 临时过载附带 Retry-After常见错误与正确做法# 错误一切返回 200 { status: 200, success: false, error: Not found } # 正确语义化地使用 HTTP 状态码 HTTP/1.1 404 Not Found { error: { code: not_found, message: User not found } } # 错误把校验错误返回成 500 # 正确返回 400 或 422并附字段级细节 # 错误创建成功返回 200 # 正确返回 201 并附 Location 头 HTTP/1.1 201 Created Location: /api/v1/users/abc-123这三组对照是文档反复强调的底线状态码必须承担语义职责错误信息必须结构化创建成功必须用 201 Location。响应格式统一信封与错误结构成功响应{ data: { id: abc-123, email: aliceexample.com, name: Alice, created_at: 2025-01-15T10:30:00Z } }集合响应带分页元信息{ data: [ { id: abc-123, name: Alice }, { id: def-456, name: Bob } ], meta: { total: 142, page: 1, per_page: 20, total_pages: 8 }, links: { self: /api/v1/users?page1per_page20, next: /api/v1/users?page2per_page20, last: /api/v1/users?page8per_page20 } }meta承载分页统计links提供可直接请求的相对链接客户端无需自行拼接查询参数。错误响应{ error: { code: validation_error, message: Request validation failed, details: [ { field: email, message: Must be a valid email address, code: invalid_format }, { field: age, message: Must be between 0 and 150, code: out_of_range } ] } }错误结构分三层顶层error对象、机器可读的code 人类可读的message、逐字段的details字段、消息、机器码。这让客户端既可以做字符串匹配code也可以给用户展示message、details。两种信封变体// 方案 A带 data 包裹的信封公开 API 推荐 interface ApiResponseT { data: T; meta?: PaginationMeta; links?: PaginationLinks; } interface ApiError { error: { code: string; message: string; details?: FieldError[]; }; } // 方案 B扁平响应更简单常见于内部 API // 成功直接返回资源本身 // 错误返回 error 对象 // 通过 HTTP 状态码区分成败方案 A 的信封为将来扩展追加meta、links或新的顶层字段留了空间适合公开 API方案 B 依赖状态码区分语义适合内部 API。选择后应全项目保持一致。分页Offset 与 Cursor 的取舍Offset 分页简单GET /api/v1/users?page2per_page20 # 实现 SELECT * FROM users ORDER BY created_at DESC LIMIT 20 OFFSET 20;优点实现简单支持跳到第 N 页。缺点大 offset 时性能差OFFSET 100000并发插入下结果不稳定。Cursor 分页可扩展GET /api/v1/users?cursoreyJpZCI6MTIzfQlimit20 # 实现 SELECT * FROM users WHERE id :cursor_id ORDER BY id ASC LIMIT 21; -- 多取一条用于判断 has_next{ data: [...], meta: { has_next: true, next_cursor: eyJpZCI6MTQzfQ } }优点性能不随位置衰减并发插入下结果稳定。缺点不能跳转到任意页cursor 是不透明值。多取一条LIMIT 21是文档给出的关键实现细节与其额外执行 count 查询判断是否有下一页不如取limit 1条多出的那条既证明存在下一页其 id 又能直接编码为next_cursor。选型决策表使用场景分页方式管理后台、小数据集1 万条Offset无限滚动、信息流、大数据集Cursor公开 API默认 Cursor可选提供 offset搜索结果Offset用户预期页码过滤、排序、搜索与稀疏字段集过滤# 简单等值过滤 GET /api/v1/orders?statusactivecustomer_idabc-123 # 比较运算符用括号记法 GET /api/v1/products?price[gte]10price[lte]100 GET /api/v1/orders?created_at[after]2025-01-01 # 多值逗号分隔 GET /api/v1/products?categoryelectronics,clothing # 嵌套字段点记法 GET /api/v1/orders?customer.countryUS四类语法覆盖了等值、区间比较、集合成员、嵌套字段过滤全部走查询参数与 URL 命名规则自洽。排序# 单字段前缀 - 表示降序 GET /api/v1/products?sort-created_at # 多字段逗号分隔 GET /api/v1/products?sort-featured,price,-created_at全文搜索# 搜索查询参数 GET /api/v1/products?qwirelessheadphones # 指定字段搜索 GET /api/v1/users?emailalice稀疏字段集# 只返回指定字段减小响应体积 GET /api/v1/users?fieldsid,name,email GET /api/v1/orders?fieldsid,total,statusincludecustomer.namefields控制返回列include控制关联资源展开——两者组合可以显著降低列表接口的载荷。认证与授权令牌认证# Authorization 头携带 Bearer 令牌 GET /api/v1/users Authorization: Bearer eyJhbGciOiJIUzI1NiIs... # API Key服务端到服务端 GET /api/v1/data X-API-Key: sk_live_abc123用户面 API 用 Bearer 令牌服务间调用用 API Key两种场景的凭证通道不同。授权模式资源级 角色级// 资源级校验所有权 app.get(/api/v1/orders/:id, async (req, res) { const order await Order.findById(req.params.id); if (!order) return res.status(404).json({ error: { code: not_found } }); if (order.userId ! req.user.id) return res.status(403).json({ error: { code: forbidden } }); return res.json({ data: order }); }); // 角色级校验权限 app.delete(/api/v1/users/:id, requireRole(admin), async (req, res) { await User.delete(req.params.id); return res.status(204).send(); });注意第一个示例中 404 与 403 的区分先查存在性不存在则 404存在但无权限则 403——这避免了用 403 泄漏资源存在这一信息同时严格遵循了前文状态码语义表。限流响应头与分级策略限流响应头HTTP/1.1 200 OK X-RateLimit-Limit: 100 X-RateLimit-Remaining: 95 X-RateLimit-Reset: 1640000000 # 超限时 HTTP/1.1 429 Too Many Requests Retry-After: 60 { error: { code: rate_limit_exceeded, message: Rate limit exceeded. Try again in 60 seconds. } }正常响应持续下发三个X-RateLimit-*头让客户端提前感知配额超限后返回 429 Retry-After错误体沿用统一的error结构。限流分级级别限额窗口用途匿名30 次/分钟按 IP公开端点已认证100 次/分钟按用户标准 API 访问高级1000 次/分钟按 API Key付费 API 套餐内部10000 次/分钟按服务服务间调用这里只定义契约层的分级与头格式限流计数器放在哪里实现文档明确交给后端技能处理——skills/backend-patterns/SKILL.md 中要求使用 Redis、网关或平台原生限流器等共享存储禁用进程内计数器部署重置、多副本分裂、Serverless 下失效。版本化策略URL 路径版本推荐/api/v1/users /api/v2/users优点显式、易于路由、可缓存缺点跨版本 URL 会变。请求头版本GET /api/users Accept: application/vnd.myapp.v2json优点URL 干净缺点难以测试、容易被遗忘。版本化策略五步法1. 从 /api/v1/ 起步 —— 需要时再谈版本 2. 同时最多维护 2 个活跃版本当前版 上一版 3. 弃用时间线 - 宣布弃用公开 API 提前 6 个月通知 - 加 Sunset 头Sunset: Sat, 01 Jan 2026 00:00:00 GMT - 过 sunset 日期后返回 410 Gone 4. 非破坏性变更不需要新版本 - 响应中新增字段 - 新增可选查询参数 - 新增端点 5. 破坏性变更需要新版本 - 删除或重命名字段 - 变更字段类型 - 变更 URL 结构 - 变更认证方式这套策略把何时升版变成了可机械执行的判定非破坏性变更走 v1 内演进破坏性变更走 v2且用Sunset头与 410 Gone 把弃用生命周期显式化。三种语言的落地实现TypeScriptNext.js API RouteZod 校验import { z } from zod; import { NextRequest, NextResponse } from next/server; const createUserSchema z.object({ email: z.string().email(), name: z.string().min(1).max(100), }); export async function POST(req: NextRequest) { const body await req.json(); const parsed createUserSchema.safeParse(body); if (!parsed.success) { return NextResponse.json({ error: { code: validation_error, message: Request validation failed, details: parsed.error.issues.map(i ({ field: i.path.join(.), message: i.message, code: i.code, })), }, }, { status: 422 }); } const user await createUser(parsed.data); return NextResponse.json( { data: user }, { status: 201, headers: { Location: /api/v1/users/${user.id} }, }, ); }实现要点ZodsafeParse失败时直接把 schema 的 issues 映射为details数组i.path.join(.)得到字段路径与错误响应结构一一对应成功后 201 Location 头响应体走data信封。PythonDjango REST Frameworkfrom rest_framework import serializers, viewsets, status from rest_framework.response import Response class CreateUserSerializer(serializers.Serializer): email serializers.EmailField() name serializers.CharField(max_length100) class UserSerializer(serializers.ModelSerializer): class Meta: model User fields [id, email, name, created_at] class UserViewSet(viewsets.ModelViewSet): serializer_class UserSerializer permission_classes [IsAuthenticated] def get_serializer_class(self): if self.action create: return CreateUserSerializer return UserSerializer def create(self, request): serializer CreateUserSerializer(datarequest.data) serializer.is_valid(raise_exceptionTrue) user UserService.create(**serializer.validated_data) return Response( {data: UserSerializer(user).data}, statusstatus.HTTP_201_CREATED, headers{Location: f/api/v1/users/{user.id}}, )DRF 场景的对应点用get_serializer_class区分创建用输入校验 serializer与读操作用输出 serializer写侧字段少、读侧字段全permission_classes落实认证要求create手工补上 201 Location。Gonet/httpfunc (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) { var req CreateUserRequest if err : json.NewDecoder(r.Body).Decode(req); err ! nil { writeError(w, http.StatusBadRequest, invalid_json, Invalid request body) return } if err : req.Validate(); err ! nil { writeError(w, http.StatusUnprocessableEntity, validation_error, err.Error()) return } user, err : h.service.Create(r.Context(), req) if err ! nil { switch { case errors.Is(err, domain.ErrEmailTaken): writeError(w, http.StatusConflict, email_taken, Email already registered) default: writeError(w, http.StatusInternalServerError, internal_error, Internal error) } return } w.Header().Set(Location, fmt.Sprintf(/api/v1/users/%s, user.ID)) writeJSON(w, http.StatusCreated, map[string]any{data: user}) }Go 示例展示了状态码决策树JSON 解析失败 → 400业务校验失败 → 422领域错误如邮箱已注册→ 409其余未知错误统一 500 且不外泄细节成功 → 201 Location。这与文档中500 never expose details的原则直接对应。三个框架的实现共同验证了同一套契约schema 校验 → 统一错误信封 → 语义化状态码 →data包裹 → 201 Location说明该技能的规范是语言无关的。API 设计检查清单文档最后给出了发布新端点前的完整自查清单可直接作为评审模板使用资源 URL 遵循命名约定复数、kebab-case、无动词使用正确的 HTTP 方法GET 读、POST 建等返回恰当的状态码不要一切 200用 schema 校验输入Zod、Pydantic、Bean Validation错误响应遵循标准格式含 code 与 message列表端点实现了分页cursor 或 offset要求认证或明确标注为公开做了授权检查用户只能访问自己的资源配置了限流响应不泄漏内部细节堆栈、SQL 错误与现有端点命名一致camelCase 与 snake_case 的统一已更新文档OpenAPI/Swagger 规格小结api-design技能把 REST 设计从经验问题变成了清单问题URL 命名有对照规则方法/状态码有语义表错误响应有固定信封分页有选型表版本化有可执行的五步策略最后用 12 项检查清单收口。在 ECC 的 Agent 工作流中它负责 HTTP 契约层限流存储选型看 skills/backend-patterns/SKILL.md多服务间防止字段漂移则配合 skills/contract-first/SKILL.md 使用——三者组合构成了仓库中 API 工程实践的完整分工。【免费下载链接】ECCThe agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.项目地址: https://gitcode.com/GitHub_Trending/ev/ECC创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表