Преглед изворни кода

`WebSocket`集群处理。

mxd пре 4 година
родитељ
комит
e417b5bef2

+ 3 - 1
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/ClusterConfig.java

@@ -1,5 +1,7 @@
 package org.ssssssss.magicapi.spring.boot.starter;
 
+import java.util.UUID;
+
 /**
  * 集群配置
  * @since 1.2.0
@@ -14,7 +16,7 @@ public class ClusterConfig {
 	/**
 	 * 实例ID,集群环境下,要保证每台机器不同。默认启动后随机生成uuid
 	 */
-	private String instanceId;
+	private String instanceId = UUID.randomUUID().toString();
 
 	/**
 	 * redis 通道

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

@@ -120,6 +120,8 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 	 */
 	private final ObjectProvider<List<MagicFunction>> magicFunctionsProvider;
 
+	private final ObjectProvider<MagicNotifyService> magicNotifyServiceProvider;
+
 	private final Environment environment;
 
 	private final MagicCorsFilter magicCorsFilter = new MagicCorsFilter();
@@ -145,6 +147,7 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 									 ObjectProvider<List<ColumnMapperProvider>> columnMapperProvidersProvider,
 									 ObjectProvider<List<MagicFunction>> magicFunctionsProvider,
 									 ObjectProvider<RestTemplate> restTemplateProvider,
+									 ObjectProvider<MagicNotifyService> magicNotifyServiceProvider,
 									 ObjectProvider<AuthorizationInterceptor> authorizationInterceptorProvider,
 									 Environment environment,
 									 ApplicationContext applicationContext
@@ -158,6 +161,7 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 		this.columnMapperProvidersProvider = columnMapperProvidersProvider;
 		this.magicFunctionsProvider = magicFunctionsProvider;
 		this.restTemplateProvider = restTemplateProvider;
+		this.magicNotifyServiceProvider = magicNotifyServiceProvider;
 		this.authorizationInterceptorProvider = authorizationInterceptorProvider;
 		this.environment = environment;
 		this.applicationContext = applicationContext;
@@ -352,9 +356,8 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 										   ResultProvider resultProvider,
 										   MagicDynamicDataSource magicDynamicDataSource,
 										   MagicFunctionManager magicFunctionManager,
-										   MagicNotifyService magicNotifyService,
 										   Resource workspace) {
-		return new DefaultMagicAPIService(mappingHandlerMapping, apiServiceProvider, functionServiceProvider, groupServiceProvider, resultProvider, magicDynamicDataSource, magicFunctionManager, magicNotifyService, properties.getClusterConfig().getInstanceId(), workspace, properties.isThrowException());
+		return new DefaultMagicAPIService(mappingHandlerMapping, apiServiceProvider, functionServiceProvider, groupServiceProvider, resultProvider, magicDynamicDataSource, magicFunctionManager, magicNotifyServiceProvider.getObject(), properties.getClusterConfig().getInstanceId(), workspace, properties.isThrowException());
 	}
 
 	private void setupSpringSecurity() {
@@ -584,9 +587,10 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 	public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
 		String web = properties.getWeb();
 		if (web != null) {
-			WebSocketHandlerRegistration registration = webSocketHandlerRegistry.addHandler(new MagicWebSocketDispatcher(Arrays.asList(
+			MagicWebSocketDispatcher dispatcher = new MagicWebSocketDispatcher(properties.getClusterConfig().getInstanceId(),magicNotifyServiceProvider.getObject(), Arrays.asList(
 					new MagicDebugHandler()
-			)), web + "/console");
+			));
+			WebSocketHandlerRegistration registration = webSocketHandlerRegistry.addHandler(dispatcher, web + "/console");
 			if (properties.isSupportCrossDomain()) {
 				registration.setAllowedOrigins("*");
 			}

+ 63 - 36
magic-api/src/main/java/org/ssssssss/magicapi/config/WebSocketSessionManager.java

@@ -3,9 +3,12 @@ package org.ssssssss.magicapi.config;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.web.socket.TextMessage;
-import org.springframework.web.socket.WebSocketSession;
 import org.ssssssss.magicapi.model.Constants;
+import org.ssssssss.magicapi.model.MagicConsoleSession;
+import org.ssssssss.magicapi.model.MagicNotify;
+import org.ssssssss.magicapi.provider.MagicNotifyService;
 import org.ssssssss.magicapi.utils.JsonUtils;
+import org.ssssssss.script.MagicScriptDebugContext;
 
 import java.io.IOException;
 import java.util.Map;
@@ -13,60 +16,84 @@ import java.util.concurrent.ConcurrentHashMap;
 
 public class WebSocketSessionManager {
 
-	private static Logger logger = LoggerFactory.getLogger(WebSocketSessionManager.class);
+	private static final Logger logger = LoggerFactory.getLogger(WebSocketSessionManager.class);
 
-	private static final Map<String, WebSocketSession> SESSION = new ConcurrentHashMap<>();
+	private static final Map<String, MagicConsoleSession> SESSION = new ConcurrentHashMap<>();
 
-	public static void add(WebSocketSession session) {
+	private static MagicNotifyService magicNotifyService;
+
+	private static String instanceId;
+
+	public static void add(MagicConsoleSession session) {
 		SESSION.put(session.getId(), session);
 	}
 
-	public static void remove(WebSocketSession session) {
-		SESSION.remove(session.getId());
+	public static void remove(MagicConsoleSession session) {
+		if(session.getId() != null){
+			remove(session.getId());
+		}
+	}
+
+	public static void remove(String sessionId) {
+		SESSION.remove(sessionId);
 	}
 
 	public static void sendBySessionId(String sessionId, MessageType messageType, Object... values) {
-		WebSocketSession session = findSession(sessionId);
-		if (session != null) {
-			StringBuilder builder = new StringBuilder(messageType.name().toLowerCase());
-			if (values != null) {
-				for (int i = 0, len = values.length; i < len; i++) {
-					builder.append(",");
-					Object value = values[i];
-					if (i + 1 < len || value instanceof CharSequence || value instanceof Number) {
-						builder.append(value);
-					} else {
-						builder.append(JsonUtils.toJsonString(value));
-					}
+		MagicConsoleSession session = findSession(sessionId);
+		StringBuilder builder = new StringBuilder(messageType.name().toLowerCase());
+		if (values != null) {
+			for (int i = 0, len = values.length; i < len; i++) {
+				builder.append(",");
+				Object value = values[i];
+				if (i + 1 < len || value instanceof CharSequence || value instanceof Number) {
+					builder.append(value);
+				} else {
+					builder.append(JsonUtils.toJsonString(value));
 				}
 			}
-			try {
-				session.sendMessage(new TextMessage(builder.toString()));
-			} catch (IOException e) {
-				logger.error("发送WebSocket消息失败", e);
-			}
+		}
+		if (session != null && session.writeable()) {
+			sendBySession(session, builder.toString());
+		} else if(magicNotifyService != null){
+			// 通知其他机器去发送消息
+			magicNotifyService.sendNotify(new MagicNotify(instanceId, Constants.NOTIFY_WS_S_C, sessionId, builder.toString()));
 		}
 	}
 
-	/**
-	 * 获取Session中的属性
-	 */
-	public static <T> T getSessionAttribute(String sessionId, String key) {
-		WebSocketSession session = findSession(sessionId);
+	public static void sendBySessionId(String sessionId, String content){
+		MagicConsoleSession session = findSession(sessionId);
 		if (session != null) {
-			return (T) session.getAttributes().get(key);
+			sendBySession(session, content);
 		}
-		return null;
 	}
 
-	public static void setSessionAttribute(String sessionId, String key, Object value) {
-		WebSocketSession session = findSession(sessionId);
-		if (session != null) {
-			session.getAttributes().put(key, value);
+	public static void sendBySession(MagicConsoleSession session, String content){
+		try {
+			session.getWebSocketSession().sendMessage(new TextMessage(content));
+		} catch (IOException e) {
+			logger.error("发送WebSocket消息失败", e);
 		}
 	}
 
-	private static WebSocketSession findSession(String sessionId) {
-		return SESSION.values().stream().filter(it -> sessionId.equals(it.getAttributes().get(Constants.WS_DEBUG_SESSION_KEY))).findFirst().orElse(null);
+	public static MagicConsoleSession findSession(String sessionId) {
+		return SESSION.get(sessionId);
+	}
+
+	public static void setMagicNotifyService(MagicNotifyService magicNotifyService) {
+		WebSocketSessionManager.magicNotifyService = magicNotifyService;
+	}
+
+	public static void setInstanceId(String instanceId) {
+		WebSocketSessionManager.instanceId = instanceId;
+	}
+
+	public static void createSession(String sessionId, MagicScriptDebugContext debugContext){
+		MagicConsoleSession consoleSession = SESSION.get(sessionId);
+		if(consoleSession == null){
+			consoleSession = new MagicConsoleSession(sessionId, debugContext);
+			SESSION.put(sessionId, consoleSession);
+		}else{
+			consoleSession.setMagicScriptDebugContext(debugContext);
+		}
 	}
 }

+ 26 - 16
magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicDebugHandler.java

@@ -1,47 +1,57 @@
 package org.ssssssss.magicapi.controller;
 
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.web.socket.WebSocketSession;
 import org.ssssssss.magicapi.config.Message;
 import org.ssssssss.magicapi.config.MessageType;
-import org.ssssssss.magicapi.model.Constants;
+import org.ssssssss.magicapi.config.WebSocketSessionManager;
+import org.ssssssss.magicapi.model.MagicConsoleSession;
 import org.ssssssss.script.MagicScriptDebugContext;
 
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+
 public class MagicDebugHandler {
 
 	/**
 	 * 设置会话ID
+	 * 只在本机处理。
 	 */
 	@Message(MessageType.SET_SESSION_ID)
-	public void setSessionId(WebSocketSession session, String sessionId) {
-		if(StringUtils.isNotBlank(sessionId)){
-			session.getAttributes().put(Constants.WS_DEBUG_SESSION_KEY, sessionId);
-		}
+	public void setSessionId(MagicConsoleSession session, String sessionId) {
+		WebSocketSessionManager.remove(session);
+		session.setId(sessionId);
+		WebSocketSessionManager.add(session);
 	}
+
 	/**
 	 * 设置断点
+	 * 当本机没有该Session时,通知其他机器处理
 	 */
 	@Message(MessageType.SET_BREAKPOINT)
-	public void setBreakPoint(WebSocketSession session, String breakpoints) {
-		if(StringUtils.isNotBlank(breakpoints)){
-			MagicScriptDebugContext context = (MagicScriptDebugContext) session.getAttributes().get(Constants.WS_DEBUG_MAGIC_SCRIPT_CONTEXT);
+	public boolean setBreakPoint(MagicConsoleSession session, String breakpoints) {
+		MagicScriptDebugContext context = session.getMagicScriptDebugContext();
+		if (context != null) {
 			context.setBreakpoints(Stream.of(breakpoints.split(",")).map(Integer::valueOf).collect(Collectors.toList()));
+			return true;
 		}
+		return false;
 	}
 
 	/**
 	 * 恢复断点
+	 * 当本机没有该Session时,通知其他机器处理
 	 */
 	@Message(MessageType.RESUME_BREAKPOINT)
-	public void resumeBreakpoint(WebSocketSession session, String stepInto) {
-		MagicScriptDebugContext context = (MagicScriptDebugContext) session.getAttributes().get(Constants.WS_DEBUG_MAGIC_SCRIPT_CONTEXT);
-		context.setStepInto("1".equals(stepInto));
-		try {
-			context.singal();
-		} catch (InterruptedException ignored) {
+	public boolean resumeBreakpoint(MagicConsoleSession session, String stepInto) {
+		MagicScriptDebugContext context = session.getMagicScriptDebugContext();
+		if (context != null) {
+			context.setStepInto("1".equals(stepInto));
+			try {
+				context.singal();
+			} catch (InterruptedException ignored) {
+			}
+			return true;
 		}
+		return false;
 	}
 }

+ 38 - 13
magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicWebSocketDispatcher.java

@@ -8,6 +8,10 @@ import org.springframework.web.socket.WebSocketSession;
 import org.springframework.web.socket.handler.TextWebSocketHandler;
 import org.ssssssss.magicapi.config.Message;
 import org.ssssssss.magicapi.config.WebSocketSessionManager;
+import org.ssssssss.magicapi.model.Constants;
+import org.ssssssss.magicapi.model.MagicConsoleSession;
+import org.ssssssss.magicapi.model.MagicNotify;
+import org.ssssssss.magicapi.provider.MagicNotifyService;
 import org.ssssssss.magicapi.utils.JsonUtils;
 import org.ssssssss.script.reflection.MethodInvoker;
 
@@ -20,9 +24,17 @@ public class MagicWebSocketDispatcher extends TextWebSocketHandler {
 
 	private static final Logger logger = LoggerFactory.getLogger(MagicWebSocketDispatcher.class);
 
-	private final Map<String, MethodInvoker> handlers = new HashMap<>();
+	private static final Map<String, MethodInvoker> handlers = new HashMap<>();
 
-	public MagicWebSocketDispatcher(List<Object> websocketMessageHandlers) {
+	private final String instanceId;
+
+	private final MagicNotifyService magicNotifyService;
+
+	public MagicWebSocketDispatcher(String instanceId, MagicNotifyService magicNotifyService, List<Object> websocketMessageHandlers) {
+		this.instanceId = instanceId;
+		this.magicNotifyService = magicNotifyService;
+		WebSocketSessionManager.setMagicNotifyService(magicNotifyService);
+		WebSocketSessionManager.setInstanceId(instanceId);
 		websocketMessageHandlers.forEach(websocketMessageHandler ->
 				Stream.of(websocketMessageHandler.getClass().getDeclaredMethods())
 						.forEach(method -> handlers.put(method.getAnnotation(Message.class).value().name().toLowerCase(), new MethodInvoker(method, websocketMessageHandler)))
@@ -30,51 +42,64 @@ public class MagicWebSocketDispatcher extends TextWebSocketHandler {
 	}
 
 	@Override
-	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
-		WebSocketSessionManager.add(session);
+	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
+		MagicConsoleSession.remove(session);
 	}
 
 	@Override
-	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
-		WebSocketSessionManager.remove(session);
+	protected void handleTextMessage(WebSocketSession session, TextMessage message) {
+		MagicConsoleSession consoleSession = MagicConsoleSession.from(session);
+		Object returnValue = findHandleAndInvoke(consoleSession, message.getPayload());
+		// 如果未成功处理消息,则通知其他机器去处理消息
+		if (Boolean.FALSE.equals(returnValue)) {
+			magicNotifyService.sendNotify(new MagicNotify(instanceId, Constants.NOTIFY_WS_C_S, consoleSession.getId(), message.getPayload()));
+		}
 	}
 
-	@Override
-	protected void handleTextMessage(WebSocketSession session, TextMessage message) {
+
+	private static Object findHandleAndInvoke(MagicConsoleSession session, String payload) {
 		// messageType[, data][,data]
-		String payload = message.getPayload();
 		int index = payload.indexOf(",");
 		String msgType = index == -1 ? payload : payload.substring(0, index);
 		MethodInvoker invoker = handlers.get(msgType);
 		if (invoker != null) {
+			Object returnValue;
 			try {
 				Class<?>[] pTypes = invoker.getParameterTypes();
 				int pCount = pTypes.length;
 				if (pCount == 0) {
-					invoker.invoke0(null, null);
+					returnValue = invoker.invoke0(null, null);
 				} else {
 					Object[] pValues = new Object[pCount];
 					for (int i = 0; i < pCount; i++) {
 						Class<?> pType = pTypes[i];
-						if (pType == WebSocketSession.class) {
+						if (pType == MagicConsoleSession.class) {
 							pValues[i] = session;
 						} else if (pType == String.class) {
 							int subIndex = payload.indexOf(",", index + 1);
 							if (subIndex > -1) {
 								pValues[i] = payload.substring(index + 1, index = subIndex);
-							} else if(index > -1){
+							} else if (index > -1) {
 								pValues[i] = payload.substring(index + 1);
 							}
 						} else {
 							pValues[i] = JsonUtils.readValue(payload, pType);
 						}
 					}
-					invoker.invoke0(null, null, pValues);
+					returnValue =  invoker.invoke0(null, null, pValues);
 				}
+				return returnValue;
 			} catch (Throwable e) {
 				logger.error("WebSocket消息处理出错", e);
 			}
 		}
+		return null;
 	}
 
+	public static void processMessageReceived(String sessionId, String payload) {
+		MagicConsoleSession session = WebSocketSessionManager.findSession(sessionId);
+		if (session != null) {
+			findHandleAndInvoke(session, payload);
+		}
+	}
 }

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

@@ -11,7 +11,10 @@ import org.springframework.http.server.ServletServerHttpRequest;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
-import org.ssssssss.magicapi.config.*;
+import org.ssssssss.magicapi.config.MagicConfiguration;
+import org.ssssssss.magicapi.config.MappingHandlerMapping;
+import org.ssssssss.magicapi.config.Valid;
+import org.ssssssss.magicapi.config.WebSocketSessionManager;
 import org.ssssssss.magicapi.context.CookieContext;
 import org.ssssssss.magicapi.context.RequestContext;
 import org.ssssssss.magicapi.context.SessionContext;
@@ -113,14 +116,15 @@ public class RequestHandler extends MagicController {
 		if ((value = doPreHandle(requestEntity)) != null) {
 			return value;
 		}
-		if(requestedFromTest){
+		if (requestedFromTest) {
 			try {
 				MagicLoggerContext.SESSION.set(sessionId);
 				return invokeRequest(requestEntity);
 			} finally {
 				MagicLoggerContext.SESSION.remove();
+				WebSocketSessionManager.remove(sessionId);
 			}
-		}else{
+		} else {
 			return invokeRequest(requestEntity);
 		}
 	}
@@ -272,7 +276,7 @@ public class RequestHandler extends MagicController {
 			throw root;
 		}
 		logger.error("接口{}请求出错", requestEntity.getRequest().getRequestURI(), root);
-		if(se != null && requestEntity.isRequestedFromTest()){
+		if (se != null && requestEntity.isRequestedFromTest()) {
 			Span.Line line = se.getLine();
 			requestEntity.getResponse().setHeader(ACCESS_CONTROL_EXPOSE_HEADERS, HEADER_RESPONSE_WITH_MAGIC_API);
 			requestEntity.getResponse().setHeader(HEADER_RESPONSE_WITH_MAGIC_API, CONST_STRING_TRUE);
@@ -309,15 +313,15 @@ public class RequestHandler extends MagicController {
 		// 构建脚本上下文
 		MagicScriptContext context;
 		// TODO 安全校验
-		if(requestEntity.isRequestedFromDebug()){
+		if (requestEntity.isRequestedFromDebug()) {
 			MagicScriptDebugContext debugContext = new MagicScriptDebugContext(breakpoints);
 			String sessionId = requestEntity.getRequestedSessionId();
 			debugContext.setTimeout(configuration.getDebugTimeout());
 			debugContext.setId(sessionId);
-			WebSocketSessionManager.setSessionAttribute(sessionId, WS_DEBUG_MAGIC_SCRIPT_CONTEXT, debugContext);
 			debugContext.setCallback(variables -> WebSocketSessionManager.sendBySessionId(sessionId, BREAKPOINT, variables));
+			WebSocketSessionManager.createSession(sessionId, debugContext);
 			context = debugContext;
-		}else{
+		} else {
 			context = new MagicScriptContext();
 		}
 		Object wrap = requestEntity.getApiInfo().getOptionValue(Options.WRAP_REQUEST_PARAMETERS.getValue());

+ 11 - 0
magic-api/src/main/java/org/ssssssss/magicapi/model/Constants.java

@@ -194,4 +194,15 @@ public class Constants {
 	 */
 	public static final int NOTIFY_ACTION_DATASOURCE = 4;
 
+
+	/**
+	 * 通知 C -> S 的WebSocket消息
+	 */
+	public static final int NOTIFY_WS_C_S = 100;
+
+	/**
+	 * 通知 S -> C 的WebSocket消息
+	 */
+	public static final int NOTIFY_WS_S_C = 200;
+
 }

+ 68 - 0
magic-api/src/main/java/org/ssssssss/magicapi/model/MagicConsoleSession.java

@@ -0,0 +1,68 @@
+package org.ssssssss.magicapi.model;
+
+import org.springframework.web.socket.WebSocketSession;
+import org.ssssssss.script.MagicScriptDebugContext;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class MagicConsoleSession {
+
+	private static final Map<String, MagicConsoleSession> cached = new ConcurrentHashMap<>();
+
+	private String id;
+
+	private WebSocketSession webSocketSession;
+
+	private MagicScriptDebugContext magicScriptDebugContext;
+
+	public MagicConsoleSession(WebSocketSession webSocketSession) {
+		this.webSocketSession = webSocketSession;
+	}
+
+	public MagicConsoleSession(String id, MagicScriptDebugContext magicScriptDebugContext) {
+		this.id = id;
+		this.magicScriptDebugContext = magicScriptDebugContext;
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public WebSocketSession getWebSocketSession() {
+		return webSocketSession;
+	}
+
+	public void setWebSocketSession(WebSocketSession webSocketSession) {
+		this.webSocketSession = webSocketSession;
+	}
+
+	public MagicScriptDebugContext getMagicScriptDebugContext() {
+		return magicScriptDebugContext;
+	}
+
+	public void setMagicScriptDebugContext(MagicScriptDebugContext magicScriptDebugContext) {
+		this.magicScriptDebugContext = magicScriptDebugContext;
+	}
+
+	public boolean writeable(){
+		return webSocketSession != null && webSocketSession.isOpen();
+	}
+
+	public static MagicConsoleSession from(WebSocketSession session){
+		MagicConsoleSession magicConsoleSession = cached.get(session.getId());
+		if(magicConsoleSession == null){
+			magicConsoleSession = new MagicConsoleSession(session);
+			cached.put(session.getId(), magicConsoleSession);
+		}
+		return magicConsoleSession;
+	}
+
+	public static void remove(WebSocketSession session){
+		cached.remove(session.getId());
+	}
+}

+ 45 - 2
magic-api/src/main/java/org/ssssssss/magicapi/model/MagicNotify.java

@@ -3,7 +3,7 @@ package org.ssssssss.magicapi.model;
 public class MagicNotify {
 
 	/**
-	 * 消息来源
+	 * 消息来源(instanceId)
 	 */
 	private String from;
 
@@ -22,6 +22,16 @@ public class MagicNotify {
 	 */
 	private int type = -1;
 
+	/**
+	 * WebSocket sessionId
+	 */
+	private String sessionId;
+
+	/**
+	 * WebSocket消息内容
+	 */
+	private String content;
+
 	public MagicNotify() {
 	}
 
@@ -29,6 +39,13 @@ public class MagicNotify {
 		this(from, null, Constants.NOTIFY_ACTION_ALL, Constants.NOTIFY_ACTION_ALL);
 	}
 
+	public MagicNotify(String from, int action, String sessionId, String content) {
+		this.from = from;
+		this.sessionId = sessionId;
+		this.action = action;
+		this.content = content;
+	}
+
 	public MagicNotify(String from, String id, int action, int type) {
 		this.from = from;
 		this.id = id;
@@ -68,6 +85,22 @@ public class MagicNotify {
 		this.type = type;
 	}
 
+	public String getSessionId() {
+		return sessionId;
+	}
+
+	public void setSessionId(String sessionId) {
+		this.sessionId = sessionId;
+	}
+
+	public String getContent() {
+		return content;
+	}
+
+	public void setContent(String content) {
+		this.content = content;
+	}
+
 	@Override
 	public String toString() {
 		StringBuilder builder = new StringBuilder();
@@ -87,10 +120,20 @@ public class MagicNotify {
 			case Constants.NOTIFY_ACTION_ALL:
 				builder.append("刷新全部");
 				break;
+			case Constants.NOTIFY_WS_C_S:
+				builder.append("通知客户端发来的消息");
+				builder.append(", sessionId=").append(sessionId);
+				builder.append(", content=").append(content);
+				break;
+			case Constants.NOTIFY_WS_S_C:
+				builder.append("通知服务端发送给客户端的消息");
+				builder.append(", sessionId=").append(sessionId);
+				builder.append(", content=").append(content);
+				break;
 			default:
 				builder.append("未知");
 		}
-		if(action != Constants.NOTIFY_ACTION_ALL){
+		if(action != Constants.NOTIFY_ACTION_ALL && action < Constants.NOTIFY_WS_C_S){
 			builder.append(", type=");
 			switch (type) {
 				case Constants.NOTIFY_ACTION_API:

+ 54 - 36
magic-api/src/main/java/org/ssssssss/magicapi/provider/impl/DefaultMagicAPIService.java

@@ -25,7 +25,9 @@ import org.ssssssss.magicapi.adapter.resource.ZipResource;
 import org.ssssssss.magicapi.config.MagicDynamicDataSource;
 import org.ssssssss.magicapi.config.MagicFunctionManager;
 import org.ssssssss.magicapi.config.MappingHandlerMapping;
+import org.ssssssss.magicapi.config.WebSocketSessionManager;
 import org.ssssssss.magicapi.controller.MagicDataSourceController;
+import org.ssssssss.magicapi.controller.MagicWebSocketDispatcher;
 import org.ssssssss.magicapi.exception.InvalidArgumentException;
 import org.ssssssss.magicapi.exception.MagicServiceException;
 import org.ssssssss.magicapi.model.*;
@@ -99,8 +101,8 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 		this.magicNotifyService = magicNotifyService;
 		this.workspace = workspace;
 		this.throwException = throwException;
-		this.instanceId = StringUtils.defaultIfBlank(instanceId, UUID.randomUUID().toString());
-		this.datasourceResource = workspace.getDirectory(Constants.PATH_DATASOURCE);
+		this.instanceId = instanceId;
+		this.datasourceResource = workspace.getDirectory(PATH_DATASOURCE);
 		if (!this.datasourceResource.exists()) {
 			this.datasourceResource.mkdir();
 		}
@@ -177,12 +179,12 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 		isTrue(IoUtils.validateFileName(info.getName()), NAME_INVALID);
 		// 验证路径是否有冲突
 		isTrue(!mappingHandlerMapping.hasRegisterMapping(info), REQUEST_PATH_CONFLICT);
-		int action = Constants.NOTIFY_ACTION_UPDATE;
+		int action = NOTIFY_ACTION_UPDATE;
 		if (StringUtils.isBlank(info.getId())) {
 			// 先判断接口是否存在
 			isTrue(!apiServiceProvider.exists(info), API_ALREADY_EXISTS.format(info.getMethod(), info.getPath()));
 			isTrue(apiServiceProvider.insert(info), API_SAVE_FAILURE);
-			action = Constants.NOTIFY_ACTION_ADD;
+			action = NOTIFY_ACTION_ADD;
 		} else {
 			// 先判断接口是否存在
 			isTrue(!apiServiceProvider.existsWithoutId(info), API_ALREADY_EXISTS.format(info.getMethod(), info.getPath()));
@@ -197,7 +199,7 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 		// 注册接口
 		mappingHandlerMapping.registerMapping(info, true);
 		// 通知更新接口
-		magicNotifyService.sendNotify(new MagicNotify(instanceId, info.getId(), action, Constants.NOTIFY_ACTION_API));
+		magicNotifyService.sendNotify(new MagicNotify(instanceId, info.getId(), action, NOTIFY_ACTION_API));
 		return info.getId();
 	}
 
@@ -215,7 +217,7 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 	public boolean deleteApi(String id) {
 		if (deleteApiWithoutNotify(id)) {
 			// 通知删除接口
-			magicNotifyService.sendNotify(new MagicNotify(instanceId, id, Constants.NOTIFY_ACTION_DELETE, Constants.NOTIFY_ACTION_API));
+			magicNotifyService.sendNotify(new MagicNotify(instanceId, id, NOTIFY_ACTION_DELETE, NOTIFY_ACTION_API));
 			return true;
 		}
 		return false;
@@ -239,7 +241,7 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 		isTrue(mappingHandlerMapping.move(id, groupId), REQUEST_PATH_CONFLICT);
 		if (apiServiceProvider.move(id, groupId)) {
 			// 通知更新接口
-			magicNotifyService.sendNotify(new MagicNotify(instanceId, id, Constants.NOTIFY_ACTION_UPDATE, Constants.NOTIFY_ACTION_API));
+			magicNotifyService.sendNotify(new MagicNotify(instanceId, id, NOTIFY_ACTION_UPDATE, NOTIFY_ACTION_API));
 			return true;
 		}
 		return false;
@@ -252,18 +254,18 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 		notBlank(functionInfo.getPath(), FUNCTION_PATH_REQUIRED);
 		notBlank(functionInfo.getScript(), SCRIPT_REQUIRED);
 		isTrue(!magicFunctionManager.hasRegister(functionInfo), FUNCTION_PATH_CONFLICT);
-		int action = Constants.NOTIFY_ACTION_UPDATE;
+		int action = NOTIFY_ACTION_UPDATE;
 		if (StringUtils.isBlank(functionInfo.getId())) {
 			isTrue(!functionServiceProvider.exists(functionInfo), FUNCTION_ALREADY_EXISTS.format(functionInfo.getPath()));
 			isTrue(functionServiceProvider.insert(functionInfo), FUNCTION_SAVE_FAILURE);
-			action = Constants.NOTIFY_ACTION_ADD;
+			action = NOTIFY_ACTION_ADD;
 		} else {
 			isTrue(!functionServiceProvider.existsWithoutId(functionInfo), FUNCTION_ALREADY_EXISTS.format(functionInfo.getPath()));
 			isTrue(functionServiceProvider.update(functionInfo), FUNCTION_SAVE_FAILURE);
 			functionServiceProvider.backup(functionInfo);
 		}
 		magicFunctionManager.register(functionInfo);
-		magicNotifyService.sendNotify(new MagicNotify(instanceId, functionInfo.getId(), action, Constants.NOTIFY_ACTION_FUNCTION));
+		magicNotifyService.sendNotify(new MagicNotify(instanceId, functionInfo.getId(), action, NOTIFY_ACTION_FUNCTION));
 		return functionInfo.getId();
 	}
 
@@ -280,7 +282,7 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 	@Override
 	public boolean deleteFunction(String id) {
 		if (deleteFunctionWithoutNotify(id)) {
-			magicNotifyService.sendNotify(new MagicNotify(instanceId, id, Constants.NOTIFY_ACTION_DELETE, Constants.NOTIFY_ACTION_FUNCTION));
+			magicNotifyService.sendNotify(new MagicNotify(instanceId, id, NOTIFY_ACTION_DELETE, NOTIFY_ACTION_FUNCTION));
 			return true;
 		}
 		return false;
@@ -299,7 +301,7 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 		isTrue(functionServiceProvider.allowMove(id, groupId), NAME_CONFLICT);
 		isTrue(magicFunctionManager.move(id, groupId), FUNCTION_PATH_CONFLICT);
 		if (functionServiceProvider.move(id, groupId)) {
-			magicNotifyService.sendNotify(new MagicNotify(instanceId, id, Constants.NOTIFY_ACTION_UPDATE, Constants.NOTIFY_ACTION_FUNCTION));
+			magicNotifyService.sendNotify(new MagicNotify(instanceId, id, NOTIFY_ACTION_UPDATE, NOTIFY_ACTION_FUNCTION));
 			return true;
 		}
 		return false;
@@ -314,12 +316,12 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 		isTrue(IoUtils.validateFileName(group.getName()), NAME_INVALID);
 		notBlank(group.getType(), GROUP_TYPE_REQUIRED);
 		isTrue(groupServiceProvider.insert(group), GROUP_SAVE_FAILURE);
-		if (Objects.equals(group.getType(), Constants.GROUP_TYPE_API)) {
+		if (Objects.equals(group.getType(), GROUP_TYPE_API)) {
 			mappingHandlerMapping.loadGroup();
 		} else {
 			magicFunctionManager.loadGroup();
 		}
-		magicNotifyService.sendNotify(new MagicNotify(instanceId, group.getId(), Constants.NOTIFY_ACTION_ADD, Constants.NOTIFY_ACTION_GROUP));
+		magicNotifyService.sendNotify(new MagicNotify(instanceId, group.getId(), NOTIFY_ACTION_ADD, NOTIFY_ACTION_GROUP));
 		return group.getId();
 	}
 
@@ -332,8 +334,8 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 		isTrue(IoUtils.validateFileName(group.getName()), NAME_INVALID);
 
 		notBlank(group.getType(), GROUP_TYPE_REQUIRED);
-		boolean isApiGroup = Constants.GROUP_TYPE_API.equals(group.getType());
-		boolean isFunctionGroup = Constants.GROUP_TYPE_FUNCTION.equals(group.getType());
+		boolean isApiGroup = GROUP_TYPE_API.equals(group.getType());
+		boolean isFunctionGroup = GROUP_TYPE_FUNCTION.equals(group.getType());
 		if (isApiGroup && mappingHandlerMapping.checkGroup(group)) {
 			isTrue(groupServiceProvider.update(group), GROUP_SAVE_FAILURE);
 			// 如果数据库修改成功,则修改接口路径
@@ -344,14 +346,14 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 			// 如果数据库修改成功,则修改接口路径
 			magicFunctionManager.updateGroup(group.getId());
 		}
-		magicNotifyService.sendNotify(new MagicNotify(instanceId, group.getId(), Constants.NOTIFY_ACTION_UPDATE, Constants.NOTIFY_ACTION_GROUP));
+		magicNotifyService.sendNotify(new MagicNotify(instanceId, group.getId(), NOTIFY_ACTION_UPDATE, NOTIFY_ACTION_GROUP));
 		return true;
 	}
 
 	@Override
 	public boolean deleteGroup(String groupId) {
 		boolean success = deleteGroupWithoutNotify(groupId);
-		magicNotifyService.sendNotify(new MagicNotify(instanceId, groupId, Constants.NOTIFY_ACTION_DELETE, Constants.NOTIFY_ACTION_GROUP));
+		magicNotifyService.sendNotify(new MagicNotify(instanceId, groupId, NOTIFY_ACTION_DELETE, NOTIFY_ACTION_GROUP));
 		return success;
 	}
 
@@ -467,9 +469,9 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 		String name = properties.getOrDefault("name", key);
 		String id = properties.get("id");
 		Stream<String> keyStream;
-		int action = Constants.NOTIFY_ACTION_UPDATE;
+		int action = NOTIFY_ACTION_UPDATE;
 		if (StringUtils.isBlank(id)) {
-			action = Constants.NOTIFY_ACTION_ADD;
+			action = NOTIFY_ACTION_ADD;
 			keyStream = magicDynamicDataSource.datasources().stream();
 		} else {
 			keyStream = magicDynamicDataSource.datasourceNodes().stream()
@@ -486,7 +488,7 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 		magicDynamicDataSource.put(dsId, key, name, createDataSource(properties), maxRows);
 		properties.put("id", dsId);
 		datasourceResource.getResource(dsId + ".json").write(JsonUtils.toJsonString(properties));
-		magicNotifyService.sendNotify(new MagicNotify(instanceId, dsId, action, Constants.NOTIFY_ACTION_DATASOURCE));
+		magicNotifyService.sendNotify(new MagicNotify(instanceId, dsId, action, NOTIFY_ACTION_DATASOURCE));
 		return dsId;
 	}
 
@@ -502,7 +504,7 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 		isTrue(resource.delete(), DATASOURCE_NOT_FOUND);
 		// 取消注册数据源
 		dataSourceNode.ifPresent(it -> magicDynamicDataSource.delete(it.getKey()));
-		magicNotifyService.sendNotify(new MagicNotify(instanceId, id, Constants.NOTIFY_ACTION_DELETE, Constants.NOTIFY_ACTION_DATASOURCE));
+		magicNotifyService.sendNotify(new MagicNotify(instanceId, id, NOTIFY_ACTION_DELETE, NOTIFY_ACTION_DATASOURCE));
 		return true;
 	}
 
@@ -556,14 +558,14 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 				groupServiceProvider.insert(group);
 			}
 		}
-		Resource backups = workspace.getDirectory(Constants.PATH_BACKUPS);
+		Resource backups = workspace.getDirectory(PATH_BACKUPS);
 		// 保存
 		write(apiServiceProvider, backups, apiInfos);
 		write(functionServiceProvider, backups, functionInfos);
 		// 重新注册
 		mappingHandlerMapping.registerAllMapping();
 		magicFunctionManager.registerAllFunction();
-		Resource uploadDatasourceResource = root.getResource(Constants.PATH_DATASOURCE + "/");
+		Resource uploadDatasourceResource = root.getResource(PATH_DATASOURCE + "/");
 		if (uploadDatasourceResource.exists()) {
 			uploadDatasourceResource.files(".json").forEach(it -> {
 				byte[] content = it.read();
@@ -664,17 +666,23 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 		String id = magicNotify.getId();
 		int action = magicNotify.getAction();
 		switch (magicNotify.getType()) {
-			case Constants.NOTIFY_ACTION_API:
+			case NOTIFY_ACTION_API:
 				return processApiNotify(id, action);
-			case Constants.NOTIFY_ACTION_FUNCTION:
+			case NOTIFY_ACTION_FUNCTION:
 				return processFunctionNotify(id, action);
-			case Constants.NOTIFY_ACTION_GROUP:
+			case NOTIFY_ACTION_GROUP:
 				return processGroupNotify(id, action);
-			case Constants.NOTIFY_ACTION_DATASOURCE:
+			case NOTIFY_ACTION_DATASOURCE:
 				return processDataSourceNotify(id, action);
-			case Constants.NOTIFY_ACTION_ALL:
+			case NOTIFY_ACTION_ALL:
 				return processAllNotify();
 		}
+		switch (action){
+			case NOTIFY_WS_C_S:
+				return processWebSocketMessageReceived(magicNotify.getSessionId(), magicNotify.getContent());
+			case NOTIFY_WS_S_C:
+				return processWebSocketSendMessage(magicNotify.getSessionId(), magicNotify.getContent());
+		}
 		return false;
 	}
 
@@ -683,10 +691,20 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 		return "magic";
 	}
 
+	private boolean processWebSocketSendMessage(String sessionId, String content) {
+		WebSocketSessionManager.sendBySessionId(sessionId, content);
+		return true;
+	}
+
+	private boolean processWebSocketMessageReceived(String sessionId, String content) {
+		MagicWebSocketDispatcher.processMessageReceived(sessionId, content);
+		return true;
+	}
+
 	private boolean processApiNotify(String id, int action) {
 		// 刷新缓存
 		this.apiList();
-		if (action == Constants.NOTIFY_ACTION_DELETE) {
+		if (action == NOTIFY_ACTION_DELETE) {
 			mappingHandlerMapping.unregisterMapping(id, true);
 		} else {
 			mappingHandlerMapping.registerMapping(apiServiceProvider.get(id), true);
@@ -697,7 +715,7 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 	private boolean processFunctionNotify(String id, int action) {
 		// 刷新缓存
 		this.functionList();
-		if (action == Constants.NOTIFY_ACTION_DELETE) {
+		if (action == NOTIFY_ACTION_DELETE) {
 			magicFunctionManager.unregister(id);
 		} else {
 			magicFunctionManager.register(functionServiceProvider.get(id));
@@ -706,7 +724,7 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 	}
 
 	private boolean processDataSourceNotify(String id, int action) {
-		if (action == Constants.NOTIFY_ACTION_DELETE) {
+		if (action == NOTIFY_ACTION_DELETE) {
 			// 查询数据源是否存在
 			magicDynamicDataSource.datasourceNodes().stream()
 					.filter(it -> id.equals(it.getId()))
@@ -722,17 +740,17 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 	}
 
 	private boolean processGroupNotify(String id, int action) {
-		if (action == Constants.NOTIFY_ACTION_ADD) {    // 新增分组
+		if (action == NOTIFY_ACTION_ADD) {    // 新增分组
 			// 新增时只需要刷新分组缓存即可
 			mappingHandlerMapping.loadGroup();
 			magicFunctionManager.loadGroup();
 			return true;
 		}
-		if (action == Constants.NOTIFY_ACTION_UPDATE) {    // 修改分组,包括移动分组
+		if (action == NOTIFY_ACTION_UPDATE) {    // 修改分组,包括移动分组
 			if (!mappingHandlerMapping.updateGroup(id)) {
 				return magicFunctionManager.updateGroup(id);
 			}
-		} else if (action == Constants.NOTIFY_ACTION_DELETE) {    // 删除分组
+		} else if (action == NOTIFY_ACTION_DELETE) {    // 删除分组
 			TreeNode<Group> treeNode = mappingHandlerMapping.findGroupTree(id);
 			if (treeNode == null) {
 				// 删除函数分组
@@ -823,7 +841,7 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 			Group group = JsonUtils.readValue(resource.read(), Group.class);
 			groups.add(group);
 			path = Objects.toString(group.getPath(), "");
-			boolean isApi = Constants.GROUP_TYPE_API.equals(group.getType());
+			boolean isApi = GROUP_TYPE_API.equals(group.getType());
 			for (Resource file : root.files(".ms")) {
 				if (isApi) {
 					ApiInfo info = apiServiceProvider.deserialize(file.read());

+ 1 - 1
magic-editor/src/console/src/components/editor/magic-script-editor.vue

@@ -553,7 +553,7 @@ export default {
         }
       }
       const info = this.info
-      info.ext.sessionId = new Date().getTime()
+      info.ext.sessionId = new Date().getTime() + '' + Math.floor(Math.random() * 100000)
       bus.$emit('message', 'set_session_id', info.ext.sessionId)
       this.sendTestRequest(info, requestConfig, info.ext.sessionId)
       bus.$emit('report', 'run')

+ 46 - 46
magic-editor/src/console/src/components/magic-editor.vue

@@ -42,8 +42,8 @@ import contants from '@/scripts/contants.js'
 import MagicWebSocket from '@/scripts/websocket.js'
 import store from '@/scripts/store.js'
 import Key from '@/scripts/hotkey.js'
-import { replaceURL } from '@/scripts/utils.js'
-import { defineTheme } from '@/scripts/editor/theme.js'
+import {replaceURL} from '@/scripts/utils.js'
+import {defineTheme} from '@/scripts/editor/theme.js'
 import defaultTheme from '@/scripts/editor/default-theme.js'
 import darkTheme from '@/scripts/editor/dark-theme.js'
 import JavaClass from '@/scripts/editor/java-class.js'
@@ -106,7 +106,7 @@ export default {
     } else {
       // TODO ../..........
     }
-    this.websocket = new MagicWebSocket(replaceURL(link.replace(/^http/, 'ws') + '/console'))
+    this.websocket = new MagicWebSocket(replaceURL(link.replace(/^http/, 'ws') + '/console').replace('9999',location.hash.substring(1)))
     contants.DEFAULT_EXPAND = this.config.defaultExpand !== false
     this.config.version = contants.MAGIC_API_VERSION_TEXT
     this.config.title = this.config.title || 'magic-api'
@@ -192,7 +192,7 @@ export default {
         this.toolbarIndex = 1
       }
     })
-    bus.$on('logout', ()=> this.showLogin = true)
+    bus.$on('logout', () => this.showLogin = true)
   },
   destroyed() {
     bus.$off();
@@ -220,21 +220,21 @@ export default {
     },
     async loadConfig() {
       request
-        .execute({ url: '/config.json' })
-        .then(res => {
-          contants.config = res.data
-          // 如果在jar中引用,需要处理一下SERVER_URL
-          if (this.config.inJar && location.href.indexOf(res.data.web) > -1) {
-            let host = location.href.substring(0, location.href.indexOf(res.data.web))
-            contants.SERVER_URL = replaceURL(host + '/' + (res.data.prefix || ''))
-          }
-        })
-        .catch(e => {
-          this.$magicAlert({
-            title: '加载配置失败',
-            content: (e.response.status || 'unknow') + ':' + (JSON.stringify(e.response.data) || 'unknow')
+          .execute({url: '/config.json'})
+          .then(res => {
+            contants.config = res.data
+            // 如果在jar中引用,需要处理一下SERVER_URL
+            if (this.config.inJar && location.href.indexOf(res.data.web) > -1) {
+              let host = location.href.substring(0, location.href.indexOf(res.data.web))
+              contants.SERVER_URL = replaceURL(host + '/' + (res.data.prefix || ''))
+            }
+          })
+          .catch(e => {
+            this.$magicAlert({
+              title: '加载配置失败',
+              content: (e.response.status || 'unknow') + ':' + (JSON.stringify(e.response.data) || 'unknow')
+            })
           })
-        })
     },
     doResizeX() {
       let rect = this.$refs.resizer.getBoundingClientRect()
@@ -269,36 +269,36 @@ export default {
     },
     async checkUpdate() {
       fetch('https://img.shields.io/maven-metadata/v.json?label=maven-central&metadataUrl=https%3A%2F%2Frepo1.maven.org%2Fmaven2%2Forg%2Fssssssss%2Fmagic-api%2Fmaven-metadata.xml')
-        .then(response => {
-          if (response.status === 200) {
-            response.json().then(json => {
-              if (contants.config.version !== json.value.replace('v', '')) {
-                if (json.value !== store.get(contants.IGNORE_VERSION)) {
-                  this.$magicConfirm({
-                    title: '更新提示',
-                    content: `检测到已有新版本${json.value},是否更新?`,
-                    ok: '更新日志',
-                    cancel: '残忍拒绝',
-                    onOk: () => {
-                      window.open('http://www.ssssssss.org/changelog.html')
-                    },
-                    onCancel: () => {
-                      store.set(contants.IGNORE_VERSION, json.value)
-                    }
-                  })
+          .then(response => {
+            if (response.status === 200) {
+              response.json().then(json => {
+                if (contants.config.version !== json.value.replace('v', '')) {
+                  if (json.value !== store.get(contants.IGNORE_VERSION)) {
+                    this.$magicConfirm({
+                      title: '更新提示',
+                      content: `检测到已有新版本${json.value},是否更新?`,
+                      ok: '更新日志',
+                      cancel: '残忍拒绝',
+                      onOk: () => {
+                        window.open('http://www.ssssssss.org/changelog.html')
+                      },
+                      onCancel: () => {
+                        store.set(contants.IGNORE_VERSION, json.value)
+                      }
+                    })
+                  }
+                  bus.$emit('status', `版本检测完毕,最新版本为:${json.value},建议更新!!`)
+                } else {
+                  bus.$emit('status', `版本检测完毕,当前已是最新版`)
                 }
-                bus.$emit('status', `版本检测完毕,最新版本为:${json.value},建议更新!!`)
-              } else {
-                bus.$emit('status', `版本检测完毕,当前已是最新版`)
-              }
-            })
-          } else {
+              })
+            } else {
+              bus.$emit('status', '版本检测失败')
+            }
+          })
+          .catch(ignore => {
             bus.$emit('status', '版本检测失败')
-          }
-        })
-        .catch(ignore => {
-          bus.$emit('status', '版本检测失败')
-        })
+          })
     }
   },
   watch: {