> transfer(Object source) {
+ return transfer(null, source);
+ }
+
+
+ /**
+ * 溯源异常转换器.通常配合{@link Mono#onErrorResume(Function)}使用.
+ *
+ * 转换逻辑:
+ *
+ * 1. 如果捕获的异常不是TraceSourceException,则直接创建新的TraceSourceException并返回.
+ *
+ * 2. 如果捕获的异常是TraceSourceException,并且上下文没有指定{@link TraceSourceException#deepTraceContext()},
+ * 则修改捕获的TraceSourceException异常中的source.如果上下文中指定了{@link TraceSourceException#deepTraceContext()}
+ * 则创建新的TraceSourceException
+ *
+ *
{@code
+ *
+ * doSomething()
+ * .onErrorResume(TraceSourceException.transfer(data))
+ *
+ * }
+ *
+ * @param operation 操作名称
+ * @param source 源
+ * @param 泛型
+ * @return 转换器
+ * @see reactor.core.publisher.Flux#onErrorResume(Function)
+ * @see Mono#onErrorResume(Function)
+ */
+ public static Function> 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 tryGetOperationLocalizedReactive(Throwable err) {
+ return LocaleUtils
+ .currentReactive()
+ .handle((locale, sink) -> {
+ String opt = tryGetOperationLocalized(err, locale);
+ if (opt != null) {
+ sink.next(opt);
+ }
+ });
+ }
+}
diff --git a/hsweb-core/src/main/java/org/hswebframework/web/i18n/LocaleUtils.java b/hsweb-core/src/main/java/org/hswebframework/web/i18n/LocaleUtils.java
index d3c66ade3..fc01cef1a 100644
--- a/hsweb-core/src/main/java/org/hswebframework/web/i18n/LocaleUtils.java
+++ b/hsweb-core/src/main/java/org/hswebframework/web/i18n/LocaleUtils.java
@@ -1,15 +1,18 @@
package org.hswebframework.web.i18n;
+import io.netty.util.concurrent.FastThreadLocal;
+import lombok.AllArgsConstructor;
import org.hswebframework.web.exception.I18nSupportException;
import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscription;
import org.springframework.context.MessageSource;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
-import reactor.core.publisher.Signal;
-import reactor.core.publisher.SignalType;
+import reactor.core.CoreSubscriber;
+import reactor.core.publisher.*;
import reactor.util.context.Context;
+import javax.annotation.Nonnull;
import java.util.Locale;
+import java.util.concurrent.Callable;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
@@ -23,7 +26,6 @@ import java.util.function.Function;
* {@link LocaleUtils#current()}
* {@link LocaleUtils#currentReactive()}
* {@link LocaleUtils#resolveMessageReactive(String, Object...)}
- * {@link LocaleUtils#doOnNext(BiConsumer)}
*
*
* @author zhouhao
@@ -33,7 +35,7 @@ public final class LocaleUtils {
public static final Locale DEFAULT_LOCALE = Locale.getDefault();
- private static final ThreadLocal CONTEXT_THREAD_LOCAL = new ThreadLocal<>();
+ private static final FastThreadLocal CONTEXT_THREAD_LOCAL = new FastThreadLocal<>();
static MessageSource messageSource = UnsupportedMessageSource.instance();
@@ -63,11 +65,12 @@ public final class LocaleUtils {
* @return 返回值
*/
public static R doWith(T data, Locale locale, BiFunction mapper) {
+ Locale old = CONTEXT_THREAD_LOCAL.get();
try {
CONTEXT_THREAD_LOCAL.set(locale);
return mapper.apply(data, locale);
} finally {
- CONTEXT_THREAD_LOCAL.remove();
+ CONTEXT_THREAD_LOCAL.set(old);
}
}
@@ -78,11 +81,12 @@ public final class LocaleUtils {
* @param consumer 任务
*/
public static void doWith(Locale locale, Consumer consumer) {
+ Locale old = CONTEXT_THREAD_LOCAL.get();
try {
CONTEXT_THREAD_LOCAL.set(locale);
consumer.accept(locale);
} finally {
- CONTEXT_THREAD_LOCAL.remove();
+ CONTEXT_THREAD_LOCAL.set(old);
}
}
@@ -112,6 +116,23 @@ public final class LocaleUtils {
.subscriberContext()
.map(ctx -> ctx.getOrDefault(Locale.class, DEFAULT_LOCALE));
}
+ public static Mono doInReactive(Callable call) {
+ return currentReactive()
+ .handle((locale, sink) -> {
+ Locale old = CONTEXT_THREAD_LOCAL.get();
+ try {
+ CONTEXT_THREAD_LOCAL.set(locale);
+ T data = call.call();
+ if (data != null) {
+ sink.next(data);
+ }
+ } catch (Throwable e) {
+ sink.error(e);
+ } finally {
+ CONTEXT_THREAD_LOCAL.set(old);
+ }
+ });
+ }
/**
* 响应式方式解析出异常的区域消息,并进行结果转换.
@@ -450,4 +471,92 @@ public final class LocaleUtils {
return doOn(SignalType.ON_ERROR, (s, l) -> operation.accept(s.getThrowable(), l));
}
+ public static Flux transform(Flux flux) {
+ return new LocaleFlux<>(flux);
+ }
+
+ public static Mono transform(Mono mono) {
+ return new LocaleMono<>(mono);
+ }
+
+ @AllArgsConstructor
+ static class LocaleMono extends Mono {
+ private final Mono source;
+
+ @Override
+ public void subscribe(@Nonnull CoreSubscriber super T> actual) {
+ doWith(actual,
+ actual.currentContext().getOrDefault(Locale.class, DEFAULT_LOCALE),
+ (a, l) -> {
+ source.subscribe(
+ new LocaleSwitchSubscriber<>(a)
+ );
+ return null;
+ }
+ );
+ }
+ }
+
+ @AllArgsConstructor
+ static class LocaleFlux extends Flux {
+ private final Flux source;
+
+ @Override
+ public void subscribe(@Nonnull CoreSubscriber super T> actual) {
+ doWith(actual,
+ actual.currentContext().getOrDefault(Locale.class, DEFAULT_LOCALE),
+ (a, l) -> {
+ source.subscribe(
+ new LocaleSwitchSubscriber<>(a)
+ );
+ return null;
+ }
+ );
+ }
+ }
+
+ @AllArgsConstructor
+ static class LocaleSwitchSubscriber extends BaseSubscriber {
+ private final CoreSubscriber actual;
+
+ @Override
+ @Nonnull
+ public Context currentContext() {
+ return actual
+ .currentContext();
+ }
+
+ @Override
+ protected void hookOnSubscribe(@Nonnull Subscription subscription) {
+ actual.onSubscribe(this);
+ }
+
+ private Locale current() {
+ return currentContext()
+ .getOrDefault(Locale.class, DEFAULT_LOCALE);
+ }
+
+ @Override
+ protected void hookOnComplete() {
+ doWith(current(), (l) -> actual.onComplete());
+ }
+
+ @Override
+ protected void hookOnError(@Nonnull Throwable error) {
+
+ doWith(error, current(), (v, l) -> {
+ actual.onError(v);
+ return null;
+ });
+ }
+
+ @Override
+ protected void hookOnNext(@Nonnull T value) {
+
+ doWith(value, current(), (v, l) -> {
+ actual.onNext(v);
+ return null;
+ });
+ }
+ }
}
diff --git a/hsweb-core/src/main/java/org/hswebframework/web/i18n/WebFluxLocaleFilter.java b/hsweb-core/src/main/java/org/hswebframework/web/i18n/WebFluxLocaleFilter.java
index 748d7faee..61a34e1d6 100644
--- a/hsweb-core/src/main/java/org/hswebframework/web/i18n/WebFluxLocaleFilter.java
+++ b/hsweb-core/src/main/java/org/hswebframework/web/i18n/WebFluxLocaleFilter.java
@@ -15,6 +15,7 @@ public class WebFluxLocaleFilter implements WebFilter {
public Mono filter(@NonNull ServerWebExchange exchange, WebFilterChain chain) {
return chain
.filter(exchange)
+ .as(LocaleUtils::transform)
.subscriberContext(LocaleUtils.useLocale(getLocaleContext(exchange)));
}
diff --git a/hsweb-core/src/main/java/org/hswebframework/web/id/IDGenerator.java b/hsweb-core/src/main/java/org/hswebframework/web/id/IDGenerator.java
index 0b8dca66c..844039d8d 100644
--- a/hsweb-core/src/main/java/org/hswebframework/web/id/IDGenerator.java
+++ b/hsweb-core/src/main/java/org/hswebframework/web/id/IDGenerator.java
@@ -18,13 +18,8 @@
package org.hswebframework.web.id;
-import org.hswebframework.utils.RandomUtil;
import org.hswebframework.web.utils.DigestUtils;
-import java.math.BigInteger;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
/**
* ID生成器,用于生成ID
*
@@ -53,7 +48,7 @@ public interface IDGenerator {
/**
* 随机字符
*/
- IDGenerator RANDOM = RandomUtil::randomChar;
+ IDGenerator RANDOM = RandomIdGenerator.GLOBAL;
/**
* md5(uuid())
diff --git a/hsweb-core/src/main/java/org/hswebframework/web/id/RandomIdGenerator.java b/hsweb-core/src/main/java/org/hswebframework/web/id/RandomIdGenerator.java
new file mode 100644
index 000000000..93a87046a
--- /dev/null
+++ b/hsweb-core/src/main/java/org/hswebframework/web/id/RandomIdGenerator.java
@@ -0,0 +1,64 @@
+package org.hswebframework.web.id;
+
+import io.netty.util.concurrent.FastThreadLocal;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+
+import java.util.Base64;
+import java.util.concurrent.ThreadLocalRandom;
+
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class RandomIdGenerator implements IDGenerator {
+
+ // java -Dgenerator.random.instance-id=8
+ static final RandomIdGenerator GLOBAL = new RandomIdGenerator(
+ Integer.getInteger("generator.random.instance-id", ThreadLocalRandom.current().nextInt(1,127)).byteValue()
+ );
+
+ static final Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding();
+
+ private final static FastThreadLocal HOLDER = new FastThreadLocal() {
+ @Override
+ protected byte[] initialValue() {
+ return new byte[24];
+ }
+ };
+
+ private final byte instanceId;
+
+ public static RandomIdGenerator create(byte instanceId) {
+ return new RandomIdGenerator(instanceId);
+ }
+
+ public String generate() {
+ long now = System.currentTimeMillis();
+ byte[] value = HOLDER.get();
+ value[0] = instanceId;
+
+ value[1] = (byte) (now >>> 32);
+ value[2] = (byte) (now >>> 24);
+ value[3] = (byte) (now >>> 16);
+ value[4] = (byte) (now >>> 8);
+ value[5] = (byte) (now);
+
+ nextBytes(value, 6, 8);
+ nextBytes(value, 8, 16);
+ nextBytes(value, 16, 24);
+ return encoder.encodeToString(value);
+ }
+
+
+ private static void nextBytes(byte[] bytes, int offset, int len) {
+ ThreadLocalRandom random = ThreadLocalRandom.current();
+
+ for (int i = offset; i < len; ) {
+ for (int rnd = random.nextInt(),
+ n = Math.min(len - i, Integer.SIZE / Byte.SIZE);
+ n-- > 0; rnd >>= Byte.SIZE) {
+ bytes[i++] = (byte) rnd;
+ }
+ }
+
+ }
+
+}
diff --git a/hsweb-core/src/main/java/org/hswebframework/web/id/SnowflakeIdGenerator.java b/hsweb-core/src/main/java/org/hswebframework/web/id/SnowflakeIdGenerator.java
index 373ac01a5..18328f602 100644
--- a/hsweb-core/src/main/java/org/hswebframework/web/id/SnowflakeIdGenerator.java
+++ b/hsweb-core/src/main/java/org/hswebframework/web/id/SnowflakeIdGenerator.java
@@ -5,26 +5,27 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
+import java.util.concurrent.ThreadLocalRandom;
@Slf4j
public class SnowflakeIdGenerator {
- private long workerId;
- private long dataCenterId;
+ private final long workerId;
+ private final long dataCenterId;
private long sequence = 0L;
- private long twepoch = 1288834974657L;
+ private final long twepoch = 1288834974657L;
- private long workerIdBits = 5L;
- private long datacenterIdBits = 5L;
- private long maxWorkerId = -1L ^ (-1L << workerIdBits);
- private long maxDataCenterId = -1L ^ (-1L << datacenterIdBits);
- private long sequenceBits = 12L;
+ private final long workerIdBits = 5L;
+ private final long datacenterIdBits = 5L;
+ private final long maxWorkerId = ~(-1L << workerIdBits);
+ private final long maxDataCenterId = ~(-1L << datacenterIdBits);
+ private final long sequenceBits = 12L;
- private long workerIdShift = sequenceBits;
- private long datacenterIdShift = sequenceBits + workerIdBits;
- private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
- private long sequenceMask = -1L ^ (-1L << sequenceBits);
+ private final long workerIdShift = sequenceBits;
+ private final long datacenterIdShift = sequenceBits + workerIdBits;
+ private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
+ private final long sequenceMask = ~(-1L << sequenceBits);
private long lastTimestamp = -1L;
@@ -41,7 +42,15 @@ public class SnowflakeIdGenerator {
return generator;
}
- public SnowflakeIdGenerator(long workerId, long dataCenterId) {
+ public static SnowflakeIdGenerator create(int workerId, int dataCenterId) {
+ return new SnowflakeIdGenerator(workerId, dataCenterId);
+ }
+
+ public static SnowflakeIdGenerator create() {
+ return create(ThreadLocalRandom.current().nextInt(31), ThreadLocalRandom.current().nextInt(31));
+ }
+
+ private SnowflakeIdGenerator(long workerId, long dataCenterId) {
// sanity check for workerId
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
diff --git a/hsweb-core/src/main/java/org/hswebframework/web/utils/DigestUtils.java b/hsweb-core/src/main/java/org/hswebframework/web/utils/DigestUtils.java
index ac58189f4..4c863a9c8 100644
--- a/hsweb-core/src/main/java/org/hswebframework/web/utils/DigestUtils.java
+++ b/hsweb-core/src/main/java/org/hswebframework/web/utils/DigestUtils.java
@@ -1,6 +1,9 @@
package org.hswebframework.web.utils;
+import io.netty.util.concurrent.FastThreadLocal;
+import io.seruco.encoding.base62.Base62;
import org.apache.commons.codec.binary.Hex;
+import org.hswebframework.web.id.RandomIdGenerator;
import java.security.MessageDigest;
import java.util.function.Consumer;
@@ -8,10 +11,33 @@ import java.util.function.Supplier;
public class DigestUtils {
- public static final ThreadLocal md5 = ThreadLocal.withInitial(org.apache.commons.codec.digest.DigestUtils::getMd5Digest);
- public static final ThreadLocal sha256 = ThreadLocal.withInitial(org.apache.commons.codec.digest.DigestUtils::getSha256Digest);
- public static final ThreadLocal sha1 = ThreadLocal.withInitial(org.apache.commons.codec.digest.DigestUtils::getSha1Digest);
+ public static final FastThreadLocal md5 = new FastThreadLocal() {
+ @Override
+ protected MessageDigest initialValue() {
+ return org.apache.commons.codec.digest.DigestUtils.getMd5Digest();
+ }
+ };
+ public static final FastThreadLocal sha256 = new FastThreadLocal() {
+ @Override
+ protected MessageDigest initialValue() {
+ return org.apache.commons.codec.digest.DigestUtils.getSha256Digest();
+ }
+ };
+
+ public static final FastThreadLocal sha1 = new FastThreadLocal() {
+ @Override
+ protected MessageDigest initialValue() {
+ return org.apache.commons.codec.digest.DigestUtils.getSha1Digest();
+ }
+ };
+
+ private static final Base62 base62 = Base62.createInstance();
+
+
+ public static Base62 base62(){
+ return base62;
+ }
public static byte[] md5(Consumer digestHandler) {
return digest(md5::get, digestHandler);
}
@@ -47,6 +73,9 @@ public class DigestUtils {
public static String md5Hex(String str) {
return Hex.encodeHexString(md5(str.getBytes()));
}
+ public static String md5Base62(String str) {
+ return new String(base62.encode(md5(str.getBytes())));
+ }
public static byte[] sha256(byte[] data) {
return org.apache.commons.codec.digest.DigestUtils.digest(sha256.get(), data);
diff --git a/hsweb-core/src/main/java/org/hswebframework/web/validator/ValidatorUtils.java b/hsweb-core/src/main/java/org/hswebframework/web/validator/ValidatorUtils.java
index 3db2fad56..4c5a1a748 100644
--- a/hsweb-core/src/main/java/org/hswebframework/web/validator/ValidatorUtils.java
+++ b/hsweb-core/src/main/java/org/hswebframework/web/validator/ValidatorUtils.java
@@ -38,7 +38,7 @@ public final class ValidatorUtils {
public static T tryValidate(T bean, Class>... group) {
Set> violations = getValidator().validate(bean, group);
if (!violations.isEmpty()) {
- throw new ValidationException(violations);
+ throw new ValidationException(violations).withSource(bean);
}
return bean;
@@ -47,7 +47,7 @@ public final class ValidatorUtils {
public static T tryValidate(T bean, String property, Class>... group) {
Set> violations = getValidator().validateProperty(bean, property, group);
if (!violations.isEmpty()) {
- throw new ValidationException(violations);
+ throw new ValidationException(violations).withSource(bean);
}
return bean;
@@ -56,7 +56,7 @@ public final class ValidatorUtils {
public static void tryValidate(Class bean, String property, Object value, Class>... group) {
Set> violations = getValidator().validateValue(bean, property, value, group);
if (!violations.isEmpty()) {
- throw new ValidationException(violations);
+ throw new ValidationException(violations).withSource(value);
}
}
diff --git a/hsweb-core/src/test/java/org/hswebframework/web/bean/FastBeanCopierTest.java b/hsweb-core/src/test/java/org/hswebframework/web/bean/FastBeanCopierTest.java
index fe7e177e4..0c73a2b45 100644
--- a/hsweb-core/src/test/java/org/hswebframework/web/bean/FastBeanCopierTest.java
+++ b/hsweb-core/src/test/java/org/hswebframework/web/bean/FastBeanCopierTest.java
@@ -1,16 +1,14 @@
package org.hswebframework.web.bean;
import com.google.common.collect.ImmutableMap;
-import jdk.nashorn.internal.objects.annotations.Getter;
+import lombok.Getter;
+import lombok.Setter;
import org.junit.Assert;
import org.junit.Test;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -61,6 +59,43 @@ public class FastBeanCopierTest {
}
+ @Test
+ public void testMapList() {
+ Map data = new HashMap<>();
+ data.put("templates", new HashMap() {
+ {
+ put("0", Collections.singletonMap("name", "test"));
+ put("1", Collections.singletonMap("name", "test"));
+ }
+ });
+
+ Config config = FastBeanCopier.copy(data, new Config());
+
+ Assert.assertNotNull(config);
+ Assert.assertNotNull(config.templates);
+ System.out.println(config.templates);
+ Assert.assertEquals(2,config.templates.size());
+
+
+ }
+
+ @Getter
+ @Setter
+ public static class Config {
+ private List templates;
+ }
+
+ @Getter
+ @Setter
+ public static class Template {
+ private String name;
+
+ @Override
+ public String toString() {
+ return "name:"+name;
+ }
+ }
+
@Test
public void testCopyMap() {
@@ -87,10 +122,10 @@ public class FastBeanCopierTest {
@Test
public void testProxy() {
- AtomicReference