From a1fdd0b66ec6477ef8cece6d5225b1c859ae964e Mon Sep 17 00:00:00 2001 From: hzzz Date: Tue, 29 May 2018 15:36:16 +0800 Subject: [PATCH] merge --- .../loghandler/socketio/DesignerSocketIO.java | 4 +- .../RemoteDesignAuthorityManagerAction.java | 7 +- .../src/com/fr/design/utils/DesignUtils.java | 105 +- .../fr/env/DesignAuthorityConfigurable.java | 2 +- designer-base/src/com/fr/env/RemoteEnv.java | 1350 ++++++++++++++++- .../src/com/fr/env/RemoteEnvUtils.java | 2 +- designer-base/src/com/fr/env/SignIn.java | 2 +- .../AccessibleBodyWatermarkEditor.java | 8 + .../actions/report/ReportWatermarkAction.java | 8 + 9 files changed, 1369 insertions(+), 119 deletions(-) diff --git a/designer-base/src/com/fr/design/mainframe/loghandler/socketio/DesignerSocketIO.java b/designer-base/src/com/fr/design/mainframe/loghandler/socketio/DesignerSocketIO.java index 2444ae3d1..14be4cfb1 100644 --- a/designer-base/src/com/fr/design/mainframe/loghandler/socketio/DesignerSocketIO.java +++ b/designer-base/src/com/fr/design/mainframe/loghandler/socketio/DesignerSocketIO.java @@ -47,7 +47,7 @@ public class DesignerSocketIO { static { EventDispatcher.listen(EnvEvents.AFTER_SIGN_OUT, new Listener() { @Override - public void on(Event event, Null param) { + public void on(Event event, Null param) { if (socketIO.isPresent()) { socketIO.get().close(); socketIO = Optional.absent(); @@ -56,7 +56,7 @@ public class DesignerSocketIO { }); EventDispatcher.listen(EnvEvents.AFTER_SIGN_IN, new Listener() { @Override - public void on(Event event, Null param) { + public void on(Event event, Null param) { updateSocket(); } }); diff --git a/designer-base/src/com/fr/design/remote/action/RemoteDesignAuthorityManagerAction.java b/designer-base/src/com/fr/design/remote/action/RemoteDesignAuthorityManagerAction.java index 39d5921d6..da2122cb4 100644 --- a/designer-base/src/com/fr/design/remote/action/RemoteDesignAuthorityManagerAction.java +++ b/designer-base/src/com/fr/design/remote/action/RemoteDesignAuthorityManagerAction.java @@ -48,11 +48,8 @@ public class RemoteDesignAuthorityManagerAction extends UpdateAction { public void doOk() { DesignAuthority[] authorities = managerPane.update(); if (!FRContext.getCurrentEnv().isLocalEnv()) { - try { - ((RemoteEnv) FRContext.getCurrentEnv()).updateAuthorities(authorities); - } catch (Exception exception) { - FRContext.getLogger().error(exception.getMessage()); - } + boolean success = ((RemoteEnv) FRContext.getCurrentEnv()).updateAuthorities(authorities); + FRContext.getLogger().info("update remote design authority: " + success); } } diff --git a/designer-base/src/com/fr/design/utils/DesignUtils.java b/designer-base/src/com/fr/design/utils/DesignUtils.java index 469e7fd1a..25be2df48 100644 --- a/designer-base/src/com/fr/design/utils/DesignUtils.java +++ b/designer-base/src/com/fr/design/utils/DesignUtils.java @@ -1,11 +1,11 @@ package com.fr.design.utils; import com.fr.base.BaseUtils; -import com.fr.base.ServerConfig; import com.fr.base.Env; import com.fr.base.EnvException; import com.fr.base.FRContext; import com.fr.base.FeedBackInfo; +import com.fr.base.ServerConfig; import com.fr.base.Utils; import com.fr.base.remote.RemoteDeziConstants; import com.fr.dav.DavXMLUtils; @@ -23,6 +23,7 @@ import com.fr.general.FRLogger; import com.fr.general.GeneralContext; import com.fr.general.Inter; import com.fr.general.http.HttpClient; +import com.fr.security.JwtUtils; import com.fr.stable.ArrayUtils; import com.fr.stable.CodeUtils; import com.fr.stable.EncodeConstants; @@ -30,8 +31,11 @@ import com.fr.stable.StableUtils; import com.fr.stable.StringUtils; import com.fr.start.StartServer; -import javax.swing.*; -import java.awt.*; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import java.awt.Desktop; +import java.awt.Font; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; @@ -74,7 +78,8 @@ public class DesignUtils { /** * 通过端口是否被占用判断设计器有没有启动 - * s + * s + * * @return 启动了返回true */ public static boolean isStarted() { @@ -120,11 +125,12 @@ public class DesignUtils { } /** - *建立监听端口 - * @param startPort 端口 - * @param suffixs 文件后缀 + * 建立监听端口 + * + * @param startPort 端口 + * @param suffixs 文件后缀 */ - public static void creatListeningServer(final int startPort,final String[] suffixs) { + public static void creatListeningServer(final int startPort, final String[] suffixs) { Thread serverSocketThread = new Thread() { public void run() { ServerSocket serverSocket = null; @@ -146,7 +152,7 @@ public class DesignUtils { String path = f.getAbsolutePath(); boolean isMatch = false; - for(int i= 0; i threadLocal = new ThreadLocal<>(); + private boolean isReadTimeOut = false; private String buildFilePath; - private final RemoteEnvConfig env; + + public RemoteEnv() { + this.clock = new Clock(this); + } public RemoteEnv(String path, String userName, String password) { - env = new RemoteEnvConfig(path, userName, password); + this(); + this.path = path; + this.user = userName; + this.password = password; } /** @@ -85,7 +105,11 @@ public class RemoteEnv extends AbstractEnv implements DesignAuthorityConfigurabl */ @Override public String getPath() { - return env.getPath(); + return this.path; + } + + public void setPath(String s) { + this.path = s; } /** @@ -93,16 +117,76 @@ public class RemoteEnv extends AbstractEnv implements DesignAuthorityConfigurabl */ @Override public String getUser() { - return env.getUsername(); + return user; } + public void setUser(String user) { + this.user = user; + clearUserID(); + } public String getPassword() { - return env.getPassword(); + return password; } + /** + * 修复密码中包含特殊字符,无法登录的问题 + * + * @return + */ + private String getEncodedPassword() { + try { + return URLEncoder.encode(password, "UTF-8"); + } catch (UnsupportedEncodingException e) { + return password; + } + } + + public void setPassword(String password) { + this.password = password; + clearUserID(); + } - private HttpClient createHttpMethod(HashMap para) throws EnvException, UnsupportedEncodingException { + public Clock getClock() { + return this.clock; + } + + public void setClock(Clock clock) { + this.clock = clock; + } + + private void clearUserID() { + this.userID = null; + } + + public void setThreadLocal(String value) { + synchronized (this) { + threadLocal.set(value); + } + + } + + public String getThreadLocal() { + return threadLocal.get(); + } + + /** + * 所有与服务器端交互前,都要调用这个方法生成UserID + */ + private String createUserID() throws EnvException { + // 如果登录之后userID还是null + if (this.userID == null) { + if (!VT4FR.RemoteDesign.support() && licNotSupport <= 0) { + licNotSupport++; + JOptionPane.showMessageDialog(null, Inter.getLocText("FR-Lic_does_not_Support_Remote")); + } + throw new EnvException(Inter.getLocText("Env-Invalid_User_and_Password")); + } + + return this.userID; + } + + private HttpClient createHttpMethod(HashMap para) throws EnvException { return createHttpMethod(para, false); } @@ -110,9 +194,9 @@ public class RemoteEnv extends AbstractEnv implements DesignAuthorityConfigurabl * 根据nameValuePairs,也就是参数对,生成PostMethod */ private HttpClient createHttpMethod(HashMap para, boolean isSignIn) throws EnvException { - String methodPath = getPath(); + String methodPath = this.path; if (!isSignIn) { - methodPath = methodPath + "?id=" + EnvContext.currentToken(); + methodPath = methodPath + "?id=" + createUserID(); } HttpClient client = new HttpClient(methodPath, para); /* @@ -129,14 +213,10 @@ public class RemoteEnv extends AbstractEnv implements DesignAuthorityConfigurabl * 根据nameValuePairs,也就是参数对,生成PostMethod,不同之处在于,参数拼在path后面,不是method.addParameters */ private HttpClient createHttpMethod2(HashMap para) throws EnvException, UnsupportedEncodingException { - String methodPath = getPath() + '?' + "id=" + createUserID(); + String methodPath = path + '?' + "id=" + createUserID(); return new HttpClient(methodPath); } - private String createUserID() { - return EnvContext.currentToken(); - } - /* * Read the response body. @@ -192,6 +272,17 @@ public class RemoteEnv extends AbstractEnv implements DesignAuthorityConfigurabl } } + private void doWithTimeOutException() { + boolean isNotNeedTip = ComparatorUtils.equals(getThreadLocal(), "HEART_BEAT") || ComparatorUtils.equals(getThreadLocal(), "LOG_MESSAGE"); + if (!isReadTimeOut && !isNotNeedTip) { + isReadTimeOut = true; + JOptionPane.showMessageDialog(DesignerContext.getDesignerFrame(), Inter.getLocText(new String[]{"Data", "read_time_out"})); + isReadTimeOut = false; + } + FRContext.getLogger().info("Connection reset "); + } + + /** * nameValuePairs,这个参数要接着this.path,拼成一个URL,否则服务器端req.getParameter是无法得到的 * @@ -240,28 +331,127 @@ public class RemoteEnv extends AbstractEnv implements DesignAuthorityConfigurabl return sb.toString(); } + /** + * 测试连接服务器 + * + * @return 测试连接成功返回true + * @throws Exception 异常 + */ public boolean testServerConnection() throws Exception { - throw new UnsupportedOperationException("unsupport now"); -// return testConnection(true, true, DesignerContext.getDesignerFrame()); + return testConnection(true, true, DesignerContext.getDesignerFrame()); } + /** + * 测试当前配置是否正确 + * + * @return 链接是否成功 + * @throws Exception 异常 + */ @Override public boolean testServerConnectionWithOutShowMessagePane() throws Exception { - throw new UnsupportedOperationException("unsupport now"); + return testConnection(false, true, DesignerContext.getDesignerFrame()); } + /** + * 主要用于在环境配置面板中的测试连接按钮时,不要注册进远程环境 + * + * @param messageParentPane 弹框的依赖的面板 + * @return 是否测试连接成功 + * @throws Exception 异常 + */ public boolean testConnectionWithOutRegisteServer(Component messageParentPane) throws Exception { - throw new UnsupportedOperationException("unsupport now"); + return testConnection(true, false, messageParentPane); + } + + + private boolean testConnection(boolean needMessage, boolean isRegisteServer, Component parentComponent) throws Exception { + checkArgument(parentComponent instanceof Component, "parentComponent should be a java.awt.component"); + Component component = parentComponent; + String url = String.format("%s/connection", EnvConstants.toDecisionPath(getPath())); + ImmutableMap params = ImmutableMap.of( + "version", (Object) ProductConstants.DESIGNER_VERSION + ); + ImmutableMap headers = ImmutableMap.of( + EnvConstants.USERNAME, getUser(), + EnvConstants.PWD, getPassword()); + String res = HttpToolbox.post(url, params, headers); + if (res == null) { + if (needMessage) { + JOptionPane.showMessageDialog(component, Inter.getLocText("Datasource-Connection_failed")); + } + return false; + } else if (ComparatorUtils.equals(res, "true")) { + return true; + } else { + if (ComparatorUtils.equals(res, EnvConstants.AUTH_ERROR)) { + JOptionPane.showMessageDialog(component, + Inter.getLocText(new String[]{"Datasource-Connection_failed", "Registration-User_Name", "Password", "Error"}, new String[]{",", "", "", "!"}) + , Inter.getLocText("FR-Server-All_Error"), JOptionPane.ERROR_MESSAGE); + return false; + } else { + if (ComparatorUtils.equals(res, EnvConstants.WAR_ERROR)) { + if (needMessage) { + JOptionPane.showMessageDialog(component, Inter.getLocText(new String[]{"Datasource-Connection_failed", "NS-war-remote"}, new String[]{",", "!"})); + } else { + FineLoggerFactory.getLogger().info(Inter.getLocText(new String[]{"Datasource-Connection_failed", "NS-war-remote"}, new String[]{",", "!"})); + } + return false; + } else { + if (needMessage) { + JOptionPane.showMessageDialog(component, Inter.getLocText(new String[]{"Datasource-Connection_failed", "Version-does-not-support"}, new String[]{",", "!"})); + } else { + FineLoggerFactory.getLogger().info(Inter.getLocText(new String[]{"Datasource-Connection_failed", "Version-does-not-support"}, new String[]{",", "!"})); + } + return false; + } + } + } + } + + private void extraChangeEnvPara() { + //在env连接之前, 加载一下不依赖env的插件. 看看需不需要改变参数. + DesignerEnvProcessor envProcessor = ExtraDesignClassManager.getInstance().getSingle(DesignerEnvProcessor.XML_TAG); + if (envProcessor != null) { + this.path = envProcessor.changeEnvPathBeforeConnect(user, password, path); + } } private void setHttpsParas() { - if (getPath().startsWith(HTTPS_PREFIX) && System.getProperty(CERT_KEY) == null) { + if (path.startsWith(HTTPS_PREFIX) && System.getProperty(CERT_KEY) == null) { DesignerEnvManager envManager = DesignerEnvManager.getEnvManager(); System.setProperty(CERT_KEY, envManager.getCertificatePath()); System.setProperty(PWD_KEY, envManager.getCertificatePass()); } } + + /** + * 心跳访问,用来更新当前用户的访问时间 + * + * @throws Exception e + */ + public void heartBeatConnection() throws Exception { + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "heart_beat"); + para.put("user", user); + + HttpClient client = createHttpMethod(para); + execute4InputStream(client); + + //这做法不好, 30秒刷一次, 刷新的时候会重新构建树, 构建完会把子节点都收缩起来, 效果太差. + //为什么不保存刷新前树的伸缩状态, 因为刷新后的树和刷新前的树的结构未必是一致的. + + //服务器通知客户端更新左上角文件树面板 +// try { +// if (ComparatorUtils.equals(stream2String(execute4InputStream(method)), "true")) { +// DesignerFrameFileDealerPane.getInstance().refresh(); +// } +// } catch (Exception e) { +// FRContext.getLogger().error(e.getMessage()); +// } + } + /** * 返回描述该运行环境的名字 * @@ -272,6 +462,193 @@ public class RemoteEnv extends AbstractEnv implements DesignAuthorityConfigurabl return Inter.getLocText("Env-Remote_Server"); } + /** + * 登录,返回userID + */ + @Override + public void signIn() throws Exception { + throw new UnsupportedOperationException("unsupport now"); + } + + + private void startLogTimer() { + if (logTimer != null) { + logTimer.cancel(); + } + + logTimer = new Timer(); + logTimer.schedule(new TimerTask() { + @Override + public void run() { + try { + RemoteEnv.this.setThreadLocal("LOG_MESSAGE"); + FRContext.getCurrentEnv().printLogMessage(); + } catch (Exception e) { + FRContext.getLogger().info(e.getMessage()); + } + } + }, 10000, 10000); + } + + private void stopLogTimer() { + if (logTimer != null) { + logTimer.cancel(); + logTimer = null; + } + } + + /** + * 根据userID sign out + * + * @return 成功签出返回true + * @throws Exception e + */ + @Override + public boolean signOut() throws Exception { + if (userID == null) { + return true; + } + stopLogTimer(); + // richer:登出的时候就把定时发送的时钟停掉 + clock.stop(); + // richer:把轮训使用的定时器也去掉 + timer.cancel(); + + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "r_sign_out"); + para.put("id", userID); + + return simulaRPC(para, false + ); + } + + protected boolean simulaRPC(HashMap para, boolean isSignIn) throws Exception { + HttpClient client = createHttpMethod(para, isSignIn); + + // execute method取到input stream再转成String + String resJSON = null; + try { + resJSON = stream2String(execute4InputStream(client)); + } catch (Exception e) { + FRContext.getLogger().error(e.getMessage()); + } + + if (resJSON == null) { + return false; + } + if (resJSON.contains("RegistEditionException")) { + JOptionPane.showMessageDialog(null, Inter.getLocText("FR-Lic_does_not_Support_Remote")); + return false; + } + try { + JSONObject jo = new JSONObject(resJSON); + + if (isSignIn) { + if (jo.has("id")) { + userID = jo.getString("id"); + } + if (jo.has("isRoot")) { + isRoot = jo.getBoolean("isRoot"); + } + + if (userID != null) { + return true; + } + } else { + if (jo.has("res")) { + return jo.getBoolean("res"); + } + } + String exception = jo.getString("exp"); + if (exception != null) { + throw new EnvException(exception); + } + } catch (JSONException je) { + // 返回的resJSON不是JSON格式的,那就直接返回resJSON作为userID + return true; + } + + return true; + } + + protected boolean doLockOperation(String[] filePathes, String cmd) throws Exception { + if (filePathes == null || filePathes.length == 0) { + return true; + } + + JSONArray ja = new JSONArray(filePathes); + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", cmd); + para.put("pathes", ja.toString()); + + return simulaRPC(para, false); + } + + /** + * 取路径filePath下面文件的lock + *

+ * 处于同一个原子操作,要么拿到所有的锁,要么一个锁也没有拿到 + */ + public boolean getLock(String[] filePathes) throws Exception { + return doLockOperation(filePathes, "design_get_lock"); + } + + /** + * 解锁文件 + * + * @param filePathes 文件路径 + * @return 成功解锁返回true + * @throws Exception e + */ + public boolean releaseLock(String[] filePathes) throws Exception { + return doLockOperation(filePathes, "design_release_lock"); + } + + /** + * 当前Env下,tplPath目录下是否存在模板 + * + * @param reportPath 路径 + * @return 是否存在 + */ + @Override + public boolean isTemplateExist(String reportPath) throws Exception { + if (reportPath == null) { + return false; + } + + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "design_report_exist"); + para.put("report_path", reportPath); + + HttpClient client = createHttpMethod(para); + InputStream input = execute4InputStream(client); + + return ComparatorUtils.equals(stream2String(input), "true"); + } + + /** + * 解锁当前模板,用于远程设计。当远程设计某张模板 时,在解锁之前改模板处于锁定状态 + * + * @param tplPath 路径 + * @throws Exception e + */ + @Override + public void unlockTemplate(String tplPath) throws Exception { + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "design_close_report"); + para.put(RemoteDeziConstants.TEMPLATE_PATH, tplPath); + HttpClient client = createHttpMethod(para); + + InputStream input = execute4InputStream(client); + String info = Utils.inputStream2String(input, EncodeConstants.ENCODING_UTF_8); + + FRContext.getLogger().error(info); + } + public class Bytes2ServerOutputStream extends OutputStream { private ByteArrayOutputStream out = new ByteArrayOutputStream(); private HashMap nameValuePairs; @@ -329,14 +706,287 @@ public class RemoteEnv extends AbstractEnv implements DesignAuthorityConfigurabl } } + /** + * 测试数据连接是否能够正确的连接上 + * + * @param database 数据连接 + * @return 如果能正确的连接到数据库则返回true + * @throws Exception 无法正确连接到数据库则抛出此异常 + * TODO alex_ENV 个人以为,这里应该是测试所有Connection的连接,所以Connection与TableData接口的关联需要思考 + */ + @Override + public boolean testConnection(com.fr.data.impl.Connection database) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + // 把database写成xml文件到out + DavXMLUtils.writeXMLFileDatabaseConnection(database, out); + + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "design_test_con"); + + InputStream input = postBytes2ServerB(out.toByteArray(), para); + + if (input == null) { + return false; + } + + return Boolean.valueOf(IOUtils.inputStream2String(input, EncodeConstants.ENCODING_UTF_8)); + } + @Override - public boolean updateAuthorities(DesignAuthority[] authorities) throws Exception { + public boolean updateAuthorities(DesignAuthority[] authorities) { return RemoteEnvUtils.updateAuthorities(authorities, this); } @Override public DesignAuthority[] getAuthorities() { + return RemoteEnvUtils.getAuthorities(this); + + } + + /** + * ben:取schema + */ + @Override + public String[] getTableSchema(com.fr.data.impl.Connection database) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + DavXMLUtils.writeXMLFileDatabaseConnection(database, out); + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "design_get_schema"); + InputStream input = postBytes2ServerB(out.toByteArray(), para); + if (input == null) { + return null; + } + return DavXMLUtils.readXMLFileSchema(input); + } + + /** + * b:分别取Table,View,Procedure,实际应用时更有意义 + */ + @Override + public TableProcedure[] getTableProcedure(com.fr.data.impl.Connection database, String type, String schema) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + DavXMLUtils.writeXMLFileDatabaseConnection(database, out); + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "design_get_tables"); + para.put("__type__", type); + para.put("__dbschema__", schema); + InputStream input = postBytes2ServerB(out.toByteArray(), para); + if (input == null) { + return new TableProcedure[0]; + } + return DavXMLUtils.readXMLSQLTables(input); + } + + @Override + public List getProcedures(com.fr.data.impl.Connection datasource, String[] schemas, boolean isOracle, boolean isOracleSysSpace) throws Exception { + HashMap schemaTableProcedureMap = new HashMap(); + List sqlTableObjs = new ArrayList(); + TableProcedure[] sqlTableObj = null; + int len = schemas.length; + if (len > 0) { + for (int i = 0; i < len; i++) { + String schema = schemas[i]; + sqlTableObj = this.getTableProcedure(datasource, TableProcedure.PROCEDURE, schema); + if (sqlTableObj == null) { + sqlTableObj = new TableProcedure[0]; + } + sqlTableObjs.add(sqlTableObj); + schemaTableProcedureMap.put(schema, sqlTableObj); + } + } else { + sqlTableObj = this.getTableProcedure(datasource, TableProcedure.PROCEDURE, null); + if (sqlTableObj == null) { + sqlTableObj = new TableProcedure[0]; + } + sqlTableObjs.add(sqlTableObj); + schemaTableProcedureMap.put(null, sqlTableObj); + } + DataCoreUtils.putProcedureMap(datasource, schemaTableProcedureMap); + return sqlTableObjs; + } + + /** + * 在当前路径下新建文件夹 + * + * @param folderPath 文件名 + * @return 成功创建返回true + * @throws Exception e + */ + @Override + public boolean createFolder(String folderPath) throws Exception { + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "design_create_folder"); + para.put("folder_path", folderPath); + + HttpClient client = createHttpMethod(para); + InputStream input = execute4InputStream(client); + + if (input == null) { + return false; + } + + return Boolean.valueOf(IOUtils.inputStream2String(input, EncodeConstants.ENCODING_UTF_8)); + } + + /** + * 新建一个文件 + * + * @param filePath :目标文件相对路径 + * @return 成功新建返回true + * @throws Exception e + */ + @Override + public boolean createFile(String filePath) throws Exception { + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "design_create_file"); + para.put("file_path", filePath); + + HttpClient client = createHttpMethod(para); + InputStream input = execute4InputStream(client); + + if (input == null) { + return false; + } + + return Boolean.valueOf(IOUtils.inputStream2String(input, EncodeConstants.ENCODING_UTF_8)); + } + + @Override + public boolean renameFile(String newPath, String oldPath) throws Exception { + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "design_rename_file"); + para.put("newPath", newPath); + para.put("oldPath", oldPath); + + HttpClient client = createHttpMethod(para); + InputStream input = execute4InputStream(client); + + if (input == null) { + return false; + } + + return Boolean.valueOf(IOUtils.inputStream2String(input, EncodeConstants.ENCODING_UTF_8)); + } + + /** + * 判断文件是否存在 + * + * @param filePath :目标文件相对路径 + * @return 文件是否存在 + * @throws Exception e + */ + @Override + public boolean fileExists(String filePath) throws Exception { + if (filePath == null) { + return false; + } + + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "design_file_exists"); + para.put("file_path", filePath); + + HttpClient client = createHttpMethod(para); + InputStream input = execute4InputStream(client); + + if (input == null) { + return false; + } + + return Boolean.valueOf(IOUtils.inputStream2String(input, EncodeConstants.ENCODING_UTF_8)); + } + + /** + * 判断文件是否锁住 + * + * @param filePath 文件路径 + * @return 文件被锁住了,返回true + * @throws Exception e + */ + @Override + public boolean fileLocked(String filePath) throws Exception { + if (filePath == null) { + return false; + } + + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "design_file_locked"); + para.put("file_path", filePath); + + HttpClient client = createHttpMethod(para); + InputStream input = execute4InputStream(client); + + if (input == null) { + return false; + } + + return Boolean.valueOf(IOUtils.inputStream2String(input, EncodeConstants.ENCODING_UTF_8)); + } + + + /** + * 注册环境,用于检测是否启动定时器,主要用于本地环境来监测远程 + * + * @param env 用户环境 + */ + @Override + public void registerUserEnv(UserBaseEnv env) { + } + + /** + * 用于检测用户环境 + * ,启动定时器 + */ + @Override + public void startUserCheckTimer() { + } + + + /** + * 停止定时器 + */ + public void stopUserCheckTimer() { + } + + /** + * 删除文件 + * + * @param filePath 文件地址 + * @return 删除成功返回true + */ + @Override + public boolean deleteFile(String filePath) { + if (filePath == null) { + return false; + } + try { + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "delete_file"); + para.put("file_path", filePath); + + HttpClient client = createHttpMethod(para); + InputStream input = execute4InputStream(client); + + if (input == null) { + return false; + } + + return Boolean.valueOf(IOUtils.inputStream2String(input, EncodeConstants.ENCODING_UTF_8)); + } catch (Exception e) { + FRContext.getLogger().error(e.getMessage()); + } + return false; } /** @@ -351,8 +1001,8 @@ public class RemoteEnv extends AbstractEnv implements DesignAuthorityConfigurabl HashMap para = new HashMap<>(); para.put("op", "fr_remote_design"); para.put("cmd", "write_privilege_map"); - para.put("current_user", getUser()); - para.put("current_password", this.getPassword()); + para.put("current_user", this.user); + para.put("current_password", this.password); para.put("key", key); para.put("value", value); @@ -404,6 +1054,168 @@ public class RemoteEnv extends AbstractEnv implements DesignAuthorityConfigurabl } } + /** + * 列出WEB-INF目录下指定路径的文件夹与文件 + * + * @param rootFilePath 指定目录 + * @return WEB-INF目录下指定路径的文件夹与文件 + * @throws Exception e + */ + @Override + public FileNode[] listFile(String rootFilePath) throws Exception { + return listFile(rootFilePath, false); + } + + /** + * 列出WEB-INF上层目录下指定路径的文件夹与文件 + * + * @param rootFilePath 指定目录 + * @return WEB-INF上层目录下指定路径的文件夹与文件 + * @throws Exception e + */ + @Override + public FileNode[] listReportPathFile(String rootFilePath) throws Exception { + return listFile(rootFilePath, true); + } + + private FileNode[] listFile(String rootFilePath, boolean isWebReport) throws Exception { + FileNode[] fileNodes; + + HashMap para = new HashMap<>(); + para.put("op", "fs_remote_design"); + para.put("cmd", "design_list_file"); + para.put("file_path", rootFilePath); + para.put("currentUserName", this.getUser()); + para.put("currentUserId", this.createUserID()); + para.put("isWebReport", isWebReport ? "true" : "false"); + + HttpClient client = createHttpMethod(para); + InputStream input = execute4InputStream(client); + + if (input == null) { + return new FileNode[0]; + } + + // 远程环境下左侧目录树暂不需要打开xlsx,xls文件 + fileNodes = DavXMLUtils.readXMLFileNodes(input); + ArrayList al = new ArrayList<>(); + for (int i = 0; i < fileNodes.length; i++) { + al.add(fileNodes[i]); + } + + FileNode[] fileNodes2 = new FileNode[al.size()]; + for (int i = 0; i < al.size(); i++) { + fileNodes2[i] = al.get(i); + } + + return fileNodes2; + } + + + /** + * 列出目标目录下所有cpt文件或文件夹 + * + * @param rootFilePath 指定目录 + * @return 列出目标目录下所有cpt文件或文件夹 + */ + @Override + public FileNode[] listCpt(String rootFilePath) { + return listCpt(rootFilePath, false); + } + + /** + * 列出目标目录下所有cpt文件或文件夹 + * + * @param rootFilePath 指定目录 + * @param recurse 是否递归查找其子目录 + * @return 列出目标目录下所有cpt文件或文件夹 + * @throws Exception e + */ + @Override + public FileNode[] listCpt(String rootFilePath, boolean recurse) { + List fileNodeList = new ArrayList<>(); + try { + listAll(rootFilePath, fileNodeList, new String[]{"cpt"}, recurse); + } catch (Exception e) { + FRContext.getLogger().error(e.getMessage(), e); + } + return fileNodeList.toArray(new FileNode[fileNodeList.size()]); + } + + private void listAll(String rootFilePath, List nodeList, String[] fileTypes, boolean recurse) throws Exception { + FileNode[] fns = listFile(rootFilePath); + for (FileNode fileNode : fns) { + if (isAcceptFileType(fileNode, fileTypes)) { + nodeList.add(fileNode); + } else if (fileNode.isDirectory()) { + if (recurse) { + listAll(rootFilePath + File.separator + fileNode.getName(), nodeList, fileTypes, true); + } else { + nodeList.add(fileNode); + } + } + } + } + + private boolean isAcceptFileType(FileNode fileNode, String[] fileTypes) { + for (String fileType : fileTypes) { + if (fileNode.isFileType(fileType)) { + return true; + } + } + return false; + } + + /** + * 获取指定数据集的参数 + * + * @param tableData 数据集 + * @return 数据集的参数 + * @throws Exception 获取参数失败则抛出此异常 + */ + @Override + public Parameter[] getTableDataParameters(TableData tableData) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + //把tableData写成xml文件到out + DavXMLUtils.writeXMLFileTableData(tableData, out); + + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "design_td_pars"); + InputStream input = postBytes2ServerB(out.toByteArray(), para); + + if (input == null) { + return new Parameter[0]; + } + return DavXMLUtils.readXMLParameters(input); + } + + + /** + * 获取存储过程中的参数 + * + * @param storeProcedure 存储过程 + * @return 返回存储过程中的所有参数组成的数组 + * @throws Exception 如果获取参数失败则抛出此异常 + */ + @Override + public Parameter[] getStoreProcedureParameters(StoreProcedure storeProcedure) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + // 把tableData写成xml文件到out + DavXMLUtils.writeXMLFileStoreProcedureAndSource(storeProcedure, out); + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "design_sp_pars"); + InputStream input = postBytes2ServerB(out.toByteArray(), para); + + if (input == null) { + return new Parameter[0]; + } + return DavXMLUtils.readXMLParameters(input); + } + @Override public EmbeddedTableData previewTableData(Object tableData, Map parameterMap, int rowCount) throws Exception { return previewTableData(null, tableData, parameterMap, rowCount); @@ -485,6 +1297,90 @@ public class RemoteEnv extends AbstractEnv implements DesignAuthorityConfigurabl return execute4InputStream(client); } + /** + * Read XML.
+ * The method will be invoked when read data from XML file.
+ * May override the method to read the data that you saved. + */ + @Override + public void readXML(XMLableReader reader) { + if (reader.isChildNode()) { + String tmpVal; + if ("DIR".equals(reader.getTagName())) { + if ((tmpVal = reader.getAttrAsString("path", null)) != null) { + this.path = tmpVal; + } + if ((tmpVal = reader.getAttrAsString("user", null)) != null) { + this.user = tmpVal; + } + if ((tmpVal = reader.getAttrAsString("password", null)) != null) { + this.password = tmpVal; + } + } + } + } + + /** + * Write XML.
+ * The method will be invoked when save data to XML file.
+ * May override the method to save your own data. + * + * @param writer the PrintWriter. + */ + @Override + public void writeXML(XMLPrintWriter writer) { + writer.startTAG("DIR").attr("path", this.path).attr("user", this.user).attr("password", this.password).end(); + } + + + public static class Clock { + + private static final long CONNECT_INTERVAL = 3000L; + private boolean connected = false; + + private RemoteEnv remoteEnv; + + public Clock(RemoteEnv remoteEnv) { + this.remoteEnv = remoteEnv; + } + + /** + * 停止连接 + */ + public void stop() { + connected = false; + } + + private void attemptConnect() throws Exception { + Thread.sleep(CONNECT_INTERVAL); + Pattern pattern = Pattern.compile("[/:]+"); + String[] strs = pattern.split(remoteEnv.path); + + //host,如:192.168.100.195 + String shost = strs[1]; + //端口,如:8080 + int sport = Integer.parseInt(strs[2]); + + Socket socket = new Socket(shost, sport); + //OOBBINLINE:是否支持发送一个字节的TCP紧急数据,false表示服务器不用处理这个数据 + socket.setOOBInline(false); + socket.sendUrgentData(0xFF); + socket.close(); + } + } + + /** + * 读报表运行环境所需的配置文件,如datasource.xml, config.xml,这些文件都保存在WEB-INF/resources目录下面 + * + * @param resourceName 配置文件的名字,如datasource.xml + * @return 输入流 + * @throws Exception e + */ + @Override + public InputStream readResource(String resourceName) throws Exception { + return readBean(resourceName, ProjectConstants.RESOURCES_NAME); + } + /** * 读取路径下的svg文件 @@ -659,11 +1555,176 @@ public class RemoteEnv extends AbstractEnv implements DesignAuthorityConfigurabl return new Bytes2ServerOutputStream(para); } + /** + * 返回数据库表的列名 + * + * @param selectedName 所选择数据库名 + * @param schema 数据库模式,用于存储过程 + * @param tableName 所选择数据库名 + */ + @Override + public String[] getColumns(String selectedName, String schema, String tableName) throws Exception { + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "design_columns"); + para.put("dsName", selectedName); + para.put("schema", schema); + para.put("tableName", tableName); + + HttpClient client = createHttpMethod2(para); + InputStream input = execute4InputStream(client); + + if (input == null) { + return null; + } + + String colums = stream2String(input); + if (StringUtils.isEmpty(colums)) { + return null; + } + return colums.split("\\."); + } + + /** + * 返回模板文件路径 + */ @Override public String getWebReportPath() { return getPath().substring(0, getPath().lastIndexOf("/")); } + @Override + public String getProcedureText(String connectionName, String databaseName) throws Exception { + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "design_get_procedure_text"); + para.put("procedure_name", databaseName); + para.put("connectionName", connectionName); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + InputStream input = postBytes2ServerB(out.toByteArray(), para); + if (input == null) { + return StringUtils.EMPTY; + } + return DavXMLUtils.readXMLProcedureText(input); + } + + @Override + public StoreProcedureParameter[] getStoreProcedureDeclarationParameters(String connectionName, String databaseName, String parameterDefaultValue) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "design_get_sp_parameters"); + para.put("__name__", databaseName); + para.put("__default_value__", parameterDefaultValue); + para.put("connectionName", connectionName); + + InputStream input = postBytes2ServerB(out.toByteArray(), para); + if (input == null) { + return new StoreProcedureParameter[0]; + } + return DavXMLUtils.readXMLStoreProcedureParameters(input); + } + + /** + * 获取datasource.xml文件的修改表 + */ + @Override + public ModifiedTable getDataSourceModifiedTables(String type) { + try { + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "get_datasource_modified_tables"); + para.put("type", type); + + HttpClient client = createHttpMethod(para); + InputStream input = execute4InputStream(client); + if (input == null) { + return new ModifiedTable(); + } + return DavXMLUtils.readXMLModifiedTables(input); + } catch (Exception e) { + FRContext.getLogger().error(e.getMessage()); + } + return new ModifiedTable(); + } + + + /** + * 写修改表 + * + * @param modifiedTable 修改表 + * @param type 操作类型,是数据连接还是服务器数据集 + * @return 写入成功返回true + */ + @Override + public boolean writeDataSourceModifiedTables(ModifiedTable modifiedTable, String type) { + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + // 把tableData写成xml文件到out + DavXMLUtils.writeXMLModifiedTables(modifiedTable, out); + try { + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "update_modifytable_to_server"); + para.put("type", type); + + InputStream input = postBytes2ServerB(out.toByteArray(), para); + + if (input == null) { + return false; + } + + return Boolean.valueOf(IOUtils.inputStream2String(input, EncodeConstants.ENCODING_UTF_8)); + } catch (Exception e) { + FRContext.getLogger().error(e.getMessage()); + } + return false; + } + + public String[] getProcedureColumns(StoreProcedure storeProcedure, java.util.Map parameterMap) throws Exception { + String[] columns; + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "list_sp"); + HttpClient client = createHttpMethod(para); + try { + InputStream input = execute4InputStream(client); + + if (input == null) { + return ArrayUtils.EMPTY_STRING_ARRAY; + } + + columns = DavXMLUtils.readXMLSPColumns(input); + return columns; + } catch (Exception e) { + FRContext.getLogger().error(e.getMessage()); + } + + return new String[0]; + } + + public String[] getProcedureColumns(String name) throws Exception { + String[] columns; + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "list_sp_columns_name"); + para.put("name", name); + HttpClient client = createHttpMethod(para); + try { + InputStream input = execute4InputStream(client); + if (input == null) { + return ArrayUtils.EMPTY_STRING_ARRAY; + } + columns = DavXMLUtils.readXMLSPColumns(input); + return columns; + } catch (Exception e) { + FRContext.getLogger().error(e.getMessage()); + } + return new String[0]; + + } /** * 输出日志信息 @@ -687,12 +1748,103 @@ public class RemoteEnv extends AbstractEnv implements DesignAuthorityConfigurabl } } + @Override + public String getUserID() { + return userID; + } + + + /** + * 预览存储过程 + * + * @param storeProcedure 存储过程 + * @param parameterMap 参数map + * @param rowCount 行数 + * @return 返回取到的存储过程 + */ + @Override + public ProcedureDataModel[] previewProcedureDataModel(StoreProcedure storeProcedure, Map parameterMap, int rowCount) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + // 把tableData写成xml文件到out + DavXMLUtils.writeXMLFileStoreProcedureAndSource(storeProcedure, out); + + // 把parameterMap转成JSON格式的字符串 + JSONObject jo = new JSONObject(parameterMap); + String jsonParameter = jo.toString(); + + try { + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "list_sp"); + para.put("pars", jsonParameter); + + InputStream input = postBytes2ServerB(out.toByteArray(), para); + if (input == null) { + return null; + } + + TableData[] tableDatas = DavXMLUtils.readXMLTableDataArray(input); + if (tableDatas == null || tableDatas.length == 0) { + return new ProcedureDataModel[0]; + } + ProcedureDataModel[] procedureDataModels = new ProcedureDataModel[tableDatas.length]; + for (int i = 0; i < tableDatas.length; i++) { + if (tableDatas[i] instanceof EmbeddedTableData) { + procedureDataModels[i] = ((EmbeddedTableData) tableDatas[i]).trans2ProcedureDataModel(); + } + } + return procedureDataModels; + + + } catch (Exception e) { + FRContext.getLogger().error(e.getMessage()); + } + return new ProcedureDataModel[0]; + } + + + @Override + public String getAppName() { + return "WebReport"; + } + + /** + * 是否为Oracle数据连接 + * + * @param database 数据连接 + * @return 是返回true + * @throws Exception + */ + @Override + public boolean isOracle(Connection database) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + DavXMLUtils.writeXMLFileDatabaseConnection(database, out); + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "design_get_isOracle"); + InputStream input = postBytes2ServerB(out.toByteArray(), para); + if (input == null) { + return true; + } + return DavXMLUtils.readXMLBoolean(input); + } @Override public String[] getSupportedTypes() { return FILE_TYPE; } + /** + * 在模板面板中是否支持增加打开所在文件夹、重命名、删除三个工具栏选项 + * + * @return 不支持返回false + */ + @Override + public boolean isSupportLocalFileOperate() { + return false; + } + /** * 判断是否有文件夹权限 * @@ -723,6 +1875,43 @@ public class RemoteEnv extends AbstractEnv implements DesignAuthorityConfigurabl } + /** + * 是否是管理员身份 + * + * @return 是则返回true + */ + @Override + public boolean isRoot() { + return isRoot; + } + + /** + * 是否为压缩包部署 + * + * @return 是则返回true + */ + @Override + public boolean isPackDeploy() { + return false; + } + + @Override + public String getDesignerVersion() throws Exception { + HashMap para = new HashMap<>(); + para.put("op", "fr_remote_design"); + para.put("cmd", "design_get_designer_version"); + para.put("user", user); + para.put("password", getEncodedPassword()); + + HttpClient client = createHttpMethod(para, true); + try { + return stream2String(execute4InputStream(client)); + } catch (Exception e) { + FRContext.getLogger().error(e.getMessage()); + } + return null; + } + @Override public InputStream getDataSourceInputStream(String filePath) throws Exception { return readBean(filePath, "datasource"); @@ -751,6 +1940,17 @@ public class RemoteEnv extends AbstractEnv implements DesignAuthorityConfigurabl return allRoleList; } + @Override + public String getLicName() { + return License.FILE_NAME; + } + + @Override + public void setLicName(String licName) { + //do nth + } + + /** * 获取当前env的build文件路径 */ @@ -928,10 +2128,38 @@ public class RemoteEnv extends AbstractEnv implements DesignAuthorityConfigurabl } } + @Override + public void doWhenServerShutDown() { + + } + + @Override + public boolean isLocalEnv() { + + return false; + } @Override public boolean hasPluginServiceStarted(String key) { return true; } + + @Override + public JSONArray getPluginStatus() { + + try { + HashMap para = new HashMap<>(); + para.put("op", "plugin"); + para.put("cmd", "get_status"); + para.put("current_uid", this.createUserID()); + para.put("currentUsername", this.getUser()); + + HttpClient client = createHttpMethod(para); + InputStream input = execute4InputStream(client); + return new JSONArray(stream2String(input)); + } catch (Exception e) { + return JSONArray.create(); + } + } } \ No newline at end of file diff --git a/designer-base/src/com/fr/env/RemoteEnvUtils.java b/designer-base/src/com/fr/env/RemoteEnvUtils.java index d22150bd8..5580222be 100644 --- a/designer-base/src/com/fr/env/RemoteEnvUtils.java +++ b/designer-base/src/com/fr/env/RemoteEnvUtils.java @@ -3,6 +3,7 @@ package com.fr.env; import com.fr.base.FRContext; import com.fr.general.IOUtils; import com.fr.report.DesignAuthority; +import com.fr.report.util.AuthorityXMLUtils; import com.fr.stable.EncodeConstants; import com.fr.third.org.apache.http.HttpEntity; import com.fr.third.org.apache.http.client.methods.CloseableHttpResponse; @@ -13,7 +14,6 @@ import com.fr.third.org.apache.http.entity.InputStreamEntity; import com.fr.third.org.apache.http.impl.client.CloseableHttpClient; import com.fr.third.org.apache.http.impl.client.HttpClients; import com.fr.third.org.apache.http.util.EntityUtils; -import com.fr.web.utils.AuthorityXMLUtils; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; diff --git a/designer-base/src/com/fr/env/SignIn.java b/designer-base/src/com/fr/env/SignIn.java index f876302ba..09ba8c911 100644 --- a/designer-base/src/com/fr/env/SignIn.java +++ b/designer-base/src/com/fr/env/SignIn.java @@ -25,7 +25,7 @@ public class SignIn { static { EventDispatcher.listen(EnvEvents.CONNECTION_ERROR, new Listener() { @Override - public void on(Event event, Null param) { + public void on(Event event, Null param) { if (JOptionPane.showConfirmDialog(null, Inter.getLocText("FR-Remote_Connect2Server_Again"), UIManager.getString("OptionPane.titleText"), JOptionPane.YES_NO_OPTION) == JOptionPane.OK_OPTION) { try { diff --git a/designer-form/src/com/fr/design/mainframe/widget/accessibles/AccessibleBodyWatermarkEditor.java b/designer-form/src/com/fr/design/mainframe/widget/accessibles/AccessibleBodyWatermarkEditor.java index 863dd1b95..a3cd042fa 100644 --- a/designer-form/src/com/fr/design/mainframe/widget/accessibles/AccessibleBodyWatermarkEditor.java +++ b/designer-form/src/com/fr/design/mainframe/widget/accessibles/AccessibleBodyWatermarkEditor.java @@ -7,6 +7,9 @@ import com.fr.design.mainframe.widget.editors.ITextComponent; import com.fr.design.mainframe.widget.renderer.WatermarkRenderer; import com.fr.design.mainframe.widget.wrappers.WatermarkWrapper; import com.fr.design.report.WatermarkPane; +import com.fr.plugin.ExtraClassManager; +import com.fr.stable.ReportFunctionProcessor; +import com.fr.stable.fun.FunctionProcessor; import javax.swing.SwingUtilities; import java.awt.Dimension; @@ -40,6 +43,11 @@ public class AccessibleBodyWatermarkEditor extends UneditableAccessibleEditor { public void doOk() { setValue(watermarkPane.update()); fireStateChanged(); + // 功能点 + FunctionProcessor processor = ExtraClassManager.getInstance().getFunctionProcessor(); + if (processor != null) { + processor.recordFunction(ReportFunctionProcessor.WATERMARK); + } } }); watermarkPane.populate((WatermarkAttr) getValue()); diff --git a/designer-realize/src/com/fr/design/actions/report/ReportWatermarkAction.java b/designer-realize/src/com/fr/design/actions/report/ReportWatermarkAction.java index ebe0b4ed1..d74d1698a 100644 --- a/designer-realize/src/com/fr/design/actions/report/ReportWatermarkAction.java +++ b/designer-realize/src/com/fr/design/actions/report/ReportWatermarkAction.java @@ -9,7 +9,10 @@ import com.fr.design.mainframe.JWorkBook; import com.fr.design.menu.KeySetUtils; import com.fr.design.report.WatermarkPane; import com.fr.main.impl.WorkBook; +import com.fr.plugin.ExtraClassManager; import com.fr.report.core.ReportUtils; +import com.fr.stable.ReportFunctionProcessor; +import com.fr.stable.fun.FunctionProcessor; import java.awt.event.ActionEvent; @@ -42,6 +45,11 @@ public class ReportWatermarkAction extends JWorkBookAction { public void doOk() { wbTpl.addAttrMark(watermarkPane.update()); jwb.fireTargetModified(); + // 功能点 + FunctionProcessor processor = ExtraClassManager.getInstance().getFunctionProcessor(); + if (processor != null) { + processor.recordFunction(ReportFunctionProcessor.WATERMARK); + } } }).setVisible(true); }