perf: GroovyClassLoader 全局单例,避免每次执行创建新 ClassLoader
This commit is contained in:
@@ -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<String, Object> 或 List<Map<String, Object>>
|
||||
* 共享 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<String, Object> 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<String, Object> m = (Map<String, Object>) 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<String, Object> buildResult(Map<String, Object> result, Object scriptResult) {
|
||||
if (scriptResult instanceof Map) {
|
||||
result.put("matched", true);
|
||||
Map<String, Object> m = (Map<String, Object>) 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;
|
||||
|
||||
Reference in New Issue
Block a user