diff --git a/develop-toolkit-base/pom.xml b/develop-toolkit-base/pom.xml
index 83dacd9..5128436 100644
--- a/develop-toolkit-base/pom.xml
+++ b/develop-toolkit-base/pom.xml
@@ -3,7 +3,7 @@
develop-toolkit
com.github.developframework
- 1.0.1-SNAPSHOT
+ 1.0.7-SNAPSHOT
4.0.0
@@ -24,5 +24,18 @@
com.github.developframework
expression
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-xml
+ true
+
\ No newline at end of file
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/components/BatchTask.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/BatchTask.java
new file mode 100644
index 0000000..916943d
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/BatchTask.java
@@ -0,0 +1,66 @@
+package develop.toolkit.base.components;
+
+import develop.toolkit.base.utils.DateTimeAdvice;
+import lombok.Getter;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+
+/**
+ * 批量任务计时
+ *
+ * @author qiushui on 2020-07-23.
+ */
+@SuppressWarnings("unused")
+public final class BatchTask {
+
+ @Getter
+ private final int total;
+
+ @Getter
+ private int current;
+
+ private long sumCostTime;
+
+ private Instant start;
+
+ public BatchTask(int total) {
+ this.total = total;
+ }
+
+ /**
+ * 开始任务
+ */
+ public void start() {
+ start = Instant.now();
+ current++;
+ }
+
+ /**
+ * 完成单次任务
+ */
+ public String finishOnce(String message) {
+ if (current > total) {
+ throw new IllegalStateException("The task have been finished.");
+ }
+ final long costTime = start.until(Instant.now(), ChronoUnit.MILLIS);
+ sumCostTime += costTime;
+
+ final long avgTime = sumCostTime / current;
+ final long surplus = (total - current) * avgTime;
+ LocalDateTime finishTime = LocalDateTime.now().plusSeconds(surplus / 1000);
+ return String.format(
+ "%d/%d\t(%.02f%%) [cur: %s | avg: %s | sum: %s | sur: %s]\tfinish at: %s - %s",
+ current,
+ total,
+ (float) current / (float) total * 100,
+ DateTimeAdvice.millisecondPretty(costTime),
+ DateTimeAdvice.millisecondPretty(avgTime),
+ DateTimeAdvice.millisecondPretty(sumCostTime),
+ DateTimeAdvice.millisecondPretty(surplus),
+ DateTimeAdvice.format(finishTime),
+ message
+ );
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/components/ConcurrentTesting.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/ConcurrentTesting.java
new file mode 100644
index 0000000..7edfd1b
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/ConcurrentTesting.java
@@ -0,0 +1,67 @@
+package develop.toolkit.base.components;
+
+import develop.toolkit.base.struct.http.HttpClientReceiver;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * 并发测试工具
+ *
+ * @author qiushui on 2021-12-04.
+ */
+public final class ConcurrentTesting {
+
+ private final ExecutorService service;
+
+ private final int cycleCount;
+
+ private final int triggerCount;
+
+ private final int interval;
+
+ private final HttpClientHelper helper;
+
+ public ConcurrentTesting(int threadCount, int triggerCount, int cycleCount, int interval) {
+ this.helper = HttpClientHelper.buildDefault();
+ this.service = Executors.newFixedThreadPool(threadCount);
+ this.triggerCount = triggerCount;
+ this.cycleCount = cycleCount;
+ this.interval = interval;
+ }
+
+ public ConcurrentTesting(HttpClientHelper helper, int threadCount, int triggerCount, int cycleCount, int interval) {
+ this.helper = helper;
+ this.service = Executors.newFixedThreadPool(threadCount);
+ this.triggerCount = triggerCount;
+ this.cycleCount = cycleCount;
+ this.interval = interval;
+ }
+
+ public void start(Function> function) {
+ start(
+ function,
+ receiver -> System.out.printf("【%s】%s\t%s%n", Thread.currentThread().getName(), receiver.getHttpStatus(), receiver.getBody())
+ );
+ }
+
+ public void start(Function> function, Consumer> consumer) {
+ for (int i = 0; i < triggerCount; i++) {
+ service.execute(() -> {
+ for (int j = 0; j < cycleCount; j++) {
+ if (interval > 0) {
+ try {
+ Thread.sleep(interval);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ consumer.accept(function.apply(helper));
+ }
+ });
+ }
+ service.shutdown();
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/components/Counter.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/Counter.java
new file mode 100644
index 0000000..e7cd391
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/Counter.java
@@ -0,0 +1,66 @@
+package develop.toolkit.base.components;
+
+import develop.toolkit.base.struct.KeyValuePairs;
+
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 计数器
+ *
+ * @author qiushui on 2020-03-25.
+ */
+@SuppressWarnings("unused")
+public final class Counter {
+
+ private final ConcurrentHashMap map = new ConcurrentHashMap<>();
+
+ /**
+ * 加
+ */
+ public void add(K key, final int count) {
+ map.compute(key, (k, v) -> v == null ? count : (v + count));
+ }
+
+ /**
+ * 加1
+ */
+ public void add(K key) {
+ add(key, 1);
+ }
+
+ /**
+ * 减
+ */
+ public void subtract(K key, final int count) {
+ map.compute(key, (k, v) -> (v == null || v == count) ? 0 : (v - count));
+ }
+
+ /**
+ * 减1
+ */
+ public void subtract(K key) {
+ subtract(key, 1);
+ }
+
+ /**
+ * 取值
+ */
+ public int get(K key) {
+ return map.getOrDefault(key, 0);
+ }
+
+ /**
+ * 获得所有键集合
+ */
+ public Set keySet() {
+ return map.keySet();
+ }
+
+ /**
+ * 转化成KeyValuePairs
+ */
+ public KeyValuePairs toKeyValuePairs() {
+ return KeyValuePairs.fromMap(map);
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/components/EntityRegistry.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/EntityRegistry.java
new file mode 100644
index 0000000..d1f470c
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/EntityRegistry.java
@@ -0,0 +1,92 @@
+package develop.toolkit.base.components;
+
+import lombok.NonNull;
+
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * 实体注册器
+ *
+ * @author qiushui on 2018-05-29.
+ */
+@SuppressWarnings("unused")
+public abstract class EntityRegistry, K> implements Serializable {
+
+ private static final long serialVersionUID = 8580818076321536793L;
+
+ protected Map entityMap;
+
+ public EntityRegistry() {
+ this.entityMap = new LinkedHashMap<>();
+ T[] defaultEntities = defaultEntity();
+ if (defaultEntities != null) {
+ for (T defaultEntity : defaultEntities) {
+ entityMap.put(defaultEntity.key(), defaultEntity);
+ }
+ }
+ }
+
+ /**
+ * 提供默认实体
+ *
+ * @return 默认实体数组
+ */
+ protected abstract T[] defaultEntity();
+
+ /**
+ * 添加自定义实体
+ *
+ * @param customEntities 自定义实体
+ */
+ public final void addCustomEntities(@NonNull T[] customEntities) {
+ if (customEntities != null) {
+ for (T entity : customEntities) {
+ entityMap.put(entity.key(), entity);
+ }
+ }
+ }
+
+ /**
+ * 提取
+ *
+ * @param key 标记
+ * @return 实体
+ */
+ public final Optional extract(K key) {
+ if (entityMap.containsKey(key)) {
+ return Optional.of(entityMap.get(key));
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * 强制提取
+ *
+ * @param key 标记
+ * @param customRuntimeException 如果实体不存在于注册器则抛出自定义异常
+ * @return 实体
+ */
+ public final T extractRequired(K key, RuntimeException customRuntimeException) {
+ if (entityMap.containsKey(key)) {
+ return entityMap.get(key);
+ }
+ throw customRuntimeException;
+ }
+
+ /**
+ * 提取,失败使用默认值
+ *
+ * @param key 标记
+ * @param defaultValue 默认值
+ * @return 实体
+ */
+ public final T extractOrDefault(K key, T defaultValue) {
+ if (entityMap.containsKey(key)) {
+ return entityMap.get(key);
+ }
+ return defaultValue;
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/components/EntitySign.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/EntitySign.java
new file mode 100644
index 0000000..2713ee0
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/EntitySign.java
@@ -0,0 +1,16 @@
+package develop.toolkit.base.components;
+
+/**
+ * 实体记号接口
+ *
+ * @author qiushui on 2018-05-29.
+ */
+public interface EntitySign {
+
+ /**
+ * 识别名
+ *
+ * @return 识别名
+ */
+ K key();
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/components/HttpClientHelper.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/HttpClientHelper.java
new file mode 100644
index 0000000..e3f2943
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/HttpClientHelper.java
@@ -0,0 +1,145 @@
+package develop.toolkit.base.components;
+
+import develop.toolkit.base.struct.http.HttpClientGlobalOptions;
+import develop.toolkit.base.struct.http.HttpPostProcessor;
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.net.ProxySelector;
+import java.net.http.HttpClient;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.time.Duration;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Http发送助手
+ *
+ * @author qiushui on 2020-09-10.
+ */
+@SuppressWarnings("unused")
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public class HttpClientHelper {
+
+ private final HttpClient httpClient;
+
+ private final HttpClientGlobalOptions options;
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static HttpClientHelper buildDefault() {
+ return builder().build();
+ }
+
+ public HttpClientSender request(String method, String url) {
+ return new HttpClientSender(httpClient, method, url, options);
+ }
+
+ public HttpClientSender get(String url) {
+ return request("GET", url);
+ }
+
+ public HttpClientSender post(String url) {
+ return request("POST", url);
+ }
+
+ public HttpClientSender put(String url) {
+ return request("PUT", url);
+ }
+
+ public HttpClientSender delete(String url) {
+ return request("DELETE", url);
+ }
+
+ public static class Builder {
+
+ private SSLContext sslContext;
+
+ private Duration connectTimeout = Duration.ofSeconds(10L);
+
+ private InetSocketAddress proxyAddress;
+
+ private Executor executor;
+
+ private final HttpClientGlobalOptions globalOptions = new HttpClientGlobalOptions();
+
+ public Builder onlyPrintFailed(boolean onlyPrintFailed) {
+ globalOptions.onlyPrintFailed = onlyPrintFailed;
+ return this;
+ }
+
+ public Builder connectTimeout(Duration connectTimeout) {
+ this.connectTimeout = connectTimeout;
+ return this;
+ }
+
+ public Builder proxyAddress(InetSocketAddress proxyAddress) {
+ this.proxyAddress = proxyAddress;
+ return this;
+ }
+
+ public Builder executor(Executor executor) {
+ this.executor = executor;
+ return this;
+ }
+
+ public Builder readTimeout(Duration readTimeout) {
+ globalOptions.readTimeout = readTimeout;
+ return this;
+ }
+
+ public Builder addGlobalPostProcessor(HttpPostProcessor globalPostProcessor) {
+ globalOptions.postProcessors.add(globalPostProcessor);
+ return this;
+ }
+
+ public Builder constant(String key, String value) {
+ globalOptions.constants.putConstant(key, value);
+ return this;
+ }
+
+ public Builder constantMap(Map constantMap) {
+ globalOptions.constants.putConstantMap(constantMap);
+ return this;
+ }
+
+ public Builder ssl(InputStream pkcs12, String password) {
+ try {
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ char[] passwordChars = password.toCharArray();
+ ks.load(pkcs12, passwordChars);
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ kmf.init(ks, passwordChars);
+ sslContext = SSLContext.getInstance("SSL");
+ sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
+ } catch (Exception e) {
+ throw new IllegalStateException(e.getMessage());
+ }
+ return this;
+ }
+
+ public HttpClientHelper build() {
+ final HttpClient.Builder builder = HttpClient.newBuilder()
+ .version(HttpClient.Version.HTTP_1_1)
+ .followRedirects(HttpClient.Redirect.NEVER)
+ .connectTimeout(connectTimeout);
+ if (sslContext != null) {
+ builder.sslContext(sslContext);
+ }
+ if (proxyAddress != null) {
+ builder.proxy(ProxySelector.of(proxyAddress));
+ }
+ if (executor != null) {
+ builder.executor(executor);
+ }
+ return new HttpClientHelper(builder.build(), globalOptions);
+ }
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/components/HttpClientSender.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/HttpClientSender.java
new file mode 100644
index 0000000..b2e4b9a
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/HttpClientSender.java
@@ -0,0 +1,260 @@
+package develop.toolkit.base.components;
+
+import develop.toolkit.base.struct.http.*;
+import develop.toolkit.base.utils.K;
+import develop.toolkit.base.utils.StringAdvice;
+import lombok.Getter;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.*;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Http发送器
+ *
+ * @author qiushui on 2020-09-10.
+ */
+@Getter
+public final class HttpClientSender {
+
+ private final HttpClient httpClient;
+
+ private final String method;
+
+ private final String url;
+
+ private final Map headers = new LinkedHashMap<>();
+
+ private final Map parameters = new LinkedHashMap<>();
+
+ private final List postProcessors;
+
+ private Duration readTimeout;
+
+ private String debugLabel;
+
+ private HttpRequestBody> requestBody;
+
+ private String requestStringBody;
+
+ private boolean onlyPrintFailed;
+
+ private URI uri;
+
+ private final HttpClientConstants constants;
+
+ protected HttpClientSender(HttpClient httpClient, String method, String url, HttpClientGlobalOptions options) {
+ this.httpClient = httpClient;
+ this.method = method;
+ this.readTimeout = options.readTimeout;
+ this.postProcessors = new LinkedList<>(options.postProcessors);
+ this.constants = options.constants;
+ this.onlyPrintFailed = options.onlyPrintFailed;
+ this.url = constants.replace(url);
+ }
+
+ public HttpClientSender header(String header, String value) {
+ this.headers.put(header, constants.replace(value));
+ return this;
+ }
+
+ public HttpClientSender headers(Map customHeaders) {
+ if (customHeaders != null) {
+ customHeaders.forEach((k, v) -> this.headers.put(k, constants.replace(v)));
+ }
+ return this;
+ }
+
+ public HttpClientSender headerAuthorization(String value) {
+ this.headers.put("Authorization", constants.replace(value));
+ return this;
+ }
+
+ public HttpClientSender headerContentType(String contentType) {
+ this.headers.put("Content-Type", contentType);
+ return this;
+ }
+
+ public HttpClientSender parameter(String parameter, Object value) {
+ this.parameters.put(parameter, value);
+ return this;
+ }
+
+ public HttpClientSender parameters(Map parameterMap) {
+ if (parameterMap != null) {
+ parameterMap.forEach((k, v) -> {
+ if (v instanceof String) {
+ this.parameters.put(k, constants.replace((String) v));
+ } else {
+ this.parameters.put(k, v);
+ }
+ });
+ }
+ return this;
+ }
+
+ public HttpClientSender readTimeout(Duration readTimeout) {
+ this.readTimeout = readTimeout;
+ return this;
+ }
+
+ public HttpClientSender debugLabel(String debugLabel) {
+ this.debugLabel = debugLabel;
+ return this;
+ }
+
+ public HttpClientSender onlyPrintFailed(boolean onlyPrintFailed) {
+ this.onlyPrintFailed = onlyPrintFailed;
+ return this;
+ }
+
+ public HttpClientSender addPostProcessor(HttpPostProcessor postProcessor) {
+ postProcessors.add(postProcessor);
+ return this;
+ }
+
+ public HttpClientSender bodyJson(String json) {
+ headers.put("Content-Type", "application/json;charset=utf-8");
+ this.requestBody = K.map(json, RawRequestBody::new);
+ return this;
+ }
+
+ public HttpClientSender bodyXml(String xml) {
+ headers.put("Content-Type", "application/xml;charset=utf-8");
+ this.requestBody = K.map(xml, RawRequestBody::new);
+ return this;
+ }
+
+ public HttpClientSender bodyText(String text) {
+ headers.put("Content-Type", "text/plain;charset=utf-8");
+ this.requestBody = K.map(text, RawRequestBody::new);
+ return this;
+ }
+
+ public HttpClientSender bodyBytes(byte[] bytes) {
+ this.requestBody = K.map(bytes, ByteRequestBody::new);
+ return this;
+ }
+
+ public HttpClientSender bodyMultiPartFormData(MultiPartFormDataBody multiPartFormDataBody) {
+ headers.put("Content-Type", "multipart/form-data; boundary=" + multiPartFormDataBody.getBoundary());
+ this.requestBody = multiPartFormDataBody;
+ return this;
+ }
+
+ public HttpClientSender bodyFormUrlencoded(FormUrlencodedBody formUrlencodedBody) {
+ headers.put("Content-Type", "application/x-www-form-urlencoded");
+ this.requestBody = formUrlencodedBody;
+ return this;
+ }
+
+ public void download(Path path, OpenOption... openOptions) {
+ send(HttpResponse.BodyHandlers::ofByteArray).ifSuccess(r -> r.save(path, openOptions));
+ }
+
+ public HttpClientReceiver send() {
+ return send(new StringBodySenderHandler());
+ }
+
+ public CompletableFuture> sendAsync() {
+ return sendAsync(new StringBodySenderHandler());
+ }
+
+ /**
+ * 核心发送逻辑
+ *
+ * @param senderHandler 发送器扩展逻辑
+ * @param 响应内容
+ * @return receiver
+ */
+ public HttpClientReceiver send(SenderHandler senderHandler) {
+ this.uri = URI.create(url + StringAdvice.urlParametersFormat(parameters, true));
+ final HttpRequest.Builder builder = HttpRequest
+ .newBuilder()
+ .version(httpClient.version())
+ .uri(uri);
+ headers.forEach(builder::header);
+ final HttpRequest request = builder
+ .method(method, senderHandler.bodyPublisher(requestBody))
+ .timeout(readTimeout)
+ .build();
+ requestStringBody = HttpRequestBody.bodyToString(requestBody);
+ final HttpClientReceiver receiver = new HttpClientReceiver<>();
+ Instant start = Instant.now();
+ try {
+ HttpResponse response = httpClient.send(request, senderHandler.bodyHandler());
+ receiver.setHttpStatus(response.statusCode());
+ receiver.setHeaders(response.headers().map());
+ receiver.setBody(response.body());
+ } catch (HttpConnectTimeoutException e) {
+ receiver.setConnectTimeout(true);
+ } catch (HttpTimeoutException e) {
+ receiver.setReadTimeout(true);
+ } catch (InterruptedException | IOException e) {
+ e.printStackTrace();
+ receiver.setErrorMessage(e.getMessage());
+ } finally {
+ receiver.setCostTime(start.until(Instant.now(), ChronoUnit.MILLIS));
+ doPostProcessors(receiver);
+ }
+ return receiver;
+ }
+
+ /**
+ * 核心发送逻辑(异步)
+ *
+ * @param senderHandler 发送器扩展逻辑
+ * @param 响应内容
+ * @return completableFuture
+ */
+ public CompletableFuture> sendAsync(SenderHandler senderHandler) {
+ this.uri = URI.create(url + StringAdvice.urlParametersFormat(parameters, true));
+ final HttpRequest.Builder builder = HttpRequest
+ .newBuilder()
+ .version(httpClient.version())
+ .uri(uri);
+ headers.forEach(builder::header);
+ final HttpRequest request = builder
+ .method(method, senderHandler.bodyPublisher(requestBody))
+ .timeout(readTimeout)
+ .build();
+ requestStringBody = HttpRequestBody.bodyToString(requestBody);
+ final Instant start = Instant.now();
+ return httpClient
+ .sendAsync(request, senderHandler.bodyHandler())
+ .handle((response, e) -> {
+ final HttpClientReceiver receiver = new HttpClientReceiver<>();
+ if (e == null) {
+ receiver.setHttpStatus(response.statusCode());
+ receiver.setHeaders(response.headers().map());
+ receiver.setBody(response.body());
+ } else if (e instanceof HttpConnectTimeoutException) {
+ receiver.setConnectTimeout(true);
+ } else if (e instanceof HttpTimeoutException) {
+ receiver.setReadTimeout(true);
+ } else if (e instanceof InterruptedException || e instanceof IOException) {
+ e.printStackTrace();
+ receiver.setErrorMessage(e.getMessage());
+ }
+ receiver.setCostTime(start.until(Instant.now(), ChronoUnit.MILLIS));
+ doPostProcessors(receiver);
+ return receiver;
+ });
+ }
+
+ private void doPostProcessors(HttpClientReceiver receiver) {
+ for (HttpPostProcessor postProcessor : postProcessors) {
+ postProcessor.process(this, receiver);
+ }
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/components/IWantData.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/IWantData.java
new file mode 100644
index 0000000..5519583
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/IWantData.java
@@ -0,0 +1,122 @@
+package develop.toolkit.base.components;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * 我想要的数据
+ *
+ * @author qiushui on 2021-10-30.
+ */
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public class IWantData {
+
+ // 是否成功
+ private final boolean success;
+
+ // 失败信息
+ private final String message;
+
+ // 想要的数据
+ private final T data;
+
+ /**
+ * 成功获取
+ *
+ * @param data 数据
+ */
+ public static IWantData ok(T data) {
+ return new IWantData<>(true, null, data);
+ }
+
+ /**
+ * 成功获取
+ */
+ public static IWantData ok() {
+ return ok(null);
+ }
+
+ /**
+ * 获取失败
+ *
+ * @param message 失败信息
+ */
+ public static IWantData fail(String message) {
+ return new IWantData<>(false, message, null);
+ }
+
+ /**
+ * 转换
+ *
+ * @param function 转换函数
+ * @param 目标类型
+ * @return 转换值
+ */
+ public IWantData map(Function super T, ? extends R> function) {
+ return success ? IWantData.ok(function.apply(data)) : IWantData.fail(message);
+ }
+
+ /**
+ * 扁平化转换
+ *
+ * @param function 转换函数
+ * @param 目标类型
+ * @return 转换值
+ */
+ public IWantData flatMap(Function super T, IWantData> function) {
+ return success ? function.apply(data) : IWantData.fail(message);
+ }
+
+ /**
+ * 提供默认值的数据获取
+ *
+ * @param defaultValue 默认值
+ * @param messageConsumer 失败信息处理
+ * @return 数据值
+ */
+ public T returnData(T defaultValue, Consumer messageConsumer) {
+ if (success) {
+ return data;
+ }
+ if (messageConsumer != null) {
+ messageConsumer.accept(message);
+ }
+ return defaultValue;
+ }
+
+ /**
+ * 提供默认值的数据获取
+ *
+ * @param defaultSupplier 默认值提供器
+ * @param messageConsumer 失败信息处理
+ * @return 数据值
+ */
+ public T returnData(Supplier defaultSupplier, Consumer messageConsumer) {
+ if (success) {
+ return data;
+ }
+ if (messageConsumer != null) {
+ messageConsumer.accept(message);
+ }
+ return defaultSupplier.get();
+ }
+
+ /**
+ * 会抛异常的数据获取
+ *
+ * @param throwableFunction 异常函数
+ * @return 数据值
+ */
+ public T returnDataThrows(Function throwableFunction) {
+ if (success) {
+ return data;
+ }
+ throw throwableFunction.apply(message);
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/components/SnowflakeIdWorker.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/SnowflakeIdWorker.java
new file mode 100644
index 0000000..70b72b7
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/SnowflakeIdWorker.java
@@ -0,0 +1,128 @@
+package develop.toolkit.base.components;
+
+import java.time.Instant;
+
+/**
+ * 雪花算法ID生成器
+ *
+ * @author qiushui on 2021-08-05.
+ */
+public class SnowflakeIdWorker {
+
+ /**
+ * 机器id所占的位数
+ */
+ private final long workerIdBits = 5L;
+
+ /**
+ * 数据标识id所占的位数
+ */
+ private final long datacenterIdBits = 5L;
+
+ /**
+ * 工作机器ID(0~31)
+ */
+ private final long workerId;
+
+ /**
+ * 数据中心ID(0~31)
+ */
+ private final long datacenterId;
+
+ /**
+ * 毫秒内序列(0~4095)
+ */
+ private long sequence = 0L;
+
+ /**
+ * 上次生成ID的时间截
+ */
+ private long lastTimestamp = -1L;
+
+ /**
+ * 构造函数
+ *
+ * @param workerId 工作ID (0~31)
+ * @param datacenterId 数据中心ID (0~31)
+ */
+ public SnowflakeIdWorker(long workerId, long datacenterId) {
+ // 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
+ long maxWorkerId = ~(-1L << workerIdBits);
+ if (workerId > maxWorkerId || workerId < 0) {
+ throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
+ }
+ // 支持的最大数据标识id,结果是31
+ long maxDatacenterId = ~(-1L << datacenterIdBits);
+ if (datacenterId > maxDatacenterId || datacenterId < 0) {
+ throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
+ }
+ this.workerId = workerId;
+ this.datacenterId = datacenterId;
+ }
+
+ /**
+ * 获得下一个ID (该方法是线程安全的)
+ *
+ * @return SnowflakeId
+ */
+ public synchronized long nextId() {
+ long timestamp = timeGen();
+ // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
+ if (timestamp < lastTimestamp) {
+ throw new RuntimeException(
+ String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
+ }
+ // 如果是同一时间生成的,则进行毫秒内序列
+ // 序列在id中占的位数
+ long sequenceBits = 12L;
+ if (lastTimestamp == timestamp) {
+ // 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
+ long sequenceMask = ~(-1L << sequenceBits);
+ sequence = (sequence + 1) & sequenceMask;
+ // 毫秒内序列溢出
+ if (sequence == 0) {
+ // 阻塞到下一个毫秒,获得新的时间戳
+ timestamp = tilNextMillis(lastTimestamp);
+ }
+ }
+ // 时间戳改变,毫秒内序列重置
+ else {
+ sequence = 0L;
+ }
+
+ // 上次生成ID的时间截
+ lastTimestamp = timestamp;
+ long datacenterIdShift = sequenceBits + workerIdBits;
+ // 时间截向左移22位(5+5+12)
+ long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
+ // 开始时间截 (2020-01-01)
+ long twepoch = 1577808000000L;
+ return ((timestamp - twepoch) << timestampLeftShift)
+ | (datacenterId << datacenterIdShift)
+ | (workerId << sequenceBits)
+ | sequence;
+ }
+
+ /**
+ * 阻塞到下一个毫秒,直到获得新的时间戳
+ *
+ * @param lastTimestamp 上次生成ID的时间截
+ * @return 当前时间戳
+ */
+ private long tilNextMillis(long lastTimestamp) {
+ long timestamp = timeGen();
+ while (timestamp <= lastTimestamp) {
+ timestamp = timeGen();
+ }
+ return timestamp;
+ }
+
+ /**
+ * 高并发环境下,返回以毫秒为单位的当前时间
+ *
+ * @return 当前时间(毫秒)
+ */
+ private long timeGen() {
+ return Instant.now().toEpochMilli();
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/components/Sorter.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/Sorter.java
deleted file mode 100644
index e2230e4..0000000
--- a/develop-toolkit-base/src/main/java/develop/toolkit/base/components/Sorter.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package develop.toolkit.base.components;
-
-import develop.toolkit.base.struct.CollectionInMap;
-import lombok.NonNull;
-
-import java.util.*;
-
-/**
- * 分拣器
- *
- * @author qiushui on 2018-07-09.
- */
-public class Sorter {
-
- private CollectionInMap map = new CollectionInMap<>(key -> new LinkedList());
-
- private SortFunction sortFunction;
-
- public Sorter(@NonNull SortFunction sortFunction) {
- this.sortFunction = sortFunction;
- }
-
- /**
- * 分拣
- * @param collection
- */
- public void sort(Collection collection) {
- for (V item : collection) {
- K key = sortFunction.sort(item);
- map.addItemSoft(key, item);
- }
- }
-
- /**
- * 清空
- */
- public void clear() {
- map.clear();
- }
-
- /**
- * 返回单列列表
- * @param key
- * @return
- */
- @SuppressWarnings("unchecked")
- public List getSingleList(K key) {
- if (map.containsKey(key)) {
- return new ArrayList<>(map.get(key));
- } else {
- return Collections.EMPTY_LIST;
- }
- }
-
- /**
- * 返回所有键
- *
- * @return
- */
- public Set allKey() {
- return map.keySet();
- }
-
- @FunctionalInterface
- public interface SortFunction {
-
- /**
- * 返回需要投递的key
- *
- * @param item
- * @return
- */
- K sort(V item);
- }
-}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/components/StopWatch.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/StopWatch.java
index 1b81f5a..ec9934b 100644
--- a/develop-toolkit-base/src/main/java/develop/toolkit/base/components/StopWatch.java
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/components/StopWatch.java
@@ -3,6 +3,7 @@
import develop.toolkit.base.utils.DateTimeAdvice;
import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -11,17 +12,18 @@
*
* @author qiushui on 2019-03-17.
*/
+@SuppressWarnings("unused")
public final class StopWatch {
public static final String DEFAULT_NAME = "default";
- private Map startInstantMap = new ConcurrentHashMap<>();
+ private final Map startInstantMap = new ConcurrentHashMap<>();
- private StopWatch() {
- start(DEFAULT_NAME);
+ private StopWatch(String name) {
+ pause(name);
}
- public void start(String name) {
+ public void pause(String name) {
startInstantMap.put(name, Instant.now());
}
@@ -30,8 +32,15 @@ public long end() {
}
public long end(String name) {
- final Instant end = Instant.now();
- return end.toEpochMilli() - startInstantMap.get(name).toEpochMilli();
+ return startInstantMap.get(name).until(Instant.now(), ChronoUnit.MILLIS);
+ }
+
+ public long interval(String startName, String endName) {
+ return startInstantMap.get(startName).until(startInstantMap.get(endName), ChronoUnit.MILLIS);
+ }
+
+ public String formatEnd() {
+ return DateTimeAdvice.millisecondPretty(end());
}
public String formatEnd(String label) {
@@ -43,6 +52,10 @@ public String formatEnd(String label, String name) {
}
public static StopWatch start() {
- return new StopWatch();
+ return new StopWatch(DEFAULT_NAME);
+ }
+
+ public static StopWatch start(String name) {
+ return new StopWatch(name);
}
}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/constants/DateFormatConstants.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/constants/DateFormatConstants.java
index ca19f14..1bde0a7 100644
--- a/develop-toolkit-base/src/main/java/develop/toolkit/base/constants/DateFormatConstants.java
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/constants/DateFormatConstants.java
@@ -7,15 +7,20 @@
*
* @author qiushui on 2019-02-20.
*/
+@SuppressWarnings("unused")
public interface DateFormatConstants {
String STANDARD = "yyyy-MM-dd HH:mm:ss";
String COMPACT = "yyyyMMddHHmmss";
String DATE = "yyyy-MM-dd";
+ String MONTH = "yyyy-MM";
String TIME = "HH:mm:ss";
- String MYSQL_FORMAT_DATETIME = "%Y-%m-%d %H:%i:%s";
+ String MYSQL_FORMAT_DATETIME = "%Y-%m-%d %H:%i:%S";
String MYSQL_FORMAT_DATE = "%Y-%m-%d";
String MYSQL_FORMAT_TIME = "%H:%i:%s";
+ String MYSQL_FORMAT_MONTH = "%Y-%m";
DateTimeFormatter STANDARD_FORMATTER = DateTimeFormatter.ofPattern(STANDARD);
+ DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE);
+ DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(TIME);
}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/constants/DateTimeConstants.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/constants/DateTimeConstants.java
index 7c3471e..70e5bb7 100644
--- a/develop-toolkit-base/src/main/java/develop/toolkit/base/constants/DateTimeConstants.java
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/constants/DateTimeConstants.java
@@ -5,9 +5,8 @@
/**
* @author qiushui on 2019-05-07.
*/
+@SuppressWarnings("unused")
public interface DateTimeConstants {
LocalTime LAST_SECOND = LocalTime.of(23, 59, 59);
-
-
}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/exception/CryptException.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/exception/CryptException.java
new file mode 100644
index 0000000..209635a
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/exception/CryptException.java
@@ -0,0 +1,13 @@
+package develop.toolkit.base.exception;
+
+/**
+ * 加密解密异常
+ *
+ * @author qiushui on 2021-10-08.
+ */
+public class CryptException extends RuntimeException {
+
+ public CryptException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/exception/FormatRuntimeException.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/exception/FormatRuntimeException.java
index b146a43..2a9a231 100644
--- a/develop-toolkit-base/src/main/java/develop/toolkit/base/exception/FormatRuntimeException.java
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/exception/FormatRuntimeException.java
@@ -5,6 +5,7 @@
*
* @author qiushui
*/
+@SuppressWarnings("unused")
public abstract class FormatRuntimeException extends RuntimeException{
public FormatRuntimeException() {
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/AbstractCollectionInMap.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/AbstractCollectionInMap.java
new file mode 100644
index 0000000..6b19141
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/AbstractCollectionInMap.java
@@ -0,0 +1,83 @@
+package develop.toolkit.base.struct;
+
+import lombok.NonNull;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+/**
+ * Map里有集合结构
+ *
+ * @author qiushui on 2020-08-13.
+ */
+@SuppressWarnings("unused")
+public abstract class AbstractCollectionInMap> extends LinkedHashMap {
+
+ protected final Supplier supplier;
+
+ public AbstractCollectionInMap(Supplier supplier) {
+ this.supplier = supplier;
+ }
+
+ /**
+ * 追加元素
+ *
+ * @param key map key
+ * @param item 新元素
+ */
+ public final void putItem(K key, V item) {
+ getInternal(key).add(item);
+ }
+
+ /**
+ * 追加元素
+ *
+ * @param key map key
+ * @param items 新元素
+ */
+ public final void putAllItem(K key, @NonNull Set items) {
+ getInternal(key).addAll(items);
+ }
+
+ private COLLECTION getInternal(K key) {
+ COLLECTION collection = get(key);
+ if (collection == null) {
+ collection = supplier.get();
+ put(key, collection);
+ }
+ return collection;
+ }
+
+ /**
+ * 删除元素
+ *
+ * @param key map key
+ * @param item 元素
+ */
+ public final void removeItem(K key, V item) {
+ COLLECTION collection = get(key);
+ if (collection != null) {
+ collection.remove(item);
+ } else {
+ throw new IllegalStateException("key \"" + key + "\" is not exist.");
+ }
+ }
+
+ /**
+ * 根据条件删除元素
+ *
+ * @param key map key
+ * @param filter 条件
+ */
+ public final void removeIfItem(K key, @NonNull Predicate super V> filter) {
+ COLLECTION collection = get(key);
+ if (collection != null) {
+ collection.removeIf(filter);
+ } else {
+ throw new IllegalStateException("key \"" + key + "\" is not exist.");
+ }
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/CollectionInMap.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/CollectionInMap.java
deleted file mode 100644
index a6e3f58..0000000
--- a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/CollectionInMap.java
+++ /dev/null
@@ -1,133 +0,0 @@
-package develop.toolkit.base.struct;
-
-import lombok.NonNull;
-
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Predicate;
-
-/**
- * Map里有集合结构
- *
- * @author qiushui on 2018-07-09.
- */
-public class CollectionInMap extends ConcurrentHashMap> implements Serializable {
-
- private static final long serialVersionUID = 3068493190714636107L;
- private CollectionProvider collectionProvider;
-
- public CollectionInMap() {
- this.collectionProvider = k -> new LinkedList();
- }
-
- public CollectionInMap(@NonNull CollectionProvider collectionProvider) {
- this.collectionProvider = collectionProvider;
- }
-
- public CollectionInMap(int initialCapacity, @NonNull CollectionProvider collectionProvider) {
- super(initialCapacity);
- this.collectionProvider = collectionProvider;
- }
-
- /**
- * 追加元素
- * @param key map key
- * @param item 新元素
- */
- public void addItem(K key, V item) {
- if (containsKey(key)) {
- Collection collection = get(key);
- collection.add(item);
- } else {
- throw new IllegalStateException("key \"" + "\" is not exist.");
- }
- }
-
- /**
- * 追加元素
- * @param key map key
- * @param items 新元素
- */
- public void addAllItem(K key, @NonNull Collection items) {
- if (containsKey(key)) {
- Collection collection = get(key);
- collection.addAll(items);
- } else {
- throw new IllegalStateException("key \"" + "\" is not exist.");
- }
- }
-
- /**
- * 软追加元素
- * @param key map key
- * @param item 新元素
- */
- @SuppressWarnings("unchecked")
- public void addItemSoft(K key, V item) {
- if (containsKey(key)) {
- Collection collection = get(key);
- collection.add(item);
- } else {
- Collection collection = collectionProvider.provide(key);
- collection.add(item);
- put(key, collection);
- }
- }
-
- /**
- * 软追加元素
- * @param key map key
- * @param items 新元素
- */
- @SuppressWarnings("unchecked")
- public void addAllItemSoft(K key, @NonNull Collection items) {
- if (containsKey(key)) {
- Collection collection = get(key);
- collection.addAll(items);
- } else {
- Collection collection = collectionProvider.provide(key);
- collection.addAll(items);
- put(key, collection);
- }
- }
-
- /**
- * 删除元素
- * @param key map key
- * @param item 元素
- */
- public void removeItem(K key, V item) {
- if (containsKey(key)) {
- Collection collection = get(key);
- collection.remove(item);
- } else {
- throw new IllegalStateException("key \"" + key + "\" is not exist.");
- }
- }
-
- /**
- * 根据条件删除元素
- * @param key map key
- * @param filter 条件
- */
- public void removeIfItem(K key, @NonNull Predicate super V> filter) {
- if (containsKey(key)) {
- Collection collection = get(key);
- collection.removeIf(filter);
- } else {
- throw new IllegalStateException("key \"" + key + "\" is not exist.");
- }
- }
-
- /**
- * 集合提供器
- * @param
- */
- @FunctionalInterface
- public interface CollectionProvider {
-
- Collection provide(K key);
- }
-}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/KeyValuePair.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/KeyValuePair.java
index 198b517..df705b4 100644
--- a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/KeyValuePair.java
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/KeyValuePair.java
@@ -3,6 +3,7 @@
import lombok.*;
import java.io.Serializable;
+import java.util.Map;
/**
* 键值对结构体
@@ -24,9 +25,6 @@ public class KeyValuePair implements Serializable {
/**
* 美化成字符串
- *
- * @param separator
- * @return
*/
public String formatString(String separator) {
return key + separator + value;
@@ -39,14 +37,20 @@ public String toString() {
/**
* 带值初始化
- *
- * @param key
- * @param value
- * @param
- * @param
- * @return
*/
public static KeyValuePair of(K key, V value) {
return new KeyValuePair<>(key, value);
}
+
+ public static KeyValuePair of(T[] objs) {
+ return new KeyValuePair<>(objs[0], objs[1]);
+ }
+
+ /**
+ * 从Entry初始化
+ */
+ public static KeyValuePair of(Map.Entry entry) {
+ return new KeyValuePair<>(entry.getKey(), entry.getValue());
+ }
+
}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/KeyValuePairs.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/KeyValuePairs.java
index a065451..e9906d6 100644
--- a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/KeyValuePairs.java
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/KeyValuePairs.java
@@ -1,9 +1,6 @@
package develop.toolkit.base.struct;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import java.util.stream.Collectors;
/**
@@ -11,13 +8,13 @@
*
* @author qiushui on 2018-11-03.
*/
+@SuppressWarnings("unused")
public class KeyValuePairs extends LinkedList> {
+ private static final long serialVersionUID = -3327179013671312416L;
+
/**
* 添加键值对
- *
- * @param key
- * @param value
*/
public void addKeyValue(K key, V value) {
this.add(new KeyValuePair<>(key, value));
@@ -25,8 +22,6 @@ public void addKeyValue(K key, V value) {
/**
* 获取所有键
- *
- * @return
*/
public List allKey() {
return this.stream().map(KeyValuePair::getKey).collect(Collectors.toList());
@@ -34,8 +29,6 @@ public List allKey() {
/**
* 获取所有值
- *
- * @return
*/
public List allValue() {
return this.stream().map(KeyValuePair::getValue).collect(Collectors.toList());
@@ -43,8 +36,6 @@ public List allValue() {
/**
* 转化成Map形式
- *
- * @return
*/
public Map toMap() {
Map map = new HashMap<>();
@@ -54,11 +45,6 @@ public Map toMap() {
/**
* 从Map转化
- *
- * @param map
- * @param
- * @param
- * @return
*/
public static KeyValuePairs fromMap(Map map) {
KeyValuePairs keyValuePairs = new KeyValuePairs<>();
@@ -68,16 +54,18 @@ public static KeyValuePairs fromMap(Map map) {
/**
* 带值初始化
- *
- * @param keyValuePairArray
- * @param
- * @param
- * @return
*/
@SafeVarargs
public static KeyValuePairs of(KeyValuePair... keyValuePairArray) {
+ return of(List.of(keyValuePairArray));
+ }
+
+ /**
+ * 带值初始化
+ */
+ public static KeyValuePairs of(Collection> keyValuePairCollection) {
KeyValuePairs keyValuePairs = new KeyValuePairs<>();
- keyValuePairs.addAll(List.of(keyValuePairArray));
+ keyValuePairs.addAll(keyValuePairCollection);
return keyValuePairs;
}
}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/ListInMap.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/ListInMap.java
new file mode 100644
index 0000000..a86b103
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/ListInMap.java
@@ -0,0 +1,24 @@
+package develop.toolkit.base.struct;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * Map里有列表结构
+ *
+ * @author qiushui on 2020-08-13.
+ */
+@SuppressWarnings("unused")
+public class ListInMap extends AbstractCollectionInMap> {
+
+ private static final long serialVersionUID = -6928970809459612701L;
+
+ public ListInMap() {
+ super(LinkedList::new);
+ }
+
+ public ListInMap(Supplier> supplier) {
+ super(supplier);
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/Pager.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/Pager.java
index ae5aa16..d01aa52 100644
--- a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/Pager.java
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/Pager.java
@@ -1,7 +1,7 @@
package develop.toolkit.base.struct;
-import lombok.AllArgsConstructor;
import lombok.Getter;
+import lombok.Setter;
import java.io.Serializable;
@@ -9,25 +9,39 @@
* 分页请求参数信息
*
* @author qiushui on 2018-06-07.
- * @since 0.1
*/
-@AllArgsConstructor
+@Getter
public class Pager implements Serializable {
- private static final long serialVersionUID = -4527797845938921337L;
+ private static final long serialVersionUID = -4527797845938921337L;
- public static final int DEFAULT_PAGE = 1;
- public static final int DEFAULT_SIZE = 10;
+ public static final int DEFAULT_PAGE = 0;
+ public static final int DEFAULT_SIZE = 20;
/* 页码 */
- @Getter
protected int page;
/* 页容量 */
- @Getter
protected int size;
+ /* 记录总条数 */
+ @Setter
+ private long recordTotal;
+
+ /* 页总数 */
+ @Setter
+ private long pageTotal;
+
public Pager() {
this(DEFAULT_PAGE, DEFAULT_SIZE);
}
+ public Pager(int page, int size) {
+ this.page = page;
+ this.size = size;
+ }
+
+ @SuppressWarnings("unused")
+ public int getOffset() {
+ return page * size;
+ }
}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/PagerResult.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/PagerResult.java
index 8d94efe..6911fd8 100644
--- a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/PagerResult.java
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/PagerResult.java
@@ -13,27 +13,22 @@
*/
@Getter
@NoArgsConstructor
+@SuppressWarnings("unused")
public class PagerResult implements Serializable {
private static final long serialVersionUID = -3028130281925624773L;
/* 数据列表 */
private List list;
- /* 记录总条数 */
- private long recordTotal;
-
/* 分页信息 */
private Pager pager;
- /* 页总数 */
- private long pageTotal;
-
public PagerResult(Pager pager, List list, long recordTotal) {
- this.list = list;
- this.recordTotal = recordTotal;
- this.pager = pager;
- this.pageTotal = recordTotal % pager.getSize() == 0 ? recordTotal / pager.getSize() : (recordTotal / pager.getSize() + 1L);
- }
+ this.list = list;
+ this.pager = pager;
+ this.pager.setRecordTotal(recordTotal);
+ this.pager.setPageTotal(recordTotal % pager.getSize() == 0 ? recordTotal / pager.getSize() : (recordTotal / pager.getSize() + 1L));
+ }
public PagerResult(int page, int size, List list, long total) {
this(new Pager(page, size), list, total);
@@ -41,8 +36,6 @@ public PagerResult(int page, int size, List list, long total) {
/**
* 空分页结果
- *
- * @return
*/
public static PagerResult empty(Class clazz, int page, int size) {
return new PagerResult<>(page, size, List.of(), 0);
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/SetInMap.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/SetInMap.java
new file mode 100644
index 0000000..3183942
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/SetInMap.java
@@ -0,0 +1,24 @@
+package develop.toolkit.base.struct;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Supplier;
+
+/**
+ * Map里有集合结构
+ *
+ * @author qiushui on 2020-08-13.
+ */
+@SuppressWarnings("unused")
+public class SetInMap extends AbstractCollectionInMap> {
+
+ private static final long serialVersionUID = 3476351408668231918L;
+
+ public SetInMap() {
+ super(HashSet::new);
+ }
+
+ public SetInMap(Supplier> supplier) {
+ super(supplier);
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/ThreeValues.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/ThreeValues.java
index ea122a4..aba46fa 100644
--- a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/ThreeValues.java
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/ThreeValues.java
@@ -25,16 +25,15 @@ public class ThreeValues implements Serializable {
/**
* 带值初始化
- *
- * @param firstValue
- * @param secondValue
- * @param thirdValue
- * @param
- * @param
- * @param
- * @return
*/
public static ThreeValues of(T firstValue, S secondValue, U thirdValue) {
return new ThreeValues<>(firstValue, secondValue, thirdValue);
}
+
+ public static ThreeValues of(T[] objs) {
+ if (objs.length != 3) {
+ throw new IllegalArgumentException("TwoValues of array length is " + objs.length);
+ }
+ return new ThreeValues<>(objs[0], objs[1], objs[2]);
+ }
}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/TwoValues.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/TwoValues.java
index cca596d..4517ef9 100644
--- a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/TwoValues.java
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/TwoValues.java
@@ -23,14 +23,15 @@ public class TwoValues implements Serializable {
/**
* 带值初始化
- *
- * @param firstValue
- * @param secondValue
- * @param
- * @param
- * @return
*/
public static TwoValues of(T firstValue, S secondValue) {
return new TwoValues<>(firstValue, secondValue);
}
+
+ public static TwoValues of(T[] objs) {
+ if (objs.length != 2) {
+ throw new IllegalArgumentException("TwoValues of array length is " + objs.length);
+ }
+ return new TwoValues<>(objs[0], objs[1]);
+ }
}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/ZipWrapper.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/ZipWrapper.java
new file mode 100644
index 0000000..8efc582
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/ZipWrapper.java
@@ -0,0 +1,63 @@
+package develop.toolkit.base.struct;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.InputStream;
+import java.nio.file.attribute.FileTime;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.function.Supplier;
+import java.util.zip.ZipEntry;
+
+/**
+ * @author qiushui on 2022-04-19.
+ */
+@Getter
+@Setter
+public class ZipWrapper {
+
+ private String filename;
+
+ private Supplier inputStreamSupplier;
+
+ private boolean file;
+
+ private List children;
+
+ private boolean stored;
+
+ private long compressedSize;
+
+ private long size;
+
+ private long crc;
+
+ private FileTime creationTime;
+
+ private FileTime lastAccessTime;
+
+ private FileTime lastModifyTime;
+
+ private long time;
+
+ private String comment;
+
+ private LocalDateTime timeLocal;
+
+ private byte[] extra;
+
+ public void configureZipEntry(ZipEntry zipEntry) {
+ zipEntry.setMethod(stored ? ZipEntry.STORED : ZipEntry.DEFLATED);
+ zipEntry.setCompressedSize(compressedSize);
+ zipEntry.setSize(size);
+ zipEntry.setCrc(crc);
+ zipEntry.setCreationTime(creationTime);
+ zipEntry.setLastAccessTime(lastAccessTime);
+ zipEntry.setLastModifiedTime(lastModifyTime);
+ zipEntry.setTime(time);
+ zipEntry.setTimeLocal(timeLocal);
+ zipEntry.setExtra(extra);
+ zipEntry.setComment(comment);
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/ByteRequestBody.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/ByteRequestBody.java
new file mode 100644
index 0000000..6052fff
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/ByteRequestBody.java
@@ -0,0 +1,22 @@
+package develop.toolkit.base.struct.http;
+
+import lombok.RequiredArgsConstructor;
+
+/**
+ * @author qiushui on 2021-09-16.
+ */
+@RequiredArgsConstructor
+public class ByteRequestBody implements HttpRequestBody {
+
+ private final byte[] bytes;
+
+ @Override
+ public String toString() {
+ return "(Binary byte data)";
+ }
+
+ @Override
+ public byte[] getBody() {
+ return bytes;
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/FormUrlencodedBody.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/FormUrlencodedBody.java
new file mode 100644
index 0000000..2571ba6
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/FormUrlencodedBody.java
@@ -0,0 +1,35 @@
+package develop.toolkit.base.struct.http;
+
+import develop.toolkit.base.utils.StringAdvice;
+import lombok.RequiredArgsConstructor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author qiushui on 2020-09-15.
+ */
+@RequiredArgsConstructor
+public final class FormUrlencodedBody implements HttpRequestBody {
+
+ private final Map pairs;
+
+ public FormUrlencodedBody() {
+ pairs = new HashMap<>();
+ }
+
+ @Override
+ public String getBody() {
+ return StringAdvice.urlParametersFormat(pairs, false);
+ }
+
+ @Override
+ public String toString() {
+ return getBody();
+ }
+
+ public FormUrlencodedBody addPair(String name, Object value) {
+ pairs.put(name, value);
+ return this;
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/HttpClientConstants.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/HttpClientConstants.java
new file mode 100644
index 0000000..faadd1e
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/HttpClientConstants.java
@@ -0,0 +1,36 @@
+package develop.toolkit.base.struct.http;
+
+import develop.toolkit.base.utils.StringAdvice;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 常量可以被{{}}占位符取代
+ *
+ * @author qiushui on 2021-09-17.
+ */
+public final class HttpClientConstants {
+
+ private final Map constants = new HashMap<>();
+
+ public void putConstant(String key, String value) {
+ constants.put(key, value);
+ }
+
+ public void putConstantMap(Map constantMap) {
+ constants.putAll(constantMap);
+ }
+
+ public String replace(String string) {
+ if (!constants.isEmpty()) {
+ for (String key : StringAdvice.regexMatchStartEnd(string, "\\{\\{", "\\}\\}")) {
+ final String value = constants.get(key);
+ if (value != null) {
+ string = string.replace("{{" + key + "}}", value);
+ }
+ }
+ }
+ return string;
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/HttpClientGlobalOptions.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/HttpClientGlobalOptions.java
new file mode 100644
index 0000000..5b09daa
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/HttpClientGlobalOptions.java
@@ -0,0 +1,19 @@
+package develop.toolkit.base.struct.http;
+
+import java.time.Duration;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author qiushui on 2021-09-17.
+ */
+public class HttpClientGlobalOptions {
+
+ public boolean onlyPrintFailed = true;
+
+ public Duration readTimeout = Duration.ofSeconds(30L);
+
+ public List postProcessors = new LinkedList<>(List.of(new PrintLogHttpPostProcessor()));
+
+ public HttpClientConstants constants = new HttpClientConstants();
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/HttpClientReceiver.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/HttpClientReceiver.java
new file mode 100644
index 0000000..f03e65c
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/HttpClientReceiver.java
@@ -0,0 +1,75 @@
+package develop.toolkit.base.struct.http;
+
+import develop.toolkit.base.utils.IOAdvice;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * Http接收器
+ *
+ * @author qiushui on 2020-09-10.
+ */
+@Getter
+@Setter
+public final class HttpClientReceiver {
+
+ private int httpStatus;
+
+ private Map> headers;
+
+ private T body;
+
+ private long costTime;
+
+ private boolean connectTimeout;
+
+ private boolean readTimeout;
+
+ private String errorMessage;
+
+ public String getHeader(String header) {
+ return StringUtils.join(headers.getOrDefault(header, List.of()), ";");
+ }
+
+ public boolean isTimeout() {
+ return connectTimeout || readTimeout;
+ }
+
+ public boolean isSuccess() {
+ return errorMessage == null && !isTimeout() && httpStatus >= 200 && httpStatus < 300;
+ }
+
+ public void ifSuccess(Consumer> consumer) {
+ if (isSuccess()) {
+ consumer.accept(this);
+ }
+ }
+
+ public void save(Path path, OpenOption... openOptions) {
+ byte[] data;
+ if (body instanceof InputStream) {
+ data = IOAdvice.toByteArray((InputStream) body);
+ } else if (body.getClass().isArray()) {
+ data = (byte[]) body;
+ } else if (body instanceof String) {
+ data = ((String) body).getBytes();
+ } else {
+ throw new IllegalArgumentException();
+ }
+ try {
+ Files.write(path, data, openOptions);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/HttpPostProcessor.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/HttpPostProcessor.java
new file mode 100644
index 0000000..d6d9bd0
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/HttpPostProcessor.java
@@ -0,0 +1,14 @@
+package develop.toolkit.base.struct.http;
+
+import develop.toolkit.base.components.HttpClientSender;
+
+/**
+ * Http发送器后置处理
+ *
+ * @author qiushui on 2021-09-16.
+ */
+@FunctionalInterface
+public interface HttpPostProcessor {
+
+ void process(HttpClientSender sender, HttpClientReceiver> receiver);
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/HttpRequestBody.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/HttpRequestBody.java
new file mode 100644
index 0000000..9cb31b2
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/HttpRequestBody.java
@@ -0,0 +1,13 @@
+package develop.toolkit.base.struct.http;
+
+/**
+ * @author qiushui on 2021-09-16.
+ */
+public interface HttpRequestBody {
+
+ BODY getBody();
+
+ static String bodyToString(HttpRequestBody> requestBody) {
+ return requestBody == null ? "(No content)" : requestBody.toString();
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/MultiPartFormDataBody.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/MultiPartFormDataBody.java
new file mode 100644
index 0000000..e812da9
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/MultiPartFormDataBody.java
@@ -0,0 +1,224 @@
+package develop.toolkit.base.struct.http;
+
+import lombok.Getter;
+import org.apache.commons.lang3.RandomStringUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * @author qiushui on 2020-09-14.
+ */
+public final class MultiPartFormDataBody implements HttpRequestBody {
+
+ private final List partsSpecificationList = new ArrayList<>();
+
+ @Getter
+ private final String boundary = RandomStringUtils.randomAlphabetic(10);
+
+ @Override
+ public byte[] getBody() {
+ if (partsSpecificationList.isEmpty()) {
+ return new byte[0];
+ }
+ addFinalBoundaryPart();
+
+ /*
+ * 直接使用迭代器获取字节数据会报错 Too few bytes returned by the publisher
+ * JDK的bug 参考 https://bugs.openjdk.java.net/browse/JDK-8222968
+ */
+ // return HttpRequest.BodyPublishers.ofByteArrays(PartsIterator::new);
+ return assemble();
+ }
+
+ @Override
+ public String toString() {
+ return "(Binary byte data)";
+ }
+
+ public MultiPartFormDataBody addPart(String name, String value) {
+ PartsSpecification newPart = new PartsSpecification();
+ newPart.type = PartsSpecification.Type.STRING;
+ newPart.name = name;
+ newPart.value = value;
+ partsSpecificationList.add(newPart);
+ return this;
+ }
+
+ public MultiPartFormDataBody addPart(String name, Path path) {
+ PartsSpecification newPart = new PartsSpecification();
+ newPart.type = PartsSpecification.Type.FILE;
+ newPart.name = name;
+ newPart.path = path;
+ partsSpecificationList.add(newPart);
+ return this;
+ }
+
+ public MultiPartFormDataBody addPart(String name, String filename, String contentType, byte[] bytes) {
+ PartsSpecification newPart = new PartsSpecification();
+ newPart.type = PartsSpecification.Type.BYTES;
+ newPart.name = name;
+ newPart.bytes = bytes;
+ newPart.filename = filename;
+ newPart.contentType = contentType;
+ partsSpecificationList.add(newPart);
+ return this;
+ }
+
+ public MultiPartFormDataBody addPart(String name, String filename, String contentType, Supplier stream) {
+ PartsSpecification newPart = new PartsSpecification();
+ newPart.type = PartsSpecification.Type.STREAM;
+ newPart.name = name;
+ newPart.stream = stream;
+ newPart.filename = filename;
+ newPart.contentType = contentType;
+ partsSpecificationList.add(newPart);
+ return this;
+ }
+
+ private void addFinalBoundaryPart() {
+ PartsSpecification newPart = new PartsSpecification();
+ newPart.type = PartsSpecification.Type.FINAL_BOUNDARY;
+ newPart.value = "--" + boundary + "--";
+ partsSpecificationList.add(newPart);
+ }
+
+ private static class PartsSpecification {
+
+ public enum Type {
+ STRING, FILE, BYTES, STREAM, FINAL_BOUNDARY
+ }
+
+ public Type type;
+ public String name;
+ public String value;
+ public Path path;
+ public byte[] bytes;
+ public Supplier stream;
+ public String filename;
+ public String contentType;
+
+ }
+
+ private class PartsIterator implements Iterator {
+
+ private final Iterator iterator = partsSpecificationList.iterator();
+
+ private InputStream currentInputStream;
+
+ private byte[] nextBytes;
+
+ private static final String NEW_LINE = "\r\n";
+
+ @Override
+ public boolean hasNext() {
+ try {
+ nextBytes = currentInputStream == null ? determineNextPart() : readCurrentInputStream();
+ return nextBytes != null;
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ @Override
+ public byte[] next() {
+ byte[] result = nextBytes;
+ nextBytes = null;
+ return result;
+ }
+
+ /**
+ * 决定下一个Part
+ */
+ private byte[] determineNextPart() throws IOException {
+ if (!iterator.hasNext()) return null;
+ final PartsSpecification nextPart = iterator.next();
+ switch (nextPart.type) {
+ case FINAL_BOUNDARY: {
+ return nextPart.value.getBytes(StandardCharsets.UTF_8);
+ }
+ case STRING: {
+ currentInputStream = new ByteArrayInputStream((nextPart.value).getBytes(StandardCharsets.UTF_8));
+ return headerBytes(nextPart.name, null, "text/plain; charset=UTF-8");
+ }
+ case BYTES: {
+ currentInputStream = new ByteArrayInputStream(nextPart.bytes);
+ return headerBytes(
+ nextPart.name,
+ nextPart.filename,
+ nextPart.contentType
+ );
+ }
+ case FILE: {
+ currentInputStream = Files.newInputStream(nextPart.path);
+ return headerBytes(
+ nextPart.name,
+ nextPart.path.getFileName().toString(),
+ Files.probeContentType(nextPart.path)
+ );
+ }
+ case STREAM: {
+ currentInputStream = nextPart.stream.get();
+ return headerBytes(
+ nextPart.name,
+ nextPart.filename,
+ nextPart.contentType
+ );
+ }
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ private byte[] readCurrentInputStream() throws IOException {
+ byte[] buffer = new byte[8192];
+ int r = currentInputStream.read(buffer);
+ if (r > 0) {
+ byte[] actualBytes = new byte[r];
+ System.arraycopy(buffer, 0, actualBytes, 0, r);
+ return actualBytes;
+ } else {
+ currentInputStream.close();
+ currentInputStream = null;
+ return NEW_LINE.getBytes();
+ }
+ }
+
+ private byte[] headerBytes(String name, String filename, String contentType) {
+ StringBuilder sb = new StringBuilder("--")
+ .append(boundary).append(NEW_LINE)
+ .append("Content-Disposition: form-data; name=").append(name);
+ if (filename != null) {
+ sb.append("; filename=").append(filename);
+ }
+ sb.append(NEW_LINE).append("Content-Type: ").append(contentType).append(NEW_LINE).append(NEW_LINE);
+ return sb.toString().getBytes(StandardCharsets.UTF_8);
+ }
+ }
+
+ private byte[] assemble() {
+ // 使用以下方法 自己拼装byte[]
+ int length = 0, pos = 0;
+ PartsIterator iteratorForCount = new PartsIterator();
+ while (iteratorForCount.hasNext()) {
+ length += iteratorForCount.next().length;
+ }
+ byte[] data = new byte[length];
+ PartsIterator iteratorForBytes = new PartsIterator();
+ while (iteratorForBytes.hasNext()) {
+ final byte[] nextBytes = iteratorForBytes.next();
+ System.arraycopy(nextBytes, 0, data, pos, nextBytes.length);
+ pos += nextBytes.length;
+ }
+ return data;
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/PrintLogHttpPostProcessor.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/PrintLogHttpPostProcessor.java
new file mode 100644
index 0000000..5f05c3f
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/PrintLogHttpPostProcessor.java
@@ -0,0 +1,63 @@
+package develop.toolkit.base.struct.http;
+
+import develop.toolkit.base.components.HttpClientSender;
+import develop.toolkit.base.utils.DateTimeAdvice;
+import develop.toolkit.base.utils.K;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author qiushui on 2021-09-16.
+ */
+@Slf4j
+public final class PrintLogHttpPostProcessor implements HttpPostProcessor {
+
+ @Override
+ public void process(HttpClientSender sender, HttpClientReceiver> receiver) {
+ if (log.isDebugEnabled() && (!sender.isOnlyPrintFailed() || !receiver.isSuccess())) {
+ debugPrintLog(sender, receiver);
+ }
+ }
+
+ private void debugPrintLog(HttpClientSender sender, HttpClientReceiver> receiver) {
+ StringBuilder sb = new StringBuilder("\n=========================================================================================================\n");
+ sb
+ .append("\nlabel: ").append(K.def(sender.getDebugLabel(), "(Undefined)"))
+ .append("\nhttp request:\n method: ").append(sender.getMethod()).append("\n url: ")
+ .append(sender.getUri().toString()).append("\n headers:\n");
+ sender
+ .getHeaders()
+ .forEach((k, v) -> sb.append(" ").append(k).append(": ").append(StringUtils.join(v, ";")).append("\n"));
+ sb.append(" body: ").append(sender.getRequestStringBody()).append("\n").append("\nhttp response:\n");
+ if (receiver.isConnectTimeout()) {
+ sb.append(" (connect timeout ").append(sender.getHttpClient().connectTimeout().map(Duration::getSeconds).orElse(0L)).append("s)");
+ } else if (receiver.isReadTimeout()) {
+ sb.append(" (read timeout ").append(sender.getReadTimeout().getSeconds()).append("s)");
+ } else if (receiver.getErrorMessage() != null) {
+ sb.append(" (ioerror ").append(receiver.getErrorMessage()).append(")");
+ } else if (receiver.getHeaders() != null) {
+ sb.append(" status: ").append(receiver.getHttpStatus()).append("\n headers:\n");
+ for (Map.Entry> entry : receiver.getHeaders().entrySet()) {
+ sb.append(" ").append(entry.getKey()).append(": ").append(StringUtils.join(entry.getValue(), ";")).append("\n");
+ }
+ sb.append(" cost: ").append(DateTimeAdvice.millisecondPretty(receiver.getCostTime())).append("\n");
+ sb.append(" body: ").append(bodyToString(receiver.getBody()));
+ }
+ sb.append("\n\n=========================================================================================================\n");
+ log.debug(sb.toString());
+ }
+
+ private String bodyToString(Object body) {
+ if (body == null) {
+ return "(No content)";
+ } else if (body instanceof String) {
+ return (String) body;
+ } else {
+ return "(Binary byte data)";
+ }
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/RawRequestBody.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/RawRequestBody.java
new file mode 100644
index 0000000..8718fd4
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/RawRequestBody.java
@@ -0,0 +1,22 @@
+package develop.toolkit.base.struct.http;
+
+import lombok.RequiredArgsConstructor;
+
+/**
+ * @author qiushui on 2021-09-16.
+ */
+@RequiredArgsConstructor
+public class RawRequestBody implements HttpRequestBody {
+
+ private final String raw;
+
+ @Override
+ public String toString() {
+ return raw;
+ }
+
+ @Override
+ public String getBody() {
+ return raw;
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/SenderHandler.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/SenderHandler.java
new file mode 100644
index 0000000..848d41c
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/SenderHandler.java
@@ -0,0 +1,31 @@
+package develop.toolkit.base.struct.http;
+
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+
+/**
+ * 发送器扩展逻辑
+ *
+ * @author qiushui on 2020-09-11.
+ */
+@FunctionalInterface
+public interface SenderHandler {
+
+ default HttpRequest.BodyPublisher bodyPublisher(HttpRequestBody> requestBody) {
+ if (requestBody == null) {
+ return HttpRequest.BodyPublishers.noBody();
+ } else if (requestBody instanceof RawRequestBody) {
+ return HttpRequest.BodyPublishers.ofString(((RawRequestBody) requestBody).getBody());
+ } else if (requestBody instanceof FormUrlencodedBody) {
+ return HttpRequest.BodyPublishers.ofString(((FormUrlencodedBody) requestBody).getBody());
+ } else if (requestBody instanceof ByteRequestBody) {
+ return HttpRequest.BodyPublishers.ofByteArray(((ByteRequestBody) requestBody).getBody());
+ } else if (requestBody instanceof MultiPartFormDataBody) {
+ return HttpRequest.BodyPublishers.ofByteArray(((MultiPartFormDataBody) requestBody).getBody());
+ } else {
+ throw new AssertionError();
+ }
+ }
+
+ HttpResponse.BodyHandler bodyHandler();
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/StringBodySenderHandler.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/StringBodySenderHandler.java
new file mode 100644
index 0000000..9e6a916
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/http/StringBodySenderHandler.java
@@ -0,0 +1,14 @@
+package develop.toolkit.base.struct.http;
+
+import java.net.http.HttpResponse;
+
+/**
+ * @author qiushui on 2020-09-11.
+ */
+public final class StringBodySenderHandler implements SenderHandler {
+
+ @Override
+ public HttpResponse.BodyHandler bodyHandler() {
+ return HttpResponse.BodyHandlers.ofString();
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/range/IntRange.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/range/IntRange.java
new file mode 100644
index 0000000..9e66676
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/range/IntRange.java
@@ -0,0 +1,22 @@
+package develop.toolkit.base.struct.range;
+
+/**
+ * 整型范围
+ */
+public class IntRange extends Range {
+
+ public IntRange(Integer start, Integer end) {
+ super(start, end);
+ }
+
+ /**
+ * 生成整型数组
+ */
+ public final Integer[] generate() {
+ Integer[] array = new Integer[end - start];
+ for (int i = 0; i < array.length; i++) {
+ array[i] = start + i;
+ }
+ return array;
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/range/LongRange.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/range/LongRange.java
new file mode 100644
index 0000000..8f2c6f8
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/range/LongRange.java
@@ -0,0 +1,23 @@
+package develop.toolkit.base.struct.range;
+
+/**
+ * 长整型范围
+ */
+@SuppressWarnings("unused")
+public class LongRange extends Range {
+
+ public LongRange(Long start, Long end) {
+ super(start, end);
+ }
+
+ /**
+ * 生成长整型数组
+ */
+ public final long[] generate() {
+ long[] array = new long[(int) (end - start)];
+ for (int i = 0; i < array.length; i++) {
+ array[i] = start + i;
+ }
+ return array;
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/range/Range.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/range/Range.java
new file mode 100644
index 0000000..552e26c
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/struct/range/Range.java
@@ -0,0 +1,26 @@
+package develop.toolkit.base.struct.range;
+
+import develop.toolkit.base.utils.CompareAdvice;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import org.apache.commons.lang3.Validate;
+
+/**
+ * 范围结构体
+ *
+ * @param
+ */
+@Getter
+@EqualsAndHashCode
+public class Range> {
+
+ protected T start;
+
+ protected T end;
+
+ public Range(T start, T end) {
+ Validate.isTrue(CompareAdvice.gte(end, start), "Start value must be smaller or equal to end value.");
+ this.start = start;
+ this.end = end;
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/ArrayAdvice.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/ArrayAdvice.java
new file mode 100644
index 0000000..e549526
--- /dev/null
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/ArrayAdvice.java
@@ -0,0 +1,424 @@
+package develop.toolkit.base.utils;
+
+import develop.toolkit.base.components.Counter;
+import develop.toolkit.base.struct.KeyValuePairs;
+import develop.toolkit.base.struct.ListInMap;
+import develop.toolkit.base.struct.TwoValues;
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.lang.reflect.Array;
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 数组增强工具
+ *
+ * @author qiushui on 2020-07-17.
+ */
+@SuppressWarnings("unused")
+public final class ArrayAdvice {
+
+ /**
+ * 检查元素存在
+ */
+ public static boolean contains(E[] array, Object target, Function function) {
+ if (array != null) {
+ for (E item : array) {
+ Object value = function.apply(item);
+ if (target == null) {
+ return value == null;
+ } else if (target.equals(value)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 检查元素存在
+ */
+ public static boolean contains(E[] array, Object target) {
+ if (array != null) {
+ for (E item : array) {
+ if (target == null) {
+ return item == null;
+ } else if (target.equals(item)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 获得第一个匹配的元素
+ */
+ public static Optional getFirstMatch(E[] array, Object target, Function function) {
+ if (array != null) {
+ for (E item : array) {
+ final Object value = function.apply(item);
+ if (target != null) {
+ if (target.equals(value)) {
+ return Optional.ofNullable(item);
+ }
+ } else if (value == null) {
+ return Optional.ofNullable(item);
+ }
+ }
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * 获得第一个匹配的元素
+ */
+ public static Optional getFirstMatch(E[] array, Object target) {
+ if (array != null && target != null) {
+ for (E item : array) {
+ if (target.equals(item)) {
+ return Optional.of(item);
+ }
+ }
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * 获得第一个判断是true的元素
+ */
+ public static Optional getFirstTrue(E[] array, Predicate predicate) {
+ if (array != null) {
+ for (E item : array) {
+ if (predicate.test(item)) {
+ return Optional.ofNullable(item);
+ }
+ }
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * 获得第一个判断是false的元素
+ */
+ public static Optional getFirstFalse(E[] array, Predicate predicate) {
+ if (array != null) {
+ for (E item : array) {
+ if (!predicate.test(item)) {
+ return Optional.ofNullable(item);
+ }
+ }
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * 获得全部匹配的元素
+ */
+ public static List getAllMatch(E[] array, Object target, Function function) {
+ if (array == null) {
+ return null;
+ }
+ return Stream
+ .of(array)
+ .filter(item -> {
+ Object value = function == null ? item : function.apply(item);
+ if (target == null) {
+ return value == null;
+ } else {
+ return target.equals(value);
+ }
+ })
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 全部匹配
+ */
+ public static boolean allMatch(E[] array, Predicate predicate) {
+ if (predicate == null || array == null) {
+ return false;
+ }
+ for (E e : array) {
+ if (!predicate.test(e)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 任意一个匹配
+ */
+ public static boolean anyMatch(E[] array, Predicate predicate) {
+ if (array != null && predicate != null) {
+ for (E e : array) {
+ if (predicate.test(e)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 判断所有元素的处理值相等
+ */
+ public static boolean allAccept(E[] array, Function function) {
+ if (array == null || array.length == 0) {
+ return false;
+ }
+ Object targetValue = function == null ? array[0] : function.apply(array[0]);
+ for (int i = 1, size = array.length; i < size; i++) {
+ Object itemValue = function == null ? array[i] : function.apply(array[i]);
+ if ((targetValue != null && !targetValue.equals(itemValue)) || (targetValue == null && itemValue != null)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 分组
+ */
+ public static ListInMap grouping(E[] array, Function keySupplier, Function valueSupplier) {
+ ListInMap map = new ListInMap<>();
+ for (E item : array) {
+ map.putItem(keySupplier.apply(item), valueSupplier.apply(item));
+ }
+ return map;
+ }
+
+ public static ListInMap grouping(V[] array, Function keySupplier) {
+ ListInMap map = new ListInMap<>();
+ for (V item : array) {
+ map.putItem(keySupplier.apply(item), item);
+ }
+ return map;
+ }
+
+ public static Map groupingUniqueKey(V[] array, Function keySupplier) {
+ Map map = new HashMap<>();
+ for (V item : array) {
+ map.put(keySupplier.apply(item), item);
+ }
+ return map;
+ }
+
+ public static Map groupingUniqueKey(E[] array, Function keySupplier, Function valueSupplier) {
+ Map map = new HashMap<>();
+ for (E item : array) {
+ map.put(keySupplier.apply(item), valueSupplier.apply(item));
+ }
+ return map;
+ }
+
+ /**
+ * 分组求数量
+ */
+ public static Counter groupingCount(E[] array, Function keySupplier) {
+ Counter counter = new Counter<>();
+ for (E item : array) {
+ counter.add(keySupplier.apply(item));
+ }
+ return counter;
+ }
+
+ /**
+ * 并集
+ */
+ @SafeVarargs
+ public static Set union(E[]... arrays) {
+ Set set = new HashSet<>();
+ for (E[] array : arrays) {
+ set.addAll(Arrays.asList(array));
+ }
+ return set;
+ }
+
+ /**
+ * 交集
+ */
+ @SafeVarargs
+ public static Set intersection(E[] master, E[]... other) {
+ Set set = new HashSet<>();
+ a:
+ for (E e : master) {
+ for (E[] array : other) {
+ if (!contains(array, e)) continue a;
+ }
+ set.add(e);
+ }
+ return set;
+ }
+
+ /**
+ * 差集
+ */
+ public static Set difference(E[] master, E[] other) {
+ Set set = new HashSet<>();
+ for (E e : master) {
+ if (!contains(other, e)) {
+ set.add(e);
+ }
+ }
+ return set;
+ }
+
+ /**
+ * 合并多数组
+ */
+ @SuppressWarnings("unchecked")
+ @SafeVarargs
+ public static E[] merge(Class clazz, E[]... arrays) {
+ E[] mergeArray = (E[]) Array.newInstance(
+ clazz,
+ Stream.of(arrays).mapToInt(array -> array.length).sum()
+ );
+ int i = 0;
+ for (E[] array : arrays) {
+ for (E item : array) {
+ if (item != null) {
+ mergeArray[i++] = item;
+ }
+ }
+ }
+ return mergeArray;
+ }
+
+ /**
+ * 关联
+ * 将集合target按条件与集合master配对
+ */
+ public static ListInMap associate(E[] master, T[] target, BiPredicate predicate) {
+ ListInMap map = new ListInMap<>();
+ for (E e : master) {
+ for (T t : target) {
+ if (predicate.test(e, t)) {
+ map.putItem(e, t);
+ }
+ }
+ }
+ return map;
+ }
+
+ /**
+ * 关联 (明确是单个的)
+ * 将集合target按条件与集合master配对
+ */
+ public static KeyValuePairs associateOne(E[] master, T[] target, BiPredicate predicate) {
+ final KeyValuePairs keyValuePairs = new KeyValuePairs<>();
+ for (E e : master) {
+ final T matchT = getFirstTrue(target, t -> predicate.test(e, t)).orElse(null);
+ keyValuePairs.addKeyValue(e, matchT);
+ }
+ return keyValuePairs;
+ }
+
+ /**
+ * 划分
+ * 按条件把集合拆分成满足条件和不满足条件的两个集合
+ */
+ public static TwoValues, List> partition(E[] collection, Predicate predicate) {
+ List match = new LinkedList<>();
+ List notMatch = new LinkedList<>();
+ for (E e : collection) {
+ if (predicate.test(e)) {
+ match.add(e);
+ } else {
+ notMatch.add(e);
+ }
+ }
+ return TwoValues.of(
+ Collections.unmodifiableList(match),
+ Collections.unmodifiableList(notMatch)
+ );
+ }
+
+ /**
+ * 压缩
+ * 将两个集合的元素按索引捆绑到一起
+ */
+ public static List> zip(T[] master, S[] other) {
+ final int length = master.length;
+ if (length != other.length) {
+ throw new IllegalArgumentException("list size must be same");
+ }
+ List> list = new ArrayList<>(length);
+ for (int i = 0; i < length; i++) {
+ list.add(TwoValues.of(master[i], other[i]));
+ }
+ return list;
+ }
+
+ /**
+ * 分页处理
+ */
+ public static void pagingProcess(T[] array, int size, PagingProcessor consumer) {
+ final int total = array.length;
+ final int page = total % size == 0 ? (total / size) : (total / size + 1);
+ for (int i = 0; i < page; i++) {
+ int fromIndex = i * size;
+ int toIndex = fromIndex + Math.min(total - fromIndex, size);
+ T[] subArray = ArrayUtils.subarray(array, fromIndex, toIndex);
+ consumer.process(i, page, subArray);
+ }
+ }
+
+ /**
+ * 分页处理
+ */
+ public static R pagingProcess(
+ T[] array,
+ int size,
+ R initialValue,
+ BiFunction reduceFunction,
+ PagingReduceProcessor consumer
+ ) {
+ final int total = array.length;
+ final int page = total % size == 0 ? (total / size) : (total / size + 1);
+ for (int i = 0; i < page; i++) {
+ int fromIndex = i * size;
+ int toIndex = fromIndex + Math.min(total - fromIndex, size);
+ T[] subArray = ArrayUtils.subarray(array, fromIndex, toIndex);
+ final R r = consumer.process(i, page, subArray);
+ initialValue = reduceFunction.apply(initialValue, r);
+ }
+ return initialValue;
+ }
+
+ /**
+ * 指定排序
+ * 把master的元素值按sortTarget的元素值排序,条件按predicate
+ */
+ public static List sort(T[] master, S[] sortTarget, BiPredicate predicate) {
+ return Stream
+ .of(sortTarget)
+ .map(s -> ArrayAdvice.getFirstTrue(master, c -> predicate.test(c, s)).orElse(null))
+ .collect(Collectors.toList());
+ }
+
+ public static List sort(T[] master, Collection sortTarget, BiPredicate predicate) {
+ return sortTarget
+ .stream()
+ .map(s -> ArrayAdvice.getFirstTrue(master, c -> predicate.test(c, s)).orElse(null))
+ .collect(Collectors.toList());
+ }
+
+ @FunctionalInterface
+ public interface PagingProcessor {
+
+ void process(int page, int total, T[] subArray);
+ }
+
+ @FunctionalInterface
+ public interface PagingReduceProcessor {
+
+ R process(int page, int total, T[] subArray);
+ }
+}
diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/CollectionAdvice.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/CollectionAdvice.java
index 917dd5e..b465a3d 100644
--- a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/CollectionAdvice.java
+++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/CollectionAdvice.java
@@ -1,38 +1,61 @@
package develop.toolkit.base.utils;
-import develop.toolkit.base.struct.CollectionInMap;
+import develop.toolkit.base.components.Counter;
+import develop.toolkit.base.struct.KeyValuePairs;
+import develop.toolkit.base.struct.ListInMap;
import develop.toolkit.base.struct.TwoValues;
-import lombok.NonNull;
import java.util.*;
-import java.util.function.Function;
-import java.util.function.Predicate;
+import java.util.function.*;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* 集合增强工具
*
* @author qiushui on 2018-12-20.
*/
-public final class CollectionAdvice {
-
- /**
- * 检查元素
- *
- * @param collection
- * @param target
- * @param function
- * @param
- * @param
- * @return
- */
- public static boolean contains(@NonNull Collection collection, R target, @NonNull Function function) {
- for (E item : collection) {
- R value = function.apply(item);
- if (target == null) {
- return value == null;
- } else if (target.equals(value)) {
- return true;
+@SuppressWarnings("unused")
+public abstract class CollectionAdvice {
+
+ /**
+ * 获得元素
+ */
+ public static Optional get(List list, int index) {
+ return Optional
+ .ofNullable(list)
+ .filter(Predicate.not(List::isEmpty))
+ .map(c -> c.get(index));
+ }
+
+ /**
+ * 检查元素存在
+ */
+ public static boolean contains(Collection