From 83f6a729c27342c5f29ca1ec67a950a265f93c56 Mon Sep 17 00:00:00 2001 From: mediabot-pt <295750538+mediabot-pt@users.noreply.github.com> Date: Wed, 1 Jul 2026 10:35:33 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20GroovyClassLoader=20=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E5=8D=95=E4=BE=8B=EF=BC=8C=E9=81=BF=E5=85=8D=E6=AF=8F=E6=AC=A1?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E5=88=9B=E5=BB=BA=E6=96=B0=20ClassLoader?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../par/core/adapter/GroovyScriptEngine.java | 106 +++++++++--------- 1 file changed, 53 insertions(+), 53 deletions(-) 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 f6603db..a8fef11 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 @@ -2,6 +2,7 @@ package com.par.core.adapter; import com.fasterxml.jackson.databind.ObjectMapper; import groovy.lang.Binding; +import groovy.lang.GroovyClassLoader; import groovy.lang.GroovyShell; import lombok.extern.slf4j.Slf4j; import org.jsoup.Jsoup; @@ -11,19 +12,16 @@ import java.util.*; /** * Groovy 脚本解析引擎。 - * 脚本可访问以下内置变量: - * content - String,页面内容(HTML 或 JSON) - * url - String,访问的 URL - * jsoup - 可直接调用 Jsoup.parse() 等 - * json - ObjectMapper,可直接调用 readTree() 等 - * utils - 工具方法集合 - * 脚本应返回 Map 或 List> + * 共享 GroovyClassLoader 避免频繁创建 ClassLoader,每次执行复用同一个。 + * 脚本可访问内置变量:content, url, jsoup, json, utils */ @Slf4j @Service public class GroovyScriptEngine { private static final ObjectMapper objectMapper = new ObjectMapper(); + /** 全局单例 ClassLoader,避免每次 new GroovyShell 产生新 ClassLoader */ + private static final GroovyClassLoader classLoader = new GroovyClassLoader(); @SuppressWarnings("unchecked") public Map execute(String groovyScript, String content, String url) { @@ -37,61 +35,67 @@ public class GroovyScriptEngine { binding.setVariable("json", objectMapper); binding.setVariable("utils", new ScriptUtils()); - GroovyShell shell = new GroovyShell(binding); + GroovyShell shell = new GroovyShell(classLoader, binding); Object scriptResult = shell.evaluate(groovyScript); - if (scriptResult instanceof Map) { - result.put("matched", true); - Map m = (Map) scriptResult; - result.put("pageName", m.getOrDefault("pageName", "Groovy脚本")); - result.put("fieldCount", m.containsKey("fields") ? ((Map) m.get("fields")).size() : 0); - // 有 fields map → 展示为一行 - if (m.containsKey("fields")) { - result.put("extracted", List.of(m.get("fields"))); - result.put("rowCount", 1); - } else if (m.containsKey("extracted")) { - result.put("extracted", m.get("extracted")); - result.put("rowCount", m.getOrDefault("rowCount", ((List) m.get("extracted")).size())); - } else { - result.put("extracted", List.of(m)); - result.put("rowCount", 1); - } - result.put("message", m.getOrDefault("message", "解析成功")); - } else if (scriptResult instanceof List) { - result.put("matched", true); - result.put("pageName", "Groovy脚本"); - result.put("extracted", (List) scriptResult); - result.put("rowCount", ((List) scriptResult).size()); - result.put("fieldCount", !((List) scriptResult).isEmpty() && ((List) scriptResult).get(0) instanceof Map - ? ((Map) ((List) scriptResult).get(0)).size() : 0); - result.put("message", "共 " + ((List) scriptResult).size() + " 行"); - } else if (scriptResult != null) { - result.put("matched", true); - result.put("pageName", "Groovy脚本"); - result.put("extracted", List.of(Map.of("result", scriptResult))); - result.put("rowCount", 1); - result.put("message", String.valueOf(scriptResult)); - } else { - result.put("matched", true); - result.put("message", "脚本返回 null"); - result.put("extracted", List.of()); - result.put("rowCount", 0); - } + return buildResult(result, scriptResult); } catch (Exception e) { result.put("matched", false); result.put("message", "Groovy 脚本执行异常: " + e.getMessage()); - log.error("Groovy script error for url={}", url, e); + log.error("Groovy error for url={}", url, e); + } + return result; + } + + private Map buildResult(Map result, Object scriptResult) { + if (scriptResult instanceof Map) { + result.put("matched", true); + Map m = (Map) scriptResult; + result.put("pageName", m.getOrDefault("pageName", "Groovy脚本")); + if (m.containsKey("fields")) { + result.put("extracted", List.of(m.get("fields"))); + result.put("rowCount", 1); + result.put("fieldCount", ((Map) m.get("fields")).size()); + } else if (m.containsKey("extracted")) { + List ex = (List) m.get("extracted"); + result.put("extracted", ex); + result.put("rowCount", ex.size()); + result.put("fieldCount", !ex.isEmpty() && ex.get(0) instanceof Map + ? ((Map) ex.get(0)).size() : 0); + } else { + result.put("extracted", List.of(m)); + result.put("rowCount", 1); + result.put("fieldCount", m.size()); + } + result.put("message", m.getOrDefault("message", "解析成功")); + } else if (scriptResult instanceof List) { + result.put("matched", true); + result.put("pageName", "Groovy脚本"); + List list = (List) scriptResult; + result.put("extracted", list); + result.put("rowCount", list.size()); + result.put("fieldCount", !list.isEmpty() && list.get(0) instanceof Map + ? ((Map) list.get(0)).size() : 0); + result.put("message", "共 " + list.size() + " 行"); + } else if (scriptResult != null) { + result.put("matched", true); + result.put("pageName", "Groovy脚本"); + result.put("extracted", List.of(Map.of("result", scriptResult))); + result.put("rowCount", 1); + } else { + result.put("matched", true); + result.put("extracted", List.of()); + result.put("rowCount", 0); + result.put("message", "脚本返回 null"); } return result; } - /** Jsoup 辅助类 */ public static class JsoupHelper { public Object parse(String html) { return Jsoup.parse(html); } public Object parse(String html, String baseUri) { return Jsoup.parse(html, baseUri); } } - /** 脚本工具方法 */ public static class ScriptUtils { public String text(Object node) { try { return (String) node.getClass().getMethod("text").invoke(node); } @@ -101,13 +105,9 @@ public class GroovyScriptEngine { try { return (String) node.getClass().getMethod("attr", String.class).invoke(node, attr); } catch (Exception e) { return null; } } - public Object select(Object node, String cssQuery) { - try { return node.getClass().getMethod("select", String.class).invoke(node, cssQuery); } - catch (Exception e) { return null; } - } public Object selectFirst(Object node, String cssQuery) { try { - Object elements = select(node, cssQuery); + Object elements = node.getClass().getMethod("select", String.class).invoke(node, cssQuery); if (elements instanceof List && !((List) elements).isEmpty()) return ((List) elements).get(0); return null;