服务工具平台-代码执行器原创
金蝶云社区-lzpeng723
lzpeng723
5人赞赏了该文章 1492次浏览 未经作者许可,禁止转载编辑于2022年01月31日 15:16:44

服务工具平台-代码执行器

相关链接: 服务工具平台

简介

代码执行器提供线上动态运行代码的功能,支持Java代码、JS代码(包括RhinoNashornKScript)的动态运行。

原理

原理介绍—代码执行器(Java)

  • 先在 IDE 中编写 Java 源码,源码中可以引用任意线上 JVM 里已经加载的类。

  • 通过 JavaCompiler 对源码进行编译,并将当前线程上下文类加载器设置为编译时的父类加载器

  • 编译完成后会得到一个自定义类加载器,从其中得到 Class 类,并实例化。

  • 通过反射调用实例的方法。

  • 将运行结果返回给前端和苍穹系统

原理介绍—代码执行器(Js)

  • 先编写 js 脚本(Rhino, Nashorn,KScript),脚本中可以引用任意线上已部署的类。

  • 将 java 对象注入到脚本中。

  • 调用 js 引擎执行脚本。

  • 将运行结果返回给前端和苍穹系统。

实现

具体实现—代码执行器(Java)

package kd.lzp.servicetools.task;
import cn.hutool.core.compiler.CompilerUtil;
import kd.bos.context.RequestContext;
import kd.bos.exception.ErrorCode;
import kd.bos.exception.KDException;
import java.util.Map;
 /**
 * @author lzpeng
 * @version 1.0
 * @description Java代码执行器任务
 * @since 2021-05-02 11:28
 */
public class JavaCodeExecuteTask extends AbstractCodeExecuteTask {
     @Override
    public void execute(RequestContext reqCtx, Map<String, Object> map) throws KDException {
        String codeName = (String) map.get("codeName");
        Map<String, Object> result = getReturnMap();
        try {
            feedbackCustomdata(result);
            feedbackProgress(5, "开始编译" + codeName, result);
            String code = (String) map.get("code");
            ClassLoader classLoader = CompilerUtil.getCompiler(null)
                    .addSource("DynamicTask", code)
                    .compile();
            feedbackProgress(5, "开始执行" + codeName, result);
            Class<?> clazz = classLoader.loadClass("DynamicTask");
            AbstractCodeExecuteTask task = (AbstractCodeExecuteTask) clazz.newInstance();
            task.setTaskId(this.taskId);
            task.setMessageHandle(this.getMessageHandler());
            task.setReturnMap(result);
            task.execute(reqCtx, map);
            feedbackProgress(99, codeName + "执行完毕,开始输出结果", result);
            // 任务执行完毕,生成执行结果输出
            result.putIfAbsent(MSG_TYPE, "success");
            result.putIfAbsent(RETURN_VALUE, codeName + "源码执行成功");
        } catch (KDException e) {
            returnExceptionToView(e);
            throw e;
        } catch (Exception e) {
            returnExceptionToView(e);
            throw new KDException(e, new ErrorCode("500001", "执行" + codeName + "异常"));
        } finally {
            // 输出定制结果
            feedbackCustomdata(result);
            saveCodeExecuteRecord(reqCtx, map);
        }
     }
}

具体实现—代码执行器(Js)

Rhino

package kd.lzp.servicetools.task;
import cn.hutool.extra.expression.ExpressionEngine;
import cn.hutool.extra.expression.engine.rhino.RhinoEngine;
import kd.bos.context.RequestContext;
import kd.bos.exception.ErrorCode;
import kd.bos.exception.KDException;
import java.util.Map;
 /**
 * @author lzpeng
 * @version 1.0
 * @description Rhino脚本执行器任务
 * @since 2021-05-02 15:22
 */
public class RhinoCodeExecuteTask extends AbstractCodeExecuteTask {
     @Override
    public void execute(RequestContext reqCtx, Map<String, Object> map) throws KDException {
        String codeName = (String) map.get("codeName");
        Map<String, Object> result = getReturnMap();
        try {
            feedbackCustomdata(result);
            String code = (String) map.get("code");
            map.put("task", this);
            map.put("ctx", reqCtx);
                         feedbackProgress(5, "开始执行" + codeName, result);
            ExpressionEngine engine = new RhinoEngine();
            Object returnValue = engine.eval(code, map);
            feedbackProgress(99, codeName + "执行完毕,开始输出结果", result);
            // 任务执行完毕,生成执行结果输出
            result.putIfAbsent(MSG_TYPE, "success");
            result.putIfAbsent(RETURN_VALUE, returnValue);
        } catch (KDException e) {
            returnExceptionToView(e);
            throw e;
        } catch (Exception e) {
            returnExceptionToView(e);
            throw new KDException(e, new ErrorCode("500001", "执行" + codeName + "异常"));
        } finally {
            // 输出定制结果
            feedbackCustomdata(result);
            saveCodeExecuteRecord(reqCtx, map);
        }
     }
}


Nashorn

package kd.lzp.servicetools.task;
import kd.bos.context.RequestContext;
import kd.bos.exception.ErrorCode;
import kd.bos.exception.KDException;
import kd.lzp.servicetools.util.NashornUtils;
import java.util.Map;
 /**
 * @author lzpeng
 * @version 1.0
 * @description Nashorn脚本执行器任务
 * @since 2021-05-02 17:41
 */
public class NashornCodeExecuteTask extends AbstractCodeExecuteTask {
     @Override
    public void execute(RequestContext reqCtx, Map<String, Object> map) throws KDException {
        String codeName = (String) map.get("codeName");
        Map<String, Object> result = getReturnMap();
        try {
            feedbackCustomdata(result);
            String code = (String) map.get("code");
            feedbackProgress(5, "开始执行" + codeName, result);
            map.put("task", this);
            map.put("ctx", reqCtx);
                         Object returnValue = NashornUtils.execute(code, map);
            feedbackProgress(99, codeName + "执行完毕,开始输出结果", result);
            // 任务执行完毕,生成执行结果输出
            result.putIfAbsent(MSG_TYPE, "success");
            result.putIfAbsent(RETURN_VALUE, returnValue);
        } catch (KDException e) {
            returnExceptionToView(e);
            throw e;
        } catch (Exception e) {
            returnExceptionToView(e);
            throw new KDException(e, new ErrorCode("500001", "执行" + codeName + "异常"));
        } finally {
            // 输出定制结果
            feedbackCustomdata(result);
            saveCodeExecuteRecord(reqCtx, map);
        }
    }
 }

KScript

package kd.lzp.servicetools.task;
import kd.bos.context.RequestContext;
import kd.bos.exception.ErrorCode;
import kd.bos.exception.KDException;
import kd.bos.form.operate.formop.RunScript;
import kd.bos.script.ScriptExecutor;
import java.util.Map;
 /**
 * @author lzpeng
 * @version 1.0
 * @description KScript脚本执行器任务
 * @see RunScript
 * @since 2021-05-02 17:41
 */
public class KScriptCodeExecuteTask extends AbstractCodeExecuteTask {
     @Override
    public void execute(RequestContext reqCtx, Map<String, Object> map) throws KDException {
        String codeName = (String) map.get("codeName");
        Map<String, Object> result = getReturnMap();
        try {
            feedbackCustomdata(result);
            String code = (String) map.get("code");
            feedbackProgress(5, "开始执行" + codeName, result);
            map.put("task", this);
            map.put("ctx", reqCtx);
            Object returnValue = execScript(code, map);
            feedbackProgress(99, codeName + "执行完毕,开始输出结果", result);
            // 任务执行完毕,生成执行结果输出
            result.putIfAbsent(MSG_TYPE, "success");
            result.putIfAbsent(RETURN_VALUE, returnValue);
        } catch (KDException e) {
            returnExceptionToView(e);
            throw e;
        } catch (Exception e) {
            returnExceptionToView(e);
            throw new KDException(e, new ErrorCode("500001", "执行" + codeName + "异常"));
        } finally {
            // 输出定制结果
            feedbackCustomdata(result);
            saveCodeExecuteRecord(reqCtx, map);
        }
    }
      private Object execScript(String script, Map<String, Object> map) {
        ScriptExecutor scriptExecutor = ScriptExecutor.create();
        scriptExecutor.init(ctx -> map.forEach(ctx::set));
        scriptExecutor.begin();
        try {
            return scriptExecutor.exec(script);
        } finally {
            scriptExecutor.end();
        }
    }
}

使用方法

代码执行器(Java)

  1. 先编写待执行的代码,需要继承 kd.lzp.servicetools.task.AbstractCodeExecuteTask 类,重写 execute 方法,例如:

  2. import kd.bos.context.RequestContext;
    import kd.bos.exception.KDException;
    import kd.bos.instance.Instance;
    import kd.lzp.servicetools.task.AbstractCodeExecuteTask;
    import java.util.Map;
    import java.util.concurrent.TimeUnit;
     /**
     * 请先选择执行应用和代码类型,然后输入需要运行的代码,然后点击执行
     * 如选择人力资源云->假勤管理应用,将会在mservice-hr上执行代码
     * 通过returnDataToView将数据返回页面,会通过当前表单的showMessage展示出来
     * 通过returnDataToView将异常抛到页面,会通过当前表单的showErrMessage展示出来
     */
    public final class DynamicTask extends AbstractCodeExecuteTask {
         @Override
        public void execute(RequestContext ctx, Map<String, Object> map) throws KDException {
            try {
                for (int i = 0; i < 10; i++) {
                    feedbackProgress(i * 10, "已执行 " + (i * 10) + "% 。");
                    TimeUnit.SECONDS.sleep(1);
                }
                // 将数据返回页面
                returnDataToView(ctx.getUserName() + "@" + Instance.getAppName());
            } catch (Exception e) {
                // 将异常抛到页面
                returnExceptionToView(e);
            }
        }
         // TODO 添加新的方法
     }
  3. 打开【公共服务云】->【服务工具平台】->【代码执行器】菜单,选择执行应用,比如假勤管理,代码类型选择Java源码,复制粘贴上一步编辑好的代码到代码编辑器中,点击执行。

    image2021-11-15_10-36-33.png

  4. 等待执行结束

    image2021-11-15_10-41-24.png

  5. 如果代码出现编译错误或者允许错误均会将错误信息返回给前台

    image2021-11-15_10-48-4.png

代码执行器(Js)

RhinoNashornKScript的语法是一致的,这里只举一个例子。

  1. 先编写待执行的代码,例如:

  2. /**
     * 请先选择执行应用和代码类型,然后输入需要运行的代码,然后点击执行
     * 如选择人力资源云->假勤管理应用,将会在mservice-hr上执行代码
     * 通过returnDataToView将数据返回页面,会通过当前表单的showMessage展示出来
     * 通过returnDataToView将异常抛到页面,会通过当前表单的showErrMessage展示出来
     */
    var imp = JavaImporter(
        Packages.kd.bos.context,
        Packages.kd.bos.instance,
        Packages.java.util.concurrent
    );
    with (imp) {
        try {
            for (var i = 0; i < 10; i++) {
                task.feedbackProgress(i * 10, "已执行 " + (i * 10) + "% 。");
                TimeUnit.SECONDS.sleep(1);
            }
            // 将数据返回页面
            task.returnDataToView(ctx.getUserName() + "@" + Instance.getAppName());
        } catch (e) {
            // 将异常抛到页面
            task.returnExceptionToView(e);
        }
    }
  3. 打开【公共服务云】->【服务工具平台】->【代码执行器】菜单,选择执行应用,比如假勤管理,代码类型选择Rhino脚本,复制粘贴上一步编辑好的代码到代码编辑器中,点击执行。

    1.png

  4. 等待执行结束

    2.png

  5. 如果代码出现编译错误或者允许错误均会将错误信息返回给前台

    4.png

统计信息

打开【公共服务云】->【服务工具平台】->【代码执行记录】菜单,可以查看代码执行记录,便于问题追踪、保留执行历史。

3.png


赞 5