14 KiB
MVP 共享 API 契约
本文件是服务端、Web 和 Android 的共享契约事实来源。字段可以在实施工单中补充,但语义变化必须同步更新本文档。
通用约定
- 正式环境仅允许 HTTPS。
- 每台设备使用独立 Bearer Token。
- 时间使用 RFC 3339 UTC,金额使用整数分。
- 所有写接口携带
requestId并保证幂等。 - 错误响应包含稳定的
code、可读的message和retryable。
管理端:PDD 商品
POST /api/admin/v1/pdd-products
PATCH /api/admin/v1/pdd-products/{productId}
GET /api/admin/v1/pdd-products
新增请求只提交 url。服务端展开并规范化 URL、提取 goods_id;无法提取时返回 PDD_GOODS_ID_INVALID,已存在时返回 PDD_PRODUCT_EXISTS 和现有商品 ID。编辑 URL 不改变已有任务快照。
管理端:采集规则
POST /api/admin/v1/collection-rules
PATCH /api/admin/v1/collection-rules/{ruleId}
DELETE /api/admin/v1/collection-rules/{ruleId}
GET /api/admin/v1/collection-rules
GET /api/admin/v1/collection-rules/templates/pdd-product-detail-v1
规则只有 name 和 content 等当前值,创建成功后立即可用。删除为软删除;已删除规则不能创建新任务,已有任务继续使用任务内快照。
模板接口返回服务端内置的完整 v2 content,以及浏览器包名、别名数量/长度、超时、遍历上限和规格面板滑动动作的可编辑范围。管理端以安全表单修改这些参数,并显示只读 JSON 预览;模板中的采集器 ID、价格解析器、页面根证据、动作类型和语义目标不可在界面中改写。最终创建或更新请求仍由服务端按完整 v2 契约重新校验,不能依赖前端校验。
content 支持原有 schemaVersion: 1 线性步骤和 schemaVersion: 2 类型化采集器契约。v1 契约如下:
{
"schemaVersion": 1,
"packageName": "com.xunmeng.pinduoduo",
"steps": [
{
"id": "extract-title",
"page": "product-detail",
"activityName": "com.xunmeng.pinduoduo.activity.NewPageActivity",
"action": "extract",
"selector": {"resourceId": "com.xunmeng.pinduoduo:id/title"},
"field": "title",
"timeoutMs": 5000
}
]
}
action只允许wait、click、input、back、extract;除back外都必须有非空selector。selector可组合resourceId、text、contentDescription、className、clickable,执行操作前必须唯一命中;不唯一时以RULE_AMBIGUOUS失败,不猜测目标。- 点击步骤仍以唯一命中的节点作为语义证据;若文字节点本身不可点击,只允许沿其父链点击最近的
clickable容器,不搜索兄弟节点或相近候选。父链不存在可点击容器时以RULE_ACTION_FAILED失败。 - 每一步可以用
packageName覆盖根级默认值;只允许拼多多、受支持浏览器和 Android 系统选择器包。 activityName可声明该步骤允许执行的精确 Activity;商品详情页规则应同时校验 PDD 包名、目标 Activity 和唯一控件,避免登录页等同包页面误判为商品页。input必须有value,extract必须有field;单步timeoutMs范围为 100~30000 毫秒。optional: true仅表示单步超时后继续,适合不同 ROM 可能不存在的浏览器/系统“打开”确认层;多匹配和动作执行失败仍立即失败。- 规则解析阶段拒绝支付、付款、提交订单等危险目标。完整控件树只在 Android 内存中用于匹配,不保存也不上传。
- 浏览器和 Android 系统包中的
click只允许精确目标打开拼多多APP、打开拼多多 App或打开;其它点击在规则解析阶段拒绝。Android 先以浏览器打开服务端已规范化的 PDD URL,再由这些受控步骤进入拼多多。
v2 PDD 商品详情规则
v2 完整示例见 PDD 商品详情规则。规则声明:
ruleType: pddProductDetail。navigation.steps:只负责浏览器和系统确认层;不能点击 PDD 页面。pageEvidence:精确的 PDD 包名、Activity 和非空节点证据。collector.collectorId: pddProductDetailV1:引用 Agent 中经过测试的类型化能力,不下发可执行代码。hooks.afterSpecPanelOpen:固定阶段的安全动作;当前只允许对语义目标specPanel执行swipe,方向为上下左右、单动作次数 1~5、等待 0~2000 毫秒,单阶段最多 8 个动作。collector.dimensionAliases、timeoutsMs和limits:分别管理规格标题别名、超时和遍历/SKU 上限。
例如规格面板打开后向上滑动两次,只修改规则:
{"hooks":{"afterSpecPanelOpen":[{"action":"swipe","target":"specPanel","direction":"up","count":2,"settleMs":350}]}}
Agent 使用可扩展的类型化动作注册表,而不是任意脚本。采集规则不能创建订单;未来采购规则可以引用单独审核的创建订单能力,但任何规则都不能执行付款。
Android 的 pddProductDetailV1 采集器执行以下固定流程:
- 以规则中的包名、精确 Activity 和节点选择器验证商品详情页,并持续识别登录、验证码、风控和失效商品页面。
- 在商品页有限次纵向查找安全规格入口;规格面板必须同时具备规格维度和确认摘要或可滚动区域等强证据。
- 颜色列表先向起点归边,再按可见行蛇形去重遍历;每次点击都在最新无障碍树中重新定位唯一控件,确认选中后连续读取相同价格。
- 尺码仅通过有限次纵向滑动读取,不点击尺码;颜色价格展开到该颜色下的可用尺码 SKU。
- 缺失颜色价格、尺码、第三规格维度或超过 SKU 上限时提交有界的
completed_partial;不猜测缺失值。采集期间离开商品页或出现验证码、登录、风控时明确失败。
无障碍节点只投影为 Agent 进程内的瞬时不可变模型,不序列化、不上传、不写入文件。完整状态机接入后 Agent 才上报 collector.pdd.product-detail.v1。
管理端:采集任务
POST /api/admin/v1/collection-tasks
GET /api/admin/v1/collection-tasks
GET /api/admin/v1/collection-tasks/{taskId}
POST /api/admin/v1/collection-tasks/{taskId}/reset
DELETE /api/admin/v1/collection-tasks/{taskId}
创建请求:
{
"requestId": "uuid",
"pddProductId": "product-id",
"ruleId": "rule-id",
"deviceId": "device-id-or-null"
}
服务端在事务中复制当前 url、goods_id 和完整规则内容到任务。同一商品已有 pending 或 running 任务时返回 PDD_PRODUCT_TASK_ACTIVE。
重置只允许 completed、completed_partial 或 failed:
- 保留
pddProductId、deviceId、urlSnapshot、goodsIdSnapshot、ruleId和ruleSnapshot。 - 删除全部规格维度、规格值、SKU 和 SKU 值关联。
- 清空标题、店铺、销量、评价、错误与执行时间。
- 状态恢复为
pending。
running 禁止重置和删除。失败任务允许删除后重新创建。
reset 请求体和失败任务 DELETE 请求体均为 {"requestId":"uuid"}。只允许删除 failed;重置允许 completed、completed_partial、failed。二者均按 requestId 幂等。
Android:注册与心跳
POST /api/agent/v1/register
POST /api/agent/v1/heartbeat
客户端首次启动生成并持久化 installId,直接提交设备信息;服务端收到合法请求即创建或更新设备,不要求注册码、后台审核或人工确认。installId 在服务端唯一,硬件标识不能作为替代唯一键。响应返回 deviceId、Device Token 和心跳间隔,客户端安全保存 Token,服务端只保存其不可逆摘要。后续用 installId 保持同一安装实例的设备身份;心跳上报设备状态和 currentTaskId,服务端据此判断在线/空闲,但任务领取仍以数据库原子约束为准。
注册请求:
{
"requestId": "uuid",
"installId": "uuid",
"name": "OPPO-PKG110",
"manufacturer": "OPPO",
"model": "PKG110",
"androidVersion": "16",
"agentVersion": "0.1.0",
"pddVersion": "7.72.0",
"capabilities": ["rule.schema.v2", "action.swipe.v1", "collector.pdd.product-detail.v1"]
}
- 新
installId立即创建设备并返回一次deviceToken;响应带Cache-Control: no-store。 - 相同
requestId重放只幂等返回原deviceId,不再次返回 Token,也不覆盖设备信息。 - 已存在
installId的新请求必须携带该设备的 Bearer Token,认证成功后幂等更新设备信息,不轮换 Token。 - 缺少或使用错误/已吊销 Token 返回 HTTP 409 和
DEVICE_INSTALL_ID_CONFLICT。 - 生产模式只接受 TLS。只有服务部署在可信反向代理之后并显式设置
GOAUTO_TRUST_FORWARDED_PROTO=true时,服务端才接受代理的X-Forwarded-Proto: https;不得在服务直接暴露公网时开启。注册接口另有单实例、按直连来源 IP 的基础限流,网关仍须设置共享限流。 capabilities最多 32 项,使用小写的版本化能力名。旧 Agent 可以不提交该字段并继续执行 v1;v2 任务只能由包含规则所需全部能力的设备领取。
管理员设备动作:
POST /api/admin/v1/devices/{deviceId}/disable
POST /api/admin/v1/devices/{deviceId}/token/revoke
两者都要求 go-admin 管理端认证与角色授权。吊销同时停用设备;重新启用和重新签发 Token 不在本工单范围。
心跳请求使用设备 Bearer Token:
{
"requestId": "uuid",
"currentTaskId": "task-id-or-null",
"capabilities": ["rule.schema.v2", "action.swipe.v1"]
}
服务端校验 currentTaskId 必须与该设备唯一的 running 任务一致,不一致返回 HTTP 409 和 DEVICE_TASK_MISMATCH,不更新心跳。响应返回服务端确认的 currentTaskId、online、由运行任务派生的 busy、服务端时间和下一次心跳间隔。相同 requestId 重放不推进心跳时间。
默认心跳间隔 15 秒、离线阈值 45 秒、扫描间隔 15 秒。超过阈值后设备标为 offline;其 running 任务同时清除租约和活动 guard,转为 failed,错误码为 DEVICE_OFFLINE,且不自动重试或换机。设备重新发送合法心跳后可以恢复 online,但失败任务不会自动恢复。
Android:获取、领取和开始任务
GET /api/agent/v1/tasks/next
POST /api/agent/v1/tasks/{taskId}/claim
POST /api/agent/v1/tasks/{taskId}/start
claim 和 start 请求体都为 {"requestId":"uuid"}。相同 requestId 重放返回首次成功后的当前任务载荷,不延长租约、不增加租约版本。next 没有可用任务时返回 HTTP 204。
领取规则:
- 指定设备的任务只能由指定设备领取。
deviceId为空的任务可由在线且没有活动任务的设备领取。claim在一个数据库事务中设置设备和租约,竞争失败返回TASK_ALREADY_CLAIMED。- 设备已有活动任务时返回
DEVICE_BUSY。 - 指定设备和领取设备必须具备规则快照要求的全部能力;不兼容时返回
DEVICE_CAPABILITY_MISMATCH。未指定设备任务会跳过不兼容设备。
claim 保持任务为 pending,写入设备、两分钟领取租约和递增的 leaseVersion;start 只接受当前设备持有的有效租约,将状态原子改为 running、记录开始时间并续租。Android 进程还必须以本地互斥锁确保同一时刻只有一个任务进入执行器。
任务载荷包含:
{
"taskId": "task-id",
"pddProductId": "product-id",
"urlSnapshot": "https://mobile.yangkeduo.com/goods.html?goods_id=123",
"goodsIdSnapshot": "123",
"ruleId": "rule-id",
"ruleSnapshot": {},
"timeoutSeconds": 120
}
规则已经软删除不影响载荷和执行。
Android:提交结果
POST /api/agent/v1/tasks/{taskId}/result
{
"requestId": "uuid",
"status": "completed_partial",
"product": {
"pddGoodsId": "123",
"title": "商品标题",
"shopName": "店铺名",
"salesText": "已拼5872件",
"reviewCount": 64
},
"dimensions": [
{"key": "color", "name": "颜色", "values": ["黑色", "白色"]},
{"key": "size", "name": "尺码", "values": ["M", "L"]}
],
"colorPrices": [
{"color": "黑色", "priceCent": 1000},
{"color": "白色", "priceCent": 1200}
],
"skus": [
{"specs": {"color": "黑色", "size": "M"}, "priceCent": 1000, "available": true}
],
"missing": ["白色/L"]
}
服务端验证任务当前设备和状态后,在一个事务中更新 collection_task 结果字段并重建结果子表。终态任务拒绝不同内容的再次提交;相同 requestId 幂等返回原响应。
Android:提交失败
POST /api/agent/v1/tasks/{taskId}/fail
| 错误码 | 含义 | 默认重试 |
|---|---|---|
DEVICE_OFFLINE |
执行中连接中断 | 否 |
ACCESSIBILITY_NOT_READY |
无障碍未绑定 | 否 |
PDD_LOGIN_REQUIRED |
PDD 登录失效 | 否 |
PDD_CAPTCHA_REQUIRED |
验证码或人机验证 | 否 |
PDD_RISK_CONTROL |
风控页面 | 否 |
PDD_LINK_INVALID |
商品链接无效、商品不存在或已下架 | 否 |
RULE_NOT_MATCHED |
找不到唯一控件或页面 | 否 |
RULE_AMBIGUOUS |
规则同时匹配多个控件 | 否 |
TASK_ALREADY_CLAIMED |
未指定任务已被其他设备领取 | 否 |
DEVICE_BUSY |
设备已有活动任务 | 否 |
DEVICE_CAPABILITY_MISMATCH |
设备缺少任务规则要求的版本化能力 | 否 |