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 function) { + return success ? IWantData.ok(function.apply(data)) : IWantData.fail(message); + } + + /** + * 扁平化转换 + * + * @param function 转换函数 + * @param 目标类型 + * @return 转换值 + */ + public IWantData flatMap(Function> 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 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 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 collection, Object target, Function function) { + if (collection != null) { + for (E item : collection) { + Object value = function.apply(item); + if (target == null) { + return value == null; + } else if (target.equals(value)) { + return true; + } + } + } + return false; + } + + /** + * 检查元素存在 + */ + public static boolean contains(Collection collection, Object target) { + if (collection != null) { + for (E item : collection) { + if (target == null) { + return item == null; + } else if (target.equals(item)) { + return true; + } } } return false; @@ -40,21 +63,32 @@ public static boolean contains(@NonNull Collection collection, R targe /** * 获得第一个匹配的元素 - * - * @param collection - * @param target - * @param function - * @param - * @param - * @return - */ - public static Optional getFirstMatch(@NonNull Collection collection, R target, @NonNull Function function) { - for (E item : collection) { - R value = function.apply(item); - if (target == null) { - return value == null ? Optional.ofNullable(item) : Optional.empty(); - } else if (target.equals(value)) { - return Optional.ofNullable(item); + */ + public static Optional getFirstMatch(Collection collection, Object target, Function function) { + if (collection != null) { + for (E item : collection) { + 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(Collection collection, Object target) { + if (collection != null && target != null) { + for (E item : collection) { + if (target.equals(item)) { + return Optional.of(item); + } } } return Optional.empty(); @@ -62,16 +96,13 @@ public static Optional getFirstMatch(@NonNull Collection collection /** * 获得第一个判断是true的元素 - * - * @param collection - * @param predicate - * @param - * @return - */ - public static Optional getFirstTrue(@NonNull Collection collection, @NonNull Predicate predicate) { - for (E item : collection) { - if (predicate.test(item)) { - return Optional.ofNullable(item); + */ + public static Optional getFirstTrue(Collection collection, Predicate predicate) { + if (collection != null) { + for (E item : collection) { + if (predicate.test(item)) { + return Optional.ofNullable(item); + } } } return Optional.empty(); @@ -79,16 +110,13 @@ public static Optional getFirstTrue(@NonNull Collection collection, @N /** * 获得第一个判断是false的元素 - * - * @param collection - * @param predicate - * @param - * @return - */ - public static Optional getFirstFalse(@NonNull Collection collection, @NonNull Predicate predicate) { - for (E item : collection) { - if (!predicate.test(item)) { - return Optional.ofNullable(item); + */ + public static Optional getFirstFalse(Collection collection, Predicate predicate) { + if (collection != null) { + for (E item : collection) { + if (!predicate.test(item)) { + return Optional.ofNullable(item); + } } } return Optional.empty(); @@ -96,56 +124,114 @@ public static Optional getFirstFalse(@NonNull Collection collection, @ /** * 获得全部匹配的元素 - * - * @param collection - * @param target - * @param function - * @param - * @param - * @return - */ - public static List getAllMatch(@NonNull Collection collection, R target, @NonNull Function function) { - return collection.stream().filter(item -> { - R value = function.apply(item); - if (target == null) { - return value == null; - } else { - return target.equals(value); + */ + public static List getAllMatch(Collection collection, Object target, Function function) { + if (collection == null) { + return null; + } + return collection + .stream() + .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(Collection collection, Predicate predicate) { + if (predicate == null || collection == null) { + return false; + } + for (E e : collection) { + if (!predicate.test(e)) { + return false; } - }).collect(Collectors.toList()); + } + return true; } /** - * 转化为Map - * - * @param collection - * @param keySupplier - * @param valueSupplier - * @param - * @param - * @param - * @return + * 任意一个匹配 */ - public static Map toMap(@NonNull Collection collection, @NonNull Function keySupplier, @NonNull Function valueSupplier) { - Map map = new HashMap<>(); - for (E item : collection) { - map.put(keySupplier.apply(item), valueSupplier.apply(item)); + public static boolean anyMatch(Collection collection, Predicate predicate) { + if (collection != null && predicate != null) { + for (E e : collection) { + if (predicate.test(e)) { + return true; + } + } } + return false; + } + + /** + * 判断所有元素的处理值相等 + */ + public static boolean allAccept(Collection collection, Function function) { + if (collection == null || collection.isEmpty()) { + return false; + } + List list = new ArrayList<>(collection); + Object targetValue = function == null ? list.get(0) : function.apply(list.get(0)); + for (int i = 1, size = list.size(); i < size; i++) { + Object itemValue = function == null ? list.get(i) : function.apply(list.get(i)); + if ((targetValue != null && !targetValue.equals(itemValue)) || (targetValue == null && itemValue != null)) { + return false; + } + } + return true; + } + + /** + * 分组 + */ + public static ListInMap grouping(Collection collection, Function keySupplier, Function valueSupplier) { + ListInMap map = new ListInMap<>(); + collection.forEach(item -> map.putItem(keySupplier.apply(item), valueSupplier.apply(item))); + return map; + } + + public static ListInMap grouping(Collection collection, Function keySupplier) { + ListInMap map = new ListInMap<>(); + collection.forEach(item -> map.putItem(keySupplier.apply(item), item)); + return map; + } + + public static Map groupingUniqueKey(Collection collection, Function keySupplier) { + Map map = new HashMap<>(); + collection.forEach(item -> map.put(keySupplier.apply(item), item)); return map; } + public static Map groupingUniqueKey(Collection collection, Function keySupplier, Function valueSupplier) { + Map map = new HashMap<>(); + collection.forEach(item -> map.put(keySupplier.apply(item), valueSupplier.apply(item))); + return map; + } + + /** + * 分组求数量 + */ + public static Counter groupingCount(Collection collection, Function keySupplier) { + Counter counter = new Counter<>(); + collection.forEach(item -> counter.add(keySupplier.apply(item))); + return counter; + } + /** * 并集 - * - * @param master - * @param other - * @param - * @return */ @SafeVarargs - public static Set union(Collection master, Collection... other) { - Set set = new HashSet<>(master); - for (Collection collection : other) { + public static Set union(Collection... collections) { + Set set = new HashSet<>(); + for (Collection collection : collections) { set.addAll(collection); } return set; @@ -153,52 +239,75 @@ public static Set union(Collection master, Collection... other) { /** * 交集 - * - * @param master - * @param other - * @param - * @return */ @SafeVarargs public static Set intersection(Collection master, Collection... other) { - Set set = new HashSet<>(master); - for (Collection collection : other) { - set.removeIf(Predicate.not(collection::contains)); + Set set = new HashSet<>(); + a: + for (E e : master) { + for (Collection collection : other) { + if (!contains(collection, e)) continue a; + } + set.add(e); } return set; } + /** + * 差集 + */ + public static Set difference(Collection master, Collection other) { + Set set = new HashSet<>(master); + set.removeIf(other::contains); + return set; + } + + /** + * 合并多集合 + */ + @SafeVarargs + public static , E> T merge(Supplier supplier, Collection... collections) { + T collection = supplier.get(); + for (Collection coll : collections) { + if (coll != null) { + collection.addAll(coll); + } + } + return collection; + } + /** * 关联 * 将集合target按条件与集合master配对 - * - * @param master - * @param target - * @param predicate - * @param - * @param - * @return - */ - public static CollectionInMap associate(Collection master, Collection target, AssociatePredicate predicate) { - CollectionInMap map = new CollectionInMap<>(); + */ + public static ListInMap associate(Collection master, Collection target, BiPredicate predicate) { + ListInMap map = new ListInMap<>(); for (E e : master) { for (T t : target) { if (predicate.test(e, t)) { - map.addItemSoft(e, t); + map.putItem(e, t); } } } return map; } + /** + * 关联 (明确是单个的) + * 将集合target按条件与集合master配对 + */ + public static KeyValuePairs associateOne(Collection master, Collection 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; + } + /** * 划分 * 按条件把集合拆分成满足条件和不满足条件的两个集合 - * - * @param collection - * @param predicate - * @param - * @return */ public static TwoValues, List> partition(Collection collection, Predicate predicate) { List match = new LinkedList<>(); @@ -210,30 +319,112 @@ public static TwoValues, List> partition(Collection collection notMatch.add(e); } } - return TwoValues.of(match, notMatch); + return TwoValues.of( + Collections.unmodifiableList(match), + Collections.unmodifiableList(notMatch) + ); } /** * 压缩 * 将两个集合的元素按索引捆绑到一起 - * - * @param master - * @param other - * @param - * @return */ - public static List> zip(List master, List other) { + public static List> zip(List master, List other) { if (master.size() != other.size()) { - throw new IllegalArgumentException("list size must be some"); + throw new IllegalArgumentException("list size must be same"); } - List> list = new LinkedList<>(); + List> list = new LinkedList<>(); for (int i = 0; i < master.size(); i++) { list.add(TwoValues.of(master.get(i), other.get(i))); } return list; } - public interface AssociatePredicate { - boolean test(E master, T target); + /** + * 分页处理 + */ + public static void pagingProcess(List list, int size, PagingProcessor processor) { + final int total = list.size(); + 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); + List subList = list.subList(fromIndex, toIndex); + processor.process(i, page, subList); + } + } + + /** + * 分页处理 (通过总数) + */ + public static void pagingProcess(int total, int size, BiConsumer consumer) { + final int page = total % size == 0 ? (total / size) : (total / size + 1); + for (int i = 0; i < page; i++) { + consumer.accept(i, page); + } + } + + /** + * 分页处理 (含返回值) + */ + public static R pagingProcess( + List list, + int size, + R initialValue, + BiFunction reduceFunction, + PagingReduceProcessor processor + ) { + final int total = list.size(); + 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); + List subList = list.subList(fromIndex, toIndex); + final R r = processor.process(i, page, subList); + initialValue = reduceFunction.apply(initialValue, r); + } + return initialValue; + } + + /** + * 分页处理 (通过总数 含返回值) + */ + public static R pagingProcess(int total, int size, R initialValue, BiFunction reduceFunction, BiFunction function) { + final int page = total % size == 0 ? (total / size) : (total / size + 1); + for (int i = 0; i < page; i++) { + final R r = function.apply(i, page); + initialValue = reduceFunction.apply(initialValue, r); + } + return initialValue; + } + + /** + * 指定排序 + * 把master的元素值按sortTarget的元素值排序,条件按predicate + */ + public static List sort(Collection master, Collection sortTarget, BiPredicate predicate) { + return sortTarget + .stream() + .map(s -> CollectionAdvice.getFirstTrue(master, c -> predicate.test(c, s)).orElse(null)) + .collect(Collectors.toList()); + } + + public static List sort(Collection master, S[] sortTarget, BiPredicate predicate) { + return Stream + .of(sortTarget) + .map(s -> CollectionAdvice.getFirstTrue(master, c -> predicate.test(c, s)).orElse(null)) + .collect(Collectors.toList()); + } + + @FunctionalInterface + public interface PagingProcessor { + + void process(int page, int total, List subList); + } + + @FunctionalInterface + public interface PagingReduceProcessor { + + R process(int page, int total, List subList); } } diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/CompareAdvice.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/CompareAdvice.java index 2d38489..f08a2cd 100644 --- a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/CompareAdvice.java +++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/CompareAdvice.java @@ -7,14 +7,11 @@ * * @author qiushui on 2019-08-23. */ +@SuppressWarnings("unused") public final class CompareAdvice { /** * 小于 - * - * @param a - * @param b - * @return */ public static > boolean lt(@NonNull T a, @NonNull T b) { return a.compareTo(b) < 0; @@ -22,10 +19,6 @@ public static > boolean lt(@NonNull T a, @NonNull T b) { /** * 小于等于 - * - * @param a - * @param b - * @return */ public static > boolean lte(@NonNull T a, @NonNull T b) { return a.compareTo(b) <= 0; @@ -33,10 +26,6 @@ public static > boolean lte(@NonNull T a, @NonNull T b) /** * 大于 - * - * @param a - * @param b - * @return */ public static > boolean gt(@NonNull T a, @NonNull T b) { return a.compareTo(b) > 0; @@ -44,10 +33,6 @@ public static > boolean gt(@NonNull T a, @NonNull T b) { /** * 大于等于 - * - * @param a - * @param b - * @return */ public static > boolean gte(@NonNull T a, @NonNull T b) { return a.compareTo(b) >= 0; @@ -55,10 +40,6 @@ public static > boolean gte(@NonNull T a, @NonNull T b) /** * 等于 - * - * @param a - * @param b - * @return */ public static > boolean eq(@NonNull T a, @NonNull T b) { return a.compareTo(b) == 0; @@ -66,36 +47,58 @@ public static > boolean eq(@NonNull T a, @NonNull T b) { /** * 在之间(闭区间) - * - * @param a - * @param start - * @param end - * @param - * @return */ public static > boolean between(@NonNull T a, @NonNull T start, @NonNull T end) { return gte(a, start) && lte(a, end); } + /** + * 在之间(左闭区间) + */ + public static > boolean betweenLeft(@NonNull T a, @NonNull T start, @NonNull T end) { + return gte(a, start) && lt(a, end); + } + + /** + * 在之间(右闭区间) + */ + public static > boolean betweenRight(@NonNull T a, @NonNull T start, @NonNull T end) { + return gt(a, start) && lte(a, end); + } + + /** + * 在之间(开区间) + */ + public static > boolean betweenOpen(@NonNull T a, @NonNull T start, @NonNull T end) { + return gt(a, start) && lt(a, end); + } + /** * 返回两者中较大值 - * - * @param a - * @param b - * @return */ public static > T max(@NonNull T a, @NonNull T b) { - return gt(a, b) ? a : b; + return gte(a, b) ? a : b; } /** * 返回两者中较小值 - * - * @param a - * @param b - * @return */ public static > T min(@NonNull T a, @NonNull T b) { - return lt(a, b) ? a : b; + return lte(a, b) ? a : b; + } + + /** + * 调整边界值 + */ + public static > T adjustRange(T x, T start, T end) { + if (gt(start, end)) { + throw new IllegalArgumentException("start great than end"); + } else if (lt(x, start)) { + return start; + } else if (gt(x, end)) { + return end; + } else { + return x; + } } } diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/CompressAdvice.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/CompressAdvice.java new file mode 100644 index 0000000..5e097ee --- /dev/null +++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/CompressAdvice.java @@ -0,0 +1,159 @@ +package develop.toolkit.base.utils; + +import develop.toolkit.base.struct.ZipWrapper; + +import java.io.*; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.util.List; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * 压缩增强 + * + * @author qiushui on 2022-04-17. + */ +public abstract class CompressAdvice { + + public static class GZip { + + public static void compress(InputStream inputStream, OutputStream outputStream) throws IOException { + try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream)) { + inputStream.transferTo(gzipOutputStream); + } + } + + public static void compress(byte[] data, OutputStream outputStream) throws IOException { + try (ByteArrayInputStream bais = new ByteArrayInputStream(data)) { + compress(bais, outputStream); + } + } + + public static byte[] compress(InputStream inputStream) throws IOException { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + compress(inputStream, baos); + return baos.toByteArray(); + } + } + + public static byte[] compress(byte[] data) throws IOException { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + compress(data, baos); + return baos.toByteArray(); + } + } + + public static void uncompress(InputStream inputStream, OutputStream outputStream) throws IOException { + try (GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream)) { + gzipInputStream.transferTo(outputStream); + } + } + + public static void uncompress(byte[] data, OutputStream outputStream) throws IOException { + try (ByteArrayInputStream bais = new ByteArrayInputStream(data)) { + uncompress(bais, outputStream); + } + } + + public static byte[] uncompress(InputStream inputStream) throws IOException { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + uncompress(inputStream, baos); + return baos.toByteArray(); + } + } + + public static byte[] uncompress(byte[] data) throws IOException { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + uncompress(data, baos); + return baos.toByteArray(); + } + } + } + + public static class Zip { + + public static void compress(Path path, OutputStream outputStream) throws IOException { + try (ZipOutputStream zos = new ZipOutputStream(outputStream)) { + Files.walkFileTree(path, new FileVisitor<>() { + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path filePath, BasicFileAttributes attrs) throws IOException { + String currentName = filePath.subpath(path.getNameCount(), filePath.getNameCount()).toString(); + File file = filePath.toFile(); + final ZipEntry zipEntry = new ZipEntry(currentName); + zipEntry.setMethod(currentName.endsWith(".zip") ? ZipEntry.STORED : ZipEntry.DEFLATED); + zipEntry.setLastModifiedTime(FileTime.fromMillis(file.lastModified())); + zos.putNextEntry(zipEntry); + try (InputStream is = new FileInputStream(file)) { + is.transferTo(zos); + } + zos.closeEntry(); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + return FileVisitResult.CONTINUE; + } + }); + } + } + + public static byte[] compress(Path path) throws IOException { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + compress(path, baos); + return baos.toByteArray(); + } + } + + public static void compress(Path path, Path outPath) throws IOException { + try (OutputStream os = new FileOutputStream(outPath.toFile())) { + compress(path, os); + } + } + + + public static void compress(ZipWrapper zipWrapper, OutputStream outputStream) throws IOException { + try (ZipOutputStream zos = new ZipOutputStream(outputStream)) { + recursiveCompress(zipWrapper, "", zos); + } + } + + private static void recursiveCompress(ZipWrapper zipWrapper, String parentPath, ZipOutputStream zos) throws IOException { + final String currentName = parentPath + zipWrapper.getFilename(); + if (zipWrapper.isFile()) { + final ZipEntry zipEntry = new ZipEntry(currentName); + zipWrapper.configureZipEntry(zipEntry); + zos.putNextEntry(zipEntry); + try (InputStream is = zipWrapper.getInputStreamSupplier().get()) { + is.transferTo(zos); + } + zos.closeEntry(); + } else { + List children = zipWrapper.getChildren(); + if (children != null) { + for (ZipWrapper childZipWrapper : children) { + recursiveCompress(childZipWrapper, currentName + File.separator, zos); + } + } + } + } + } +} diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/CryptAdvice.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/CryptAdvice.java new file mode 100644 index 0000000..072251e --- /dev/null +++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/CryptAdvice.java @@ -0,0 +1,323 @@ +package develop.toolkit.base.utils; + +import develop.toolkit.base.exception.CryptException; +import develop.toolkit.base.struct.TwoValues; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.apache.commons.lang3.ArrayUtils; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +/** + * 加密解密增强 + * + * @author qiushui on 2021-04-23. + */ +public abstract class CryptAdvice { + + /** + * DES算法 + */ + public static class DES { + + /** + * 加密 + * + * @param original 原文 + * @param secretKey 密钥 + * @return 密文 + */ + public static String encrypt(String original, String secretKey) { + try { + final Cipher cipher = initCipher(secretKey, Cipher.ENCRYPT_MODE); + final byte[] data = cipher.doFinal(original.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(data); + } catch (Exception e) { + throw new CryptException(e); + } + } + + /** + * 解密 + * + * @param ciphertext 密文 + * @param secretKey 密钥 + * @return 原文 + */ + public static String decrypt(String ciphertext, String secretKey) throws CryptException { + try { + final byte[] data = Base64.getDecoder().decode(ciphertext); + final Cipher cipher = initCipher(secretKey, Cipher.DECRYPT_MODE); + return new String(cipher.doFinal(data), StandardCharsets.UTF_8); + } catch (Exception e) { + throw new CryptException(e); + } + } + + private static Cipher initCipher(String secretKey, int mode) throws Exception { + final DESKeySpec dks = new DESKeySpec(secretKey.getBytes(StandardCharsets.UTF_8)); + final SecretKey secureKey = SecretKeyFactory.getInstance("DES").generateSecret(dks); + final Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); + cipher.init(mode, secureKey, new SecureRandom()); + return cipher; + } + } + + public static class AES { + + private static final String ALGORITHM = "AES"; + private static final String SECRET_KEY_ALGORITHM = "PBKDF2WithHmacSHA256"; + + /** + * 密钥长度枚举 + */ + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + public enum KeyLength { + + KEY_LENGTH_128(128), + KEY_LENGTH_192(192), + KEY_LENGTH_256(256); + + @Getter + private final int length; + } + + /** + * 创建密钥和iv + * + * @param keyLength 密钥长度 + * @return 密钥 + */ + @SneakyThrows(NoSuchAlgorithmException.class) + public static TwoValues createSecretKeyAndIv(KeyLength keyLength) { + final KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM); + keyGenerator.init(keyLength.getLength()); + final byte[] iv = new byte[16]; + new SecureRandom().nextBytes(iv); + final Base64.Encoder encoder = Base64.getEncoder(); + return TwoValues.of( + encoder.encodeToString(keyGenerator.generateKey().getEncoded()), + encoder.encodeToString(iv) + ); + } + + /** + * 根据密码创建密钥和iv + * + * @param keyLength 密钥长度 + * @param password 密码 + * @param salt 盐 + * @return 密钥 + */ + @SneakyThrows({NoSuchAlgorithmException.class, InvalidKeySpecException.class}) + public static TwoValues createSecretKeyAndIvByPassword(KeyLength keyLength, String password, String salt) { + final SecretKeyFactory factory = SecretKeyFactory.getInstance(SECRET_KEY_ALGORITHM); + final KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 65536, keyLength.getLength()); + final SecretKeySpec secretKeySpec = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), ALGORITHM); + final byte[] iv = new byte[16]; + new SecureRandom().nextBytes(iv); + final Base64.Encoder encoder = Base64.getEncoder(); + return TwoValues.of( + encoder.encodeToString(secretKeySpec.getEncoded()), + encoder.encodeToString(iv) + ); + } + + /** + * 加密 + * + * @param original 原文 + * @param secretKeyBase64 base64密钥 + * @param ivBase64 base64 iv + * @return 密文 + * @throws CryptException + */ + public static String encrypt(String original, String secretKeyBase64, String ivBase64) throws CryptException { + try { + final Base64.Decoder decoder = Base64.getDecoder(); + final byte[] secretKey = decoder.decode(secretKeyBase64); + final byte[] iv = decoder.decode(ivBase64); + final SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, SECRET_KEY_ALGORITHM); + final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(iv)); + final byte[] cipherText = cipher.doFinal(original.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(cipherText); + } catch (Exception e) { + throw new CryptException(e); + } + } + + /** + * 解密 + * + * @param cipherText 密文 + * @param secretKeyBase64 base64密钥 + * @param ivBase64 base64 iv + * @return 原文 + * @throws CryptException + */ + public static String decrypt(String cipherText, String secretKeyBase64, String ivBase64) throws CryptException { + try { + final Base64.Decoder decoder = Base64.getDecoder(); + final byte[] secretKey = decoder.decode(secretKeyBase64); + final byte[] iv = decoder.decode(ivBase64); + final SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, SECRET_KEY_ALGORITHM); + final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(iv)); + final byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(cipherText)); + return new String(plainText, StandardCharsets.UTF_8); + } catch (Exception e) { + throw new CryptException(e); + } + } + } + + /** + * RSA算法 + */ + public static class RSA { + + private static final String KEY_ALGORITHM = "RSA"; + private static final int KEY_SIZE = 1024; + private static final String SIGNATURE_ALGORITHM = "Sha1WithRSA"; + + /** + * 生成公钥和私钥对 + * + * @return 公钥 私钥 + */ + @SneakyThrows(NoSuchAlgorithmException.class) + public static TwoValues createRSAKeys() { + final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM); + keyPairGenerator.initialize(KEY_SIZE, new SecureRandom()); + final KeyPair keyPair = keyPairGenerator.generateKeyPair(); + final PublicKey publicKey = keyPair.getPublic(); + final PrivateKey privateKey = keyPair.getPrivate(); + final Base64.Encoder encoder = Base64.getEncoder(); + return TwoValues.of( + encoder.encodeToString(publicKey.getEncoded()), + encoder.encodeToString(privateKey.getEncoded()) + ); + } + + /** + * RSA公钥加密 + * + * @param original 原文 + * @param publicKeyBase64 base64公钥 + * @return 密文 + */ + public static String encrypt(String original, String publicKeyBase64) throws CryptException { + try { + final byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyBase64); + RSAPublicKey pubKey = (RSAPublicKey) KeyFactory + .getInstance(KEY_ALGORITHM) + .generatePublic(new X509EncodedKeySpec(publicKeyBytes)); + Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, pubKey); + final byte[] bytes = original.getBytes(StandardCharsets.UTF_8); + final int offset = 64; + byte[] enBytes = null; + for (int i = 0; i < bytes.length; i += offset) { + byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(bytes, i, i + 64)); + enBytes = ArrayUtils.addAll(enBytes, doFinal); + } + return Base64.getEncoder().encodeToString(enBytes); + } catch (Exception e) { + throw new CryptException(e); + } + } + + /** + * RSA私钥解密 + * + * @param ciphertext 密文 + * @param privateKeyBase64 base64私钥 + * @return 明文 + */ + public static String decrypt(String ciphertext, String privateKeyBase64) throws CryptException { + try { + final Base64.Decoder decoder = Base64.getDecoder(); + final byte[] privateKeyBytes = decoder.decode(privateKeyBase64); + PrivateKey privateKey = KeyFactory + .getInstance(KEY_ALGORITHM) + .generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes)); + Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + byte[] inputBytes = decoder.decode(ciphertext.getBytes(StandardCharsets.UTF_8)); + StringBuilder sb = new StringBuilder(); + final int offset = 128; + for (int i = 0; i < inputBytes.length; i += offset) { + byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(inputBytes, i, i + offset)); + sb.append(new String(doFinal)); + } + return sb.toString(); + } catch (Exception e) { + throw new CryptException(e); + } + } + + /** + * 生成base64签名结果 + * + * @param data 数据 + * @param privateKeyBase64 base64私钥 + * @return base64签名 + */ + public static String signature(byte[] data, String privateKeyBase64) { + try { + final byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyBase64); + Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM); + PrivateKey privateKey = KeyFactory + .getInstance(KEY_ALGORITHM) + .generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes)); + sign.initSign(privateKey); + sign.update(data); + return Base64.getEncoder().encodeToString(sign.sign()); + } catch (Exception e) { + throw new CryptException(e); + } + } + + /** + * 用公钥验证签名 + * + * @param data 数据 + * @param signatureBase64 base64签名字符串 + * @param publicKeyBase64 base64公钥 + */ + public static boolean verifySignature(byte[] data, String signatureBase64, String publicKeyBase64) { + try { + final Base64.Decoder decoder = Base64.getDecoder(); + final byte[] publicKeyBytes = decoder.decode(publicKeyBase64); + final byte[] signatureBytes = decoder.decode(signatureBase64); + RSAPublicKey publicKey = (RSAPublicKey) KeyFactory + .getInstance(KEY_ALGORITHM) + .generatePublic(new X509EncodedKeySpec(publicKeyBytes)); + Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM); + sign.initVerify(publicKey); + sign.update(data); + return sign.verify(signatureBytes); + } catch (Exception e) { + return false; + } + } + } +} diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/DateTimeAdvice.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/DateTimeAdvice.java index ec1c168..2adeb24 100644 --- a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/DateTimeAdvice.java +++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/DateTimeAdvice.java @@ -12,6 +12,7 @@ * * @author qiushui on 2019-02-20. */ +@SuppressWarnings("unused") public final class DateTimeAdvice { public static String now() { @@ -20,9 +21,6 @@ public static String now() { /** * 格式化Date - * - * @param date - * @return */ public static String format(Date date) { return format(date, DateFormatConstants.STANDARD); @@ -30,9 +28,6 @@ public static String format(Date date) { /** * 解析标准日期时间字符串 - * - * @param dateStr - * @return */ public static LocalDateTime parseStandard(CharSequence dateStr) { return LocalDateTime.parse(dateStr, DateFormatConstants.STANDARD_FORMATTER); @@ -40,10 +35,6 @@ public static LocalDateTime parseStandard(CharSequence dateStr) { /** * 格式化Date - * - * @param date - * @param pattern - * @return */ public static String format(Date date, String pattern) { if (date == null) { @@ -54,9 +45,6 @@ public static String format(Date date, String pattern) { /** * 格式化LocalDateTime - * - * @param localDateTime - * @return */ public static String format(LocalDateTime localDateTime) { return format(localDateTime, DateFormatConstants.STANDARD); @@ -64,10 +52,6 @@ public static String format(LocalDateTime localDateTime) { /** * 格式化LocalDateTime - * - * @param localDateTime - * @param pattern - * @return */ public static String format(LocalDateTime localDateTime, String pattern) { if (localDateTime == null) { @@ -79,9 +63,6 @@ public static String format(LocalDateTime localDateTime, String pattern) { /** * Date转到LocalDateTime - * - * @param date - * @return */ public static LocalDateTime toLocalDateTime(Date date) { if (date == null) { @@ -92,9 +73,6 @@ public static LocalDateTime toLocalDateTime(Date date) { /** * Date转到LocalDate - * - * @param date - * @return */ public static LocalDate toLocalDate(Date date) { if (date == null) { @@ -105,9 +83,6 @@ public static LocalDate toLocalDate(Date date) { /** * Date转到LocalTime - * - * @param date - * @return */ public static LocalTime toLocalTime(Date date) { if (date == null) { @@ -118,9 +93,6 @@ public static LocalTime toLocalTime(Date date) { /** * Date转到Instant - * - * @param date - * @return */ public static Instant toInstant(Date date) { if (date == null) { @@ -131,9 +103,6 @@ public static Instant toInstant(Date date) { /** * 从LocalDateTime转到Date - * - * @param localDateTime - * @return */ public static Date fromLocalDateTime(LocalDateTime localDateTime) { if (localDateTime == null) { @@ -144,24 +113,16 @@ public static Date fromLocalDateTime(LocalDateTime localDateTime) { /** * 从LocalDate转到Date - * - * @param localDate - * @return */ public static Date fromLocalDate(LocalDate localDate) { if (localDate == null) { return null; } - LocalTime localTime = LocalTime.of(0, 0, 0, 0); - LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime); - return fromLocalDateTime(localDateTime); + return fromLocalDateTime(localDate.atTime(LocalTime.MIN)); } /** * 从LocalTime转到Date - * - * @param localTime - * @return */ public static Date fromLocalTime(LocalTime localTime) { if (localTime == null) { @@ -173,9 +134,6 @@ public static Date fromLocalTime(LocalTime localTime) { /** * 从Instant转到Date - * - * @param instant - * @return */ public static Date fromInstant(Instant instant) { if (instant == null) { @@ -186,10 +144,6 @@ public static Date fromInstant(Instant instant) { /** * 判断是否同一个月 - * - * @param day1 - * @param day2 - * @return */ public static boolean isSameMonth(LocalDate day1, LocalDate day2) { return day1.getYear() == day2.getYear() && day1.getMonth().getValue() == day2.getMonth().getValue(); @@ -197,9 +151,6 @@ public static boolean isSameMonth(LocalDate day1, LocalDate day2) { /** * 毫秒美化 - * - * @param millisecond - * @return */ public static String millisecondPretty(long millisecond) { if (millisecond < 1000) { @@ -226,4 +177,14 @@ public static String millisecondPretty(long millisecond) { return day + "d" + hour + "h" + minute + "m" + second + "s" + milli + "ms"; } } + + /** + * 秒美化成HHmmSS格式 + */ + public static String secondPrettyToHHmmss(long seconds) { + long hours = seconds / 3600; + long minutes = (seconds - hours * 3600) / 60; + long second = seconds % 60; + return String.format("%02d:%02d:%02d", hours, minutes, second); + } } diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/FileAdvice.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/FileAdvice.java new file mode 100644 index 0000000..6938d20 --- /dev/null +++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/FileAdvice.java @@ -0,0 +1,116 @@ +package develop.toolkit.base.utils; + +import java.io.*; +import java.nio.charset.Charset; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Predicate; + +/** + * @author qiushui on 2021-03-15. + */ +public abstract class FileAdvice { + + public static void write(Path filePath, CharSequence text, Charset charset, boolean append) { + try { + touch(filePath); + Files.writeString( + filePath, + text, + charset, + StandardOpenOption.WRITE, + append ? StandardOpenOption.APPEND : StandardOpenOption.TRUNCATE_EXISTING + ); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static void write(Path filePath, Iterable lines, Charset charset, boolean append) { + try { + touch(filePath); + Files.write( + filePath, + lines, + charset, + StandardOpenOption.WRITE, + append ? StandardOpenOption.APPEND : StandardOpenOption.TRUNCATE_EXISTING + ); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static void touch(Path path) throws IOException { + final Path parent = path.getParent(); + if (Files.notExists(parent)) { + Files.createDirectories(parent); + } + if (Files.notExists(path)) { + Files.createFile(path); + } + } + + /** + * 遍历目录 找到所有满足条件的文件 + */ + public static List files(Path path, Predicate predicate) { + List paths = new LinkedList<>(); + try { + Files.walkFileTree(path, new FileVisitor<>() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (predicate.test(file)) { + paths.add(path); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + return FileVisitResult.CONTINUE; + } + }); + return Collections.unmodifiableList(paths); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * 截取文件中某一段的字节数据 + * + * @param bufferSize 缓冲区大小 + * @param offset 偏移量 + * @param chunkSize 截取块大小 + * @param file 文件 内部会采用随机读取文件RandomAccessFile + * @param out 输出流 + * @throws IOException IO异常 + */ + public static long sliceBytes(int bufferSize, long offset, long chunkSize, File file, OutputStream out) throws IOException { + try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) { + randomAccessFile.seek(offset); + long transferred = 0L; + int read; + final byte[] buffer = new byte[bufferSize]; + while (transferred < chunkSize && (read = randomAccessFile.read(buffer, 0, (int) Math.min(buffer.length, chunkSize - transferred))) != -1) { + transferred += read; + out.write(buffer, 0, read); + } + return transferred; + } + } +} diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/IOAdvice.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/IOAdvice.java index 57cae26..37d84b7 100644 --- a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/IOAdvice.java +++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/IOAdvice.java @@ -1,15 +1,14 @@ package develop.toolkit.base.utils; -import lombok.NonNull; +import develop.toolkit.base.struct.ListInMap; import java.io.*; +import java.lang.reflect.Constructor; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.util.LinkedList; -import java.util.List; -import java.util.Scanner; -import java.util.function.Consumer; +import java.util.*; import java.util.function.Function; +import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -17,88 +16,196 @@ * * @author qiushui on 2019-02-21. */ +@SuppressWarnings("unused") public final class IOAdvice { + /** + * 转换成字节数组 + */ + public static byte[] toByteArray(InputStream inputStream) { + try (inputStream; ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + inputStream.transferTo(baos); + return baos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * 转换成字节数组 + */ + public static byte[] toByteArrayFromClasspath(String filename) { + return toByteArray(readInputStreamFromClasspath(filename)); + } + /** * 文件读取行 - * - * @param filename - * @return - * @throws IOException */ - public static Stream readLines(String filename) throws IOException { - return readLines(filename, StandardCharsets.UTF_8); + public static Stream readLines(String filename) { + return readLines(filename, null); } /** * 文件读取行 - * - * @param filename - * @param charset - * @return - * @throws IOException */ - public static Stream readLines(String filename, Charset charset) throws IOException { - return readLines(new FileInputStream(filename), charset); + public static Stream readLines(String filename, Charset charset) { + try { + return readLines(new FileInputStream(filename), charset); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } } /** * 文本流读取行 - * - * @param inputStream - * @return - * @throws IOException */ public static Stream readLines(InputStream inputStream) { - return readLines(inputStream, StandardCharsets.UTF_8); + return readLines(inputStream, null); } /** * 文本流读取行 - * - * @param inputStream - * @param charset - * @return */ - public static Stream readLines(@NonNull InputStream inputStream, Charset charset) { - Scanner scanner = new Scanner(inputStream, charset); - List lines = new LinkedList<>(); - while (scanner.hasNext()) { - lines.add(scanner.nextLine()); + public static Stream readLines(InputStream inputStream, Charset charset) { + try (inputStream) { + Scanner scanner = new Scanner(inputStream, charset == null ? StandardCharsets.UTF_8 : charset); + List lines = new LinkedList<>(); + while (scanner.hasNext()) { + lines.add(scanner.nextLine()); + } + scanner.close(); + return lines.stream(); + } catch (IOException e) { + throw new RuntimeException(e); } - scanner.close(); - return lines.stream(); + } + + /** + * 从classpath读流 + */ + public static InputStream readInputStreamFromClasspath(String filename) { + return IOAdvice.class.getResourceAsStream(filename.startsWith("/") ? filename : ("/" + filename)); } /** * 从classpath读取文件 - * - * @param filename - * @param charset - * @return - * @throws IOException */ public static Stream readLinesFromClasspath(String filename, Charset charset) { - return readLines(IOAdvice.class.getResourceAsStream(filename), charset); + return readLines(readInputStreamFromClasspath(filename), charset); } /** * 从classpath读取文件 - * - * @param filename - * @return - * @throws IOException */ public static Stream readLinesFromClasspath(String filename) { - return readLines(IOAdvice.class.getResourceAsStream(filename)); + return readLines(readInputStreamFromClasspath(filename), null); + } + + /** + * 从classpath读取文件并每行用regex切分 + */ + public static Stream splitFromClasspath(String filename, String regex) { + return readLinesFromClasspath(filename).map(line -> line.split(regex)); + } + + /** + * 从classpath读取文件并每行用regex切分,然后装填到实体类 + */ + @SuppressWarnings("unchecked") + public static Stream splitFromClasspath(String filename, String regex, Class clazz) { + List list = splitFromClasspath(filename, regex).collect(Collectors.toList()); + if (list.isEmpty()) { + return Stream.empty(); + } else { + final String[] first = list.get(0); + final Constructor constructor = ArrayAdvice + .getFirstMatch(clazz.getConstructors(), first.length, Constructor::getParameterCount) + .orElseThrow(() -> new IllegalArgumentException("No match constructor for parameter size: " + first.length)); + final Class[] parameterTypes = constructor.getParameterTypes(); + return list + .stream() + .map(objs -> { + try { + final Object[] parameters = new Object[objs.length]; + for (int i = 0; i < objs.length; i++) { + parameters[i] = ObjectAdvice.primitiveTypeCast(objs[i], parameterTypes[i]); + } + return (T) constructor.newInstance(parameters); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + } + + /** + * 从classpath读取文件并每行用regex切分,然后按keyFunction分组 + */ + public static ListInMap splitGroupingFormClasspath(String filename, String regex, Function keyFunction) { + ListInMap map = new ListInMap<>(); + splitFromClasspath(filename, regex).forEach(objs -> map.putItem(keyFunction.apply(objs), objs)); + return map; + } + + /** + * 从classpath读取文件并每行用regex切分,然后按keyFunction分组,明确值是唯一的 + */ + public static Map splitGroupingUniqueFormClasspath(String filename, String regex, Function keyFunction) { + Map map = new HashMap<>(); + splitFromClasspath(filename, regex).forEach(objs -> { + K k = keyFunction.apply(objs); + if (map.containsKey(k)) { + throw new IllegalStateException("exists key \"" + k + "\""); + } + map.put(k, objs); + }); + return map; + } + + /** + * 从classpath读取文件并每行用regex切分,装填到实体类,然后按keyFunction分组 + */ + public static ListInMap splitGroupingFormClasspath(String filename, String regex, Class clazz, Function keyFunction) { + ListInMap map = new ListInMap<>(); + splitFromClasspath(filename, regex, clazz).forEach(t -> map.putItem(keyFunction.apply(t), t)); + return map; + } + + /** + * 从classpath读取文件并每行用regex切分,然后按keyFunction分组 + */ + public static ListInMap splitGroupingFormClasspath(String filename, String regex, Function keyFunction, Function valueFunction) { + ListInMap map = new ListInMap<>(); + splitFromClasspath(filename, regex).forEach(objs -> map.putItem(keyFunction.apply(objs), valueFunction.apply(objs))); + return map; + } + + /** + * 从classpath读取文件并每行用regex切分,装填到实体类,然后按keyFunction分组 + */ + public static ListInMap splitGroupingFormClasspath(String filename, String regex, Class clazz, Function keyFunction, Function valueFunction) { + ListInMap map = new ListInMap<>(); + splitFromClasspath(filename, regex, clazz).forEach(t -> map.putItem(keyFunction.apply(t), valueFunction.apply(t))); + return map; + } + + /** + * 从classpath读取文件并每行用regex切分,然后按keyFunction分组,明确值是唯一的 + */ + public static Map splitGroupingUniqueFormClasspath(String filename, String regex, Function keyFunction, Function valueFunction) { + Map map = new HashMap<>(); + splitFromClasspath(filename, regex).forEach(objs -> { + K k = keyFunction.apply(objs); + if (map.containsKey(k)) { + throw new IllegalStateException("exists key \"" + k + "\""); + } + map.put(k, valueFunction.apply(objs)); + }); + return map; } /** * 读取文本 - * - * @param inputStream - * @param charset - * @return */ public static String readText(InputStream inputStream, Charset charset) { StringBuilder sb = new StringBuilder(); @@ -108,9 +215,6 @@ public static String readText(InputStream inputStream, Charset charset) { /** * 读取文本 - * - * @param inputStream - * @return */ public static String readText(InputStream inputStream) { StringBuilder sb = new StringBuilder(); @@ -120,117 +224,85 @@ public static String readText(InputStream inputStream) { /** * 从classpath读取文本 - * - * @param filename - * @param charset - * @return */ public static String readTextFromClasspath(String filename, Charset charset) { StringBuilder sb = new StringBuilder(); - forEachFromClasspath(filename, charset, line -> sb.append(line.trim())); + readLinesFromClasspath(filename, charset).forEach(line -> sb.append(line.trim())); return sb.toString(); } /** * 从classpath读取文本 - * - * @param filename - * @return */ public static String readTextFromClasspath(String filename) { StringBuilder sb = new StringBuilder(); - forEachFromClasspath(filename, line -> sb.append(line.trim())); + readLinesFromClasspath(filename).forEach(line -> sb.append(line.trim())); return sb.toString(); } /** - * 文本流按行循环处理 - * - * @param inputStream - * @param charset - * @param consumer - */ - public static void forEach(InputStream inputStream, Charset charset, Consumer consumer) { - readLines(inputStream, charset).forEach(consumer); - } - - /** - * 文本流按行循环处理 - * - * @param inputStream - * @param consumer - */ - public static void forEach(InputStream inputStream, Consumer consumer) { - readLines(inputStream).forEach(consumer); - } - - /** - * classpath文本流按行循环处理 - * - * @param filename - * @param charset - * @param consumer + * 写出文本行到文件 */ - public static void forEachFromClasspath(String filename, Charset charset, Consumer consumer) { - readLinesFromClasspath(filename, charset).forEach(consumer); + public static void writeLines(List lines, String filename, Charset charset) { + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename), charset))) { + for (String line : lines) { + writer.write(line); + writer.newLine(); + } + writer.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } } /** - * classpath文本流按行循环处理 - * - * @param filename - * @param consumer + * 写出文本行到文件 */ - public static void forEachFromClasspath(String filename, Consumer consumer) { - readLinesFromClasspath(filename).forEach(consumer); + public static void writeLines(List lines, String filename) { + writeLines(lines, filename, StandardCharsets.UTF_8); } /** - * 写出文本行到文件 - * - * @param lines - * @param filename - * @param charset - * @throws IOException + * 写出文本行 */ - public static void writeLines(List lines, String filename, Charset charset) throws IOException { - try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename), charset))) { + public static void writeLines(List lines, OutputStream outputStream, Charset charset) { + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, charset))) { for (String line : lines) { writer.write(line); writer.newLine(); } writer.flush(); + } catch (IOException e) { + throw new RuntimeException(e); } } /** - * 写出文本行 - * - * @param lines - * @param outputStream - * @param charset - * @throws IOException + * 追加文本行 */ - public static void writeLines(List lines, OutputStream outputStream, Charset charset) throws IOException { - try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, charset))) { + public static void appendLines(List lines, String filename, Charset charset) { + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename, true), charset))) { for (String line : lines) { writer.write(line); writer.newLine(); } writer.flush(); + } catch (IOException e) { + throw new RuntimeException(e); } } /** - * 转移 - * - * @param inputStream - * @param outputStream - * @param charset - * @param function - * @throws IOException + * 追加文本行 */ - public static void transferText(InputStream inputStream, OutputStream outputStream, Charset charset, Function function) throws IOException { + public static void appendLines(List lines, String filename) { + appendLines(lines, filename, StandardCharsets.UTF_8); + } + + /** + * 复制文本 + */ + public static void copyText(InputStream inputStream, OutputStream outputStream, Charset charset, Function function) { Scanner scanner = new Scanner(inputStream, charset); try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, charset))) { while (scanner.hasNext()) { @@ -238,27 +310,38 @@ public static void transferText(InputStream inputStream, OutputStream outputStre writer.write(line); writer.newLine(); } + } catch (IOException e) { + throw new RuntimeException(e); } scanner.close(); } + /** + * 安静地复制文件 + */ + public static long copyQuietly(File source, File target) { + if (target.getParentFile().mkdirs()) { + try ( + InputStream inputStream = new FileInputStream(source); + OutputStream outputStream = new FileOutputStream(target) + ) { + return inputStream.transferTo(outputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return -1; + } + /** * 打印文件 - * - * @param filename - * @param charset - * @throws IOException */ - public static void printFile(String filename, Charset charset) throws IOException { + public static void printFile(String filename, Charset charset) { readLines(filename, charset).forEach(System.out::println); } /** * 打印流文件 - * - * @param inputStream - * @param charset - * @throws IOException */ public static void printInputStream(InputStream inputStream, Charset charset) { readLines(inputStream, charset).forEach(System.out::println); @@ -266,10 +349,30 @@ public static void printInputStream(InputStream inputStream, Charset charset) { /** * 打印流文件 - * - * @param inputStream */ public static void printInputStream(InputStream inputStream) { readLines(inputStream, StandardCharsets.UTF_8).forEach(System.out::println); } + + /** + * 截取输入流中某一段的字节数据 + * + * @param bufferSize 缓冲区大小 + * @param offset 偏移量 + * @param chunkSize 截取块大小 + * @param in 输入流 + * @param out 输出流 + * @throws IOException IO异常 + */ + public static long sliceBytes(int bufferSize, long offset, long chunkSize, InputStream in, OutputStream out) throws IOException { + in.skip(offset); + long transferred = 0L; + int read; + final byte[] buffer = new byte[bufferSize]; + while (transferred < chunkSize && (read = in.read(buffer, 0, (int) Math.min(buffer.length, chunkSize - transferred))) != -1) { + transferred += read; + out.write(buffer, 0, read); + } + return transferred; + } } diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/JacksonAdvice.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/JacksonAdvice.java new file mode 100644 index 0000000..e72e9fd --- /dev/null +++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/JacksonAdvice.java @@ -0,0 +1,216 @@ +package develop.toolkit.base.utils; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.github.developframework.expression.ArrayExpression; +import com.github.developframework.expression.EmptyExpression; +import com.github.developframework.expression.Expression; +import develop.toolkit.base.constants.DateFormatConstants; +import develop.toolkit.base.struct.KeyValuePair; +import lombok.SneakyThrows; + +import java.lang.reflect.Array; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +/** + * @author qiushui on 2020-09-15. + */ +public final class JacksonAdvice { + + /** + * 常用默认的ObjectMapper配置 + */ + public static ObjectMapper defaultObjectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + objectMapper.setDateFormat(new SimpleDateFormat(DateFormatConstants.STANDARD)); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, SerializationFeature.FAIL_ON_EMPTY_BEANS); + objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN); + JavaTimeModule javaTimeModule = new JavaTimeModule(); + javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateFormatConstants.STANDARD_FORMATTER)); + javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateFormatConstants.STANDARD_FORMATTER)); + objectMapper.registerModule(javaTimeModule); + return objectMapper; + } + + /** + * 常用默认的XmlMapper配置 + */ + public static XmlMapper defaultXmlMapper() { + XmlMapper xmlMapper = new XmlMapper(); + xmlMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + xmlMapper.setDateFormat(new SimpleDateFormat(DateFormatConstants.STANDARD)); + xmlMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, SerializationFeature.FAIL_ON_EMPTY_BEANS); + xmlMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + xmlMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN); + JavaTimeModule javaTimeModule = new JavaTimeModule(); + javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateFormatConstants.STANDARD_FORMATTER)); + javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateFormatConstants.STANDARD_FORMATTER)); + xmlMapper.registerModule(javaTimeModule); + return xmlMapper; + } + + /** + * 安静地序列化 + */ + @SneakyThrows(JsonProcessingException.class) + public static String serializeQuietly(ObjectMapper objectMapper, Object object, boolean pretty) { + if (pretty) { + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object); + } else { + return objectMapper.writeValueAsString(object); + } + } + + @SneakyThrows(JsonProcessingException.class) + public static JsonNode deserializeTreeQuietly(ObjectMapper objectMapper, String json) { + return objectMapper.readTree(json); + } + + @SneakyThrows(JsonProcessingException.class) + public static T treeToValueQuietly(ObjectMapper objectMapper, TreeNode node, Class clazz) { + return objectMapper.treeToValue(node, clazz); + } + + @SneakyThrows(JsonProcessingException.class) + public static T deserializeQuietly(ObjectMapper objectMapper, String json, Class clazz) { + return objectMapper.readValue(json, clazz); + } + + @SneakyThrows(JsonProcessingException.class) + public static T[] deserializeArrayQuietly(ObjectMapper objectMapper, String json, Class clazz) { + return objectMapper.readValue(json, objectMapper.getTypeFactory().constructArrayType(clazz)); + } + + @SneakyThrows(JsonProcessingException.class) + public static , E> T deserializeCollectionQuietly(ObjectMapper objectMapper, String json, Class collectionClass, Class itemClass) { + return objectMapper.readValue(json, objectMapper.getTypeFactory().constructCollectionType(collectionClass, itemClass)); + } + + /** + * 用表达式从json中取值 + */ + @SneakyThrows(JsonProcessingException.class) + public static T deserializeValue(ObjectMapper objectMapper, JsonNode rootNode, String expressionValue, Class clazz) { + return objectMapper.treeToValue( + parseExpressionToJsonNode(rootNode, Expression.parse(expressionValue)), + clazz + ); + } + + /** + * 用表达式从json中取数组 + */ + @SneakyThrows(JsonProcessingException.class) + public static T[] deserializeArray(ObjectMapper objectMapper, JsonNode rootNode, String expressionValue, Class clazz) { + final JsonNode jsonNode = parseExpressionToJsonNode(rootNode, Expression.parse(expressionValue)); + if (!jsonNode.isArray()) { + throw new IllegalArgumentException("\"" + expressionValue + "\" value is not a array."); + } + return objectMapper.readValue( + jsonNode.toString(), + objectMapper.getTypeFactory().constructArrayType(clazz) + ); + } + + /** + * 用表达式从json中取列表 + */ + @SneakyThrows(JsonProcessingException.class) + public static List deserializeList(ObjectMapper objectMapper, JsonNode rootNode, String expressionValue, Class clazz) { + final JsonNode jsonNode = parseExpressionToJsonNode(rootNode, Expression.parse(expressionValue)); + if (!jsonNode.isArray()) { + throw new IllegalArgumentException("\"" + expressionValue + "\" value is not a list."); + } + return objectMapper.readValue( + jsonNode.toString(), + objectMapper.getTypeFactory().constructCollectionType(List.class, clazz) + ); + } + + @SafeVarargs + @SneakyThrows(JsonProcessingException.class) + public static Object[] deserializeValues(ObjectMapper objectMapper, JsonNode rootNode, KeyValuePair>... expressionValues) { + final Object[] values = new Object[expressionValues.length]; + for (int i = 0; i < expressionValues.length; i++) { + final KeyValuePair> kv = expressionValues[i]; + final Expression expression = Expression.parse(kv.getKey()); + JsonNode jsonNode = parseExpressionToJsonNode(rootNode, expression); + values[i] = objectMapper.treeToValue(jsonNode, kv.getValue()); + } + return values; + } + + /** + * ArrayNode转到List + */ + public static List arrayNodeToList(ArrayNode arrayNode, Function function) { + List list = new ArrayList<>(arrayNode.size()); + for (JsonNode node : arrayNode) { + list.add(function.apply(node)); + } + return Collections.unmodifiableList(list); + } + + /** + * ArrayNode转到数组 + */ + @SuppressWarnings("unchecked") + public static T[] arrayNodeToArray(ArrayNode arrayNode, Class clazz, Function function) { + final T[] array = (T[]) Array.newInstance(clazz, arrayNode.size()); + int i = 0; + for (JsonNode node : arrayNode) { + array[i++] = function.apply(node); + } + return array; + } + + private static JsonNode parseExpressionToJsonNode(JsonNode jsonNode, Expression expression) { + if (expression != EmptyExpression.INSTANCE) { + for (Expression singleExpression : expression.expressionTree()) { + if (singleExpression.isObject()) { + jsonNode = existsJsonNode(jsonNode, singleExpression.getName()); + } else if (singleExpression.isArray()) { + ArrayExpression ae = (ArrayExpression) singleExpression; + jsonNode = existsJsonNode(jsonNode, ae.getName()); + for (int i : ae.getIndexArray()) { + if (jsonNode.isArray()) { + jsonNode = jsonNode.get(i); + } else { + throw new IllegalArgumentException("jsonNode is not Array,for expression:" + singleExpression); + } + } + } else if (singleExpression.isMethod()) { + throw new IllegalArgumentException("not support method expression."); + } + } + } + return jsonNode; + } + + private static JsonNode existsJsonNode(JsonNode parentNode, String propertyName) { + if (propertyName.isEmpty() && parentNode.isArray()) { + return parentNode; + } else { + final JsonNode node = parentNode.get(propertyName); + if (node == null) { + throw new IllegalArgumentException("Not found node \"" + propertyName + "\""); + } + return node; + } + } +} diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/JavaBeanUtils.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/JavaBeanUtils.java index 2b224f1..9f5f434 100644 --- a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/JavaBeanUtils.java +++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/JavaBeanUtils.java @@ -4,14 +4,11 @@ * @author qiushui on 2018-10-03. * @since 0.1 */ +@SuppressWarnings("unused") public final class JavaBeanUtils { /** * 根据属性名称和java类型,获取对应的getter方法名 - * - * @param property - * @param javaType - * @return */ public static String getGetterMethodName(String property, Class javaType) { StringBuilder sb = new StringBuilder(); @@ -31,9 +28,6 @@ public static String getGetterMethodName(String property, Class javaType) { /** * 根据属性名称获取对应的setter方法名称 - * - * @param property - * @return */ public static String getSetterMethodName(String property) { StringBuilder sb = new StringBuilder(); @@ -49,9 +43,6 @@ public static String getSetterMethodName(String property) { /** * 驼峰转下划线 - * - * @param camelcaseString - * @return */ public static String camelcaseToUnderline(String camelcaseString) { StringBuilder sb = new StringBuilder(); @@ -68,9 +59,6 @@ public static String camelcaseToUnderline(String camelcaseString) { /** * 驼峰转中划线 - * - * @param camelcaseString - * @return */ public static String camelcaseToMiddleLine(String camelcaseString) { StringBuilder sb = new StringBuilder(); @@ -90,9 +78,6 @@ public static String camelcaseToMiddleLine(String camelcaseString) { /** * 下划线转驼峰 - * - * @param underlineString - * @return */ public static String underlineToCamelcase(String underlineString) { StringBuilder sb = new StringBuilder(); @@ -115,9 +100,6 @@ public static String underlineToCamelcase(String underlineString) { /** * 中划线转驼峰 - * - * @param middleLineString - * @return */ public static String middleLineToCamelcase(String middleLineString) { StringBuilder sb = new StringBuilder(); @@ -140,9 +122,6 @@ public static String middleLineToCamelcase(String middleLineString) { /** * 开头字母转大写 - * - * @param text - * @return */ public static String startUpperCaseText(String text) { StringBuilder sb = new StringBuilder(); @@ -159,9 +138,6 @@ public static String startUpperCaseText(String text) { /** * 开头字母转小写 - * - * @param text - * @return */ public static String startLowerCaseText(String text) { StringBuilder sb = new StringBuilder(); diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/K.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/K.java new file mode 100644 index 0000000..8d267f8 --- /dev/null +++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/K.java @@ -0,0 +1,147 @@ +package develop.toolkit.base.utils; + +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * 判空处理简化操作 + */ +@SuppressWarnings("unused") +public final class K { + + /** + * 如果为null返回默认值 + * + * @param value 值 + * @param defaultSupplier 默认值提供器 + * @param 泛型 + * @return 值 + */ + public static T def(T value, Supplier defaultSupplier) { + return value != null ? value : defaultSupplier.get(); + } + + /** + * 如果为null返回默认值 + * + * @param value 值 + * @param defaultValue 默认值 + * @param 泛型 + * @return 值 + */ + public static T def(T value, T defaultValue) { + return value != null ? value : defaultValue; + } + + /** + * 如果不为null则执行consumer + * + * @param value 值 + * @param 泛型 + */ + public static void let(T value, Consumer consumer) { + if (value != null) { + consumer.accept(value); + } + } + + /** + * 如果map的取值不为null则消费 + * + * @param map map + * @param key 键 + * @param consumer 消费者 + * @param 键类型 + * @param 值类型 + */ + public static void let(Map map, KEY key, Consumer consumer) { + if (map != null) { + let(map.get(key), consumer); + } + } + + /** + * 如果列表的值不为null则消费 + * + * @param list 列表 + * @param i 索引 + * @param consumer 消费者 + * @param 列表元素类型 + */ + public static void let(List list, int i, Consumer consumer) { + if (list != null) { + let(list.get(i), consumer); + } + } + + /** + * 如果数组的值不为null则消费 + * + * @param array 数组 + * @param i 索引 + * @param consumer 消费者 + * @param 数组元素类型 + */ + public static void let(T[] array, int i, Consumer consumer) { + if (array != null) { + let(array[i], consumer); + } + } + + /** + * 如果不为null则返回转化值 + * + * @param value 值 + * @param function 转化函数 + * @return 转化值 + */ + public static R map(T value, Function function) { + return value == null ? null : function.apply(value); + } + + /** + * 如果map的取值不为null则转化 + * + * @param map map + * @param key 键 + * @param function 函数 + * @param 键类型 + * @param 值类型 + * @param 转化类型 + * @return 转化值 + */ + public static T map(Map map, KEY key, Function function) { + return map == null ? null : map(map.get(key), function); + } + + /** + * 如果列表的值不为null则消费 + * + * @param list 列表 + * @param i 索引 + * @param function 函数 + * @param 列表值类型 + * @param 转化类型 + * @return 转化值 + */ + public static T map(List list, int i, Function function) { + return list == null ? null : map(list.get(i), function); + } + + /** + * 如果数组的值不为null则消费 + * + * @param array 数组 + * @param i 索引 + * @param function 函数 + * @param 列表值类型 + * @param 转化类型 + * @return 转化值 + */ + public static T map(V[] array, int i, Function function) { + return array == null ? null : map(array[i], function); + } +} diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/MathAdvice.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/MathAdvice.java new file mode 100644 index 0000000..46ed11d --- /dev/null +++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/MathAdvice.java @@ -0,0 +1,79 @@ +package develop.toolkit.base.utils; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 数学增强 + * + * @author qiushui on 2020-01-02. + */ +@SuppressWarnings("unused") +public final class MathAdvice { + + /** + * 最大值 + */ + public static double max(Collection numbers) { + return numbers + .stream() + .mapToDouble(Number::doubleValue) + .max().orElseThrow(); + } + + /** + * 最小值 + */ + public static double min(Collection numbers) { + return numbers + .stream() + .mapToDouble(Number::doubleValue) + .min().orElseThrow(); + } + + /** + * 平均值 + */ + public static double average(Collection numbers) { + return numbers + .stream() + .mapToDouble(Number::doubleValue) + .average().orElseThrow(); + } + + /** + * 方差 + */ + public static double variance(Collection numbers) { + final double average = average(numbers); + return numbers + .stream() + .mapToDouble(number -> Math.pow(number.doubleValue() - average, 2)) + .average().orElseThrow(); + } + + /** + * 标准差 + */ + public static double standardDeviation(Collection numbers) { + return Math.sqrt(variance(numbers)); + } + + /** + * 中位数 + */ + public static double median(Collection numbers) { + final List list = numbers + .stream() + .sorted() + .map(Number::doubleValue) + .collect(Collectors.toList()); + if (list.size() % 2 == 0) { + int half = list.size() / 2; + return (list.get(half) + list.get(half + 1)) / 2; + } else { + return list.get(list.size() / 2 + 1); + } + } +} diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/ObjectAdvice.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/ObjectAdvice.java index 9decb1b..de614eb 100644 --- a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/ObjectAdvice.java +++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/ObjectAdvice.java @@ -1,113 +1,191 @@ package develop.toolkit.base.utils; import lombok.NonNull; +import lombok.SneakyThrows; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.commons.lang3.reflect.MethodUtils; -import java.util.function.Supplier; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; /** * 实例对象处理增强工具 * * @author qiushui on 2019-02-20. */ +@SuppressWarnings("unused") public final class ObjectAdvice { /** - * 赋值 - * - * @param obj 值 - * @param defaultSupplier 默认值提供器 - * @param - * @return + * 值是否在数组里 */ - public static T assign(T obj, @NonNull Supplier defaultSupplier) { - return obj != null ? obj : defaultSupplier.get(); + @SafeVarargs + public static boolean valueIn(@NonNull T obj, T... targets) { + for (T target : targets) { + if (obj.equals(target)) { + return true; + } + } + return false; } /** - * 是否是字节 - * - * @param obj - * @return + * 值是否不在数组里 */ - public static boolean isByte(Object obj) { - var clazz = obj.getClass(); - return clazz == byte.class || clazz == Byte.class; + @SafeVarargs + public static boolean valueNotIn(@NonNull T obj, T... targets) { + for (T target : targets) { + if (obj.equals(target)) { + return false; + } + } + return true; } /** - * 是否是短整型 + * 反射设置值 * - * @param obj - * @return + * @param instance 实例 + * @param field 字段 + * @param value 值 + * @param firstUseSetterMethod 优先使用setter方法 */ - public static boolean isShort(Object obj) { - var clazz = obj.getClass(); - return clazz == short.class || clazz == Short.class; + @SneakyThrows + public static void set(Object instance, Field field, Object value, boolean firstUseSetterMethod) { + if (firstUseSetterMethod) { + try { + final String setterMethodName = JavaBeanUtils.getSetterMethodName(field.getName()); + MethodUtils.invokeMethod(instance, true, setterMethodName); + } catch (NoSuchMethodException e) { + FieldUtils.writeField(field, instance, value, true); + } + } else { + FieldUtils.writeField(field, instance, value, true); + } } /** - * 是否是整型 + * 反射设置值 * - * @param obj - * @return + * @param instance 实例 + * @param fieldName 字段 + * @param value 值 + * @param firstUseSetterMethod 优先使用setter方法 */ - public static boolean isInt(Object obj) { - var clazz = obj.getClass(); - return clazz == int.class || clazz == Integer.class; + @SneakyThrows + public static void set(Object instance, String fieldName, Object value, boolean firstUseSetterMethod) { + if (firstUseSetterMethod) { + try { + final String setterMethodName = JavaBeanUtils.getSetterMethodName(fieldName); + MethodUtils.invokeMethod(instance, true, setterMethodName); + } catch (NoSuchMethodException e) { + FieldUtils.writeField(instance, fieldName, value, true); + } + } else { + FieldUtils.writeField(instance, fieldName, value, true); + } } /** - * 是否是长整型 + * 反射获取值 * - * @param obj - * @return + * @param instance 实例 + * @param field 字段 + * @param firstUseGetterMethod 优先使用getter方法 + * @return 反射值 */ - public static boolean isLong(Object obj) { - var clazz = obj.getClass(); - return clazz == long.class || clazz == Long.class; + @SneakyThrows + public static Object get(Object instance, Field field, boolean firstUseGetterMethod) { + if (firstUseGetterMethod) { + try { + final String getterMethodName = JavaBeanUtils.getGetterMethodName(field.getName(), field.getType()); + return MethodUtils.invokeMethod(instance, true, getterMethodName); + } catch (NoSuchMethodException e) { + return FieldUtils.readField(instance, field.getName(), true); + } + } else { + return FieldUtils.readField(instance, field.getName(), true); + } } /** - * 是否是单精度浮点型 + * 反射获取值 * - * @param obj - * @return + * @param instance 实例 + * @param fieldName 字段 + * @param firstUseGetterMethod 优先使用getter方法 + * @return 反射值 */ - public static boolean isFloat(Object obj) { - var clazz = obj.getClass(); - return clazz == float.class || clazz == Float.class; + @SneakyThrows + public static Object get(Object instance, String fieldName, boolean firstUseGetterMethod) { + if (firstUseGetterMethod) { + try { + Field field = instance.getClass().getField(fieldName); + final String getterMethodName = JavaBeanUtils.getGetterMethodName(fieldName, field.getType()); + return MethodUtils.invokeMethod(instance, true, getterMethodName); + } catch (NoSuchMethodException e) { + return FieldUtils.readField(instance, fieldName, true); + } + } else { + return FieldUtils.readField(instance, fieldName, true); + } } /** - * 是否是双精度浮点型 + * 读取全部字段值 * - * @param obj - * @return + * @param instance 实例 + * @return 所有字段值 */ - public static boolean isDouble(Object obj) { - var clazz = obj.getClass(); - return clazz == double.class || clazz == Double.class; + public static Map readAllFieldValue(Object instance) { + Map map = new HashMap<>(); + Field[] fields = FieldUtils.getAllFields(instance.getClass()); + for (Field field : fields) { + map.put(field, get(instance, field, true)); + } + return map; } /** - * 是否是字符型 - * - * @param obj - * @return + * 安静地使用无参构造方法new对象 */ - public static boolean isChar(Object obj) { - var clazz = obj.getClass(); - return clazz == char.class || clazz == Character.class; + @SneakyThrows + public static T newInstanceQuietly(Class clazz) { + return clazz.getConstructor().newInstance(); + } + + @SneakyThrows + public static T newInstanceQuietly(Class clazz, Class[] parameterClasses, Object... values) { + return clazz.getConstructor(parameterClasses).newInstance(values); } /** - * 是否是布尔型 - * - * @param obj - * @return + * 字符串值转化成基本类型值 */ - public static boolean isBoolean(Object obj) { - var clazz = obj.getClass(); - return clazz == boolean.class || clazz == Boolean.class; + public static Object primitiveTypeCast(String value, Class clazz) { + if (value == null) { + return null; + } else if (clazz == String.class) { + return value; + } else if (clazz == int.class || clazz == Integer.class) { + return Integer.parseInt(value); + } else if (clazz == long.class || clazz == Long.class) { + return Long.parseLong(value); + } else if (clazz == boolean.class || clazz == Boolean.class) { + return Boolean.parseBoolean(value); + } else if (clazz == float.class || clazz == Float.class) { + return Float.parseFloat(value); + } else if (clazz == double.class || clazz == Double.class) { + return Double.parseDouble(value); + } else if (clazz == short.class || clazz == Short.class) { + return Short.parseShort(value); + } else if (clazz == char.class || clazz == Character.class) { + return value.charAt(0); + } else if (clazz == byte.class || clazz == Byte.class) { + return Byte.parseByte(value); + } else { + throw new ClassCastException(); + } } } diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/RandomAdvice.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/RandomAdvice.java new file mode 100644 index 0000000..a391746 --- /dev/null +++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/RandomAdvice.java @@ -0,0 +1,44 @@ +package develop.toolkit.base.utils; + +import develop.toolkit.base.struct.range.IntRange; +import org.apache.commons.lang3.RandomUtils; +import org.apache.commons.lang3.Validate; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +@SuppressWarnings("unused") +public final class RandomAdvice { + + /** + * 随机count个整数 + */ + public static int[] nextInts(final int startInclusive, final int endExclusive, final int count) { + Validate.isTrue(endExclusive - startInclusive >= count, "Difference value must be greater or equal to end value."); + List list = new ArrayList<>(List.of(new IntRange(startInclusive, endExclusive).generate())); + int[] result = new int[count]; + for (int i = 0; i < result.length; i++) { + result[i] = list.remove(RandomUtils.nextInt(0, list.size())); + } + return result; + } + + /** + * 随机count个元素 + */ + public static List nextElements(final List list, final int count) { + return IntStream + .of(nextInts(0, list.size(), count)) + .mapToObj(list::get) + .collect(Collectors.toList()); + } + + /** + * 随机一个元素 + */ + public static T nextElement(final List list) { + return list.get(list.size() == 1 ? 0 : RandomUtils.nextInt(0, list.size())); + } +} diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/SortAdvice.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/SortAdvice.java new file mode 100644 index 0000000..7e4388d --- /dev/null +++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/SortAdvice.java @@ -0,0 +1,78 @@ +package develop.toolkit.base.utils; + +/** + * 排序算法 + * + * @author qiushui on 2021-02-07. + */ +public abstract class SortAdvice { + + /** + * 快速排序 + * + * @param array 数组 + * @param 数组类型 + */ + public static > void quickSort(T[] array) { + if (array == null || array.length < 2) return; + quickSortRecursive(array, 0, array.length - 1); + } + + /** + * 快速排序递归 + *

+ * 以第一个元素作为基准值 + * 从左往右找出第一个大于基准值的元素 和 从右往左找出第一个小于基准值的元素 交换 + * 划分出的两个区间继续用该方法交换,直到每个区间只有单个元素 + * + * @param array 数组 + * @param start 开始区间 + * @param end 结束区间 + * @param 数组类型 + */ + private static > void quickSortRecursive(T[] array, int start, int end) { + if (start >= end) return; + int l = start, r = end; + // 基准值 + final T standard = array[start]; + while (l != r) { + // 从左边找大于基准值 + while (l < end && array[l].compareTo(standard) < 0) l++; + // 从右边找小于基准值 + while (start < r && array[r].compareTo(standard) > 0) r--; + // 交换值 + T t = array[l]; + array[l] = array[r]; + array[r] = t; + } + // 分成两份继续递归 + quickSortRecursive(array, 0, l - 1); + quickSortRecursive(array, l + 1, end); + } + + /** + * 选择排序 + *

+ * 从左往右依次遍历 + * 每次选出剩余数组里最小的值,与当前值替换 + * + * @param array 数组 + * @param 数组类型 + */ + public static > void selectSort(T[] array) { + if (array == null || array.length < 2) return; + for (int i = 0; i < array.length; i++) { + int minIndex = i; + for (int j = i + 1; j < array.length; j++) { + if (array[j].compareTo(array[minIndex]) < 0) { + minIndex = j; + } + } + if (i != minIndex) { + T t = array[i]; + array[i] = array[minIndex]; + array[minIndex] = t; + } + } + } +} diff --git a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/StringAdvice.java b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/StringAdvice.java index 90f3015..08a4f5a 100644 --- a/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/StringAdvice.java +++ b/develop-toolkit-base/src/main/java/develop/toolkit/base/utils/StringAdvice.java @@ -2,27 +2,149 @@ import develop.toolkit.base.struct.TwoValues; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + /** * 字符串增强工具 * * @author qiushui on 2018-09-06. - * @since 0.1 */ +@SuppressWarnings("unused") public final class StringAdvice { - /** - * 从index位置切断字符串 - * @param string - * @param index - * @return - */ + /** + * 判断是null和空 + */ + public static boolean isEmpty(String content) { + return content == null || content.isEmpty(); + } + + /** + * 判断不是null和空 + */ + public static boolean isNotEmpty(String content) { + return content != null && !content.isEmpty(); + } + + /** + * null的话默认为空字符串 + */ + public static String defaultEmpty(String content) { + return content != null ? content : ""; + } + + /** + * 空字符串的话默认为默认值 + */ + public static String emptyOr(String content, String defaultValue) { + return isEmpty(content) ? defaultValue : content; + } + + /** + * 头尾添加字符串 + */ + public static String headTail(String content, String sign) { + return sign + content + sign; + } + + /** + * 从index位置切断字符串 + */ public static TwoValues cutOff(String string, int index) { if (index > string.length() || index < 0) { - throw new IllegalArgumentException(); - } + return null; + } return TwoValues.of( string.substring(0, index), string.substring(index) ); - } + } + + /** + * 切掉尾部字符串 + */ + public static String cutTail(String string, String tail) { + return string.endsWith(tail) ? string.substring(0, string.length() - tail.length()) : string; + } + + /** + * 切掉头部字符串 + */ + public static String cutHead(String string, String head) { + return string.startsWith(head) ? string.substring(head.length()) : string; + } + + /** + * 正则取值 + */ + public static List regexMatch(String string, String regex) { + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(string); + List list = new ArrayList<>(matcher.groupCount()); + while (matcher.find()) { + list.add(matcher.group()); + } + return list; + } + + /** + * 正则开头结尾匹配抓取中间字符串值 + */ + public static List regexMatchStartEnd(String string, String start, String end) { + return regexMatch(string, String.format("(?<=%s)(.+?)(?=%s)", start, end)); + } + + /** + * 间隔美化 + */ + public static String intervalFormat(String separator, Object... objs) { + return Stream.of(objs).map(o -> o == null ? "null" : o.toString()).collect(Collectors.joining(separator)); + } + + /** + * 处理成url参数格式 + */ + public static String urlParametersFormat(Map parameters, boolean needQuestionMark) { + if (parameters == null || parameters.isEmpty()) { + return ""; + } + return (needQuestionMark ? "?" : "") + parameters + .entrySet() + .stream() + .filter(kv -> kv.getValue() != null) + .map(kv -> String.format("%s=%s", kv.getKey(), URLEncoder.encode(kv.getValue().toString(), StandardCharsets.UTF_8))) + .collect(Collectors.joining("&")); + } + + /** + * 去除字符串左右指定的字符 + */ + public static String trim(String str, char ch) { + if (str == null) { + return null; + } + int leftSkip = 0, rightSkip = 0, len = str.length(); + boolean left = true, right = true; + for (int i = 0; i < len && (left || right); i++) { + if (left && (left = str.charAt(i) == ch)) leftSkip++; + if (right && (right = str.charAt(len - 1 - i) == ch)) rightSkip++; + } + return str.substring(leftSkip, len - rightSkip); + } + + /** + * 修改编码 + */ + public static String changeCharset(String str, String charset) { + return new String(str.getBytes(StandardCharsets.ISO_8859_1), Charset.forName("GBK")); + } } diff --git a/develop-toolkit-base/src/main/java/module-info.java b/develop-toolkit-base/src/main/java/module-info.java index 9f3468f..71ae732 100644 --- a/develop-toolkit-base/src/main/java/module-info.java +++ b/develop-toolkit-base/src/main/java/module-info.java @@ -2,13 +2,20 @@ * @author qiushui on 2019-02-26. */ module develop.toolkit.base { - requires lombok; requires org.slf4j; + requires org.apache.commons.lang3; + requires java.net.http; + requires com.fasterxml.jackson.databind; + requires com.fasterxml.jackson.datatype.jsr310; + requires com.fasterxml.jackson.dataformat.xml; + requires expression; exports develop.toolkit.base.components; exports develop.toolkit.base.constants; exports develop.toolkit.base.exception; exports develop.toolkit.base.struct; + exports develop.toolkit.base.struct.http; + exports develop.toolkit.base.struct.range; exports develop.toolkit.base.utils; } \ No newline at end of file diff --git a/develop-toolkit-support/pom.xml b/develop-toolkit-db/pom.xml similarity index 56% rename from develop-toolkit-support/pom.xml rename to develop-toolkit-db/pom.xml index 9a88037..eb52012 100644 --- a/develop-toolkit-support/pom.xml +++ b/develop-toolkit-db/pom.xml @@ -1,49 +1,36 @@ - - - - com.github.developframework - develop-toolkit - 1.0.1-SNAPSHOT - - 4.0.0 - - develop-toolkit-support - 开发工具箱 - 扩展支持 - - - 3.10.1 - 8.0.15 - 2.9.9 - - - - - com.github.developframework - develop-toolkit-base - - - org.mongodb - mongo-java-driver - ${version.mongo-java-driver} - true - - - mysql - mysql-connector-java - ${version.mysql-connector-java} - true - - - com.fasterxml.jackson.core - jackson-databind - ${version.jackson} - true - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - ${version.jackson} - true - - + + + + com.github.developframework + develop-toolkit + 1.0.7-SNAPSHOT + + 4.0.0 + + develop-toolkit-db + 开发工具箱 - 数据库服务 + + + 3.12.4 + 8.0.20 + + + + + com.github.developframework + develop-toolkit-base + + + org.mongodb + mongo-java-driver + ${version.mongo-java-driver} + true + + + mysql + mysql-connector-java + ${version.mysql-connector-java} + true + + \ No newline at end of file diff --git a/develop-toolkit-support/src/main/java/develop/toolkit/support/db/DBAdvice.java b/develop-toolkit-db/src/main/java/develop/toolkit/db/DBAdvice.java similarity index 85% rename from develop-toolkit-support/src/main/java/develop/toolkit/support/db/DBAdvice.java rename to develop-toolkit-db/src/main/java/develop/toolkit/db/DBAdvice.java index b535693..f5d805d 100644 --- a/develop-toolkit-support/src/main/java/develop/toolkit/support/db/DBAdvice.java +++ b/develop-toolkit-db/src/main/java/develop/toolkit/db/DBAdvice.java @@ -1,29 +1,30 @@ -package develop.toolkit.support.db; - -import develop.toolkit.support.db.mysql.MysqlClient; -import develop.toolkit.support.db.mysql.MysqlProperties; - -import java.sql.SQLException; -import java.util.Map; - -/** - * @author qiushui on 2019-09-03. - */ -public final class DBAdvice { - - public static MysqlClient mysql(String domain, String username, String password, String database, Map parameters) throws SQLException { - return mysql(domain, 3306, username, password, database, parameters); - } - - public static MysqlClient mysql(String domain, String username, String password, String database) throws SQLException { - return mysql(domain, 3306, username, password, database, null); - } - - public static MysqlClient mysql(String domain, int port, String username, String password, String database, Map parameters) throws SQLException { - MysqlProperties mysqlProperties = new MysqlProperties(domain, port, username, password, database); - if (parameters != null) { - mysqlProperties.getParameters().putAll(parameters); - } - return new MysqlClient(mysqlProperties); - } -} +package develop.toolkit.db; + +import develop.toolkit.db.mysql.MysqlClient; +import develop.toolkit.db.mysql.MysqlProperties; + +import java.sql.SQLException; +import java.util.Map; + +/** + * @author qiushui on 2019-09-03. + */ +@SuppressWarnings("unused") +public final class DBAdvice { + + public static MysqlClient mysql(String domain, String username, String password, String database, Map parameters) throws SQLException { + return mysql(domain, 3306, username, password, database, parameters); + } + + public static MysqlClient mysql(String domain, String username, String password, String database) throws SQLException { + return mysql(domain, 3306, username, password, database, null); + } + + public static MysqlClient mysql(String domain, int port, String username, String password, String database, Map parameters) throws SQLException { + MysqlProperties mysqlProperties = new MysqlProperties(domain, port, username, password, database); + if (parameters != null) { + mysqlProperties.getParameters().putAll(parameters); + } + return new MysqlClient(mysqlProperties); + } +} diff --git a/develop-toolkit-support/src/main/java/develop/toolkit/support/db/mysql/MysqlClient.java b/develop-toolkit-db/src/main/java/develop/toolkit/db/mysql/MysqlClient.java similarity index 54% rename from develop-toolkit-support/src/main/java/develop/toolkit/support/db/mysql/MysqlClient.java rename to develop-toolkit-db/src/main/java/develop/toolkit/db/mysql/MysqlClient.java index 966a7bd..6038c06 100644 --- a/develop-toolkit-support/src/main/java/develop/toolkit/support/db/mysql/MysqlClient.java +++ b/develop-toolkit-db/src/main/java/develop/toolkit/db/mysql/MysqlClient.java @@ -1,186 +1,146 @@ -package develop.toolkit.support.db.mysql; - -import com.github.developframework.expression.ExpressionUtils; -import develop.toolkit.base.utils.JavaBeanUtils; -import org.apache.commons.lang3.StringUtils; - -import java.sql.*; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * @author qiushui on 2019-09-03. - */ -public class MysqlClient implements AutoCloseable { - - private Connection connection; - - public MysqlClient(MysqlProperties mysqlProperties) throws SQLException { - try { - Class.forName("com.mysql.cj.jdbc.Driver"); - connection = DriverManager.getConnection( - mysqlProperties.getUrl(), - mysqlProperties.getUsername(), - mysqlProperties.getPassword() - ); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - - /** - * 查询列表 - * - * @param sql - * @param rowMapper - * @param - * @return - * @throws SQLException - */ - public List query(String sql, RowMapper rowMapper) throws SQLException { - Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery(sql); - List list = new LinkedList<>(); - while (resultSet.next()) { - list.add(rowMapper.mapping(resultSet)); - } - resultSet.close(); - statement.close(); - return list; - } - - /** - * 查询列表 - * - * @param sql - * @param setter - * @param rowMapper - * @param - * @return - * @throws SQLException - */ - public List query(String sql, PreparedStatementSetter setter, RowMapper rowMapper) throws SQLException { - PreparedStatement statement = connection.prepareStatement(sql); - setter.set(statement); - ResultSet resultSet = statement.executeQuery(); - List list = new LinkedList<>(); - while (resultSet.next()) { - list.add(rowMapper.mapping(resultSet)); - } - resultSet.close(); - statement.close(); - return list; - } - - /** - * 查询单记录 - * - * @param sql - * @param rowMapper - * @param - * @return - * @throws SQLException - */ - public Optional queryOne(String sql, RowMapper rowMapper) throws SQLException { - List list = query(sql, rowMapper); - if (list.size() > 1) { - throw new NoUniqueResultException(list.size()); - } - return Optional.ofNullable( - list.isEmpty() ? null : list.get(0) - ); - } - - /** - * 查询单记录 - * - * @param sql - * @param setter - * @param rowMapper - * @param - * @return - * @throws SQLException - */ - public Optional queryOne(String sql, PreparedStatementSetter setter, RowMapper rowMapper) throws SQLException { - List list = query(sql, setter, rowMapper); - if (list.size() > 1) { - throw new NoUniqueResultException(list.size()); - } - return Optional.ofNullable( - list.isEmpty() ? null : list.get(0) - ); - } - - public int insertAll(String table, List list, String... fields) throws SQLException { - String sql = new StringBuilder() - .append("INSERT INTO ").append(table).append("(") - .append(StringUtils.join(fields, ",")).append(") VALUES") - .append( - list - .stream() - .map(data -> - "(" + Stream.of(fields) - .map(field -> { - Object object = ExpressionUtils.getValue(data, JavaBeanUtils.underlineToCamelcase(field)); - String value = object != null ? object.toString() : null; - return StringUtils.isNumeric(value) ? value : ("'" + value + "'"); - }) - .collect(Collectors.joining(",")) + ")" - ) - .collect(Collectors.joining(",")) - ) - .toString(); - Statement statement = connection.createStatement(); - int count = statement.executeUpdate(sql); - statement.close(); - return count; - } - - /** - * 插入记录 - * - * @param table - * @param data - * @param fields - * @param - * @return - * @throws SQLException - */ - public int insert(String table, T data, String... fields) throws SQLException { - return insertAll(table, List.of(data), fields); - } - - /** - * 执行修改语句 - * - * @param sql - * @param setter - * @return - * @throws SQLException - */ - public int executeUpdate(String sql, PreparedStatementSetter setter) throws SQLException { - PreparedStatement preparedStatement = connection.prepareStatement(sql); - setter.set(preparedStatement); - return preparedStatement.executeUpdate(); - } - - /** - * 执行修改语句 - * - * @param sql - * @return - * @throws SQLException - */ - public int executeUpdate(String sql) throws SQLException { - PreparedStatement preparedStatement = connection.prepareStatement(sql); - return preparedStatement.executeUpdate(); - } - - @Override - public void close() throws SQLException { - connection.close(); - } -} +package develop.toolkit.db.mysql; + +import com.github.developframework.expression.ExpressionUtils; +import develop.toolkit.base.utils.JavaBeanUtils; +import develop.toolkit.base.utils.K; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; + +import java.sql.*; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Mysql客户端 + * + * @author qiushui on 2019-09-03. + */ +@SuppressWarnings("unused") +public class MysqlClient implements AutoCloseable { + + @Getter + private final Connection connection; + + public MysqlClient(MysqlProperties mysqlProperties) throws SQLException { + try { + Class.forName("com.mysql.cj.jdbc.Driver"); + connection = DriverManager.getConnection( + mysqlProperties.getUrl(), + mysqlProperties.getUsername(), + mysqlProperties.getPassword() + ); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + /** + * 查询列表 + */ + public List query(String sql, RowMapper rowMapper) throws SQLException { + return query(sql, null, rowMapper); + } + + /** + * 查询列表 + */ + public List query(String sql, PreparedStatementSetter setter, RowMapper rowMapper) throws SQLException { + final PreparedStatement statement = connection.prepareStatement(sql); + if (setter != null) { + setter.set(statement); + } + final ResultSet resultSet = statement.executeQuery(); + try (statement; resultSet) { + List list = new LinkedList<>(); + while (resultSet.next()) { + list.add(rowMapper.mapping(resultSet)); + } + return Collections.unmodifiableList(list); + } + } + + /** + * 查询单记录 + */ + public Optional queryOne(String sql, RowMapper rowMapper) throws SQLException { + List list = query(sql, rowMapper); + if (list.size() > 1) { + throw new NoUniqueResultException(list.size()); + } + return Optional.ofNullable( + list.isEmpty() ? null : list.get(0) + ); + } + + /** + * 查询单记录 + */ + public Optional queryOne(String sql, PreparedStatementSetter setter, RowMapper rowMapper) throws SQLException { + List list = query(sql, setter, rowMapper); + if (list.size() > 1) { + throw new NoUniqueResultException(list.size()); + } + return Optional.ofNullable( + list.isEmpty() ? null : list.get(0) + ); + } + + public int insertAll(String table, Collection collection, String... fields) throws SQLException { + String sql = new StringBuilder() + .append("INSERT INTO ").append(table).append("(") + .append(Stream.of(fields).map(f -> String.format("`%s`", f)).collect(Collectors.joining(","))) + .append(") VALUES") + .append( + collection + .stream() + .map(data -> + Stream.of(fields) + .map(field -> { + String value = K.map( + ExpressionUtils.getValue(data, JavaBeanUtils.underlineToCamelcase(field)), + Object::toString + ); + return StringUtils.isNumeric(value) ? value : ("'" + value + "'"); + }) + .collect(Collectors.joining(",", "(", ")")) + ) + .collect(Collectors.joining(",")) + ) + .toString(); + try (Statement statement = connection.createStatement()) { + return statement.executeUpdate(sql); + } + } + + /** + * 插入记录 + */ + public int insert(String table, T data, String... fields) throws SQLException { + return insertAll(table, List.of(data), fields); + } + + /** + * 执行修改语句 + */ + public int executeUpdate(String sql, PreparedStatementSetter setter) throws SQLException { + try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) { + if (setter != null) { + setter.set(preparedStatement); + } + return preparedStatement.executeUpdate(); + } + } + + /** + * 执行修改语句 + */ + public int executeUpdate(String sql) throws SQLException { + return executeUpdate(sql, null); + } + + @Override + public void close() throws SQLException { + connection.close(); + } +} diff --git a/develop-toolkit-support/src/main/java/develop/toolkit/support/db/mysql/MysqlProperties.java b/develop-toolkit-db/src/main/java/develop/toolkit/db/mysql/MysqlProperties.java similarity index 70% rename from develop-toolkit-support/src/main/java/develop/toolkit/support/db/mysql/MysqlProperties.java rename to develop-toolkit-db/src/main/java/develop/toolkit/db/mysql/MysqlProperties.java index 34d235f..5314074 100644 --- a/develop-toolkit-support/src/main/java/develop/toolkit/support/db/mysql/MysqlProperties.java +++ b/develop-toolkit-db/src/main/java/develop/toolkit/db/mysql/MysqlProperties.java @@ -1,40 +1,41 @@ -package develop.toolkit.support.db.mysql; - -import lombok.Getter; - -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; - -/** - * @author qiushui on 2019-09-03. - */ -@Getter -public class MysqlProperties { - - private String domain; - - private int port; - - private String username; - - private String password; - - private String database; - - private Map parameters = new HashMap<>(); - - public MysqlProperties(String domain, int port, String username, String password, String database) { - this.domain = domain; - this.port = port; - this.username = username; - this.password = password; - this.database = database; - parameters.put("useSSL", "false"); - } - - public String getUrl() { - return String.format("jdbc:mysql://%s:%d/%s?", domain, port, database) + - parameters.entrySet().stream().map(entry -> String.format("%s=%s", entry.getKey(), entry.getValue())).collect(Collectors.joining("&")); - } -} +package develop.toolkit.db.mysql; + +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author qiushui on 2019-09-03. + */ +@Getter +public class MysqlProperties { + + private final String domain; + + private final int port; + + private final String username; + + private final String password; + + private final String database; + + private final Map parameters = new HashMap<>(); + + public MysqlProperties(String domain, int port, String username, String password, String database) { + this.domain = domain; + this.port = port; + this.username = username; + this.password = password; + this.database = database; + parameters.put("useSSL", "false"); + parameters.put("serverTimezone", "Asia/Shanghai"); + } + + public String getUrl() { + return String.format("jdbc:mysql://%s:%d/%s?", domain, port, database) + + parameters.entrySet().stream().map(entry -> String.format("%s=%s", entry.getKey(), entry.getValue())).collect(Collectors.joining("&")); + } +} diff --git a/develop-toolkit-support/src/main/java/develop/toolkit/support/db/mysql/NoUniqueResultException.java b/develop-toolkit-db/src/main/java/develop/toolkit/db/mysql/NoUniqueResultException.java similarity index 79% rename from develop-toolkit-support/src/main/java/develop/toolkit/support/db/mysql/NoUniqueResultException.java rename to develop-toolkit-db/src/main/java/develop/toolkit/db/mysql/NoUniqueResultException.java index b045a9d..f24fd97 100644 --- a/develop-toolkit-support/src/main/java/develop/toolkit/support/db/mysql/NoUniqueResultException.java +++ b/develop-toolkit-db/src/main/java/develop/toolkit/db/mysql/NoUniqueResultException.java @@ -1,11 +1,11 @@ -package develop.toolkit.support.db.mysql; - -/** - * @author qiushui on 2019-09-03. - */ -public class NoUniqueResultException extends RuntimeException { - - public NoUniqueResultException(int size) { - super("no unique result: " + size); - } -} +package develop.toolkit.db.mysql; + +/** + * @author qiushui on 2019-09-03. + */ +public class NoUniqueResultException extends RuntimeException { + + public NoUniqueResultException(int size) { + super("no unique result: " + size); + } +} diff --git a/develop-toolkit-db/src/main/java/develop/toolkit/db/mysql/PreparedStatementSetter.java b/develop-toolkit-db/src/main/java/develop/toolkit/db/mysql/PreparedStatementSetter.java new file mode 100644 index 0000000..d9f5d5c --- /dev/null +++ b/develop-toolkit-db/src/main/java/develop/toolkit/db/mysql/PreparedStatementSetter.java @@ -0,0 +1,12 @@ +package develop.toolkit.db.mysql; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * @author qiushui on 2019-09-03. + */ +public interface PreparedStatementSetter { + + void set(PreparedStatement preparedStatement) throws SQLException; +} diff --git a/develop-toolkit-db/src/main/java/develop/toolkit/db/mysql/RowMapper.java b/develop-toolkit-db/src/main/java/develop/toolkit/db/mysql/RowMapper.java new file mode 100644 index 0000000..a3e0c1d --- /dev/null +++ b/develop-toolkit-db/src/main/java/develop/toolkit/db/mysql/RowMapper.java @@ -0,0 +1,12 @@ +package develop.toolkit.db.mysql; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * @author qiushui on 2019-09-03. + */ +public interface RowMapper { + + T mapping(ResultSet resultSet) throws SQLException; +} diff --git a/develop-toolkit-support/src/main/java/develop/toolkit/support/db/mysql/SQLFactory.java b/develop-toolkit-db/src/main/java/develop/toolkit/db/mysql/SQLFactory.java similarity index 90% rename from develop-toolkit-support/src/main/java/develop/toolkit/support/db/mysql/SQLFactory.java rename to develop-toolkit-db/src/main/java/develop/toolkit/db/mysql/SQLFactory.java index c80d731..21603fa 100644 --- a/develop-toolkit-support/src/main/java/develop/toolkit/support/db/mysql/SQLFactory.java +++ b/develop-toolkit-db/src/main/java/develop/toolkit/db/mysql/SQLFactory.java @@ -1,61 +1,62 @@ -package develop.toolkit.support.db.mysql; - -import develop.toolkit.base.utils.IOAdvice; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -/** - * @author qiushui on 2019-09-18. - */ -public class SQLFactory { - - private Map sqlMap = new HashMap<>(); - - public SQLFactory(String... sqlFiles) { - for (String sqlFile : sqlFiles) { - parseSqlFile(sqlFile); - } - } - - private void parseSqlFile(String sqlFile) { - List lines = IOAdvice.readLinesFromClasspath(sqlFile).collect(Collectors.toList()); - String key = null; - StringBuilder sb = new StringBuilder(); - for (String line : lines) { - if (line.isBlank()) { - continue; - } - if (line.startsWith("#")) { - putSql(key, sb); - key = line.substring(1).trim(); - } else { - if (sb.length() > 0) { - sb.append(" "); - } - sb.append(line.trim()); - } - } - putSql(key, sb); - } - - private void putSql(String key, StringBuilder sb) { - if (key != null && sb.length() > 0) { - if (sqlMap.containsKey(key)) { - throw new RuntimeException("sql map exists \"" + key + "\""); - } - sqlMap.put(key, sb.toString()); - sb.setLength(0); - } - } - - public String getSql(String key) { - String sql = sqlMap.get(key); - if (sql == null) { - throw new RuntimeException("sql map not exists \"" + key + "\""); - } - return sql; - } -} +package develop.toolkit.db.mysql; + +import develop.toolkit.base.utils.IOAdvice; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author qiushui on 2019-09-18. + */ +@SuppressWarnings("unused") +public class SQLFactory { + + private final Map sqlMap = new HashMap<>(); + + public SQLFactory(String... sqlFiles) { + for (String sqlFile : sqlFiles) { + parseSqlFile(sqlFile); + } + } + + private void parseSqlFile(String sqlFile) { + List lines = IOAdvice.readLinesFromClasspath(sqlFile).collect(Collectors.toList()); + String key = null; + StringBuilder sb = new StringBuilder(); + for (String line : lines) { + if (line.isBlank()) { + continue; + } + if (line.startsWith("#")) { + putSql(key, sb); + key = line.substring(1).trim(); + } else { + if (sb.length() > 0) { + sb.append(" "); + } + sb.append(line.trim()); + } + } + putSql(key, sb); + } + + private void putSql(String key, StringBuilder sb) { + if (key != null && sb.length() > 0) { + if (sqlMap.containsKey(key)) { + throw new RuntimeException("sql map exists \"" + key + "\""); + } + sqlMap.put(key, sb.toString()); + sb.setLength(0); + } + } + + public String getSql(String key) { + String sql = sqlMap.get(key); + if (sql == null) { + throw new RuntimeException("sql map not exists \"" + key + "\""); + } + return sql; + } +} diff --git a/develop-toolkit-support/src/main/java/module-info.java b/develop-toolkit-db/src/main/java/module-info.java similarity index 51% rename from develop-toolkit-support/src/main/java/module-info.java rename to develop-toolkit-db/src/main/java/module-info.java index 89d9687..ae3ccce 100644 --- a/develop-toolkit-support/src/main/java/module-info.java +++ b/develop-toolkit-db/src/main/java/module-info.java @@ -1,17 +1,16 @@ -/** - * @author qiushui on 2019-02-27. - */ -module develop.toolkit.support { - requires lombok; - requires develop.toolkit.base; - requires org.apache.commons.lang3; - requires java.sql; - requires mysql.connector.java; - requires expression; - requires java.net.http; - requires com.fasterxml.jackson.databind; - requires com.fasterxml.jackson.dataformat.xml; - - exports develop.toolkit.support.db; - exports develop.toolkit.support.db.mysql; +/** + * @author qiushui on 2019-02-27. + */ +module develop.toolkit.db { + requires develop.toolkit.base; + requires expression; + requires java.net.http; + requires java.sql; + requires lombok; + requires mysql.connector.java; + requires org.apache.commons.lang3; + requires mongo.java.driver; + + exports develop.toolkit.db; + exports develop.toolkit.db.mysql; } \ No newline at end of file diff --git a/develop-toolkit-multimedia/pom.xml b/develop-toolkit-multimedia/pom.xml new file mode 100644 index 0000000..42432cc --- /dev/null +++ b/develop-toolkit-multimedia/pom.xml @@ -0,0 +1,30 @@ + + + + develop-toolkit + com.github.developframework + 1.0.7-SNAPSHOT + + 4.0.0 + + develop-toolkit-multimedia + + 开发工具箱 - 多媒体处理 + + + + com.github.developframework + develop-toolkit-base + + + commons-io + commons-io + 2.11.0 + + + com.drewnoakes + metadata-extractor + + + + \ No newline at end of file diff --git a/develop-toolkit-multimedia/src/main/java/develop/toolkit/multimedia/image/ImageAdvice.java b/develop-toolkit-multimedia/src/main/java/develop/toolkit/multimedia/image/ImageAdvice.java new file mode 100644 index 0000000..75f6db7 --- /dev/null +++ b/develop-toolkit-multimedia/src/main/java/develop/toolkit/multimedia/image/ImageAdvice.java @@ -0,0 +1,196 @@ +package develop.toolkit.multimedia.image; + +import com.drew.imaging.FileType; +import com.drew.imaging.ImageMetadataReader; +import com.drew.imaging.ImageProcessingException; +import com.drew.metadata.Metadata; +import com.drew.metadata.MetadataException; +import com.drew.metadata.exif.ExifIFD0Directory; +import develop.toolkit.base.utils.CompareAdvice; +import org.apache.commons.io.IOUtils; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * @author qiushui on 2021-06-20. + */ +public abstract class ImageAdvice { + + /** + * 修正图片角度后裁切图片 + * + * @param inputStream 图片输入流 + * @param outputStream 输出流 + * @param rectangle 裁切区域 + * @param outFileType 输出图片类型 + * @throws IOException + */ + public static void fixOrientationAndCut(InputStream inputStream, OutputStream outputStream, Rectangle rectangle, FileType outFileType) throws IOException { + final byte[] data = IOUtils.toByteArray(inputStream); + ByteArrayInputStream bais = new ByteArrayInputStream(data); + final int angle = readOrientationAngle(bais); + bais.close(); + bais = new ByteArrayInputStream(data); + final BufferedImage image = cut( + rotate(ImageIO.read(bais), angle), + rectangle + ); + bais.close(); + ImageIO.write(image, outFileType.getAllExtensions()[0], outputStream); + } + + /** + * 修正图片角度后定宽缩放 + * + * @param inputStream 图片输入流 + * @param outputStream 输出流 + * @param width 定宽 + * @param outFileType 输出图片类型 + * @throws IOException + */ + public static void fixOrientationAndZoom(InputStream inputStream, OutputStream outputStream, int width, FileType outFileType) throws IOException { + final byte[] data = IOUtils.toByteArray(inputStream); + ByteArrayInputStream bais = new ByteArrayInputStream(data); + final int angle = readOrientationAngle(bais); + bais.close(); + bais = new ByteArrayInputStream(data); + final BufferedImage image = zoom( + rotate(ImageIO.read(bais), angle), + width + ); + bais.close(); + ImageIO.write(image, outFileType.getAllExtensions()[0], outputStream); + } + + /** + * 裁切图片 + * + * @param originalImage 原图 + * @param rectangle 裁切区域 + * @return 新图片 + */ + public static BufferedImage cut(BufferedImage originalImage, Rectangle rectangle) { + final int MAX_WIDTH = originalImage.getWidth(); + final int MAX_HEIGHT = originalImage.getHeight(); + final int x = CompareAdvice.adjustRange((int) rectangle.getX(), 0, MAX_WIDTH - 1); + final int y = CompareAdvice.adjustRange((int) rectangle.getY(), 0, MAX_HEIGHT - 1); + final int width = CompareAdvice.adjustRange((int) rectangle.getWidth(), 1, MAX_WIDTH - x); + final int height = CompareAdvice.adjustRange((int) rectangle.getHeight(), 1, MAX_HEIGHT - y); + return originalImage.getSubimage(x, y, width, height); + } + + /** + * 旋转角度 + * + * @param originalImage 原图 + * @param angle 旋转角度 + * @return 新图片 + */ + public static BufferedImage rotate(BufferedImage originalImage, int angle) { + // 修正角度 + while (angle < 0) { + angle += 360; + } + angle %= 360; + if (angle == 0) { + return originalImage; + } + final int MAX_WIDTH = originalImage.getWidth(); + final int MAX_HEIGHT = originalImage.getHeight(); + Rectangle rectangle = computeRotatedSize(new Rectangle(new Dimension(MAX_WIDTH, MAX_HEIGHT)), angle); + BufferedImage newImage = new BufferedImage(rectangle.width, rectangle.height, BufferedImage.TYPE_INT_RGB); + Graphics2D graphics = newImage.createGraphics(); + graphics.translate((rectangle.width - MAX_WIDTH) / 2, (rectangle.height - MAX_HEIGHT) / 2); + graphics.rotate(Math.toRadians(angle), (double) MAX_WIDTH / 2, (double) MAX_HEIGHT / 2); + graphics.drawImage(originalImage, null, null); + return newImage; + } + + /** + * 定宽缩放 + * + * @param originalImage 原图 + * @param width 定宽 + * @return 新图片 + */ + public static BufferedImage zoom(BufferedImage originalImage, int width) { + final int MAX_WIDTH = originalImage.getWidth(); + final int MAX_HEIGHT = originalImage.getHeight(); + if (MAX_WIDTH == width) { + return originalImage; + } + final int newHeight = (int) ((double) width / (double) MAX_WIDTH * (double) MAX_HEIGHT); + final BufferedImage newImage = new BufferedImage(width, newHeight, BufferedImage.TYPE_INT_RGB); + final Image image = originalImage.getScaledInstance(width, newHeight, BufferedImage.SCALE_FAST); + newImage.getGraphics().drawImage(image, 0, 0, null); + return newImage; + } + + /** + * 计算旋转后的画布大小 + */ + private static Rectangle computeRotatedSize(Rectangle rectangle, int angle) { + if (angle >= 90) { + if (angle / 90 % 2 == 1) { + int temp = rectangle.height; + rectangle.height = rectangle.width; + rectangle.width = temp; + } + angle %= 90; + } + double r = Math.sqrt(rectangle.height * rectangle.height + rectangle.width * rectangle.width) / 2; + double len = 2 * Math.sin(Math.toRadians(angle) / 2) * r; + double angelAlpha = (Math.PI - Math.toRadians(angle)) / 2; + double angelDeltaWidth = Math.atan((double) rectangle.height / rectangle.width); + double angelDeltaHeight = Math.atan((double) rectangle.width / rectangle.height); + int lenDeltaWidth = (int) (len * Math.cos(Math.PI - angelAlpha - angelDeltaWidth)); + int lenDeltaHeight = (int) (len * Math.cos(Math.PI - angelAlpha - angelDeltaHeight)); + int desWidth = rectangle.width + lenDeltaWidth * 2; + int des_height = rectangle.height + lenDeltaHeight * 2; + return new Rectangle(new Dimension(desWidth, des_height)); + } + + /** + * 读取图片拍摄角度 + * + * @param inputStream 文件输入流 + * @return 角度 + * @throws IOException + */ + private static int readOrientationAngle(InputStream inputStream) throws IOException { + final int orientation; + try { + final Metadata metadata = ImageMetadataReader.readMetadata(inputStream); + final ExifIFD0Directory directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); + if (directory != null && directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) { + orientation = directory.getInt(ExifIFD0Directory.TAG_ORIENTATION); + } else { + orientation = 0; + } + } catch (ImageProcessingException | MetadataException e) { + throw new RuntimeException("read image metadata fail: " + e.getMessage()); + } + int angle; + switch (orientation) { + case 3: + angle = 180; + break; + case 6: + angle = 90; + break; + case 8: + angle = 270; + break; + default: + angle = 0; + break; + } + return angle; + } +} diff --git a/develop-toolkit-multimedia/src/main/java/develop/toolkit/multimedia/image/MediaMetadataAdvice.java b/develop-toolkit-multimedia/src/main/java/develop/toolkit/multimedia/image/MediaMetadataAdvice.java new file mode 100644 index 0000000..e072242 --- /dev/null +++ b/develop-toolkit-multimedia/src/main/java/develop/toolkit/multimedia/image/MediaMetadataAdvice.java @@ -0,0 +1,99 @@ +package develop.toolkit.multimedia.image; + +import com.drew.imaging.FileType; +import com.drew.imaging.ImageMetadataReader; +import com.drew.imaging.ImageProcessingException; +import com.drew.metadata.Directory; +import com.drew.metadata.Metadata; +import com.drew.metadata.Tag; +import com.drew.metadata.exif.ExifSubIFDDirectory; +import com.drew.metadata.mov.QuickTimeDirectory; +import com.drew.metadata.mp4.Mp4Directory; +import develop.toolkit.base.utils.DateTimeAdvice; +import lombok.SneakyThrows; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.time.LocalDateTime; +import java.util.Optional; + +/** + * @author qiushui on 2021-12-17. + */ +public abstract class MediaMetadataAdvice { + + /** + * 根据扩展名获取文件类型 + * + * @param extensionName 扩展名 + */ + public static FileType getFileTypeByExtensionName(String extensionName) { + for (FileType fileType : FileType.values()) { + for (String extension : fileType.getAllExtensions()) { + if (extension.equals(extensionName)) { + return fileType; + } + } + } + return FileType.Unknown; + } + + /** + * 根据媒体类型获取文件类型 + * + * @param mediaType 媒体类型 + */ + public static FileType getFileTypeByMediaType(String mediaType) { + for (FileType fileType : FileType.values()) { + if (fileType.getMimeType().equals(mediaType)) { + return fileType; + } + } + return FileType.Unknown; + } + + /** + * 打印所有值 + * + * @param inputStream 文件流 + */ + @SneakyThrows({ImageProcessingException.class, IOException.class}) + public static void printMetadataTags(InputStream inputStream) { + final Metadata metadata = ImageMetadataReader.readMetadata(inputStream); + final Iterable directories = metadata.getDirectories(); + for (Directory directory : directories) { + for (Tag tag : directory.getTags()) { + System.out.println(tag); + } + } + } + + /** + * 提取图片文件中的创建时间 + */ + @SneakyThrows({ImageProcessingException.class, IOException.class}) + public static LocalDateTime extractCreateTimeForImage(File file) { + Metadata metadata = ImageMetadataReader.readMetadata(file); + return getMetadataDate(metadata, ExifSubIFDDirectory.class, ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL).orElse(null); + } + + /** + * 提取视频文件中的创建时间 + */ + @SneakyThrows({ImageProcessingException.class, IOException.class}) + public static LocalDateTime extractCreateTimeForVideo(File file) { + Metadata metadata = ImageMetadataReader.readMetadata(file); + return getMetadataDate(metadata, Mp4Directory.class, Mp4Directory.TAG_CREATION_TIME) + .orElseGet(() -> + getMetadataDate(metadata, QuickTimeDirectory.class, QuickTimeDirectory.TAG_CREATION_TIME).orElse(null) + ); + } + + private static Optional getMetadataDate(Metadata metadata, Class directoryClass, int tag) { + return Optional + .ofNullable(metadata.getFirstDirectoryOfType(directoryClass)) + .map(directory -> directory.getDate(tag)) + .map(DateTimeAdvice::toLocalDateTime); + } +} diff --git a/develop-toolkit-multimedia/src/main/java/module-info.java b/develop-toolkit-multimedia/src/main/java/module-info.java new file mode 100644 index 0000000..86811a3 --- /dev/null +++ b/develop-toolkit-multimedia/src/main/java/module-info.java @@ -0,0 +1,12 @@ +/** + * @author qiushui on 2021-06-20. + */ +module develop.toolkit.multimedia { + requires metadata.extractor; + requires develop.toolkit.base; + requires java.desktop; + requires lombok; + requires org.apache.commons.io; + + exports develop.toolkit.multimedia.image; +} \ No newline at end of file diff --git a/develop-toolkit-mybatis/pom.xml b/develop-toolkit-mybatis/pom.xml new file mode 100644 index 0000000..08a65c5 --- /dev/null +++ b/develop-toolkit-mybatis/pom.xml @@ -0,0 +1,41 @@ + + + + develop-toolkit + com.github.developframework + 1.0.7-SNAPSHOT + + 4.0.0 + + develop-toolkit-mybatis + + + 3.5.5 + 8.0.20 + 4.0.3 + + + + + com.github.developframework + develop-toolkit-base + + + org.mybatis + mybatis + ${mybatis.version} + + + mysql + mysql-connector-java + ${mysql-connector-java.version} + runtime + + + com.zaxxer + HikariCP + ${HikariCP.version} + + + + \ No newline at end of file diff --git a/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/BaseMapper.java b/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/BaseMapper.java new file mode 100644 index 0000000..01fcd13 --- /dev/null +++ b/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/BaseMapper.java @@ -0,0 +1,14 @@ +package develop.toolkit.mybatis; + +import org.apache.ibatis.annotations.SelectProvider; + +import java.util.List; + +/** + * @author qiushui on 2022-02-10. + */ +public interface BaseMapper { + + @SelectProvider(type = BaseMapperMysqlProvider.class, method = "select") + List select(Object search); +} diff --git a/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/BaseMapperMysqlProvider.java b/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/BaseMapperMysqlProvider.java new file mode 100644 index 0000000..c53b5e1 --- /dev/null +++ b/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/BaseMapperMysqlProvider.java @@ -0,0 +1,11 @@ +package develop.toolkit.mybatis; + +/** + * @author qiushui on 2022-02-10. + */ +public class BaseMapperMysqlProvider { + + public String select(Object search) { + return null; + } +} diff --git a/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/ConfigurationHandler.java b/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/ConfigurationHandler.java new file mode 100644 index 0000000..3c8ae82 --- /dev/null +++ b/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/ConfigurationHandler.java @@ -0,0 +1,52 @@ +package develop.toolkit.mybatis; + +import com.zaxxer.hikari.HikariConfig; +import org.apache.ibatis.binding.MapperRegistry; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.type.TypeAliasRegistry; + +import java.util.List; + +/** + * @author qiushui on 2021-06-22. + */ +@FunctionalInterface +public interface ConfigurationHandler { + + void configHikari(HikariConfig config); + + /** + * 配置Mapper + * + * @param mapperRegistry mapper注册器 + */ + default void configMapperRegistry(MapperRegistry mapperRegistry) { + + } + + /** + * 配置别名 + * + * @param typeAliasRegistry 别名注册器 + */ + default void configTypeAliasRegistry(TypeAliasRegistry typeAliasRegistry) { + + } + + /** + * 配置拦截器 + * + * @param interceptors 拦截器链 + */ + default void configInterceptors(List interceptors) { + + } + + /** + * 其它配置 + */ + default void configurationSettings(Configuration configuration) { + + } +} diff --git a/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/MybatisAdvice.java b/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/MybatisAdvice.java new file mode 100644 index 0000000..fa1ff9d --- /dev/null +++ b/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/MybatisAdvice.java @@ -0,0 +1,48 @@ +package develop.toolkit.mybatis; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.apache.ibatis.mapping.Environment; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.apache.ibatis.transaction.TransactionFactory; +import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; + +import javax.sql.DataSource; + +/** + * @author qiushui on 2021-06-22. + */ +public abstract class MybatisAdvice { + + /** + * 构建SqlSessionFactory + * + * @param configurationHandler 配置处理接口 + */ + public static SqlSessionFactory buildSqlSessionFactory(ConfigurationHandler configurationHandler) { + final Configuration configuration = new Configuration(); + configuration.setMapUnderscoreToCamelCase(true); + configuration.setUseGeneratedKeys(true); + configuration.setLogPrefix("mybatis."); + final HikariConfig hikariConfig = new HikariConfig(); + configurationHandler.configHikari(hikariConfig); + final DataSource dataSource = new HikariDataSource(hikariConfig); + final TransactionFactory transactionFactory = new JdbcTransactionFactory(); + configuration.setEnvironment(new Environment("default", transactionFactory, dataSource)); + configurationHandler.configMapperRegistry(configuration.getMapperRegistry()); + configurationHandler.configTypeAliasRegistry(configuration.getTypeAliasRegistry()); + configurationHandler.configInterceptors(configuration.getInterceptors()); + + for (MappedStatement mappedStatement : configuration.getMappedStatements()) { + try { + SimpleMapperHelper.changeMs(mappedStatement); + } catch (Exception e) { + e.printStackTrace(); + } + } + return new SqlSessionFactoryBuilder().build(configuration); + } +} diff --git a/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/MybatisPager.java b/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/MybatisPager.java new file mode 100644 index 0000000..a755a92 --- /dev/null +++ b/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/MybatisPager.java @@ -0,0 +1,26 @@ +package develop.toolkit.mybatis; + +import develop.toolkit.base.struct.Pager; + +/** + * @author qiushui on 2021-06-22. + */ +public final class MybatisPager extends Pager { + + public MybatisPager() { + super(); + } + + public MybatisPager(int index, int size) { + super(index, size); + } + + /** + * 生成 LIMIT 语句 + * + * @return LIMIT 语句 + */ + public String limitSQL() { + return String.format("LIMIT %d, %d", page * size, size); + } +} diff --git a/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/PagingInterceptor.java b/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/PagingInterceptor.java new file mode 100644 index 0000000..7c65182 --- /dev/null +++ b/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/PagingInterceptor.java @@ -0,0 +1,129 @@ +package develop.toolkit.mybatis; + +import org.apache.commons.lang3.StringUtils; +import org.apache.ibatis.cache.CacheKey; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ResultMap; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Signature; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; + +import java.lang.reflect.Field; +import java.sql.SQLException; +import java.util.*; + +/** + * @author qiushui on 2021-06-18. + */ +@Intercepts(@Signature( + type = Executor.class, + method = "query", + args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} +)) +@SuppressWarnings("unchecked") +public class PagingInterceptor implements Interceptor { + + @Override + public Object intercept(Invocation invocation) throws Throwable { + final Object[] args = invocation.getArgs(); + final MappedStatement ms = (MappedStatement) args[0]; + final Object parameterObject = args[1]; +// final RowBounds rowBounds = (RowBounds) args[2]; + + final Optional mybatisPagerOptional = extractPager(parameterObject); + if (mybatisPagerOptional.isEmpty()) { + //不需要分页,直接返回结果 + return invocation.proceed(); + } + final ResultHandler resultHandler = (ResultHandler) args[3]; + final MybatisPager mybatisPager = mybatisPagerOptional.get(); + final Executor executor = (Executor) invocation.getTarget(); + final BoundSql boundSql = ms.getBoundSql(parameterObject); + final Map additionalParameters = getAdditionalParameters(boundSql); + final long count = queryCount(executor, ms, boundSql, parameterObject, additionalParameters, resultHandler); + if (count == 0) { + return new ArrayList<>(); + } + return queryList(executor, ms, boundSql, parameterObject, additionalParameters, resultHandler, mybatisPager); + } + + /** + * 查询总条数 + */ + private long queryCount(Executor executor, MappedStatement ms, BoundSql boundSql, Object parameterObject, Map additionalParameters, ResultHandler resultHandler) throws SQLException { + MappedStatement countMs = newMappedStatement(ms); + CacheKey countKey = executor.createCacheKey(countMs, parameterObject, RowBounds.DEFAULT, boundSql); + String countSql = String.format("SELECT COUNT(*) FROM (%s) total", boundSql.getSql()); + BoundSql countBoundSql = new BoundSql(ms.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject); + additionalParameters.forEach(countBoundSql::setAdditionalParameter); + List countQueryResult = executor.query(countMs, parameterObject, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql); + return (Long) countQueryResult.get(0); + } + + /** + * 查询列表 + */ + private List queryList(Executor executor, MappedStatement ms, BoundSql boundSql, Object parameterObject, Map additionalParameters, ResultHandler resultHandler, MybatisPager pager) throws SQLException { + CacheKey pageKey = executor.createCacheKey(ms, parameterObject, RowBounds.DEFAULT, boundSql); + String pageSql = boundSql.getSql() + " " + pager.limitSQL(); + BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameterObject); + additionalParameters.forEach(pageBoundSql::setAdditionalParameter); + return executor.query(ms, parameterObject, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql); + } + + private MappedStatement newMappedStatement(MappedStatement ms) { + return new MappedStatement.Builder( + ms.getConfiguration(), + ms.getId() + "_count", + ms.getSqlSource(), + ms.getSqlCommandType() + ) + .resource(ms.getResource()) + .fetchSize(ms.getFetchSize()) + .statementType(ms.getStatementType()) + .timeout(ms.getTimeout()) + .parameterMap(ms.getParameterMap()) + .resultSetType(ms.getResultSetType()) + .cache(ms.getCache()) + .flushCacheRequired(ms.isFlushCacheRequired()) + .useCache(ms.isUseCache()) + .resultMaps( + Collections.singletonList( + new ResultMap.Builder( + ms.getConfiguration(), + ms.getId(), + Long.class, + Collections.emptyList() + ).build() + ) + ) + .keyProperty(StringUtils.join(ms.getKeyProperties(), ",")) + .build(); + } + + private Optional extractPager(Object parameterObject) { + if (parameterObject instanceof MybatisPager) { + return Optional.of((MybatisPager) parameterObject); + } else if (parameterObject instanceof Map) { + return ((Map) parameterObject) + .values() + .stream() + .filter(v -> v instanceof MybatisPager) + .map(v -> (MybatisPager) v) + .findFirst(); + } else { + return Optional.empty(); + } + } + + private Map getAdditionalParameters(BoundSql boundSql) throws NoSuchFieldException, IllegalAccessException { + Field additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters"); + additionalParametersField.setAccessible(true); + return (Map) additionalParametersField.get(boundSql); + } +} diff --git a/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/SimpleMapperHelper.java b/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/SimpleMapperHelper.java new file mode 100644 index 0000000..a5d85ff --- /dev/null +++ b/develop-toolkit-mybatis/src/main/java/develop/toolkit/mybatis/SimpleMapperHelper.java @@ -0,0 +1,79 @@ +package develop.toolkit.mybatis; + +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlSource; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.SystemMetaObject; +import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +/** + * @author qiushui on 2022-02-10. + */ +public class SimpleMapperHelper { + + public static final XMLLanguageDriver XML_LANGUAGE_DRIVER = new XMLLanguageDriver(); + + /** + * 获取泛型类型 + */ + public static Class getEntityClass(Class mapperClass) { + Type[] types = mapperClass.getGenericInterfaces(); + Class entityClass = null; + for (Type type : types) { + if (type instanceof ParameterizedType) { + ParameterizedType t = (ParameterizedType) type; + //判断父接口是否为 BaseMapper.class + if (t.getRawType() == BaseMapper.class) { + //得到泛型类型 + entityClass = (Class) t.getActualTypeArguments()[0]; + break; + } + } + } + return entityClass; + } + + /** + * 替换 SqlSource + */ + public static void changeMs(MappedStatement ms) throws Exception { + String msId = ms.getId(); + //标准msId为 包名.接口名.方法名 + int lastIndex = msId.lastIndexOf("."); + String methodName = msId.substring(lastIndex + 1); + String interfaceName = msId.substring(0, lastIndex); + Class mapperClass = Class.forName(interfaceName); + //判断是否继承了通用接口 + if (BaseMapper.class.isAssignableFrom(mapperClass)) { + //判断当前方法是否为通用 select 方法 + if (methodName.equals("select")) { + Class entityClass = getEntityClass(mapperClass); + //必须使用"); + //解析 sqlSource + SqlSource sqlSource = XML_LANGUAGE_DRIVER.createSqlSource(ms.getConfiguration(), sqlBuilder.toString(), entityClass); + //替换 + MetaObject msObject = SystemMetaObject.forObject(ms); + msObject.setValue("sqlSource", sqlSource); + } + } + } +} diff --git a/develop-toolkit-support/src/main/java/develop/toolkit/support/db/mysql/PreparedStatementSetter.java b/develop-toolkit-support/src/main/java/develop/toolkit/support/db/mysql/PreparedStatementSetter.java deleted file mode 100644 index 3e14bf4..0000000 --- a/develop-toolkit-support/src/main/java/develop/toolkit/support/db/mysql/PreparedStatementSetter.java +++ /dev/null @@ -1,11 +0,0 @@ -package develop.toolkit.support.db.mysql; - -import java.sql.PreparedStatement; - -/** - * @author qiushui on 2019-09-03. - */ -public interface PreparedStatementSetter { - - void set(PreparedStatement preparedStatement); -} diff --git a/develop-toolkit-support/src/main/java/develop/toolkit/support/db/mysql/RowMapper.java b/develop-toolkit-support/src/main/java/develop/toolkit/support/db/mysql/RowMapper.java deleted file mode 100644 index 7b6abf1..0000000 --- a/develop-toolkit-support/src/main/java/develop/toolkit/support/db/mysql/RowMapper.java +++ /dev/null @@ -1,11 +0,0 @@ -package develop.toolkit.support.db.mysql; - -import java.sql.ResultSet; - -/** - * @author qiushui on 2019-09-03. - */ -public interface RowMapper { - - T mapping(ResultSet resultSet); -} diff --git a/develop-toolkit-support/src/main/java/develop/toolkit/support/http/HttpAdvice.java b/develop-toolkit-support/src/main/java/develop/toolkit/support/http/HttpAdvice.java deleted file mode 100644 index ccdb4f8..0000000 --- a/develop-toolkit-support/src/main/java/develop/toolkit/support/http/HttpAdvice.java +++ /dev/null @@ -1,166 +0,0 @@ -package develop.toolkit.support.http; - -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; - -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.charset.StandardCharsets; -import java.util.Map; -import java.util.stream.Collectors; - -/** - * Http增强工具 - */ -@Slf4j -public final class HttpAdvice { - - /** - * GET请求 - * - * @param httpClient - * @param url - * @param headers - * @param parameters - * @return - * @throws IOException - */ - public static HttpAdviceResponse get(String label, HttpClient httpClient, String url, Map headers, Map parameters) throws IOException { - return send( - label, - httpClient, - HttpMethod.GET, - builder(url, headers, parameters), - null - ); - } - - /** - * 发送x-www-form-urlencoded格式请求 - * - * @param httpClient - * @param httpMethod - * @param url - * @param headers - * @param parameters - * @param form - * @return - * @throws IOException - */ - public static HttpAdviceResponse sendFormUrlencoded(String label, HttpClient httpClient, HttpMethod httpMethod, String url, Map headers, Map parameters, Map form) throws IOException { - return send( - label, - httpClient, - httpMethod, - builder(url, headers, parameters).header("Content-Type", "application/x-www-form-urlencoded"), - form - .entrySet() - .stream() - .map(kv -> String.format("%s=%s", kv.getKey(), kv.getValue())) - .collect(Collectors.joining("&")) - ); - } - - /** - * 发送json请求 - * - * @param httpClient - * @param httpMethod - * @param url - * @param headers - * @param parameters - * @param json - * @return - * @throws IOException - */ - public static HttpAdviceResponse sendJson(String label, HttpClient httpClient, HttpMethod httpMethod, String url, Map headers, Map parameters, String json) throws IOException { - return send( - label, - httpClient, - httpMethod, - builder(url, headers, parameters).header("Content-Type", "application/json;charset=UTF-8"), - json - ); - } - - /** - * 发送xml请求 - * - * @param httpClient - * @param httpMethod - * @param url - * @param headers - * @param parameters - * @param xml - * @return - * @throws IOException - */ - public static HttpAdviceResponse sendXml(String label, HttpClient httpClient, HttpMethod httpMethod, String url, Map headers, Map parameters, String xml) throws IOException { - return send( - label, - httpClient, - httpMethod, - builder(url, headers, parameters).header("Content-Type", "application/xml;charset=UTF-8"), - xml - ); - } - - private static HttpRequest.Builder builder(String url, Map headers, Map parameters) { - if (parameters != null) { - url += parameters - .entrySet() - .stream() - .map(kv -> String.format("%s=%s", kv.getKey(), kv.getValue())) - .collect(Collectors.joining("&", "?", "")); - } - HttpRequest.Builder builder = HttpRequest - .newBuilder() - .version(HttpClient.Version.HTTP_1_1) - .uri(URI.create(url)); - if (headers != null) { - headers.forEach(builder::header); - } - return builder; - } - - private static HttpAdviceResponse send(String label, HttpClient httpClient, HttpMethod httpMethod, HttpRequest.Builder builder, String content) throws IOException { - HttpAdviceResponse response = null; - HttpRequest httpRequest = null; - try { - httpRequest = builder.method( - httpMethod.name(), - content == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(content, StandardCharsets.UTF_8) - ).build(); - HttpResponse httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofByteArray()); - response = new HttpAdviceResponse( - httpResponse.statusCode(), - httpResponse.headers().map(), - httpResponse.body() - ); - return response; - } catch (InterruptedException e) { - throw new RuntimeException(e); - } finally { - if (log.isDebugEnabled() && httpRequest != null) { - StringBuilder sb = new StringBuilder(); - sb - .append("\nlabel: ").append(label) - .append("\nhttp request:\n") - .append("\turl: ").append(httpRequest.uri().toString()).append("\n") - .append("\theaders:\n"); - httpRequest - .headers() - .map() - .forEach((k, v) -> sb.append("\t\t").append(k).append(": ").append(StringUtils.join(v, ";")).append("\n")); - sb.append("\tbody: ").append(content != null ? content : "(no content)").append("\n"); - if (response != null) { - sb.append(response.toString()); - } - log.debug(sb.toString()); - } - } - } -} diff --git a/develop-toolkit-support/src/main/java/develop/toolkit/support/http/HttpAdviceResponse.java b/develop-toolkit-support/src/main/java/develop/toolkit/support/http/HttpAdviceResponse.java deleted file mode 100644 index 0d6ca1f..0000000 --- a/develop-toolkit-support/src/main/java/develop/toolkit/support/http/HttpAdviceResponse.java +++ /dev/null @@ -1,57 +0,0 @@ -package develop.toolkit.support.http; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.xml.XmlMapper; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.apache.commons.lang3.StringUtils; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Map; - -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -public class HttpAdviceResponse { - - private int httpStatus; - - private Map> headers; - - private byte[] body; - - public String ofString() { - return new String(body, StandardCharsets.UTF_8); - } - - public T parseJson(Class clazz, ObjectMapper objectMapper) throws IOException { - return objectMapper.readValue(body, clazz); - } - - public T parseXml(Class clazz, XmlMapper xmlMapper) throws IOException { - return xmlMapper.readValue(body, clazz); - } - - public String getHeader(String header) { - return StringUtils.join(headers.getOrDefault(header, List.of()), ";"); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb - .append("\nhttp response:\n") - .append("\tstatus: ").append(httpStatus).append("\n") - .append("\theaders:\n"); - for (Map.Entry> entry : headers.entrySet()) { - sb.append("\t\t").append(entry.getKey()).append(": ").append(StringUtils.join(entry.getValue(), ";")).append("\n"); - } - sb.append("\tbody: ").append(ofString()); - return sb.toString(); - } -} diff --git a/develop-toolkit-support/src/main/java/develop/toolkit/support/http/HttpMethod.java b/develop-toolkit-support/src/main/java/develop/toolkit/support/http/HttpMethod.java deleted file mode 100644 index 43adfc0..0000000 --- a/develop-toolkit-support/src/main/java/develop/toolkit/support/http/HttpMethod.java +++ /dev/null @@ -1,12 +0,0 @@ -package develop.toolkit.support.http; - -public enum HttpMethod { - - GET, - - POST, - - PUT, - - DELETE -} diff --git a/develop-toolkit-world/pom.xml b/develop-toolkit-world/pom.xml index f76cecf..c984d84 100644 --- a/develop-toolkit-world/pom.xml +++ b/develop-toolkit-world/pom.xml @@ -3,7 +3,7 @@ develop-toolkit com.github.developframework - 1.0.1-SNAPSHOT + 1.0.7-SNAPSHOT 4.0.0 diff --git a/develop-toolkit-world/src/main/java/develop/toolkit/world/normal/Season.java b/develop-toolkit-world/src/main/java/develop/toolkit/world/normal/Season.java index 2beb725..fca2ed3 100644 --- a/develop-toolkit-world/src/main/java/develop/toolkit/world/normal/Season.java +++ b/develop-toolkit-world/src/main/java/develop/toolkit/world/normal/Season.java @@ -12,6 +12,7 @@ * * @author qiushui on 2019-04-28. */ +@SuppressWarnings("unused") public enum Season { SPRING(1), @@ -23,7 +24,7 @@ public enum Season { WINTER(4); @Getter - private int value; + private final int value; Season(int value) { this.value = value; @@ -31,8 +32,6 @@ public enum Season { /** * 日期范围 - * - * @return */ public TwoValues range() { switch (this) { @@ -67,22 +66,16 @@ public TwoValues range() { /** * 这一天是这个季节的第几天 - * - * @param day - * @return */ public int getDayOfSeason(LocalDate day) { if (isDayAt(MonthDay.from(day), this)) { - return (int) range().getFirstValue().atYear(day.getYear()).until(day, ChronoUnit.DAYS); + return (int) range().getFirstValue().atYear(day.getYear()).until(day, ChronoUnit.DAYS) + 1; } return -1; } /** * 日期落在哪个季节 - * - * @param monthDay - * @return */ public static Season dayAt(MonthDay monthDay) { for (Season season : Season.values()) { @@ -96,10 +89,6 @@ public static Season dayAt(MonthDay monthDay) { /** * 日期是否是某个季节 - * - * @param monthDay - * @param season - * @return */ public static boolean isDayAt(MonthDay monthDay, Season season) { return dayAt(monthDay) == season; @@ -107,9 +96,6 @@ public static boolean isDayAt(MonthDay monthDay, Season season) { /** * 值 - * - * @param value - * @return */ public static Season valueOf(int value) { for (Season season : Season.values()) { diff --git a/develop-toolkit-world/src/main/java/develop/toolkit/world/person/Account.java b/develop-toolkit-world/src/main/java/develop/toolkit/world/person/Account.java index 62593d3..2637e9a 100644 --- a/develop-toolkit-world/src/main/java/develop/toolkit/world/person/Account.java +++ b/develop-toolkit-world/src/main/java/develop/toolkit/world/person/Account.java @@ -9,19 +9,16 @@ * * @author qiushui on 2019-02-26. */ +@SuppressWarnings("unused") public interface Account extends Serializable { /** * 获得账号 - * - * @return */ String getAccount(); /** * 获得密码 - * - * @return */ Password getPassword(); } diff --git a/develop-toolkit-world/src/main/java/develop/toolkit/world/person/IdentificationCard.java b/develop-toolkit-world/src/main/java/develop/toolkit/world/person/IdentificationCard.java index c655ead..85a0cbc 100644 --- a/develop-toolkit-world/src/main/java/develop/toolkit/world/person/IdentificationCard.java +++ b/develop-toolkit-world/src/main/java/develop/toolkit/world/person/IdentificationCard.java @@ -16,6 +16,7 @@ * * @author qiushui on 2019-02-27. */ +@SuppressWarnings("unused") @Getter @Setter @EqualsAndHashCode(of = "card") @@ -55,8 +56,6 @@ public String toString() { /** * 15位身份证 - * - * @return */ public boolean length15() { return card.length() == 15; @@ -64,8 +63,6 @@ public boolean length15() { /** * 18位身份证 - * - * @return */ public boolean length18() { return card.length() == 18; @@ -73,16 +70,13 @@ public boolean length18() { /** * 验证身份证号有效 - * - * @param card - * @return */ public static boolean isValid(String card) { if (card != null && card.matches(Regex.IDENTIFICATION_CARD_RELAXED)) { if (card.length() == 15) { return true; } else { - return card.charAt(17) == computeLastCode(card.substring(0, card.length() - 1)); + return card.toUpperCase().charAt(17) == computeLastCode(card.substring(0, card.length() - 1)); } } return false; @@ -90,8 +84,6 @@ public static boolean isValid(String card) { /** * 解析 - * - * @param regionParser */ public void parse(RegionParser regionParser) { if (regionParser != null) { @@ -113,8 +105,6 @@ public void parse(RegionParser regionParser) { /** * 获得年龄 - * - * @return */ public int getAge() { LocalDate now = LocalDate.now(); diff --git a/develop-toolkit-world/src/main/java/develop/toolkit/world/person/Sex.java b/develop-toolkit-world/src/main/java/develop/toolkit/world/person/Sex.java index 0125b41..3ab2b57 100644 --- a/develop-toolkit-world/src/main/java/develop/toolkit/world/person/Sex.java +++ b/develop-toolkit-world/src/main/java/develop/toolkit/world/person/Sex.java @@ -5,6 +5,7 @@ * * @author qiushui on 2019-02-26. */ +@SuppressWarnings("unused") public enum Sex { MALE, FEMALE, UNKNOWN diff --git a/develop-toolkit-world/src/main/java/develop/toolkit/world/verify/CarPlateVerify.java b/develop-toolkit-world/src/main/java/develop/toolkit/world/verify/CarPlateVerify.java new file mode 100644 index 0000000..e2475a0 --- /dev/null +++ b/develop-toolkit-world/src/main/java/develop/toolkit/world/verify/CarPlateVerify.java @@ -0,0 +1,73 @@ +package develop.toolkit.world.verify; + +/** + * 车牌验证 + * + * @author qiushui on 2020-05-12. + */ +public final class CarPlateVerify { + + /** + * 参考https://my.oschina.net/chenyoca/blog/1571062 + */ + public static boolean checkValid(String plate) { + final String PROVINCES = "京津晋冀蒙辽吉黑沪苏浙皖闽赣鲁豫鄂湘粤桂琼渝川贵云藏陕甘青宁新"; + String provinceShortName = String.valueOf(plate.charAt(0)); + if (!PROVINCES.contains(provinceShortName)) { + return false; + } + if (plate.length() == 7) { + return validNormal(plate); + } else if (plate.length() == 8) { + return validNewEnergy(plate); + } else { + return false; + } + } + + /** + * 宽松的验证 + */ + public static boolean checkValidRelaxed(String plate) { + return plate.matches("^[京津晋冀蒙辽吉黑沪苏浙皖闽赣鲁豫鄂湘粤桂琼渝川贵云藏陕甘青宁新][A-Z].{5,6}$"); + } + + /** + * 普通民用车牌 + */ + private static boolean validNormal(String plate) { + final String PLATE_CHARS_ORG = "ABCDEFGHJKLMNPQRSTUVWXYZ"; + final String PLATE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ0123456789"; + if (PLATE_CHARS_ORG.contains(String.valueOf(plate.charAt(1)))) { + for (int i = 2; i < 7; i++) { + String ch = String.valueOf(plate.charAt(i)); + if (!PLATE_CHARS.contains(ch)) { + return i == 6 && "学挂".contains(ch); + } + } + return true; + } + return false; + } + + /** + * 新能源车牌 + */ + private static boolean validNewEnergy(String plate) { + final String PLATE_CHARS_ORG = "ABCDEFGHJKLMNPQRSTUVWXYZ"; + if (PLATE_CHARS_ORG.contains(String.valueOf(plate.charAt(1)))) { + if ( + "123456789DF".contains(String.valueOf(plate.charAt(2))) && + "ABCDEFGHJKLMNPQRSTUVWXYZ123456789".contains(String.valueOf(plate.charAt(3))) && + "0123456789DF".contains(String.valueOf(plate.charAt(7))) + ) { + for (int i = 4; i < 6; i++) { + if ("0123456789".contains(String.valueOf(plate.charAt(i)))) { + return true; + } + } + } + } + return false; + } +} diff --git a/develop-toolkit-world/src/main/java/develop/toolkit/world/verify/Regex.java b/develop-toolkit-world/src/main/java/develop/toolkit/world/verify/Regex.java index 08e0014..2dc275a 100644 --- a/develop-toolkit-world/src/main/java/develop/toolkit/world/verify/Regex.java +++ b/develop-toolkit-world/src/main/java/develop/toolkit/world/verify/Regex.java @@ -5,6 +5,7 @@ * * @author qiushui on 2019-02-26. */ +@SuppressWarnings("unused") public interface Regex { // 不严格的手机号 diff --git a/develop-toolkit-world/src/main/java/develop/toolkit/world/verify/WorldVerify.java b/develop-toolkit-world/src/main/java/develop/toolkit/world/verify/WorldVerify.java index 4339542..b2bac6c 100644 --- a/develop-toolkit-world/src/main/java/develop/toolkit/world/verify/WorldVerify.java +++ b/develop-toolkit-world/src/main/java/develop/toolkit/world/verify/WorldVerify.java @@ -7,13 +7,11 @@ * * @author qiushui on 2019-02-26. */ +@SuppressWarnings("unused") public final class WorldVerify { /** * 不严格验证手机号 - * - * @param mobile - * @return */ public static boolean isMobileRelaxed(String mobile) { return mobile != null && mobile.matches(Regex.MOBILE_RELAXED); @@ -21,9 +19,6 @@ public static boolean isMobileRelaxed(String mobile) { /** * 不严格验证身份证 - * - * @param card - * @return */ public static boolean isIdentificationCardRelaxed(String card) { return card != null && card.matches(Regex.IDENTIFICATION_CARD_RELAXED); @@ -31,9 +26,6 @@ public static boolean isIdentificationCardRelaxed(String card) { /** * 严格验证身份证 - * - * @param card - * @return */ public static boolean isIdentificationCard(String card) { return IdentificationCard.isValid(card); diff --git a/pom.xml b/pom.xml index 9ff0ea0..5656813 100644 --- a/pom.xml +++ b/pom.xml @@ -1,207 +1,222 @@ - - - 4.0.0 - - com.github.developframework - develop-toolkit - pom - 1.0.1-SNAPSHOT - 开发工具箱 - 2018 - Develop Toolkit - https://github.com/developframework/develop-toolkit - - - develop-toolkit-base - develop-toolkit-support - develop-toolkit-world - - - - 1.18.10 - 1.7.28 - 1.3.2 - - - - - - com.github.developframework - develop-toolkit-base - 1.0.1-SNAPSHOT - - - com.github.developframework - develop-toolkit-support - 1.0.1-SNAPSHOT - - - com.github.developframework - develop-toolkit-world - 1.0.1-SNAPSHOT - - - org.projectlombok - lombok - ${version.lombok} - - - org.slf4j - slf4j-api - ${version.slf4j-api} - - - com.github.developframework - expression - ${version.expression} - - - - - - - qiuzhenhao - 408000511@qq.com - developframework - http://blog.qiushuicloud.xyz - - - - - GitHub Issues - https://github.com/developframework/develop-toolkit - - - - - The Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - - - - - scm:git:git@github.com:developframework/develop-toolkit.git - scm:git:git@github.com:developframework/develop-toolkit.git - https://github.com/developframework/develop-toolkit - v1.0.0 - - - - - - - - - - - - - - - - - hclc nexus - http://nexus.hclc-tech.com:8081/repository/maven-snapshots/ - - - hclc nexus - http://nexus.hclc-tech.com:8081/repository/maven-releases/ - - - - - - - - maven-source-plugin - 3.0.1 - - - attach-sources - verify - - jar-no-fork - - - - - - maven-release-plugin - - v@{project.version} - true - - - - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - - - - - - maven-source-plugin - - - maven-compiler-plugin - 3.8.0 - - - - org.projectlombok - lombok - ${version.lombok} - - - - - - - - - - release - - - - performRelease - true - - - - - - - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - none - - - - - - + + + 4.0.0 + + com.github.developframework + develop-toolkit + pom + 1.0.7-SNAPSHOT + 开发工具箱 + 2018 + Develop Toolkit + https://github.com/developframework/develop-toolkit + + + develop-toolkit-base + develop-toolkit-db + develop-toolkit-world + develop-toolkit-multimedia + develop-toolkit-mybatis + + + + 11 + 11 + UTF-8 + + 1.18.26 + 1.7.30 + 1.6.0 + 2.11.3 + 2.16.0 + + + + + + com.github.developframework + develop-toolkit-base + 1.0.7-SNAPSHOT + + + com.github.developframework + develop-toolkit-db + 1.0.7-SNAPSHOT + + + com.github.developframework + develop-toolkit-world + 1.0.7-SNAPSHOT + + + com.github.developframework + develop-toolkit-multimedia + 1.0.7-SNAPSHOT + + + com.github.developframework + develop-toolkit-mybatis + 1.0.7-SNAPSHOT + + + org.projectlombok + lombok + ${version.lombok} + + + org.slf4j + slf4j-api + ${version.slf4j-api} + + + com.github.developframework + expression + ${version.expression} + + + com.fasterxml.jackson.core + jackson-databind + ${version.jackson} + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${version.jackson} + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${version.jackson} + + + com.drewnoakes + metadata-extractor + ${version.metadata-extractor} + + + + + + + qiuzhenhao + 408000511@qq.com + developframework + http://blog.qiushuicloud.xyz + + + + + GitHub Issues + https://github.com/developframework/develop-toolkit + + + + + The Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + scm:git:git@github.com:developframework/develop-toolkit.git + scm:git:git@github.com:developframework/develop-toolkit.git + https://github.com/developframework/develop-toolkit + HEAD + + + + + + + maven-source-plugin + 3.1.0 + + + attach-sources + verify + + jar-no-fork + + + + + + maven-release-plugin + + v@{project.version} + true + + + + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + + + maven-source-plugin + + + maven-compiler-plugin + 3.8.1 + + + + org.projectlombok + lombok + ${version.lombok} + + + + + + + + + + release + + + + performRelease + true + + + + + + + maven-gpg-plugin + + + maven-javadoc-plugin + + none + + + + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + \ No newline at end of file