将 Go SDK novada-go 移植为功能等价、风格地道的 Java 库,通过 Maven 发布到 Maven Central。
本计划与 PROMPT.md 配套:PROMPT 给 Claude Code 当系统级指令,PLAN 给人看进度与验收。
| Go 设计 | Java 对应 |
|---|---|
顶层 Client,三个 baseURL + Bearer 注入 + 重试 |
NovadaClient.builder(apiKey).baseUrl(...).maxRetries(...).build() |
Functional Options (WithTimeout...) |
Builder 模式 |
可替换 http.Client |
注入自定义 java.net.http.HttpClient,默认内部构造 |
context.Context(取消/超时) |
每请求 HttpRequest.timeout(...);无取消原语,仅超时 |
子包 + transport.Doer 接口解耦 |
内部 Transport,子服务持有其引用,挂在 client.proxy()/.scraper()/.wallet() |
参数 struct + 必填校验 → ValidationError |
不可变 Params(record 或 Builder)+ 校验抛 ValidationException |
统一响应包 {code,data,msg,timestamp},仅 code==0 成功 |
Envelope.decode(Jackson),code!=0 → ApiException |
APIError + IsAuthError/IsRateLimited/CodeOf |
NovadaException → ApiException → AuthException/RateLimitException,instanceof + code()/httpStatus() |
| 两套编码:multipart / x-www-form-urlencoded | 手写 multipart body builder / URLEncoder(含 Raw 变体) |
| 重试只针对网络错误 + 429/5xx,绝不重试业务 code | 同语义(IOException + 429/5xx) |
| 抓取响应格式不定,返回原始文本 | doRequest() 返回 ScrapeResponse{raw};Google 走结构化解码 |
| 仅标准库 | HTTP 用 JDK HttpClient;仅 Jackson 一个第三方运行时依赖 |
reference/novada-go/:Go 源码(*.go、README.md、novada-go-sdk-spec.md)。reference/openapi/:novada-openapi.json、webunblocker_openapi.json、Serpapi_openapi.json。 (用仓库根的scripts/sync_reference.sh同步,见文末。)- 确认 Maven 坐标(groupId/artifactId)、包根命名空间、发布仓库(Central / 内部 Nexus)。
pom.xml(Java 17、jackson-databind、JUnit 5、Spotless(google-java-format)、surefire、 可选 sources/javadoc 插件)、LICENSE(MIT)、Version.java、internal/Json.java(共享ObjectMapper, 关 FAIL_ON_UNKNOWN_PROPERTIES)。exception/:NovadaException / ApiException / AuthException / RateLimitException / ValidationException。internal/Envelope.java:envelope 解码 +code!=0转ApiException+ list 解包 helper。internal/Transport.java:doMultipart/doFormUrlencoded(含Raw变体)、用HttpClient发HttpRequest(POST)、Bearer 注入、Accept/User-Agent头、线性退避重试(仅 IOException + 429/5xx)、joinUrl、非 2xx →AuthException/RateLimitException/ApiException。手写 multipart boundary 编码。NovadaClient.java+ Builder:构造、三 baseURL、env 回退(System.getenv("NOVADA_API_KEY"))、 默认 HttpClient、子服务装配(先留空)。- 验收:JUnit + JDK
HttpServermock 一个white_list/list,断言 Bearer 头、multipart body、 envelope 解包、code!=0抛ApiException。mvn spotless:check verify全绿。
internal/FormBuilder.java(req/opt 系列)、internal/Validator.java、分页默认值。proxy/WhitelistService.java+proxy/AccountService.java,对应model/的 Params/返回类型 +Productenum。- 验收:每个方法成功路径 +
ValidationException(必填缺失)单测;确立"Params(record/Builder) + 校验 + multipart 编码 + Jackson 解码"的可复制模式。
- 依据
novada-openapi.json的 requestBody properties,批量生成Residential / Mobile / RotatingIsp / RotatingDc / StaticIsp / DedicatedDc / Unlimited / ProhibitDomain的方法、Params、返回类型与必填校验。 - 注意:导出类(静态 IP export)返回文件流 → 用
Raw变体不走 envelope(对应 Go 的DoMultipartRaw), 返回byte[]。 - 共享 model:
TimeRange、FlowBalance、FlowConsumeLog等。 - 验收:每个子服务至少一个编码 + 解包单测;Spotless 通过。
scraper/ScraperService.java:Target枚举、ScrapeRequest/ScrapeResponse、doRequest()(scraper_params = Json.write(params),URLEncoder编码,按target选 host,返回ScrapeResponse{raw})、 必填校验抛ValidationException。- 验收:单测断言 urlencoded body、
scraper_paramsJSON、host 路由 (ScraperAPI vs WebUnblocker)、scraper_errors。
api().youtube().videoPost(薄封装doRequest)、api().google().search(扁平 form 字段 + envelope 结构化解码,对应GoogleSearchResult/Data,json字段保留为JsonNode/原始)。unblocker().scrape(发target_url/response_format/js_render/country/wait_ms, 解码UnblockerResult:html/code/msg/msgDetail/useBalance)。- 走通用 host 的查询:
unblocker().countries()、browser().countries()、universal().balance()、universal().unit()。 - 验收:Google 结构化解码单测;unblocker scrape 字段单测;查询接口走通用 host 验证。
wallet().balance()、wallet().usageRecord(...)(分页默认 1/10)。- 验收:单测覆盖分页默认值 + 解码。
README.md:Maven/Gradle 依赖坐标、quick start、三 baseURL 表、proxy/scraper/wallet 各一例、 错误处理示例(对齐 Go 版)。examples/:proxy / scraper / wallet 各一个可运行示例(读NOVADA_API_KEY)。.github/workflows/ci.yml:Java 17/21 矩阵,mvn -B verify。- 发布元数据:sources-jar、javadoc-jar、GPG 签名、
distributionManagement/central-publishing 说明; 完善 POM 的name/description/url/licenses/scm/developers。 - 验收:
mvn -B verify通过;本地示例可跑;POM 满足 Central 校验要求。
- OpenAPI 是字段真相:表单字段以
novada-openapi.json的 requestBody 为准,逐字段核对必填/可选。 code == 0才成功:最容易踩的坑,不要用 HTTP 200 判断。- 空值省略语义:可选字段 null/空字符串/0 不发送;三态布尔用
Boolean(可 null)。 - 抓取响应不解 envelope(除 Google Search 外),格式不定,原样返回。
- 重试边界:业务
code!=0永不重试;只重试 IOException 与 429/5xx。 - multipart 手写:JDK HttpClient 无内置 multipart 编码,需手写 boundary + 字段;注意空值省略、
正确的 Content-Type(含 boundary)。urlencoded 用
URLEncoder.encode(..., UTF_8)。 - 线程安全:client 与子服务无可变共享状态,可被多线程复用(对应 Go 的并发安全)。
- 命名冲突:Go 的
Do在 Java 用doRequest;code()与Throwable无冲突(Go 的 CodeOf →ApiException.code())。
仓库根的 scripts/sync_reference.sh 会把 Go 项目源码与 OpenAPI 拷入 reference/。
如 Go 项目路径不同,编辑脚本顶部的 GO_SRC 变量。