你要在当前空仓库里,参照一份成熟的 Go SDK,实现一个功能等价、API 风格地道 的 Java SDK,用于 Novada API(代理管理 / 抓取 / 钱包)。通过 Maven 发布到 Maven Central。
-
Go 源码参考实现(权威设计来源),位于
./reference/novada-go/:client.goconfig.gotransport.goerrors.goenvelope.goversion.gointernal/transport/transport.go(子服务依赖的接口)proxy/*.goscraper/*.gowallet/*.goREADME.mdnovada-go-sdk-spec.md(中文设计规范,含实施顺序)
-
OpenAPI 文档(接口字段的权威来源,用来生成参数与必填校验),位于
./reference/openapi/:novada-openapi.json(51 个 /v1/* 管理接口)webunblocker_openapi.json(Web Unblocker /request)Serpapi_openapi.json(Google Search 等 scraper 参数)
先把 Go 源码当作"形状",把 OpenAPI 当作"字段事实来源"。两者冲突时以 OpenAPI 为准, 并在代码注释里标注。
- 鉴权:每个请求注入
Authorization: Bearer <API_KEY>。 - 三个 base URL,全部可覆盖,默认指向生产:
- 通用
https://api-m.novada.com—— 所有/v1/*(proxy、wallet,以及 scraper 目录下查 地区/余额/单价的 /v1/* 接口) - Web Unblocker
https://webunlocker.novada.com—— 仅 Web Unblocker 的POST /request - Scraper API
https://scraper.novada.com—— 仅 Scraper API 的POST /request
- 通用
- 两套传输编码:
/v1/*管理接口:multipart/form-data,空值字段省略;响应是统一包裹。- 抓取
/request:application/x-www-form-urlencoded;字段scraper_name、scraper_id、scraper_params(Params 列表 Jackson 序列化后的 JSON 字符串)、可选scraper_errors=true。
- 统一响应包裹
{code, data, msg, timestamp},只有code == 0才算成功。 解包流程:HTTP 非 2xx → ApiException(httpStatus);2xx 但 code!=0 → ApiException(code,msg); 成功 → 把data解码并返回。 - 重试:仅对网络错误(IOException)和 HTTP 429/5xx 重试(线性退避,base 200ms × attempt),
绝不对业务 code!=0 重试。重试次数
maxRetries默认 2。 - 列表接口的 data 形如
{list:[...], total:N}(个别还带 page/count)。 - 抓取
/request的响应体是抓取产物本身(JSON/CSV/XLSX),格式不定 → 返回原始文本, 不走 envelope 解码。例外:Google Search 走 envelope 并结构化解码(见 sources.go)。
-
语言/构建:Java 17+(用 record、sealed、switch pattern、text block),Maven(
pom.xml)。 Groupcom.novada,artifactnovada-java,包根com.novada。 -
HTTP 客户端用 JDK 内置
java.net.http.HttpClient(对应 Go 的net/http,零第三方 HTTP 依赖)。 允许调用方注入自定义HttpClient。 -
JSON 用 Jackson(
jackson-databind)——这是唯一必需的运行时第三方依赖(Java 无好用的内置 JSON 解析)。配置FAIL_ON_UNKNOWN_PROPERTIES=false以向前兼容。 -
顶层入口(Builder 模式,对应 Go 的 functional options):
import com.novada.NovadaClient; NovadaClient client = NovadaClient.builder("API_KEY") // 空则回退读 NOVADA_API_KEY 环境变量 .baseUrl("...").webUnblockerUrl("...").scraperUrl("...") .timeout(Duration.ofSeconds(30)).maxRetries(2) .httpClient(customHttpClient).userAgent("...") .build(); // 便捷:NovadaClient.of("API_KEY") 使用全部默认值无 key 且环境变量也空 → 抛
NovadaException(对应 Go 的 ErrNoAPIKey)。三个 URL 默认指向生产。 -
子服务作为 final 字段 + getter(或直接 public final 字段),小驼峰命名:
client.proxy().whitelist().list(...) / .add(...) / .delete(...) / .remark(...) client.proxy().account() / .residential() / .mobile() / .rotatingIsp() / .rotatingDc() / .staticIsp() / .dedicatedDc() / .unlimited() / .prohibitDomain() client.scraper().doRequest(req) / .api().youtube().videoPost(...) / .api().google().search(...) / .unblocker().scrape(...) / .unblocker().countries() / .universal().balance() / .universal().unit() / .browser().countries() client.wallet().balance() / .usageRecord(...)(
do是 Java 关键字,通用驱动方法命名为doRequest。) -
每个方法的参数用 不可变 Params 类:字段少的用
record;字段多/含可选项的用带 Builder 的类 (与 Go 的 *Params struct 一一对应)。必填字段缺失在发请求前抛ValidationException(列出所有缺失字段名,对应 Go 的 validator)。可选字段为null/空字符串/0 时不发送 (对应 Go 的 optStr/optInt)。需要"未设置 vs 设为 false"语义的布尔(Go 里是 *bool)用Boolean(可为 null)。 -
枚举:
Product用 enum + int code(Product.RESIDENTIAL(1)… 提供code()),值固定 (1 住宅 / 2 RotatingISP / 3 RotatingDC / 4 Unlimited / 5 StaticISP / 7 Unblocker / 9 Mobile / 10 BrowserAPI)。 -
返回值:管理接口的 data 用 Jackson 反序列化为 record/POJO(字段用
@JsonProperty("snake_case")映射,或全局 snake_case 命名策略);列表返回List<T>或带 total 的包装类; 抓取doRequest()返回含raw()的ScrapeResponse。 -
异常类型(在
com.novada.exception,全部extends RuntimeException,非受检异常):NovadaException // 基类 ApiException extends NovadaException // httpStatus() / code() / getMessage() AuthException extends ApiException // http 401/403 RateLimitException extends ApiException // http 429 ValidationException extends NovadaException // 客户端必填校验,method() / fields()用
instanceof判断类型(对应 Go 的 IsAuthError/IsRateLimited);ApiException.code()取业务 code。
pom.xml # Java 17、jackson 依赖、surefire、spotless、可选 shade
README.md
LICENSE # MIT,与 Go 版一致
src/main/java/com/novada/
NovadaClient.java # 顶层 client + Builder:三 baseURL、Bearer、子服务装配
Version.java # public static final String VERSION = "0.1.0"
internal/
Transport.java # doMultipart / doFormUrlencoded(+ Raw 变体)、重试、joinUrl、HttpClient 发请求
Envelope.java # envelope 解码 + code!=0 → ApiException + list 解包(用 Jackson)
Json.java # 共享 ObjectMapper
FormBuilder.java Validator.java
exception/
NovadaException.java ApiException.java AuthException.java
RateLimitException.java ValidationException.java
proxy/
ProxyService.java # 装配各子服务
WhitelistService.java AccountService.java ResidentialService.java MobileService.java
RotatingService.java StaticIspService.java DedicatedDcService.java
UnlimitedService.java ProhibitDomainService.java
Product.java # enum
model/ # 各 Params 与返回 record/POJO
scraper/
ScraperService.java # Target 枚举、doRequest()、ScrapeRequest/ScrapeResponse
ApiService.java YouTubeService.java GoogleService.java
UnblockerService.java UniversalService.java BrowserService.java
model/
wallet/
WalletService.java
model/
src/test/java/com/novada/ # JUnit 5 + mock server
examples/ # 可运行示例(或 examples 模块)
.github/workflows/ci.yml
- 用 JUnit 5 + 一个本地 mock HTTP server(推荐
com.sun.net.httpserver.HttpServer(JDK 内置) 或 WireMock/MockWebServer 作 test 依赖)模拟{code,data,msg,timestamp}响应。 必须覆盖:code!=0 → ApiException;HTTP 401/403/429/5xx 映射到对应异常;multipart 与 urlencoded 的编码正确(含 scraper_params 的 Jackson 序列化 + 空值省略);Bearer 注入; list 解包;重试逻辑(429→重试、业务 code!=0→不重试);ValidationException 在发请求前触发。 - 集成测试用环境变量
NOVADA_API_KEY控制(缺失则Assumptions.assumeTrue(...)跳过)。 - CI:GitHub Actions,矩阵 Java 17/21,跑
mvn -B verify(含 spotless 检查 + 单测)。 - 代码风格/静态检查:Spotless(google-java-format,类比 gofmt)+ 可选 Error Prone/SpotBugs (类比 golangci-lint)。
- 脚手架(pom.xml/spotless/surefire)+ 顶层 NovadaClient(Builder) + Transport + Envelope + exception + Json,用 mock server 跑通一个最简管理接口(white_list/list)验证解包与 Bearer。
- Proxy Whitelist + Account(最简单 CRUD),配单测,确立 multipart + Params/Builder 模式。
- 按 OpenAPI 批量铺开 proxy 其余子服务(用 requestBody properties 生成 Params 与必填校验)。
- Scraper doRequest 通用驱动(urlencoded + scraper_params JSON),单测验证编码。
- Scraper api().youtube() / api().google()(结构化)/ unblocker().scrape() + 各 /v1/* 查询接口。
- Wallet。
- README + examples + CI + Maven 发布元数据(sources/javadoc jar、GPG、central 配置说明)。
- HTTP 层只用 JDK
java.net.http.HttpClient;JSON 只用 Jackson;不引入 OkHttp/Guava/Apache HttpClient。 - 不要把 HTTP 200 当成功——一律以 code == 0 判定。
- 保持与 Go 版 README 等价的文档示例(同样的三个 baseURL 表、错误处理示例)。
- 多线程安全:
NovadaClient及子服务应无可变共享状态、可被多线程复用(对应 Go 的并发安全说明)。 - 每完成一步运行
mvn spotless:check verify,全绿再进入下一步。