# Distribution API ## 1. 行为契约 Distribution API 只提供一次性独占提取: ```text 筛选 AVAILABLE -> 锁定候选 -> 校验 Gateway 预留、TTL、健康与过滤条件 -> 原子 AVAILABLE -> EXTRACTED -> 写 Extraction Record -> 提交事务 -> 返回真实代理地址 ``` 事务提交前不得把地址写给 Client。返回成功后,该 Proxy 不再参与 Gateway、 再次提取或可用库存统计。系统不跟踪 Client 是否使用、使用并发或何时停止, 也不提供 release、renew、status 或租约端点。 HTTP 契约源文件:`api/openapi/proxy-pool.yaml`。 ## 2. 提取请求 ```http POST /api/v1/proxies/extract HTTP/1.1 Host: 127.0.0.1:8081 Content-Type: application/json X-API-Key: TOKEN X-Request-ID: req_01J4EXAMPLE Idempotency-Key: extract-01J4EXAMPLE { "count": 5, "fulfillment": "partial", "filters": { "protocols": ["http"], "regions": ["shanghai"], "carriers": ["telecom"], "allowedUpstreams": ["provider-a"] } } ``` PowerShell 调用示例: ```powershell $body = @{ count = 5 fulfillment = "partial" filters = @{ protocols = @("http"); regions = @("shanghai") } } | ConvertTo-Json -Depth 4 Invoke-RestMethod ` -Method Post ` -Uri http://127.0.0.1:8081/api/v1/proxies/extract ` -Headers @{ "X-API-Key" = "TOKEN"; "Idempotency-Key" = "extract-SERIAL" } ` -ContentType application/json ` -Body $body ``` 请求约束: - `count` 至少为 1,且不超过服务端 `maxCountPerRequest`。 - `fulfillment` 省略时使用服务端配置,默认 `partial`。 - 所有过滤数组执行“数组内 OR、不同维度 AND”。空数组等同不限制。 - 每个过滤维度最多包含 64 个值,且数组内不得重复。 - `allowedUpstreams` 只能缩小 Client 可访问的 Upstream 集,不能扩大权限。 ## 3. 成功响应 ```json { "requestId": "req_01J4EXAMPLE", "requested": 5, "returned": 2, "proxies": [ { "id": "px_01J4A", "protocol": "http", "host": "192.0.2.10", "port": 8080, "username": "USER", "password": "PASSWORD", "url": "http://USER:PASSWORD@192.0.2.10:8080", "region": "shanghai", "carrier": "telecom", "upstream": "provider-a", "expiresAt": "2026-07-28T10:30:00Z", "remainingTtlSeconds": 83, "extractedAt": "2026-07-28T10:28:37Z" } ] } ``` `returned` 必须等于 `proxies` 数组长度。`partial` 模式中 `returned` 可以为 零;这仍表示请求语法有效,只是当前没有可提取库存。 返回的 `password` 和 `url` 含真实凭据。Client 必须限制日志、追踪和错误上报 对响应体的采集。服务端访问日志只记录数量、过滤摘要、Client、Upstream 和 Request ID,不记录地址或凭据。 ## 4. 数量语义 ### 4.1 partial 锁定的符合条件数量少于 `count` 时,提交实际数量: ```text requested=10, eligible=6, reserve=0 -> returned=6 ``` ### 4.2 allOrNothing 锁定数量不足时整个事务回滚: ```json { "type": "https://proxy-pool.local/problems/insufficient-proxies", "title": "Insufficient proxies", "status": 409, "code": "INSUFFICIENT_PROXIES", "detail": "requested 10 proxies but only 6 are currently eligible", "requestId": "req_01J4EXAMPLE" } ``` 冲突响应后,之前被该请求临时锁定的 Proxy 仍为 AVAILABLE。 ## 5. 资格过滤与 Gateway 预留 候选必须同时满足: 1. 权威状态是 AVAILABLE。 2. 没有 Worker 所有权,或已完成 Drain 且 Active/Reserved 均为零。 3. 剩余 TTL 不低于 `minRemainingTTL`。 4. 最近健康检查不早于 `maxHealthCheckAge`。 5. protocol、region、carrier、Upstream 满足过滤与 Client 权限。 6. 提取后符合条件的共享库存不低于 `reserveForGateway`。 Worker-owned Proxy 不得直接提取。Controller 需要先发布 DRAINING,等待 Worker 确认没有 Active/Reserved,再清除所有权并进入提取事务。快照延迟时仍禁止 Gateway 与 Client 同时获得同一 Proxy。 ## 6. 并发与事务 PostgreSQL Adapter 应在一个事务内使用 `FOR UPDATE SKIP LOCKED` 获取候选, 更新状态并写审计记录。所有状态更新必须包含 `state = 'AVAILABLE'` 前置条件。 关键不变量: - 两个并发成功响应的 Proxy ID 集合交集为空。 - 状态更新或审计写入任一步失败,整个批次不返回。 - `allOrNothing` 不足时零行变为 EXTRACTED。 - PostgreSQL 不可写时返回 503,不以内存结果冒充成功。 ## 7. 幂等 提取是消耗库存的写操作。客户端收到超时后盲目重试可能再次提取一批不同 Proxy,因此自动重试应提供稳定的 `Idempotency-Key`。 服务端幂等记录至少包含:Client ID、Key、请求体摘要、提交结果和过期时间。 同一 Client、同一 Key、相同摘要返回首次结果;摘要不同返回 409。幂等记录和 Extraction Record 必须与状态更新处在相同事务边界或由同一权威恢复流程保证。 ## 8. 认证、识别与限制 认证由部署配置决定,OpenAPI 同时声明 API Key、Basic、Bearer 和无认证场景。 无认证并不关闭来源 CIDR、Client 识别和限流: - 直连请求使用来源 IP 形成匿名 Client。 - 只有来源属于 `trustedProxies` 时才接受转发头。 - 全局和每 Client 限流在查询库存前执行。 - 过滤条件、数量、请求体和 Header 都有长度/数量上限。 ## 9. 错误模型 所有非 2xx 响应使用 `application/problem+json`: - `400`:JSON、Header 或基本格式无效。 - `401`:认证失败。 - `403`:来源控制、权限或 Upstream 访问被拒绝。 - `409`:allOrNothing 库存不足,或幂等 Key 冲突。 - `413`:请求体超过 Distribution 配置上限。 - `415`:请求体不是 `application/json`。 - `422`:数量、枚举或过滤组合违反业务约束。 - `429`:全局或 Client 速率限制,响应 `Retry-After`。 - `503`:PostgreSQL 不可写、服务排空或权威状态不可用。 - `500`:未分类的内部错误;响应不包含底层错误文本。 错误响应不得包含 Provider Secret、Proxy 凭据、SQL 或内部拓扑。 ## 10. 审计记录 每个被提交的 Proxy 对应一条 Extraction Record: ```text proxyId, clientId, sourceIP, requestId, upstream, extractedAt, expiresAt ``` 无认证时 `clientId` 使用可信代理链解析后的规范化来源 IP 稳定标识,并保留 `sourceIP`。记录只用于审计、排错和计费事实,不承担资源归还语义。Proxy 到期后可以清理运行记录,但 Extraction Record 按审计保留策略归档。 ## 11. 运行时实现边界 `distribution.Handler` 是薄 HTTP Adapter,只负责严格解码、Header/DTO 校验、 身份结果注入、错误映射和健康探针。独占提取、TTL、Gateway 预留及幂等事务 继续由 `extraction.Service` 和持久化 Store 承担。 请求体解码、Request ID 与 Problem JSON 统一复用 `platform/httpapi`。认证、 可信代理、来源控制、Client ID 和准入限流由必需的 `IdentityResolver` 注入, 标准装配使用 `httpsecurity.Protection`;解析结果至少包含稳定 Client ID 或 Source IP,且安全检查先于请求体解析。`controller/runtime` 将 Distribution 与 Admin 装配到不同 `net.Listener`,健康端点保持公开,提取端点执行独立认证。