Forráskód Böngészése

支持定时任务

mxd 3 éve
szülő
commit
81930ab61c

+ 14 - 0
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/MagicAPIAutoConfiguration.java

@@ -22,6 +22,7 @@ import org.springframework.http.MediaType;
 import org.springframework.http.converter.HttpMessageConverter;
 import org.springframework.http.converter.StringHttpMessageConverter;
 import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.TaskScheduler;
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.client.RestTemplate;
@@ -388,6 +389,18 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 		return new DataSourceMagicDynamicRegistry(dataSourceInfoMagicResourceStorage, magicDynamicDataSource);
 	}
 
+	@Bean
+	@ConditionalOnMissingBean
+	public TaskInfoMagicResourceStorage taskInfoMagicResourceStorage() {
+		return new TaskInfoMagicResourceStorage();
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	public TaskMagicDynamicRegistry taskMagicDynamicRegistry(TaskInfoMagicResourceStorage taskInfoMagicResourceStorage, TaskScheduler taskScheduler) {
+		return new TaskMagicDynamicRegistry(taskInfoMagicResourceStorage, taskScheduler);
+	}
+
 
 	@Bean
 	@ConditionalOnMissingBean(MagicNotifyService.class)
@@ -572,6 +585,7 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 			mapping.registerController(magicWorkbenchController)
 					.registerController(new MagicResourceController(configuration))
 					.registerController(new MagicDataSourceController(configuration))
+					.registerController(new MagicTaskController(configuration))
 					.registerController(new MagicBackupController(configuration));
 		}
 		// 注册接收推送的接口

+ 30 - 0
magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicTaskController.java

@@ -0,0 +1,30 @@
+package org.ssssssss.magicapi.controller;
+
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.ssssssss.magicapi.config.MagicConfiguration;
+import org.ssssssss.magicapi.model.DebugRequest;
+import org.ssssssss.magicapi.model.JsonBean;
+import org.ssssssss.magicapi.model.MagicEntity;
+import org.ssssssss.magicapi.script.ScriptManager;
+import org.ssssssss.script.MagicScriptDebugContext;
+
+import javax.servlet.http.HttpServletRequest;
+
+public class MagicTaskController extends MagicController implements MagicExceptionHandler{
+
+	public MagicTaskController(MagicConfiguration configuration) {
+		super(configuration);
+	}
+
+	@PostMapping("/task/execute")
+	@ResponseBody
+	public JsonBean<Object> execute(String id, HttpServletRequest request){
+		MagicEntity entity = MagicConfiguration.getMagicResourceService().file(id);
+		notNull(entity, FILE_NOT_FOUND);
+		String script = entity.getScript();
+		DebugRequest debugRequest = DebugRequest.create(request);
+		MagicScriptDebugContext magicScriptContext = debugRequest.createMagicScriptContext(configuration.getDebugTimeout());
+		return new JsonBean<>(ScriptManager.executeScript(script, magicScriptContext));
+	}
+}

+ 7 - 17
magic-api/src/main/java/org/ssssssss/magicapi/controller/RequestHandler.java

@@ -168,7 +168,8 @@ public class RequestHandler extends MagicController {
 			return afterCompletion(requestEntity, value);
 		}
 		if (requestedFromTest) {
-			String sessionAndScriptId = requestEntity.getRequestedClientId() + requestEntity.getRequestedScriptId();
+			DebugRequest debugRequest = requestEntity.getDebugRequest();
+			String sessionAndScriptId = debugRequest.getRequestedClientId() + debugRequest.getRequestedScriptId();
 			try {
 				if (context instanceof MagicScriptDebugContext) {
 					WebSocketSessionManager.addMagicScriptContext(sessionAndScriptId, (MagicScriptDebugContext) context);
@@ -358,8 +359,8 @@ public class RequestHandler extends MagicController {
 		} while ((parent = parent.getCause()) != null);
 		if (se != null && requestEntity.isRequestedFromTest()) {
 			Span.Line line = se.getLine();
-			WebSocketSessionManager.sendByClientId(requestEntity.getRequestedClientId(), EXCEPTION, Arrays.asList(
-					requestEntity.getRequestedScriptId(),
+			WebSocketSessionManager.sendByClientId(requestEntity.getDebugRequest().getRequestedClientId(), EXCEPTION, Arrays.asList(
+					requestEntity.getDebugRequest().getRequestedScriptId(),
 					se.getSimpleMessage(),
 					line == null ? null : Arrays.asList(line.getLineNumber(), line.getEndLineNumber(), line.getStartCol(), line.getEndCol())
 			));
@@ -395,24 +396,13 @@ public class RequestHandler extends MagicController {
 	 * 构建 MagicScriptContext
 	 */
 	private MagicScriptContext createMagicScriptContext(String scriptName, RequestEntity requestEntity) {
-		List<Integer> breakpoints = requestEntity.getRequestedBreakpoints();
+		DebugRequest debugRequest = requestEntity.getDebugRequest();
+		List<Integer> breakpoints = debugRequest.getRequestedBreakpoints();
 		// 构建脚本上下文
 		MagicScriptContext context;
 		// TODO 安全校验
 		if (requestEntity.isRequestedFromDebug() && breakpoints.size() > 0) {
-			MagicScriptDebugContext debugContext = new MagicScriptDebugContext(requestEntity.getRequestedBreakpoints());
-			String scriptId = requestEntity.getRequestedScriptId();
-			String clientId = requestEntity.getRequestedClientId();
-			debugContext.setTimeout(configuration.getDebugTimeout());
-			debugContext.setId(scriptId);
-			debugContext.setCallback(variables -> {
-				List<Map<String, Object>> varList = (List<Map<String, Object>>) variables.get("variables");
-				varList.stream().filter(it -> it.containsKey("value")).forEach(variable -> {
-					variable.put("value", JsonUtils.toJsonStringWithoutLog(variable.get("value")));
-				});
-				WebSocketSessionManager.sendByClientId(clientId, BREAKPOINT, scriptId, variables);
-			});
-			context = debugContext;
+			context = debugRequest.createMagicScriptContext(configuration.getDebugTimeout());
 		} else {
 			context = new MagicScriptContext();
 		}

+ 73 - 0
magic-api/src/main/java/org/ssssssss/magicapi/model/DebugRequest.java

@@ -0,0 +1,73 @@
+package org.ssssssss.magicapi.model;
+
+import org.ssssssss.magicapi.config.WebSocketSessionManager;
+import org.ssssssss.magicapi.utils.JsonUtils;
+import org.ssssssss.script.MagicScriptDebugContext;
+import org.ssssssss.script.functions.ObjectConvertExtension;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.ssssssss.magicapi.config.MessageType.BREAKPOINT;
+import static org.ssssssss.magicapi.model.Constants.*;
+
+public class DebugRequest {
+
+	private HttpServletRequest request;
+
+	private DebugRequest(HttpServletRequest request) {
+		this.request = request;
+	}
+
+	public static DebugRequest create(HttpServletRequest request){
+		return new DebugRequest(request);
+	}
+
+	/**
+	 * 获得断点
+	 */
+	public List<Integer> getRequestedBreakpoints() {
+		String breakpoints = request.getHeader(HEADER_REQUEST_BREAKPOINTS);
+		if (breakpoints != null) {
+			return Arrays.stream(breakpoints.split(","))
+					.map(val -> ObjectConvertExtension.asInt(val, -1))
+					.filter(it -> it > 0)
+					.collect(Collectors.toList());
+		}
+		return Collections.emptyList();
+	}
+
+	/**
+	 * 获取测试scriptId
+	 */
+	public String getRequestedScriptId() {
+		return request.getHeader(HEADER_REQUEST_SCRIPT_ID);
+	}
+
+	/**
+	 * 获取测试clientId
+	 */
+	public String getRequestedClientId() {
+		return request.getHeader(HEADER_REQUEST_CLIENT_ID);
+	}
+
+	public MagicScriptDebugContext createMagicScriptContext(int debugTimeout){
+		MagicScriptDebugContext debugContext = new MagicScriptDebugContext(getRequestedBreakpoints());
+		String scriptId = getRequestedScriptId();
+		String clientId = getRequestedClientId();
+		debugContext.setTimeout(debugTimeout);
+		debugContext.setId(scriptId);
+		debugContext.setCallback(variables -> {
+			List<Map<String, Object>> varList = (List<Map<String, Object>>) variables.get("variables");
+			varList.stream().filter(it -> it.containsKey("value")).forEach(variable -> {
+				variable.put("value", JsonUtils.toJsonStringWithoutLog(variable.get("value")));
+			});
+			WebSocketSessionManager.sendByClientId(clientId, BREAKPOINT, scriptId, variables);
+		});
+		return debugContext;
+	}
+}

+ 2 - 0
magic-api/src/main/java/org/ssssssss/magicapi/model/JsonCodeConstants.java

@@ -55,6 +55,8 @@ public interface JsonCodeConstants {
 
 	JsonCode GROUP_ID_REQUIRED = new JsonCode(0, "请选择分组");
 
+	JsonCode CRON_ID_REQUIRED = new JsonCode(0, "cron表达式不能为空");
+
 	JsonCode NAME_INVALID = new JsonCode(0, "名称不能包含特殊字符,只允许中文、数字、字母以及+_-.()的组合且不能.开头");
 
 	JsonCode DATASOURCE_KEY_INVALID = new JsonCode(0, "数据源Key不能包含特殊字符,只允许中文、数字、字母以及_组合");

+ 7 - 32
magic-api/src/main/java/org/ssssssss/magicapi/model/RequestEntity.java

@@ -1,14 +1,11 @@
 package org.ssssssss.magicapi.model;
 
 import org.ssssssss.script.MagicScriptContext;
-import org.ssssssss.script.functions.ObjectConvertExtension;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import java.util.*;
-import java.util.stream.Collectors;
-
-import static org.ssssssss.magicapi.model.Constants.*;
+import java.util.Map;
+import java.util.UUID;
 
 /**
  * 请求信息
@@ -27,6 +24,7 @@ public class RequestEntity {
 	private Map<String, Object> pathVariables;
 	private MagicScriptContext magicScriptContext;
 	private Object requestBody;
+	private DebugRequest debugRequest;
 
 	private Map<String, Object> headers;
 
@@ -53,6 +51,7 @@ public class RequestEntity {
 
 	public RequestEntity request(HttpServletRequest request) {
 		this.request = request;
+		this.debugRequest = DebugRequest.create(request);
 		return this;
 	}
 
@@ -75,7 +74,7 @@ public class RequestEntity {
 	}
 
 	public boolean isRequestedFromDebug() {
-		return requestedFromTest && !getRequestedBreakpoints().isEmpty();
+		return requestedFromTest && !this.debugRequest.getRequestedBreakpoints().isEmpty();
 	}
 
 	public Map<String, Object> getParameters() {
@@ -134,31 +133,7 @@ public class RequestEntity {
 		return this;
 	}
 
-	/**
-	 * 获取测试scriptId
-	 */
-	public String getRequestedScriptId() {
-		return request.getHeader(HEADER_REQUEST_SCRIPT_ID);
-	}
-
-	/**
-	 * 获取测试clientId
-	 */
-	public String getRequestedClientId() {
-		return request.getHeader(HEADER_REQUEST_CLIENT_ID);
-	}
-
-	/**
-	 * 获得断点
-	 */
-	public List<Integer> getRequestedBreakpoints() {
-		String breakpoints = request.getHeader(HEADER_REQUEST_BREAKPOINTS);
-		if (breakpoints != null) {
-			return Arrays.stream(breakpoints.split(","))
-					.map(val -> ObjectConvertExtension.asInt(val, -1))
-					.filter(it -> it > 0)
-					.collect(Collectors.toList());
-		}
-		return Collections.emptyList();
+	public DebugRequest getDebugRequest() {
+		return debugRequest;
 	}
 }

+ 67 - 0
magic-api/src/main/java/org/ssssssss/magicapi/model/TaskInfo.java

@@ -0,0 +1,67 @@
+package org.ssssssss.magicapi.model;
+
+import java.util.Objects;
+
+public class TaskInfo extends PathMagicEntity{
+
+	/**
+	 *  cron 表达式
+	 */
+	private String cron;
+
+	/**
+	 * 是否启用
+	 */
+	private boolean enabled;
+
+
+	public String getCron() {
+		return cron;
+	}
+
+	public void setCron(String cron) {
+		this.cron = cron;
+	}
+
+	public boolean isEnabled() {
+		return enabled;
+	}
+
+	public void setEnabled(boolean enabled) {
+		this.enabled = enabled;
+	}
+
+	public TaskInfo copy() {
+		TaskInfo info = new TaskInfo();
+		super.copyTo(info);
+		info.setCron(this.cron);
+		info.setEnabled(this.enabled);
+		return info;
+	}
+
+	@Override
+	public MagicEntity simple() {
+		TaskInfo info = new TaskInfo();
+		super.simple(info);
+		return info;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (this == o) return true;
+		if (o == null || getClass() != o.getClass()) return false;
+		if (!super.equals(o)) return false;
+		TaskInfo taskInfo = (TaskInfo) o;
+		return Objects.equals(id, taskInfo.id) &&
+				Objects.equals(path, taskInfo.path) &&
+				Objects.equals(script, taskInfo.script) &&
+				Objects.equals(name, taskInfo.name) &&
+				Objects.equals(cron, taskInfo.cron) &&
+				Objects.equals(enabled, taskInfo.enabled);
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(id, path, script, name, groupId, cron, enabled);
+	}
+}

+ 1 - 3
magic-api/src/main/java/org/ssssssss/magicapi/provider/MagicResourceStorage.java

@@ -68,9 +68,7 @@ public interface MagicResourceStorage<T extends MagicEntity> {
 		return read(resource.read());
 	}
 
-	default String buildMappingKey(T entity) {
-		return null;
-	}
+	String buildMappingKey(T entity);
 
 	default String buildScriptName(T entity) {
 		return null;

+ 27 - 0
magic-api/src/main/java/org/ssssssss/magicapi/service/impl/TaskInfoMagicResourceStorage.java

@@ -0,0 +1,27 @@
+package org.ssssssss.magicapi.service.impl;
+
+import org.ssssssss.magicapi.model.ApiInfo;
+import org.ssssssss.magicapi.model.TaskInfo;
+
+public class TaskInfoMagicResourceStorage extends AbstractPathMagicResourceStorage<TaskInfo> {
+
+	@Override
+	public String folder() {
+		return "task";
+	}
+
+	@Override
+	public Class<TaskInfo> magicClass() {
+		return TaskInfo.class;
+	}
+
+	@Override
+	public void validate(TaskInfo entity) {
+		notBlank(entity.getCron(), CRON_ID_REQUIRED);
+	}
+
+	@Override
+	public String buildMappingKey(TaskInfo info) {
+		return buildMappingKey(info, magicResourceService.getGroupPath(info.getGroupId()));
+	}
+}

+ 71 - 0
magic-api/src/main/java/org/ssssssss/magicapi/service/impl/TaskMagicDynamicRegistry.java

@@ -0,0 +1,71 @@
+package org.ssssssss.magicapi.service.impl;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.config.CronTask;
+import org.ssssssss.magicapi.event.FileEvent;
+import org.ssssssss.magicapi.event.GroupEvent;
+import org.ssssssss.magicapi.model.TaskInfo;
+import org.ssssssss.magicapi.provider.MagicResourceStorage;
+import org.ssssssss.magicapi.script.ScriptManager;
+import org.ssssssss.magicapi.service.AbstractMagicDynamicRegistry;
+import org.ssssssss.script.MagicScriptContext;
+
+import java.util.concurrent.ScheduledFuture;
+
+public class TaskMagicDynamicRegistry extends AbstractMagicDynamicRegistry<TaskInfo> {
+
+	private final TaskScheduler taskScheduler;
+
+	private static final Logger logger = LoggerFactory.getLogger(TaskMagicDynamicRegistry.class);
+
+	public TaskMagicDynamicRegistry(MagicResourceStorage<TaskInfo> magicResourceStorage, TaskScheduler taskScheduler) {
+		super(magicResourceStorage);
+		this.taskScheduler = taskScheduler;
+	}
+
+	@EventListener(condition = "#event.type == 'task'")
+	public void onFileEvent(FileEvent event) {
+		processEvent(event);
+	}
+
+	@EventListener(condition = "#event.type == 'task'")
+	public void onGroupEvent(GroupEvent event) {
+		processEvent(event);
+	}
+
+	@Override
+	protected boolean register(MappingNode<TaskInfo> mappingNode) {
+		TaskInfo info = mappingNode.getEntity();
+		CronTask cronTask = new CronTask(() -> {
+			try {
+				ScriptManager.executeScript(info.getScript(), new MagicScriptContext());
+			} catch (Exception e) {
+				logger.error("定时任务执行出错", e);
+			}
+		}, info.getCron());
+		mappingNode.setMappingData(taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger()));
+		if(taskScheduler != null){
+			logger.debug("注册定时任务:[{}, {}, {}]", info.getName(), info.getPath(), info.getCron());
+		} else {
+			logger.debug("注册定时任务失败:[{}, {}, {}], 当前 TaskScheduler 为空", info.getName(), info.getPath(), info.getCron());
+		}
+
+		return true;
+	}
+
+	@Override
+	protected void unregister(MappingNode<TaskInfo> mappingNode) {
+		TaskInfo info = mappingNode.getEntity();
+		logger.debug("取消注册定时任务:[{}, {}, {}]", info.getName(), info.getPath(), info.getCron());
+		ScheduledFuture<?> scheduledFuture = (ScheduledFuture<?>) mappingNode.getMappingData();
+		if(scheduledFuture != null){
+			try {
+				scheduledFuture.cancel(true);
+			} catch (Exception ignored) {
+			}
+		}
+	}
+}

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
magic-editor/src/console/dist/assets/app.968aee6d.js


+ 1 - 1
magic-editor/src/console/dist/assets/index.dd6bc269.js → magic-editor/src/console/dist/assets/index.2cdad179.js

@@ -1 +1 @@
-import"./app.e9d5d532.js";import"./vue.7304e5c5.js";import"./vendor.5f04ef2d.js";import"./axios.23e7b955.js";const s=function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))i(e);new MutationObserver(e=>{for(const r of e)if(r.type==="childList")for(const o of r.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&i(o)}).observe(document,{childList:!0,subtree:!0});function n(e){const r={};return e.integrity&&(r.integrity=e.integrity),e.referrerpolicy&&(r.referrerPolicy=e.referrerpolicy),e.crossorigin==="use-credentials"?r.credentials="include":e.crossorigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function i(e){if(e.ep)return;e.ep=!0;const r=n(e);fetch(e.href,r)}};s();
+import"./app.968aee6d.js";import"./vue.7304e5c5.js";import"./vendor.5f04ef2d.js";import"./axios.23e7b955.js";const s=function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))i(e);new MutationObserver(e=>{for(const r of e)if(r.type==="childList")for(const o of r.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&i(o)}).observe(document,{childList:!0,subtree:!0});function n(e){const r={};return e.integrity&&(r.integrity=e.integrity),e.referrerpolicy&&(r.referrerPolicy=e.referrerpolicy),e.crossorigin==="use-credentials"?r.credentials="include":e.crossorigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function i(e){if(e.ep)return;e.ep=!0;const r=n(e);fetch(e.href,r)}};s();

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
magic-editor/src/console/dist/assets/style.079cdc93.css


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
magic-editor/src/console/dist/assets/style.a7af0077.css


+ 3 - 3
magic-editor/src/console/dist/index.html

@@ -23,12 +23,12 @@
 		@keyframes stretch {0% {transform: scale(1);}25% {transform: scale(1.2);}50% {transform: scale(1);}100% {transform: scale(1);}}
 		@keyframes blink-loading {0% {opacity: 1;}50% {opacity: 0.5;}100% {opacity: 1;}}
 	</style>
-  <script type="module" crossorigin src="./assets/index.dd6bc269.js"></script>
+  <script type="module" crossorigin src="./assets/index.2cdad179.js"></script>
   <link rel="modulepreload" href="./assets/vue.7304e5c5.js">
   <link rel="modulepreload" href="./assets/axios.23e7b955.js">
   <link rel="modulepreload" href="./assets/vendor.5f04ef2d.js">
-  <link rel="modulepreload" href="./assets/app.e9d5d532.js">
-  <link rel="stylesheet" href="./assets/style.079cdc93.css">
+  <link rel="modulepreload" href="./assets/app.968aee6d.js">
+  <link rel="stylesheet" href="./assets/style.a7af0077.css">
 </head>
 <body>
 	<div class="magic-loading-wrapper" id="magic-loading-wrapper">

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott