diff --git a/designer-base/src/main/java/com/fr/design/RPCConnectHandlerCenter.java b/designer-base/src/main/java/com/fr/design/RPCConnectHandlerCenter.java new file mode 100644 index 0000000000..eb08789d0d --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/RPCConnectHandlerCenter.java @@ -0,0 +1,159 @@ +package com.fr.design; + +import com.fr.design.dialog.FineJOptionPane; +import com.fr.design.i18n.Toolkit; +import com.fr.design.mainframe.DesignerContext; +import com.fr.design.ui.util.UIUtil; +import com.fr.event.Event; +import com.fr.event.EventDispatcher; +import com.fr.event.Listener; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StableUtils; +import com.fr.third.org.apache.http.client.config.RequestConfig; +import com.fr.third.org.apache.http.client.methods.CloseableHttpResponse; +import com.fr.third.org.apache.http.client.methods.HttpGet; +import com.fr.third.org.apache.http.impl.client.CloseableHttpClient; +import com.fr.third.org.apache.http.impl.client.HttpClients; +import com.fr.workspace.WorkContext; +import com.fr.workspace.Workspace; +import com.fr.workspace.WorkspaceEvent; +import com.fr.workspace.base.WorkspaceConstants; +import com.fr.workspace.connect.WorkspaceConnectionInfo; +import com.fr.workspace.engine.channel.WorkspaceChannelFactory; +import com.fr.workspace.engine.exception.WorkspaceConnectionException; + +import javax.swing.JOptionPane; +import javax.swing.UIManager; + +/** + * RPC连接处理中心 + * + * @author Roger + * @since 11.0 + * Created on 2023/12/13 + */ +public class RPCConnectHandlerCenter { + + private static volatile boolean alerting = false; + private static final int TIMEOUT = 5000; + + private static Listener listener = new Listener() { + @Override + public void on(Event event, Workspace workspace) { + //暂时先不做重连处理,3次RPC连接失败后提示切换工作目录 + showRPCDisconnectDialog(); + } + }; + + /** + * 开启事件监听 + */ + public static void startListener() { + if (!WorkContext.getCurrent().isLocal()) { + EventDispatcher.listen(WorkspaceEvent.LostConnect, listener); + } + } + + /** + * 弹窗提示连接断开 + */ + public static void showRPCDisconnectDialog() { + UIUtil.invokeLaterIfNeeded(RPCConnectHandlerCenter::showDialog); + } + + /** + * RPC连接测试 + * + * @param info 连接信息 + * @return 是否连接成功 + */ + public static boolean checkRPCConnect(WorkspaceConnectionInfo info) { + try { + return WorkspaceChannelFactory.testConnection(info); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e, e.getMessage()); + return false; + } + } + + /** + * http连接检测,从DesignerSocketIO中移过来的,先保留着 + * + * @param info 连接信息 + * @return 是否连接成功 + */ + public static boolean checkHttpConnect(WorkspaceConnectionInfo info) { + CloseableHttpClient httpclient = HttpClients.createDefault(); + HttpGet httpGet = new HttpGet(StableUtils.pathJoin(info.getUrl(), WorkspaceConstants.CONTROLLER_PREFIX, WorkspaceConstants.VT)); + RequestConfig requestConfig = RequestConfig + .custom() + .setConnectTimeout(TIMEOUT) + .setConnectionRequestTimeout(TIMEOUT) + .build(); + httpGet.setConfig(requestConfig); + try { + CloseableHttpResponse response = httpclient.execute(httpGet); + if (isErrorStatus(response.getStatusLine().getStatusCode())) { + //这边nginx做负载,服务器被kill掉,返回的是502,不会抛错,导致checkRPCConnect通过 + //针对500-600的错误码加个判断,其他类型的状态码暂不考虑,如果有遇到再处理,不然怕影响范围大 + throw new WorkspaceConnectionException("Response " + response.getStatusLine().toString()); + } + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e, e.getMessage()); + return false; + } + return true; + } + + /** + * 提示连接已经断开,如果已经在提示中了就直接返回 + */ + private static void showDialog() { + if (alerting) { + return; + } + synchronized (RPCConnectHandlerCenter.class) { + if (alerting) { + return; + } + alerting = true; + try { + FineJOptionPane.showMessageDialog( + DesignerContext.getDesignerFrame(), + Toolkit.i18nText("Fine-Design_Basic_Remote_Disconnected"), + UIManager.getString("OptionPane.messageDialogTitle"), + JOptionPane.ERROR_MESSAGE, + UIManager.getIcon("OptionPane.errorIcon")); + EnvChangeEntrance.getInstance().chooseEnv(); + } finally { + alerting = false; + } + } + } + + /** + * 错误状态码 + * 5xx(服务器错误)这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错。代码 说明 + * 500 (服务器内部错误) 服务器遇到错误,无法完成请求。 + * 501 (尚未实施) 服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码。 + * 502 (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。 + * 503 (服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。 + * 504 (网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求。 + * 505 (HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本。 + * + * @param status 错误状态码 + * @return 是否是错误状态码 + */ + private static boolean isErrorStatus(int status) { + return status >= 500 && status <= 600; + } + + /** + * 停止事件监听 + */ + public static void stopListener() { + if (!WorkContext.getCurrent().isLocal()) { + EventDispatcher.stopListen(listener); + } + } +} diff --git a/designer-base/src/main/java/com/fr/design/lock/TemplateLockInfoReSave.java b/designer-base/src/main/java/com/fr/design/lock/TemplateLockInfoReSave.java new file mode 100644 index 0000000000..f1f927abb1 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/lock/TemplateLockInfoReSave.java @@ -0,0 +1,56 @@ +package com.fr.design.lock; + +import com.fr.design.file.HistoryTemplateListCache; +import com.fr.design.mainframe.JTemplate; +import com.fr.event.Event; +import com.fr.event.EventDispatcher; +import com.fr.event.Listener; +import com.fr.log.FineLoggerFactory; +import com.fr.report.lock.LockInfoOperator; +import com.fr.stable.collections.CollectionUtils; +import com.fr.workspace.WorkContext; +import com.fr.workspace.Workspace; +import com.fr.workspace.WorkspaceEvent; + +import java.util.List; + +/** + * 更新模板锁信息 + * + * @author Roger + * @since 11.0 + * Created on 2023/12/11 + */ +public class TemplateLockInfoReSave { + + private static Listener listener = new Listener() { + @Override + public void on(Event event, Workspace workspace) { + List> templates = HistoryTemplateListCache.getInstance().getHistoryList(); + if (CollectionUtils.isEmpty(templates)) { + return; + } + String[] paths = templates.stream().map(JTemplate::getPath).toArray(String[]::new); + String[] lockedPath = WorkContext.getCurrent().get(LockInfoOperator.class).lockTemplates(paths); + FineLoggerFactory.getLogger().warn("template lock failed:{}", String.join(";", lockedPath)); + } + }; + + /** + * 启动监听 + */ + public static void startListener() { + if (!WorkContext.getCurrent().isLocal()) { + EventDispatcher.listen(WorkspaceEvent.ConnectSuccess, listener); + } + } + + /** + * 停止事件监听 + */ + public static void stopListener() { + if (!WorkContext.getCurrent().isLocal()) { + EventDispatcher.stopListen(listener); + } + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/JTemplate.java b/designer-base/src/main/java/com/fr/design/mainframe/JTemplate.java index ef7c1a5201..3cd8fdb78c 100644 --- a/designer-base/src/main/java/com/fr/design/mainframe/JTemplate.java +++ b/designer-base/src/main/java/com/fr/design/mainframe/JTemplate.java @@ -92,6 +92,7 @@ import com.fr.plugin.observer.PluginEventListener; import com.fr.plugin.observer.PluginEventType; import com.fr.plugin.observer.PluginListenerRegistration; import com.fr.report.InconsistentLockException; +import com.fr.report.LockedException; import com.fr.report.UnLockedException; import com.fr.report.cell.Elem; import com.fr.report.cell.cellattr.CellImage; @@ -103,6 +104,7 @@ import com.fr.stable.StringUtils; import com.fr.stable.core.UUID; import com.fr.widgettheme.designer.WidgetThemeDisplayAction; import com.fr.workspace.WorkContext; +import com.fr.workspace.base.UserInfo; import com.fr.workspace.server.lock.TplOperator; import javax.swing.BorderFactory; @@ -1059,13 +1061,20 @@ public abstract class JTemplate> protected void checkBeforeSave() throws Exception { // 保存前校验下未解锁 - if (WorkContext.getCurrent().get(LockInfoOperator.class).isTplUnLocked(getEditingFILE().getPath())) { + LockInfoOperator lockInfoOperator = WorkContext.getCurrent().get(LockInfoOperator.class); + String path = getEditingFILE().getPath(); + if (lockInfoOperator.isTplUnLocked(path)) { throw new UnLockedException(); } + //睡眠超过90s之后,锁信息会被清掉,之后其他人可能打开模板进行锁定,所以定这里还判断一下模板是否被其他人锁 + UserInfo userInfo = lockInfoOperator.getUserInfo(path); + if (userInfo != null) { + throw new LockedException(userInfo); + } // 过滤掉本地文件 boolean localFile = getEditingFILE() instanceof FileFILE; boolean inconsistent = !localFile && getEditingFILE().exists() - && !WorkContext.getCurrent().get(LockInfoOperator.class).isConsistentLock(getEditingFILE().getPath()); + && !lockInfoOperator.consistentCheckAndLockIfNecessary(path); if (inconsistent) { throw new InconsistentLockException(); } diff --git a/designer-base/src/main/java/com/fr/design/worker/save/SaveFailureHandler.java b/designer-base/src/main/java/com/fr/design/worker/save/SaveFailureHandler.java index 9ab897d7e8..458ead48a1 100644 --- a/designer-base/src/main/java/com/fr/design/worker/save/SaveFailureHandler.java +++ b/designer-base/src/main/java/com/fr/design/worker/save/SaveFailureHandler.java @@ -4,6 +4,7 @@ import com.fr.common.exception.ThrowableHandler; import com.fr.design.dialog.FineJOptionPane; import com.fr.design.file.HistoryTemplateListCache; import com.fr.design.i18n.Toolkit; +import com.fr.design.lock.LockInfoDialog; import com.fr.design.mainframe.DesignerContext; import com.fr.design.mainframe.JTemplate; import com.fr.design.ui.util.UIUtil; @@ -11,7 +12,9 @@ import com.fr.design.utils.TemplateUtils; import com.fr.file.FileNodeFILE; import com.fr.file.filetree.FileNode; import com.fr.general.IOUtils; +import com.fr.report.LockedException; import com.fr.report.UnLockedException; +import com.fr.workspace.base.UserInfo; import com.fr.workspace.exception.DiskSpaceFullException; import com.fr.report.InconsistentLockException; import java.awt.Frame; @@ -89,6 +92,25 @@ public class SaveFailureHandler implements ThrowableHandler { } }, + LOCKED { + @Override + public boolean process(Throwable e) { + LockedException exception = null; + if (e.getCause() instanceof LockedException) { + exception = (LockedException) e.getCause(); + } + if (e instanceof LockedException) { + exception = (LockedException) e; + } + if (exception != null) { + UserInfo userInfo = exception.getUserInfo(); + LockInfoDialog.show(userInfo); + return true; + } + return false; + } + }, + Other { @Override public boolean process(Throwable e) { diff --git a/designer-realize/src/main/java/com/fr/design/mainframe/socketio/DesignerSocketIO.java b/designer-realize/src/main/java/com/fr/design/mainframe/socketio/DesignerSocketIO.java index 3ceef3ae28..f5b86fd454 100644 --- a/designer-realize/src/main/java/com/fr/design/mainframe/socketio/DesignerSocketIO.java +++ b/designer-realize/src/main/java/com/fr/design/mainframe/socketio/DesignerSocketIO.java @@ -2,15 +2,13 @@ package com.fr.design.mainframe.socketio; import com.fr.config.RemoteConfigEvent; import com.fr.design.DesignerEnvManager; -import com.fr.design.EnvChangeEntrance; -import com.fr.design.dialog.FineJOptionPane; +import com.fr.design.RPCConnectHandlerCenter; import com.fr.design.env.DesignerWorkspaceInfo; import com.fr.design.env.DesignerWorkspaceInfoContext; import com.fr.design.gui.ilable.UILabel; import com.fr.design.i18n.LocaleLinkProvider; import com.fr.design.i18n.Toolkit; import com.fr.design.layout.FRGUIPaneFactory; -import com.fr.design.mainframe.DesignerContext; import com.fr.design.mainframe.loghandler.DesignerLogger; import com.fr.design.mainframe.share.ui.base.MouseClickListener; import com.fr.design.mainframe.toast.DesignerToastMsgUtil; @@ -21,18 +19,11 @@ import com.fr.event.EventDispatcher; import com.fr.log.FineLoggerFactory; import com.fr.serialization.SerializerHelper; import com.fr.stable.ArrayUtils; -import com.fr.stable.StableUtils; import com.fr.third.apache.logging.log4j.core.LogEvent; -import com.fr.third.org.apache.http.client.config.RequestConfig; -import com.fr.third.org.apache.http.client.methods.CloseableHttpResponse; -import com.fr.third.org.apache.http.client.methods.HttpGet; -import com.fr.third.org.apache.http.impl.client.CloseableHttpClient; -import com.fr.third.org.apache.http.impl.client.HttpClients; import com.fr.workspace.WorkContext; import com.fr.workspace.Workspace; import com.fr.workspace.base.WorkspaceConstants; import com.fr.workspace.connect.WorkspaceConnectionInfo; -import com.fr.workspace.engine.exception.WorkspaceConnectionException; import com.fr.workspace.server.socket.CustomLogEvent; import com.fr.workspace.server.socket.LogEventConverter; import io.socket.client.IO; @@ -40,9 +31,7 @@ import io.socket.client.Socket; import io.socket.emitter.Emitter; import javax.swing.BorderFactory; -import javax.swing.JOptionPane; import javax.swing.JPanel; -import javax.swing.UIManager; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Cursor; @@ -76,7 +65,6 @@ public class DesignerSocketIO { private static Status status = Status.Disconnected; private static Timer disConnectHintTimer = null; private static long disConnectHintTimerDelay = 3000; - private static final int TIMEOUT = 5000; //维护一个当前工作环境的websocket连接信息列表 private static ConnectionInfo[] connectionInfos; //维护一个关于uri列表的计数器 @@ -205,10 +193,10 @@ public class DesignerSocketIO { }; private static void dealWithSocketDisconnect() { - if (checkRPCConnect()) { + if (RPCConnectHandlerCenter.checkRPCConnect(getConnectionInfo())) { showSocketDisconnectToast(); } else { - showRPCDisconnectDialog(); + RPCConnectHandlerCenter.showRPCDisconnectDialog(); } } @@ -245,62 +233,6 @@ public class DesignerSocketIO { return jPanel; } - private static void showRPCDisconnectDialog() { - UIUtil.invokeLaterIfNeeded(new Runnable() { - @Override - public void run() { - FineJOptionPane.showMessageDialog( - DesignerContext.getDesignerFrame(), - Toolkit.i18nText("Fine-Design_Basic_Remote_Disconnected"), - UIManager.getString("OptionPane.messageDialogTitle"), - JOptionPane.ERROR_MESSAGE, - UIManager.getIcon("OptionPane.errorIcon")); - EnvChangeEntrance.getInstance().chooseEnv(); - } - }); - } - - private static boolean checkRPCConnect() { - CloseableHttpClient httpclient = HttpClients.createDefault(); - WorkspaceConnectionInfo info = getConnectionInfo(); - HttpGet httpGet = new HttpGet(StableUtils.pathJoin(info.getUrl(), WorkspaceConstants.CONTROLLER_PREFIX, WorkspaceConstants.VT)); - RequestConfig requestConfig = RequestConfig - .custom() - .setConnectTimeout(TIMEOUT) - .setConnectionRequestTimeout(TIMEOUT) - .build(); - httpGet.setConfig(requestConfig); - try { - CloseableHttpResponse response = httpclient.execute(httpGet); - if (isErrorStatus(response.getStatusLine().getStatusCode())) { - //这边nginx做负载,服务器被kill掉,返回的是502,不会抛错,导致checkRPCConnect通过 - //针对500-600的错误码加个判断,其他类型的状态码暂不考虑,如果有遇到再处理,不然怕影响范围大 - throw new WorkspaceConnectionException("Response " + response.getStatusLine().toString()); - } - } catch (Exception e) { - FineLoggerFactory.getLogger().error(e, e.getMessage()); - return false; - } - return true; - } - - /** - * 错误状态码 - * 5xx(服务器错误)这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错。代码 说明 - * 500 (服务器内部错误) 服务器遇到错误,无法完成请求。 - * 501 (尚未实施) 服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码。 - * 502 (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。 - * 503 (服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。 - * 504 (网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求。 - * 505 (HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本。 - * - * @param status 错误状态码 - * @return 是否是错误状态码 - */ - private static boolean isErrorStatus(int status) { - return status >= 500 && status <= 600; - } - //配置变更监听器 private static final Emitter.Listener modifyConfig = new Emitter.Listener() { @Override diff --git a/designer-realize/src/main/java/com/fr/start/module/DesignerActivator.java b/designer-realize/src/main/java/com/fr/start/module/DesignerActivator.java index 0a7b3c6e73..15edc3e095 100644 --- a/designer-realize/src/main/java/com/fr/start/module/DesignerActivator.java +++ b/designer-realize/src/main/java/com/fr/start/module/DesignerActivator.java @@ -17,6 +17,7 @@ import com.fr.decision.webservice.v10.plugin.helper.category.impl.PluginResource import com.fr.decision.webservice.v10.plugin.helper.category.impl.UpmResourceLoader; import com.fr.design.DesignerEnvManager; import com.fr.design.ExtraDesignClassManager; +import com.fr.design.RPCConnectHandlerCenter; import com.fr.design.actions.NewFormAction; import com.fr.design.actions.core.ActionFactory; import com.fr.design.actions.insert.cell.BiasCellAction; @@ -51,6 +52,7 @@ import com.fr.design.javascript.EmailPane; import com.fr.design.javascript.JavaScriptImplPane; import com.fr.design.javascript.ParameterJavaScriptPane; import com.fr.design.javascript.ProcessTransitionAdapter; +import com.fr.design.lock.TemplateLockInfoReSave; import com.fr.design.login.DesignerLoginType; import com.fr.design.login.guide.DesignerGuideHelper; import com.fr.design.login.message.DesignerMessageHelper; @@ -214,6 +216,8 @@ public class DesignerActivator extends Activator implements Prepare { AlphaFineHelper.switchConfig4Locale(); RecoverManager.register(new RecoverForDesigner()); WidgetThemeListenerStarter.start(); + TemplateLockInfoReSave.startListener(); + RPCConnectHandlerCenter.startListener(); }, DesignerStartupPool.common()); CompletableFuture resourcePrepare = CompletableFuture.runAsync(() -> { @@ -537,6 +541,8 @@ public class DesignerActivator extends Activator implements Prepare { public void stop() { unloadLogAppender(); DesignerSocketIO.close(); + TemplateLockInfoReSave.stopListener(); + RPCConnectHandlerCenter.stopListener(); } @Override