Browse Source

feat: REPORT-70565 设计器环境监测(jar包异常、finedb、杀毒软件)

调整一下环境检测相关的内容
1-国际化效果\通过 html 封装 text 实现替换逻辑
2-补充注释
3-添加 EnvPrepare 注入到环境中,用来帮助环境启动时的一些相关钩子的启动
feature/x
Harrison 3 years ago
parent
commit
9457aebed6
  1. 58
      designer-base/src/main/java/com/fr/env/detect/EnvDetectorCenter.java
  2. 21
      designer-base/src/main/java/com/fr/env/detect/EnvPrepare.java
  3. 13
      designer-base/src/main/java/com/fr/env/detect/base/DetectorBridge.java
  4. 10
      designer-base/src/main/java/com/fr/env/detect/base/DetectorManager.java
  5. 70
      designer-base/src/main/java/com/fr/env/detect/base/DetectorUtil.java
  6. 59
      designer-base/src/main/java/com/fr/env/detect/base/EnvDetectorConfig.java
  7. 34
      designer-base/src/main/java/com/fr/env/detect/base/ExceptionDetectorConfig.java
  8. 12
      designer-base/src/main/java/com/fr/env/detect/impl/JarInconsistentDetector.java
  9. 34
      designer-base/src/main/java/com/fr/env/detect/impl/JarLackDetector.java
  10. 25
      designer-base/src/main/java/com/fr/env/detect/impl/converter/ClassConflictConvertor.java
  11. 46
      designer-base/src/main/java/com/fr/env/detect/impl/converter/FineDbDirtyConverter.java
  12. 5
      designer-base/src/main/java/com/fr/env/detect/impl/converter/FineDbLockedConverter.java
  13. 5
      designer-base/src/main/java/com/fr/env/detect/impl/converter/FineDbPermissionConverter.java
  14. 17
      designer-base/src/main/java/com/fr/env/detect/thowable/ThrowableLogAppender.java
  15. 8
      designer-base/src/main/java/com/fr/env/detect/ui/DetectorErrorDialog.java
  16. 28
      designer-base/src/main/java/com/fr/env/detect/ui/EnvDetectorAction.java
  17. 8
      designer-base/src/main/java/com/fr/env/detect/ui/EnvDetectorDialog.java
  18. 2
      designer-base/src/main/java/com/fr/env/detect/ui/EnvDetectorItem.java
  19. 17
      designer-base/src/main/java/com/fr/env/detect/ui/EnvDetectorModel.java
  20. 3
      designer-base/src/main/resources/com/fr/env/detect/detect.svg
  21. 3
      designer-base/src/main/resources/com/fr/env/detect/detect_normal.svg

58
designer-base/src/main/java/com/fr/env/detect/EnvDetectorCenter.java vendored

@ -1,5 +1,6 @@
package com.fr.env.detect; package com.fr.env.detect;
import com.fr.common.util.Collections;
import com.fr.design.components.notification.NotificationDialog; import com.fr.design.components.notification.NotificationDialog;
import com.fr.design.components.notification.NotificationDialogProperties; import com.fr.design.components.notification.NotificationDialogProperties;
import com.fr.design.components.notification.NotificationModel; import com.fr.design.components.notification.NotificationModel;
@ -28,6 +29,9 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**
* 环境检测中心
* 如果环境检测 ->
*
* created by Harrison on 2022/05/27 * created by Harrison on 2022/05/27
**/ **/
public class EnvDetectorCenter { public class EnvDetectorCenter {
@ -42,6 +46,9 @@ public class EnvDetectorCenter {
private final AtomicReference<DetectorProcess> PROCESS = new AtomicReference<>(); private final AtomicReference<DetectorProcess> PROCESS = new AtomicReference<>();
/**
* 初始化
*/
public void init() { public void init() {
// 默认是启动 // 默认是启动
@ -58,18 +65,11 @@ public class EnvDetectorCenter {
} }
}); });
// 切换工作目录 // 切换完成后的监听
EventDispatcher.listen(WorkspaceEvent.BeforeSwitch, new Listener<Workspace>() {
@Override
public void on(Event event, Workspace param) {
PROCESS.set(DetectorProcess.ENV_SWITCH);
start();
}
});
EventDispatcher.listen(WorkspaceEvent.AfterSwitch, new Listener<Workspace>() { EventDispatcher.listen(WorkspaceEvent.AfterSwitch, new Listener<Workspace>() {
@Override @Override
public void on(Event event, Workspace param) { public void on(Event event, Workspace param) {
if (isSameProcess(DetectorProcess.ENV_SWITCH)) { if (isSameProcess(DetectorProcess.DESIGN_LAUNCH)) {
stop(); stop();
} }
} }
@ -108,33 +108,44 @@ public class EnvDetectorCenter {
return PROCESS.compareAndSet(process, null); return PROCESS.compareAndSet(process, null);
} }
/**
* 启动
*/
public void start() { public void start() {
DetectorBridge.getInstance().start(); DetectorBridge.getInstance().start();
} }
/**
* 关闭
*/
public void stop() { public void stop() {
Stream<DetectorResult> resultStream = DetectorBridge.getInstance().detect();
// 展示效果
NotificationDialogProperties properties = new NotificationDialogProperties(DesignerContext.getDesignerFrame(), Toolkit.i18nText("Fine-Design_Basic_Detect_Notification_Title"));
List<NotificationModel> notificationModels = resultStream
.filter(Objects::nonNull)
.filter((e) -> e.getStatus() == DetectorStatus.EXCEPTION)
.map(DetectorUtil::convert2Notification)
.collect(Collectors.toList());
// 一分钟后执行 // 一分钟后执行
DelayHelper.delayCall(EnvDetectorCenter.class.getName(), () -> { DelayHelper.delayCall(EnvDetectorCenter.class.getName(), () -> {
Stream<DetectorResult> resultStream = DetectorBridge.getInstance().detect();
// 结束
DetectorBridge.getInstance().stop();
// 展示效果
NotificationDialogProperties properties = new NotificationDialogProperties(DesignerContext.getDesignerFrame(), Toolkit.i18nText("Fine-Design_Basic_Detect_Notification_Title"));
List<NotificationModel> notificationModels = resultStream
.filter(Objects::nonNull)
.filter((e) -> e.getStatus() == DetectorStatus.EXCEPTION)
.map(DetectorUtil::convert2Notification)
.collect(Collectors.toList());
if (Collections.isEmpty(notificationModels)) {
return;
}
UIUtil.invokeLaterIfNeeded(() -> { UIUtil.invokeLaterIfNeeded(() -> {
NotificationDialog dialog = new NotificationDialog(properties, notificationModels); NotificationDialog dialog = new NotificationDialog(properties, notificationModels);
dialog.open(); dialog.open();
}); });
}, 1, TimeUnit.MINUTES); }, 30, TimeUnit.SECONDS);
// 结束
DetectorBridge.getInstance().stop();
} }
/** /**
@ -146,10 +157,9 @@ public class EnvDetectorCenter {
public List<DetectorResult> terminate(Throwable throwable) { public List<DetectorResult> terminate(Throwable throwable) {
Stream<DetectorResult> resultStream = DetectorBridge.getInstance().detect(throwable); Stream<DetectorResult> resultStream = DetectorBridge.getInstance().detect(throwable);
List<DetectorResult> results = resultStream return resultStream
.filter((e) -> e.getStatus() == DetectorStatus.EXCEPTION) .filter((e) -> e.getStatus() == DetectorStatus.EXCEPTION)
.collect(Collectors.toList()); .collect(Collectors.toList());
return results;
} }
private enum DetectorProcess { private enum DetectorProcess {

21
designer-base/src/main/java/com/fr/env/detect/EnvPrepare.java vendored

@ -0,0 +1,21 @@
package com.fr.env.detect;
import com.fr.module.Activator;
/**
* 设计器环境准备
*
* created by Harrison on 2022/05/29
**/
public class EnvPrepare extends Activator {
@Override
public void start() {
EnvDetectorCenter.getInstance().init();
}
@Override
public void stop() {
}
}

13
designer-base/src/main/java/com/fr/env/detect/base/DetectorBridge.java vendored

@ -17,6 +17,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**
* 检测器桥接逻辑
* [detect-core] - bridge - ui
*
* created by Harrison on 2022/05/13 * created by Harrison on 2022/05/13
**/ **/
public class DetectorBridge { public class DetectorBridge {
@ -53,7 +56,7 @@ public class DetectorBridge {
public void start() { public void start() {
if (!hasStarted.get() && ExceptionDetectorConfig.getInstance().isOpen()) { if (!hasStarted.get() && EnvDetectorConfig.getInstance().isEnabled()) {
// 开始注册异常处理 // 开始注册异常处理
ThrowableLogAppender.getInstance().enable(); ThrowableLogAppender.getInstance().enable();
hasStarted.set(true); hasStarted.set(true);
@ -91,11 +94,10 @@ public class DetectorBridge {
@NotNull @NotNull
public DetectorResult detect(DetectorType type, Throwable throwable) { public DetectorResult detect(DetectorType type, Throwable throwable) {
// 先清理一下
ThrowableStore.getInstance().reset();
ThrowableStore.getInstance().add(throwable); ThrowableStore.getInstance().add(throwable);
DetectorResult result = detect(type); DetectorResult result = detect(type);
ThrowableStore.getInstance().reset(); ThrowableStore.getInstance().reset();
return result; return result;
@ -123,9 +125,6 @@ public class DetectorBridge {
*/ */
public Stream<DetectorResult> detect(Throwable throwable) { public Stream<DetectorResult> detect(Throwable throwable) {
// 先清理一下
ThrowableStore.getInstance().reset();
ThrowableStore.getInstance().add(throwable); ThrowableStore.getInstance().add(throwable);
Stream<DetectorResult> result = detect(); Stream<DetectorResult> result = detect();
ThrowableStore.getInstance().reset(); ThrowableStore.getInstance().reset();

10
designer-base/src/main/java/com/fr/env/detect/base/DetectorManager.java vendored

@ -7,9 +7,12 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**
* 检测器中心
*
* created by Harrison on 2022/05/24 * created by Harrison on 2022/05/24
**/ **/
class DetectorManager { class DetectorManager {
@ -29,9 +32,10 @@ class DetectorManager {
.map(ExceptionDetector::detect) .map(ExceptionDetector::detect)
.filter(Objects::nonNull); .filter(Objects::nonNull);
// 记录一下日志 List<DetectorResult> resultList = results.collect(Collectors.toList());
results.forEach(DetectorResult::log); resultList.forEach(DetectorResult::log);
return results;
return resultList.stream();
} }
public DetectorResult detect(DetectorType type) { public DetectorResult detect(DetectorType type) {

70
designer-base/src/main/java/com/fr/env/detect/base/DetectorUtil.java vendored

@ -7,6 +7,7 @@ import com.fr.design.components.notification.NotificationModel;
import com.fr.design.components.notification.NotificationType; import com.fr.design.components.notification.NotificationType;
import com.fr.design.dialog.link.MessageWithLink; import com.fr.design.dialog.link.MessageWithLink;
import com.fr.design.gui.ilable.UILabel; import com.fr.design.gui.ilable.UILabel;
import com.fr.design.utils.LinkStrUtils;
import com.fr.env.detect.bean.DetectorResult; import com.fr.env.detect.bean.DetectorResult;
import com.fr.env.detect.bean.ExceptionSolution; import com.fr.env.detect.bean.ExceptionSolution;
import com.fr.env.detect.bean.ExceptionTips; import com.fr.env.detect.bean.ExceptionTips;
@ -29,11 +30,26 @@ import java.util.function.Function;
**/ **/
public class DetectorUtil { public class DetectorUtil {
/**
* 是否是设计器的 jar
*
* @param info 信息
* @return /
*/
public static boolean isDesignerJar(BuildInfo info) { public static boolean isDesignerJar(BuildInfo info) {
if (info == null) {
return false;
}
return StringUtils.contains(info.getJar(), "fine-report-designer"); return StringUtils.contains(info.getJar(), "fine-report-designer");
} }
/**
* 将结果转化为提醒的数据
*
* @param result 结果
* @return 数据
*/
public static NotificationModel convert2Notification(DetectorResult result) { public static NotificationModel convert2Notification(DetectorResult result) {
List<NotificationMessage> messages = new ArrayList<>(); List<NotificationMessage> messages = new ArrayList<>();
@ -59,34 +75,46 @@ public class DetectorUtil {
}; };
ExceptionTips tips = result.getTips(); ExceptionTips tips = result.getTips();
convert2NotificationMsg if (tips != null) {
.apply(tips.getMessage()) convert2NotificationMsg
.ifPresent(messages::add); .apply(tips.getMessage())
.ifPresent(messages::add);
}
ExceptionSolution solution = result.getSolution(); ExceptionSolution solution = result.getSolution();
convert2NotificationMsg.apply(solution.getMessage()) if (solution != null) {
.ifPresent(messages::add); convert2NotificationMsg.apply(solution.getMessage())
.ifPresent(messages::add);
}
NotificationAction notificationAction = null; NotificationAction notificationAction = null;
SolutionAction solutionAction = solution.getAction(); if (solution != null) {
if (solutionAction != null) { SolutionAction solutionAction = solution.getAction();
notificationAction = new NotificationAction() { if (solutionAction != null) {
@Override notificationAction = new NotificationAction() {
public String name() { @Override
return solutionAction.name(); public String name() {
} return solutionAction.name();
}
@Override
public void run(Object... args) { @Override
solutionAction.run(); public void run(Object... args) {
} solutionAction.run();
}; }
};
}
} }
return new NotificationModel(NotificationType.WARNING, notificationAction, messages); return new NotificationModel(NotificationType.WARNING, notificationAction, messages);
} }
public static JComponent convert2Component(@NotNull Message message) { /**
* 将信息转化为展示的组件
*
* @param message 信息
* @return 组件
*/
public static JComponent convert2TextComponent(@NotNull Message message) {
if (message.getType() == Message.Type.LINK) { if (message.getType() == Message.Type.LINK) {
Message.Link linkMsg = (Message.Link) message; Message.Link linkMsg = (Message.Link) message;
@ -94,6 +122,6 @@ public class DetectorUtil {
Desktop.getDesktop().browse(URI.create(linkMsg.getLink())); Desktop.getDesktop().browse(URI.create(linkMsg.getLink()));
})); }));
} }
return new UILabel(message.get()); return new UILabel(LinkStrUtils.generateHtmlTag(message.get()));
} }
} }

59
designer-base/src/main/java/com/fr/env/detect/base/EnvDetectorConfig.java vendored

@ -0,0 +1,59 @@
package com.fr.env.detect.base;
import com.fr.design.DesignerEnvManager;
import com.fr.stable.xml.XMLPrintWriter;
import com.fr.stable.xml.XMLable;
import com.fr.stable.xml.XMLableReader;
/**
* created by Harrison on 2022/05/13
**/
public class EnvDetectorConfig implements XMLable {
public static final String XML_TAG = "EnvDetectorConfig";
private static final long serialVersionUID = -8170289826729582122L;
private static final EnvDetectorConfig INSTANCE = new EnvDetectorConfig();
public static EnvDetectorConfig getInstance() {
return INSTANCE;
}
private boolean enabled = true;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
save();
}
private void save() {
DesignerEnvManager.getEnvManager(false).saveXMLFile();
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public void readXML(XMLableReader reader) {
if (reader.isAttr()) {
this.setEnabled(reader.getAttrAsBoolean("isEnabled", true));
}
}
@Override
public void writeXML(XMLPrintWriter writer) {
writer.startTAG(XML_TAG);
writer.attr("isEnabled", this.isEnabled());
writer.end();
}
}

34
designer-base/src/main/java/com/fr/env/detect/base/ExceptionDetectorConfig.java vendored

@ -1,34 +0,0 @@
package com.fr.env.detect.base;
import com.fr.config.ConfigContext;
import com.fr.config.DefaultConfiguration;
import com.fr.config.holder.Conf;
import com.fr.config.holder.factory.Holders;
/**
* created by Harrison on 2022/05/13
**/
public class ExceptionDetectorConfig extends DefaultConfiguration {
private static volatile ExceptionDetectorConfig instance = null;
public static ExceptionDetectorConfig getInstance() {
if (instance == null) {
instance = ConfigContext.getConfigInstance(ExceptionDetectorConfig.class);
}
return instance;
}
private final Conf<Boolean> open = Holders.simple(true);
public void setOpen(boolean open) {
this.open.set(open);
}
public boolean isOpen() {
return open.get();
}
}

12
designer-base/src/main/java/com/fr/env/detect/impl/JarInconsistentDetector.java vendored

@ -33,6 +33,8 @@ import java.util.stream.Collectors;
**/ **/
public class JarInconsistentDetector extends AbstractExceptionDetector { public class JarInconsistentDetector extends AbstractExceptionDetector {
public static final String SEPARATOR = ",";
public JarInconsistentDetector() { public JarInconsistentDetector() {
super(DetectorType.JAR_IN_CONSISTENCE); super(DetectorType.JAR_IN_CONSISTENCE);
@ -81,14 +83,14 @@ public class JarInconsistentDetector extends AbstractExceptionDetector {
Set<String> inConsistentJars = diffInCommon.keySet(); Set<String> inConsistentJars = diffInCommon.keySet();
String message = StringUtils.join(",", inConsistentJars); String message = StringUtils.join(inConsistentJars, SEPARATOR);
Message.Simple tipsMessage = new Message.Simple(Toolkit.i18nText("Fine_Design_Basic_Detect_Server") + Toolkit.i18nText(type().getTipsLocale()) + "\n" + message); Message.Simple tipsMessage = new Message.Simple(Toolkit.i18nText("Fine_Design_Basic_Detect_Server") + Toolkit.i18nText(type().getTipsLocale()) + message);
return DetectorResult.exception(type(), return DetectorResult.exception(type(),
new ExceptionTips(tipsMessage), new ExceptionTips(tipsMessage),
new ExceptionSolution(new Message.Link(Toolkit.i18nText(type().getSolutionLocale(),DetectorConstants.JAR_HELP_LINK) new ExceptionSolution(new Message.Link(Toolkit.i18nText(type().getSolutionLocale(),DetectorConstants.JAR_HELP_LINK)
, DetectorConstants.JAR_HELP_LINK), null), , DetectorConstants.JAR_HELP_LINK), null),
ExceptionLog.create(type().getLogLocale() + message)); ExceptionLog.create(Toolkit.i18nText(type().getLogLocale()) + message));
} }
@NotNull @NotNull
@ -124,8 +126,8 @@ public class JarInconsistentDetector extends AbstractExceptionDetector {
List<String> inConsistentJars = inConsistentInfos.stream() List<String> inConsistentJars = inConsistentInfos.stream()
.map(BuildInfo::getJar) .map(BuildInfo::getJar)
.collect(Collectors.toList()); .collect(Collectors.toList());
String message = StringUtils.join(",", inConsistentJars); String message = StringUtils.join(inConsistentJars, SEPARATOR);
String tipsMessage = Toolkit.i18nText("Fine_Design_Basic_Detect_Local") + Toolkit.i18nText(type().getTipsLocale()) + "\n" + message; String tipsMessage = Toolkit.i18nText("Fine_Design_Basic_Detect_Local") + Toolkit.i18nText(type().getTipsLocale()) + message;
return DetectorResult.exception(type(), return DetectorResult.exception(type(),
new ExceptionTips(new Message.Simple(tipsMessage)), new ExceptionTips(new Message.Simple(tipsMessage)),

34
designer-base/src/main/java/com/fr/env/detect/impl/JarLackDetector.java vendored

@ -1,7 +1,7 @@
package com.fr.env.detect.impl; package com.fr.env.detect.impl;
import com.fr.common.util.Collections; import com.fr.common.util.Collections;
import com.fr.common.util.Strings; import com.fr.design.i18n.Toolkit;
import com.fr.env.detect.base.AbstractExceptionDetector; import com.fr.env.detect.base.AbstractExceptionDetector;
import com.fr.env.detect.base.DetectorConstants; import com.fr.env.detect.base.DetectorConstants;
import com.fr.env.detect.base.DetectorUtil; import com.fr.env.detect.base.DetectorUtil;
@ -14,7 +14,6 @@ import com.fr.env.detect.bean.Message;
import com.fr.general.build.BuildInfo; import com.fr.general.build.BuildInfo;
import com.fr.general.build.BuildInfoOperator; import com.fr.general.build.BuildInfoOperator;
import com.fr.general.build.impl.BuildInfoOperatorImpl; import com.fr.general.build.impl.BuildInfoOperatorImpl;
import com.fr.locale.InterProviderFactory;
import com.fr.third.guava.collect.Lists; import com.fr.third.guava.collect.Lists;
import com.fr.third.org.apache.commons.lang3.StringUtils; import com.fr.third.org.apache.commons.lang3.StringUtils;
import com.fr.workspace.WorkContext; import com.fr.workspace.WorkContext;
@ -30,6 +29,11 @@ import java.util.stream.Collectors;
**/ **/
public class JarLackDetector extends AbstractExceptionDetector { public class JarLackDetector extends AbstractExceptionDetector {
public static final String SEPARATOR = "、";
public static final String BR_TAG = "<br/>";
public static final String WEB_LIB_PATH = "%FR_HOME%\\webapps\\webroot\\WEB-INF\\lib:";
public static final String FR_HOME_LIB = "%FR_HOME%\\lib:";
public JarLackDetector() { public JarLackDetector() {
super(DetectorType.LACK_OF_JAR); super(DetectorType.LACK_OF_JAR);
@ -57,9 +61,10 @@ public class JarLackDetector extends AbstractExceptionDetector {
Message tipsMsg = tipsMessage(lackInfos); Message tipsMsg = tipsMessage(lackInfos);
ExceptionLog exceptionLog = exceptionLog(lackInfos); ExceptionLog exceptionLog = exceptionLog(lackInfos);
return DetectorResult.exception(type(), new ExceptionTips(tipsMsg), return DetectorResult.exception(type(),
new ExceptionTips(tipsMsg),
new ExceptionSolution( new ExceptionSolution(
new Message.Link(InterProviderFactory.getProvider().getLocText(type().getSolutionLocale()), DetectorConstants.JAR_HELP_LINK), new Message.Link(Toolkit.i18nText(type().getSolutionLocale()), DetectorConstants.JAR_HELP_LINK),
null), exceptionLog); null), exceptionLog);
} }
@ -68,8 +73,8 @@ public class JarLackDetector extends AbstractExceptionDetector {
List<String> jarInfos = lackInfos.stream() List<String> jarInfos = lackInfos.stream()
.map(BuildInfo::getJar) .map(BuildInfo::getJar)
.collect(Collectors.toList()); .collect(Collectors.toList());
String message = StringUtils.join(",", jarInfos); String message = StringUtils.join(jarInfos, ",");
return ExceptionLog.create(type().getLogLocale() + message); return ExceptionLog.create(Toolkit.i18nText(type().getLogLocale()) + message);
} }
private boolean isLackInfo(BuildInfo e) { private boolean isLackInfo(BuildInfo e) {
@ -78,8 +83,8 @@ public class JarLackDetector extends AbstractExceptionDetector {
private Message tipsMessage(List<BuildInfo> infos) { private Message tipsMessage(List<BuildInfo> infos) {
String webLibPath = "%FR_HOME%\\webapps\\webroot\\WEB-INF\\lib:"; String webLibPath = WEB_LIB_PATH;
String homeLibPath = "%FR_HOME%\\lib:"; String homeLibPath = FR_HOME_LIB;
Map<String, List<String>> libMap = groupByPath(infos, homeLibPath, webLibPath); Map<String, List<String>> libMap = groupByPath(infos, homeLibPath, webLibPath);
@ -87,23 +92,24 @@ public class JarLackDetector extends AbstractExceptionDetector {
List<String> homeLibs = libMap.get(homeLibPath); List<String> homeLibs = libMap.get(homeLibPath);
if (!Collections.isEmpty(homeLibs)) { if (!Collections.isEmpty(homeLibs)) {
sb.append(homeLibPath).append(":"); sb.append(homeLibPath);
sb.append(StringUtils.join("、", homeLibs)); sb.append(StringUtils.join(homeLibs, SEPARATOR));
} }
List<String> webLibs = libMap.get(webLibPath); List<String> webLibs = libMap.get(webLibPath);
if (!Collections.isEmpty(webLibs)) { if (!Collections.isEmpty(webLibs)) {
if (sb.length() != 0) { if (sb.length() != 0) {
sb.append("\n"); sb.append(BR_TAG);
} }
sb.append(Strings.join("、", webLibs)); sb.append(webLibPath);
sb.append(StringUtils.join(webLibs, SEPARATOR));
} }
String mainContent = sb.toString(); String mainContent = sb.toString();
DetectorType type = this.type(); DetectorType type = this.type();
String header = InterProviderFactory.getProvider().getLocText(type.getTipsLocale()); String header = Toolkit.i18nText(type.getTipsLocale());
return new Message.Simple(header + "\n" + mainContent); return new Message.Simple(header + mainContent);
} }
@NotNull @NotNull

25
designer-base/src/main/java/com/fr/env/detect/impl/converter/ClassConflictConvertor.java vendored

@ -1,6 +1,5 @@
package com.fr.env.detect.impl.converter; package com.fr.env.detect.impl.converter;
import com.fr.common.util.Strings;
import com.fr.design.i18n.Toolkit; import com.fr.design.i18n.Toolkit;
import com.fr.env.detect.base.DetectorConstants; import com.fr.env.detect.base.DetectorConstants;
import com.fr.env.detect.bean.DetectorResult; import com.fr.env.detect.bean.DetectorResult;
@ -10,6 +9,8 @@ import com.fr.env.detect.bean.ExceptionSolution;
import com.fr.env.detect.bean.ExceptionTips; import com.fr.env.detect.bean.ExceptionTips;
import com.fr.env.detect.thowable.ThrowableConverter; import com.fr.env.detect.thowable.ThrowableConverter;
import com.fr.stable.resource.ResourceLoader; import com.fr.stable.resource.ResourceLoader;
import com.fr.third.org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import javax.el.MethodNotFoundException; import javax.el.MethodNotFoundException;
import java.io.IOException; import java.io.IOException;
@ -33,13 +34,15 @@ import java.util.regex.Pattern;
**/ **/
public class ClassConflictConvertor implements ThrowableConverter { public class ClassConflictConvertor implements ThrowableConverter {
private Map<Class<?>, ClassNameConverter> throwableMap = new HashMap<>(); public static final String CLASSES = "classes";
public static final String SEPARATOR = "、";
/** /**
* 获取对应的 JAR 包名称 * 获取对应的 JAR 包名称
*/ */
private static final Pattern JAR_NAME_PATTERN = Pattern.compile("([\\w+-\\.]*\\.jar)"); private static final Pattern JAR_NAME_PATTERN = Pattern.compile("([\\w+-\\.]*\\.jar)");
private final Map<Class<?>, ClassNameConverter> throwableMap = new HashMap<>();
public ClassConflictConvertor() { public ClassConflictConvertor() {
// 类异常 // 类异常
@ -82,7 +85,7 @@ public class ClassConflictConvertor implements ThrowableConverter {
Set<String> allPath = new HashSet<>(); Set<String> allPath = new HashSet<>();
for (String className : classNames) { for (String className : classNames) {
String classFile = "/" + className.replaceAll("\\.", "/") + ".class"; String classFile = convertClass2Path(className);
try { try {
Enumeration<URL> urls = ResourceLoader.getResources(classFile, this.getClass()); Enumeration<URL> urls = ResourceLoader.getResources(classFile, this.getClass());
List<URL> urlList = new ArrayList<>(); List<URL> urlList = new ArrayList<>();
@ -97,9 +100,9 @@ public class ClassConflictConvertor implements ThrowableConverter {
String jar = matcher.group(); String jar = matcher.group();
allPath.add(jar); allPath.add(jar);
} else { } else {
boolean containsClasses = file.contains("classes"); boolean containsClasses = file.contains(CLASSES);
if (containsClasses) { if (containsClasses) {
allPath.add("classes"); allPath.add(CLASSES);
} }
} }
} }
@ -107,15 +110,21 @@ public class ClassConflictConvertor implements ThrowableConverter {
} }
} }
String msg = Strings.join("、", allPath); String msg = StringUtils.join(allPath, SEPARATOR);
DetectorType type = DetectorType.JAR_CONFLICT; DetectorType type = DetectorType.JAR_CONFLICT;
return DetectorResult.exception(type, return DetectorResult.exception(type,
ExceptionTips.create(Toolkit.i18nText(type.getTipsLocale()) + msg), ExceptionTips.create(Toolkit.i18nText(type.getTipsLocale(), msg)),
ExceptionSolution.create(Toolkit.i18nText(type.getSolutionLocale()), DetectorConstants.JAR_HELP_LINK, null), ExceptionSolution.create(Toolkit.i18nText(type.getSolutionLocale()), DetectorConstants.JAR_HELP_LINK, null),
ExceptionLog.create(Toolkit.i18nText(type.getLogLocale()), msg)); ExceptionLog.create(Toolkit.i18nText(type.getLogLocale()), msg));
} }
@NotNull
private String convertClass2Path(String className) {
return "/" + className.replaceAll("\\.", "/") + ".class";
}
private interface ClassNameConverter { private interface ClassNameConverter {
/** /**

46
designer-base/src/main/java/com/fr/env/detect/impl/converter/FineDbDirtyConverter.java vendored

@ -19,9 +19,7 @@ import com.fr.third.org.hibernate.exception.GenericJDBCException;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
/** /**
* 脏数据检测 * 脏数据检测
@ -53,24 +51,22 @@ public class FineDbDirtyConverter implements ThrowableConverter {
*/ */
@Override @Override
public @Nullable DetectorResult convert(Throwable throwable) { public @Nullable DetectorResult convert(Throwable throwable) {
// 检测 Config
Throwable sign = throwable;
while (sign.getClass() != GenericJDBCException.class) {
sign = sign.getCause();
}
Map<String, String> configMap = getAllConfigurationClassName();
StackTraceElement[] stackTrace = sign.getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
String className = stackTraceElement.getClassName();
if (configMap.containsKey(className)) {
Iterator<String> tableNames = ConfigContext.getConfigNames();
while (tableNames.hasNext()) {
String tableName = tableNames.next();
Class<? extends Configuration> configClass = ConfigContext.getConfigClass(tableName);
Configuration configuration = ConfigContext.getConfigInstance(configClass);
try {
// 尝试获取每一个值
configuration.mirror();
} catch (Throwable e) {
DetectorType detectorType = DetectorType.FINE_DB_DIRTY; DetectorType detectorType = DetectorType.FINE_DB_DIRTY;
DetectorResult.DetectorResultBuilder builder = DetectorResult.builder() DetectorResult.DetectorResultBuilder builder = DetectorResult.builder()
.withType(detectorType); .withType(detectorType);
String tableName = configMap.get(className);
String tipsLocale = Toolkit.i18nText(detectorType.getTipsLocale(), tableName); String tipsLocale = Toolkit.i18nText(detectorType.getTipsLocale(), tableName);
String solutionLocale = detectorType.getSolutionLocale(); String solutionLocale = detectorType.getSolutionLocale();
@ -79,7 +75,7 @@ public class FineDbDirtyConverter implements ThrowableConverter {
public String name() { public String name() {
return Toolkit.i18nText("Fine-Design_Basic_Reset_Immediately"); return Toolkit.i18nText("Fine-Design_Basic_Reset_Immediately");
} }
@Override @Override
public void run() { public void run() {
boolean success = false; boolean success = false;
@ -90,6 +86,7 @@ public class FineDbDirtyConverter implements ThrowableConverter {
} catch (Exception e) { } catch (Exception e) {
FineLoggerFactory.getLogger().error(e.getMessage(), e); FineLoggerFactory.getLogger().error(e.getMessage(), e);
} }
// todo 最好的逻辑是,这里应该和 UI 隔离开的
if (!success) { if (!success) {
FineJOptionPane.showMessageDialog(null, FineJOptionPane.showMessageDialog(null,
Toolkit.i18nText("Fine-Design_Error_Finedb_Backup_Reset_Result", Toolkit.i18nText("Fine-Design_Error_Finedb_Backup_Reset_Result",
@ -106,21 +103,8 @@ public class FineDbDirtyConverter implements ThrowableConverter {
return builder.build(); return builder.build();
} }
} }
return null; return DetectorResult.normal(DetectorType.FINE_DB_DIRTY);
} }
private Map<String, String> getAllConfigurationClassName() {
Map<String, String> configMap = new HashMap<>();
Iterator<String> tableNames = ConfigContext.getConfigNames();
while (tableNames.hasNext()) {
String tableName = tableNames.next();
Class<? extends Configuration> configClass = ConfigContext.getConfigClass(tableName);
String className = configClass.getName();
configMap.put(className, tableName);
}
return configMap;
}
} }

5
designer-base/src/main/java/com/fr/env/detect/impl/converter/FineDbLockedConverter.java vendored

@ -5,6 +5,7 @@ import com.fr.env.detect.base.DetectorConstants;
import com.fr.env.detect.bean.DetectorResult; import com.fr.env.detect.bean.DetectorResult;
import com.fr.env.detect.bean.DetectorType; import com.fr.env.detect.bean.DetectorType;
import com.fr.env.detect.thowable.ThrowableConverter; import com.fr.env.detect.thowable.ThrowableConverter;
import com.fr.third.org.apache.commons.lang3.StringUtils;
import com.fr.third.org.hsqldb.HsqlException; import com.fr.third.org.hsqldb.HsqlException;
/** /**
@ -12,6 +13,8 @@ import com.fr.third.org.hsqldb.HsqlException;
**/ **/
public class FineDbLockedConverter implements ThrowableConverter { public class FineDbLockedConverter implements ThrowableConverter {
public static final String LOCK_FILE = "lockFile";
@Override @Override
public boolean accept(Throwable throwable) { public boolean accept(Throwable throwable) {
@ -42,7 +45,7 @@ public class FineDbLockedConverter implements ThrowableConverter {
DetectorType type = DetectorType.FINE_DB_LOCKED; DetectorType type = DetectorType.FINE_DB_LOCKED;
String message = sign.getMessage(); String message = sign.getMessage();
if (message.contains("lock")) { if (StringUtils.containsIgnoreCase(message, LOCK_FILE)) {
return DetectorResult.builder() return DetectorResult.builder()
.withType(type) .withType(type)

5
designer-base/src/main/java/com/fr/env/detect/impl/converter/FineDbPermissionConverter.java vendored

@ -5,6 +5,7 @@ import com.fr.env.detect.base.DetectorConstants;
import com.fr.env.detect.bean.DetectorResult; import com.fr.env.detect.bean.DetectorResult;
import com.fr.env.detect.bean.DetectorType; import com.fr.env.detect.bean.DetectorType;
import com.fr.env.detect.thowable.ThrowableConverter; import com.fr.env.detect.thowable.ThrowableConverter;
import com.fr.third.org.apache.commons.lang3.StringUtils;
import com.fr.third.org.hsqldb.HsqlException; import com.fr.third.org.hsqldb.HsqlException;
/** /**
@ -14,6 +15,8 @@ import com.fr.third.org.hsqldb.HsqlException;
**/ **/
public class FineDbPermissionConverter implements ThrowableConverter { public class FineDbPermissionConverter implements ThrowableConverter {
public static final String PERMISSION = "permission";
@Override @Override
public boolean accept(Throwable throwable) { public boolean accept(Throwable throwable) {
@ -37,7 +40,7 @@ public class FineDbPermissionConverter implements ThrowableConverter {
DetectorType type = DetectorType.FINE_DB_PERMISSION; DetectorType type = DetectorType.FINE_DB_PERMISSION;
String message = sign.getMessage(); String message = sign.getMessage();
if (message.contains("permission")) { if (StringUtils.containsIgnoreCase(message, PERMISSION)) {
return DetectorResult.builder() return DetectorResult.builder()
.withType(type) .withType(type)

17
designer-base/src/main/java/com/fr/env/detect/thowable/ThrowableLogAppender.java vendored

@ -25,24 +25,33 @@ public class ThrowableLogAppender extends AbstractAppender {
} }
private static class ExceptionLogAppenderHolder { private static class ExceptionLogAppenderHolder {
private static final ThrowableLogAppender INSTANCE = new ThrowableLogAppender("exception-detect", null, null, false, null); private static final ThrowableLogAppender INSTANCE = new ThrowableLogAppender("exception-detect-appender", null, null, false, null);
} }
private LogHandler<ThrowableLogAppender> logHandler = toHandler(); private final LogHandler<ThrowableLogAppender> logHandler = toHandler();
@Override @Override
public void append(LogEvent logEvent) { public void append(LogEvent logEvent) {
if (logEvent.getLevel() == Level.ERROR) { if (logEvent.getLevel() == Level.ERROR) {
Throwable thrown = logEvent.getThrown(); Throwable thrown = logEvent.getThrown();
ThrowableStore.getInstance().add(thrown); if (thrown != null) {
ThrowableStore.getInstance().add(thrown);
}
} }
} }
private LogHandler<ThrowableLogAppender> toHandler() { private LogHandler<ThrowableLogAppender> toHandler() {
final ThrowableLogAppender appender = this; final ThrowableLogAppender appender = this;
return () -> appender; appender.start();
LogHandler<ThrowableLogAppender> handler = new LogHandler<ThrowableLogAppender>() {
@Override
public ThrowableLogAppender getHandler() {
return appender;
}
};
return handler;
} }

8
designer-base/src/main/java/com/fr/env/detect/ui/DetectorErrorDialog.java vendored

@ -5,7 +5,6 @@ import com.fr.design.gui.ibutton.UIButton;
import com.fr.design.gui.ilable.UILabel; import com.fr.design.gui.ilable.UILabel;
import com.fr.design.i18n.Toolkit; import com.fr.design.i18n.Toolkit;
import com.fr.design.layout.FRGUIPaneFactory; import com.fr.design.layout.FRGUIPaneFactory;
import com.fr.design.layout.VerticalFlowLayout;
import com.fr.design.utils.ColorUtils; import com.fr.design.utils.ColorUtils;
import com.fr.design.utils.gui.GUICoreUtils; import com.fr.design.utils.gui.GUICoreUtils;
import com.fr.env.detect.base.DetectorUtil; import com.fr.env.detect.base.DetectorUtil;
@ -34,6 +33,7 @@ import java.util.List;
/** /**
* 未知错误框 * 未知错误框
* todo 其实这里可以将多个 error-dialog 抽象在一起的 时间不够 简单写写 * todo 其实这里可以将多个 error-dialog 抽象在一起的 时间不够 简单写写
* {@link com.fr.design.dialog.ErrorDialog}
* *
* created by Harrison on 2022/05/29 * created by Harrison on 2022/05/29
**/ **/
@ -63,19 +63,19 @@ public class DetectorErrorDialog extends JDialog implements ActionListener {
UILabel detailDesc = new UILabel(Toolkit.i18nText("Fine-Design_Problem_Detail_Message")); UILabel detailDesc = new UILabel(Toolkit.i18nText("Fine-Design_Problem_Detail_Message"));
centerPane.add(detailDesc, BorderLayout.NORTH); centerPane.add(detailDesc, BorderLayout.NORTH);
JPanel detailPanel = FRGUIPaneFactory.createVerticalFlowLayout_Pane(true, VerticalFlowLayout.TOP, 0, 10); JPanel detailPanel = FRGUIPaneFactory.createBorderLayout_S_Pane();
detailPanel.setBorder(BorderFactory.createEmptyBorder(0, 20, 10, 10)); detailPanel.setBorder(BorderFactory.createEmptyBorder(0, 20, 10, 10));
for (DetectorResult result : results) { for (DetectorResult result : results) {
ExceptionTips tips = result.getTips(); ExceptionTips tips = result.getTips();
if (tips != null) { if (tips != null) {
Message tipsMsg = tips.getMessage(); Message tipsMsg = tips.getMessage();
detailPanel.add(DetectorUtil.convert2Component(tipsMsg)); detailPanel.add(DetectorUtil.convert2TextComponent(tipsMsg), BorderLayout.NORTH);
} }
ExceptionSolution solution = result.getSolution(); ExceptionSolution solution = result.getSolution();
if (solution != null) { if (solution != null) {
Message solutionMsg = solution.getMessage(); Message solutionMsg = solution.getMessage();
detailPanel.add(DetectorUtil.convert2Component(solutionMsg)); detailPanel.add(DetectorUtil.convert2TextComponent(solutionMsg), BorderLayout.CENTER);
} }
} }

28
designer-base/src/main/java/com/fr/env/detect/ui/EnvDetectorAction.java vendored

@ -0,0 +1,28 @@
package com.fr.env.detect.ui;
import com.fr.design.actions.UpdateAction;
import com.fr.design.i18n.Toolkit;
import com.fr.design.mainframe.DesignerContext;
import java.awt.event.ActionEvent;
/**
* 工具栏里面的行为
*
* created by Harrison on 2022/05/29
**/
public class EnvDetectorAction extends UpdateAction {
public EnvDetectorAction() {
this.setName(Toolkit.i18nText("Fine-Design_Basic_Detect_Toolbar_Title"));
this.setSmallIcon("com/fr/env/detect/detect_normal.svg");
}
@Override
public void actionPerformed(ActionEvent e) {
EnvDetectorDialog dialog = new EnvDetectorDialog(DesignerContext.getDesignerFrame());
dialog.setVisible(true);
}
}

8
designer-base/src/main/java/com/fr/env/detect/ui/EnvDetectorDialog.java vendored

@ -17,7 +17,7 @@ import com.fr.design.utils.gui.GUICoreUtils;
import com.fr.design.utils.gui.GUIPaintUtils; import com.fr.design.utils.gui.GUIPaintUtils;
import com.fr.env.detect.base.DetectorBridge; import com.fr.env.detect.base.DetectorBridge;
import com.fr.env.detect.base.DetectorUtil; import com.fr.env.detect.base.DetectorUtil;
import com.fr.env.detect.base.ExceptionDetectorConfig; import com.fr.env.detect.base.EnvDetectorConfig;
import com.fr.env.detect.bean.DetectorResult; import com.fr.env.detect.bean.DetectorResult;
import com.fr.env.detect.bean.DetectorStatus; import com.fr.env.detect.bean.DetectorStatus;
import com.fr.env.detect.bean.DetectorType; import com.fr.env.detect.bean.DetectorType;
@ -79,7 +79,7 @@ public class EnvDetectorDialog extends JDialog {
/* config model */ /* config model */
private boolean detectOpen = ExceptionDetectorConfig.getInstance().isOpen(); private boolean detectOpen = EnvDetectorConfig.getInstance().isEnabled();
public EnvDetectorDialog(Frame owner) { public EnvDetectorDialog(Frame owner) {
this(owner, null); this(owner, null);
@ -213,7 +213,7 @@ public class EnvDetectorDialog extends JDialog {
if (buttonStatus.isExecuting()) { if (buttonStatus.isExecuting()) {
// 执行结束 // 执行结束
buttonStatus = buttonStatus.next(); buttonStatus = EnvDetectorButtonStatus.A_NEW;
UIUtil.invokeLaterIfNeeded(() -> detectButton.setText(buttonStatus.getDesc())); UIUtil.invokeLaterIfNeeded(() -> detectButton.setText(buttonStatus.getDesc()));
} }
} }
@ -366,7 +366,7 @@ public class EnvDetectorDialog extends JDialog {
setVisible(false); setVisible(false);
dispose(); dispose();
// 配置处理 // 配置处理
ExceptionDetectorConfig.getInstance().setOpen(this.detectOpen); EnvDetectorConfig.getInstance().setEnabled(this.detectOpen);
}); });
actionsPanel.add(confirmButton, BorderLayout.WEST); actionsPanel.add(confirmButton, BorderLayout.WEST);

2
designer-base/src/main/java/com/fr/env/detect/ui/EnvDetectorItem.java vendored

@ -4,6 +4,8 @@ import com.fr.env.detect.bean.DetectorResult;
import com.fr.env.detect.bean.DetectorType; import com.fr.env.detect.bean.DetectorType;
/** /**
* 环境检测条目
*
* created by Harrison on 2022/05/27 * created by Harrison on 2022/05/27
**/ **/
public class EnvDetectorItem { public class EnvDetectorItem {

17
designer-base/src/main/java/com/fr/env/detect/ui/EnvDetectorModel.java vendored

@ -12,17 +12,28 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**
* 环境检测数据格式
*
* created by Harrison on 2022/05/27 * created by Harrison on 2022/05/27
**/ **/
public class EnvDetectorModel { public class EnvDetectorModel {
private Map<DetectorType.Kind, List<EnvDetectorItem>> itemMap = new LinkedHashMap<>(); /**
* 类型 -> list [{type-result}]
*/
private final Map<DetectorType.Kind, List<EnvDetectorItem>> itemMap = new LinkedHashMap<>();
public EnvDetectorModel() { public EnvDetectorModel() {
itemMap.put(DetectorType.Kind.JAR, Lists.newArrayList(new EnvDetectorItem(DetectorType.LACK_OF_JAR), new EnvDetectorItem(DetectorType.JAR_IN_CONSISTENCE), new EnvDetectorItem(DetectorType.JAR_CONFLICT))); itemMap.put(DetectorType.Kind.JAR, Lists.newArrayList(
new EnvDetectorItem(DetectorType.LACK_OF_JAR),
new EnvDetectorItem(DetectorType.JAR_IN_CONSISTENCE),
new EnvDetectorItem(DetectorType.JAR_CONFLICT)));
itemMap.put(DetectorType.Kind.FINE_DB, Lists.newArrayList(new EnvDetectorItem(DetectorType.FINE_DB_LOCKED), new EnvDetectorItem(DetectorType.FINE_DB_PERMISSION), new EnvDetectorItem(DetectorType.FINE_DB_DIRTY))); itemMap.put(DetectorType.Kind.FINE_DB, Lists.newArrayList(
new EnvDetectorItem(DetectorType.FINE_DB_LOCKED),
new EnvDetectorItem(DetectorType.FINE_DB_PERMISSION),
new EnvDetectorItem(DetectorType.FINE_DB_DIRTY)));
} }
public void update(DetectorType type, DetectorResult result) { public void update(DetectorType type, DetectorResult result) {

3
designer-base/src/main/resources/com/fr/env/detect/detect.svg vendored

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2H14.5V5H1.5V2ZM1.5 6L1.5 14H14.5V6H1.5ZM15.5 6V14C15.5 14.5523 15.0523 15 14.5 15H1.5C0.947715 15 0.5 14.5523 0.5 14V2C0.5 1.44772 0.947715 1 1.5 1H14.5C15.0523 1 15.5 1.44772 15.5 2V5V6ZM6.67926 6.87216L7.10381 7.79011L8.53859 10.8923L9.06189 9.94086L9.20436 9.68182H9.5H12.5H13V10.6818H12.5H9.79564L8.93811 12.241L8.46141 13.1077L8.04619 12.2099L6.62074 9.12784L5.94232 10.415L5.80166 10.6818H5.5H3.5H3V9.68182H3.5H5.19834L6.20768 7.76686L6.67926 6.87216ZM3.5 3H2.5V4H3.5V3ZM4 3H5V4H4V3ZM6.5 3H5.5V4H6.5V3Z" fill="#333334"/>
</svg>

After

Width:  |  Height:  |  Size: 685 B

3
designer-base/src/main/resources/com/fr/env/detect/detect_normal.svg vendored

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2H14.5V5H1.5V2ZM1.5 6L1.5 14H14.5V6H1.5ZM15.5 6V14C15.5 14.5523 15.0523 15 14.5 15H1.5C0.947715 15 0.5 14.5523 0.5 14V2C0.5 1.44772 0.947715 1 1.5 1H14.5C15.0523 1 15.5 1.44772 15.5 2V5V6ZM6.67926 6.87216L7.10381 7.79011L8.53859 10.8923L9.06189 9.94086L9.20436 9.68182H9.5H12.5H13V10.6818H12.5H9.79564L8.93811 12.241L8.46141 13.1077L8.04619 12.2099L6.62074 9.12784L5.94232 10.415L5.80166 10.6818H5.5H3.5H3V9.68182H3.5H5.19834L6.20768 7.76686L6.67926 6.87216ZM3.5 3H2.5V4H3.5V3ZM4 3H5V4H4V3ZM6.5 3H5.5V4H6.5V3Z" fill="#333334"/>
</svg>

After

Width:  |  Height:  |  Size: 685 B

Loading…
Cancel
Save