Skip to content

Latest commit

 

History

History
150 lines (133 loc) · 10.2 KB

PROMPT.md

File metadata and controls

150 lines (133 loc) · 10.2 KB

任务:把 novada-go SDK 移植为一个 Java 库(novada-java)

你要在当前空仓库里,参照一份成熟的 Go SDK,实现一个功能等价、API 风格地道 的 Java SDK,用于 Novada API(代理管理 / 抓取 / 钱包)。通过 Maven 发布到 Maven Central。

输入材料(必须先读)

  1. Go 源码参考实现(权威设计来源),位于 ./reference/novada-go/:

    • client.go config.go transport.go errors.go envelope.go version.go
    • internal/transport/transport.go(子服务依赖的接口)
    • proxy/*.go scraper/*.go wallet/*.go
    • README.md novada-go-sdk-spec.md(中文设计规范,含实施顺序)
  2. 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 化的设计决定(请严格执行)

  • 语言/构建:Java 17+(用 record、sealed、switch pattern、text block),Maven(pom.xml)。 Group com.novada,artifact novada-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。

目录结构(Maven 标准布局)

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

测试与 CI

  • 用 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)。

实施顺序(务必分步,每步先跑通再继续;详见 PLAN.md)

  1. 脚手架(pom.xml/spotless/surefire)+ 顶层 NovadaClient(Builder) + Transport + Envelope + exception + Json,用 mock server 跑通一个最简管理接口(white_list/list)验证解包与 Bearer。
  2. Proxy Whitelist + Account(最简单 CRUD),配单测,确立 multipart + Params/Builder 模式。
  3. 按 OpenAPI 批量铺开 proxy 其余子服务(用 requestBody properties 生成 Params 与必填校验)。
  4. Scraper doRequest 通用驱动(urlencoded + scraper_params JSON),单测验证编码。
  5. Scraper api().youtube() / api().google()(结构化)/ unblocker().scrape() + 各 /v1/* 查询接口。
  6. Wallet。
  7. 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,全绿再进入下一步。