从 Spring Boot 调用 CAPTCHA API
注册一个服务端 CaptchaClient bean,仅创建一次任务,然后使用已保存的 ID 轮询。下面的辅助类使用同步的 Spring RestClient 调用,并返回 Gson JsonObject 响应。
本页内容
添加依赖并注册辅助类
提供的辅助类使用 Java 21+、Spring Web 7.0.10 和 Gson 2.15.0。在现有 Spring Boot 项目中,请保留由 Boot 管理的兼容 Spring 版本,不要为了匹配示例而仅覆盖 spring-web 的版本。如果应用尚未包含 Gson,请将其添加为依赖。
// Java 21+, Spring Web 7.0.10 and Gson 2.15.0; use your Boot dependency management.
// Save as CaptchaClient.java in your application's package.
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.net.http.HttpClient;
import java.time.Duration;
import org.springframework.http.MediaType;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.web.client.RestClient;
public final class CaptchaClient {
private final RestClient api;
public CaptchaClient(String key) {
var transport = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
var factory = new JdkClientHttpRequestFactory(transport);
factory.setReadTimeout(Duration.ofSeconds(25));
api = RestClient.builder().baseUrl("https://api.solvethree.com/v1")
.requestFactory(factory).defaultHeader("Authorization", "Bearer " + key).build();
}
public JsonObject create(String site, String proxy, String requestId) {
var input = new JsonObject();
input.addProperty("site", site);
input.addProperty("action", "login");
input.addProperty("proxy", proxy);
input.addProperty("requestId", requestId); // Save before calling create.
String body = api.post().uri("/tasks?waitMs=0").contentType(MediaType.APPLICATION_JSON)
.body(input.toString()).retrieve().body(String.class);
return JsonParser.parseString(body).getAsJsonObject();
}
public JsonObject get(String id) {
if (!id.matches("[A-Za-z0-9_-]+")) throw new IllegalArgumentException("Invalid task ID");
String body = api.get().uri("/tasks/{id}?waitMs=15000", id)
.retrieve().body(String.class);
return JsonParser.parseString(body).getAsJsonObject();
}
}
// Construct once with System.getenv("SOLVETHREE_API_KEY") in a server-side @Bean.
// Call create once, save its id, then get(id) until ready/failed or your deadline.
// RestClient throws on HTTP errors; keep the saved requestId and any known task id.将该类保存为应用包中的 CaptchaClient.java。在服务端的 @Bean 工厂方法中,使用 SOLVETHREE_API_KEY 环境变量构造一次实例。启动时检查是否已配置密钥。辅助类会将其添加到 Bearer 授权标头中;不要将其放入浏览器端代码。
通过构造函数注入复用该 bean。RestClient 可以跨线程复用,但每次请求都会阻塞调用线程。不要在 WebFlux 事件循环中调用这些方法。Spring 的 REST 客户端文档介绍了该同步客户端。
传输层使用 10 秒连接超时和 25 秒读取超时,且不跟随重定向。响应以字符串形式读取并由 Gson 解析,不会映射为强类型响应对象。
设置站点、操作和代理
调用 create 前,请在工作区中为目标站点配置一个已启用的配置文件。发送主机名和操作不会创建该配置文件。如果尚未配置站点,请参阅站点配置文件设置。
- 将主机名作为
site传入,而不是完整的页面 URL。reCAPTCHA 请求没有url或type字段。 - 辅助类将
action硬编码为login。如果预期请求使用不同的操作,请修改该值,或将其设为方法参数。 - 传入一个代理 URL。API 要求在
proxy和proxyCandidates中且仅指定其中一个;此辅助类实现的是单代理选项。 - 辅助类省略了
recaptchaType,因此 API 使用默认值enterprise-v3。
仅创建一次,然后使用已保存的任务 ID 轮询
- 1.
创建前保存请求 ID
选择一个包含 1–64 个 ASCII 字母、数字、下划线或连字符的
requestId。调用create前,将其与任务输入一起保存。如果创建响应丢失,可用它恢复同一个任务。 - 2.
调用 create 并保存返回的 id
使用主机名、代理和已保存的请求 ID 调用 bean 的
create方法。该方法向/v1/tasks发送一次 POST 请求,并设置waitMs=0。保存返回的id;后续查询使用该任务 ID,而不是请求 ID。 - 3.
调用 get,直到任务达到终态
将已保存的任务 ID 传给
get。每次查询都会请求waitMs=15000,并在等待响应时阻塞。对于pending或solving,在应用层截止时间内继续轮询。对于ready或failed,停止轮询。HTTP 超时限制的是单次请求,而不是整个轮询循环。 - 4.
仅在就绪时读取求解结果
对于 reCAPTCHA,将
solution.token提交给预期目标,而不是提交任务 ID。使用返回的userAgent和secChUa,并继续使用成功求解时的同一代理。不要替换为无关的浏览器标头,也不要更改目标 URL。就绪的任务会提供求解结果,但这并不代表目标已接受后续请求。
分别处理 HTTP 错误和任务失败
- create 调用在收到任务 ID 前超时。
即使客户端未收到响应,服务端也可能已创建任务。
试试这样做: 使用已保存的
requestId重复发送相同的任务请求来恢复。输入匹配时会返回现有任务。除非打算创建新的逻辑任务,否则不要生成新的请求 ID。- get 调用在 HTTP 层失败。
RestClient.retrieve()会在收到非成功 HTTP 响应时抛出异常。这不等同于任务状态为failed。试试这样做: 在尝试读取成功响应体前,先处理 HTTP 错误。保留已知任务 ID 以便恢复,而不是创建另一个任务。对于无效输入,先查明问题,再决定是否原样重试。
- 任务响应的状态为
failed。 读取其
errorCode和error;site-unreachable是可能出现的任务错误码之一。试试这样做: 停止轮询该任务,并在开始另一个任务前解决所报告的原因。求解失败会退款;不要认为失败就意味着余额不足。
- 复用请求 ID 时发生冲突。
将新的任务输入与该请求 ID 对应的已保存输入进行比较。
试试这样做: 使用原始输入恢复现有任务。如果有意使用不同的任务输入,请使用新的请求 ID。
有关错误解释,请参阅 CAPTCHA API 错误。
运行您的第一个任务。
使用 $5 工作区额度开始体验。