Browse Source

优化异常处理

zhouhao 2 years ago
parent
commit
1284a3ec21

+ 26 - 0
hsweb-core/src/main/java/org/hswebframework/web/exception/I18nSupportException.java

@@ -5,6 +5,7 @@ import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.Setter;
 import org.hswebframework.web.i18n.LocaleUtils;
+import org.springframework.util.StringUtils;
 import reactor.core.publisher.Mono;
 
 import java.util.Locale;
@@ -70,4 +71,29 @@ public class I18nSupportException extends TraceSourceException {
                 .currentReactive()
                 .map(this::getLocalizedMessage);
     }
+
+    public static String tryGetLocalizedMessage(Throwable error, Locale locale) {
+        if (error instanceof I18nSupportException) {
+            return ((I18nSupportException) error).getLocalizedMessage(locale);
+        }
+        String msg = error.getMessage();
+
+        if (!StringUtils.hasText(msg)) {
+            msg = "error." + error.getClass().getSimpleName();
+        }
+        if (msg.contains(".")) {
+            return LocaleUtils.resolveMessage(msg, locale, msg);
+        }
+        return msg;
+    }
+
+    public static String tryGetLocalizedMessage(Throwable error) {
+        return tryGetLocalizedMessage(error, LocaleUtils.current());
+    }
+
+    public static Mono<String> tryGetLocalizedMessageReactive(Throwable error) {
+        return LocaleUtils
+                .currentReactive()
+                .map(locale -> tryGetLocalizedMessage(error, locale));
+    }
 }

+ 113 - 4
hsweb-core/src/main/java/org/hswebframework/web/exception/TraceSourceException.java

@@ -1,7 +1,14 @@
 package org.hswebframework.web.exception;
 
+import org.hswebframework.web.i18n.LocaleUtils;
+import org.springframework.util.StringUtils;
+import reactor.core.publisher.Mono;
+import reactor.util.context.Context;
+
 import javax.annotation.Nullable;
+import java.util.Locale;
 import java.util.Optional;
+import java.util.function.Function;
 
 /**
  * 支持溯源的异常,通过{@link TraceSourceException#withSource(Object) }来标识异常的源头.
@@ -12,6 +19,11 @@ import java.util.Optional;
  */
 public class TraceSourceException extends RuntimeException {
 
+    private static final String deepTraceKey = TraceSourceException.class.getName() + "_deep";
+    private static final Context deepTraceContext = Context.of(deepTraceKey, true);
+
+    private String operation;
+
     private Object source;
 
     public TraceSourceException() {
@@ -30,21 +42,118 @@ public class TraceSourceException extends RuntimeException {
         super(message, e);
     }
 
-    public Optional<Object> sourceOptional() {
-        return Optional.ofNullable(source);
-    }
-
     @Nullable
     public Object getSource() {
         return source;
     }
 
+    @Nullable
+    public String getOperation() {
+        return operation;
+    }
+
     public TraceSourceException withSource(Object source) {
         this.source = source;
         return self();
     }
 
+    public TraceSourceException withSource(String operation, Object source) {
+        this.operation = operation;
+        this.source = source;
+        return self();
+    }
+
     protected TraceSourceException self() {
         return this;
     }
+
+    /**
+     * 深度溯源上下文,用来标识是否是深度溯源的异常.开启深度追踪后,会创建新的{@link  TraceSourceException}对象.
+     *
+     * @return 上下文
+     * @see reactor.core.publisher.Flux#subscriberContext(Context)
+     * @see Mono#subscriberContext(Context)
+     */
+    public static Context deepTraceContext() {
+        return deepTraceContext;
+    }
+
+    public static <T> Function<Throwable, Mono<T>> transfer(Object source) {
+        return transfer(null, source);
+    }
+
+
+    /**
+     * 溯源异常转换器.通常配合{@link  Mono#onErrorResume(Function)}使用.
+     * <p>
+     * 转换逻辑:
+     * <p>
+     * 1. 如果捕获的异常不是TraceSourceException,则直接创建新的TraceSourceException并返回.
+     * <p>
+     * 2. 如果捕获的异常是TraceSourceException,并且上下文没有指定{@link TraceSourceException#deepTraceContext()},
+     * 则修改捕获的TraceSourceException异常中的source.如果上下文中指定了{@link TraceSourceException#deepTraceContext()}
+     * 则创建新的TraceSourceException
+     *
+     * <pre>{@code
+     *
+     *  doSomething()
+     *  .onErrorResume(TraceSourceException.transfer(data))
+     *
+     * }</pre>
+     *
+     * @param operation 操作名称
+     * @param source    源
+     * @param <T>       泛型
+     * @return 转换器
+     * @see reactor.core.publisher.Flux#onErrorResume(Function)
+     * @see Mono#onErrorResume(Function)
+     */
+    public static <T> Function<Throwable, Mono<T>> transfer(String operation, Object source) {
+        if (source == null && operation == null) {
+            return Mono::error;
+        }
+        return err -> {
+            if (err instanceof TraceSourceException) {
+                return Mono
+                        .deferWithContext(ctx -> {
+                            if (ctx.hasKey(deepTraceKey)) {
+                                return Mono.error(new TraceSourceException(err).withSource(operation,source));
+                            } else {
+                                return Mono.error(((TraceSourceException) err).withSource(operation,source));
+                            }
+                        });
+            }
+            return Mono.error(new TraceSourceException(err).withSource(operation,source));
+        };
+    }
+
+    public static Object tryGetSource(Throwable err) {
+        if (err instanceof TraceSourceException) {
+            return ((TraceSourceException) err).getSource();
+        }
+        return null;
+    }
+
+    public static String tryGetOperation(Throwable err) {
+        if (err instanceof TraceSourceException) {
+            return ((TraceSourceException) err).getOperation();
+        }
+        return null;
+    }
+
+    public static String tryGetOperationLocalized(Throwable err, Locale locale) {
+        String opt = tryGetOperation(err);
+        return StringUtils.hasText(opt) ? LocaleUtils.resolveMessage(opt, locale, opt) : opt;
+    }
+
+    public static Mono<String> tryGetOperationLocalizedReactive(Throwable err) {
+        return LocaleUtils
+                .currentReactive()
+                .handle((locale, sink) -> {
+                    String opt = tryGetOperationLocalized(err, locale);
+                    if (opt != null) {
+                        sink.next(opt);
+                    }
+                });
+    }
 }