服务工具平台-代码执行器
相关链接: 服务工具平台
简介
代码执行器提供线上动态运行代码的功能,支持Java代码、JS代码(包括Rhino、Nashorn、KScript)的动态运行。
原理
原理介绍—代码执行器(Java)
先在 IDE 中编写 Java 源码,源码中可以引用任意线上 JVM 里已经加载的类。
通过 JavaCompiler 对源码进行编译,并将当前线程上下文类加载器设置为编译时的父类加载器
编译完成后会得到一个自定义类加载器,从其中得到 Class 类,并实例化。
通过反射调用实例的方法。
将运行结果返回给前端和苍穹系统
原理介绍—代码执行器(Js)
将 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)
先编写待执行的代码,需要继承 kd.lzp.servicetools.task.AbstractCodeExecuteTask 类,重写 execute 方法,例如:
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 添加新的方法 }
打开【公共服务云】->【服务工具平台】->【代码执行器】菜单,选择执行应用,比如假勤管理,代码类型选择Java源码,复制粘贴上一步编辑好的代码到代码编辑器中,点击执行。
等待执行结束
如果代码出现编译错误或者允许错误均会将错误信息返回给前台
代码执行器(Js)
因Rhino、Nashorn、KScript的语法是一致的,这里只举一个例子。
先编写待执行的代码,例如:
/** * 请先选择执行应用和代码类型,然后输入需要运行的代码,然后点击执行 * 如选择人力资源云->假勤管理应用,将会在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); } }
打开【公共服务云】->【服务工具平台】->【代码执行器】菜单,选择执行应用,比如假勤管理,代码类型选择Rhino脚本,复制粘贴上一步编辑好的代码到代码编辑器中,点击执行。
等待执行结束
如果代码出现编译错误或者允许错误均会将错误信息返回给前台
统计信息
打开【公共服务云】->【服务工具平台】->【代码执行记录】菜单,可以查看代码执行记录,便于问题追踪、保留执行历史。
推荐阅读