diff --git a/par-core/src/main/java/com/par/core/adapter/GroovyScriptEngine.java b/par-core/src/main/java/com/par/core/adapter/GroovyScriptEngine.java index 30168c0..d40d556 100644 --- a/par-core/src/main/java/com/par/core/adapter/GroovyScriptEngine.java +++ b/par-core/src/main/java/com/par/core/adapter/GroovyScriptEngine.java @@ -22,6 +22,10 @@ import java.util.concurrent.ConcurrentHashMap; public class GroovyScriptEngine { private static final ObjectMapper objectMapper = new ObjectMapper(); + private static final java.net.http.HttpClient httpClient = java.net.http.HttpClient.newBuilder() + .connectTimeout(java.time.Duration.ofSeconds(10)) + .followRedirects(java.net.http.HttpClient.Redirect.NORMAL) + .build(); private static final GroovyClassLoader classLoader = new GroovyClassLoader( Thread.currentThread().getContextClassLoader()); /** 类型 → 默认脚本 */ @@ -62,6 +66,14 @@ public class GroovyScriptEngine { @SuppressWarnings("unchecked") public Map execute(String groovyScript, String content, String url) { + return execute(groovyScript, content, url, null); + } + + /** + * 执行带认证上下文的脚本(用于 PAR 端发起真实 HTTP 请求测试) + */ + @SuppressWarnings("unchecked") + public Map execute(String groovyScript, String content, String url, Map siteCtx) { Map result = new LinkedHashMap<>(); result.put("engine", "groovy"); try { @@ -71,6 +83,8 @@ public class GroovyScriptEngine { binding.setVariable("jsoup", new JsoupHelper()); binding.setVariable("json", objectMapper); binding.setVariable("utils", new ScriptUtils()); + binding.setVariable("http", new HttpHelper(httpClient)); + if (siteCtx != null) binding.setVariable("_ctx", siteCtx); GroovyShell shell = new GroovyShell(classLoader, binding); String fullScript = SCRIPT_PREFIX + groovyScript + "\n// PAR 调用入口\nreturn parse(content, url)"; @@ -175,6 +189,48 @@ public class GroovyScriptEngine { public Object parse(String html, String baseUri) { return Jsoup.parse(html, baseUri); } } + /** + * HTTP 请求工具(注入为 http),对齐 mediabo 的 httpGet/httpPost。 + * 提供 http.get(url, cookie) 和 http.post(url, body, cookie) 两个方法。 + */ + public static class HttpHelper { + private final java.net.http.HttpClient client; + public HttpHelper(java.net.http.HttpClient client) { this.client = client; } + + /** HTTP GET,返回响应体字符串 */ + public String get(String url, String cookie) { + try { + var builder = java.net.http.HttpRequest.newBuilder() + .uri(java.net.URI.create(url)) + .timeout(java.time.Duration.ofSeconds(15)) + .header("User-Agent", "PAR/1.0") + .GET(); + if (cookie != null && !cookie.isBlank()) builder.header("Cookie", cookie); + var resp = client.send(builder.build(), java.net.http.HttpResponse.BodyHandlers.ofString()); + return resp.body(); + } catch (Exception e) { throw new RuntimeException("HTTP GET failed: " + url, e); } + } + + /** HTTP POST JSON,返回响应体字符串 */ + public String post(String url, String body, String auth) { + try { + var builder = java.net.http.HttpRequest.newBuilder() + .uri(java.net.URI.create(url)) + .timeout(java.time.Duration.ofSeconds(15)) + .header("User-Agent", "PAR/1.0") + .header("Content-Type", "application/json"); + if (auth != null && !auth.isBlank()) { + if (auth.startsWith("Bearer") || auth.startsWith("x-api-key")) + builder.header("Authorization", auth); + else builder.header("Cookie", auth); + } + builder.POST(java.net.http.HttpRequest.BodyPublishers.ofString(body != null ? body : "")); + var resp = client.send(builder.build(), java.net.http.HttpResponse.BodyHandlers.ofString()); + return resp.body(); + } catch (Exception e) { throw new RuntimeException("HTTP POST failed: " + url, e); } + } + } + public static class ScriptUtils { public String text(Object node) { try { return (String) node.getClass().getMethod("text").invoke(node); }