执行groovy脚本

1.springboot项目和groovy脚本配合
备注:groovy脚本需要继承springboot项目的基类,并实现固定的方法
2.基类AbstractInspectScript

package com.xxx.inspect.plugin;

import com.alibaba.fastjson.JSON;

import com.xxx.inspect.domain.InspectObject;
import com.xxx.inspect.domain.InspectResult;
import lombok.extern.slf4j.Slf4j;


import java.util.List;
import java.util.Map;


@Slf4j
public abstract class AbstractInspectScript {
    public abstract List<InspectResult> inspect(List<InspectObject> objects, String threshold, Map param);

    protected void log(String msg){
        log.info(msg);
    }

    protected String object2string(Object object){
        return JSON.toJSONString(object);
    }

    protected boolean empty(String str){
        return str == null || str.isEmpty();
    }

}

3.巡检插件

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.apache.http.client.methods.HttpRequestBase;
import com.xxx.inspect.domain.InspectResult;
import com.xxx.inspect.domain.InspectObject;
import com.xxx.inspect.plugin.AbstractInspectScript;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSON;
import java.time.LocalDateTime;

public class SimpleInspectScript extends AbstractInspectScript {

    @Override
    public List<InspectResult> inspect(List<InspectObject> objects, String threshold, Map param) {
        List<InspectResult> result = new ArrayList();
        for (obj in objects) {
		result.add(new InspectResult(obj.object,true,"概览","详情",true));
	}
        return result;
    }
}

4.调度过程

package com.xxx.inspect.executor;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.xxx.inspect.domain.InspectObject;
import com.xxx.inspect.domain.InspectResult;
import com.xxx.inspect.domain.ScriptExecutionResult;
import com.xxx.inspect.plugin.AbstractInspectScript;
import groovy.lang.GroovyClassLoader;
import lombok.extern.slf4j.Slf4j;
import org.codehaus.groovy.control.CompilationFailedException;
import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;

@Slf4j
@Component
public class GroovyScriptExecutor {
    private final GroovyClassLoader groovyClassLoader = new GroovyClassLoader(this.getClass().getClassLoader());

    private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
    
    /**
     * 已经分好分片,扫描对象不能超200
     * @param scriptContent
     * @param objects
     * @param threshold
     * @return
     */
    public ScriptExecutionResult executeScript(String scriptContent, List<String> objects, String threshold) {
        ScriptExecutionResult result = new ScriptExecutionResult();
        List<InspectResult> inspectResults = new ArrayList<>();

        if (StringUtils.isBlank(scriptContent)) {
            result.setSuccess(false);
            result.setError("巡检脚本内容为空");
            return result;
        }

        if (objects.size() > 200) {
            result.setSuccess(false);
            result.setError("对象数量不能超过200");
            return result;
        }

        // 用于捕获脚本输出的流
        ByteArrayOutputStream logStream = new ByteArrayOutputStream();
        PrintStream originalOut = System.out;
        PrintStream originalErr = System.err;
        PrintStream logPrintStream = new PrintStream(logStream);

        Future<List<InspectResult>> executionFuture = null;

        try {
            // 重定向标准输出和错误输出以捕获脚本日志
            System.setOut(logPrintStream);
            System.setErr(logPrintStream);
            // groovy脚本依赖的jar包存储到oss,并数据库中维护到脚本和jar包的关系

            // TODO: 将groovy脚本依赖的jar包下载下来放到本地临时目录中,取到其URI 地址
            URL[] urls = null;
            URLClassLoader scriptClassLoader = new URLClassLoader(urls, parentClassLoader);
            GroovyClassLoader groovyClassLoader = new GroovyClassLoader(scriptClassLoader);


            Class<?> scriptClass = groovyClassLoader.parseClass(scriptContent);
            AbstractInspectScript plugin = (AbstractInspectScript) scriptClass.getDeclaredConstructor().newInstance();
            List<InspectObject> inspectionObjects = objects.stream()
                    .map(object -> new InspectObject(object))
                    .collect(Collectors.toList());
            // 设置上下文类加载器,为了解决groovy脚本中使用外部springboot项目依赖的效果
            Thread.currentThread().setContextClassLoader(groovyClassLoader);

            // 带超时控制的脚本执行
            executionFuture = executorService.submit(() ->
                    plugin.inspect(inspectionObjects, threshold, null)
            );

            // 设置执行超时(10秒)
            inspectResults = executionFuture.get(20, TimeUnit.MINUTES);

            result.setSuccess(true);
            result.setResults(inspectResults);
        } catch (CompilationFailedException e) {
            // 脚本编译错误(语法错误)
            result.setSuccess(false);
            result.setError("脚本编译失败: " + e.getMessage());
            log.error("脚本编译错误", e);
        } catch (TimeoutException e) {
            // 执行超时
            result.setSuccess(false);
            result.setError("脚本执行超时(超过20分钟)");
            log.error("脚本执行超时", e);
            if (executionFuture != null) {
                executionFuture.cancel(true);
            }
        } catch (Exception e) {
            // 其他运行时错误
            result.setSuccess(false);
            result.setError("脚本执行失败: " + e.getMessage());
            log.error("脚本执行错误", e);
        } finally {
            // 恢复标准输出
            System.setOut(originalOut);
            System.setErr(originalErr);
            // 保存捕获的日志
            result.setLogs(logStream.toString());
        }

        return result;
    }
}

List objects巡检对象是按,分隔的字符串进行split转换而来

posted @ 2025-06-17 15:49  SpecialSpeculator  阅读(28)  评论(0)    收藏  举报