diff --git a/magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/ClusterConfig.java b/magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/ClusterConfig.java index a5ed73dc..1a549472 100644 --- a/magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/ClusterConfig.java +++ b/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 通道 diff --git a/magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/MagicAPIAutoConfiguration.java b/magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/MagicAPIAutoConfiguration.java index 363f4cfc..f9112b52 100644 --- a/magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/MagicAPIAutoConfiguration.java +++ b/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> magicFunctionsProvider; + private final ObjectProvider magicNotifyServiceProvider; + private final Environment environment; private final MagicCorsFilter magicCorsFilter = new MagicCorsFilter(); @@ -145,6 +147,7 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon ObjectProvider> columnMapperProvidersProvider, ObjectProvider> magicFunctionsProvider, ObjectProvider restTemplateProvider, + ObjectProvider magicNotifyServiceProvider, ObjectProvider 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("*"); } diff --git a/magic-api/src/main/java/org/ssssssss/magicapi/config/WebSocketSessionManager.java b/magic-api/src/main/java/org/ssssssss/magicapi/config/WebSocketSessionManager.java index e4040b61..d609a52d 100644 --- a/magic-api/src/main/java/org/ssssssss/magicapi/config/WebSocketSessionManager.java +++ b/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 SESSION = new ConcurrentHashMap<>(); + private static final Map 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 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); - } - return null; - } - - public static void setSessionAttribute(String sessionId, String key, Object value) { - WebSocketSession session = findSession(sessionId); - if (session != null) { - session.getAttributes().put(key, value); + sendBySession(session, content); } } - 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 void sendBySession(MagicConsoleSession session, String content){ + try { + session.getWebSocketSession().sendMessage(new TextMessage(content)); + } catch (IOException e) { + logger.error("发送WebSocket消息失败", e); + } + } + + 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); + } } } diff --git a/magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicDebugHandler.java b/magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicDebugHandler.java index 22b36fd4..1f5cccee 100644 --- a/magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicDebugHandler.java +++ b/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; } } diff --git a/magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicWebSocketDispatcher.java b/magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicWebSocketDispatcher.java index e5692b8e..4c33af37 100644 --- a/magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicWebSocketDispatcher.java +++ b/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 handlers = new HashMap<>(); + private static final Map handlers = new HashMap<>(); - public MagicWebSocketDispatcher(List websocketMessageHandlers) { + private final String instanceId; + + private final MagicNotifyService magicNotifyService; + + public MagicWebSocketDispatcher(String instanceId, MagicNotifyService magicNotifyService, List 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); - } - - @Override - public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { - WebSocketSessionManager.remove(session); + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + MagicConsoleSession.remove(session); } @Override 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())); + } + } + + + 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); + } + } } diff --git a/magic-api/src/main/java/org/ssssssss/magicapi/controller/RequestHandler.java b/magic-api/src/main/java/org/ssssssss/magicapi/controller/RequestHandler.java index 776dd67c..2bdca698 100644 --- a/magic-api/src/main/java/org/ssssssss/magicapi/controller/RequestHandler.java +++ b/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()); diff --git a/magic-api/src/main/java/org/ssssssss/magicapi/model/Constants.java b/magic-api/src/main/java/org/ssssssss/magicapi/model/Constants.java index 1dea3901..2f71e92d 100644 --- a/magic-api/src/main/java/org/ssssssss/magicapi/model/Constants.java +++ b/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; + } diff --git a/magic-api/src/main/java/org/ssssssss/magicapi/model/MagicConsoleSession.java b/magic-api/src/main/java/org/ssssssss/magicapi/model/MagicConsoleSession.java new file mode 100644 index 00000000..f34168fc --- /dev/null +++ b/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 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()); + } +} diff --git a/magic-api/src/main/java/org/ssssssss/magicapi/model/MagicNotify.java b/magic-api/src/main/java/org/ssssssss/magicapi/model/MagicNotify.java index 9724758b..9429729e 100644 --- a/magic-api/src/main/java/org/ssssssss/magicapi/model/MagicNotify.java +++ b/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: diff --git a/magic-api/src/main/java/org/ssssssss/magicapi/provider/impl/DefaultMagicAPIService.java b/magic-api/src/main/java/org/ssssssss/magicapi/provider/impl/DefaultMagicAPIService.java index a37cac0a..a25decf1 100644 --- a/magic-api/src/main/java/org/ssssssss/magicapi/provider/impl/DefaultMagicAPIService.java +++ b/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 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 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()); diff --git a/magic-editor/src/console/src/components/editor/magic-script-editor.vue b/magic-editor/src/console/src/components/editor/magic-script-editor.vue index 2a6e4330..e0026d55 100644 --- a/magic-editor/src/console/src/components/editor/magic-script-editor.vue +++ b/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') diff --git a/magic-editor/src/console/src/components/magic-editor.vue b/magic-editor/src/console/src/components/magic-editor.vue index 85b813eb..3cfde692 100644 --- a/magic-editor/src/console/src/components/magic-editor.vue +++ b/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: {