From a5a532bc82cdc9b336006b04beac5282d1059ca9 Mon Sep 17 00:00:00 2001 From: minglipro Date: Thu, 5 Feb 2026 11:42:52 +0800 Subject: [PATCH 1/2] no message --- build.gradle.kts | 25 +- gradle.properties | 4 +- jdk8/build.gradle.kts | 3 +- .../mingliqiye/utils/stream/SuperStream.java | 938 ++++++++++-------- .../com/mingliqiye/utils/array/ArrayUtils.kt | 388 ++++++++ .../JsonException.kt => base/BaseType.kt} | 20 +- .../com/mingliqiye/utils/clone/CloneUtils.kt | 11 +- .../mingliqiye/utils/concurrent/IsChanged.kt | 17 +- .../com/mingliqiye/utils/file/FileUtils.kt | 23 +- .../mingliqiye/utils/functions/Functions.kt | 609 +++++++++++- .../mingliqiye/utils/functions/Pipeline.kt | 272 +++++ .../com/mingliqiye/utils/http/Response.kt | 235 ++++- .../utils/http/exception/Exceptions.kt | 372 +++++++ src/main/kotlin/com/mingliqiye/utils/io/IO.kt | 231 ++++- .../com/mingliqiye/utils/json/GsonJsonApi.kt | 231 ----- .../com/mingliqiye/utils/json/api/JSONA.kt | 644 ++++++++++++ .../utils/json/{ => api}/JacksonJsonApi.kt | 94 +- .../utils/json/{ => api/base}/JsonApi.kt | 231 ++++- .../utils/json/api/exception/JsonException.kt | 56 ++ .../json/{ => api/type}/JsonTypeReference.kt | 29 +- .../json/{ => api/type}/JsonTypeUtils.kt | 6 +- .../utils/json/api/type/TypeReference.kt | 76 ++ .../json/converters/DateTimeJsonConverter.kt | 102 ++ .../json/converters/JsonStringConverter.kt | 251 ----- .../json/converters/UUIDJsonConverter.kt | 101 ++ .../json/converters/base/AnnotationGetter.kt | 62 ++ .../base/BaseJsonBigDecimalConverter.kt | 49 + .../base/BaseJsonBigIntegerConverter.kt | 45 + .../base/BaseJsonBooleanConverter.kt | 46 + .../converters/base/BaseJsonByteConverter.kt | 43 + .../json/converters/base/BaseJsonConverter.kt | 210 ++++ .../base/BaseJsonDoubleConverter.kt | 42 + .../converters/base/BaseJsonFloatConverter.kt | 46 + .../converters/base/BaseJsonIntConverter.kt | 46 + .../converters/base/BaseJsonLongConverter.kt | 43 + .../converters/base/BaseJsonShortConverter.kt | 46 + .../base/BaseJsonStringConverter.kt | 46 + .../json/converters/base/JsonConverter.kt | 59 ++ .../com/mingliqiye/utils/logger/Loggers.kt | 69 +- .../utils/logger/MingLiLoggerFactory.kt | 106 ++ .../com/mingliqiye/utils/math/MathUtils.kt | 374 +++++++ .../utils/mybatis/QuickBaseTypeHandler.kt | 14 +- .../datetime/DateTimeTypeHandler.kt | 25 +- .../mysql/uuid/MysqlUUIDBinaryTypeHandler.kt | 28 +- .../pgsql/uuid/PgsqlUUIDTypeHandler.kt | 63 ++ .../utils/mybatisplus/BaseMapperQuery.kt | 124 +++ .../utils/mybatisplus/QueryWrapper.kt | 72 -- .../utils/netty/ClientScheduleReconnect.kt | 144 ++- .../utils/netty/NamedNioEventLoopGroup.kt | 47 + .../utils/netty/NamedThreadFactory.kt | 56 +- .../com/mingliqiye/utils/netty/NettyUtils.kt | 211 +++- .../mingliqiye/utils/network/AddressPort.kt | 509 ---------- .../utils/network/NetworkAddress.kt | 247 +++++ .../utils/network/NetworkEndpoint.kt | 206 ++++ .../utils/network/NetworkException.kt | 39 + .../mingliqiye/utils/network/NetworkPort.kt | 91 ++ .../com/mingliqiye/utils/objects/NullUtils.kt | 53 + .../com/mingliqiye/utils/path/OsPath.kt | 9 + .../mingliqiye/utils/random/RandomBytes.kt | 13 +- .../com/mingliqiye/utils/random/RandomInt.kt | 21 +- .../mingliqiye/utils/random/RandomString.kt | 4 +- .../com/mingliqiye/utils/request/Require.kt | 225 ----- .../com/mingliqiye/utils/require/Require.kt | 200 ++++ .../RequireLayzExceptionConstructor.kt} | 14 +- .../require/RequireLayzMessageConstructor.kt | 28 + .../utils/resource/ResourceUtils.kt | 152 ++- .../mingliqiye/utils/security/SecureUtils.kt | 41 +- .../com/mingliqiye/utils/sleep/SleepUtils.kt | 79 ++ .../autoconfigure/AutoConfiguration.kt | 47 +- .../autoconfigure/GsonAutoConfiguration.kt | 91 -- .../autoconfigure/JacksonAutoConfiguration.kt | 94 +- .../utils/springboot/bean/SpringBeanUtils.kt | 13 +- .../converters/DateTimeToStringConverter.kt | 35 + ...erters.kt => StringToDateTimeConverter.kt} | 29 +- .../converters/StringToUUIDConverter.kt} | 24 +- .../converters/UUIDToStringConverter.kt | 34 + .../mingliqiye/utils/string/StringUtils.kt | 98 +- .../com/mingliqiye/utils/system/SystemUtil.kt | 110 +- .../mingliqiye/utils/thread/ThreadEvent.kt | 112 +++ .../mingliqiye/utils/thread/ThreadRunner.kt | 110 ++ .../mingliqiye/utils/thread/ThreadUtils.kt | 43 + .../com/mingliqiye/utils/time/DateTime.kt | 172 +--- .../utils/time/DateTimeJsonFormat.kt | 30 + .../mingliqiye/utils/time/DateTimeOffset.kt | 61 ++ .../com/mingliqiye/utils/time/Formatter.kt | 108 ++ .../kotlin/com/mingliqiye/utils/uuid/UUID.kt | 28 +- .../mingliqiye/utils/uuid/UUIDFormatType.kt | 30 + .../mingliqiye/utils/uuid/UUIDJsonFormat.kt | 31 + ...ot.autoconfigure.AutoConfiguration.imports | 5 +- .../kotlin/com/mingliqiye/utils/JsonTest.kt | 82 ++ 90 files changed, 7945 insertions(+), 2458 deletions(-) create mode 100644 src/main/kotlin/com/mingliqiye/utils/array/ArrayUtils.kt rename src/main/kotlin/com/mingliqiye/utils/{json/JsonException.kt => base/BaseType.kt} (64%) create mode 100644 src/main/kotlin/com/mingliqiye/utils/functions/Pipeline.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/http/exception/Exceptions.kt delete mode 100644 src/main/kotlin/com/mingliqiye/utils/json/GsonJsonApi.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/api/JSONA.kt rename src/main/kotlin/com/mingliqiye/utils/json/{ => api}/JacksonJsonApi.kt (79%) rename src/main/kotlin/com/mingliqiye/utils/json/{ => api/base}/JsonApi.kt (59%) create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/api/exception/JsonException.kt rename src/main/kotlin/com/mingliqiye/utils/json/{ => api/type}/JsonTypeReference.kt (87%) rename src/main/kotlin/com/mingliqiye/utils/json/{ => api/type}/JsonTypeUtils.kt (98%) create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/api/type/TypeReference.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/DateTimeJsonConverter.kt delete mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/JsonStringConverter.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/UUIDJsonConverter.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/base/AnnotationGetter.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonBigDecimalConverter.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonBigIntegerConverter.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonBooleanConverter.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonByteConverter.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonConverter.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonDoubleConverter.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonFloatConverter.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonIntConverter.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonLongConverter.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonShortConverter.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonStringConverter.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/base/JsonConverter.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/logger/MingLiLoggerFactory.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/math/MathUtils.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/mybatis/typehandler/pgsql/uuid/PgsqlUUIDTypeHandler.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/mybatisplus/BaseMapperQuery.kt delete mode 100644 src/main/kotlin/com/mingliqiye/utils/mybatisplus/QueryWrapper.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/netty/NamedNioEventLoopGroup.kt delete mode 100644 src/main/kotlin/com/mingliqiye/utils/network/AddressPort.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/network/NetworkAddress.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/network/NetworkEndpoint.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/network/NetworkException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/network/NetworkPort.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/objects/NullUtils.kt delete mode 100644 src/main/kotlin/com/mingliqiye/utils/request/Require.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/require/Require.kt rename src/main/kotlin/com/mingliqiye/utils/{main/Main.kt => require/RequireLayzExceptionConstructor.kt} (74%) create mode 100644 src/main/kotlin/com/mingliqiye/utils/require/RequireLayzMessageConstructor.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/sleep/SleepUtils.kt delete mode 100644 src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/GsonAutoConfiguration.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/springboot/converters/DateTimeToStringConverter.kt rename src/main/kotlin/com/mingliqiye/utils/springboot/converters/{Converters.kt => StringToDateTimeConverter.kt} (62%) rename src/main/kotlin/com/mingliqiye/utils/{json/converters/JsonConverter.kt => springboot/converters/StringToUUIDConverter.kt} (59%) create mode 100644 src/main/kotlin/com/mingliqiye/utils/springboot/converters/UUIDToStringConverter.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/thread/ThreadEvent.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/thread/ThreadRunner.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/thread/ThreadUtils.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/time/DateTimeJsonFormat.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/time/DateTimeOffset.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/time/Formatter.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/uuid/UUIDFormatType.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/uuid/UUIDJsonFormat.kt create mode 100644 src/test/kotlin/com/mingliqiye/utils/JsonTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 22830a5..b5ad80c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils * CurrentFile build.gradle.kts - * LastUpdate 2026-01-14 13:01:44 + * LastUpdate 2026-02-05 11:04:04 * UpdateUser MingLiPro */ @@ -59,6 +59,10 @@ sourceSets { } } +tasks.test { + useJUnitPlatform() +} + java { withSourcesJar() toolchain.languageVersion.set(JavaLanguageVersion.of(8)) @@ -67,19 +71,24 @@ java { dependencies { implementation("org.slf4j:slf4j-api:2.0.17") - implementation("com.mingliqiye.utils.jna:WinKernel32Api:1.0.1") compileOnly("org.mindrot:jbcrypt:0.4") - compileOnly("org.springframework.boot:spring-boot-starter:2.7.14") - compileOnly("com.fasterxml.jackson.core:jackson-databind:2.19.2") - compileOnly("com.google.code.gson:gson:2.13.1") + compileOnly("com.squareup.okhttp3:okhttp:5.3.2") + compileOnly("com.fasterxml.jackson.core:jackson-databind:2.21.0") + compileOnly("com.fasterxml.jackson.module:jackson-module-kotlin:2.21.0") + compileOnly("org.springframework.boot:spring-boot-starter-web:2.7.18") compileOnly("org.mybatis:mybatis:3.5.19") - compileOnly("com.alibaba.fastjson2:fastjson2:2.0.58") compileOnly("io.netty:netty-all:4.1.130.Final") - compileOnly("com.baomidou:mybatis-plus-core:3.5.15") compileOnly("net.java.dev.jna:jna:5.17.0") + compileOnly("com.baomidou:mybatis-plus-extension:3.5.15") + testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") +// testImplementation("com.squareup.okhttp3:okhttp:5.3.2") + testImplementation("com.mingliqiye.logger:logger-log4j2:1.0.5") + testImplementation("com.fasterxml.jackson.core:jackson-databind:2.21.0") + testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.21.0") } @@ -126,6 +135,8 @@ tasks.withType { } val isJdk8Build = project.findProperty("buildForJdk8") == "true" + + repositories { maven { url = uri("https://maven.aliyun.com/repository/public/") diff --git a/gradle.properties b/gradle.properties index fdcb12e..6f73be9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,13 +16,13 @@ # ProjectName mingli-utils # ModuleName mingli-utils # CurrentFile gradle.properties -# LastUpdate 2026-01-14 13:01:41 +# LastUpdate 2026-02-05 11:06:50 # UpdateUser MingLiPro # JDKVERSIONS=1.8 GROUPSID=com.mingliqiye.utils ARTIFACTID=mingli-utils -VERSIONS=4.3.5 +VERSIONS=4.6.0 signing.keyId=B22AA93B signing.password= signing.secretKeyRingFile=secret.gpg diff --git a/jdk8/build.gradle.kts b/jdk8/build.gradle.kts index 13cc797..4a603b0 100644 --- a/jdk8/build.gradle.kts +++ b/jdk8/build.gradle.kts @@ -24,8 +24,6 @@ plugins { id("java-library") id("maven-publish") signing - kotlin("jvm") version "2.2.20" - id("org.jetbrains.dokka") version "2.0.0" } val GROUPSID = project.properties["GROUPSID"] as String val VERSIONS = project.properties["VERSIONS"] as String @@ -84,6 +82,7 @@ publishing { } java.toolchain.languageVersion.set(JavaLanguageVersion.of(8)) + dependencies { api(rootProject) implementation("com.mingliqiye.utils.jna:WinKernel32Platform:1.0.1") diff --git a/src/main/java/com/mingliqiye/utils/stream/SuperStream.java b/src/main/java/com/mingliqiye/utils/stream/SuperStream.java index 1d71c41..3b440a5 100644 --- a/src/main/java/com/mingliqiye/utils/stream/SuperStream.java +++ b/src/main/java/com/mingliqiye/utils/stream/SuperStream.java @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile SuperStream.java - * LastUpdate 2025-09-21 14:22:13 + * LastUpdate 2026-01-20 13:27:09 * UpdateUser MingLiPro */ @@ -36,641 +36,735 @@ import java.util.stream.*; /** - * SuperStream 是对 Java 原生 Stream 的增强封装类,提供更便捷的流式操作接口。 - * 它支持链式调用,并扩展了部分原生 Stream 不支持的功能,例如带索引的 forEach 操作。 - * - * @param 流中元素的类型 + * SuperStream是一个增强版的Stream包装器,提供了更多的便捷方法和重载方法来处理各种数据类型的流操作。 + * 它继承了Stream接口并实现了所有Stream的方法,同时添加了一些额外的便利功能。 */ public class SuperStream implements Stream { private final Stream stream; + /** - * 构造方法,将传入的 Stream 包装为 SuperStream。 + * 构造一个新的SuperStream实例 * - * @param stream 要包装的原始 Stream + * @param stream 被包装的基础Stream对象 */ private SuperStream(Stream stream) { this.stream = stream; } /** - * 将指定的 Stream 包装为 SuperStream。 + * 创建一个包含指定整数元素的SuperStream * - * @param stream 原始 Stream + * @param args 可变参数的整数值数组 + * @return 包含指定整数的SuperStream实例 + */ + public static SuperStream of(int... args) { + return of(Collections.newArrayLists(args)); + } + + /** + * 创建一个包含指定字节元素的SuperStream + * + * @param args 可变参数的字节数组 + * @return 包含指定字节的SuperStream实例 + */ + public static SuperStream of(byte... args) { + return of(Collections.newArrayLists(args)); + } + + /** + * 创建一个包含指定短整数元素的SuperStream + * + * @param args 可变参数的短整数数组 + * @return 包含指定短整数的SuperStream实例 + */ + public static SuperStream of(short... args) { + return of(Collections.newArrayLists(args)); + } + + /** + * 创建一个包含指定字符元素的SuperStream + * + * @param args 可变参数的字符数组 + * @return 包含指定字符的SuperStream实例 + */ + public static SuperStream of(char... args) { + return of(Collections.newArrayLists(args)); + } + + /** + * 创建一个包含指定长整数元素的SuperStream + * + * @param args 可变参数的长整数数组 + * @return 包含指定长整数的SuperStream实例 + */ + public static SuperStream of(long... args) { + return of(Collections.newArrayLists(args)); + } + + /** + * 创建一个包含指定浮点数元素的SuperStream + * + * @param args 可变参数的浮点数数组 + * @return 包含指定浮点数的SuperStream实例 + */ + public static SuperStream of(float... args) { + return of(Collections.newArrayLists(args)); + } + + /** + * 创建一个包含指定双精度浮点数元素的SuperStream + * + * @param args 可变参数的双精度浮点数数组 + * @return 包含指定双精度浮点数的SuperStream实例 + */ + public static SuperStream of(double... args) { + return of(Collections.newArrayLists(args)); + } + + + /** + * 将现有的Stream包装成SuperStream + * + * @param stream 需要包装的Stream对象 * @param 流中元素的类型 - * @return 包装后的 SuperStream 实例 + * @return 包装后的SuperStream实例 */ public static SuperStream of(Stream stream) { return new SuperStream<>(stream); } + /** + * 创建一个包含指定数组元素的SuperStream,支持并行处理 + * + * @param ts 元素数组 + * @param parallel 是否使用并行流 + * @param 数组元素的类型 + * @return 包含指定数组元素的SuperStream实例 + */ public static SuperStream of(T[] ts, boolean parallel) { return of(Collections.newArrayLists(ts), parallel); } + /** + * 创建一个包含指定数组元素的SuperStream(默认串行) + * + * @param ts 元素数组 + * @param 数组元素的类型 + * @return 包含指定数组元素的SuperStream实例 + */ public static SuperStream of(T[] ts) { return of(ts, false); } + /** - * 将指定的 Collection 转换为 SuperStream,默认为顺序流。 + * 创建一个包含指定集合元素的SuperStream(默认串行) * - * @param collection 原始集合 - * @param 集合中元素的类型 - * @return 包装后的 SuperStream 实例 + * @param collection 包含元素的集合 + * @param 集合元素的类型 + * @return 包含指定集合元素的SuperStream实例 */ public static SuperStream of(Collection collection) { return of(collection, false); } + /** - * 将指定的 Map 转换为 SuperStream,默认为顺序流。 + * 创建一个包含指定Map键值对Entry的SuperStream(默认串行) * - * @param map 原始 Map - * @param Map 键的类型 - * @param Map 值的类型 - * @return 包含 Map.Entry 的 SuperStream 实例 + * @param map 源Map对象 + * @param Map的键类型 + * @param Map的值类型 + * @return 包含Map Entry的SuperStream实例 */ public static SuperStream> of(Map map) { return of(map.entrySet(), false); } + /** - * 将指定的 Map 转换为 SuperStream,可选择是否为并行流。 + * 创建一个包含指定Map键值对Entry的SuperStream,支持并行处理 * - * @param map 原始 Map + * @param map 源Map对象 * @param parallel 是否使用并行流 - * @param Map 键的类型 - * @param Map 值的类型 - * @return 包含 Map.Entry 的 SuperStream 实例 + * @param Map的键类型 + * @param Map的值类型 + * @return 包含Map Entry的SuperStream实例 */ public static SuperStream> of(Map map, boolean parallel) { return SuperStream.of(map.entrySet(), parallel); } + /** - * 将指定的 Collection 转换为 SuperStream,可选择是否为并行流。 + * 创建一个包含指定集合元素的SuperStream,支持并行处理 * - * @param stream 原始集合 + * @param stream 包含元素的集合 * @param parallel 是否使用并行流 - * @param 集合中元素的类型 - * @return 包装后的 SuperStream 实例 + * @param 集合元素的类型 + * @return 包含指定集合元素的SuperStream实例 */ public static SuperStream of(Collection stream, boolean parallel) { return parallel ? SuperStream.of(stream.parallelStream()) : SuperStream.of(stream.stream()); } + /** - * 返回一个用于收集 Double 类型统计信息的 Collector。 + * 返回一个收集器,该收集器产生一个DoubleSummaryStatistics,描述输入元素的统计信息 * - * @param mapper 映射函数,将元素转换为 double 值 - * @param 元素类型 - * @return Collector 实例 + * @param mapper 用于将输入元素映射到double值的函数 + * @param 输入元素的类型 + * @return 产生DoubleSummaryStatistics的收集器 */ public static Collector summarizingDouble(@NotNull ToDoubleFunction mapper) { return Collectors.summarizingDouble(mapper); } + /** - * 返回一个用于收集 Long 类型统计信息的 Collector。 + * 返回一个收集器,该收集器产生一个LongSummaryStatistics,描述输入元素的统计信息 * - * @param mapper 映射函数,将元素转换为 long 值 - * @param 元素类型 - * @return Collector 实例 + * @param mapper 用于将输入元素映射到long值的函数 + * @param 输入元素的类型 + * @return 产生LongSummaryStatistics的收集器 */ public static Collector summarizingLong(@NotNull ToLongFunction mapper) { return Collectors.summarizingLong(mapper); } + /** - * 返回一个用于收集 Integer 类型统计信息的 Collector。 + * 返回一个收集器,该收集器产生一个IntSummaryStatistics,描述输入元素的统计信息 * - * @param mapper 映射函数,将元素转换为 int 值 - * @param 元素类型 - * @return Collector 实例 + * @param mapper 用于将输入元素映射到int值的函数 + * @param 输入元素的类型 + * @return 产生IntSummaryStatistics的收集器 */ public static Collector summarizingInt(@NotNull ToIntFunction mapper) { return Collectors.summarizingInt(mapper); } + /** - * 返回一个用于收集元素到 ConcurrentMap 的 Collector,支持自定义合并策略和 Map 工厂。 + * 返回一个收集器,该收集器将输入元素累积到ConcurrentMap中 * - * @param keyMapper 键映射函数 - * @param valueMapper 值映射函数 - * @param mergeFunction 合并函数 - * @param mapSupplier Map 工厂 - * @param 元素类型 - * @param 键类型 - * @param 值类型 - * @param Map 类型 - * @return Collector 实例 + * @param keyMapper 用于产生键的映射函数 + * @param valueMapper 用于产生值的映射函数 + * @param mergeFunction 当键冲突时合并值的函数 + * @param mapSupplier 用于创建新Map的供应函数 + * @param 输入元素的类型 + * @param 键的类型 + * @param 值的类型 + * @param Map的具体类型 + * @return 产生ConcurrentMap的收集器 */ - public static > Collector toConcurrentMap( - @NotNull Function keyMapper, - @NotNull Function valueMapper, - @NotNull BinaryOperator mergeFunction, - @NotNull Supplier mapSupplier) { + public static > Collector toConcurrentMap(@NotNull Function keyMapper, @NotNull Function valueMapper, @NotNull BinaryOperator mergeFunction, @NotNull Supplier mapSupplier) { return Collectors.toConcurrentMap(keyMapper, valueMapper, mergeFunction, mapSupplier); } + /** - * 返回一个用于收集元素到 ConcurrentMap 的 Collector,支持自定义合并策略。 + * 返回一个收集器,该收集器将输入元素累积到ConcurrentMap中,使用BinaryOperator处理键冲突 * - * @param keyMapper 键映射函数 - * @param valueMapper 值映射函数 - * @param mergeFunction 合并函数 - * @param 元素类型 - * @param 键类型 - * @param 值类型 - * @return Collector 实例 + * @param keyMapper 用于产生键的映射函数 + * @param valueMapper 用于产生值的映射函数 + * @param mergeFunction 当键冲突时合并值的函数 + * @param 输入元素的类型 + * @param 键的类型 + * @param 值的类型 + * @return 产生ConcurrentMap的收集器 */ - public static Collector> toConcurrentMap( - @NotNull Function keyMapper, - @NotNull Function valueMapper, - @NotNull BinaryOperator mergeFunction) { + public static Collector> toConcurrentMap(@NotNull Function keyMapper, @NotNull Function valueMapper, @NotNull BinaryOperator mergeFunction) { return Collectors.toConcurrentMap(keyMapper, valueMapper, mergeFunction); } + /** - * 返回一个用于收集元素到 ConcurrentMap 的 Collector。 + * 返回一个收集器,该收集器将输入元素累积到ConcurrentMap中,使用HashMap.merge处理键冲突 * - * @param keyMapper 键映射函数 - * @param valueMapper 值映射函数 - * @param 元素类型 - * @param 键类型 - * @param 值类型 - * @return Collector 实例 + * @param keyMapper 用于产生键的映射函数 + * @param valueMapper 用于产生值的映射函数 + * @param 输入元素的类型 + * @param 键的类型 + * @param 值的类型 + * @return 产生ConcurrentMap的收集器 */ - public static Collector> toConcurrentMap( - @NotNull Function keyMapper, - @NotNull Function valueMapper) { + public static Collector> toConcurrentMap(@NotNull Function keyMapper, @NotNull Function valueMapper) { return Collectors.toConcurrentMap(keyMapper, valueMapper); } + /** - * 返回一个用于收集元素到 Map 的 Collector,支持自定义合并策略和 Map 工厂。 + * 返回一个收集器,该收集器将输入元素累积到Map中 * - * @param keyMapper 键映射函数 - * @param valueMapper 值映射函数 - * @param mergeFunction 合并函数 - * @param mapSupplier Map 工厂 - * @param 元素类型 - * @param 键类型 - * @param 值类型 - * @param Map 类型 - * @return Collector 实例 + * @param keyMapper 用于产生键的映射函数 + * @param valueMapper 用于产生值的映射函数 + * @param mergeFunction 当键冲突时合并值的函数 + * @param mapSupplier 用于创建新Map的供应函数 + * @param 输入元素的类型 + * @param 键的类型 + * @param 值的类型 + * @param Map的具体类型 + * @return 产生Map的收集器 */ - public static > Collector toMap( - @NotNull Function keyMapper, - @NotNull Function valueMapper, - @NotNull BinaryOperator mergeFunction, - @NotNull Supplier mapSupplier) { + public static > Collector toMap(@NotNull Function keyMapper, @NotNull Function valueMapper, @NotNull BinaryOperator mergeFunction, @NotNull Supplier mapSupplier) { return Collectors.toMap(keyMapper, valueMapper, mergeFunction, mapSupplier); } + /** - * 返回一个用于收集元素到 Map 的 Collector,支持自定义合并策略。 + * 返回一个收集器,该收集器将输入元素累积到Map中,使用BinaryOperator处理键冲突 * - * @param keyMapper 键映射函数 - * @param valueMapper 值映射函数 - * @param mergeFunction 合并函数 - * @param 元素类型 - * @param 键类型 - * @param 值类型 - * @return Collector 实例 + * @param keyMapper 用于产生键的映射函数 + * @param valueMapper 用于产生值的映射函数 + * @param mergeFunction 当键冲突时合并值的函数 + * @param 输入元素的类型 + * @param 键的类型 + * @param 值的类型 + * @return 产生Map的收集器 */ - public static Collector> toMap( - @NotNull Function keyMapper, - @NotNull Function valueMapper, - @NotNull BinaryOperator mergeFunction) { + public static Collector> toMap(@NotNull Function keyMapper, @NotNull Function valueMapper, @NotNull BinaryOperator mergeFunction) { return Collectors.toMap(keyMapper, valueMapper, mergeFunction); } + /** - * 返回一个用于收集元素到 Map 的 Collector。 + * 返回一个收集器,该收集器将输入元素累积到Map中,使用HashMap.merge处理键冲突 * - * @param keyMapper 键映射函数 - * @param valueMapper 值映射函数 - * @param 元素类型 - * @param 键类型 - * @param 值类型 - * @return Collector 实例 + * @param keyMapper 用于产生键的映射函数 + * @param valueMapper 用于产生值的映射函数 + * @param 输入元素的类型 + * @param 键的类型 + * @param 值的类型 + * @return 产生Map的收集器 */ - public static Collector> toMap( - @NotNull Function keyMapper, - @NotNull Function valueMapper) { + public static Collector> toMap(@NotNull Function keyMapper, @NotNull Function valueMapper) { return Collectors.toMap(keyMapper, valueMapper); } + /** - * 返回一个用于根据条件将元素分组到两个列表中的 Collector。 + * 返回一个收集器,该收集器根据谓词将输入元素分区到Map中 * - * @param predicate 判断条件 - * @param downstream 下游 Collector - * @param 元素类型 - * @param 下游结果类型 - * @return Collector 实例 + * @param predicate 用于确定元素分区的谓词 + * @param downstream 应用于分区结果的下游收集器 + * @param 输入元素的类型 + * @param 下游收集器的结果类型 + * @param 下游收集器的累加器类型 + * @return 产生分区Map的收集器 */ - public static Collector> partitioningBy( - @NotNull Predicate predicate, - @NotNull Collector downstream) { + public static Collector> partitioningBy(@NotNull Predicate predicate, @NotNull Collector downstream) { return Collectors.partitioningBy(predicate, downstream); } + /** - * 返回一个用于根据条件将元素分组到两个列表中的 Collector。 + * 返回一个收集器,该收集器根据谓词将输入元素分区到Map中 * - * @param predicate 判断条件 - * @param 元素类型 - * @return Collector 实例 + * @param predicate 用于确定元素分区的谓词 + * @param 输入元素的类型 + * @return 产生分区Map的收集器 */ public static Collector>> partitioningBy(@NotNull Predicate predicate) { return Collectors.partitioningBy(predicate); } + /** - * 返回一个用于并发分组的 Collector,支持自定义 Map 工厂和下游 Collector。 + * 返回一个收集器,该收集器将输入元素按分类器分组到ConcurrentMap中 * - * @param classifier 分类函数 - * @param mapFactory Map 工厂 - * @param downstream 下游 Collector - * @param 元素类型 - * @param 键类型 - * @param 下游结果类型 - * @param 下游中间类型 - * @param Map 类型 - * @return Collector 实例 + * @param classifier 用于分类元素的函数 + * @param mapFactory 用于创建Map的工厂函数 + * @param downstream 应用于分组结果的下游收集器 + * @param 输入元素的类型 + * @param 分类键的类型 + * @param 下游收集器的结果类型 + * @param 下游收集器的累加器类型 + * @param Map的具体类型 + * @return 产生ConcurrentMap的收集器 */ - public static > Collector groupingByConcurrent( - @NotNull Function classifier, - @NotNull Supplier mapFactory, - @NotNull Collector downstream) { + public static > Collector groupingByConcurrent(@NotNull Function classifier, @NotNull Supplier mapFactory, @NotNull Collector downstream) { return Collectors.groupingByConcurrent(classifier, mapFactory, downstream); } + /** - * 返回一个用于并发分组的 Collector,支持自定义下游 Collector。 + * 返回一个收集器,该收集器将输入元素按分类器分组到ConcurrentMap中 * - * @param classifier 分类函数 - * @param downstream 下游 Collector - * @param 元素类型 - * @param 键类型 - * @param 下游中间类型 - * @param 下游结果类型 - * @return Collector 实例 + * @param classifier 用于分类元素的函数 + * @param downstream 应用于分组结果的下游收集器 + * @param 输入元素的类型 + * @param 分类键的类型 + * @param 下游收集器的累加器类型 + * @param 下游收集器的结果类型 + * @return 产生ConcurrentMap的收集器 */ - public static Collector> groupingByConcurrent( - @NotNull Function classifier, - @NotNull Collector downstream) { + public static Collector> groupingByConcurrent(@NotNull Function classifier, @NotNull Collector downstream) { return Collectors.groupingByConcurrent(classifier, downstream); } + /** - * 返回一个用于并发分组的 Collector。 + * 返回一个收集器,该收集器将输入元素按分类器分组到ConcurrentMap中 * - * @param classifier 分类函数 - * @param 元素类型 - * @param 键类型 - * @return Collector 实例 + * @param classifier 用于分类元素的函数 + * @param 输入元素的类型 + * @param 分类键的类型 + * @return 产生ConcurrentMap的收集器 */ - public static Collector>> groupingByConcurrent( - @NotNull Function classifier) { + public static Collector>> groupingByConcurrent(@NotNull Function classifier) { return Collectors.groupingByConcurrent(classifier); } + /** - * 返回一个用于分组的 Collector,支持自定义 Map 工厂和下游 Collector。 + * 返回一个收集器,该收集器将输入元素按分类器分组到Map中 * - * @param classifier 分类函数 - * @param mapFactory Map 工厂 - * @param downstream 下游 Collector - * @param 元素类型 - * @param 键类型 - * @param 下游结果类型 - * @param 下游中间类型 - * @param Map 类型 - * @return Collector 实例 + * @param classifier 用于分类元素的函数 + * @param mapFactory 用于创建Map的工厂函数 + * @param downstream 应用于分组结果的下游收集器 + * @param 输入元素的类型 + * @param 分类键的类型 + * @param 下游收集器的结果类型 + * @param 下游收集器的累加器类型 + * @param Map的具体类型 + * @return 产生Map的收集器 */ - public static > Collector groupingBy( - @NotNull Function classifier, - @NotNull Supplier mapFactory, - @NotNull Collector downstream) { + public static > Collector groupingBy(@NotNull Function classifier, @NotNull Supplier mapFactory, @NotNull Collector downstream) { return Collectors.groupingBy(classifier, mapFactory, downstream); } + /** - * 返回一个用于分组的 Collector,支持自定义下游 Collector。 + * 返回一个收集器,该收集器将输入元素按分类器分组到Map中 * - * @param classifier 分类函数 - * @param downstream 下游 Collector - * @param 元素类型 - * @param 键类型 - * @param 下游中间类型 - * @param 下游结果类型 - * @return Collector 实例 + * @param classifier 用于分类元素的函数 + * @param downstream 应用于分组结果的下游收集器 + * @param 输入元素的类型 + * @param 分类键的类型 + * @param 下游收集器的累加器类型 + * @param 下游收集器的结果类型 + * @return 产生Map的收集器 */ - public static Collector> groupingBy( - @NotNull Function classifier, - @NotNull Collector downstream) { + public static Collector> groupingBy(@NotNull Function classifier, @NotNull Collector downstream) { return Collectors.groupingBy(classifier, downstream); } + /** - * 返回一个用于分组的 Collector。 + * 返回一个收集器,该收集器将输入元素按分类器分组到Map中 * - * @param classifier 分类函数 - * @param 元素类型 - * @param 键类型 - * @return Collector 实例 + * @param classifier 用于分类元素的函数 + * @param 输入元素的类型 + * @param 分类键的类型 + * @return 产生Map的收集器 */ public static Collector>> groupingBy(@NotNull Function classifier) { return Collectors.groupingBy(classifier); } + /** - * 返回一个用于归约的 Collector,支持映射和合并操作。 + * 返回一个收集器,该收集器执行归约操作 * - * @param identity 初始值 - * @param mapper 映射函数 - * @param op 合并函数 - * @param 元素类型 - * @param 结果类型 - * @return Collector 实例 + * @param identity 归约操作的初始值 + * @param mapper 用于将输入元素映射到中间值的函数 + * @param op 用于组合中间值的二元操作符 + * @param 输入元素的类型 + * @param 中间值和结果的类型 + * @return 执行归约操作的收集器 */ public static Collector reducing(U identity, @NotNull Function mapper, @NotNull BinaryOperator op) { return Collectors.reducing(identity, mapper, op); } + /** - * 返回一个用于归约的 Collector。 + * 返回一个收集器,该收集器执行归约操作 * - * @param op 合并函数 - * @param 元素类型 - * @return Collector 实例 + * @param op 用于组合元素的二元操作符 + * @param 输入元素的类型 + * @return 执行归约操作的收集器 */ public static Collector> reducing(@NotNull BinaryOperator op) { return Collectors.reducing(op); } + /** - * 返回一个用于归约的 Collector,支持初始值。 + * 返回一个收集器,该收集器执行归约操作 * - * @param identity 初始值 - * @param op 合并函数 - * @param 元素类型 - * @return Collector 实例 + * @param identity 归约操作的初始值 + * @param op 用于组合元素的二元操作符 + * @param 输入元素的类型 + * @return 执行归约操作的收集器 */ public static Collector reducing(T identity, @NotNull BinaryOperator op) { return Collectors.reducing(identity, op); } + /** - * 返回一个用于计算平均值的 Collector,适用于 double 类型。 + * 返回一个收集器,该收集器计算输入元素的平均值 * - * @param mapper 映射函数 - * @param 元素类型 - * @return Collector 实例 + * @param mapper 用于将输入元素映射到double值的函数 + * @param 输入元素的类型 + * @return 计算平均值的收集器 */ public static Collector averagingDouble(@NotNull ToDoubleFunction mapper) { return Collectors.averagingDouble(mapper); } + /** - * 返回一个用于计算平均值的 Collector,适用于 long 类型。 + * 返回一个收集器,该收集器计算输入元素的平均值 * - * @param mapper 映射函数 - * @param 元素类型 - * @return Collector 实例 + * @param mapper 用于将输入元素映射到long值的函数 + * @param 输入元素的类型 + * @return 计算平均值的收集器 */ public static Collector averagingLong(@NotNull ToLongFunction mapper) { return Collectors.averagingLong(mapper); } + /** - * 返回一个用于计算平均值的 Collector,适用于 int 类型。 + * 返回一个收集器,该收集器计算输入元素的平均值 * - * @param mapper 映射函数 - * @param 元素类型 - * @return Collector 实例 + * @param mapper 用于将输入元素映射到int值的函数 + * @param 输入元素的类型 + * @return 计算平均值的收集器 */ public static Collector averagingInt(@NotNull ToIntFunction mapper) { return Collectors.averagingInt(mapper); } + /** - * 返回一个用于求和的 Collector,适用于 double 类型。 + * 返回一个收集器,该收集器计算输入元素的总和 * - * @param mapper 映射函数 - * @param 元素类型 - * @return Collector 实例 + * @param mapper 用于将输入元素映射到double值的函数 + * @param 输入元素的类型 + * @return 计算总和的收集器 */ public static Collector summingDouble(@NotNull ToDoubleFunction mapper) { return Collectors.summingDouble(mapper); } + /** - * 返回一个用于求和的 Collector,适用于 long 类型。 + * 返回一个收集器,该收集器计算输入元素的总和 * - * @param mapper 映射函数 - * @param 元素类型 - * @return Collector 实例 + * @param mapper 用于将输入元素映射到long值的函数 + * @param 输入元素的类型 + * @return 计算总和的收集器 */ public static Collector summingLong(@NotNull ToLongFunction mapper) { return Collectors.summingLong(mapper); } + /** - * 返回一个用于求和的 Collector,适用于 int 类型。 + * 返回一个收集器,该收集器计算输入元素的总和 * - * @param mapper 映射函数 - * @param 元素类型 - * @return Collector 实例 + * @param mapper 用于将输入元素映射到int值的函数 + * @param 输入元素的类型 + * @return 计算总和的收集器 */ public static Collector summingInt(@NotNull ToIntFunction mapper) { return Collectors.summingInt(mapper); } + /** - * 返回一个用于查找最大值的 Collector。 + * 返回一个收集器,该收集器根据比较器找到最大元素 * - * @param comparator 比较器 - * @param 元素类型 - * @return Collector 实例 + * @param comparator 用于比较元素的比较器 + * @param 输入元素的类型 + * @return 找到最大元素的收集器 */ public static Collector> maxBy(@NotNull Comparator comparator) { return Collectors.maxBy(comparator); } + /** - * 返回一个用于查找最小值的 Collector。 + * 返回一个收集器,该收集器根据比较器找到最小元素 * - * @param comparator 比较器 - * @param 元素类型 - * @return Collector 实例 + * @param comparator 用于比较元素的比较器 + * @param 输入元素的类型 + * @return 找到最小元素的收集器 */ public static Collector> minBy(@NotNull Comparator comparator) { return Collectors.minBy(comparator); } + /** - * 返回一个用于计数的 Collector。 + * 返回一个收集器,该收集器计算输入元素的数量 * - * @param 元素类型 - * @return Collector 实例 + * @param 输入元素的类型 + * @return 计算元素数量的收集器 */ public static Collector counting() { return Collectors.counting(); } + /** - * 返回一个用于在下游收集后进行后续处理的 Collector。 + * 返回一个收集器,该收集器在应用下游收集器后执行finisher转换 * - * @param downstream 下游 Collector - * @param finisher 后续处理函数 - * @param 元素类型 - * @param 下游中间类型 - * @param 下游结果类型 + * @param downstream 下游收集器 + * @param finisher 在下游收集器完成后应用的finisher函数 + * @param 输入元素的类型 + * @param 下游收集器的累加器类型 + * @param 下游收集器的结果类型 * @param 最终结果类型 - * @return Collector 实例 + * @return 执行转换的收集器 */ - public static Collector collectingAndThen( - @NotNull Collector downstream, - @NotNull Function finisher) { + public static Collector collectingAndThen(@NotNull Collector downstream, @NotNull Function finisher) { return Collectors.collectingAndThen(downstream, finisher); } + /** - * 返回一个用于映射后收集的 Collector。 + * 返回一个收集器,该收集器将输入元素映射后应用下游收集器 * - * @param mapper 映射函数 - * @param downstream 下游 Collector - * @param 元素类型 - * @param 映射后的类型 - * @param 下游中间类型 - * @param 下游结果类型 - * @return Collector 实例 + * @param mapper 用于映射输入元素的函数 + * @param downstream 应用于映射后元素的下游收集器 + * @param 输入元素的类型 + * @param 映射后元素的类型 + * @param 下游收集器的累加器类型 + * @param 下游收集器的结果类型 + * @return 执行映射和收集的收集器 */ - public static Collector mapping( - @NotNull Function mapper, - @NotNull Collector downstream) { + public static Collector mapping(@NotNull Function mapper, @NotNull Collector downstream) { return Collectors.mapping(mapper, downstream); } + /** - * 返回一个用于连接字符串的 Collector,支持前缀、后缀和分隔符。 + * 返回一个收集器,该收集器连接输入的字符序列 * * @param delimiter 分隔符 * @param prefix 前缀 * @param suffix 后缀 - * @return Collector 实例 + * @return 连接字符序列的收集器 */ public static Collector joining(@NotNull CharSequence delimiter, @NotNull CharSequence prefix, @NotNull CharSequence suffix) { return Collectors.joining(delimiter, prefix, suffix); } + /** - * 返回一个用于连接字符串的 Collector,支持分隔符。 + * 返回一个收集器,该收集器连接输入的字符序列 * * @param delimiter 分隔符 - * @return Collector 实例 + * @return 连接字符序列的收集器 */ public static Collector joining(@NotNull CharSequence delimiter) { return Collectors.joining(delimiter); } + /** - * 返回一个用于连接字符串的 Collector。 + * 返回一个收集器,该收集器连接输入的字符序列 * - * @return Collector 实例 + * @return 连接字符序列的收集器 */ public static Collector joining() { return Collectors.joining(); } + /** - * 返回一个用于收集元素到 Set 的 Collector。 + * 返回一个收集器,该收集器将输入元素累积到Set中 * - * @param 元素类型 - * @return Collector 实例 + * @param 输入元素的类型 + * @return 产生Set的收集器 */ public static Collector> toSet() { return Collectors.toSet(); } + /** - * 返回一个用于收集元素到 List 的 Collector。 + * 返回一个收集器,该收集器将输入元素累积到List中 * - * @param 元素类型 - * @return Collector 实例 + * @param 输入元素的类型 + * @return 产生List的收集器 */ public static Collector> toList() { return Collectors.toList(); } + /** - * 返回一个用于收集元素到指定集合类型的 Collector。 + * 返回一个收集器,该收集器将输入元素累积到指定类型的集合中 * - * @param collectionFactory 集合工厂 - * @param 元素类型 - * @param 集合类型 - * @return Collector 实例 + * @param collectionFactory 用于创建集合的工厂函数 + * @param 输入元素的类型 + * @param 目标集合的具体类型 + * @return 产生指定类型集合的收集器 */ public static > Collector toCollection(@NotNull Supplier collectionFactory) { return Collectors.toCollection(collectionFactory); } + /** - * 返回一个用于收集元素到 Map 的 Collector,键由 keyMapper 提供,值为元素本身。 + * 返回一个收集器,该收集器将输入元素累积到Map中,键由keyMapper提供,值为元素本身 * - * @param keyMapper 键映射函数 - * @param 元素类型 - * @param 键类型 - * @return Collector 实例 + * @param keyMapper 用于产生键的映射函数 + * @param 输入元素的类型 + * @param 键的类型 + * @return 产生Map的收集器 */ public static Collector> toMap(Function keyMapper) { return toMap(keyMapper, Function.identity()); } + /** - * 返回一个用于收集 Map.Entry 到 Map 的 Collector。 + * 返回一个收集器,该收集器将Map.Entry类型的输入元素累积到Map中 * - * @param Map.Entry 类型 - * @param 键类型 - * @param 值类型 - * @return Collector 实例 + * @param 输入元素的类型(必须是Map.Entry) + * @param 键的类型 + * @param 值的类型 + * @return 产生Map的收集器 */ public static , K, V> Collector> toMap() { return Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue); } /** - * 过滤流中的元素。 + * 对流中的每个元素应用过滤条件 * - * @param predicate 过滤条件 - * @return 过滤后的 SuperStream 实例 + * @param predicate 用于测试元素是否满足条件的谓词 + * @return 过滤后的SuperStream实例 */ public SuperStream filter(Predicate predicate) { return SuperStream.of(stream.filter(predicate)); } /** - * 使用指定的 Collector 收集流中的元素。 + * 使用指定的收集器对流进行收集操作 * - * @param collector Collector 实例 - * @param 结果类型 - * @param 中间类型 - * @return 收集结果 + * @param collector 用于收集元素的收集器 + * @param 收集结果的类型 + * @param 收集器累加器的类型 + * @return 收集操作的结果 */ public R collect(Collector collector) { return stream.collect(collector); } /** - * 返回此流的Spliterator对象 + * 获取流的Spliterator * - * @return 此流的Spliterator对象,用于并行处理流中的元素 + * @return 流的Spliterator实例 */ @NotNull public Spliterator spliterator() { @@ -678,48 +772,47 @@ public class SuperStream implements Stream { } /** - * 将此流中的每个元素转换为一个IntStream,然后将所有生成的IntStream连接成一个IntStream + * 将每个元素映射为IntStream并展平结果 * - * @param mapper 用于将流中每个元素转换为IntStream的函数 - * @return 由所有转换后的IntStream连接而成的新IntStream + * @param mapper 用于将元素映射为IntStream的函数 + * @return 展平后的IntStream */ public IntStream flatMapToInt(Function mapper) { return stream.flatMapToInt(mapper); } /** - * 判断此流是否为并行流 + * 检查流是否为并行流 * - * @return 如果此流是并行流则返回true,否则返回false + * @return 如果流是并行的则返回true,否则返回false */ public boolean isParallel() { return stream.isParallel(); } /** - * 返回此流的迭代器对象 + * 获取流的迭代器 * - * @return 此流的迭代器对象,用于顺序访问流中的元素 + * @return 流的Iterator实例 */ @NotNull public Iterator iterator() { return stream.iterator(); } - /** - * 对流中每个元素执行指定操作。 + * 对流中的每个元素执行指定的动作 * - * @param action 操作函数 + * @param action 要执行的动作函数 */ public void forEach(P1Function action) { stream.forEach(action::call); } /** - * 对流中每个元素执行指定操作,并传入元素索引。 + * 对流中的每个元素执行指定的动作,并传递索引 * - * @param action 操作函数,接受元素和索引 + * @param action 要执行的动作函数,接收元素和索引作为参数 */ public void forEach(P2Function action) { AtomicInteger i22 = new AtomicInteger(0); @@ -729,29 +822,29 @@ public class SuperStream implements Stream { } /** - * 对流中的每个元素执行给定的操作。 + * 对流中的每个元素执行指定的动作 * - * @param action 要对每个元素执行的操作,不能为 null + * @param action 要执行的动作函数 */ public void forEach(Consumer action) { stream.forEach(action); } /** - * 将流中的每个元素转换为一个流,并将这些流合并为一个流。 + * 将每个元素映射为Stream并展平结果 * - * @param mapper 用于将每个元素映射为流的函数,不能为 null - * @param 映射后流中元素的类型 - * @return 合并后的流 + * @param mapper 用于将元素映射为Stream的函数 + * @param 映射后元素的类型 + * @return 展平后的Stream实例 */ public Stream flatMap(Function> mapper) { return stream.flatMap(mapper); } /** - * 返回一个顺序执行的流。 + * 返回一个串行流 * - * @return 顺序执行的 SuperStream 实例 + * @return 串行的SuperStream实例 */ @NotNull public SuperStream sequential() { @@ -759,32 +852,32 @@ public class SuperStream implements Stream { } /** - * 跳过流中的前 n 个元素。 + * 跳过指定数量的元素 * * @param n 要跳过的元素数量 - * @return 跳过元素后的 SuperStream 实例 + * @return 跳过元素后的SuperStream实例 */ public SuperStream skip(long n) { return SuperStream.of(stream.skip(n)); } /** - * 使用提供的供应器、累加器和组合器对流进行归约操作。 + * 使用指定的收集器对流进行收集操作 * - * @param supplier 结果容器的供应器 - * @param accumulator 将元素累积到结果容器中的函数 - * @param combiner 合并两个结果容器的函数 - * @param 结果容器的类型 - * @return 归约后的结果 + * @param supplier 用于创建初始累加器的供应函数 + * @param accumulator 用于将元素添加到累加器的操作 + * @param combiner 用于合并两个累加器的操作 + * @param 收集结果的类型 + * @return 收集操作的结果 */ public R collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner) { return stream.collect(supplier, accumulator, combiner); } /** - * 返回一个并行执行的流。 + * 返回一个并行流 * - * @return 并行执行的 SuperStream 实例 + * @return 并行的SuperStream实例 */ @NotNull public SuperStream parallel() { @@ -792,17 +885,17 @@ public class SuperStream implements Stream { } /** - * 将流中的每个元素映射为 DoubleStream,并将这些流合并为一个 DoubleStream。 + * 将每个元素映射为DoubleStream并展平结果 * - * @param mapper 用于将每个元素映射为 DoubleStream 的函数,不能为 null - * @return 合并后的 DoubleStream + * @param mapper 用于将元素映射为DoubleStream的函数 + * @return 展平后的DoubleStream */ public DoubleStream flatMapToDouble(Function mapper) { return stream.flatMapToDouble(mapper); } /** - * 使用提供的生成器函数将流转换为数组。 + * 将流转换为指定类型的数组 * * @param generator 用于创建数组的函数 * @param 数组元素的类型 @@ -814,9 +907,9 @@ public class SuperStream implements Stream { } /** - * 返回流中的第一个元素(如果存在)。 + * 查找流中的第一个元素 * - * @return 包含第一个元素的 Optional,如果流为空则返回空 Optional + * @return 包含第一个元素的Optional,如果流为空则返回空Optional */ @NotNull public Optional findFirst() { @@ -824,9 +917,9 @@ public class SuperStream implements Stream { } /** - * 返回一个无序的流。 + * 返回一个无序流 * - * @return 无序的 SuperStream 实例 + * @return 无序的SuperStream实例 */ @NotNull public SuperStream unordered() { @@ -834,10 +927,10 @@ public class SuperStream implements Stream { } /** - * 根据提供的比较器返回流中的最小元素(如果存在)。 + * 根据比较器查找流中的最小元素 * - * @param comparator 用于比较元素的比较器,不能为 null - * @return 包含最小元素的 Optional,如果流为空则返回空 Optional + * @param comparator 用于比较元素的比较器 + * @return 包含最小元素的Optional,如果流为空则返回空Optional */ @NotNull public Optional min(Comparator comparator) { @@ -845,9 +938,9 @@ public class SuperStream implements Stream { } /** - * 返回流中的任意元素(如果存在)。 + * 查找流中的任意元素 * - * @return 包含任意元素的 Optional,如果流为空则返回空 Optional + * @return 包含任意元素的Optional,如果流为空则返回空Optional */ @NotNull public Optional findAny() { @@ -855,10 +948,10 @@ public class SuperStream implements Stream { } /** - * 注册一个关闭处理器,在流关闭时执行。 + * 注册一个关闭处理器,在流关闭时执行 * - * @param closeHandler 流关闭时要执行的处理器,不能为 null - * @return 注册了关闭处理器的 SuperStream 实例 + * @param closeHandler 关闭时要执行的Runnable + * @return 添加了关闭处理器的SuperStream实例 */ @NotNull public SuperStream onClose(@NotNull Runnable closeHandler) { @@ -866,23 +959,20 @@ public class SuperStream implements Stream { } /** - * 将流中的每个元素映射为 LongStream,并将这些流合并为一个 LongStream。 + * 将每个元素映射为LongStream并展平结果 * - * @param mapper 用于将每个元素映射为 LongStream 的函数,不能为 null - * @return 合并后的 LongStream + * @param mapper 用于将元素映射为LongStream的函数 + * @return 展平后的LongStream */ public LongStream flatMapToLong(Function mapper) { return stream.flatMapToLong(mapper); } /** - * 将流转换为数组。
- * 使用反射获取类型 是空的的话抛出 - * {@code StreamEmptyException } + * 将流转换为指定类型的数组 * + * @param clazz 数组元素的Class对象 * @return 转换后的数组 - * @throws StreamEmptyException 如果流为空 - * @see StreamEmptyException */ @NotNull @SuppressWarnings("unchecked") @@ -890,32 +980,37 @@ public class SuperStream implements Stream { return stream.toArray(i -> (T[]) Array.newInstance(clazz, i)); } + /** + * 将流转换为Object数组 + * + * @return 转换后的Object数组 + */ @NotNull public Object[] toArray() { return stream.toArray(); } /** - * 按照 encounter order 对流中的每个元素执行给定的操作。 + * 按顺序对流中的每个元素执行指定的动作 * - * @param action 要对每个元素执行的操作,不能为 null + * @param action 要执行的动作函数 */ public void forEachOrdered(Consumer action) { stream.forEachOrdered(action); } /** - * 关闭流并释放相关资源。 + * 关闭流并释放相关资源 */ public void close() { stream.close(); } /** - * 使用提供的累加器函数对流进行归约操作。 + * 对流执行归约操作 * - * @param accumulator 用于归约的累加器函数 - * @return 包含归约结果的 Optional,如果流为空则返回空 Optional + * @param accumulator 用于组合元素的二元操作符 + * @return 归约操作的结果,如果流为空则返回空Optional */ @NotNull public Optional reduce(BinaryOperator accumulator) { @@ -923,89 +1018,89 @@ public class SuperStream implements Stream { } /** - * 根据提供的比较器对流进行排序。 + * 返回一个根据指定比较器排序的流 * - * @param comparator 用于排序的比较器,不能为 null - * @return 排序后的 SuperStream 实例 + * @param comparator 用于比较元素的比较器 + * @return 排序后的SuperStream实例 */ public SuperStream sorted(Comparator comparator) { return SuperStream.of(stream.sorted(comparator)); } /** - * 检查流中的所有元素是否都满足给定的谓词。 + * 检查流中的所有元素是否都满足给定的谓词 * - * @param predicate 用于测试元素的谓词,不能为 null - * @return 如果所有元素都满足谓词则返回 true,否则返回 false + * @param predicate 用于测试元素的谓词 + * @return 如果所有元素都满足谓词或流为空则返回true,否则返回false */ public boolean allMatch(Predicate predicate) { return stream.allMatch(predicate); } /** - * 对流中的元素进行自然排序。 + * 返回一个自然排序的流 * - * @return 排序后的 SuperStream 实例 + * @return 排序后的SuperStream实例 */ public SuperStream sorted() { return SuperStream.of(stream.sorted()); } /** - * 返回流中元素的数量。 + * 计算流中元素的数量 * - * @return 元素的数量 + * @return 流中元素的数量 */ public long count() { return stream.count(); } /** - * 将流中的每个元素通过映射函数转换为另一种类型。 + * 将流中的每个元素通过映射函数转换 * - * @param mapper 用于映射元素的函数,不能为 null + * @param mapper 用于转换元素的映射函数 * @param 映射后元素的类型 - * @return 映射后的 SuperStream 实例 + * @return 转换后的SuperStream实例 */ public SuperStream map(Function mapper) { return SuperStream.of(stream.map(mapper)); } /** - * 使用提供的标识值和累加器函数对流进行归约操作。 + * 对流执行归约操作 * - * @param identity 归约的初始值 - * @param accumulator 用于归约的累加器函数 - * @return 归约后的结果 + * @param identity 归约操作的初始值 + * @param accumulator 用于组合元素的二元操作符 + * @return 归约操作的结果 */ public T reduce(T identity, BinaryOperator accumulator) { return stream.reduce(identity, accumulator); } /** - * 检查流中是否没有任何元素满足给定的谓词。 + * 检查流中的所有元素都不满足给定的谓词 * - * @param predicate 用于测试元素的谓词,不能为 null - * @return 如果没有任何元素满足谓词则返回 true,否则返回 false + * @param predicate 用于测试元素的谓词 + * @return 如果所有元素都不满足谓词则返回true,否则返回false */ public boolean noneMatch(Predicate predicate) { return stream.noneMatch(predicate); } /** - * 返回一个去除重复元素后的流。 + * 返回一个去重后的流 * - * @return 去重后的 SuperStream 实例 + * @return 去重后的SuperStream实例 */ public SuperStream distinct() { return SuperStream.of(stream.distinct()); } /** - * 根据提供的比较器返回流中的最大元素(如果存在)。 + * 根据比较器查找流中的最大元素 * - * @param comparator 用于比较元素的比较器,不能为 null - * @return 包含最大元素的 Optional,如果流为空则返回空 Optional + * @param comparator 用于比较元素的比较器 + * @return 包含最大元素的Optional,如果流为空则返回空Optional */ @NotNull public Optional max(Comparator comparator) { @@ -1013,116 +1108,115 @@ public class SuperStream implements Stream { } /** - * 将流中的每个元素映射为 double 值,并返回一个 DoubleStream。 + * 将流中的每个元素映射为double值 * - * @param mapper 用于将元素映射为 double 的函数,不能为 null - * @return 映射后的 DoubleStream + * @param mapper 用于映射元素为double值的函数 + * @return 映射后的DoubleStream */ public DoubleStream mapToDouble(ToDoubleFunction mapper) { return stream.mapToDouble(mapper); } /** - * 限制流中元素的数量。 + * 限制流中元素的数量 * * @param maxSize 最大元素数量 - * @return 限制后的 SuperStream 实例 + * @return 限制后的SuperStream实例 */ public SuperStream limit(long maxSize) { return SuperStream.of(stream.limit(maxSize)); } /** - * 将流中的每个元素映射为 long 值,并返回一个 LongStream。 + * 将流中的每个元素映射为long值 * - * @param mapper 用于将元素映射为 long 的函数,不能为 null - * @return 映射后的 LongStream + * @param mapper 用于映射元素为long值的函数 + * @return 映射后的LongStream */ public LongStream mapToLong(ToLongFunction mapper) { return stream.mapToLong(mapper); } /** - * 对流中的每个元素执行给定的操作,并返回相同的流。 + * 对流中的每个元素执行动作,然后返回原流 * - * @param action 要对每个元素执行的操作,不能为 null - * @return 相同的 SuperStream 实例 + * @param action 要执行的动作函数 + * @return 原始的SuperStream实例 */ public SuperStream peek(Consumer action) { return SuperStream.of(stream.peek(action)); } /** - * 使用提供的标识值、累加器函数和组合器对流进行归约操作。 + * 对流执行归约操作 * - * @param identity 归约的初始值 - * @param accumulator 用于归约的累加器函数 - * @param combiner 用于合并部分结果的组合器函数 - * @param 归约结果的类型 - * @return 归约后的结果 + * @param identity 归约操作的初始值 + * @param accumulator 用于组合元素的二元操作符 + * @param combiner 用于合并累加器的二元操作符 + * @param 累加器和结果的类型 + * @return 归约操作的结果 */ public U reduce(U identity, BiFunction accumulator, BinaryOperator combiner) { return stream.reduce(identity, accumulator, combiner); } /** - * 将流中的每个元素映射为 int 值,并返回一个 IntStream。 + * 将流中的每个元素映射为int值 * - * @param mapper 用于将元素映射为 int 的函数,不能为 null - * @return 映射后的 IntStream + * @param mapper 用于映射元素为int值的函数 + * @return 映射后的IntStream */ public IntStream mapToInt(ToIntFunction mapper) { return stream.mapToInt(mapper); } /** - * 检查流中是否有任何元素满足给定的谓词。 + * 检查流中是否有任何元素满足给定的谓词 * - * @param predicate 用于测试元素的谓词,不能为 null - * @return 如果有任何元素满足谓词则返回 true,否则返回 false + * @param predicate 用于测试元素的谓词 + * @return 如果有任何元素满足谓词则返回true,否则返回false */ public boolean anyMatch(Predicate predicate) { return stream.anyMatch(predicate); } - /** - * 获取原始 Stream。 + * 获取被包装的基础Stream对象 * - * @return 原始 Stream 实例 + * @return 基础Stream对象 */ public Stream getStream() { return stream; } /** - * 将流中的元素收集到 Map 中。 + * 将流中的元素收集到Map中,使用指定的键和值映射函数 * - * @param keyMapper 键映射函数 - * @param valueMapper 值映射函数 - * @param 键类型 - * @param 值类型 - * @return 收集结果 Map + * @param keyMapper 用于生成键的映射函数 + * @param valueMapper 用于生成值的映射函数 + * @param 键的类型 + * @param 值的类型 + * @return 包含映射结果的Map */ public Map toAMap(Function keyMapper, Function valueMapper) { return collect(toMap(keyMapper, valueMapper)); } /** - * 将流中的元素收集到 Map 中,值为元素本身。 + * 将流中的元素收集到Map中,使用指定的键映射函数,值为元素本身 * - * @param keyMapper 键映射函数 - * @param 键类型 - * @return 收集结果 Map + * @param keyMapper 用于生成键的映射函数 + * @param 键的类型 + * @return 包含映射结果的Map */ public Map toAMap(Function keyMapper) { return collect(toMap(keyMapper)); } /** - * 将流中的元素收集到 List 中。 + * 将流中的元素收集到List中 * - * @return 收集结果 List + * @return 包含流中所有元素的List */ public List toAList() { return collect(toList()); diff --git a/src/main/kotlin/com/mingliqiye/utils/array/ArrayUtils.kt b/src/main/kotlin/com/mingliqiye/utils/array/ArrayUtils.kt new file mode 100644 index 0000000..a606e29 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/array/ArrayUtils.kt @@ -0,0 +1,388 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile ArrayUtils.kt + * LastUpdate 2026-01-28 08:03:28 + * UpdateUser MingLiPro + */ +@file:JvmName("ArrayUtils") + +package com.mingliqiye.utils.array + +/** + * 复制数组元素到目标数组 + * @param from 源数组 + * @param to 目标数组 + * @param fromPos 源数组起始位置,默认为0 + * @param toPos 目标数组起始位置,默认为0 + * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + * @throws ArrayStoreException 当类型不匹配时抛出 + */ +@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) +fun arrayCopy(from: Array, to: Array, fromPos: Int = 0, toPos: Int = 0, length: Int = from.size - fromPos) { + System.arraycopy(from, fromPos, to, toPos, length) +} + +/** + * 扩展函数:将当前数组元素复制到目标数组 + * @param to 目标数组 + * @param fromPos 源数组起始位置,默认为0 + * @param toPos 目标数组起始位置,默认为0 + * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + * @throws ArrayStoreException 当类型不匹配时抛出 + */ +@JvmSynthetic +@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) +fun Array.copyTo(to: Array, fromPos: Int = 0, toPos: Int = 0, length: Int = this.size - fromPos) { + System.arraycopy(this, fromPos, to, toPos, length) +} + +/** + * 复制整个数组到目标数组(重载版本) + * @param from 源数组 + * @param to 目标数组 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + * @throws ArrayStoreException 当类型不匹配时抛出 + */ +@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) +fun arrayCopy(from: Array, to: Array) { + return arrayCopy(from, to, 0, 0, from.size) +} + +/** + * 复制字节数组元素到目标数组 + * @param from 源字节数组 + * @param to 目标字节数组 + * @param fromPos 源数组起始位置,默认为0 + * @param toPos 目标数组起始位置,默认为0 + * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + */ +@Throws(IndexOutOfBoundsException::class) +fun arrayCopy(from: ByteArray, to: ByteArray, fromPos: Int = 0, toPos: Int = 0, length: Int = from.size - fromPos) { + System.arraycopy(from, fromPos, to, toPos, length) +} + +/** + * 扩展函数:将当前字节数组元素复制到目标数组 + * @param to 目标字节数组 + * @param fromPos 源数组起始位置,默认为0 + * @param toPos 目标数组起始位置,默认为0 + * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + * @throws ArrayStoreException 当类型不匹配时抛出 + */ +@JvmSynthetic +@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) +fun ByteArray.copyTo(to: ByteArray, fromPos: Int = 0, toPos: Int = 0, length: Int = this.size - fromPos) { + System.arraycopy(this, fromPos, to, toPos, length) +} + +/** + * 复制整个字节数组到目标数组(重载版本) + * @param from 源字节数组 + * @param to 目标字节数组 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + */ +@Throws(IndexOutOfBoundsException::class) +fun arrayCopy(from: ByteArray, to: ByteArray) { + arrayCopy(from, to, 0, 0, from.size) +} + +/** + * 复制短整型数组元素到目标数组 + * @param from 源短整型数组 + * @param to 目标短整型数组 + * @param fromPos 源数组起始位置,默认为0 + * @param toPos 目标数组起始位置,默认为0 + * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + */ +@Throws(IndexOutOfBoundsException::class) +fun arrayCopy(from: ShortArray, to: ShortArray, fromPos: Int = 0, toPos: Int = 0, length: Int = from.size - fromPos) { + System.arraycopy(from, fromPos, to, toPos, length) +} + +/** + * 扩展函数:将当前短整型数组元素复制到目标数组 + * @param to 目标短整型数组 + * @param fromPos 源数组起始位置,默认为0 + * @param toPos 目标数组起始位置,默认为0 + * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + * @throws ArrayStoreException 当类型不匹配时抛出 + */ +@JvmSynthetic +@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) +fun ShortArray.copyTo(to: ShortArray, fromPos: Int = 0, toPos: Int = 0, length: Int = this.size - fromPos) { + System.arraycopy(this, fromPos, to, toPos, length) +} + +/** + * 复制整个短整型数组到目标数组(重载版本) + * @param from 源短整型数组 + * @param to 目标短整型数组 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + */ +@Throws(IndexOutOfBoundsException::class) +fun arrayCopy(from: ShortArray, to: ShortArray) { + arrayCopy(from, to, 0, 0, from.size) +} + +/** + * 复制整型数组元素到目标数组 + * @param from 源整型数组 + * @param to 目标整型数组 + * @param fromPos 源数组起始位置,默认为0 + * @param toPos 目标数组起始位置,默认为0 + * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + */ +@Throws(IndexOutOfBoundsException::class) +fun arrayCopy(from: IntArray, to: IntArray, fromPos: Int = 0, toPos: Int = 0, length: Int = from.size - fromPos) { + System.arraycopy(from, fromPos, to, toPos, length) +} + +/** + * 扩展函数:将当前整型数组元素复制到目标数组 + * @param to 目标整型数组 + * @param fromPos 源数组起始位置,默认为0 + * @param toPos 目标数组起始位置,默认为0 + * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + * @throws ArrayStoreException 当类型不匹配时抛出 + */ +@JvmSynthetic +@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) +fun IntArray.copyTo(to: IntArray, fromPos: Int = 0, toPos: Int = 0, length: Int = this.size - fromPos) { + System.arraycopy(this, fromPos, to, toPos, length) +} + +/** + * 复制整个整型数组到目标数组(重载版本) + * @param from 源整型数组 + * @param to 目标整型数组 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + */ +@Throws(IndexOutOfBoundsException::class) +fun arrayCopy(from: IntArray, to: IntArray) { + arrayCopy(from, to, 0, 0, from.size) +} + +/** + * 复制长整型数组元素到目标数组 + * @param from 源长整型数组 + * @param to 目标长整型数组 + * @param fromPos 源数组起始位置,默认为0 + * @param toPos 目标数组起始位置,默认为0 + * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + */ +@Throws(IndexOutOfBoundsException::class) +fun arrayCopy(from: LongArray, to: LongArray, fromPos: Int = 0, toPos: Int = 0, length: Int = from.size - fromPos) { + System.arraycopy(from, fromPos, to, toPos, length) +} + +/** + * 扩展函数:将当前长整型数组元素复制到目标数组 + * @param to 目标长整型数组 + * @param fromPos 源数组起始位置,默认为0 + * @param toPos 目标数组起始位置,默认为0 + * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + * @throws ArrayStoreException 当类型不匹配时抛出 + */ +@JvmSynthetic +@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) +fun LongArray.copyTo(to: LongArray, fromPos: Int = 0, toPos: Int = 0, length: Int = this.size - fromPos) { + System.arraycopy(this, fromPos, to, toPos, length) +} + +/** + * 复制整个长整型数组到目标数组(重载版本) + * @param from 源长整型数组 + * @param to 目标长整型数组 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + */ +@Throws(IndexOutOfBoundsException::class) +fun arrayCopy(from: LongArray, to: LongArray) { + arrayCopy(from, to, 0, 0, from.size) +} + +/** + * 复制浮点型数组元素到目标数组 + * @param from 源浮点型数组 + * @param to 目标浮点型数组 + * @param fromPos 源数组起始位置,默认为0 + * @param toPos 目标数组起始位置,默认为0 + * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + */ +@Throws(IndexOutOfBoundsException::class) +fun arrayCopy(from: FloatArray, to: FloatArray, fromPos: Int = 0, toPos: Int = 0, length: Int = from.size - fromPos) { + System.arraycopy(from, fromPos, to, toPos, length) +} + +/** + * 扩展函数:将当前浮点型数组元素复制到目标数组 + * @param to 目标浮点型数组 + * @param fromPos 源数组起始位置,默认为0 + * @param toPos 目标数组起始位置,默认为0 + * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + * @throws ArrayStoreException 当类型不匹配时抛出 + */ +@JvmSynthetic +@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) +fun FloatArray.copyTo(to: FloatArray, fromPos: Int = 0, toPos: Int = 0, length: Int = this.size - fromPos) { + System.arraycopy(this, fromPos, to, toPos, length) +} + +/** + * 复制整个浮点型数组到目标数组(重载版本) + * @param from 源浮点型数组 + * @param to 目标浮点型数组 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + */ +@Throws(IndexOutOfBoundsException::class) +fun arrayCopy(from: FloatArray, to: FloatArray) { + arrayCopy(from, to, 0, 0, from.size) +} + +/** + * 复制双精度浮点型数组元素到目标数组 + * @param from 源双精度浮点型数组 + * @param to 目标双精度浮点型数组 + * @param fromPos 源数组起始位置,默认为0 + * @param toPos 目标数组起始位置,默认为0 + * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + */ +@Throws(IndexOutOfBoundsException::class) +fun arrayCopy(from: DoubleArray, to: DoubleArray, fromPos: Int = 0, toPos: Int = 0, length: Int = from.size - fromPos) { + System.arraycopy(from, fromPos, to, toPos, length) +} + +/** + * 扩展函数:将当前双精度浮点型数组元素复制到目标数组 + * @param to 目标双精度浮点型数组 + * @param fromPos 源数组起始位置,默认为0 + * @param toPos 目标数组起始位置,默认为0 + * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + * @throws ArrayStoreException 当类型不匹配时抛出 + */ +@JvmSynthetic +@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) +fun DoubleArray.copyTo(to: DoubleArray, fromPos: Int = 0, toPos: Int = 0, length: Int = this.size - fromPos) { + System.arraycopy(this, fromPos, to, toPos, length) +} + +/** + * 复制整个双精度浮点型数组到目标数组(重载版本) + * @param from 源双精度浮点型数组 + * @param to 目标双精度浮点型数组 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + */ +@Throws(IndexOutOfBoundsException::class) +fun arrayCopy(from: DoubleArray, to: DoubleArray) { + arrayCopy(from, to, 0, 0, from.size) +} + +/** + * 复制布尔型数组元素到目标数组 + * @param from 源布尔型数组 + * @param to 目标布尔型数组 + * @param fromPos 源数组起始位置,默认为0 + * @param toPos 目标数组起始位置,默认为0 + * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + */ +@Throws(IndexOutOfBoundsException::class) +fun arrayCopy( + from: BooleanArray, to: BooleanArray, fromPos: Int = 0, toPos: Int = 0, length: Int = from.size - fromPos +) { + System.arraycopy(from, fromPos, to, toPos, length) +} + +/** + * 扩展函数:将当前布尔型数组元素复制到目标数组 + * @param to 目标布尔型数组 + * @param fromPos 源数组起始位置,默认为0 + * @param toPos 目标数组起始位置,默认为0 + * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + * @throws ArrayStoreException 当类型不匹配时抛出 + */ +@JvmSynthetic +@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) +fun BooleanArray.copyTo(to: BooleanArray, fromPos: Int = 0, toPos: Int = 0, length: Int = this.size - fromPos) { + System.arraycopy(this, fromPos, to, toPos, length) +} + +/** + * 复制整个布尔型数组到目标数组(重载版本) + * @param from 源布尔型数组 + * @param to 目标布尔型数组 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + */ +@Throws(IndexOutOfBoundsException::class) +fun arrayCopy(from: BooleanArray, to: BooleanArray) { + arrayCopy(from, to, 0, 0, from.size) +} + +/** + * 复制字符型数组元素到目标数组 + * @param from 源字符型数组 + * @param to 目标字符型数组 + * @param fromPos 源数组起始位置,默认为0 + * @param toPos 目标数组起始位置,默认为0 + * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + */ +@Throws(IndexOutOfBoundsException::class) +fun arrayCopy(from: CharArray, to: CharArray, fromPos: Int = 0, toPos: Int = 0, length: Int = from.size - fromPos) { + System.arraycopy(from, fromPos, to, toPos, length) +} + +/** + * 扩展函数:将当前字符型数组元素复制到目标数组 + * @param to 目标字符型数组 + * @param fromPos 源数组起始位置,默认为0 + * @param toPos 目标数组起始位置,默认为0 + * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + * @throws ArrayStoreException 当类型不匹配时抛出 + */ +@JvmSynthetic +@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) +fun CharArray.copyTo(to: CharArray, fromPos: Int = 0, toPos: Int = 0, length: Int = this.size - fromPos) { + System.arraycopy(this, fromPos, to, toPos, length) +} + +/** + * 复制整个字符型数组到目标数组(重载版本) + * @param from 源字符型数组 + * @param to 目标字符型数组 + * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + */ +@Throws(IndexOutOfBoundsException::class) +fun arrayCopy(from: CharArray, to: CharArray) { + arrayCopy(from, to, 0, 0, from.size) +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/JsonException.kt b/src/main/kotlin/com/mingliqiye/utils/base/BaseType.kt similarity index 64% rename from src/main/kotlin/com/mingliqiye/utils/json/JsonException.kt rename to src/main/kotlin/com/mingliqiye/utils/base/BaseType.kt index 86692d2..87882ec 100644 --- a/src/main/kotlin/com/mingliqiye/utils/json/JsonException.kt +++ b/src/main/kotlin/com/mingliqiye/utils/base/BaseType.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,18 +15,16 @@ * * ProjectName mingli-utils * ModuleName mingli-utils.main - * CurrentFile JsonException.kt - * LastUpdate 2025-09-15 22:32:50 + * CurrentFile BaseType.kt + * LastUpdate 2026-02-04 21:54:04 * UpdateUser MingLiPro */ -package com.mingliqiye.utils.json +package com.mingliqiye.utils.base -class JsonException : RuntimeException { - - constructor(message: String) : super(message) - - constructor(message: String, cause: Throwable) : super(message, cause) - - constructor(cause: Throwable) : this(cause.message ?: "", cause) +enum class BaseType(val baseCodec: BaseCodec) { + BASE16(com.mingliqiye.utils.base.BASE16), + BASE64(com.mingliqiye.utils.base.BASE64), + BASE91(com.mingliqiye.utils.base.BASE91), + BASE256(com.mingliqiye.utils.base.BASE256), } diff --git a/src/main/kotlin/com/mingliqiye/utils/clone/CloneUtils.kt b/src/main/kotlin/com/mingliqiye/utils/clone/CloneUtils.kt index 5900668..22a1ed1 100644 --- a/src/main/kotlin/com/mingliqiye/utils/clone/CloneUtils.kt +++ b/src/main/kotlin/com/mingliqiye/utils/clone/CloneUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,15 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile CloneUtils.kt - * LastUpdate 2025-09-20 14:01:29 + * LastUpdate 2026-02-04 21:00:48 * UpdateUser MingLiPro */ @file:JvmName("CloneUtils") package com.mingliqiye.utils.clone -import com.mingliqiye.utils.json.JsonApi -import com.mingliqiye.utils.json.JsonException +import com.mingliqiye.utils.json.api.base.JsonApi +import com.mingliqiye.utils.json.api.exception.JsonException import java.io.* @@ -34,8 +34,7 @@ inline fun Serializable.deepClone(): T { inline fun T.deepJsonClone(jsonApi: JsonApi): T { try { - return jsonApi.convert(this, this!!.javaClass) as T - + return jsonApi.convert(this as Any, this!!.javaClass) as T } catch (e: Exception) { throw JsonException( "Failed to deep clone object using JSON", e diff --git a/src/main/kotlin/com/mingliqiye/utils/concurrent/IsChanged.kt b/src/main/kotlin/com/mingliqiye/utils/concurrent/IsChanged.kt index fcad409..75f5175 100644 --- a/src/main/kotlin/com/mingliqiye/utils/concurrent/IsChanged.kt +++ b/src/main/kotlin/com/mingliqiye/utils/concurrent/IsChanged.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile IsChanged.kt - * LastUpdate 2025-09-19 20:17:07 + * LastUpdate 2026-01-31 21:13:45 * UpdateUser MingLiPro */ @@ -39,17 +39,14 @@ class IsChanged { */ private val atomicReferenceData: AtomicReference = AtomicReference() - /** - * 默认构造函数,初始化数据为 null - */ - constructor() : this(null) + constructor() /** * 带参数的构造函数,使用指定的初始值初始化 * * @param data 初始数据值 */ - constructor(data: T?) : super() { + constructor(data: T) : super() { atomicReferenceData.set(data) } @@ -67,7 +64,7 @@ class IsChanged { * * @return 当前数据值 */ - fun get(): T? { + fun get(): T { return atomicReferenceData.get() } @@ -77,7 +74,7 @@ class IsChanged { * @param data 要设置的新数据值 * @return 设置前的旧数据值 */ - fun setAndGet(data: T): T? { + fun setAndGet(data: T): T { return atomicReferenceData.getAndSet(data) } @@ -89,7 +86,7 @@ class IsChanged { * @return 如果值发生变化返回 true,否则返回 false */ fun setAndChanged(data: T): Boolean { - var currentData: T? + var currentData: T do { currentData = get() // 如果新值与当前值相等,则认为没有变化,直接返回 false diff --git a/src/main/kotlin/com/mingliqiye/utils/file/FileUtils.kt b/src/main/kotlin/com/mingliqiye/utils/file/FileUtils.kt index 56d8375..f4be62d 100644 --- a/src/main/kotlin/com/mingliqiye/utils/file/FileUtils.kt +++ b/src/main/kotlin/com/mingliqiye/utils/file/FileUtils.kt @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile FileUtils.kt - * LastUpdate 2026-01-11 09:20:20 + * LastUpdate 2026-01-28 10:49:14 * UpdateUser MingLiPro */ @file:JvmName("FileUtils") @@ -642,27 +642,6 @@ fun File.copyTo(target: File) { target.parentFile?.mkdirs() this.copyTo(target, overwrite = true) } - -// 删除文件 -/** - * 删除文件 - * - * @return 删除操作是否成功 - */ -fun File.delete(): Boolean { - return this.delete() -} - -// 检查文件是否存在 -/** - * 检查文件是否存在 - * - * @return 文件是否存在 - */ -fun File.exists(): Boolean { - return this.exists() -} - // 获取文件大小 /** * 获取文件大小 diff --git a/src/main/kotlin/com/mingliqiye/utils/functions/Functions.kt b/src/main/kotlin/com/mingliqiye/utils/functions/Functions.kt index ece9ab7..ed87d46 100644 --- a/src/main/kotlin/com/mingliqiye/utils/functions/Functions.kt +++ b/src/main/kotlin/com/mingliqiye/utils/functions/Functions.kt @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile Functions.kt - * LastUpdate 2026-01-11 09:10:48 + * LastUpdate 2026-02-05 11:20:59 * UpdateUser MingLiPro */ @@ -24,6 +24,19 @@ package com.mingliqiye.utils.functions +fun T.with(function: Function): T = function.call().let { + this +} + +fun T.with(function: P1Function): T = function.call(this).let { + this +} + +@FunctionalInterface +fun interface Function { + fun call() +} + @FunctionalInterface fun interface P1Function

fX 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 Ur~15xg9d)2B^1O9#Pq%Y50bKPU;qFB literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/mingli-utils/lang/en_US.json b/src/main/resources/assets/mingli-utils/lang/en_US.json new file mode 100644 index 0000000..7124897 --- /dev/null +++ b/src/main/resources/assets/mingli-utils/lang/en_US.json @@ -0,0 +1,42 @@ +{ + "com.mingliqiye.utils": { + "i18n": { + "readjson": { + "error": "Failed to load language file %s" + } + }, + "springboot.autoconfigure": { + "AutoConfiguration": { + "jdkRuntime": "JDK Runtime Environment Version", + "pid": "Process PID", + "computerName": "Computer Name", + "userName": "User Name", + "time": "Current Time", + "buildTime": "Build Time", + "groupId": "Group ID", + "artifactId": "Artifact ID", + "version": "Version", + "buildJdkVersion": "Build JDK Version", + "author": "Author", + "website": "Official Website", + "bean": "MingliUtils Auto Configuration Bean Success" + }, + "JsonApiAutoConfiguration": { + "jsonause": "JSONA Using %s", + "jsonapiconfiged": "JsonApi Bean Auto Configuration Success", + "jacksonserializers": "MingliUtils Jackson Serializers created" + } + }, + "string": { + "StringUtils": { + "format": { + "warn": { + "placeholder": "Placeholder Count: %s, Argument Count: %s", + "template": "Template: %s", + "arguments": "Arguments: [%s]" + } + } + } + } + } +} diff --git a/src/main/resources/assets/mingli-utils/lang/zh_CN.json b/src/main/resources/assets/mingli-utils/lang/zh_CN.json new file mode 100644 index 0000000..528916f --- /dev/null +++ b/src/main/resources/assets/mingli-utils/lang/zh_CN.json @@ -0,0 +1,42 @@ +{ + "com.mingliqiye.utils": { + "i18n": { + "readjson": { + "error": "加载语言文件失败 %s" + } + }, + "springboot.autoconfigure": { + "AutoConfiguration": { + "jdkRuntime": "Jdk运行环境版本", + "pid": "程序Pid", + "computerName": "计算机名", + "userName": "用户名", + "time": "当前时间", + "buildTime": "编译时间", + "groupId": "组Id", + "artifactId": "组件Id", + "version": "版本", + "buildJdkVersion": "编译Jdk版本", + "author": "作者", + "website": "官方网址", + "bean": "MingliUtils 自动配置 Bean 成功" + }, + "JsonApiAutoConfiguration": { + "jsonause": "JSONA 使用 %s", + "jsonapiconfiged": "JsonApi Bean 自动配置成功", + "jacksonserializers": "MingliUtils Jackson 序列化器注册成功" + } + }, + "string": { + "StringUtils": { + "format": { + "warn": { + "placeholder": "占位符数量: %s, 参数数量: %s", + "template": "模板: %s", + "arguments": "参数: [%s]" + } + } + } + } + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..07f4024 --- /dev/null +++ b/src/main/resources/fabric.mod.json @@ -0,0 +1,27 @@ +{ + "schemaVersion": 1, + "id": "mingli-utils", + "groupsId": "${GROUPSID}", + "artifactId": "${ARTIFACTID}", + "name": "Mingli-Utils", + "version": "${VERSIONS}", + "environment": "*", + "license": "Apache-2.0", + "icon": "assets/mingli-utils/icon.png", + "contact": { + "homepage": "https://git.mingliqiye.com/minglipro/mingli-utils", + "issues": "https://git.mingliqiye.com/minglipro/mingli-utils/issues", + "sources": "https://git.mingliqiye.com/minglipro/mingli-utils" + }, + "buildTime": "${buildTime}", + "jdk": { + "version": "${JDKVERSIONS}" + }, + "authors": [ + "minglipro" + ], + "depends": { + "fabricloader": "*" + }, + "description": "A Java/kotlin Utils" +} diff --git a/src/test/kotlin/com/mingliqiye/utils/BaseTest.kt b/src/test/kotlin/com/mingliqiye/utils/BaseTest.kt new file mode 100644 index 0000000..bff6822 --- /dev/null +++ b/src/test/kotlin/com/mingliqiye/utils/BaseTest.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.test + * CurrentFile BaseTest.kt + * LastUpdate 2026-02-08 03:05:52 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils + +import com.mingliqiye.utils.base.code.* +import com.mingliqiye.utils.uuid.UUID +import org.junit.jupiter.api.Test + + +class BaseTest { + @Test + fun testBase2() { + val uuid = UUID.getV7() + val s = Base2.encode(uuid.toBytes()) + assert(uuid == UUID.of(Base2.decode(s))) + } + + @Test + fun testBase10() { + val uuid = UUID.getV7() + val s = Base10.encode(uuid.toBytes()) + assert(uuid == UUID.of(Base10.decode(s))) + } + + @Test + fun testBase16() { + val uuid = UUID.getV7() + val s = Base16.encode(uuid.toBytes()) + assert(uuid == UUID.of(Base16.decode(s))) + } + + @Test + fun testBase91() { + val uuid = UUID.getV7() + val s = Base91.encode(uuid.toBytes()) + assert(uuid == UUID.of(Base91.decode(s))) + } + + @Test + fun testBase256() { + val uuid = UUID.getV7() + val s = Base256.encode(uuid.toBytes()) + assert(uuid == UUID.of(Base256.decode(s))) + } +} diff --git a/src/test/kotlin/com/mingliqiye/utils/JsonTest.kt b/src/test/kotlin/com/mingliqiye/utils/JsonTest.kt index a18dc25..b68f222 100644 --- a/src/test/kotlin/com/mingliqiye/utils/JsonTest.kt +++ b/src/test/kotlin/com/mingliqiye/utils/JsonTest.kt @@ -16,28 +16,33 @@ * ProjectName mingli-utils * ModuleName mingli-utils.test * CurrentFile JsonTest.kt - * LastUpdate 2026-02-05 10:57:18 + * LastUpdate 2026-02-08 02:29:27 * UpdateUser MingLiPro */ package com.mingliqiye.utils +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import com.mingliqiye.utils.annotation.UUIDJsonFormat +import com.mingliqiye.utils.array.toHexString import com.mingliqiye.utils.base.BaseType import com.mingliqiye.utils.io.IO.println import com.mingliqiye.utils.json.api.JSONA import com.mingliqiye.utils.json.api.JacksonJsonApi import com.mingliqiye.utils.json.converters.DateTimeJsonConverter import com.mingliqiye.utils.json.converters.UUIDJsonConverter +import com.mingliqiye.utils.json.converters.base.JackSonJsonConverter.Companion.addJsonConverter +import com.mingliqiye.utils.json.converters.base.getJsonConverter +import com.mingliqiye.utils.logger.MingLiLoggerFactory import com.mingliqiye.utils.string.formatd -import com.mingliqiye.utils.time.DateTime -import com.mingliqiye.utils.time.DateTimeJsonFormat -import com.mingliqiye.utils.time.Formatter import com.mingliqiye.utils.uuid.UUID import com.mingliqiye.utils.uuid.UUIDFormatType -import com.mingliqiye.utils.uuid.UUIDJsonFormat +import com.mingliqiye.utils.uuid.mysqlToUuid +import com.mingliqiye.utils.uuid.uuidToMysql +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test - class JsonTest { @Test fun testJSONA() { @@ -55,28 +60,72 @@ class JsonTest { @Test fun testBANNER() { + val log = MingLiLoggerFactory.getLogger() com.mingliqiye.utils.springboot.autoconfigure.AutoConfiguration.printBanner() } + + @Test + fun testMysqld() { + val v = UUID.of("c3efb797-0323-11f1-8791-3aff070ec47d") + val ba = uuidToMysql(v.toBytes()) + val ca = mysqlToUuid(ba) + assertEquals( + "11F10323C3EFB79787913AFF070EC47D", + ba.toHexString().println() + ) + assertEquals( + "C3EFB797032311F187913AFF070EC47D", + ca.toHexString().println() + ) + } + + @Test + fun testGson2() { + getJsonConverter() + getJsonConverter() + } + + @Test + fun testGson() { + + val obm = jacksonObjectMapper() + .addJsonConverter() + .addJsonConverter() + + obm.readValue>>>( + obm.writeValueAsString( + AC(c = CC(b = ZZ(b = UUID.getV7()))) + ).println() + ).println() + } } data class AC( var a: String = "AC", - @field:DateTimeJsonFormat(Formatter.ISO8601, repcZero = false) - var time: DateTime = DateTime.now(), @field:UUIDJsonFormat( base = BaseType.BASE256, value = UUIDFormatType.UPPER_NO_SPACE ) var uuid: UUID = UUID.getV4(), - var b: T + var b: BC = BC(), + var c: T ) -data class BC( +data class BC( var a: String = "BC", - var b: T ) data class CC( var a: String = "BC", var b: T ) + + +data class ZZ( + var a: String = "ZZ", + @field:UUIDJsonFormat( + base = BaseType.BASE256, + value = UUIDFormatType.UPPER_NO_SPACE + ) + var b: T +) diff --git a/src/test/resources/lang/zh_CN.json b/src/test/resources/lang/zh_CN.json new file mode 100644 index 0000000..117e973 --- /dev/null +++ b/src/test/resources/lang/zh_CN.json @@ -0,0 +1,23 @@ +{ + "a": { + "b": { + "c": { + "d": "from {} d" + } + } + }, + "b": { + "b": { + "c": { + "d": "from {} d" + } + } + }, + "c": { + "b": { + "c": { + "d": "from {} d" + } + } + } +} -- 2.47.2

{ fun call(p: P) @@ -128,3 +141,597 @@ fun interface P10Function { fun interface P10RFunction { fun call(p: P, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9): R } + +@FunctionalInterface +fun interface P11Function { + fun call(p: P, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10) +} + +@FunctionalInterface +fun interface P11RFunction { + fun call(p: P, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10): R +} + +@FunctionalInterface +fun interface P12Function { + fun call(p: P, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11) +} + +@FunctionalInterface +fun interface P12RFunction { + fun call(p: P, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11): R +} + +@FunctionalInterface +fun interface P13Function { + fun call(p: P, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12) +} + +@FunctionalInterface +fun interface P13RFunction { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12 + ): R +} + +@FunctionalInterface +fun interface P14Function { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13 + ) +} + +@FunctionalInterface +fun interface P14RFunction { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13 + ): R +} + +@FunctionalInterface +fun interface P15Function { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14 + ) +} + +@FunctionalInterface +fun interface P15RFunction { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14 + ): R +} + +@FunctionalInterface +fun interface P16Function { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14, + p15: P15 + ) +} + +@FunctionalInterface +fun interface P16RFunction { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14, + p15: P15 + ): R +} + +@FunctionalInterface +fun interface P17Function { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14, + p15: P15, + p16: P16 + ) +} + +@FunctionalInterface +fun interface P17RFunction { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14, + p15: P15, + p16: P16 + ): R +} + +@FunctionalInterface +fun interface P18Function { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14, + p15: P15, + p16: P16, + p17: P17 + ) +} + +@FunctionalInterface +fun interface P18RFunction { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14, + p15: P15, + p16: P16, + p17: P17 + ): R +} + +@FunctionalInterface +fun interface P19Function { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14, + p15: P15, + p16: P16, + p17: P17, + p18: P18 + ) +} + +@FunctionalInterface +fun interface P19RFunction { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14, + p15: P15, + p16: P16, + p17: P17, + p18: P18 + ): R +} + +@FunctionalInterface +fun interface P20Function { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14, + p15: P15, + p16: P16, + p17: P17, + p18: P18, + p19: P19 + ) +} + +@FunctionalInterface +fun interface P20RFunction { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14, + p15: P15, + p16: P16, + p17: P17, + p18: P18, + p19: P19 + ): R +} + +@FunctionalInterface +fun interface P21Function { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14, + p15: P15, + p16: P16, + p17: P17, + p18: P18, + p19: P19, + p20: P20 + ) +} + +@FunctionalInterface +fun interface P21RFunction { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14, + p15: P15, + p16: P16, + p17: P17, + p18: P18, + p19: P19, + p20: P20 + ): R +} + +@FunctionalInterface +fun interface P22Function { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14, + p15: P15, + p16: P16, + p17: P17, + p18: P18, + p19: P19, + p20: P20, + p21: P21 + ) +} + +@FunctionalInterface +fun interface P22RFunction { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14, + p15: P15, + p16: P16, + p17: P17, + p18: P18, + p19: P19, + p20: P20, + p21: P21 + ): R +} + +@FunctionalInterface +fun interface P23Function { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14, + p15: P15, + p16: P16, + p17: P17, + p18: P18, + p19: P19, + p20: P20, + p21: P21, + p22: P22 + ) +} + +@FunctionalInterface +fun interface P23RFunction { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14, + p15: P15, + p16: P16, + p17: P17, + p18: P18, + p19: P19, + p20: P20, + p21: P21, + p22: P22 + ): R +} + +@FunctionalInterface +fun interface P24Function { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14, + p15: P15, + p16: P16, + p17: P17, + p18: P18, + p19: P19, + p20: P20, + p21: P21, + p22: P22, + p23: P23 + ) +} + +@FunctionalInterface +fun interface P24RFunction { + fun call( + p: P, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9, + p10: P10, + p11: P11, + p12: P12, + p13: P13, + p14: P14, + p15: P15, + p16: P16, + p17: P17, + p18: P18, + p19: P19, + p20: P20, + p21: P21, + p22: P22, + p23: P23 + ): R +} diff --git a/src/main/kotlin/com/mingliqiye/utils/functions/Pipeline.kt b/src/main/kotlin/com/mingliqiye/utils/functions/Pipeline.kt new file mode 100644 index 0000000..7dfc794 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/functions/Pipeline.kt @@ -0,0 +1,272 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile Pipeline.kt + * LastUpdate 2026-02-05 09:53:44 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.functions + +import com.mingliqiye.utils.require.Require +import java.util.* + +/** + * 流水线操作类,提供链式调用的操作方式来处理数据转换和操作 + * @param T 泛型类型,表示流水线中处理的数据类型 + */ +class Pipeline(private val value: T) { + + /** + * 对当前值进行转换操作,并返回新的Pipeline实例 + * @param transformer 转换函数,接收当前值并返回转换后的结果 + * @return 包含转换后结果的新Pipeline实例 + */ + fun transform(transformer: P1RFunction): Pipeline = Pipeline(transformer.call(value)) + + /** + * 对当前值进行转换操作,功能与transform相同,提供不同的方法名选择 + * @param transformer 转换函数,接收当前值并返回转换后的结果 + * @return 包含转换后结果的新Pipeline实例 + */ + fun then(transformer: P1RFunction): Pipeline = transform(transformer) + + + /** + * 消费当前值但不改变流水线状态,用于执行副作用操作 + * @param consumer 消费函数,接收当前值进行消费操作 + * @return 当前Pipeline实例,支持链式调用 + */ + fun consume(consumer: P1Function): Pipeline { + consumer.call(value) + return this + } + + /** + * 判断当前值是否为null + * @return true表示当前值为null,false表示不为null + */ + fun isNull(): Boolean = (value == null) + + /** + * 判断当前值是否不为null + * @return true表示当前值不为null,false表示为null + */ + fun isNotNull(): Boolean = (value != null) + + /** + * 根据条件过滤当前值,如果条件满足则保持原值,否则设置为null + * @param predicate 过滤条件函数,接收当前值并返回布尔值 + * @return 包含过滤后结果的Pipeline实例 + */ + fun filter(predicate: P1RFunction): Pipeline = + if (predicate.call(value)) Pipeline(value) else Pipeline(null) + + /** + * 获取当前流水线中的值 + * @return 当前流水线中存储的值 + */ + fun getValue(): T = value + + /** + * 安全地转换当前值,允许转换结果为null + * @param transformer 转换函数,可能返回null + * @return 包含转换后结果(可能为null)的Pipeline实例 + */ + fun safeTransform(transformer: P1RFunction): Pipeline = Pipeline(transformer.call(value)) + + /** + * 将当前值映射为另一个Pipeline实例 + * @param mapper 映射函数,接收当前值并返回Pipeline实例 + * @return 映射后的Pipeline实例 + */ + fun flatMap(mapper: P1RFunction>): Pipeline = mapper.call(value) + + /** + * 将当前值映射为非null结果,如果当前值为null则直接返回null + * @param mapper 映射函数,可能返回null + * @return 包含映射后结果的Pipeline实例 + */ + fun mapNotNull(mapper: P1RFunction): Pipeline = + if (value != null) Pipeline(mapper.call(value)!!) else Pipeline(null) + + + /** + * 将当前值强制转换为指定类型 + * @param type 目标类型Class对象 + * @return 包含转换后类型的Pipeline实例 + * @throws ClassCastException 当类型转换失败时抛出异常 + */ + @Throws(ClassCastException::class) + fun cast(type: Class): Pipeline = Pipeline(value as E) + + /** + * 根据条件判断是否保留当前值,条件满足时保留,否则返回null + * @param predicate 条件判断函数 + * @return 包含条件判断结果的Pipeline实例 + */ + fun takeIf(predicate: P1RFunction): Pipeline = Pipeline(if (predicate.call(value)) value else null) + + /** + * 根据条件判断是否保留当前值,条件不满足时保留,否则返回null + * @param predicate 条件判断函数 + * @return 包含条件判断结果的Pipeline实例 + */ + fun takeUnless(predicate: P1RFunction): Pipeline = + Pipeline(if (!predicate.call(value)) value else null) + + /** + * 在当前值上执行指定操作并返回操作结果 + * @param block 执行函数,接收当前值并返回结果 + * @return 包含执行结果的Pipeline实例 + */ + fun let(block: P1RFunction): Pipeline = Pipeline(block.call(value)) + + /** + * 如果当前值不为null则返回自身,否则返回默认值的Pipeline实例 + * @param defaultValue 默认值 + * @return 当前值或默认值的Pipeline实例 + */ + fun orElse(defaultValue: T): Pipeline = if (value != null) this else Pipeline(defaultValue) + + /** + * 如果当前值不为null则返回自身,否则通过供应商函数获取默认值 + * @param supplier 默认值供应商函数 + * @return 当前值或供应商提供的值的Pipeline实例 + */ + fun orElseGet(supplier: RFunction): Pipeline = if (value != null) this else Pipeline(supplier.call()) + + /** + * 验证当前值是否等于指定值,如果不相等则抛出异常 + * @param any 比较的目标值 + * @param message 错误消息 + * @param exception 异常类型,默认为IllegalArgumentException + * @return 当前Pipeline实例 + */ + fun require( + any: Any, message: String, exception: Class = IllegalArgumentException::class.java + ): Pipeline { + Require.require(Objects.equals(any, value), message, exception) + return this + } + + + /** + * 验证当前值是否等于指定值,如果不相等则抛出IllegalArgumentException异常 + * @param any 比较的目标值 + * @param message 错误消息 + * @return 当前Pipeline实例 + */ + fun require( + any: Any, message: String + ): Pipeline { + Require.require(any == value, message) + return this + } + + /** + * 使用P1RFunction验证当前值是否满足条件,不满足则抛出异常 + * @param must 验证条件函数 + * @param message 错误消息 + * @param exception 异常类型,默认为IllegalArgumentException + * @return 当前Pipeline实例 + */ + fun require( + must: P1RFunction, + message: String, + exception: Class = IllegalArgumentException::class.java + ): Pipeline { + Require.require(must.call(value), message, exception) + return this + } + + /** + * 使用P1RFunction验证当前值是否满足条件,不满足则抛出IllegalArgumentException异常 + * @param must 验证条件函数 + * @param message 错误消息 + * @return 当前Pipeline实例 + */ + fun require( + must: P1RFunction, message: String + ): Pipeline { + Require.require(must.call(value), message, IllegalArgumentException::class.java) + return this + } + + + /** + * 对当前值执行指定操作但不改变流水线状态 + * @param block 执行函数,接收当前值 + * @return 当前Pipeline实例 + */ + fun also(block: P1Function): Pipeline { + block.call(value) + return this + } + + /** + * 验证当前值是否满足指定条件,不满足则抛出IllegalArgumentException异常 + * @param predicate 验证条件函数 + * @param lazyMessage 延迟错误消息生成函数,默认返回"Requirement failed" + * @return 当前Pipeline实例 + */ + fun require( + predicate: P1RFunction, + lazyMessage: P1RFunction = P1RFunction { "Requirement failed" } + ): Pipeline { + if (!predicate.call(value)) { + throw IllegalArgumentException(lazyMessage.call(value).toString()) + } + return this + } + + /** + * 使用比较器比较当前值与另一个值 + * @param other 另一个比较值 + * @param comparator 比较器 + * @return 比较结果,负数表示小于,0表示相等,正数表示大于 + */ + fun compareWith(other: T, comparator: Comparator): Int = comparator.compare(value, other) + + /** + * 使用选择器将当前值转换为可比较类型后与另一个值比较 + * @param other 另一个比较值 + * @param selector 选择器函数,将当前值转换为可比较类型 + * @return 比较结果 + */ + fun > compareTo(other: R, selector: P1RFunction): Int = + selector.call(value).compareTo(other) + + + companion object { + /** + * 创建包含指定值的Pipeline实例 + * @param value 要包装的值 + * @return 新的Pipeline实例 + */ + @JvmStatic + fun of(value: T): Pipeline = Pipeline(value) + + /** + * 通过供应商函数创建Pipeline实例 + * @param transformer 值供应商函数 + * @return 新的Pipeline实例 + */ + @JvmStatic + fun of(transformer: RFunction): Pipeline = Pipeline(transformer.call()) + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/http/Response.kt b/src/main/kotlin/com/mingliqiye/utils/http/Response.kt index 340a20d..fb3f483 100644 --- a/src/main/kotlin/com/mingliqiye/utils/http/Response.kt +++ b/src/main/kotlin/com/mingliqiye/utils/http/Response.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,48 +16,243 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile Response.kt - * LastUpdate 2025-09-15 09:04:05 + * LastUpdate 2026-02-03 20:09:10 * UpdateUser MingLiPro */ package com.mingliqiye.utils.http import com.mingliqiye.utils.time.DateTime -import com.mingliqiye.utils.time.Formatter +/** + * HTTP响应数据类 + * 封装了时间、消息、数据和状态码等响应信息 + * + * @param T 响应数据的类型 + * @property time 响应时间 + * @property message 响应消息 + * @property data 响应数据 + * @property statusCode 状态码 + */ data class Response( - val time: String, - var message: String, - var data: T?, - var statusCode: Int + private var time: DateTime, private var message: String, private var data: T?, private var statusCode: Int ) { - companion object { - @JvmStatic - fun ok(data: T): Response { - return Response(DateTime.now().format(Formatter.STANDARD_DATETIME_MILLISECOUND7), "操作成功", data, 200) - } + /** + * 创建一个成功的响应对象(仅包含消息) + * + * @param message 响应消息 + * @return Response 成功的响应对象 + */ @JvmStatic - fun ok(message: String): Response { - return Response(DateTime.now().format(Formatter.STANDARD_DATETIME_MILLISECOUND7), message, null, 200) - } + fun ok(message: String) = Response( + time = DateTime.now(), + message = message, + data = null, + statusCode = 200 + ) + + /** + * 创建一个成功的响应对象(仅包含数据) + * + * @param T 响应数据的类型 + * @param data 响应数据 + * @return Response 成功的响应对象 + */ + @JvmStatic + fun okData(data: T) = Response( + time = DateTime.now(), + message = "操作成功", + data = data, + statusCode = 200 + ) + + /** + * 创建一个成功的响应对象(包含数据和消息) + * + * @param T 响应数据的类型 + * @param data 响应数据 + * @param message 响应消息 + * @return Response 成功的响应对象 + */ + @JvmStatic + fun okData(data: T, message: String) = Response( + time = DateTime.now(), + message = message, + data = data, + statusCode = 200 + ) + + + /** + * 创建一个指定状态码的响应对象(仅包含状态码) + * + * @param statusCode 状态码 + * @return Response 指定状态码的响应对象 + */ + @JvmStatic + fun code(statusCode: Int) = Response( + time = DateTime.now(), + message = "操作成功", + data = null, + statusCode = statusCode + ) + + /** + * 创建一个指定状态码的响应对象(包含状态码和消息) + * + * @param statusCode 状态码 + * @param message 响应消息 + * @return Response 指定状态码的响应对象 + */ + @JvmStatic + fun code(statusCode: Int, message: String) = Response( + time = DateTime.now(), + message = message, + data = null, + statusCode = statusCode + ) } + /** + * 默认构造函数,创建一个默认的成功响应对象 + */ + constructor() : this( + time = DateTime.now(), message = "操作成功", statusCode = 200, data = null + ) + + + /** + * 获取响应时间 + * + * @return DateTime 响应时间 + */ + fun getTime(): DateTime = time + + /** + * 设置响应时间 + * + * @param dateTime 响应时间 + * @return Response 当前响应对象(用于链式调用) + */ + fun setTime(dateTime: DateTime): Response { + time = dateTime + return this + } + + + /** + * 获取响应消息 + * + * @return String 响应消息 + */ + fun getMessage(): String = message + + /** + * 设置响应消息 + * + * @param message 响应消息 + * @return Response 当前响应对象(用于链式调用) + */ fun setMessage(message: String): Response { this.message = message return this } - fun setData(data: T): Response { - this.data = data - return ok(this.data) - .setMessage(this.message) - .setStatusCode(this.statusCode) + /** + * 获取响应数据 + * + * @return T? 响应数据,可能为空 + */ + fun getData(): T? = data + + /** + * 获取非空数据对象 + * + * 此方法强制解包data属性,如果data为null则会抛出NullPointerException异常 + * + * @param T 泛型类型参数,表示返回的数据类型 + * @return 返回非空的数据对象,类型为T + * @throws NullPointerException 当data属性为null时抛出此异常 + */ + @Throws(NullPointerException::class) + fun notNullData(): T { + return data ?: throw NullPointerException("at Response.notNullData() because data is null") } + + /** + * 设置响应数据 + * + * @param D 数据类型 + * @param data 响应数据 + * @return Response 当前响应对象(用于链式调用) + * + */ + fun setData(data: T): Response { + this.data = data + return this + } + + /** + * 创建一个新的Response对象,使用指定的时间 + * + * @param time 新的响应时间 + * @return Response 新的响应对象 + */ + fun withTime(time: DateTime): Response = + if (this.time == time) this else Response(time, message, data, statusCode) + + /** + * 创建一个新的Response对象,使用指定的数据 + * + * @param D 新的数据类型 + * @param data 新的响应数据 + * @return Response 新的响应对象 + */ + fun withData(data: D): Response = + Response(this.time, this.message, data, this.statusCode) + + /** + * 创建一个新的Response对象,使用指定的消息 + * + * @param message 新的响应消息 + * @return Response 新的响应对象 + */ + fun withMessage(message: String): Response = + if (this.message == message) this else Response(time, message, data, statusCode) + + /** + * 创建一个新的Response对象,使用指定的状态码 + * + * @param statusCode 新的状态码 + * @return Response 新的响应对象 + */ + fun withStatusCode(statusCode: Int): Response = + if (this.statusCode == statusCode) this else Response(time, message, data, statusCode) + + /** + * 获取状态码 + * + * @return Int 状态码 + */ + fun getStatusCode(): Int = statusCode + + /** + * 设置状态码 + * + * @param statusCode 状态码 + * @return Response 当前响应对象(用于链式调用) + */ fun setStatusCode(statusCode: Int): Response { this.statusCode = statusCode return this } + + /** + * 返回响应对象的字符串表示 + */ + override fun toString(): String = + "Response(time=${getTime()}, message=${getMessage()}, data:[${data?.javaClass?.simpleName}]=${getData()}, statusCode=${getStatusCode()})" } diff --git a/src/main/kotlin/com/mingliqiye/utils/http/exception/Exceptions.kt b/src/main/kotlin/com/mingliqiye/utils/http/exception/Exceptions.kt new file mode 100644 index 0000000..6710cba --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/http/exception/Exceptions.kt @@ -0,0 +1,372 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile Exceptions.kt + * LastUpdate 2026-02-05 11:12:36 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.http.exception + +/** + * 表示 HTTP 异常的基类,继承自 [RuntimeException]。 + * + * @param statusCode HTTP 状态码 + * @param message 异常信息,默认为 null + * @param cause 异常原因,默认为 null + */ +sealed class HttpException( + open val statusCode: Int, + override val message: String? = null, + override val cause: Throwable? = null +) : RuntimeException(message, cause) + +// 3xx - 重定向异常 + +/** + * 表示 HTTP 300 Multiple Choices 异常。 + * + * @param message 异常信息,默认为 "Multiple Choices" + * @param cause 异常原因,默认为 null + */ +class MultipleChoicesException( + override val message: String? = "Multiple Choices", + override val cause: Throwable? = null, +) : HttpException(300, message, cause) + +/** + * 表示 HTTP 301 Moved Permanently 异常。 + * + * @param message 异常信息,默认为 "Moved Permanently" + * @param cause 异常原因,默认为 null + */ +class MovedPermanentlyException( + override val message: String? = "Moved Permanently", + override val cause: Throwable? = null, +) : HttpException(301, message, cause) + +/** + * 表示 HTTP 302 Found 异常。 + * + * @param message 异常信息,默认为 "Found" + * @param cause 异常原因,默认为 null + */ +class FoundException( + override val message: String? = "Found", + override val cause: Throwable? = null, +) : HttpException(302, message, cause) + +/** + * 表示 HTTP 303 See Other 异常。 + * + * @param message 异常信息,默认为 "See Other" + * @param cause 异常原因,默认为 null + */ +class SeeOtherException( + override val message: String? = "See Other", + override val cause: Throwable? = null, +) : HttpException(303, message, cause) + +/** + * 表示 HTTP 304 Not Modified 异常。 + * + * @param message 异常信息,默认为 "Not Modified" + * @param cause 异常原因,默认为 null + */ +class NotModifiedException( + override val message: String? = "Not Modified", + override val cause: Throwable? = null, +) : HttpException(304, message, cause) + +/** + * 表示 HTTP 305 Use Proxy 异常。 + * + * @param message 异常信息,默认为 "Use Proxy" + * @param cause 异常原因,默认为 null + */ +class UseProxyException( + override val message: String? = "Use Proxy", + override val cause: Throwable? = null, +) : HttpException(305, message, cause) + +/** + * 表示 HTTP 307 Temporary Redirect 异常。 + * + * @param message 异常信息,默认为 "Temporary Redirect" + * @param cause 异常原因,默认为 null + */ +class TemporaryRedirectException( + override val message: String? = "Temporary Redirect", + override val cause: Throwable? = null, +) : HttpException(307, message, cause) + +// 4xx - 客户端错误异常 + +/** + * 表示 HTTP 400 Bad Request 异常。 + * + * @param message 异常信息,默认为 "Bad Request" + * @param cause 异常原因,默认为 null + */ +class BadRequestException( + override val message: String? = "Bad Request", + override val cause: Throwable? = null, +) : HttpException(400, message, cause) + +/** + * 表示 HTTP 401 Unauthorized 异常。 + * + * @param message 异常信息,默认为 "Unauthorized" + * @param cause 异常原因,默认为 null + */ +class UnauthorizedException( + override val message: String? = "Unauthorized", + override val cause: Throwable? = null, +) : HttpException(401, message, cause) + +/** + * 表示 HTTP 402 Payment Required 异常。 + * + * @param message 异常信息,默认为 "Payment Required" + * @param cause 异常原因,默认为 null + */ +class PaymentRequiredException( + override val message: String? = "Payment Required", + override val cause: Throwable? = null, +) : HttpException(402, message, cause) + +/** + * 表示 HTTP 403 Forbidden 异常。 + * + * @param message 异常信息,默认为 "Forbidden" + * @param cause 异常原因,默认为 null + */ +class ForbiddenException( + override val message: String? = "Forbidden", + override val cause: Throwable? = null, +) : HttpException(403, message, cause) + +/** + * 表示 HTTP 404 Not Found 异常。 + * + * @param message 异常信息,默认为 "Not Found" + * @param cause 异常原因,默认为 null + */ +class NotFoundException( + override val message: String? = "Not Found", + override val cause: Throwable? = null, +) : HttpException(404, message, cause) + +/** + * 表示 HTTP 405 Method Not Allowed 异常。 + * + * @param message 异常信息,默认为 "Method Not Allowed" + * @param cause 异常原因,默认为 null + */ +class MethodNotAllowedException( + override val message: String? = "Method Not Allowed", + override val cause: Throwable? = null, +) : HttpException(405, message, cause) + +/** + * 表示 HTTP 406 Not Acceptable 异常。 + * + * @param message 异常信息,默认为 "Not Acceptable" + * @param cause 异常原因,默认为 null + */ +class NotAcceptableException( + override val message: String? = "Not Acceptable", + override val cause: Throwable? = null, +) : HttpException(406, message, cause) + +/** + * 表示 HTTP 407 Proxy Authentication Required 异常。 + * + * @param message 异常信息,默认为 "Proxy Authentication Required" + * @param cause 异常原因,默认为 null + */ +class ProxyAuthenticationRequiredException( + override val message: String? = "Proxy Authentication Required", + override val cause: Throwable? = null, +) : HttpException(407, message, cause) + +/** + * 表示 HTTP 408 Request Timeout 异常。 + * + * @param message 异常信息,默认为 "Request Timeout" + * @param cause 异常原因,默认为 null + */ +class RequestTimeoutException( + override val message: String? = "Request Timeout", + override val cause: Throwable? = null, +) : HttpException(408, message, cause) + +/** + * 表示 HTTP 409 Conflict 异常。 + * + * @param message 异常信息,默认为 "Conflict" + * @param cause 异常原因,默认为 null + */ +class ConflictException( + override val message: String? = "Conflict", + override val cause: Throwable? = null, +) : HttpException(409, message, cause) + +/** + * 表示 HTTP 410 Gone 异常。 + * + * @param message 异常信息,默认为 "Gone" + * @param cause 异常原因,默认为 null + */ +class GoneException( + override val message: String? = "Gone", + override val cause: Throwable? = null, +) : HttpException(410, message, cause) + +/** + * 表示 HTTP 411 Length Required 异常。 + * + * @param message 异常信息,默认为 "Length Required" + * @param cause 异常原因,默认为 null + */ +class LengthRequiredException( + override val message: String? = "Length Required", + override val cause: Throwable? = null, +) : HttpException(411, message, cause) + +/** + * 表示 HTTP 412 Precondition Failed 异常。 + * + * @param message 异常信息,默认为 "Precondition Failed" + * @param cause 异常原因,默认为 null + */ +class PreconditionFailedException( + override val message: String? = "Precondition Failed", + override val cause: Throwable? = null, +) : HttpException(412, message, cause) + +/** + * 表示 HTTP 413 Request Entity Too Large 异常。 + * + * @param message 异常信息,默认为 "Request Entity Too Large" + * @param cause 异常原因,默认为 null + */ +class RequestEntityTooLargeException( + override val message: String? = "Request Entity Too Large", + override val cause: Throwable? = null, +) : HttpException(413, message, cause) + +/** + * 表示 HTTP 414 Request URI Too Large 异常。 + * + * @param message 异常信息,默认为 "Request URI Too Large" + * @param cause 异常原因,默认为 null + */ +class RequestUriTooLargeException( + override val message: String? = "Request URI Too Large", + override val cause: Throwable? = null, +) : HttpException(414, message, cause) + +/** + * 表示 HTTP 415 Unsupported Media Type 异常。 + * + * @param message 异常信息,默认为 "Unsupported Media Type" + * @param cause 异常原因,默认为 null + */ +class UnsupportedMediaTypeException( + override val message: String? = "Unsupported Media Type", + override val cause: Throwable? = null, +) : HttpException(415, message, cause) + +/** + * 表示 HTTP 416 Requested Range Not Satisfiable 异常。 + * + * @param message 异常信息,默认为 "Requested Range Not Satisfiable" + * @param cause 异常原因,默认为 null + */ +class RequestedRangeNotSatisfiableException( + override val message: String? = "Requested Range Not Satisfiable", + override val cause: Throwable? = null, +) : HttpException(416, message, cause) + +// 5xx - 服务器错误异常 + +/** + * 表示 HTTP 500 Internal Server Error 异常。 + * + * @param message 异常信息,默认为 "Internal Server Error" + * @param cause 异常原因,默认为 null + */ +class InternalServerErrorException( + override val message: String? = "Internal Server Error", + override val cause: Throwable? = null, +) : HttpException(500, message, cause) + +/** + * 表示 HTTP 501 Not Implemented 异常。 + * + * @param message 异常信息,默认为 "Not Implemented" + * @param cause 异常原因,默认为 null + */ +class NotImplementedException( + override val message: String? = "Not Implemented", + override val cause: Throwable? = null, +) : HttpException(501, message, cause) + +/** + * 表示 HTTP 502 Bad Gateway 异常。 + * + * @param message 异常信息,默认为 "Bad Gateway" + * @param cause 异常原因,默认为 null + */ +class BadGatewayException( + override val message: String? = "Bad Gateway", + override val cause: Throwable? = null, +) : HttpException(502, message, cause) + +/** + * 表示 HTTP 503 Service Unavailable 异常。 + * + * @param message 异常信息,默认为 "Service Unavailable" + * @param cause 异常原因,默认为 null + */ +class ServiceUnavailableException( + override val message: String? = "Service Unavailable", + override val cause: Throwable? = null, +) : HttpException(503, message, cause) + +/** + * 表示 HTTP 504 Gateway Timeout 异常。 + * + * @param message 异常信息,默认为 "Gateway Timeout" + * @param cause 异常原因,默认为 null + */ +class GatewayTimeoutException( + override val message: String? = "Gateway Timeout", + override val cause: Throwable? = null, +) : HttpException(504, message, cause) + +/** + * 表示 HTTP 505 HTTP Version Not Supported 异常。 + * + * @param message 异常信息,默认为 "HTTP Version Not Supported" + * @param cause 异常原因,默认为 null + */ +class HttpVersionNotSupportedException( + override val message: String? = "HTTP Version Not Supported", + override val cause: Throwable? = null, +) : HttpException(505, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/io/IO.kt b/src/main/kotlin/com/mingliqiye/utils/io/IO.kt index e288ee3..9cf7ca6 100644 --- a/src/main/kotlin/com/mingliqiye/utils/io/IO.kt +++ b/src/main/kotlin/com/mingliqiye/utils/io/IO.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,48 +16,217 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile IO.kt - * LastUpdate 2025-09-20 16:03:14 + * LastUpdate 2026-02-04 13:12:57 * UpdateUser MingLiPro */ package com.mingliqiye.utils.io - -fun Any?.println() { - IO.println(this) -} +import com.mingliqiye.utils.logger.MingLiLoggerFactory +import com.mingliqiye.utils.string.join +import org.slf4j.Logger +import java.io.OutputStream +import java.io.PrintStream -class IO { - companion object { +/** + * IO工具类,提供打印功能和系统输出流重定向到日志的功能 + */ +object IO { - @JvmStatic - fun print(vararg args: Any?) { - printA(" ", *args) - } + @JvmStatic + fun > T.println(): T { + println("{" + ",".join(this) + "}") + return this + } - @JvmStatic - fun println(vararg args: Any?) { - printlnA(" ", *args) - } + @JvmStatic + fun Array.println(): Array { + println("{" + ",".join(this) + "}") + return this + } - @JvmStatic - fun printlnA(sp: String, vararg args: Any?) { - printA(" ", *args) + @JvmStatic + fun T.println(): T { + println(this) + return this + } + + @JvmStatic + var originalOut: PrintStream = System.out + + @JvmStatic + var originalErr: PrintStream = System.err + + + @JvmStatic + val outLog: Logger = MingLiLoggerFactory.getLogger("out") + + @JvmStatic + val errLog: Logger = MingLiLoggerFactory.getLogger("err") + + /** + * 打印多个参数,使用空格分隔 + * @param args 要打印的参数数组 + */ + @JvmStatic + fun print(vararg args: Any?) { + printA(" ", *args) + } + + /** + * 打印多个参数并换行,使用空格分隔 + * @param args 要打印的参数数组 + */ + @JvmStatic + fun println(vararg args: Any?) { + printlnA(" ", *args) + } + + /** + * 打印多个参数并换行,指定分隔符 + * @param sp 分隔符 + * @param args 要打印的参数数组 + */ + @JvmStatic + fun printlnA(sp: String, vararg args: Any?) { + printA(" ", *args) + kotlin.io.println() + } + + /** + * 打印多个参数,指定分隔符 + * @param sp 分隔符,默认为空字符串 + * @param args 要打印的参数数组 + */ + @JvmStatic + fun printA(sp: String = "", vararg args: Any?) { + if (args.isEmpty()) { kotlin.io.println() } - - @JvmStatic - fun printA(sp: String = "", vararg args: Any?) { - if (args.isEmpty()) { - kotlin.io.println() - } - val sb = StringBuilder() - for (i in args.indices) { - sb.append(args[i]) - if (i < args.size - 1) sb.append(sp) - } - kotlin.io.print(sb) + val sb = StringBuilder() + for (i in args.indices) { + sb.append(args[i]) + if (i < args.size - 1) sb.append(sp) } + kotlin.io.print(sb) + } + + @JvmStatic + /** + * 重定向 System.out 到 INFO 级别日志 + */ + fun redirectOutToInfo() { + val outLogger = PrintStream(object : OutputStream() { + private val buffer = StringBuilder() + + override fun write(b: Int) { + if (b == '\n'.code) { + flushBuffer() + } else { + buffer.append(b.toChar()) + } + } + + override fun write(b: ByteArray, off: Int, len: Int) { + val str = String(b, off, len) + if (str.contains("\n")) { + val lines = str.split("\n") + lines.forEachIndexed { index, line -> + if (index == lines.size - 1 && !str.endsWith("\n")) { + buffer.append(line) + } else { + buffer.append(line) + flushBuffer() + } + } + } else { + buffer.append(str) + } + } + + private fun flushBuffer() { + val message = buffer.toString().trim() + if (message.isNotBlank()) { + outLog.info(message) + } + buffer.clear() + } + + override fun flush() { + flushBuffer() + super.flush() + } + }, true) + + System.setOut(outLogger) + } + + @JvmStatic + /** + * 重定向 System.err 到 ERROR 级别 + */ + fun redirectErrToError() { + val errLogger = PrintStream(object : OutputStream() { + private val buffer = StringBuilder() + + override fun write(b: Int) { + if (b == '\n'.code) { + flushBuffer() + } else { + buffer.append(b.toChar()) + } + } + + override fun write(b: ByteArray, off: Int, len: Int) { + val str = String(b, off, len) + if (str.contains("\n")) { + val lines = str.split("\n") + lines.forEachIndexed { index, line -> + if (index == lines.size - 1 && !str.endsWith("\n")) { + buffer.append(line) + } else { + buffer.append(line) + flushBuffer() + } + } + } else { + buffer.append(str) + } + } + + private fun flushBuffer() { + val message = buffer.toString().trim() + if (message.isNotBlank()) { + errLog.error(message) + } + buffer.clear() + } + + override fun flush() { + flushBuffer() + super.flush() + } + }, true) + + System.setErr(errLogger) + } + + @JvmStatic + /** + * 完全重定向(包括第三方库的输出) + */ + fun redirectAll() { + redirectOutToInfo() + redirectErrToError() + } + + @JvmStatic + /** + * 恢复原始输出流 + */ + fun restore() { + System.setOut(originalOut) + System.setErr(originalErr) } } diff --git a/src/main/kotlin/com/mingliqiye/utils/json/GsonJsonApi.kt b/src/main/kotlin/com/mingliqiye/utils/json/GsonJsonApi.kt deleted file mode 100644 index 934fb76..0000000 --- a/src/main/kotlin/com/mingliqiye/utils/json/GsonJsonApi.kt +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright 2025 mingliqiye - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ProjectName mingli-utils - * ModuleName mingli-utils.main - * CurrentFile GsonJsonApi.kt - * LastUpdate 2025-09-15 22:07:43 - * UpdateUser MingLiPro - */ - -package com.mingliqiye.utils.json - -import com.google.gson.* -import com.mingliqiye.utils.json.converters.JsonConverter -import com.mingliqiye.utils.json.converters.JsonStringConverter - -class GsonJsonApi : JsonApi { - - private var gsonUnicode: Gson - private var gsonPretty: Gson - private var gsonPrettyUnicode: Gson - private var gson: Gson - - constructor() { - gson = GsonBuilder() - .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) - .create() - - gsonUnicode = GsonBuilder() - .disableHtmlEscaping() - .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) - .create() - - gsonPretty = GsonBuilder() - .setPrettyPrinting() - .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) - .create() - - gsonPrettyUnicode = GsonBuilder() - .setPrettyPrinting() - .disableHtmlEscaping() - .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) - .create() - } - - constructor(gson: Gson) { - this.gson = gson - .newBuilder() - .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) - .create() - this.gsonUnicode = gson - .newBuilder() - .disableHtmlEscaping() - .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) - .create() - this.gsonPretty = gson - .newBuilder() - .setPrettyPrinting() - .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) - .create() - this.gsonPrettyUnicode = gson - .newBuilder() - .setPrettyPrinting() - .disableHtmlEscaping() - .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) - .create() - } - - override fun parse(json: String, clazz: Class): T { - return gson.fromJson(json, clazz) - } - - override fun parse(json: String, type: JsonTypeReference): T { - return gson.fromJson(json, type.type) - } - - override fun format(obj: Any): String { - return gson.toJson(obj) - } - - override fun formatUnicode(obj: Any): String { - return gsonUnicode.toJson(obj) - } - - override fun formatPretty(obj: Any): String { - return gsonPretty.toJson(obj) - } - - override fun formatPrettyUnicode(obj: Any): String { - return gsonPrettyUnicode.toJson(obj) - } - - override fun isValidJson(json: String): Boolean { - return try { - JsonParser.parseString(json) - true - } catch (e: JsonSyntaxException) { - false - } catch (e: Exception) { - false - } - } - - override fun merge(vararg jsons: String): String { - val merged = JsonObject() - for (json in jsons) { - if (json.isNullOrEmpty()) { - continue - } - try { - val obj = JsonParser.parseString(json).asJsonObject - for (key in obj.keySet()) { - merged.add(key, obj.get(key)) - } - } catch (e: Exception) { - // 忽略无效的 JSON 字符串 - } - } - return gson.toJson(merged) - } - - override fun getNodeValue(json: String, path: String): String? { - return try { - var element = JsonParser.parseString(json) - val paths = path.split("\\.".toRegex()).toTypedArray() - var current = element - - for (p in paths) { - if (current.isJsonObject) { - current = current.asJsonObject.get(p) - } else { - return null - } - - if (current == null) { - return null - } - } - - if (current.isJsonPrimitive) current.asString else current.toString() - } catch (e: Exception) { - null - } - } - - override fun updateNodeValue(json: String, path: String, newValue: Any): String { - return try { - val obj = JsonParser.parseString(json).asJsonObject - val paths = path.split("\\.".toRegex()).toTypedArray() - var current = obj - - // 导航到倒数第二层 - for (i in 0 until paths.size - 1) { - val p = paths[i] - if (!current.has(p) || !current.get(p).isJsonObject) { - current.add(p, JsonObject()) - } - current = current.getAsJsonObject(p) - } - - // 设置最后一层的值 - val lastPath = paths[paths.size - 1] - val element = gson.toJsonTree(newValue) - current.add(lastPath, element) - - gson.toJson(obj) - } catch (e: Exception) { - json - } - } - - override fun convert(source: T, destinationClass: Class): D { - val json = gson.toJson(source) - return gson.fromJson(json, destinationClass) - } - - override fun convert(source: T, destinationType: JsonTypeReference): D { - val json = gson.toJson(source) - return gson.fromJson(json, destinationType.type) - } - - override fun addJsonConverter(c: JsonConverter<*, *>) { - c.getStringConverter()?.let { - gson = gson - .newBuilder() - .registerTypeAdapter( - it.tClass, - it.gsonJsonStringConverterAdapter - ) - .create() - gsonUnicode = gsonUnicode - .newBuilder() - .registerTypeAdapter( - it.tClass, - it.gsonJsonStringConverterAdapter - ) - .create() - gsonPretty = gsonPretty - .newBuilder() - .registerTypeAdapter( - it.tClass, - it.gsonJsonStringConverterAdapter - ) - .create() - gsonPrettyUnicode = gsonPrettyUnicode - .newBuilder() - .registerTypeAdapter( - it.tClass, - it.gsonJsonStringConverterAdapter - ) - .create() - } - - } - - override fun addJsonStringConverter(c: JsonStringConverter<*>) { - addJsonConverter(c) - } -} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/api/JSONA.kt b/src/main/kotlin/com/mingliqiye/utils/json/api/JSONA.kt new file mode 100644 index 0000000..e4e731f --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/api/JSONA.kt @@ -0,0 +1,644 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile JSONA.kt + * LastUpdate 2026-02-05 10:34:30 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.api + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.kotlinModule +import com.mingliqiye.utils.json.api.base.JsonApi +import com.mingliqiye.utils.json.api.type.JsonTypeReference +import com.mingliqiye.utils.json.api.type.listType +import com.mingliqiye.utils.json.converters.base.BaseJsonConverter +import java.io.File +import java.io.InputStream +import java.io.OutputStream +import java.nio.file.Path + +/** + * JSON工具类,提供静态方法访问JSON API功能 + */ +object JSONA { + @JvmStatic + private var jsonApi: JsonApi? = null + + /** + * 获取JSON API实例 + * + * @return JSON API实例 + * @throws NullPointerException 当JSON API未初始化时抛出异常 + */ + @Throws(NullPointerException::class) + @JvmStatic + fun getJsonApi(): JsonApi { + if (jsonApi == null) { + throw NullPointerException("jsonApi is null plase setJsonApi first") + } + return jsonApi!! + } + + /** + * 设置JSON API实例 + * + * @param jsa JSON API实例 + */ + @JvmStatic + fun setJsonApi(jsa: JsonApi) { + jsonApi = jsa + } + + /** + * 使用反射创建并设置指定类型的JSON API实例 + * + * @param T 继承自JsonApi的具体实现类 + */ + inline fun setJsonApi() { + setJsonApi(T::class.java.newInstance() as JsonApi) + } + + /** + * 将JSON字符串解析为指定类型的对象 + * + * @param json 待解析的JSON字符串 + * @param clazz 目标对象的Class类型 + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + @JvmStatic + fun parse(json: String, clazz: Class): T = getJsonApi().parse(json, clazz) + + /** + * 将JSON字符串解析为指定泛型类型对象 + * + * @param json 待解析的JSON字符串 + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + @JvmStatic + inline fun parse(json: String): T { + return getJsonApi().parse(json, (object : JsonTypeReference() {})) + } + + /** + * 将字节数组形式的JSON解析为指定泛型类型对象 + * + * @param json 待解析的JSON字节数组 + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + @JvmStatic + inline fun parse(json: ByteArray): T = getJsonApi().parse(json, object : JsonTypeReference() {}) + + /** + * 将JSON字符串解析为指定泛型类型对象 + * + * @param json 待解析的JSON字符串 + * @param type 目标对象的Type类型(支持泛型) + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + @JvmStatic + fun parse(json: String, type: JsonTypeReference): T = getJsonApi().parse(json, type) + + /** + * 将对象格式化为JSON字符串 + * + * @param obj 待格式化的对象 + * @return 格式化后的JSON字符串 + */ + @JvmStatic + fun format(obj: Any): String = getJsonApi().format(obj) + + /** + * 将对象格式化为带Unicode转义的JSON字符串 + * + * @param obj 待格式化的对象 + * @return 格式化后的带Unicode转义的JSON字符串 + */ + @JvmStatic + fun formatUnicode(obj: Any): String = getJsonApi().formatPrettyUnicode(obj) + + /** + * 从文件路径解析JSON为指定类型的对象 + * + * @param path 文件路径 + * @param clazz 目标对象的Class类型 + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + @JvmStatic + fun parseFrom(path: String, clazz: Class): T = getJsonApi().parseFrom(path, clazz) + + /** + * 从Path对象解析JSON为指定类型的对象 + * + * @param path Path对象 + * @param clazz 目标对象的Class类型 + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + @JvmStatic + fun parseFrom(path: Path, clazz: Class): T = getJsonApi().parseFrom(path, clazz) + + /** + * 从File对象解析JSON为指定类型的对象 + * + * @param file File对象 + * @param clazz 目标对象的Class类型 + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + @JvmStatic + fun parseFrom(file: File, clazz: Class): T = getJsonApi().parseFrom(file, clazz) + + /** + * 从InputStream解析JSON为指定类型的对象 + * + * @param inputStream InputStream对象 + * @param clazz 目标对象的Class类型 + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + @JvmStatic + fun parseFrom(inputStream: InputStream, clazz: Class): T = getJsonApi().parseFrom(inputStream, clazz) + + /** + * 从文件路径解析JSON为指定泛型类型对象 + * + * @param path 文件路径 + * @param type 目标对象的Type类型(支持泛型) + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + @JvmStatic + fun parseFrom(path: String, type: JsonTypeReference): T = getJsonApi().parseFrom(path, type) + + /** + * 从Path对象解析JSON为指定泛型类型对象 + * + * @param path Path对象 + * @param type 目标对象的Type类型(支持泛型) + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + @JvmStatic + fun parseFrom(path: Path, type: JsonTypeReference): T = getJsonApi().parseFrom(path, type) + + /** + * 从File对象解析JSON为指定泛型类型对象 + * + * @param file File对象 + * @param type 目标对象的Type类型(支持泛型) + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + @JvmStatic + fun parseFrom(file: File, type: JsonTypeReference): T = getJsonApi().parseFrom(file, type) + + /** + * 从InputStream解析JSON为指定泛型类型对象 + * + * @param inputStream InputStream对象 + * @param type 目标对象的Type类型(支持泛型) + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + @JvmStatic + fun parseFrom(inputStream: InputStream, type: JsonTypeReference): T = + getJsonApi().parseFrom(inputStream, type) + + /** + * 将字节数组形式的JSON解析为指定类型的对象 + * + * @param json 待解析的JSON字节数组 + * @param clazz 目标对象的Class类型 + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + @JvmStatic + fun parse(json: ByteArray, clazz: Class): T = getJsonApi().parse(json, clazz) + + /** + * 将字节数组形式的JSON解析为指定泛型类型对象 + * + * @param json 待解析的JSON字节数组 + * @param type 目标对象的Type类型(支持泛型) + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + @JvmStatic + fun parse(json: ByteArray, type: JsonTypeReference): T = getJsonApi().parse(json, type) + + /** + * 将JSON字符串解析为指定类型的对象,解析失败时返回默认值 + * + * @param json 待解析的JSON字符串 + * @param clazz 目标对象的Class类型 + * @param defaultValue 解析失败时返回的默认值 + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例或默认值 + */ + @JvmStatic + fun parse(json: String, clazz: Class, defaultValue: T): T = getJsonApi().parse(json, clazz, defaultValue) + + /** + * 将JSON字符串解析为指定泛型类型对象,解析失败时返回默认值 + * + * @param json 待解析的JSON字符串 + * @param type 目标对象的Type类型(支持泛型) + * @param defaultValue 解析失败时返回的默认值 + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例或默认值 + */ + @JvmStatic + fun parse( + json: String, type: JsonTypeReference, defaultValue: T + ): T = getJsonApi().parse(json, type, defaultValue) + + /** + * 将对象格式化为美化格式的JSON字符串(带缩进和换行) + * + * @param obj 待格式化的对象 + * @return 格式化后的美化JSON字符串 + */ + @JvmStatic + fun formatPretty(obj: Any): String = getJsonApi().formatPretty(obj) + + /** + * 将对象格式化为美化格式的JSON字节数组 + * + * @param obj 待格式化的对象 + * @return 格式化后的美化JSON字节数组 + */ + @JvmStatic + fun formatPrettyBytes(obj: Any): ByteArray = getJsonApi().formatPrettyBytes(obj) + + /** + * 将对象格式化为带Unicode转义的美化JSON字符串 + * + * @param obj 待格式化的对象 + * @return 格式化后的带Unicode转义的美化JSON字符串 + */ + @JvmStatic + fun formatPrettyUnicode(obj: Any): String = getJsonApi().formatPrettyUnicode(obj) + + /** + * 将对象格式化为带Unicode转义的美化JSON字节数组 + * + * @param obj 待格式化的对象 + * @return 格式化后的带Unicode转义的美化JSON字节数组 + */ + @JvmStatic + fun formatPrettyUnicodeBytes(obj: Any): ByteArray = getJsonApi().formatPrettyUnicodeBytes(obj) + + /** + * 将对象格式化为美化格式的JSON字符串并写入文件 + * + * @param obj 待格式化的对象 + * @param file 文件路径 + */ + @JvmStatic + fun formatPretty(obj: Any, file: String) = getJsonApi().formatPretty(obj, file) + + /** + * 将对象格式化为美化格式的JSON字符串并写入文件 + * + * @param obj 待格式化的对象 + * @param file Path对象 + */ + @JvmStatic + fun formatPretty(obj: Any, file: Path) = getJsonApi().formatPretty(obj, file) + + /** + * 将对象格式化为美化格式的JSON字符串并写入文件 + * + * @param obj 待格式化的对象 + * @param file File对象 + */ + @JvmStatic + fun formatPretty(obj: Any, file: File) = getJsonApi().formatPretty(obj, file) + + /** + * 将对象格式化为美化格式的JSON字符串并写入输出流 + * + * @param obj 待格式化的对象 + * @param stream OutputStream对象 + */ + @JvmStatic + fun formatPretty(obj: Any, stream: OutputStream) = getJsonApi().formatPretty(obj, stream) + + /** + * 将对象格式化为带Unicode转义的美化JSON字符串并写入文件 + * + * @param obj 待格式化的对象 + * @param file 文件路径 + */ + @JvmStatic + fun formatPrettyUnicode(obj: Any, file: String) = getJsonApi().formatPrettyUnicode(obj, file) + + /** + * 将对象格式化为带Unicode转义的美化JSON字符串并写入文件 + * + * @param obj 待格式化的对象 + * @param file Path对象 + */ + @JvmStatic + fun formatPrettyUnicode(obj: Any, file: Path) = getJsonApi().formatPrettyUnicode(obj, file) + + /** + * 将对象格式化为带Unicode转义的美化JSON字符串并写入文件 + * + * @param obj 待格式化的对象 + * @param file File对象 + */ + @JvmStatic + fun formatPrettyUnicode(obj: Any, file: File) = getJsonApi().formatPrettyUnicode(obj, file) + + /** + * 将对象格式化为带Unicode转义的美化JSON字符串并写入输出流 + * + * @param obj 待格式化的对象 + * @param stream OutputStream对象 + */ + @JvmStatic + fun formatPrettyUnicode(obj: Any, stream: OutputStream) = getJsonApi().formatPrettyUnicode(obj, stream) + + /** + * 将对象格式化为JSON字节数组 + * + * @param obj 待格式化的对象 + * @return 格式化后的JSON字节数组 + */ + @JvmStatic + fun formatBytes(obj: Any): ByteArray = getJsonApi().formatBytes(obj) + + /** + * 将对象格式化为带Unicode转义的JSON字节数组 + * + * @param obj 待格式化的对象 + * @return 格式化后的带Unicode转义的JSON字节数组 + */ + @JvmStatic + fun formatUnicodeBytes(obj: Any): ByteArray = getJsonApi().formatUnicodeBytes(obj) + + /** + * 将对象格式化为JSON字符串并写入文件 + * + * @param obj 待格式化的对象 + * @param file 文件路径 + */ + @JvmStatic + fun format(obj: Any, file: String) = getJsonApi().format(obj, file) + + /** + * 将对象格式化为JSON字符串并写入文件 + * + * @param obj 待格式化的对象 + * @param file Path对象 + */ + @JvmStatic + fun format(obj: Any, file: Path) = getJsonApi().format(obj, file) + + /** + * 将对象格式化为JSON字符串并写入文件 + * + * @param obj 待格式化的对象 + * @param file File对象 + */ + @JvmStatic + fun format(obj: Any, file: File) = getJsonApi().format(obj, file) + + /** + * 将对象格式化为JSON字符串并写入输出流 + * + * @param obj 待格式化的对象 + * @param stream OutputStream对象 + */ + @JvmStatic + fun format(obj: Any, stream: OutputStream) = getJsonApi().format(obj, stream) + + /** + * 将对象格式化为带Unicode转义的JSON字符串并写入文件 + * + * @param obj 待格式化的对象 + * @param file 文件路径 + */ + @JvmStatic + fun formatUnicode(obj: Any, file: String) = getJsonApi().formatUnicode(obj, file) + + /** + * 将对象格式化为带Unicode转义的JSON字符串并写入文件 + * + * @param obj 待格式化的对象 + * @param file Path对象 + */ + @JvmStatic + fun formatUnicode(obj: Any, file: Path) = getJsonApi().formatUnicode(obj, file) + + /** + * 将对象格式化为带Unicode转义的JSON字符串并写入文件 + * + * @param obj 待格式化的对象 + * @param file File对象 + */ + @JvmStatic + fun formatUnicode(obj: Any, file: File) = getJsonApi().formatUnicode(obj, file) + + /** + * 将对象格式化为带Unicode转义的JSON字符串并写入输出流 + * + * @param obj 待格式化的对象 + * @param stream OutputStream对象 + */ + @JvmStatic + fun formatUnicode(obj: Any, stream: OutputStream) = getJsonApi().formatUnicode(obj, stream) + + /** + * 将JSON字符串解析为指定元素类型的List集合 + * + * @param json 待解析的JSON字符串 + * @param elementType List中元素的类型 + * @param 泛型参数,表示List中元素的类型 + * @return 解析后的List集合 + */ + @JvmStatic + fun parseList(json: String, elementType: Class): List = parse(json, listType(elementType)) + + /** + * 将JSON字符串解析为指定元素类型的List集合 + * + * @param json 待解析的JSON字符串 + * @param 泛型参数,表示List中元素的类型 + * @return 解析后的List集合 + */ + @JvmStatic + inline fun parseList(json: String): List = parse(json, listType(T::class.java)) + + /** + * 将JSON字符串解析为指定键值类型的Map集合 + * + * @param json 待解析的JSON字符串 + * @param keyType Map中键的类型 + * @param valueType Map中值的类型 + * @param 泛型参数,表示Map中键的类型 + * @param 泛型参数,表示Map中值的类型 + * @return 解析后的Map集合 + */ + @JvmStatic + fun parseMap( + json: String, keyType: Class, valueType: Class + ): MutableMap = getJsonApi().parseMap(json, keyType, valueType) + + /** + * 将JSON字符串解析为指定键值类型的Map集合 + * + * @param json 待解析的JSON字符串 + * @param 泛型参数,表示Map中键的类型 + * @param 泛型参数,表示Map中值的类型 + * @return 解析后的Map集合 + */ + @JvmStatic + inline fun parseMap( + json: String + ): MutableMap = getJsonApi().parseMap(json, K::class.java, V::class.java) + + /** + * 验证字符串是否为有效的JSON格式 + * + * @param json 待验证的字符串 + * @return 如果是有效的JSON格式返回true,否则返回false + */ + @JvmStatic + fun isValidJson(json: String): Boolean = getJsonApi().isValidJson(json) + + /** + * 将对象转换为JSON字节数组 + * + * @param object 待转换的对象 + * @return 转换后的JSON字节数组 + */ + @JvmStatic + fun toBytes(obj: Any): ByteArray = getJsonApi().toBytes(obj) + + /** + * 将对象转换为美化格式的JSON字节数组 + * + * @param object 待转换的对象 + * @return 转换后的美化格式JSON字节数组 + */ + @JvmStatic + fun toBytesPretty(obj: Any): ByteArray = getJsonApi().toBytesPretty(obj) + + /** + * 合并多个JSON字符串为一个JSON对象 + * + * @param jsons 待合并的JSON字符串数组 + * @return 合并后的JSON字符串 + */ + @JvmStatic + fun merge(vararg jsons: String): String = getJsonApi().merge(*jsons) + + /** + * 获取JSON字符串中指定路径节点的值 + * + * @param json JSON字符串 + * @param path 节点路径(如:"user.name") + * @return 节点值的字符串表示 + */ + @JvmStatic + fun getNodeValue(json: String, path: String): String? = getJsonApi().getNodeValue(json, path) + + /** + * 更新JSON字符串中指定路径节点的值 + * + * @param json 原始JSON字符串 + * @param path 节点路径(如:"user.name") + * @param newValue 新的节点值 + * @return 更新后的JSON字符串 + */ + @JvmStatic + fun updateNodeValue(json: String, path: String, newValue: Any): String = + getJsonApi().updateNodeValue(json, path, newValue) + + /** + * 将源对象转换为目标类型的对象 + * + * @param T 源对象的类型 + * @param D 目标对象的类型 + * @param source 需要转换的源对象 + * @param destinationClass 目标对象的Class类型 + * @return 转换后的目标类型对象 + */ + @JvmStatic + fun convert(source: Any, destinationClass: Class): D = getJsonApi().convert(source, destinationClass) + + /** + * 使用内联函数和reified类型参数将源对象转换为目标类型的对象 + * + * @param T 源对象的类型 + * @param D 目标对象的类型(通过reified类型参数推断) + * @param source 需要转换的源对象 + * @return 转换后的目标类型对象 + */ + @JvmStatic + inline fun convert(source: Any): D = getJsonApi().convert(source, object : JsonTypeReference() {}) + + /** + * 将源对象转换为目标类型的对象,使用JsonTypeReference指定目标类型 + * + * @param T 源对象的类型 + * @param D 目标对象的类型 + * @param source 需要转换的源对象 + * @param destinationType 目标对象的JsonTypeReference类型引用 + * @return 转换后的目标类型对象 + */ + @JvmStatic + fun convert(source: Any, destinationType: JsonTypeReference): D = + getJsonApi().convert(source, destinationType) + + /** + * 添加JSON转换器到API中 + * + * @param c 需要添加的JSON转换器实例 + */ + @JvmStatic + fun addJsonConverter(c: BaseJsonConverter<*, *>) = getJsonApi().addJsonConverter(c) + + /** + * 使用反射创建并添加指定类型的JSON转换器到API中 + * + * @param T 继承自BaseJsonConverter的具体实现类 + */ + inline fun > addJsonConverter() = + addJsonConverter(T::class.java.newInstance()) + + + inline fun String.parseJson() = parse(this) + inline fun ByteArray.parseJson() = parse(this) + inline fun InputStream.parseJson() = this.use { parse(readBytes()) } + inline fun File.parseJson() = this.inputStream().parseJson() + inline fun Path.parseJson() = this.toFile().inputStream().parseJson() + + fun Any.toJson() = format(this) + + fun jacksonKotlinObjectMapper(): ObjectMapper = ObjectMapper().jacksonKotlinObjectMapper() + fun ObjectMapper.jacksonKotlinObjectMapper(): ObjectMapper = this.registerModule(kotlinModule()) +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/JacksonJsonApi.kt b/src/main/kotlin/com/mingliqiye/utils/json/api/JacksonJsonApi.kt similarity index 79% rename from src/main/kotlin/com/mingliqiye/utils/json/JacksonJsonApi.kt rename to src/main/kotlin/com/mingliqiye/utils/json/api/JacksonJsonApi.kt index 1e9dfb6..fc88f4f 100644 --- a/src/main/kotlin/com/mingliqiye/utils/json/JacksonJsonApi.kt +++ b/src/main/kotlin/com/mingliqiye/utils/json/api/JacksonJsonApi.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,11 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile JacksonJsonApi.kt - * LastUpdate 2025-09-15 22:07:43 + * LastUpdate 2026-02-05 10:31:14 * UpdateUser MingLiPro */ -package com.mingliqiye.utils.json +package com.mingliqiye.utils.json.api import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.core.JsonProcessingException @@ -28,9 +28,10 @@ import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectReader import com.fasterxml.jackson.databind.node.ObjectNode -import com.mingliqiye.utils.json.converters.JsonConverter -import com.mingliqiye.utils.json.converters.JsonStringConverter -import java.io.IOException +import com.mingliqiye.utils.json.api.base.JsonApi +import com.mingliqiye.utils.json.api.exception.JsonException +import com.mingliqiye.utils.json.api.type.JsonTypeReference +import com.mingliqiye.utils.json.converters.base.BaseJsonConverter /** * 基于Jackson的JSON处理实现类,提供JSON字符串解析、格式化、合并、节点操作等功能。 @@ -52,7 +53,7 @@ class JacksonJsonApi : JsonApi { * @param objectMapper 自定义的ObjectMapper实例 */ constructor(objectMapper: ObjectMapper) { - this.objectMapper = objectMapper.copy() + this.objectMapper = objectMapper } /** @@ -62,12 +63,12 @@ class JacksonJsonApi : JsonApi { * @param clazz 目标对象类型 * @param 泛型参数,表示目标对象类型 * @return 解析后的对象 - * @throws JsonException 当解析失败时抛出异常 + * @throws com.mingliqiye.utils.json.api.exception.JsonException 当解析失败时抛出异常 */ override fun parse(json: String, clazz: Class): T { return try { objectMapper.readValue(json, clazz) - } catch (e: IOException) { + } catch (e: Exception) { throw JsonException("Failed to parse JSON string", e) } } @@ -87,7 +88,7 @@ class JacksonJsonApi : JsonApi { objectMapper.constructType(type.type) ) reader.readValue(json) - } catch (e: IOException) { + } catch (e: Exception) { throw JsonException("Failed to parse JSON string", e) } } @@ -102,21 +103,17 @@ class JacksonJsonApi : JsonApi { override fun format(obj: Any): String { return try { objectMapper.writeValueAsString(obj) - } catch (e: JsonProcessingException) { + } catch (e: Exception) { throw JsonException( - "Failed to format object to JSON string", - e + "Failed to format object to JSON string", e ) } } override fun formatUnicode(obj: Any): String { return try { - objectMapper - .writer() - .with(JsonGenerator.Feature.ESCAPE_NON_ASCII) - .writeValueAsString(obj) - } catch (e: JsonProcessingException) { + objectMapper.writer().with(JsonGenerator.Feature.ESCAPE_NON_ASCII).writeValueAsString(obj) + } catch (e: Exception) { throw JsonException(e) } } @@ -130,27 +127,21 @@ class JacksonJsonApi : JsonApi { */ override fun formatPretty(obj: Any): String { return try { - objectMapper - .writerWithDefaultPrettyPrinter() - .writeValueAsString(obj) - } catch (e: JsonProcessingException) { + objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj) + } catch (e: Exception) { throw JsonException( - "Failed to format object to pretty JSON string", - e + "Failed to format object to pretty JSON string", e ) } } override fun formatPrettyUnicode(obj: Any): String { return try { - objectMapper - .writerWithDefaultPrettyPrinter() - .with(JsonGenerator.Feature.ESCAPE_NON_ASCII) + objectMapper.writerWithDefaultPrettyPrinter().with(JsonGenerator.Feature.ESCAPE_NON_ASCII) .writeValueAsString(obj) - } catch (e: JsonProcessingException) { + } catch (e: Exception) { throw JsonException( - "Failed to format object to pretty JSON string", - e + "Failed to format object to pretty JSON string", e ) } } @@ -186,8 +177,7 @@ class JacksonJsonApi : JsonApi { if (node.isObject) { result.setAll(node as ObjectNode) } - } catch (e: IOException) { - // 忽略无效的JSON字符串 + } catch (e: Exception) { } } return try { @@ -213,7 +203,7 @@ class JacksonJsonApi : JsonApi { node = node.get(p) } node.asText() - } catch (e: IOException) { + } catch (e: Exception) { throw JsonException("Failed to get node value", e) } } @@ -247,15 +237,14 @@ class JacksonJsonApi : JsonApi { if (current is ObjectNode) { val parent: ObjectNode = current parent.set( - paths[paths.size - 1], - objectMapper.valueToTree(newValue) + paths[paths.size - 1], objectMapper.valueToTree(newValue) ) } objectMapper.writeValueAsString(objectNode) } json - } catch (e: IOException) { + } catch (e: Exception) { throw JsonException("Failed to update node value", e) } } @@ -269,8 +258,12 @@ class JacksonJsonApi : JsonApi { * @param 目标对象类型 * @return 转换后的对象 */ - override fun convert(source: T, destinationClass: Class): D { - return objectMapper.convertValue(source, destinationClass) + override fun convert(source: Any, destinationClass: Class): D { + try { + return objectMapper.convertValue(source, destinationClass) + } catch (e: Exception) { + throw JsonException("Failed to update node value", e) + } } /** @@ -282,20 +275,21 @@ class JacksonJsonApi : JsonApi { * @param 目标对象类型 * @return 转换后的对象 */ - override fun convert(source: T, destinationType: JsonTypeReference): D { - return objectMapper.convertValue( - source, - objectMapper.constructType(destinationType.type) - ) - } - - override fun addJsonConverter(c: JsonConverter<*, *>) { - c.getStringConverter()?.let { - objectMapper.registerModule(it.jacksonJsonStringConverterAdapter.jacksonModule) + override fun convert(source: Any, destinationType: JsonTypeReference): D { + try { + return objectMapper.convertValue( + source, objectMapper.constructType(destinationType.type) + ) + } catch (e: Exception) { + throw JsonException("Failed to update node value", e) } } - override fun addJsonStringConverter(c: JsonStringConverter<*>) { - addJsonConverter(c) + override fun addJsonConverter(c: BaseJsonConverter<*, *>) { + try { + objectMapper.registerModule(c.getJacksonModule()) + } catch (e: Exception) { + throw JsonException("Failed to update node value", e) + } } } diff --git a/src/main/kotlin/com/mingliqiye/utils/json/JsonApi.kt b/src/main/kotlin/com/mingliqiye/utils/json/api/base/JsonApi.kt similarity index 59% rename from src/main/kotlin/com/mingliqiye/utils/json/JsonApi.kt rename to src/main/kotlin/com/mingliqiye/utils/json/api/base/JsonApi.kt index 0de3c89..4b9cfc7 100644 --- a/src/main/kotlin/com/mingliqiye/utils/json/JsonApi.kt +++ b/src/main/kotlin/com/mingliqiye/utils/json/api/base/JsonApi.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,24 +16,128 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile JsonApi.kt - * LastUpdate 2025-09-15 22:32:50 + * LastUpdate 2026-02-04 21:00:48 * UpdateUser MingLiPro */ -package com.mingliqiye.utils.json +package com.mingliqiye.utils.json.api.base -import com.mingliqiye.utils.json.converters.JsonConverter -import com.mingliqiye.utils.json.converters.JsonStringConverter +import com.mingliqiye.utils.json.api.type.JsonTypeReference +import com.mingliqiye.utils.json.api.type.listType +import com.mingliqiye.utils.json.converters.base.BaseJsonConverter import java.io.* import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths - /** * JSON处理接口,提供JSON字符串与Java对象之间的相互转换功能 */ interface JsonApi { + + companion object { + /** + * 将JSON字符串解析为指定泛型类型对象 + * + * @param json 待解析的JSON字符串 + * @param T 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + inline fun JsonApi.parse(json: String): T = parse(json, object : JsonTypeReference() {}) + + /** + * 将JSON字节数组解析为指定泛型类型对象 + * + * @param json 待解析的JSON字节数组 + * @param T 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + inline fun JsonApi.parse(json: ByteArray): T = parse(json, object : JsonTypeReference() {}) + + /** + * 将JSON字符串解析为指定泛型类型对象,解析失败时返回默认值 + * + * @param json 待解析的JSON字符串 + * @param defect 解析失败时返回的默认值 + * @param T 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例或默认值 + */ + inline fun JsonApi.parse(json: String, defect: T): T = + parse(json, object : JsonTypeReference() {}, defect) + + /** + * 从文件路径读取JSON并解析为指定泛型类型对象 + * + * @param path 文件路径 + * @param T 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + inline fun JsonApi.parseFrom(path: String): T = parseFrom(path, object : JsonTypeReference() {}) + + /** + * 从文件读取JSON并解析为指定泛型类型对象 + * + * @param path 文件对象 + * @param T 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + inline fun JsonApi.parseFrom(path: File): T = parseFrom(path, object : JsonTypeReference() {}) + + /** + * 从输入流读取JSON并解析为指定泛型类型对象 + * + * @param path 输入流 + * @param T 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + inline fun JsonApi.parseFrom(path: InputStream): T = + parseFrom(path, object : JsonTypeReference() {}) + + /** + * 从路径读取JSON并解析为指定泛型类型对象 + * + * @param path 路径对象 + * @param T 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ + inline fun JsonApi.parseFrom(path: Path): T = parseFrom(path, object : JsonTypeReference() {}) + + /** + * 将JSON字符串解析为指定键值类型的Map集合 + * + * @param json 待解析的JSON字符串 + * @param K 泛型参数,表示Map中键的类型 + * @param V 泛型参数,表示Map中值的类型 + * @return 解析后的Map集合 + */ + inline fun JsonApi.parseMap( + json: String + ): MutableMap = parseMap(json, K::class.java, V::class.java) + + + /** + * 使用内联函数和reified类型参数将源对象转换为目标类型的对象 + * + * @param T 源对象的类型 + * @param D 目标对象的类型(通过reified类型参数推断) + * @param source 需要转换的源对象 + * @return 转换后的目标类型对象 + */ + @JvmStatic + inline fun JsonApi.convert(source: Any): D = convert(source, object : JsonTypeReference() {}) + + /** + * 添加JSON转换器到API中 + * + * @param T 需要添加的JSON转换器类 + */ + @JvmStatic + inline fun > JsonApi.addJsonConverter() { + addJsonConverter(T::class.java.newInstance()) + } + + } + /** * 将JSON字符串解析为指定类型的对象 * @@ -64,24 +168,52 @@ interface JsonApi { fun formatUnicode(obj: Any): String - @Throws(IOException::class) + /** + * 从文件路径读取JSON并解析为指定类型对象 + * + * @param path 文件路径 + * @param clazz 目标对象的Class类型 + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ fun parseFrom(path: String, clazz: Class): T { return parseFrom(Paths.get(path), clazz) } - @Throws(IOException::class) + /** + * 从路径读取JSON并解析为指定类型对象 + * + * @param path 路径对象 + * @param clazz 目标对象的Class类型 + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ fun parseFrom(path: Path, clazz: Class): T { return parseFrom(path.toFile(), clazz) } - @Throws(IOException::class) + /** + * 从文件读取JSON并解析为指定类型对象 + * + * @param file 文件对象 + * @param clazz 目标对象的Class类型 + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ fun parseFrom(file: File, clazz: Class): T { Files.newInputStream(file.toPath()).use { inputStream -> return parseFrom(inputStream, clazz) } } - @Throws(IOException::class) + /** + * 从输入流读取JSON并解析为指定类型对象 + * + * @param inputStream 输入流 + * @param clazz 目标对象的Class类型 + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ fun parseFrom(inputStream: InputStream, clazz: Class): T { val bytes = ByteArray(1024) ByteArrayOutputStream().use { bos -> @@ -93,24 +225,52 @@ interface JsonApi { } } - @Throws(IOException::class) + /** + * 从文件路径读取JSON并解析为指定泛型类型对象 + * + * @param path 文件路径 + * @param type 目标对象的Type类型(支持泛型) + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ fun parseFrom(path: String, type: JsonTypeReference): T { return parseFrom(Paths.get(path), type) } - @Throws(IOException::class) + /** + * 从路径读取JSON并解析为指定泛型类型对象 + * + * @param path 路径对象 + * @param type 目标对象的Type类型(支持泛型) + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ fun parseFrom(path: Path, type: JsonTypeReference): T { return parseFrom(path.toFile(), type) } - @Throws(IOException::class) + /** + * 从文件读取JSON并解析为指定泛型类型对象 + * + * @param file 文件对象 + * @param type 目标对象的Type类型(支持泛型) + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ fun parseFrom(file: File, type: JsonTypeReference): T { Files.newInputStream(file.toPath()).use { inputStream -> return parseFrom(inputStream, type) } } - @Throws(IOException::class) + /** + * 从输入流读取JSON并解析为指定泛型类型对象 + * + * @param inputStream 输入流 + * @param type 目标对象的Type类型(支持泛型) + * @param 泛型参数,表示目标对象的类型 + * @return 解析后的对象实例 + */ fun parseFrom(inputStream: InputStream, type: JsonTypeReference): T { val bytes = ByteArray(1024) ByteArrayOutputStream().use { bos -> @@ -156,10 +316,10 @@ interface JsonApi { * @return 解析后的对象实例或默认值 */ fun parse(json: String, clazz: Class, defaultValue: T): T { - try { - return parse(json, clazz) + return try { + parse(json, clazz) } catch (e: Exception) { - return defaultValue + defaultValue } } @@ -173,14 +333,12 @@ interface JsonApi { * @return 解析后的对象实例或默认值 **/ fun parse( - json: String, - type: JsonTypeReference, - defaultValue: T + json: String, type: JsonTypeReference, defaultValue: T ): T { - try { - return parse(json, type) + return try { + parse(json, type) } catch (e: Exception) { - return defaultValue + defaultValue } } @@ -202,46 +360,38 @@ interface JsonApi { return formatPrettyUnicode(obj)!!.toByteArray() } - @Throws(IOException::class) fun formatPretty(obj: Any, file: String) { formatPretty(obj, Paths.get(file)) } - @Throws(IOException::class) fun formatPretty(obj: Any, file: Path) { formatPretty(obj, file.toFile()) } - @Throws(IOException::class) fun formatPretty(obj: Any, file: File) { FileOutputStream(file).use { fos -> formatPretty(obj, fos) } } - @Throws(IOException::class) fun formatPretty(obj: Any, stream: OutputStream) { stream.write(formatPrettyBytes(obj)) } - @Throws(IOException::class) fun formatPrettyUnicode(obj: Any, file: String) { formatPrettyUnicode(obj, Paths.get(file)) } - @Throws(IOException::class) fun formatPrettyUnicode(obj: Any, file: Path) { formatPrettyUnicode(obj, file.toFile()) } - @Throws(IOException::class) fun formatPrettyUnicode(obj: Any, file: File) { FileOutputStream(file).use { fos -> formatPrettyUnicode(obj, fos) } } - @Throws(IOException::class) fun formatPrettyUnicode(obj: Any, stream: OutputStream) { stream.write(formatPrettyUnicodeBytes(obj)) } @@ -254,46 +404,38 @@ interface JsonApi { return formatUnicode(obj)!!.toByteArray() } - @Throws(IOException::class) fun format(obj: Any, file: String) { format(obj, Paths.get(file)) } - @Throws(IOException::class) fun format(obj: Any, file: Path) { format(obj, file.toFile()) } - @Throws(IOException::class) fun format(obj: Any, file: File) { FileOutputStream(file).use { fos -> format(obj, fos) } } - @Throws(IOException::class) fun format(obj: Any, stream: OutputStream) { stream.write(formatPrettyBytes(obj)) } - @Throws(IOException::class) fun formatUnicode(obj: Any, file: String) { formatUnicode(obj, Paths.get(file)) } - @Throws(IOException::class) fun formatUnicode(obj: Any, file: Path) { formatUnicode(obj, file.toFile()) } - @Throws(IOException::class) fun formatUnicode(obj: Any, file: File) { FileOutputStream(file).use { fos -> formatUnicode(obj, fos) } } - @Throws(IOException::class) fun formatUnicode(obj: Any, stream: OutputStream) { stream.write(formatPrettyUnicodeBytes(obj)) } @@ -321,9 +463,7 @@ interface JsonApi { * @return 解析后的Map集合 */ fun parseMap( - json: String, - keyType: Class, - valueType: Class + json: String, keyType: Class, valueType: Class ): MutableMap { val mapType: JsonTypeReference> = object : JsonTypeReference>() {} return parse>(json, mapType) @@ -384,10 +524,9 @@ interface JsonApi { */ fun updateNodeValue(json: String, path: String, newValue: Any): String - fun convert(source: T, destinationClass: Class): D + fun convert(source: Any, destinationClass: Class): D - fun convert(source: T, destinationType: JsonTypeReference): D + fun convert(source: Any, destinationType: JsonTypeReference): D - fun addJsonConverter(c: JsonConverter<*, *>) - fun addJsonStringConverter(c: JsonStringConverter<*>) + fun addJsonConverter(c: BaseJsonConverter<*, *>) } diff --git a/src/main/kotlin/com/mingliqiye/utils/json/api/exception/JsonException.kt b/src/main/kotlin/com/mingliqiye/utils/json/api/exception/JsonException.kt new file mode 100644 index 0000000..4b3c831 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/api/exception/JsonException.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile JsonException.kt + * LastUpdate 2026-02-05 11:12:36 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.api.exception + +/** + * 自定义异常类,用于处理 JSON 相关操作中出现的错误。 + * + * 该类继承自 [RuntimeException],提供了三种构造函数以支持不同的异常场景: + * 1. 仅包含错误信息的构造函数。 + * 2. 包含错误信息和原因(Throwable)的构造函数。 + * 3. 仅包含原因(Throwable)的构造函数。 + */ +class JsonException : RuntimeException { + + /** + * 构造函数:创建一个带有指定错误信息的 [JsonException] 实例。 + * + * @param message 错误信息描述。 + */ + constructor(message: String) : super(message) + + /** + * 构造函数:创建一个带有指定错误信息和原因的 [JsonException] 实例。 + * + * @param message 错误信息描述。 + * @param cause 导致此异常的根本原因。 + */ + constructor(message: String, cause: Throwable) : super(message, cause) + + /** + * 构造函数:创建一个由指定原因引发的 [JsonException] 实例。 + * + * @param cause 导致此异常的根本原因。 + */ + constructor(cause: Throwable) : super(cause) +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/JsonTypeReference.kt b/src/main/kotlin/com/mingliqiye/utils/json/api/type/JsonTypeReference.kt similarity index 87% rename from src/main/kotlin/com/mingliqiye/utils/json/JsonTypeReference.kt rename to src/main/kotlin/com/mingliqiye/utils/json/api/type/JsonTypeReference.kt index a09739d..d824f55 100644 --- a/src/main/kotlin/com/mingliqiye/utils/json/JsonTypeReference.kt +++ b/src/main/kotlin/com/mingliqiye/utils/json/api/type/JsonTypeReference.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,11 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile JsonTypeReference.kt - * LastUpdate 2025-09-15 22:32:50 + * LastUpdate 2026-02-05 11:12:36 * UpdateUser MingLiPro */ -package com.mingliqiye.utils.json +package com.mingliqiye.utils.json.api.type import java.lang.reflect.ParameterizedType import java.lang.reflect.Type @@ -68,8 +68,7 @@ abstract class JsonTypeReference : Comparable> { * @return 类型引用实例 */ companion object { - @JvmStatic - fun of(): JsonTypeReference { + inline fun of(): JsonTypeReference { return object : JsonTypeReference() {} } @@ -116,6 +115,11 @@ abstract class JsonTypeReference : Comparable> { throw IllegalStateException("无法获取原始类型: $type") } + /** + * 判断两个 JsonTypeReference 实例是否相等 + * @param other 另一个对象 + * @return 是否相等 + */ override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || this.javaClass != other.javaClass) return false @@ -142,6 +146,10 @@ abstract class JsonTypeReference : Comparable> { return Objects.equals(type, that.type) } + /** + * 计算当前实例的哈希码 + * @return 哈希码值 + */ override fun hashCode(): Int { if (type is ParameterizedType) { val paramType = type as ParameterizedType @@ -154,10 +162,19 @@ abstract class JsonTypeReference : Comparable> { return Objects.hash(type) } + /** + * 返回当前实例的字符串表示形式 + * @return 字符串描述 + */ override fun toString(): String { - return "JsonTypeReference{$type}" + return "com.mingliqiye.utils.json.JsonTypeReference<${TypeReference.getRawString(type)}>" } + /** + * 比较当前实例与另一个实例的大小关系 + * @param other 另一个 JsonTypeReference 实例 + * @return 比较结果 + */ override fun compareTo(other: JsonTypeReference): Int { return this.type.toString().compareTo(other.type.toString()) } diff --git a/src/main/kotlin/com/mingliqiye/utils/json/JsonTypeUtils.kt b/src/main/kotlin/com/mingliqiye/utils/json/api/type/JsonTypeUtils.kt similarity index 98% rename from src/main/kotlin/com/mingliqiye/utils/json/JsonTypeUtils.kt rename to src/main/kotlin/com/mingliqiye/utils/json/api/type/JsonTypeUtils.kt index b7dfe42..87e8b05 100644 --- a/src/main/kotlin/com/mingliqiye/utils/json/JsonTypeUtils.kt +++ b/src/main/kotlin/com/mingliqiye/utils/json/api/type/JsonTypeUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,12 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile JsonTypeUtils.kt - * LastUpdate 2025-09-17 11:12:06 + * LastUpdate 2026-02-04 21:00:48 * UpdateUser MingLiPro */ @file:JvmName("JsonTypeUtils") -package com.mingliqiye.utils.json +package com.mingliqiye.utils.json.api.type import java.lang.reflect.ParameterizedType import java.lang.reflect.Type diff --git a/src/main/kotlin/com/mingliqiye/utils/json/api/type/TypeReference.kt b/src/main/kotlin/com/mingliqiye/utils/json/api/type/TypeReference.kt new file mode 100644 index 0000000..f96b554 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/api/type/TypeReference.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile TypeReference.kt + * LastUpdate 2026-02-04 21:00:48 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.api.type + +import com.mingliqiye.utils.string.join +import sun.reflect.generics.reflectiveObjects.WildcardTypeImpl +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type + +class TypeReference( + val rawClass: Type, + vararg paramType: Type +) : ParameterizedType { + + private val typeArguments: Array = paramType + override fun getActualTypeArguments(): Array = typeArguments + + override fun getRawType(): Type = rawClass + + override fun getOwnerType(): Type? = null + + + override fun toString(): String = "${getRawString(rawType)}<${ + ",".join(typeArguments, TypeReference::getRawString) + }>" + + companion object { + + fun getRawString(s: Any): String { + if (s is Class<*>) { + return s.name + } + if (s is WildcardTypeImpl) { + return getRawString(s.upperBounds[0]) + } + if (s is ParameterizedType) { + return of(s).toString() + } + return s.toString() + } + + inline fun of(): TypeReference { + return of((object : JsonTypeReference() {}).type) + } + + fun of(type: Type): TypeReference { + if (type is Class<*>) { + return TypeReference(type) + } + if (type is ParameterizedType) { + return TypeReference(type.rawType, *type.actualTypeArguments) + } + throw IllegalStateException("无法获取类型: $type") + } + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/DateTimeJsonConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/DateTimeJsonConverter.kt new file mode 100644 index 0000000..7e2000b --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/DateTimeJsonConverter.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile DateTimeJsonConverter.kt + * LastUpdate 2026-02-05 11:18:57 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.converters + +import com.mingliqiye.utils.json.api.type.JsonTypeReference +import com.mingliqiye.utils.json.converters.base.AnnotationGetter +import com.mingliqiye.utils.json.converters.base.AnnotationGetter.Companion.get +import com.mingliqiye.utils.json.converters.base.BaseJsonStringConverter +import com.mingliqiye.utils.objects.isNull +import com.mingliqiye.utils.string.isNullish +import com.mingliqiye.utils.time.DateTime +import com.mingliqiye.utils.time.DateTimeJsonFormat +import com.mingliqiye.utils.time.Formatter + + +/** + * DateTimeJsonConverter 是一个用于处理 DateTime 类型与 JSON 字符串之间转换的类。 + * 它继承自 BaseJsonStringConverter,提供了序列化(convert)和反序列化(deConvert)的功能。 + */ +class DateTimeJsonConverter : BaseJsonStringConverter { + + /** + * 将 DateTime 对象转换为 JSON 字符串。 + * + * @param obj 需要转换的 DateTime 对象,可能为 null。 + * @param annotationGetter 用于获取注解信息的对象,用于决定格式化方式。 + * @return 转换后的字符串,如果输入为 null 则返回 null。 + * @throws Exception 如果在转换过程中发生异常。 + */ + @Throws(Exception::class) + override fun convert(obj: DateTime?, annotationGetter: AnnotationGetter): String? { + // 如果输入对象为 null,直接返回 null + if (obj.isNull()) return null + + // 获取 DateTimeJsonFormat 注解信息 + val dateTimeJsonFormat: DateTimeJsonFormat? = annotationGetter.get() + + // 根据注解中的格式化规则进行转换 + return if (Formatter.NONE != dateTimeJsonFormat?.value && dateTimeJsonFormat != null) { + obj.format(dateTimeJsonFormat.value, dateTimeJsonFormat.repcZero) + } else if (dateTimeJsonFormat?.formatter?.isEmpty() == false) { + obj.format(dateTimeJsonFormat.formatter, dateTimeJsonFormat.repcZero) + } else { + obj.format() + } + } + + /** + * 将 JSON 字符串转换为 DateTime 对象。 + * + * @param obj 需要转换的字符串,可能为 null 或空。 + * @param annotationGetter 用于获取注解信息的对象,用于决定解析方式。 + * @return 解析后的 DateTime 对象,如果输入为 null 或空则返回 null。 + * @throws Exception 如果在解析过程中发生异常。 + */ + @Throws(Exception::class) + override fun deConvert(obj: String?, annotationGetter: AnnotationGetter): DateTime? { + // 如果输入字符串为 null 或空,直接返回 null + if (obj.isNullish()) return null + + // 获取 DateTimeJsonFormat 注解信息 + val dateTimeJsonFormat: DateTimeJsonFormat? = annotationGetter.get() + + // 根据注解中的解析规则进行转换 + return if (Formatter.NONE != dateTimeJsonFormat?.value && dateTimeJsonFormat != null) { + DateTime.parse(obj, dateTimeJsonFormat.value, dateTimeJsonFormat.repcZero) + } else if (dateTimeJsonFormat?.formatter?.isEmpty() == false) { + DateTime.parse(obj, dateTimeJsonFormat.formatter, dateTimeJsonFormat.repcZero) + } else { + DateTime.parse(obj) + } + } + + /** + * 获取当前转换器支持的目标类型引用。 + * + * @return 返回 DateTime 类型的 JsonTypeReference。 + * @throws Exception 如果在获取类型引用时发生异常。 + */ + @Throws(Exception::class) + override fun getFromType(): JsonTypeReference = JsonTypeReference.of() +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/JsonStringConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/JsonStringConverter.kt deleted file mode 100644 index fb75153..0000000 --- a/src/main/kotlin/com/mingliqiye/utils/json/converters/JsonStringConverter.kt +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright 2025 mingliqiye - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ProjectName mingli-utils - * ModuleName mingli-utils.main - * CurrentFile JsonStringConverter.kt - * LastUpdate 2025-09-17 19:09:17 - * UpdateUser MingLiPro - */ - -package com.mingliqiye.utils.json.converters - -import com.alibaba.fastjson2.JSONReader -import com.alibaba.fastjson2.JSONWriter -import com.alibaba.fastjson2.reader.ObjectReader -import com.alibaba.fastjson2.writer.ObjectWriter -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.* -import com.fasterxml.jackson.databind.module.SimpleModule -import com.google.gson.TypeAdapter -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonWriter -import com.mingliqiye.utils.time.DateTime -import com.mingliqiye.utils.time.DateTime.Companion.parse -import com.mingliqiye.utils.uuid.UUID -import com.mingliqiye.utils.uuid.UUID.Companion.of -import java.io.IOException -import java.lang.reflect.Type - -/** - * JSON转换器接口,提供对象与字符串之间的相互转换功能,并支持多种JSON库 - * - * @param 需要转换的对象类型 - */ -abstract class JsonStringConverter : JsonConverter { - val fastjsonJsonStringConverterAdapter: FastjsonJsonStringConverterAdapter, T> - /** - * 获取 Fastjson 的适配器 - * @return 适配器实例 - */ - get() = FastjsonJsonStringConverterAdapter.of(this) - - val gsonJsonStringConverterAdapter: GsonJsonStringConverterAdapter, T> - /** - * 获取 Gson 的适配器 - * @return 适配器实例 - */ - get() = GsonJsonStringConverterAdapter.of(this) - - val jacksonJsonStringConverterAdapter: JacksonJsonStringConverterAdapter, T> - /** - * 获取 Jackson 的适配器 - * @return 适配器实例 - */ - get() = JacksonJsonStringConverterAdapter.of(this) -} - - -class JacksonJsonStringConverterAdapter, TT - > private constructor(val jsonStringConverter: T) { - - val jacksonJsonDeserializer: JsonDeserializer - /** - * 获取Jackson反序列化器 - * - * @return Jackson的JsonDeserializer实例 - */ - get() = object : JsonDeserializer() { - @Throws(IOException::class) - override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): TT? { - if (p.isNaN) return null - return jsonStringConverter.deConvert(p.valueAsString) - } - } - - val jacksonJsonSerializer: JsonSerializer - /** - * 获取Jackson序列化器 - * - * @return Jackson的JsonSerializer实例 - */ - get() = object : JsonSerializer() { - @Throws(IOException::class) - override fun serialize( - value: TT?, - gen: JsonGenerator, - serializers: SerializerProvider? - ) { - if (value == null) { - gen.writeNull() - return - } - gen.writeString(jsonStringConverter.convert(value)) - } - } - - val jacksonModule: Module - /** - * - * 获取 Jackson 的格式化模块 - * - * @return 格式化模块 - */ - get() { - val tClass = jsonStringConverter.tClass - val m = SimpleModule(tClass.getSimpleName()) - m.addSerializer(tClass, this.jacksonJsonSerializer) - m.addDeserializer(tClass, this.jacksonJsonDeserializer) - return m - } - - companion object { - /** - * - * @param t JSON转换器实例 - * @return JSON转换器的适配器 - * @param JSON转换器 - * @param JSON转换器的泛型 - **/ - fun , TT - > of(t: T): JacksonJsonStringConverterAdapter { - return JacksonJsonStringConverterAdapter(t) - } - } -} - -class GsonJsonStringConverterAdapter, TT - >(val jsonStringConverter: T) { - - val gsonTypeAdapter: TypeAdapter - /** - * 获取Gson类型适配器 - * - * @return Gson的TypeAdapter实例 - */ - get() = object : TypeAdapter() { - @Throws(IOException::class) - override fun write(out: JsonWriter, value: TT?) { - if (value == null) { - out.nullValue() - return - } - out.value(jsonStringConverter.convert(value)) - } - - @Throws(IOException::class) - override fun read(`in`: JsonReader): TT? { - val value = `in`.nextString() - return jsonStringConverter.deConvert(value) - } - } - - companion object { - fun , TT - > of(t: T): GsonJsonStringConverterAdapter { - return GsonJsonStringConverterAdapter(t) - } - } -} - -class FastjsonJsonStringConverterAdapter, TT - >(val jsonStringConverter: T) { - @Suppress("UNCHECKED_CAST") - val fastJsonObjectWriter: ObjectWriter - /** - * 获取FastJson对象写入器 - * - * @return FastJson的ObjectWriter实例 - */ - get() = ObjectWriter { writer: JSONWriter?, obj: Any?, _: Any?, _: Type?, _: Long - -> - // 如果对象为null则写入null - if (obj == null) { - writer!!.writeNull() - return@ObjectWriter - } - writer!!.writeString(jsonStringConverter.convert(obj as TT)) - } - - val fastJsonObjectReader: ObjectReader - /** - * 获取FastJson对象读取器 - * - * @return FastJson的ObjectReader实例 - */ - get() = ObjectReader { reader: JSONReader?, _: Type?, _: Any?, _: Long - -> - val value = reader!!.readString() - jsonStringConverter.deConvert(value) - } - - companion object { - fun , TT - > of(t: T): FastjsonJsonStringConverterAdapter { - return FastjsonJsonStringConverterAdapter(t) - } - } -} - - -class DateTimeJsonConverter : JsonStringConverter() { - override val tClass = DateTime::class.java - - override fun convert(obj: DateTime?): String? { - if (obj == null) { - return null - } - return obj.format() - } - - override fun deConvert(obj: String?): DateTime? { - if (obj == null) { - return null - } - return parse( - obj - ) - } -} - -class UUIDJsonStringConverter : JsonStringConverter() { - override val tClass: Class = UUID::class.java - - override fun convert(obj: UUID?): String? { - if (obj == null) { - return null - } - return obj.getString() - } - - override fun deConvert(obj: String?): UUID? { - if (obj == null) { - return null - } - return of(obj) - } - -} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/UUIDJsonConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/UUIDJsonConverter.kt new file mode 100644 index 0000000..724349a --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/UUIDJsonConverter.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile UUIDJsonConverter.kt + * LastUpdate 2026-02-05 11:19:45 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.converters + +import com.mingliqiye.utils.base.BaseType +import com.mingliqiye.utils.json.api.type.JsonTypeReference +import com.mingliqiye.utils.json.converters.base.AnnotationGetter +import com.mingliqiye.utils.json.converters.base.AnnotationGetter.Companion.get +import com.mingliqiye.utils.json.converters.base.BaseJsonStringConverter +import com.mingliqiye.utils.objects.isNull +import com.mingliqiye.utils.string.isNullish +import com.mingliqiye.utils.uuid.UUID +import com.mingliqiye.utils.uuid.UUIDFormatType +import com.mingliqiye.utils.uuid.UUIDJsonFormat + +/** + * UUIDJsonConverter 是一个用于处理 UUID 类型与 JSON 字符串之间转换的类。 + * 它继承自 BaseJsonStringConverter,并实现了 convert 和 deConvert 方法, + * 分别用于将 UUID 对象序列化为字符串以及反序列化字符串为 UUID 对象。 + */ +class UUIDJsonConverter : BaseJsonStringConverter { + + /** + * 将 UUID 对象转换为字符串形式。 + * + * @param obj 需要转换的 UUID 对象,可能为 null。 + * @param annotationGetter 用于获取注解信息的对象。 + * @return 转换后的字符串,如果输入为 null 则返回 null。 + */ + override fun convert( + obj: UUID?, + annotationGetter: AnnotationGetter + ): String? { + // 如果输入对象为 null,直接返回 null + if (obj.isNull()) return null + + // 获取 UUIDJsonFormat 注解信息,若未找到则使用默认格式 + val dateTimeJsonFormat: UUIDJsonFormat = annotationGetter.get() ?: return obj.getString() + + // 根据注解中的 base 和 value 属性选择合适的字符串表示方式 + return if (BaseType.BASE16 != dateTimeJsonFormat.base) { + obj.getString(dateTimeJsonFormat.base) + } else if (UUIDFormatType.NO_UPPER_SPACE != dateTimeJsonFormat.value) { + obj.getString(dateTimeJsonFormat.value) + } else { + obj.getString() + } + } + + /** + * 将字符串反序列化为 UUID 对象。 + * + * @param obj 需要反序列化的字符串,可能为 null 或空。 + * @param annotationGetter 用于获取注解信息的对象。 + * @return 反序列化后的 UUID 对象,如果输入无效则返回 null。 + */ + override fun deConvert( + obj: String?, + annotationGetter: AnnotationGetter + ): UUID? { + // 如果输入字符串为 null 或空,直接返回 null + if (obj.isNullish()) return null + + // 获取 UUIDJsonFormat 注解信息,若未找到则使用默认解析方式 + val dateTimeJsonFormat: UUIDJsonFormat = annotationGetter.get() ?: return UUID.of(obj) + + // 根据注解中的 base 属性选择合适的解析方式 + return if (BaseType.BASE16 != dateTimeJsonFormat.base) { + UUID.of(obj, dateTimeJsonFormat.base) + } else { + UUID.of(obj) + } + } + + /** + * 返回当前转换器支持的目标类型引用。 + * + * @return 表示 UUID 类型的 JsonTypeReference 对象。 + */ + override fun getFromType() = JsonTypeReference.of() +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/AnnotationGetter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/AnnotationGetter.kt new file mode 100644 index 0000000..c8c905e --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/AnnotationGetter.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile AnnotationGetter.kt + * LastUpdate 2026-02-05 11:12:36 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.converters.base + +/** + * 定义一个用于获取注解的接口。 + * 提供了一个通用方法来根据注解类型获取对应的注解实例。 + */ +interface AnnotationGetter { + /** + * 根据指定的注解类型获取该类型的注解实例。 + * + * @param clazz 注解的Class对象,表示要获取的注解类型。 + * @return 返回对应类型的注解实例,如果未找到则返回null。 + */ + fun get(clazz: Class): T? + + companion object { + /** + * 使用内联函数和重ified类型参数简化注解获取操作。 + * 通过传入泛型参数自动推断注解类型,避免手动传递Class对象。 + * + * @param T 注解类型,必须继承自Annotation。 + * @return 返回对应类型的注解实例,如果未找到则返回null。 + */ + inline fun AnnotationGetter.get(): T? = this.get(T::class.java) + + /** + * 提供一个默认实现,始终返回null。 + * 可用于需要空注解获取器的场景。 + */ + val nullGetter = object : AnnotationGetter { + /** + * 始终返回null,不执行任何实际逻辑。 + * + * @param clazz 注解的Class对象(未使用)。 + * @return 始终返回null。 + */ + override fun get(clazz: Class): T? = null + } + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonBigDecimalConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonBigDecimalConverter.kt new file mode 100644 index 0000000..fc12e1d --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonBigDecimalConverter.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile BaseJsonBigDecimalConverter.kt + * LastUpdate 2026-02-05 11:20:59 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.converters.base + +import com.mingliqiye.utils.json.api.type.JsonTypeReference +import java.math.BigDecimal + +/** + * 接口 [BaseJsonBigDecimalConverter] 定义了一个用于将 JSON 数据转换为 [BigDecimal] 类型的转换器。 + * + * 此接口继承自 [BaseJsonConverter],并指定目标类型为 [BigDecimal]。 + * 实现此接口的类需要提供具体的转换逻辑。 + * + * @param E 转换器处理的源数据类型。 + */ +interface BaseJsonBigDecimalConverter : BaseJsonConverter { + + /** + * 获取目标类型的引用信息。 + * + * 该方法返回一个 [JsonTypeReference] 对象,表示目标类型为 [BigDecimal]。 + * 通过调用 [JsonTypeReference.Companion.of] 方法创建类型引用。 + * + * @return 目标类型 [BigDecimal] 的类型引用。 + * @throws Exception 如果在获取类型引用时发生异常。 + */ + @Throws(Exception::class) + override fun getToType() = JsonTypeReference.of() +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonBigIntegerConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonBigIntegerConverter.kt new file mode 100644 index 0000000..2019ea6 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonBigIntegerConverter.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile BaseJsonBigIntegerConverter.kt + * LastUpdate 2026-02-05 11:20:59 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.converters.base + +import com.mingliqiye.utils.json.api.type.JsonTypeReference +import java.math.BigInteger + +/** + * 接口 [BaseJsonBigIntegerConverter] 定义了一个用于将 JSON 数据转换为 `BigInteger` 类型的转换器。 + * 该接口继承自 [BaseJsonConverter],并指定了泛型参数 [E] 和 `BigInteger`。 + * + * @param E 表示需要转换的目标类型,由实现类具体指定。 + */ +interface BaseJsonBigIntegerConverter : BaseJsonConverter { + + /** + * 获取目标类型的引用,用于 JSON 反序列化时确定目标类型。 + * 该方法重写了父接口中的 [getToType] 方法,并返回 `BigInteger` 类型的引用。 + * + * @return 返回 [JsonTypeReference] 对象,表示 `BigInteger` 类型的引用。 + * @throws Exception 如果在获取类型引用过程中发生异常,则抛出该异常。 + */ + @Throws(Exception::class) + override fun getToType() = JsonTypeReference.of() +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonBooleanConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonBooleanConverter.kt new file mode 100644 index 0000000..182c371 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonBooleanConverter.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile BaseJsonBooleanConverter.kt + * LastUpdate 2026-02-05 11:20:59 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.converters.base + +import com.mingliqiye.utils.json.api.type.JsonTypeReference + +/** + * 接口 [BaseJsonBooleanConverter] 定义了一个用于处理布尔类型 JSON 转换的基础转换器。 + * + * 该接口继承自 [BaseJsonConverter],并专门用于将某种类型 [E] 转换为布尔类型 [Boolean]。 + * 实现该接口的类需要提供具体的转换逻辑。 + */ +interface BaseJsonBooleanConverter : BaseJsonConverter { + + /** + * 获取目标类型的引用信息。 + * + * 该方法返回一个 [JsonTypeReference] 对象,表示目标类型为 [Boolean]。 + * 此方法重写了父接口中的定义,并通过 [JsonTypeReference.Companion.of] 方法获取布尔类型的引用。 + * + * @return [JsonTypeReference] 表示布尔类型的引用。 + * @throws Exception 如果在获取类型引用时发生异常,则抛出此异常。 + */ + @Throws(Exception::class) + override fun getToType() = JsonTypeReference.of() +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonByteConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonByteConverter.kt new file mode 100644 index 0000000..4a5274a --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonByteConverter.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile BaseJsonByteConverter.kt + * LastUpdate 2026-02-05 11:20:59 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.converters.base + +import com.mingliqiye.utils.json.api.type.JsonTypeReference + +/** + * BaseJsonByteConverter 是一个接口,继承自 BaseJsonConverter,用于定义字节类型(Byte)的 JSON 转换器。 + * + * 该接口的主要作用是提供一个标准的字节类型转换实现,并通过 getToType 方法返回目标类型的引用。 + * + * @param E 泛型参数,表示需要转换的目标对象类型。 + */ +interface BaseJsonByteConverter : BaseJsonConverter { + /** + * 获取目标类型的引用,用于标识转换的目标类型为 Byte。 + * + * @return 返回 JsonTypeReference 类型的实例,表示目标类型为 Byte。 + * @throws Exception 如果在获取类型引用过程中发生异常,则抛出该异常。 + */ + @Throws(Exception::class) + override fun getToType() = JsonTypeReference.of() +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonConverter.kt new file mode 100644 index 0000000..6e2eefc --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonConverter.kt @@ -0,0 +1,210 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile BaseJsonConverter.kt + * LastUpdate 2026-02-05 11:16:12 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.converters.base + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.core.JsonToken +import com.fasterxml.jackson.databind.* +import com.fasterxml.jackson.databind.deser.ContextualDeserializer +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.databind.ser.ContextualSerializer +import com.mingliqiye.utils.json.api.type.JsonTypeReference +import java.math.BigDecimal +import java.math.BigInteger + +/** + * BaseJsonConverter 是一个通用的 JSON 转换器接口,用于定义对象之间的双向转换逻辑。 + * + * @param F 源类型,表示需要被转换的对象类型。 + * @param T 目标类型,表示转换后的对象类型。 + */ +interface BaseJsonConverter { + + /** + * 将源对象转换为目标对象。 + * + * @param obj 源对象,可能为 null。 + * @param annotationGetter 注解获取器,用于获取字段上的注解信息。 + * @return 转换后的目标对象,可能为 null。 + * @throws Exception 转换过程中可能抛出的异常。 + */ + @Throws(Exception::class) + fun convert(obj: F?, annotationGetter: AnnotationGetter): T? + + /** + * 将目标对象反向转换为源对象。 + * + * @param obj 目标对象,可能为 null。 + * @param annotationGetter 注解获取器,用于获取字段上的注解信息。 + * @return 反向转换后的源对象,可能为 null。 + * @throws Exception 转换过程中可能抛出的异常。 + */ + @Throws(Exception::class) + fun deConvert(obj: T?, annotationGetter: AnnotationGetter): F? + + /** + * 获取源类型的类型引用。 + * + * @return 源类型的 JsonTypeReference 对象。 + * @throws Exception 获取过程中可能抛出的异常。 + */ + @Throws(Exception::class) + fun getFromType(): JsonTypeReference + + /** + * 获取目标类型的类型引用。 + * + * @return 目标类型的 JsonTypeReference 对象。 + * @throws Exception 获取过程中可能抛出的异常。 + */ + @Throws(Exception::class) + fun getToType(): JsonTypeReference + + /** + * 获取源类型的原始类。 + * + * @return 源类型的 Class 对象。 + * @throws Exception 获取过程中可能抛出的异常。 + */ + @Throws(Exception::class) + fun getFromClass() = getFromType().getRawType() + + /** + * 获取目标类型的原始类。 + * + * @return 目标类型的 Class 对象。 + * @throws Exception 获取过程中可能抛出的异常。 + */ + @Throws(Exception::class) + fun getToClass() = getToType().getRawType() + + /** + * 创建并返回一个 Jackson 模块,该模块包含自定义的序列化器和反序列化器。 + * + * @return 配置了自定义序列化器和反序列化器的 SimpleModule 对象。 + */ + fun getJacksonModule(): SimpleModule { + // 创建一个 Jackson 模块,名称由源类型和目标类型组成 + val module = SimpleModule("${getFromClass().name}To${getToClass().name}") + + // 定义自定义序列化器 + val serializer = object : JsonSerializer(), ContextualSerializer { + var property: BeanProperty? = null + + /** + * 序列化方法:将源对象转换为目标对象,并根据目标对象的类型写入 JSON。 + * + * @param value 源对象,可能为 null。 + * @param gen JSON 生成器,用于写入 JSON 数据。 + * @param provider 序列化提供者,可选参数。 + */ + override fun serialize( + value: F?, + gen: JsonGenerator, + provider: SerializerProvider? + ) { + // 执行转换逻辑 + val data: T? = convert(value, object : AnnotationGetter { + override fun get(clazz: Class): T? = property?.getAnnotation(clazz) + }) + + // 根据目标对象的类型写入对应的 JSON 值 + when (data) { + null -> gen.writeNull() + is String -> gen.writeString(data) + is Long -> gen.writeNumber(data) + is Short -> gen.writeNumber(data) + is Byte -> gen.writeNumber(data.toShort()) + is Int -> gen.writeNumber(data) + is BigDecimal -> gen.writeNumber(data) + is BigInteger -> gen.writeNumber(data) + is Float -> gen.writeNumber(data) + is Double -> gen.writeNumber(data) + is Boolean -> gen.writeBoolean(data) + else -> throw IllegalArgumentException("not sport data type ${data.javaClass} $data") + } + } + + /** + * 上下文感知方法:设置当前属性信息。 + * + * @param prov 序列化提供者,可选参数。 + * @param property 当前属性信息。 + * @return 返回当前序列化器实例。 + */ + override fun createContextual( + prov: SerializerProvider?, + property: BeanProperty? + ): JsonSerializer { + this.property = property + return this + } + } + + // 定义自定义反序列化器 + val deserializer = object : JsonDeserializer(), ContextualDeserializer { + /** + * 反序列化方法:从 JSON 解析数据并转换回源对象。 + * + * @param p JSON 解析器。 + * @param ctxt 反序列化上下文,可选参数。 + * @return 转换后的源对象,可能为 null。 + */ + override fun deserialize( + p: JsonParser, + ctxt: DeserializationContext? + ): F? { + // 处理 null 值情况 + if (p.currentToken == JsonToken.VALUE_NULL) return null + + // 执行反向转换逻辑 + return deConvert(p.readValueAs(getToClass()), object : AnnotationGetter { + override fun get(clazz: Class): T? = property?.getAnnotation(clazz) + }) + } + + var property: BeanProperty? = null + + /** + * 上下文感知方法:设置当前属性信息。 + * + * @param ctxt 反序列化上下文,可选参数。 + * @param property 当前属性信息。 + * @return 返回当前反序列化器实例。 + */ + override fun createContextual( + ctxt: DeserializationContext?, + property: BeanProperty? + ): JsonDeserializer { + this.property = property + return this + } + } + + // 将自定义序列化器和反序列化器注册到模块中 + return module + .addSerializer(getFromClass(), serializer) + .addDeserializer(getFromClass(), deserializer) + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonDoubleConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonDoubleConverter.kt new file mode 100644 index 0000000..1ea472c --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonDoubleConverter.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile BaseJsonDoubleConverter.kt + * LastUpdate 2026-02-05 11:20:59 + * UpdateUser MingLiPro + */ +package com.mingliqiye.utils.json.converters.base + +import com.mingliqiye.utils.json.api.type.JsonTypeReference + +/** + * BaseJsonDoubleConverter 是一个接口,继承自 BaseJsonConverter, + * 用于定义将 JSON 数据转换为 Double 类型的转换器。 + * + * @param E 泛型参数,表示需要转换的目标类型。 + */ +interface BaseJsonDoubleConverter : BaseJsonConverter { + + /** + * 获取目标类型的引用,用于标识转换的目标类型为 Double。 + * + * @return 返回 JsonTypeReference 类型的实例,表示目标类型为 Double。 + * @throws Exception 如果在获取类型引用时发生异常,则抛出该异常。 + */ + @Throws(Exception::class) + override fun getToType() = JsonTypeReference.of() +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonFloatConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonFloatConverter.kt new file mode 100644 index 0000000..dbca332 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonFloatConverter.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile BaseJsonFloatConverter.kt + * LastUpdate 2026-02-05 11:20:59 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.converters.base + +import com.mingliqiye.utils.json.api.type.JsonTypeReference + +/** + * 接口 [BaseJsonFloatConverter] 定义了一个用于处理浮点数类型 JSON 转换的基础转换器。 + * + * 该接口继承自 [BaseJsonConverter],并指定泛型参数 [E] 和 [Float], + * 表示将某种类型 [E] 转换为 [Float] 类型的 JSON 数据。 + */ +interface BaseJsonFloatConverter : BaseJsonConverter { + + /** + * 获取目标类型的引用信息。 + * + * 该方法重写了父接口中的 [getToType] 方法,返回 [Float] 类型的 [JsonTypeReference] 实例。 + * 用于标识当前转换器的目标类型为 [Float]。 + * + * @return [JsonTypeReference] 表示 [Float] 类型的引用信息。 + * @throws Exception 如果在获取类型引用时发生异常,则抛出此异常。 + */ + @Throws(Exception::class) + override fun getToType() = JsonTypeReference.of() +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonIntConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonIntConverter.kt new file mode 100644 index 0000000..2a2354a --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonIntConverter.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile BaseJsonIntConverter.kt + * LastUpdate 2026-02-05 11:20:59 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.converters.base + +import com.mingliqiye.utils.json.api.type.JsonTypeReference + +/** + * 接口 [BaseJsonIntConverter] 是一个泛型接口,继承自 [BaseJsonConverter], + * 用于定义将 JSON 数据转换为 [Int] 类型的转换器。 + * + * @param E 表示需要转换的目标类型,通常是一个具体的业务实体或数据模型。 + */ +interface BaseJsonIntConverter : BaseJsonConverter { + + /** + * 获取目标类型的引用信息。 + * + * 该方法重写了父接口中的 [getToType] 方法,返回 [Int] 类型的 [JsonTypeReference] 实例。 + * 用于标识当前转换器的目标类型为 [Int]。 + * + * @return 返回 [JsonTypeReference] 的实例,表示目标类型为 [Int]。 + * @throws Exception 如果在获取类型引用时发生异常,则抛出该异常。 + */ + @Throws(Exception::class) + override fun getToType() = JsonTypeReference.of() +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonLongConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonLongConverter.kt new file mode 100644 index 0000000..de1873b --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonLongConverter.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile BaseJsonLongConverter.kt + * LastUpdate 2026-02-05 11:20:59 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.converters.base + +import com.mingliqiye.utils.json.api.type.JsonTypeReference + +/** + * 接口 [BaseJsonLongConverter] 定义了一个用于将 JSON 数据转换为 Long 类型的转换器。 + * + * 此接口继承自 [BaseJsonConverter],并指定泛型参数 [E] 和 [Long], + * 表示该转换器处理的是从类型 [E] 到 [Long] 的转换。 + */ +interface BaseJsonLongConverter : BaseJsonConverter { + + /** + * 获取目标类型的引用,用于标识此转换器的目标类型为 [Long]。 + * + * @return 返回 [JsonTypeReference] 的实例,表示目标类型为 [Long]。 + * @throws Exception 如果在获取类型引用时发生异常,则抛出此异常。 + */ + @Throws(Exception::class) + override fun getToType() = JsonTypeReference.of() +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonShortConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonShortConverter.kt new file mode 100644 index 0000000..4f13d70 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonShortConverter.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile BaseJsonShortConverter.kt + * LastUpdate 2026-02-05 11:20:59 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.converters.base + +import com.mingliqiye.utils.json.api.type.JsonTypeReference + +/** + * 接口 [BaseJsonShortConverter] 定义了一个用于将 JSON 数据转换为 Short 类型的转换器。 + * + * 该接口继承自 [BaseJsonConverter],并指定泛型参数 [E] 和 [Short], + * 表示该转换器处理的源类型为 [E],目标类型为 [Short]。 + */ +interface BaseJsonShortConverter : BaseJsonConverter { + + /** + * 获取目标类型的引用信息。 + * + * 该方法重写了父接口中的 [getToType] 方法,返回 [Short] 类型的 [JsonTypeReference] 实例。 + * 通过 [JsonTypeReference.Companion.of] 方法创建类型引用。 + * + * @return [JsonTypeReference] 表示 [Short] 类型的引用。 + * @throws Exception 如果在获取类型引用过程中发生异常,则抛出该异常。 + */ + @Throws(Exception::class) + override fun getToType() = JsonTypeReference.of() +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonStringConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonStringConverter.kt new file mode 100644 index 0000000..eeaff15 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonStringConverter.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile BaseJsonStringConverter.kt + * LastUpdate 2026-02-05 11:20:59 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.converters.base + +import com.mingliqiye.utils.json.api.type.JsonTypeReference + +/** + * 接口 [BaseJsonStringConverter] 定义了一个用于 JSON 字符串转换的基础接口。 + * + * 该接口继承自 [BaseJsonConverter],并指定泛型参数 [E] 和 [String], + * 表示将类型 [E] 转换为字符串类型的 JSON 数据。 + */ +interface BaseJsonStringConverter : BaseJsonConverter { + + /** + * 获取目标类型的引用信息。 + * + * 该方法重写了父接口中的 [getToType] 方法,返回一个表示 [String] 类型的 [JsonTypeReference] 实例。 + * 此方法可能抛出异常,因此使用 [@Throws(Exception::class)] 注解标记。 + * + * @return 返回一个 [JsonTypeReference] 实例,表示目标类型为 [String]。 + * @throws Exception 如果在获取类型引用时发生错误,则抛出此异常。 + */ + @Throws(Exception::class) + override fun getToType() = JsonTypeReference.of() +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JsonConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JsonConverter.kt new file mode 100644 index 0000000..1a6d26b --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JsonConverter.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile JsonConverter.kt + * LastUpdate 2026-02-05 11:18:33 + * UpdateUser MingLiPro + */ +@file:JvmName("JsonConverter") + +package com.mingliqiye.utils.json.converters.base + + +import com.fasterxml.jackson.databind.ObjectMapper +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type + +/** + * 获取给定类型的实际类对象。 + * + * @param type 类型对象,可以是Class、ParameterizedType或其他Type的实现。 + * @return 返回与给定类型对应的Class对象;如果无法解析,则返回null。 + */ +fun getClass(type: Type?): Class<*>? { + // 尝试将type直接转换为Class类型,如果失败则检查是否为ParameterizedType + val clazz: Class<*>? = type as? Class<*> ?: if (type is ParameterizedType) { + // 如果是ParameterizedType,则递归获取其原始类型 + getClass(type.rawType) + } else { + // 其他情况返回null + null + } + return clazz +} + +/** + * 扩展ObjectMapper,用于注册指定类型的模块。 + * + * 此函数通过反射创建指定类型的实例,并调用其getJacksonModule方法来获取Jackson模块, + * 然后将该模块注册到当前ObjectMapper实例中。 + * + * @param T 必须继承自BaseJsonConverter的泛型类型。 + * @return 返回注册了模块后的ObjectMapper实例。 + */ +inline fun > ObjectMapper.registerModule(): ObjectMapper = + this.registerModule(T::class.java.newInstance().getJacksonModule()) diff --git a/src/main/kotlin/com/mingliqiye/utils/logger/Loggers.kt b/src/main/kotlin/com/mingliqiye/utils/logger/Loggers.kt index e75b366..5e54fc1 100644 --- a/src/main/kotlin/com/mingliqiye/utils/logger/Loggers.kt +++ b/src/main/kotlin/com/mingliqiye/utils/logger/Loggers.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile Loggers.kt - * LastUpdate 2025-09-18 09:30:48 + * LastUpdate 2026-02-05 10:20:31 * UpdateUser MingLiPro */ @@ -26,9 +26,7 @@ package com.mingliqiye.utils.logger import org.slf4j.Logger -import org.slf4j.LoggerFactory import org.slf4j.Marker -import java.util.* enum class MingLiLoggerLevel { TRACE, @@ -38,9 +36,9 @@ enum class MingLiLoggerLevel { ERROR } -class MingLiLogger : Logger { +class MingLiLogger(private val name: String) : Logger { override fun getName(): String { - return "MingLiLogger" + return name } override fun isTraceEnabled(): Boolean { @@ -345,14 +343,18 @@ class MingLiLogger : Logger { fun toPrintln(message: String, level: MingLiLoggerLevel) { when (level) { - MingLiLoggerLevel.TRACE -> println("[TRACE] $message") - MingLiLoggerLevel.DEBUG -> println("[DEBUG] $message") - MingLiLoggerLevel.INFO -> println("[INFO] $message") - MingLiLoggerLevel.WARN -> println("[WARN] $message") - MingLiLoggerLevel.ERROR -> println("[ERROR] $message") + MingLiLoggerLevel.TRACE -> wirteToSteam("[TRACE] [$name] $message\n") + MingLiLoggerLevel.DEBUG -> wirteToSteam("[DEBUG] [$name] $message\n") + MingLiLoggerLevel.INFO -> wirteToSteam("[INFO] [$name] $message\n") + MingLiLoggerLevel.WARN -> wirteToSteam("[WARN] [$name] $message\n") + MingLiLoggerLevel.ERROR -> wirteToSteam("[ERROR] [$name] $message\n") } } + fun wirteToSteam(string: String) { + System.out.write(string.toByteArray()) + } + private fun format1(format: String, arg: Any?): String { return if (format.contains("{}")) { format.replaceFirst("{}", arg?.toString() ?: "null") @@ -374,48 +376,3 @@ class MingLiLogger : Logger { return result } } - -class MingLiLoggerFactory { - private var hasSLF4JImplementation: Boolean? = null - - // 线程安全的延迟初始化 - private fun checkSLF4JImplementation(): Boolean { - if (hasSLF4JImplementation == null) { - synchronized(this) { - if (hasSLF4JImplementation == null) { - hasSLF4JImplementation = try { - // 更可靠的检测方式 - ServiceLoader.load( - Class.forName("org.slf4j.spi.SLF4JServiceProvider") - ).iterator().hasNext() - } catch (e: ClassNotFoundException) { - false - } catch (e: NoClassDefFoundError) { - false - } - } - } - } - return hasSLF4JImplementation ?: false - } - - fun getLogger(name: String): Logger { - return if (checkSLF4JImplementation()) { - LoggerFactory.getLogger(name) - } else { - MingLiLogger() - } - } - - fun getLogger(clazz: Class<*>): Logger { - return if (checkSLF4JImplementation()) { - LoggerFactory.getLogger(clazz) - } else { - MingLiLogger() - } - } -} - -val mingLiLoggerFactory: MingLiLoggerFactory by lazy { MingLiLoggerFactory() } - - diff --git a/src/main/kotlin/com/mingliqiye/utils/logger/MingLiLoggerFactory.kt b/src/main/kotlin/com/mingliqiye/utils/logger/MingLiLoggerFactory.kt new file mode 100644 index 0000000..b3b0834 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/logger/MingLiLoggerFactory.kt @@ -0,0 +1,106 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile MingLiLoggerFactory.kt + * LastUpdate 2026-02-05 10:20:31 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.logger + +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.lang.reflect.Method +import java.util.* + +class MingLiLoggerFactory { + + companion object { + + private var nameMethod: Method? = null + private var clazzMethod: Method? = null + + init { + try { + val clazz = Class.forName("com.mingliqiye.logger.Loggers") + nameMethod = clazz.getMethod("getLogger", String::class.java) + clazzMethod = clazz.getMethod("getLogger", Class::class.java) + } catch (e: Exception) { + } + } + + private val mingLiLoggerFactory: MingLiLoggerFactory by lazy { + MingLiLoggerFactory() + } + + + @JvmStatic + fun getLogger(name: String): Logger { + + return if (mingLiLoggerFactory.checkSLF4JImplementation()) { + if (nameMethod != null) { + nameMethod!!.invoke(null, name) as Logger + } else { + LoggerFactory.getLogger(name) + } + } else mingLiLoggerFactory.getLogger(name) + } + + @JvmStatic + fun getLogger(clazz: Class<*>): Logger { + return if (mingLiLoggerFactory.checkSLF4JImplementation()) { + if (clazzMethod != null) { + clazzMethod!!.invoke(null, clazz) as Logger + } else { + LoggerFactory.getLogger(clazz) + } + } else mingLiLoggerFactory.getLogger(clazz) + } + + inline fun getLogger() = getLogger(T::class.java) + } + + private var hasSLF4JImplementation: Boolean? = null + + // 线程安全的延迟初始化 + private fun checkSLF4JImplementation(): Boolean { + if (hasSLF4JImplementation == null) { + synchronized(this) { + if (hasSLF4JImplementation == null) { + hasSLF4JImplementation = try { + // 更可靠的检测方式 + ServiceLoader.load( + Class.forName("org.slf4j.spi.SLF4JServiceProvider") + ).iterator().hasNext() + } catch (e: ClassNotFoundException) { + false + } catch (e: NoClassDefFoundError) { + false + } + } + } + } + return hasSLF4JImplementation ?: false + } + + + @JvmName("getLogger_prc_name") + private fun getLogger(name: String): Logger = MingLiLogger(name) + + @JvmName("getLogger_prc_clazz") + private fun getLogger(clazz: Class<*>): Logger = MingLiLogger(clazz.name) +} diff --git a/src/main/kotlin/com/mingliqiye/utils/math/MathUtils.kt b/src/main/kotlin/com/mingliqiye/utils/math/MathUtils.kt new file mode 100644 index 0000000..fc61a27 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/math/MathUtils.kt @@ -0,0 +1,374 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile MathUtils.kt + * LastUpdate 2026-02-05 10:25:32 + * UpdateUser MingLiPro + */ +@file:JvmName("MathUtils") + +package com.mingliqiye.utils.math + +import java.math.BigDecimal +import java.math.BigInteger + +/** + * BigDecimal减法运算符重载 - BigDecimal类型 + * @param value 被减数 + * @return 减法运算结果 + */ +operator fun BigDecimal.minus(value: BigDecimal): BigDecimal = this.subtract(value) + +/** + * BigDecimal减法运算符重载 - Long类型 + * @param value 被减数 + * @return 减法运算结果 + */ +operator fun BigDecimal.minus(value: Long): BigDecimal = this.subtract(BigDecimal(value)) + + +/** + * BigDecimal减法运算符重载 - Int类型 + * @param value 被减数 + * @return 减法运算结果 + */ +operator fun BigDecimal.minus(value: Int): BigDecimal = this.subtract(BigDecimal(value)) + +/** + * BigDecimal减法运算符重载 - Double类型 + * @param value 被减数 + * @return 减法运算结果 + */ +operator fun BigDecimal.minus(value: Double): BigDecimal = this.subtract(BigDecimal(value.toString())) + +/** + * BigDecimal减法运算符重载 - Float类型 + * @param value 被减数 + * @return 减法运算结果 + */ +operator fun BigDecimal.minus(value: Float): BigDecimal = this.subtract(BigDecimal(value.toString())) + +/** + * BigDecimal减法运算符重载 - BigInteger类型 + * @param value 被减数 + * @return 减法运算结果 + */ +operator fun BigDecimal.minus(value: BigInteger): BigDecimal = this.subtract(BigDecimal(value)) + +/** + * BigDecimal加法运算符重载 - BigDecimal类型 + * @param value 加数 + * @return 加法运算结果 + */ +operator fun BigDecimal.plus(value: BigDecimal): BigDecimal = this.add(value) + +/** + * BigDecimal加法运算符重载 - Long类型 + * @param value 加数 + * @return 加法运算结果 + */ +operator fun BigDecimal.plus(value: Long): BigDecimal = this.add(BigDecimal(value)) + +/** + * BigDecimal加法运算符重载 - Int类型 + * @param value 加数 + * @return 加法运算结果 + */ +operator fun BigDecimal.plus(value: Int): BigDecimal = this.add(BigDecimal(value)) + +/** + * BigDecimal加法运算符重载 - Double类型 + * @param value 加数 + * @return 加法运算结果 + */ +operator fun BigDecimal.plus(value: Double): BigDecimal = this.add(BigDecimal(value.toString())) + +/** + * BigDecimal加法运算符重载 - Float类型 + * @param value 加数 + * @return 加法运算结果 + */ +operator fun BigDecimal.plus(value: Float): BigDecimal = this.add(BigDecimal(value.toString())) + +/** + * BigDecimal加法运算符重载 - BigInteger类型 + * @param value 加数 + * @return 加法运算结果 + */ +operator fun BigDecimal.plus(value: BigInteger): BigDecimal = this.add(BigDecimal(value)) + +/** + * BigDecimal乘法运算符重载 - BigDecimal类型 + * @param value 乘数 + * @return 乘法运算结果 + */ +operator fun BigDecimal.times(value: BigDecimal): BigDecimal = this.multiply(value) + +/** + * BigDecimal乘法运算符重载 - Long类型 + * @param value 乘数 + * @return 乘法运算结果 + */ +operator fun BigDecimal.times(value: Long): BigDecimal = this.multiply(BigDecimal(value)) + + +/** + * BigDecimal乘法运算符重载 - Int类型 + * @param value 乘数 + * @return 乘法运算结果 + */ +operator fun BigDecimal.times(value: Int): BigDecimal = this.multiply(BigDecimal(value)) + +/** + * BigDecimal乘法运算符重载 - Double类型 + * @param value 乘数 + * @return 乘法运算结果 + */ +operator fun BigDecimal.times(value: Double): BigDecimal = this.multiply(BigDecimal(value.toString())) + +/** + * BigDecimal乘法运算符重载 - Float类型 + * @param value 乘数 + * @return 乘法运算结果 + */ +operator fun BigDecimal.times(value: Float): BigDecimal = this.multiply(BigDecimal(value.toString())) + +/** + * BigDecimal乘法运算符重载 - BigInteger类型 + * @param value 乘数 + * @return 乘法运算结果 + */ +operator fun BigDecimal.times(value: BigInteger): BigDecimal = this.multiply(BigDecimal(value)) + +/** + * BigDecimal除法运算符重载 - BigDecimal类型 + * @param value 除数 + * @return 除法运算结果 + */ +operator fun BigDecimal.div(value: BigDecimal): BigDecimal = this.divide(value) + +/** + * BigDecimal除法运算符重载 - Long类型 + * @param value 除数 + * @return 除法运算结果 + */ +operator fun BigDecimal.div( + value: Long +): BigDecimal = this.divide(BigDecimal(value)) + +/** + * BigDecimal除法运算符重载 - Int类型 + * @param value 除数 + * @return 除法运算结果 + */ +operator fun BigDecimal.div( + value: Int +): BigDecimal = this.divide(BigDecimal(value)) + +/** + * BigDecimal除法运算符重载 - Double类型 + * @param value 除数 + * @return 除法运算结果 + */ +operator fun BigDecimal.div( + value: Double +): BigDecimal = this.divide(BigDecimal(value.toString())) + +/** + * BigDecimal除法运算符重载 - Float类型 + * @param value 除数 + * @return 除法运算结果 + */ +operator fun BigDecimal.div( + value: Float +): BigDecimal = this.divide(BigDecimal(value.toString())) + +/** + * BigDecimal除法运算符重载 - BigInteger类型 + * @param value 除数 + * @return 除法运算结果 + */ +operator fun BigDecimal.div( + value: BigInteger +): BigDecimal = this.divide(BigDecimal(value)) + +/** + * BigDecimal取余运算符重载 - BigDecimal类型 + * @param value 除数 + * @return 取余运算结果 + */ +operator fun BigDecimal.rem(value: BigDecimal): BigDecimal = this.remainder(value) + +/** + * BigDecimal取余运算符重载 - Long类型 + * @param value 除数 + * @return 取余运算结果 + */ +operator fun BigDecimal.rem(value: Long): BigDecimal = this.remainder(BigDecimal(value)) + +/** + * BigDecimal取余运算符重载 - Int类型 + * @param value 除数 + * @return 取余运算结果 + */ +operator fun BigDecimal.rem(value: Int): BigDecimal = this.remainder(BigDecimal(value)) + +/** + * BigDecimal取余运算符重载 - Double类型 + * @param value 除数 + * @return 取余运算结果 + */ +operator fun BigDecimal.rem(value: Double): BigDecimal = this.remainder(BigDecimal(value.toString())) + +/** + * BigDecimal取余运算符重载 - Float类型 + * @param value 除数 + * @return 取余运算结果 + */ +operator fun BigDecimal.rem(value: Float): BigDecimal = this.remainder(BigDecimal(value.toString())) + +/** + * BigDecimal取余运算符重载 - BigInteger类型 + * @param value 除数 + * @return 取余运算结果 + */ +operator fun BigDecimal.rem(value: BigInteger): BigDecimal = this.remainder(BigDecimal(value)) + +/** + * 将BigDecimal转换为去除末尾零的字符串表示 + * @return 去除末尾零后的字符串表示 + */ +fun BigDecimal.toFlexString(): String = this.stripTrailingZeros().toPlainString() + +/** + * 字符串转BigDecimal扩展函数 + * @return 转换后的BigDecimal对象 + */ +fun String.toBigDecimal(): BigDecimal = BigDecimal(this) + +/** + * Int转BigDecimal扩展函数 + * @return 转换后的BigDecimal对象 + */ +fun Int.toBigDecimal(): BigDecimal = BigDecimal(this) + +/** + * Byte转BigDecimal扩展函数 + * @return 转换后的BigDecimal对象 + */ +fun Byte.toBigDecimal(): BigDecimal = BigDecimal(this.toInt()) + +/** + * Short转BigDecimal扩展函数 + * @return 转换后的BigDecimal对象 + */ +fun Short.toBigDecimal(): BigDecimal = BigDecimal(this.toInt()) + +/** + * Float转BigDecimal扩展函数 + * @return 转换后的BigDecimal对象 + */ +fun Float.toBigDecimal(): BigDecimal = BigDecimal(this.toString()) + +/** + * Double转BigDecimal扩展函数 + * @return 转换后的BigDecimal对象 + */ +fun Double.toBigDecimal(): BigDecimal = BigDecimal(this.toString()) + +/** + * Long转BigDecimal扩展函数 + * @return 转换后的BigDecimal对象 + */ +fun Long.toBigDecimal(): BigDecimal = BigDecimal(this) + +/** + * BigInteger转BigDecimal扩展函数 + * @return 转换后的BigDecimal对象 + */ +fun BigInteger.toBigDecimal(): BigDecimal = BigDecimal(this) + +/** + * 带精度设置的字符串转BigDecimal扩展函数 + * @param scale 小数位数 + * @return 转换后并设置精度的BigDecimal对象 + */ +fun String.toBigDecimal(scale: Int): BigDecimal = BigDecimal(this).setScale(scale) + +/** + * 带精度设置的Int转BigDecimal扩展函数 + * @param scale 小数位数 + * @return 转换后并设置精度的BigDecimal对象 + */ +fun Int.toBigDecimal(scale: Int): BigDecimal = BigDecimal(this).setScale(scale) + +/** + * 带精度设置的Byte转BigDecimal扩展函数 + * @param scale 小数位数 + * @return 转换后并设置精度的BigDecimal对象 + */ +fun Byte.toBigDecimal(scale: Int): BigDecimal = BigDecimal(this.toInt()).setScale(scale) + +/** + * 带精度设置的Short转BigDecimal扩展函数 + * @param scale 小数位数 + * @return 转换后并设置精度的BigDecimal对象 + */ +fun Short.toBigDecimal(scale: Int): BigDecimal = BigDecimal(this.toInt()).setScale(scale) + +/** + * 带精度设置的Float转BigDecimal扩展函数 + * @param scale 小数位数 + * @return 转换后并设置精度的BigDecimal对象 + */ +fun Float.toBigDecimal(scale: Int): BigDecimal = BigDecimal(this.toString()).setScale(scale) + +/** + * 带精度设置的Double转BigDecimal扩展函数 + * @param scale 小数位数 + * @return 转换后并设置精度的BigDecimal对象 + */ +fun Double.toBigDecimal(scale: Int): BigDecimal = BigDecimal(this.toString()).setScale(scale) + +/** + * 带精度设置的Long转BigDecimal扩展函数 + * @param scale 小数位数 + * @return 转换后并设置精度的BigDecimal对象 + */ +fun Long.toBigDecimal(scale: Int): BigDecimal = BigDecimal(this).setScale(scale) + +/** + * 将当前 Number 对象转换为 BigDecimal 类型。 + * + * @return 返回与当前 Number 对象等值的 BigDecimal 实例。 + */ +fun Number.toBigDecimal(): BigDecimal = BigDecimal(this.toString()) + +/** + * 将当前 Number 对象转换为 BigInteger 类型。 + * + * @return 返回与当前 Number 对象等值的 BigInteger 实例。 + */ +fun Number.toBigInteger(): BigInteger = BigInteger(this.toString()) + +/** + * 将当前 Number 对象转换为指定精度的 BigDecimal 类型。 + * + * @param scale 指定小数点后的位数(精度)。 + * @return 返回与当前 Number 对象等值且具有指定精度的 BigDecimal 实例。 + */ +fun Number.toBigDecimal(scale: Int): BigDecimal = BigDecimal(this.toString()).setScale(scale) diff --git a/src/main/kotlin/com/mingliqiye/utils/mybatis/QuickBaseTypeHandler.kt b/src/main/kotlin/com/mingliqiye/utils/mybatis/QuickBaseTypeHandler.kt index 90d30bf..ddee32a 100644 --- a/src/main/kotlin/com/mingliqiye/utils/mybatis/QuickBaseTypeHandler.kt +++ b/src/main/kotlin/com/mingliqiye/utils/mybatis/QuickBaseTypeHandler.kt @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile QuickBaseTypeHandler.kt - * LastUpdate 2026-01-08 07:59:47 + * LastUpdate 2026-01-28 10:01:35 * UpdateUser MingLiPro */ @@ -53,7 +53,7 @@ abstract class QuickBaseTypeHandler : BaseTypeHandler() { ct: CallType, ci: Int?, cn: String? - ): T + ): T? /** * 抽象方法,用于将 Java 类型 T 设置到 PreparedStatement 中。 @@ -65,7 +65,7 @@ abstract class QuickBaseTypeHandler : BaseTypeHandler() { * @throws SQLException SQL 执行异常时抛出 */ @Throws(SQLException::class) - abstract fun setValue(ps: PreparedStatement, index: Int, parameter: T, jdbcType: JdbcType?) + abstract fun setValue(ps: PreparedStatement, index: Int, parameter: T?, jdbcType: JdbcType?) /** * 实现 BaseTypeHandler 的 setNonNullParameter 方法, @@ -78,7 +78,7 @@ abstract class QuickBaseTypeHandler : BaseTypeHandler() { * @throws SQLException SQL 执行异常时抛出 */ @Throws(SQLException::class) - override fun setNonNullParameter(ps: PreparedStatement, i: Int, parameter: T, jdbcType: JdbcType?) { + override fun setNonNullParameter(ps: PreparedStatement, i: Int, parameter: T?, jdbcType: JdbcType?) { setValue(ps, i, parameter, jdbcType) } @@ -92,7 +92,7 @@ abstract class QuickBaseTypeHandler : BaseTypeHandler() { * @throws SQLException SQL 执行异常时抛出 */ @Throws(SQLException::class) - override fun getNullableResult(rs: ResultSet, columnName: String): T { + override fun getNullableResult(rs: ResultSet, columnName: String): T? { return getValue(QuickBaseTypeHandlerValueGetter(null, rs), CallType.RESULTSET_NAME, null, columnName) } @@ -106,7 +106,7 @@ abstract class QuickBaseTypeHandler : BaseTypeHandler() { * @throws SQLException SQL 执行异常时抛出 */ @Throws(SQLException::class) - override fun getNullableResult(rs: ResultSet, columnIndex: Int): T { + override fun getNullableResult(rs: ResultSet, columnIndex: Int): T? { return getValue(QuickBaseTypeHandlerValueGetter(null, rs), CallType.RESULTSET_INDEX, columnIndex, null) } @@ -120,7 +120,7 @@ abstract class QuickBaseTypeHandler : BaseTypeHandler() { * @throws SQLException SQL 执行异常时抛出 */ @Throws(SQLException::class) - override fun getNullableResult(cs: CallableStatement, columnIndex: Int): T { + override fun getNullableResult(cs: CallableStatement, columnIndex: Int): T? { return getValue(QuickBaseTypeHandlerValueGetter(cs, null), CallType.CALLABLE_STATEMENT_INDEX, columnIndex, null) } } diff --git a/src/main/kotlin/com/mingliqiye/utils/mybatis/typehandler/datetime/DateTimeTypeHandler.kt b/src/main/kotlin/com/mingliqiye/utils/mybatis/typehandler/datetime/DateTimeTypeHandler.kt index 523116b..ca64ba4 100644 --- a/src/main/kotlin/com/mingliqiye/utils/mybatis/typehandler/datetime/DateTimeTypeHandler.kt +++ b/src/main/kotlin/com/mingliqiye/utils/mybatis/typehandler/datetime/DateTimeTypeHandler.kt @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile DateTimeTypeHandler.kt - * LastUpdate 2026-01-07 19:23:12 + * LastUpdate 2026-02-05 11:20:59 * UpdateUser MingLiPro */ @@ -30,6 +30,7 @@ import org.apache.ibatis.type.JdbcType import org.apache.ibatis.type.MappedTypes import java.sql.PreparedStatement import java.sql.Timestamp +import java.sql.Types.TIMESTAMP /** * DateTime类型处理器,用于在数据库和Java对象之间转换DateTime类型 @@ -42,22 +43,30 @@ class DateTimeTypeHandler : QuickBaseTypeHandler() { ct: CallType, ci: Int?, cn: String? - ): DateTime { + ): DateTime? { + val data = (when (ct) { + CallType.RESULTSET_INDEX -> vg.resultSet!!.getTimestamp(ci!!) + CallType.RESULTSET_NAME -> vg.resultSet!!.getTimestamp(cn!!) + CallType.CALLABLE_STATEMENT_INDEX -> vg.callableStatement!!.getTimestamp(ci!!) + }) + if (data == null) { + return null + } return DateTime.of( - (when (ct) { - CallType.RESULTSET_INDEX -> vg.resultSet!!.getTimestamp(ci!!) - CallType.RESULTSET_NAME -> vg.resultSet!!.getTimestamp(cn!!) - CallType.CALLABLE_STATEMENT_INDEX -> vg.callableStatement!!.getTimestamp(ci!!) - }) + data ) } override fun setValue( ps: PreparedStatement, index: Int, - parameter: DateTime, + parameter: DateTime?, jdbcType: JdbcType? ) { + if (parameter == null) { + ps.setNull(index, TIMESTAMP) + return + } ps.setTimestamp(index, Timestamp.valueOf(parameter.toLocalDateTime())) } } diff --git a/src/main/kotlin/com/mingliqiye/utils/mybatis/typehandler/mysql/uuid/MysqlUUIDBinaryTypeHandler.kt b/src/main/kotlin/com/mingliqiye/utils/mybatis/typehandler/mysql/uuid/MysqlUUIDBinaryTypeHandler.kt index 14bc082..084194a 100644 --- a/src/main/kotlin/com/mingliqiye/utils/mybatis/typehandler/mysql/uuid/MysqlUUIDBinaryTypeHandler.kt +++ b/src/main/kotlin/com/mingliqiye/utils/mybatis/typehandler/mysql/uuid/MysqlUUIDBinaryTypeHandler.kt @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile MysqlUUIDBinaryTypeHandler.kt - * LastUpdate 2026-01-07 19:30:31 + * LastUpdate 2026-01-28 10:08:46 * UpdateUser MingLiPro */ @@ -59,19 +59,29 @@ class MysqlUUIDBinaryTypeHandler : QuickBaseTypeHandler() { override fun getValue( vg: QuickBaseTypeHandlerValueGetter, ct: CallType, ci: Int?, cn: String? - ): UUID { + ): UUID? { + + val data: ByteArray? = when (ct) { + CallType.RESULTSET_NAME -> vg.resultSet!!.getBytes(cn!!) + CallType.RESULTSET_INDEX -> vg.resultSet!!.getBytes(ci!!) + CallType.CALLABLE_STATEMENT_INDEX -> vg.resultSet!!.getBytes(ci!!) + } + if (data == null) { + return null + } + return toUUID( - when (ct) { - CallType.RESULTSET_NAME -> vg.resultSet!!.getBytes(cn!!) - CallType.RESULTSET_INDEX -> vg.resultSet!!.getBytes(ci!!) - CallType.CALLABLE_STATEMENT_INDEX -> vg.resultSet!!.getBytes(ci!!) - } - )!! + data + ) } override fun setValue( - ps: PreparedStatement, index: Int, parameter: UUID, jdbcType: JdbcType? + ps: PreparedStatement, index: Int, parameter: UUID?, jdbcType: JdbcType? ) { + if (parameter == null) { + ps.setNull(index, java.sql.Types.BINARY) + return + } ps.setBytes(index, toByteArray(parameter)) } diff --git a/src/main/kotlin/com/mingliqiye/utils/mybatis/typehandler/pgsql/uuid/PgsqlUUIDTypeHandler.kt b/src/main/kotlin/com/mingliqiye/utils/mybatis/typehandler/pgsql/uuid/PgsqlUUIDTypeHandler.kt new file mode 100644 index 0000000..101a121 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/mybatis/typehandler/pgsql/uuid/PgsqlUUIDTypeHandler.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile PgsqlUUIDTypeHandler.kt + * LastUpdate 2026-02-05 11:20:59 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.mybatis.typehandler.pgsql.uuid + +import com.mingliqiye.utils.mybatis.CallType +import com.mingliqiye.utils.mybatis.QuickBaseTypeHandler +import com.mingliqiye.utils.mybatis.QuickBaseTypeHandlerValueGetter +import com.mingliqiye.utils.uuid.UUID +import org.apache.ibatis.type.JdbcType +import org.apache.ibatis.type.MappedTypes +import java.sql.PreparedStatement + +@MappedTypes(UUID::class) +class PgsqlUUIDTypeHandler : QuickBaseTypeHandler() { + override fun getValue( + vg: QuickBaseTypeHandlerValueGetter, ct: CallType, ci: Int?, cn: String? + ): UUID? { + val data: String? = when (ct) { + CallType.RESULTSET_INDEX -> + vg.resultSet!!.getString(ci!!) + + CallType.RESULTSET_NAME -> + vg.resultSet!!.getString(cn) + + CallType.CALLABLE_STATEMENT_INDEX -> + vg.callableStatement!!.getString(cn) + } + if (data == null) { + return null + } + return UUID.of(data) + } + + override fun setValue( + ps: PreparedStatement, index: Int, parameter: UUID?, jdbcType: JdbcType? + ) { + if (parameter == null) { + ps.setNull(index, jdbcType?.TYPE_CODE ?: JdbcType.JAVA_OBJECT.TYPE_CODE) + return + } + ps.setObject(index, parameter.getUuid()) + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/mybatisplus/BaseMapperQuery.kt b/src/main/kotlin/com/mingliqiye/utils/mybatisplus/BaseMapperQuery.kt new file mode 100644 index 0000000..a866e57 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/mybatisplus/BaseMapperQuery.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile QueryWrapper.kt + * LastUpdate 2026-02-03 12:03:27 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.mybatisplus + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper +import com.baomidou.mybatisplus.core.mapper.BaseMapper +import com.baomidou.mybatisplus.extension.kotlin.KtQueryWrapper +import com.baomidou.mybatisplus.extension.kotlin.KtUpdateChainWrapper +import com.baomidou.mybatisplus.extension.kotlin.KtUpdateWrapper + +/** + * BaseMapperQuery接口扩展了BaseMapper,提供了通用的查询包装器功能 + * + * @param T 实体类类型 + */ +interface BaseMapperQuery : BaseMapper { + /** + * 创建并返回一个新的QueryWrapper实例 + * + * @return QueryWrapper 返回类型化的查询包装器实例 + */ + fun queryWrapper() = QueryWrapper() + + /** + * 创建并返回一个新的UpdateWrapper实例 + * + * @return UpdateWrapper 返回类型化的更新包装器实例 + */ + fun updateWrapper() = UpdateWrapper() + + /** + * 创建并返回一个新的LambdaQueryWrapper实例 + * + * @return LambdaQueryWrapper 返回类型化的Lambda查询包装器实例 + */ + fun lambdaQueryWrapper() = LambdaQueryWrapper() + + /** + * 创建并返回一个新的LambdaUpdateWrapper实例 + * + * @return LambdaUpdateWrapper 返回类型化的Lambda更新包装器实例 + */ + fun lambdaUpdateWrapper() = LambdaUpdateWrapper() + + + companion object { + + /** + * 创建并返回一个新的QueryWrapper实例 + * + * @return QueryWrapper 返回类型化的查询包装器实例 + */ + inline fun BaseMapper.queryWrapper(): QueryWrapper = QueryWrapper() + + /** + * 创建并返回一个新的UpdateWrapper实例 + * + * @return UpdateWrapper 返回类型化的更新包装器实例 + */ + inline fun BaseMapper.updateWrapper(): UpdateWrapper = UpdateWrapper() + + /** + * 创建并返回一个新的LambdaQueryWrapper实例 + * + * @return LambdaQueryWrapper 返回类型化的Lambda查询包装器实例 + */ + inline fun BaseMapper.lambdaQueryWrapper(): LambdaQueryWrapper = + LambdaQueryWrapper(T::class.java) + + /** + * 创建并返回一个新的LambdaUpdateWrapper实例 + * + * @return LambdaUpdateWrapper 返回类型化的Lambda更新包装器实例 + */ + inline fun BaseMapper.lambdaUpdateWrapper(): LambdaUpdateWrapper = + LambdaUpdateWrapper(T::class.java) + + /** + * 创建并返回一个新的KtUpdateWrapper实例 + * + * @return KtUpdateWrapper 返回类型化的Kotlin更新包装器实例 + */ + inline fun BaseMapper.ktUpdateWrapper(): KtUpdateWrapper = + KtUpdateWrapper(T::class.java) + + /** + * 创建并返回一个新的KtQueryWrapper实例 + * + * @return KtQueryWrapper 返回类型化的Kotlin查询包装器实例 + */ + inline fun BaseMapper.ktQueryWrapper(): KtQueryWrapper = KtQueryWrapper(T::class.java) + + /** + * 创建并返回一个新的KtUpdateChainWrapper实例 + * + * @return KtUpdateChainWrapper 返回类型化的Kotlin更新链式包装器实例 + */ + inline fun BaseMapper.ktUpdateChainWrapper(): KtUpdateChainWrapper = + KtUpdateChainWrapper(T::class.java) + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/mybatisplus/QueryWrapper.kt b/src/main/kotlin/com/mingliqiye/utils/mybatisplus/QueryWrapper.kt deleted file mode 100644 index 7403add..0000000 --- a/src/main/kotlin/com/mingliqiye/utils/mybatisplus/QueryWrapper.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2026 mingliqiye - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ProjectName mingli-utils - * ModuleName mingli-utils.main - * CurrentFile QueryWrapper.kt - * LastUpdate 2026-01-14 13:00:31 - * UpdateUser MingLiPro - */ - -package com.mingliqiye.utils.mybatisplus - -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper -import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper -import com.baomidou.mybatisplus.core.mapper.BaseMapper - -/** - * BaseMapperQuery接口扩展了BaseMapper,提供了通用的查询包装器功能 - * - * @param T 实体类类型 - */ -interface BaseMapperQuery : BaseMapper { - /** - * 创建并返回一个新的QueryWrapper实例 - * - * @return QueryWrapper 返回类型化的查询包装器实例 - */ - fun queryWrapper(): QueryWrapper { - return QueryWrapper() - } - - /** - * 创建并返回一个新的UpdateWrapper实例 - * - * @return UpdateWrapper 返回类型化的更新包装器实例 - */ - fun updateWrapper(): UpdateWrapper { - return UpdateWrapper() - } - - /** - * 创建并返回一个新的LambdaQueryWrapper实例 - * - * @return LambdaQueryWrapper 返回类型化的Lambda查询包装器实例 - */ - fun lambdaQueryWrapper(): LambdaQueryWrapper { - return LambdaQueryWrapper() - } - - /** - * 创建并返回一个新的LambdaUpdateWrapper实例 - * - * @return LambdaUpdateWrapper 返回类型化的Lambda更新包装器实例 - */ - fun lambdaUpdateWrapper(): LambdaUpdateWrapper { - return LambdaUpdateWrapper() - } -} diff --git a/src/main/kotlin/com/mingliqiye/utils/netty/ClientScheduleReconnect.kt b/src/main/kotlin/com/mingliqiye/utils/netty/ClientScheduleReconnect.kt index ee305aa..ead9f4e 100644 --- a/src/main/kotlin/com/mingliqiye/utils/netty/ClientScheduleReconnect.kt +++ b/src/main/kotlin/com/mingliqiye/utils/netty/ClientScheduleReconnect.kt @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile ClientScheduleReconnect.kt - * LastUpdate 2026-01-08 10:45:37 + * LastUpdate 2026-01-20 08:10:15 * UpdateUser MingLiPro */ @@ -28,26 +28,128 @@ import io.netty.channel.ChannelFuture import io.netty.channel.ChannelFutureListener import java.util.concurrent.TimeUnit -abstract class ClientScheduleReconnect( - protected val bootstrap: Bootstrap, - protected var delay: Long = 10L, - protected var timeUnit: TimeUnit = TimeUnit.SECONDS -) { - protected var isStop = false - protected var channel: Channel? = null +/** + * 客户端调度重连抽象类,用于处理Netty客户端的自动重连机制 + * @param bootstrap Netty引导配置对象 + * @param delay 重连延迟时间,默认为10秒 + * @param timeUnit 延迟时间单位,默认为秒 + */ +abstract class ClientScheduleReconnect { + private val bootstrap: Bootstrap + private var delay: Long = 10L + private var timeUnit: TimeUnit = TimeUnit.SECONDS + constructor() : this(Bootstrap()) + constructor(bootstrap: Bootstrap) : this(bootstrap, 10L) + constructor(bootstrap: Bootstrap, delay: Long) : this(bootstrap, delay, TimeUnit.SECONDS) + constructor(bootstrap: Bootstrap, delay: Long, timeUnit: TimeUnit) { + this.bootstrap = bootstrap + this.delay = delay + this.timeUnit = timeUnit + } + + open fun start() { + initBootstrap(bootstrap) + connect() + } + + /** + * 获取Netty引导配置对象 + * @return Bootstrap 引导配置对象 + */ + open fun getBootstrap(): Bootstrap = bootstrap + + /** + * 获取当前连接的通道 + * @return Channel? 当前连接的通道,可能为空 + */ + open fun getChannel(): Channel? = channel + + /** + * 获取时间单位 + * @return TimeUnit 时间单位 + */ + open fun getTimeUnit(): TimeUnit = timeUnit + + /** + * 设置时间单位 + * @param timeUnit 新的时间单位 + */ + open fun setTimeUnit(timeUnit: TimeUnit) { + this.timeUnit = timeUnit + } + + /** + * 获取重连延迟时间 + * @return Long 延迟时间 + */ + open fun getDelay(): Long = delay + + /** + * 设置重连延迟时间 + * @param delay 新的延迟时间 + */ + open fun setDelay(delay: Long) { + this.delay = delay + } + + /** + * 检查是否已连接 + * @return Boolean 是否已连接 + */ + open fun isConnected(): Boolean = channel?.isActive ?: false + + private var isStop = false + private var channel: Channel? = null + + /** + * 连接成功时的日志记录回调 + * @param channel 成功连接的通道 + */ abstract fun onConnectedLog(channel: Channel) + + /** + * 连接失败时的日志记录回调 + * @param cause 连接失败的原因,可能为空 + */ abstract fun onConnectFailedLog(cause: Throwable?) + + /** + * 停止连接时的日志记录回调 + */ abstract fun onStoppedLog() + abstract fun onConnectClosed() + + /** + * 执行连接操作 + * @return ChannelFuture 连接结果的异步操作对象 + */ abstract fun doConnect(): ChannelFuture + /** + * 初始化引导配置 + * @param bootstrap 要初始化的引导配置对象 + */ + open fun initBootstrap(bootstrap: Bootstrap) { + + } + + /** + * 更新重连延迟配置 + * @param newDelay 新的延迟时间,必须大于0 + * @param newTimeUnit 新的时间单位 + * @throws IllegalArgumentException 当newDelay小于等于0时抛出 + */ open fun updateReconnectDelay(newDelay: Long, newTimeUnit: TimeUnit) { require(newDelay > 0) { "Delay must be positive" } this.delay = newDelay this.timeUnit = newTimeUnit } + /** + * 执行连接操作,设置连接完成监听器 + */ open fun connect() { doConnect().addListener(object : ChannelFutureListener { override fun operationComplete(future: ChannelFuture) { @@ -60,24 +162,33 @@ abstract class ClientScheduleReconnect( }) } - + /** + * 停止连接并标记为停止状态 + */ open fun stop() { isStop = true disConnect() } + /** + * 断开当前连接并清理通道引用 + */ open fun disConnect() { channel?.close() channel = null } - + /** + * 调度下一次重连操作 + */ open fun scheduleReconnect() { + // 检查是否已停止,如果已停止则记录日志并返回 if (isStop) { onStoppedLog() return } bootstrap.config().group().schedule({ + // 再次检查停止状态,避免在调度期间被停止 if (isStop) { onStoppedLog() return@schedule @@ -86,20 +197,27 @@ abstract class ClientScheduleReconnect( }, delay, timeUnit) } + /** + * 处理连接成功的逻辑 + * @param channel 成功连接的通道 + */ open fun onConnected(channel: Channel) { this.channel = channel onConnectedLog(channel) + // 监听通道关闭事件,以便在断开后重新调度连接 channel.closeFuture().addListener { _ -> scheduleReconnect() + onConnectClosed() } } + /** + * 处理连接失败的逻辑 + * @param cause 连接失败的原因 + */ open fun onConnectFailed(cause: Throwable?) { onConnectFailedLog(cause) scheduleReconnect() } - open fun isConnected(): Boolean { - return channel?.isActive ?: false - } } diff --git a/src/main/kotlin/com/mingliqiye/utils/netty/NamedNioEventLoopGroup.kt b/src/main/kotlin/com/mingliqiye/utils/netty/NamedNioEventLoopGroup.kt new file mode 100644 index 0000000..3c32db6 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/netty/NamedNioEventLoopGroup.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile NamedNioEventLoopGroup.kt + * LastUpdate 2026-02-05 10:20:31 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.netty + +import com.mingliqiye.utils.system.availableProcessors +import io.netty.channel.nio.NioEventLoopGroup +import java.text.MessageFormat + +/** + * 命名的NIO事件循环组,继承自NioEventLoopGroup + * 用于创建具有自定义命名规则的线程池,便于线程管理和调试 + * + * @param name 线程池的名称,默认为"NioEventLoopGroup" + * @param template 线程名称的格式模板,默认为"{0}-{2}-{3}",{0} 名字 {1} 类名 {2} 线程池序号 {3} 线程池内线程的序号 + * @param nThreads 线程数量,默认为核心数的2倍 + */ +open class NamedNioEventLoopGroup( + val template: String = "{0}-{2}-{3}", + val name: String = "NioEventLoopGroup", + nThreads: Int = availableProcessors * 2 +) : + NioEventLoopGroup( + nThreads, + NamedThreadFactory( + getName = { a, b, c -> MessageFormat.format(template, name, a, b, c) } + ) + ) diff --git a/src/main/kotlin/com/mingliqiye/utils/netty/NamedThreadFactory.kt b/src/main/kotlin/com/mingliqiye/utils/netty/NamedThreadFactory.kt index 7e52a9a..6d1898c 100644 --- a/src/main/kotlin/com/mingliqiye/utils/netty/NamedThreadFactory.kt +++ b/src/main/kotlin/com/mingliqiye/utils/netty/NamedThreadFactory.kt @@ -16,17 +16,19 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile NamedThreadFactory.kt - * LastUpdate 2026-01-08 13:21:00 + * LastUpdate 2026-01-31 21:05:02 * UpdateUser MingLiPro */ package com.mingliqiye.utils.netty -import io.netty.util.concurrent.FastThreadLocalThread import java.util.concurrent.ThreadFactory import java.util.concurrent.atomic.AtomicInteger - +/** + * 命名线程工厂类,用于创建具有自定义名称的线程 + * @param getName 用于获取线程名称的回调函数接口 + */ open class NamedThreadFactory(private val getName: NamedThreadFactoryNameGetter) : ThreadFactory { companion object { @@ -34,15 +36,33 @@ open class NamedThreadFactory(private val getName: NamedThreadFactoryNameGetter) @JvmStatic private val allThreadPoolNumber = AtomicInteger(0) + /** + * 函数式接口,用于定义线程名称生成规则 + */ @FunctionalInterface fun interface NamedThreadFactoryNameGetter { + /** + * 获取线程名称 + * @param clazz 线程工厂类 + * @param poolNumber 线程池编号 + * @param threadNumber 线程编号 + * @return 生成的线程名称 + */ fun getName(clazz: Class, poolNumber: Int, threadNumber: Int): String } + /** + * 默认的线程名称生成器 + */ @JvmStatic val defaultGetName = NamedThreadFactoryNameGetter { clazz, poolNumber, threadNumber -> "${clazz.simpleName}-$poolNumber-$threadNumber" } + /** + * 创建命名线程工厂实例 + * @param name 线程名称前缀 + * @return NamedThreadFactory实例 + */ @JvmStatic fun of(name: String): NamedThreadFactory { return NamedThreadFactory { a, b, c -> @@ -50,24 +70,42 @@ open class NamedThreadFactory(private val getName: NamedThreadFactoryNameGetter) } } + /** + * 创建命名线程工厂实例 + * @param getter 线程名称生成器,默认使用defaultGetName + * @return NamedThreadFactory实例 + */ @JvmStatic fun of(getter: NamedThreadFactoryNameGetter = defaultGetName): NamedThreadFactory { return NamedThreadFactory(getter) } } - + // 当前线程工厂的线程计数器 private val threadNumber = AtomicInteger(0) + + // 全局线程池计数器,用于标识不同的线程池 private val threadPoolNumber = allThreadPoolNumber.addAndGet(1) + /** + * 获取线程名称 + * @param clazz 线程工厂类 + * @param poolNumber 线程池编号 + * @param threadNumber 线程编号 + * @return 生成的线程名称 + */ open fun getThreadName(clazz: Class, poolNumber: Int, threadNumber: Int) = getName.getName(clazz, poolNumber, threadNumber) + /** + * 创建新线程 + * @param r 线程执行的任务 + * @return 新创建的线程对象 + */ override fun newThread(r: Runnable): Thread { - return FastThreadLocalThread( - null, - r, - getThreadName(this.javaClass, threadPoolNumber, threadNumber.addAndGet(1)) - ) + return Thread(r).let { + it.name = getThreadName(this.javaClass, threadPoolNumber, threadNumber.addAndGet(1)) + it + } } } diff --git a/src/main/kotlin/com/mingliqiye/utils/netty/NettyUtils.kt b/src/main/kotlin/com/mingliqiye/utils/netty/NettyUtils.kt index 1fc4519..bcf99ab 100644 --- a/src/main/kotlin/com/mingliqiye/utils/netty/NettyUtils.kt +++ b/src/main/kotlin/com/mingliqiye/utils/netty/NettyUtils.kt @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile NettyUtils.kt - * LastUpdate 2026-01-07 10:00:31 + * LastUpdate 2026-01-31 20:50:41 * UpdateUser MingLiPro */ @@ -24,9 +24,15 @@ package com.mingliqiye.utils.netty +import com.mingliqiye.utils.functions.P1Function +import io.netty.bootstrap.Bootstrap +import io.netty.bootstrap.ServerBootstrap import io.netty.buffer.ByteBuf import io.netty.buffer.Unpooled +import io.netty.channel.Channel +import io.netty.channel.ChannelInitializer import java.nio.ByteBuffer +import java.nio.charset.Charset /** * 将ByteBuffer转换为Netty的ByteBuf对象 @@ -79,3 +85,206 @@ fun ByteBuf.toByteArray(): ByteArray { fun ByteArray.toByteBuf(): ByteBuf { return Unpooled.wrappedBuffer(this) } + +/** + * 为ServerBootstrap配置子通道初始化器 + * + * 该函数用于设置ServerBootstrap的childHandler,当新的客户端连接建立时, + * 会调用提供的函数来初始化子通道 + * + * @receiver ServerBootstrap 需要配置的服务器引导程序实例 + * @param funa 用于初始化通道的回调函数,接收Channel作为参数 + * @return ServerBootstrap 返回当前实例以支持链式调用 + */ +@JvmName("channelInit") +fun ServerBootstrap.channelInitializer(funa: P1Function): ServerBootstrap { + this.childHandler(object : ChannelInitializer() { + override fun initChannel(ch: Channel) { + funa.call(ch) + } + }) + return this +} + +/** + * 为Bootstrap配置通道初始化器 + * + * 该函数用于设置Bootstrap的handler,当客户端连接建立时, + * 会调用提供的函数来初始化通道 + * + * @receiver Bootstrap 需要配置的客户端引导程序实例 + * @param funa 用于初始化通道的回调函数,接收Channel作为参数 + * @return Bootstrap 返回当前实例以支持链式调用 + */ +@JvmName("channelInit") +fun Bootstrap.channelInitializer(funa: P1Function): Bootstrap { + this.handler(object : ChannelInitializer() { + override fun initChannel(ch: Channel) { + funa.call(ch) + } + }) + return this +} + +/** + * 将字符串转换为Netty的ByteBuf对象 + * + * @receiver String 需要转换的原始字符串 + * @param charset 字符编码,默认UTF-8 + * @return ByteBuf 转换后的Netty ByteBuf对象 + */ +fun String.toByteBuf(charset: Charset = Charsets.UTF_8): ByteBuf { + return Unpooled.wrappedBuffer(this.toByteArray(charset)) +} + +/** + * 将ByteBuf转换为字符串 + * + * @receiver ByteBuf 需要转换的ByteBuf对象 + * @param charset 字符编码,默认UTF-8 + * @return String 转换后的字符串 + */ +fun ByteBuf.toString(charset: Charset = Charsets.UTF_8): String { + val bytes = this.toByteArray() + return String(bytes, charset) +} + + +/** + * 将Int转换为ByteBuf + * + * @receiver Int 需要转换的整数值 + * @return ByteBuf 转换后的Netty ByteBuf对象,包含4字节的整数数据 + */ +fun Int.toByteBuf(): ByteBuf { + val buffer = Unpooled.buffer(4) + buffer.writeInt(this) + return buffer +} + +/** + * 将Long转换为ByteBuf + * + * @receiver Long 需要转换的长整数值 + * @return ByteBuf 转换后的Netty ByteBuf对象,包含8字节的长整数数据 + */ +fun Long.toByteBuf(): ByteBuf { + val buffer = Unpooled.buffer(8) + buffer.writeLong(this) + return buffer +} + +/** + * 将ByteBuf转换为Int + * + * @receiver ByteBuf 需要转换的ByteBuf对象 + * @return Int 从ByteBuf中读取的整数值 + */ +fun ByteBuf.toInt(): Int { + return this.readInt() +} + +/** + * 将ByteBuf转换为Long + * + * @receiver ByteBuf 需要转换的ByteBuf对象 + * @return Long 从ByteBuf中读取的长整数值 + */ +fun ByteBuf.toLong(): Long { + return this.readLong() +} + +/** + * 安全地获取ByteBuf的字节数组,避免堆外内存访问异常 + * + * @receiver ByteBuf 需要转换的ByteBuf对象 + * @return ByteArray 转换后的字节数组 + */ +fun ByteBuf.safeToByteArray(): ByteArray { + return if (this.hasArray()) { + // 如果是堆内内存,直接获取数组 + this.array().copyOfRange(this.arrayOffset(), this.arrayOffset() + this.readableBytes()) + } else { + // 如果是堆外内存,使用readBytes + val array = ByteArray(this.readableBytes()) + this.getBytes(this.readerIndex(), array) + array + } +} + +/** + * 获取ByteBuf的十六进制字符串表示 + * + * @receiver ByteBuf 需要转换的ByteBuf对象 + * @return String 十六进制字符串表示 + */ +fun ByteBuf.toHexString(): String { + val array = this.safeToByteArray() + return array.joinToString("") { "%02x".format(it) } +} + +/** + * 合并多个ByteBuf为一个 + * + * @param byteBufs 需要合并的ByteBuf数组 + * @return ByteBuf 合并后的ByteBuf对象 + */ +fun combineByteBuf(vararg byteBufs: ByteBuf): ByteBuf { + val totalCapacity = byteBufs.sumOf { it.readableBytes() } + val combined = Unpooled.buffer(totalCapacity) + byteBufs.forEach { combined.writeBytes(it.duplicate()) } + return combined +} + +/** + * 安全关闭通道 + */ +fun Channel.closeSafely() { + if (isOpen) { + close() + } +} + +/** + * 检查通道是否处于活跃状态 + */ +val Channel.isActive: Boolean + get() = isOpen && isActive + +/** + * 异步写入数据到通道 + * + * @param data 需要写入的数据 + */ +fun Channel.writeAndFlushAsync(data: Any) { + writeAndFlush(data).addListener { future -> + if (!future.isSuccess) { + // 可以在这里添加错误处理逻辑 + println("Write failed: ${future.cause()}") + } + } +} + +/** + * 安全释放ByteBuf资源 + * + * @receiver ByteBuf 需要释放的ByteBuf对象 + * @return Boolean 释放操作是否成功 + */ +fun ByteBuf.releaseSafely(): Boolean { + return if (refCnt() > 0) release() else true +} + +/** + * 尝试释放ByteBuf资源,不抛出异常 + * + * @receiver ByteBuf 需要释放的ByteBuf对象 + * @return Boolean 释放操作是否成功 + */ +fun ByteBuf.tryRelease(): Boolean { + return try { + if (refCnt() > 0) release() else false + } catch (e: Exception) { + false + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/network/AddressPort.kt b/src/main/kotlin/com/mingliqiye/utils/network/AddressPort.kt deleted file mode 100644 index 88b210f..0000000 --- a/src/main/kotlin/com/mingliqiye/utils/network/AddressPort.kt +++ /dev/null @@ -1,509 +0,0 @@ -/* - * Copyright 2026 mingliqiye - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ProjectName mingli-utils - * ModuleName mingli-utils.main - * CurrentFile AddressPort.kt - * LastUpdate 2026-01-06 14:03:47 - * UpdateUser MingLiPro - */ - -package com.mingliqiye.utils.network - -import com.mingliqiye.utils.string.join -import java.io.Serializable -import java.net.InetAddress -import java.net.InetSocketAddress -import java.net.UnknownHostException -import java.nio.ByteBuffer -import java.util.regex.Pattern - -/** - * 网络地址类,用于表示一个网络地址(IP或域名),并提供相关操作。 - * 支持IPv4和IPv6地址的解析与验证。 - * - * @author MingLiPro - */ -class NetworkAddress private constructor(domip: String) : Serializable { - - /** - * IPv6标识 - */ - companion object { - const val IPV6 = 6 - - /** - * IPv4标识 - */ - const val IPV4 = 4 - - /** - * IPv4地址正则表达式 - */ - private const val IPV4REG = - "^((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})(\\.((2" + "(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})){3}$" - - /** - * 编译后的IPv4地址匹配模式 - */ - private val IPV4_PATTERN = Pattern.compile(IPV4REG) - - /** - * IPv6地址正则表达式 - */ - private const val IPV6REG = - "^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|" + "^(::([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})$" + "|" + "^(::)$|" + "^([0-9a-fA-F]{1,4}::([0-9a-fA-F]{1,4}:){0,5}[0-9a-fA-F]{1,4})$|" + "^(([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4})$|" + "^(([0-9a-fA-F]{1,4}:){6}(([0-9]{1,3}\\.){3}[0-9]{1,3}))$|" + "^::([fF]{4}:)?(([0-9]{1,3}\\.){3}[0-9]{1,3})$" - - /** - * 编译后的IPv6地址匹配模式 - */ - private val IPV6_PATTERN = Pattern.compile(IPV6REG) - - /** - * 静态工厂方法,创建 NetworkAddress 实例。 - * - * @param domip 可能是IP地址或域名的字符串 - * @return 新建的 NetworkAddress 实例 - */ - @JvmStatic - fun of(domip: String): NetworkAddress { - return NetworkAddress(domip) - } - - @JvmStatic - fun ofIpv4(byteBuffer: ByteBuffer): NetworkAddress { - val byteArray = ByteArray(4) - byteBuffer.get(byteArray) - return ofIpv4(byteArray) - } - - @JvmStatic - fun ofIpv4(byteArray: ByteArray): NetworkAddress { - return of(".".join(byteArray.map { - (it.toInt() and 0xFF).toString() - })) - } - - /** - * 静态工厂方法,通过 InetAddress 创建 NetworkAddress 实例。 - * - * @param inetAddress InetAddress 对象 - * @return 新建的 NetworkAddress 实例 - */ - @JvmStatic - fun of(inetAddress: InetAddress): NetworkAddress { - return NetworkAddress(inetAddress.hostAddress) - } - - /** - * 从DNS服务器解析域名获取对应的IP地址。 - * - * @param domain 域名 - * @return 解析出的第一个IP地址 - * @throws UnknownHostException 如果域名无法解析 - */ - @JvmStatic - @Throws(UnknownHostException::class) - fun getHostIp(domain: String): String { - val addresses = InetAddress.getAllByName(domain.trim()) - return addresses[0].hostAddress - } - - /** - * 检测给定字符串是否为有效的IPv4或IPv6地址。 - * - * @param ip 要检测的IP地址字符串 - * @return 4 表示IPv4,6 表示IPv6 - * @throws NetworkException 如果IP格式无效 - */ - @JvmStatic - fun testIp(ip: String?): Int { - if (ip == null) { - throw NetworkException("IP地址不能为null") - } - val trimmedIp = ip.trim() - - // 判断是否匹配IPv4格式 - if (IPV4_PATTERN.matcher(trimmedIp).matches()) { - return IPV4 - } - - // 判断是否匹配IPv6格式 - if (IPV6_PATTERN.matcher(trimmedIp).matches()) { - return IPV6 - } - - // 不符合任一格式时抛出异常 - throw NetworkException( - "[$ip] 不是有效的IPv4或IPv6地址" - ) - } - } - - /** - * IP地址类型:4 表示 IPv4,6 表示 IPv6 - */ - var iPv: Int = 0 - private set - - /** - * IP地址字符串 - */ - var ip: String? = null - private set - - /** - * 域名(如果输入的是域名) - */ - private var domain: String? = null - - /** - * 标识是否是域名解析来的IP - */ - private var isdom = false - - /** - * 构造方法,根据传入的字符串判断是IP地址还是域名,并进行相应处理。 - * - * @param domip 可能是IP地址或域名的字符串 - */ - init { - try { - // 尝试将输入识别为IP地址 - this.iPv = testIp(domip) - this.ip = domip - } catch (e: NetworkException) { - try { - // 如果不是有效IP,则尝试作为域名解析 - val ips = getHostIp(domip) - this.iPv = testIp(ips) - this.ip = ips - this.isdom = true - this.domain = domip - } catch (ex: UnknownHostException) { - throw NetworkException(ex) - } - } - } - - /** - * 将IPv4地址转换为字节数组 - * - * @return 返回表示IPv4地址的4字节数组 - * @throws NetworkException 当当前地址不是IPv4地址时抛出异常 - */ - fun toIpv4ByteArray(): ByteArray { - // 验证地址类型是否为IPv4 - if (iPv != IPV4) { - throw NetworkException("该地址 不是IPv4地址") - } - // 将IP地址字符串按点分割,转换为整数并进行位运算处理,最后转为字节数组 - return ip!!.split(".").map { it.toInt() and 0xFF }.map { it.toByte() }.toByteArray() - } - - /** - * 将IPv4地址写入到指定的ByteBuffer中 - * - * @param byteBuffer 要写入的ByteBuffer对象,IPv4地址将以字节数组形式写入到该缓冲区 - * @return 返回写入了IPv4地址的ByteBuffer对象,便于链式调用 - */ - fun writeIpv4ToByteBuffer(byteBuffer: ByteBuffer): ByteBuffer { - return byteBuffer.put(toIpv4ByteArray()) - } - - /** - * 将当前 NetworkAddress 转换为 InetAddress 对象。 - * - * @return InetAddress 对象 - */ - fun toInetAddress(): InetAddress { - try { - return InetAddress.getByName(if (ip != null) ip else domain) - } catch (e: UnknownHostException) { - throw RuntimeException(e) - } - } - - /** - * 返回 NetworkAddress 的字符串表示形式。 - * - * @return 字符串表示 - */ - override fun toString(): String { - return if (isdom) "NetworkAddress(IP='$ip',type='$iPv',domain='$domain')" - else "NetworkAddress(IP='$ip',type='$iPv')" - } -} - - -class NetworkPort : Serializable { - val port: Int - - constructor(port: Int) { - testPort(port) - this.port = port - } - - fun toByteArray(): ByteArray { - val byteArray = ByteArray(2) - byteArray[0] = (port shr 8 and 0xFF).toByte() - byteArray[1] = (port and 0xFF).toByte() - return byteArray - } - - fun writeToByteBuffer(byteBuffer: ByteBuffer): ByteBuffer { - return byteBuffer.put(toByteArray()) - } - - companion object { - - /** - * 创建NetworkPort实例 - * @param port 端口号 - * @return NetworkPort实例 - */ - @JvmStatic - fun of(port: Int): NetworkPort { - return NetworkPort(port) - } - - /** - * 从字节数组创建NetworkPort实例 - * @param byteArray 包含端口信息的字节数组(长度至少为2) - * @return NetworkPort实例 - */ - @JvmStatic - fun of(byteArray: ByteArray): NetworkPort { - // 将字节数组的前两个字节转换为端口号 - val port = ((byteArray[0].toInt() and 0xFF) shl 8) or (byteArray[1].toInt() and 0xFF) - return of(port) - } - - /** - * 从ByteBuffer创建NetworkPort实例 - * @param byteBuffer 包含端口信息的ByteBuffer - * @return NetworkPort实例 - */ - @JvmStatic - fun of(byteBuffer: ByteBuffer): NetworkPort { - val byteArray = ByteArray(2) - byteBuffer.get(byteArray) - return of(byteArray) - } - - @JvmStatic - fun testPort(port: Int) { - // 验证端口号范围是否在0-65535之间 - if (port !in 0..65535) { - throw NetworkException("$port 不是正确的端口号") - } - } - } -} - -class NetworkException : RuntimeException { - /** - * 构造一个带有指定详细消息的网络异常 - * - * @param message 异常的详细消息 - */ - constructor(message: String?) : super(message) - - /** - * 构造一个网络异常,指定原因异常 - * - * @param e 导致此异常的原因异常 - */ - constructor(e: Exception?) : super(e) -} - -/** - * IP和端口聚集类,用于封装网络地址与端口信息。 - * 该类提供了与InetSocketAddress之间的相互转换功能。 - * - * @author MingLiPro - * @see java.net.InetSocketAddress - */ -class NetworkEndpoint private constructor( - val networkAddress: NetworkAddress, val networkPort: NetworkPort -) : Serializable { - - - companion object { - /** - * 根据给定的InetSocketAddress对象创建NetworkEndpoint实例。 - * - * @param address InetSocketAddress对象 - * @return 新建的NetworkEndpoint实例 - * @see java.net.InetSocketAddress - */ - @JvmStatic - fun of(address: InetSocketAddress): NetworkEndpoint { - return NetworkEndpoint( - NetworkAddress.of(address.hostString), NetworkPort(address.port) - ) - } - - /** - * 根据主机名或IP字符串和端口号创建NetworkEndpoint实例。 - * - * @param s 主机名或IP地址字符串 - * @param i 端口号 - * @return 新建的NetworkEndpoint实例 - */ - @JvmStatic - fun of(s: String, i: Int): NetworkEndpoint { - val networkAddress = NetworkAddress.of(s) - val networkPort = NetworkPort(i) - return NetworkEndpoint(networkAddress, networkPort) - } - - /** - * 从字节数组创建IPv4网络端点 - * - * @param byteArray 包含IPv4地址和端口信息的字节数组,前4个字节表示IP地址,后2个字节表示端口号 - * @return NetworkEndpoint对象,封装了IPv4地址和端口信息 - */ - @JvmStatic - fun ofIpv4(byteArray: ByteArray): NetworkEndpoint { - // 提取前4个字节作为IP地址 - val address = ByteArray(4) { - byteArray[it] - } - // 提取后2个字节作为端口号 - val portInt = ByteArray(2) { - byteArray[it + 4] - } - return NetworkEndpoint(NetworkAddress.ofIpv4(address), NetworkPort.of(portInt)) - } - - /** - * 从字节缓冲区创建IPv4网络端点实例 - * - * 该方法从给定的ByteBuffer中读取数据,构建一个包含IPv4地址和端口的网络端点对象。 - * 该方法按照协议顺序从缓冲区中读取IPv4地址数据和端口数据。 - * - * @param byteBuffer 包含IPv4地址和端口数据的字节缓冲区,缓冲区中的数据应按照先地址后端口的顺序排列 - * @return 返回一个NetworkEndpoint实例,包含从缓冲区解析出的IPv4地址和端口信息 - */ - @JvmStatic - fun ofIpv4(byteBuffer: ByteBuffer): NetworkEndpoint { - return NetworkEndpoint( - NetworkAddress.ofIpv4(byteBuffer), - NetworkPort.of(byteBuffer) - ) - } - - /** - * 根据"host:port"格式的字符串创建NetworkEndpoint实例。 - * 例如:"127.0.0.1:8080" - * - * @param s "host:port"格式的字符串 - * @return 新建的NetworkEndpoint实例 - */ - @JvmStatic - fun of(s: String): NetworkEndpoint { - val lastColonIndex = s.lastIndexOf(':') - return of( - s.take(lastColonIndex), s.substring(lastColonIndex + 1).toInt() - ) - } - } - - /** - * 将当前NetworkEndpoint转换为InetSocketAddress对象。 - * - * @return 对应的InetSocketAddress对象 - * @see InetSocketAddress - */ - fun toInetSocketAddress(): InetSocketAddress { - return InetSocketAddress( - networkAddress.toInetAddress(), networkPort.port - ) - } - - /** - * 将当前NetworkEndpoint转换为"host:port"格式的字符串。 - * 例如:"127.0.0.1:25563" - * - * @return 格式化后的字符串 - */ - fun toHostPortString(): String { - return "${networkAddress.ip}:${networkPort.port}" - } - - /** - * 返回NetworkEndpoint的详细字符串表示形式。 - * 格式:NetworkEndpoint(IP=...,Port=...,Endpoint=...) - * - * @return 包含详细信息的字符串 - */ - override fun toString(): String { - return "NetworkEndpoint(IP=${networkAddress.ip},Port=${networkPort.port})" - } - - /** - * 获取主机名或IP地址字符串。 - * - * @return 主机名或IP地址 - */ - fun host(): String { - return networkAddress.ip ?: "" - } - - /** - * 获取端口号。 - * - * @return 端口号 - */ - fun port(): Int { - return networkPort.port - } - - /** - * 将网络地址和端口转换为IPv4字节数组 - * - * 该函数将当前对象的网络地址转换为IPv4字节数组,并与端口字节数组合并, - * 生成一个包含6个字节的数组,其中前4个字节为IPv4地址,后2个字节为端口号 - * - * @return ByteArray 包含6个字节的数组,前4个字节为IPv4地址,后2个字节为端口号 - */ - fun toIpv4ByteArray(): ByteArray { - val ipv4ByteArray = networkAddress.toIpv4ByteArray() - val portByteArray = networkPort.toByteArray() - // 构建包含IPv4地址和端口的6字节数组 - val byteArray = ByteArray(6) { - if (it < 4) { - ipv4ByteArray[it] - } else { - portByteArray[it - 4] - } - } - return byteArray - } - - /** - * 将IPv4地址和端口信息写入字节缓冲区 - * - * 该函数依次将网络地址的IPv4表示和网络端口写入到指定的字节缓冲区中 - * - * @param byteBuffer 要写入数据的目标字节缓冲区 - */ - fun writeIpv4toByteBuffer(byteBuffer: ByteBuffer): ByteBuffer { - networkAddress.writeIpv4ToByteBuffer(byteBuffer) - networkPort.writeToByteBuffer(byteBuffer) - return byteBuffer - } -} diff --git a/src/main/kotlin/com/mingliqiye/utils/network/NetworkAddress.kt b/src/main/kotlin/com/mingliqiye/utils/network/NetworkAddress.kt new file mode 100644 index 0000000..20f2f12 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/network/NetworkAddress.kt @@ -0,0 +1,247 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile NetworkAddress.kt + * LastUpdate 2026-02-05 10:20:31 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.network + +import com.mingliqiye.utils.string.join +import java.io.Serializable +import java.net.InetAddress +import java.net.UnknownHostException +import java.nio.ByteBuffer +import java.util.regex.Pattern + +/** + * 网络地址类,用于表示一个网络地址(IP或域名),并提供相关操作。 + * 支持IPv4和IPv6地址的解析与验证。 + * + * @author MingLiPro + */ +class NetworkAddress private constructor(domip: String) : Serializable { + + /** + * IPv6标识 + */ + companion object { + const val IPV6 = 6 + + /** + * IPv4标识 + */ + const val IPV4 = 4 + + /** + * IPv4地址正则表达式 + */ + private const val IPV4REG = + "^((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})(\\.((2" + "(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})){3}$" + + /** + * 编译后的IPv4地址匹配模式 + */ + private val IPV4_PATTERN = Pattern.compile(IPV4REG) + + /** + * IPv6地址正则表达式 + */ + private const val IPV6REG = + "^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|" + "^(::([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})$" + "|" + "^(::)$|" + "^([0-9a-fA-F]{1,4}::([0-9a-fA-F]{1,4}:){0,5}[0-9a-fA-F]{1,4})$|" + "^(([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4})$|" + "^(([0-9a-fA-F]{1,4}:){6}(([0-9]{1,3}\\.){3}[0-9]{1,3}))$|" + "^::([fF]{4}:)?(([0-9]{1,3}\\.){3}[0-9]{1,3})$" + + /** + * 编译后的IPv6地址匹配模式 + */ + private val IPV6_PATTERN = Pattern.compile(IPV6REG) + + /** + * 静态工厂方法,创建 NetworkAddress 实例。 + * + * @param domip 可能是IP地址或域名的字符串 + * @return 新建的 NetworkAddress 实例 + */ + @JvmStatic + fun of(domip: String): NetworkAddress { + return NetworkAddress(domip) + } + + @JvmStatic + fun ofIpv4(byteBuffer: ByteBuffer): NetworkAddress { + val byteArray = ByteArray(4) + byteBuffer.get(byteArray) + return ofIpv4(byteArray) + } + + @JvmStatic + fun ofIpv4(byteArray: ByteArray): NetworkAddress { + return of(".".join(byteArray.map { + (it.toInt() and 0xFF).toString() + })) + } + + /** + * 静态工厂方法,通过 InetAddress 创建 NetworkAddress 实例。 + * + * @param inetAddress InetAddress 对象 + * @return 新建的 NetworkAddress 实例 + */ + @JvmStatic + fun of(inetAddress: InetAddress): NetworkAddress { + return NetworkAddress(inetAddress.hostAddress) + } + + /** + * 从DNS服务器解析域名获取对应的IP地址。 + * + * @param domain 域名 + * @return 解析出的第一个IP地址 + * @throws java.net.UnknownHostException 如果域名无法解析 + */ + @JvmStatic + @Throws(UnknownHostException::class) + fun getHostIp(domain: String): String { + val addresses = InetAddress.getAllByName(domain.trim()) + return addresses[0].hostAddress + } + + /** + * 检测给定字符串是否为有效的IPv4或IPv6地址。 + * + * @param ip 要检测的IP地址字符串 + * @return 4 表示IPv4,6 表示IPv6 + * @throws NetworkException 如果IP格式无效 + */ + @JvmStatic + fun testIp(ip: String?): Int { + if (ip == null) { + throw NetworkException("IP地址不能为null") + } + val trimmedIp = ip.trim() + + // 判断是否匹配IPv4格式 + if (IPV4_PATTERN.matcher(trimmedIp).matches()) { + return IPV4 + } + + // 判断是否匹配IPv6格式 + if (IPV6_PATTERN.matcher(trimmedIp).matches()) { + return IPV6 + } + + // 不符合任一格式时抛出异常 + throw NetworkException( + "[$ip] 不是有效的IPv4或IPv6地址" + ) + } + } + + /** + * IP地址类型:4 表示 IPv4,6 表示 IPv6 + */ + var iPv: Int = 0 + private set + + /** + * IP地址字符串 + */ + var ip: String? = null + private set + + /** + * 域名(如果输入的是域名) + */ + private var domain: String? = null + + /** + * 标识是否是域名解析来的IP + */ + private var isdom = false + + /** + * 构造方法,根据传入的字符串判断是IP地址还是域名,并进行相应处理。 + * + * @param domip 可能是IP地址或域名的字符串 + */ + init { + try { + // 尝试将输入识别为IP地址 + this.iPv = testIp(domip) + this.ip = domip + } catch (e: NetworkException) { + try { + // 如果不是有效IP,则尝试作为域名解析 + val ips = getHostIp(domip) + this.iPv = testIp(ips) + this.ip = ips + this.isdom = true + this.domain = domip + } catch (ex: UnknownHostException) { + throw NetworkException(ex) + } + } + } + + /** + * 将IPv4地址转换为字节数组 + * + * @return 返回表示IPv4地址的4字节数组 + * @throws NetworkException 当当前地址不是IPv4地址时抛出异常 + */ + fun toIpv4ByteArray(): ByteArray { + // 验证地址类型是否为IPv4 + if (iPv != IPV4) { + throw NetworkException("该地址 不是IPv4地址") + } + // 将IP地址字符串按点分割,转换为整数并进行位运算处理,最后转为字节数组 + return ip!!.split(".").map { it.toInt() and 0xFF }.map { it.toByte() }.toByteArray() + } + + /** + * 将IPv4地址写入到指定的ByteBuffer中 + * + * @param byteBuffer 要写入的ByteBuffer对象,IPv4地址将以字节数组形式写入到该缓冲区 + * @return 返回写入了IPv4地址的ByteBuffer对象,便于链式调用 + */ + fun writeIpv4ToByteBuffer(byteBuffer: ByteBuffer): ByteBuffer { + return byteBuffer.put(toIpv4ByteArray()) + } + + /** + * 将当前 NetworkAddress 转换为 InetAddress 对象。 + * + * @return InetAddress 对象 + */ + fun toInetAddress(): InetAddress { + try { + return InetAddress.getByName(if (ip != null) ip else domain) + } catch (e: UnknownHostException) { + throw RuntimeException(e) + } + } + + /** + * 返回 NetworkAddress 的字符串表示形式。 + * + * @return 字符串表示 + */ + override fun toString(): String { + return if (isdom) "NetworkAddress(IP='$ip',type='$iPv',domain='$domain')" + else "NetworkAddress(IP='$ip',type='$iPv')" + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/network/NetworkEndpoint.kt b/src/main/kotlin/com/mingliqiye/utils/network/NetworkEndpoint.kt new file mode 100644 index 0000000..1abfc71 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/network/NetworkEndpoint.kt @@ -0,0 +1,206 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile NetworkEndpoint.kt + * LastUpdate 2026-02-05 10:20:31 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.network + +import java.io.Serializable +import java.net.InetSocketAddress +import java.nio.ByteBuffer + +/** + * IP和端口聚集类,用于封装网络地址与端口信息。 + * 该类提供了与InetSocketAddress之间的相互转换功能。 + * + * @author MingLiPro + * @see java.net.InetSocketAddress + */ +class NetworkEndpoint private constructor( + val networkAddress: NetworkAddress, val networkPort: NetworkPort +) : Serializable, InetSocketAddress(networkAddress.toInetAddress(), networkPort.port) { + + + companion object { + /** + * 根据给定的InetSocketAddress对象创建NetworkEndpoint实例。 + * + * @param address InetSocketAddress对象 + * @return 新建的NetworkEndpoint实例 + * @see InetSocketAddress + */ + @JvmStatic + fun of(address: InetSocketAddress): NetworkEndpoint { + return NetworkEndpoint( + NetworkAddress.of(address.hostString), NetworkPort(address.port) + ) + } + + /** + * 根据主机名或IP字符串和端口号创建NetworkEndpoint实例。 + * + * @param s 主机名或IP地址字符串 + * @param i 端口号 + * @return 新建的NetworkEndpoint实例 + */ + @JvmStatic + fun of(s: String, i: Int): NetworkEndpoint { + val networkAddress = NetworkAddress.of(s) + val networkPort = NetworkPort(i) + return NetworkEndpoint(networkAddress, networkPort) + } + + /** + * 从字节数组创建IPv4网络端点 + * + * @param byteArray 包含IPv4地址和端口信息的字节数组,前4个字节表示IP地址,后2个字节表示端口号 + * @return NetworkEndpoint对象,封装了IPv4地址和端口信息 + */ + @JvmStatic + fun ofIpv4(byteArray: ByteArray): NetworkEndpoint { + // 提取前4个字节作为IP地址 + val address = ByteArray(4) { + byteArray[it] + } + // 提取后2个字节作为端口号 + val portInt = ByteArray(2) { + byteArray[it + 4] + } + return NetworkEndpoint(NetworkAddress.ofIpv4(address), NetworkPort.of(portInt)) + } + + /** + * 从字节缓冲区创建IPv4网络端点实例 + * + * 该方法从给定的ByteBuffer中读取数据,构建一个包含IPv4地址和端口的网络端点对象。 + * 该方法按照协议顺序从缓冲区中读取IPv4地址数据和端口数据。 + * + * @param byteBuffer 包含IPv4地址和端口数据的字节缓冲区,缓冲区中的数据应按照先地址后端口的顺序排列 + * @return 返回一个NetworkEndpoint实例,包含从缓冲区解析出的IPv4地址和端口信息 + */ + @JvmStatic + fun ofIpv4(byteBuffer: ByteBuffer): NetworkEndpoint { + return NetworkEndpoint( + NetworkAddress.ofIpv4(byteBuffer), + NetworkPort.of(byteBuffer) + ) + } + + /** + * 根据"host:port"格式的字符串创建NetworkEndpoint实例。 + * 例如:"127.0.0.1:8080" + * + * @param s "host:port"格式的字符串 + * @return 新建的NetworkEndpoint实例 + */ + @JvmStatic + fun of(s: String): NetworkEndpoint { + val lastColonIndex = s.lastIndexOf(':') + return of( + s.take(lastColonIndex), s.substring(lastColonIndex + 1).toInt() + ) + } + } + + /** + * 将当前NetworkEndpoint转换为InetSocketAddress对象。 + * + * @return 对应的InetSocketAddress对象 + * @see InetSocketAddress + */ + fun toInetSocketAddress(): InetSocketAddress { + return InetSocketAddress( + networkAddress.toInetAddress(), networkPort.port + ) + } + + /** + * 将当前NetworkEndpoint转换为"host:port"格式的字符串。 + * 例如:"127.0.0.1:25563" + * + * @return 格式化后的字符串 + */ + fun toHostPortString(): String { + return "${networkAddress.ip}:${networkPort.port}" + } + + /** + * 返回NetworkEndpoint的详细字符串表示形式。 + * 格式:NetworkEndpoint(IP=...,Port=...,Endpoint=...) + * + * @return 包含详细信息的字符串 + */ + override fun toString(): String { + return "NetworkEndpoint(IP=${networkAddress.ip},Port=${networkPort.port})" + } + + /** + * 获取主机名或IP地址字符串。 + * + * @return 主机名或IP地址 + */ + fun host(): String { + return networkAddress.ip ?: "" + } + + /** + * 获取端口号。 + * + * @return 端口号 + */ + fun port(): Int { + return networkPort.port + } + + /** + * 将网络地址和端口转换为IPv4字节数组 + * + * 该函数将当前对象的网络地址转换为IPv4字节数组,并与端口字节数组合并, + * 生成一个包含6个字节的数组,其中前4个字节为IPv4地址,后2个字节为端口号 + * + * @return ByteArray 包含6个字节的数组,前4个字节为IPv4地址,后2个字节为端口号 + */ + fun toIpv4ByteArray(): ByteArray { + val ipv4ByteArray = networkAddress.toIpv4ByteArray() + val portByteArray = networkPort.toByteArray() + // 构建包含IPv4地址和端口的6字节数组 + val byteArray = ByteArray(6) { + if (it < 4) { + ipv4ByteArray[it] + } else { + portByteArray[it - 4] + } + } + return byteArray + } + + /** + * 将IPv4地址和端口信息写入字节缓冲区 + * + * 该函数依次将网络地址的IPv4表示和网络端口写入到指定的字节缓冲区中 + * + * @param byteBuffer 要写入数据的目标字节缓冲区 + */ + fun writeIpv4toByteBuffer(byteBuffer: ByteBuffer): ByteBuffer { + networkAddress.writeIpv4ToByteBuffer(byteBuffer) + networkPort.writeToByteBuffer(byteBuffer) + return byteBuffer + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/network/NetworkException.kt b/src/main/kotlin/com/mingliqiye/utils/network/NetworkException.kt new file mode 100644 index 0000000..c1362f3 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/network/NetworkException.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile NetworkException.kt + * LastUpdate 2026-02-05 10:20:31 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.network + +class NetworkException : RuntimeException { + /** + * 构造一个带有指定详细消息的网络异常 + * + * @param message 异常的详细消息 + */ + constructor(message: String?) : super(message) + + /** + * 构造一个网络异常,指定原因异常 + * + * @param e 导致此异常的原因异常 + */ + constructor(e: Exception?) : super(e) +} diff --git a/src/main/kotlin/com/mingliqiye/utils/network/NetworkPort.kt b/src/main/kotlin/com/mingliqiye/utils/network/NetworkPort.kt new file mode 100644 index 0000000..5ff3368 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/network/NetworkPort.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile NetworkPort.kt + * LastUpdate 2026-02-05 10:20:31 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.network + +import java.io.Serializable +import java.nio.ByteBuffer + +class NetworkPort : Serializable { + val port: Int + + constructor(port: Int) { + testPort(port) + this.port = port + } + + fun toByteArray(): ByteArray { + val byteArray = ByteArray(2) + byteArray[0] = (port shr 8 and 0xFF).toByte() + byteArray[1] = (port and 0xFF).toByte() + return byteArray + } + + fun writeToByteBuffer(byteBuffer: ByteBuffer): ByteBuffer { + return byteBuffer.put(toByteArray()) + } + + companion object { + + /** + * 创建NetworkPort实例 + * @param port 端口号 + * @return NetworkPort实例 + */ + @JvmStatic + fun of(port: Int): NetworkPort { + return NetworkPort(port) + } + + /** + * 从字节数组创建NetworkPort实例 + * @param byteArray 包含端口信息的字节数组(长度至少为2) + * @return NetworkPort实例 + */ + @JvmStatic + fun of(byteArray: ByteArray): NetworkPort { + // 将字节数组的前两个字节转换为端口号 + val port = ((byteArray[0].toInt() and 0xFF) shl 8) or (byteArray[1].toInt() and 0xFF) + return of(port) + } + + /** + * 从ByteBuffer创建NetworkPort实例 + * @param byteBuffer 包含端口信息的ByteBuffer + * @return NetworkPort实例 + */ + @JvmStatic + fun of(byteBuffer: ByteBuffer): NetworkPort { + val byteArray = ByteArray(2) + byteBuffer.get(byteArray) + return of(byteArray) + } + + @JvmStatic + fun testPort(port: Int) { + // 验证端口号范围是否在0-65535之间 + if (port !in 0..65535) { + throw NetworkException("$port 不是正确的端口号") + } + } + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/objects/NullUtils.kt b/src/main/kotlin/com/mingliqiye/utils/objects/NullUtils.kt new file mode 100644 index 0000000..b367040 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/objects/NullUtils.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile NullUtils.kt + * LastUpdate 2026-02-05 10:17:02 + * UpdateUser MingLiPro + */ +@file:JvmName("NullUtils") + +package com.mingliqiye.utils.objects + +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +@OptIn(ExperimentalContracts::class) +fun Any?.isNotNull(): Boolean { + contract { + returns(true) implies (this@isNotNull != null) + } + return !isNull() +} + +@OptIn(ExperimentalContracts::class) +fun Any?.isNull(): Boolean { + contract { + returns(false) implies (this@isNull != null) + } + return this == null +} + +fun areEquals(a: Any?, b: Any?) = (a == null && b == null) || (a == b) + +@OptIn(ExperimentalContracts::class) +fun areEqualsNoNull(a: Any?, b: Any?): Boolean { + contract { + returns(true) implies (a != null && b != null) + } + return a != null && b != null && a == b +} diff --git a/src/main/kotlin/com/mingliqiye/utils/path/OsPath.kt b/src/main/kotlin/com/mingliqiye/utils/path/OsPath.kt index f88d949..4e9dc63 100644 --- a/src/main/kotlin/com/mingliqiye/utils/path/OsPath.kt +++ b/src/main/kotlin/com/mingliqiye/utils/path/OsPath.kt @@ -28,8 +28,17 @@ import java.io.File import java.net.URI import java.nio.file.Path import java.nio.file.Paths +import java.util.* +import java.util.function.Consumer class OsPath private constructor(private val path: Path) : Path by path { + override fun forEach(action: Consumer?) { + path.forEach(action) + } + + override fun spliterator(): Spliterator { + return path.spliterator() + } companion object { @JvmStatic diff --git a/src/main/kotlin/com/mingliqiye/utils/random/RandomBytes.kt b/src/main/kotlin/com/mingliqiye/utils/random/RandomBytes.kt index cfdb33b..d4842d6 100644 --- a/src/main/kotlin/com/mingliqiye/utils/random/RandomBytes.kt +++ b/src/main/kotlin/com/mingliqiye/utils/random/RandomBytes.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,13 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile RandomBytes.kt - * LastUpdate 2025-09-16 17:42:26 + * LastUpdate 2026-01-28 10:47:13 * UpdateUser MingLiPro */ @file:JvmName("RandomBytes") package com.mingliqiye.utils.random -import java.security.SecureRandom - /** * 生成指定长度的随机字节数组 * @param length 数组长度 @@ -38,6 +36,7 @@ fun randomBytes(length: Int): ByteArray { return bytes } + /** * 生成指定长度的随机字节数组 * 从给定的字节数组中随机选择字节来填充新数组 @@ -81,12 +80,8 @@ fun randomByteNoHave(from: Byte, to: Byte): Byte { return (randomValue and 0xFF).toByte() } -val secureRandom: SecureRandom by lazy { - SecureRandom.getInstanceStrong() -} - -fun randomByteSecure(size: Int): ByteArray { +fun randomByte(size: Int): ByteArray { val bytes = ByteArray(size) secureRandom.nextBytes(bytes) return bytes diff --git a/src/main/kotlin/com/mingliqiye/utils/random/RandomInt.kt b/src/main/kotlin/com/mingliqiye/utils/random/RandomInt.kt index 367f4b2..12c7fb1 100644 --- a/src/main/kotlin/com/mingliqiye/utils/random/RandomInt.kt +++ b/src/main/kotlin/com/mingliqiye/utils/random/RandomInt.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,24 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile RandomInt.kt - * LastUpdate 2025-09-12 17:08:32 + * LastUpdate 2026-01-28 10:46:02 * UpdateUser MingLiPro */ @file:JvmName("RandomInt") package com.mingliqiye.utils.random -import java.util.concurrent.ThreadLocalRandom +import java.security.SecureRandom + +val secureRandom: SecureRandom by lazy { + SecureRandom.getInstanceStrong() +} + + +fun SecureRandom.nextInt(min: Int, max: Int): Int { + return min + nextInt(max - min) +} + /** * 生成指定范围内的随机整数 @@ -42,7 +52,7 @@ fun randomIntNoHave(min: Int, max: Int): Int { if (min == max) { return min } - return ThreadLocalRandom.current().nextInt(min, max) + return secureRandom.nextInt(min, max) } /** @@ -55,6 +65,3 @@ fun randomInt(min: Int, max: Int): Int { var max = max return randomIntNoHave(min, ++max) } - - - diff --git a/src/main/kotlin/com/mingliqiye/utils/random/RandomString.kt b/src/main/kotlin/com/mingliqiye/utils/random/RandomString.kt index 8b4ffbb..cea1495 100644 --- a/src/main/kotlin/com/mingliqiye/utils/random/RandomString.kt +++ b/src/main/kotlin/com/mingliqiye/utils/random/RandomString.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile RandomString.kt - * LastUpdate 2025-09-12 17:10:43 + * LastUpdate 2026-02-05 11:12:36 * UpdateUser MingLiPro */ @file:JvmName("RandomString") diff --git a/src/main/kotlin/com/mingliqiye/utils/request/Require.kt b/src/main/kotlin/com/mingliqiye/utils/request/Require.kt deleted file mode 100644 index 0e687f9..0000000 --- a/src/main/kotlin/com/mingliqiye/utils/request/Require.kt +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright 2026 mingliqiye - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ProjectName mingli-utils - * ModuleName mingli-utils.main - * CurrentFile Require.kt - * LastUpdate 2026-01-10 09:01:03 - * UpdateUser MingLiPro - */ - -package com.mingliqiye.utils.request - -import com.mingliqiye.utils.functions.P1RFunction -import com.mingliqiye.utils.functions.RFunction - -/** - * 扩展函数:基于布尔值创建Require对象,并指定异常消息和异常构造器 - * @param message 异常消息 - * @param exception 异常构造器函数 - * @return Require对象 - */ -fun Boolean.require(message: String, exception: P1RFunction): Require { - return Require(this, message, exception) -} - -/** - * 扩展函数:基于布尔值创建Require对象,并指定异常消息和异常类型 - * @param message 异常消息 - * @param exception 异常类型,默认为IllegalArgumentException - * @return Require对象 - */ -fun Boolean.require( - message: String, - exception: Class = IllegalArgumentException::class.java -): Require { - return Require(this, message, exception) -} - -/** - * 条件检查工具类,用于验证条件并抛出相应异常 - * @param must 需要验证的布尔条件 - */ -class Require(private val must: Boolean) { - - /** - * 构造函数:通过函数调用结果初始化条件检查器 - * @param funs 返回布尔值的函数 - */ - constructor(funs: RFunction) : this(funs.call()) - - /** - * 构造函数:通过函数调用结果初始化条件检查器,并立即执行检查 - * @param must 返回布尔值的函数 - * @param message 检查失败时的异常消息 - */ - constructor(must: RFunction, message: String) : this(must) { - throws(message) - } - - /** - * 构造函数:通过函数调用结果初始化条件检查器,并立即执行检查 - * @param must 返回布尔值的函数 - * @param message 检查失败时的异常消息 - * @param exception 检查失败时抛出的异常类型,默认为IllegalArgumentException - */ - constructor( - must: RFunction, - message: String, - exception: Class = IllegalArgumentException::class.java - ) : this(must) { - throws(message, exception) - } - - /** - * 构造函数:通过布尔值初始化条件检查器,并立即执行检查 - * @param must 需要验证的布尔条件 - * @param message 检查失败时的异常消息 - */ - constructor(must: Boolean, message: String) : this(must) { - throws(message) - } - - /** - * 构造函数:通过布尔值初始化条件检查器,并立即执行检查 - * @param must 需要验证的布尔条件 - * @param message 检查失败时的异常消息 - * @param exception 检查失败时抛出的异常类型,默认为IllegalArgumentException - */ - constructor( - must: Boolean, message: String, exception: Class = IllegalArgumentException::class.java - ) : this(must) { - throws(message, exception) - } - - /** - * 构造函数:通过布尔值初始化条件检查器,并立即执行检查 - * @param must 需要验证的布尔条件 - * @param message 检查失败时的异常消息 - * @param exception 检查失败时抛出的异常构造器函数 - */ - constructor( - must: Boolean, message: String, exception: P1RFunction - ) : this(must) { - throws(message, exception) - } - - companion object { - - /** - * 工厂方法:创建Require对象并指定异常消息和异常类型 - * @param must 需要验证的布尔条件 - * @param message 检查失败时的异常消息 - * @param exception 检查失败时抛出的异常类型,默认为IllegalArgumentException - * @return Require对象 - */ - @JvmStatic - fun require( - must: Boolean, message: String, exception: Class = IllegalArgumentException::class.java - ): Require { - return Require(must, message, exception) - } - - /** - * 工厂方法:创建Require对象并指定异常消息 - * @param must 需要验证的布尔条件 - * @param message 检查失败时的异常消息 - * @return Require对象 - */ - @JvmStatic - fun require(must: Boolean, message: String): Require { - return Require(must, message) - } - - /** - * 工厂方法:创建Require对象 - * @param must 需要验证的布尔条件 - * @return Require对象 - */ - @JvmStatic - fun require(must: Boolean): Require { - return Require(must) - } - - /** - * 工厂方法:创建Require对象并指定异常消息和异常类型 - * @param must 返回布尔值的函数 - * @param message 检查失败时的异常消息 - * @param exception 检查失败时抛出的异常类型,默认为IllegalArgumentException - * @return Require对象 - */ - @JvmStatic - fun require( - must: RFunction, - message: String, - exception: Class = IllegalArgumentException::class.java - ): Require { - return Require(must, message, exception) - } - - /** - * 工厂方法:创建Require对象并指定异常消息 - * @param must 返回布尔值的函数 - * @param message 检查失败时的异常消息 - * @return Require对象 - */ - @JvmStatic - fun require(must: RFunction, message: String): Require { - return Require(must, message) - } - - /** - * 工厂方法:创建Require对象 - * @param must 返回布尔值的函数 - * @return Require对象 - */ - @JvmStatic - fun require(must: RFunction): Require { - return Require(must) - } - } - - /** - * 执行条件检查,如果条件为false则抛出IllegalArgumentException - * @param message 检查失败时的异常消息 - */ - fun throws(message: String) { - if (!must) { - throw IllegalArgumentException(message) - } - } - - /** - * 执行条件检查,如果条件为false则抛出指定类型的异常 - * @param string 检查失败时的异常消息 - * @param exception 检查失败时抛出的异常类型 - */ - fun throws(string: String, exception: Class) { - if (!must) { - throw exception.getConstructor(String::class.java).newInstance(string) - } - } - - /** - * 执行条件检查,如果条件为false则抛出由函数构造的异常 - * @param string 检查失败时的异常消息 - * @param exception 检查失败时抛出的异常构造器函数 - */ - fun throws(string: String, exception: P1RFunction) { - if (!must) { - throw exception.call(string) - } - } -} diff --git a/src/main/kotlin/com/mingliqiye/utils/require/Require.kt b/src/main/kotlin/com/mingliqiye/utils/require/Require.kt new file mode 100644 index 0000000..21c23e5 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/require/Require.kt @@ -0,0 +1,200 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile Require.kt + * LastUpdate 2026-02-05 11:08:03 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.require + +import com.mingliqiye.utils.logger.MingLiLoggerFactory +import org.slf4j.Logger +import java.lang.reflect.Constructor +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +/** + * 工具对象,用于提供条件检查功能。 + * 支持抛出自定义异常、延迟消息构造以及日志记录等功能。 + */ +object Require { + + /** + * 日志记录器实例,用于记录错误信息。 + */ + @JvmStatic + var logger: Logger? = MingLiLoggerFactory.getLogger() + + /** + * 控制是否在抛出异常时记录错误日志。 + */ + @JvmStatic + var isLogError = true + + /** + * 检查给定条件是否为真。如果为假,则抛出 [IllegalArgumentException] 异常。 + * + * @param value 需要检查的布尔值。 + */ + @OptIn(ExperimentalContracts::class) + @JvmStatic + fun require(value: Boolean) { + contract { + returns() implies value + } + require(value, "the require conditions are not met.") + } + + /** + * 检查给定条件是否为真。如果为假,则根据指定的异常类型抛出异常。 + * + * @param value 需要检查的布尔值。 + * @param message 异常消息。 + * @param T 异常类型。 + */ + @OptIn(ExperimentalContracts::class) + @JvmName("__inline_Require") + inline fun require(value: Boolean, message: String) { + contract { + returns() implies value + } + require(value, message, T::class.java) + } + + /** + * 检查给定条件是否为真。如果为假,则抛出 [IllegalArgumentException] 异常。 + * + * @param value 需要检查的布尔值。 + * @param message 异常消息。 + */ + @OptIn(ExperimentalContracts::class) + fun require(value: Boolean, message: String) { + contract { + returns() implies value + } + if (!value) throwThrowable(getExceptionConstructor().newInstance(message)) + } + + /** + * 检查给定条件是否为真。如果为假,则使用延迟消息构造器生成异常并抛出。 + * + * @param value 需要检查的布尔值。 + * @param throwable 异常类型。 + * @param layzMessage 延迟消息构造器。 + */ + @OptIn(ExperimentalContracts::class) + @JvmStatic + fun require(value: Boolean, throwable: Class, layzMessage: RequireLayzMessageConstructor) { + contract { + returns() implies value + } + require(value, layzMessage.call(), throwable) + } + + /** + * 检查给定条件是否为真。如果为假,则使用延迟异常构造器生成异常并抛出。 + * + * @param value 需要检查的布尔值。 + * @param layzThrowable 延迟异常构造器。 + */ + @OptIn(ExperimentalContracts::class) + @JvmStatic + fun require(value: Boolean, layzThrowable: RequireLayzExceptionConstructor) { + contract { + returns() implies value + } + if (!value) throwThrowable(layzThrowable.call()) + } + + /** + * 检查给定条件是否为真。如果为假,则使用延迟消息构造器生成 [IllegalArgumentException] 并抛出。 + * + * @param value 需要检查的布尔值。 + * @param layzMessage 延迟消息构造器。 + */ + @OptIn(ExperimentalContracts::class) + @JvmStatic + fun requireLayzMessage(value: Boolean, layzMessage: RequireLayzMessageConstructor) { + contract { + returns() implies value + } + require(value, layzMessage) + } + + /** + * 检查给定条件是否为真。如果为假,则根据指定的异常类型和延迟消息构造器生成异常并抛出。 + * + * @param value 需要检查的布尔值。 + * @param layzMessage 延迟消息构造器。 + * @param T 异常类型。 + */ + @OptIn(ExperimentalContracts::class) + @JvmName("__inline_Require") + inline fun require(value: Boolean, layzMessage: RequireLayzMessageConstructor) { + contract { + returns() implies value + } + if (!value) throwThrowable(getExceptionConstructor().newInstance(layzMessage.call())) + } + + /** + * 检查给定条件是否为真。如果为假,则根据指定的异常类型和消息生成异常并抛出。 + * + * @param value 需要检查的布尔值。 + * @param message 异常消息。 + * @param throwable 异常类型。 + */ + @OptIn(ExperimentalContracts::class) + @JvmStatic + fun require(value: Boolean, message: String, throwable: Class) { + contract { + returns() implies value + } + if (!value) throwThrowable(getExceptionConstructor(throwable).newInstance(message)) + } + + /** + * 抛出指定的异常,并在启用日志记录时记录错误信息。 + * + * @param throwable 需要抛出的异常。 + */ + fun throwThrowable(throwable: Throwable) { + if (isLogError && logger != null) logger!!.error(throwable.message, throwable) + throw throwable + } + + /** + * 获取指定异常类型的构造函数(带字符串参数)。 + * + * @param T 异常类型。 + * @return 异常类型的构造函数。 + */ + @JvmName("__inline_GetExceptionConstructor") + inline fun getExceptionConstructor(): Constructor = + getExceptionConstructor(T::class.java) + + /** + * 获取指定异常类型的构造函数(带字符串参数)。 + * + * @param throwable 异常类型。 + * @return 异常类型的构造函数。 + */ + @JvmName("__GetExceptionConstructor") + fun getExceptionConstructor(throwable: Class): Constructor = + throwable.getConstructor(String::class.java) +} diff --git a/src/main/kotlin/com/mingliqiye/utils/main/Main.kt b/src/main/kotlin/com/mingliqiye/utils/require/RequireLayzExceptionConstructor.kt similarity index 74% rename from src/main/kotlin/com/mingliqiye/utils/main/Main.kt rename to src/main/kotlin/com/mingliqiye/utils/require/RequireLayzExceptionConstructor.kt index 6ff2225..977c7c6 100644 --- a/src/main/kotlin/com/mingliqiye/utils/main/Main.kt +++ b/src/main/kotlin/com/mingliqiye/utils/require/RequireLayzExceptionConstructor.kt @@ -15,16 +15,14 @@ * * ProjectName mingli-utils * ModuleName mingli-utils.main - * CurrentFile Main.kt - * LastUpdate 2026-01-06 14:36:10 + * CurrentFile RequireLayzExceptionConstructor.kt + * LastUpdate 2026-02-05 09:36:19 * UpdateUser MingLiPro */ -@file:JvmName("Main") -package com.mingliqiye.utils.main +package com.mingliqiye.utils.require -import com.mingliqiye.utils.springboot.autoconfigure.AutoConfiguration - -fun main() { - AutoConfiguration.printBanner() +@FunctionalInterface +fun interface RequireLayzExceptionConstructor { + fun call(): Throwable } diff --git a/src/main/kotlin/com/mingliqiye/utils/require/RequireLayzMessageConstructor.kt b/src/main/kotlin/com/mingliqiye/utils/require/RequireLayzMessageConstructor.kt new file mode 100644 index 0000000..b19741a --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/require/RequireLayzMessageConstructor.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile RequireLayzMessageConstructor.kt + * LastUpdate 2026-02-05 09:36:09 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.require + +@FunctionalInterface +fun interface RequireLayzMessageConstructor { + fun call(): String +} diff --git a/src/main/kotlin/com/mingliqiye/utils/resource/ResourceUtils.kt b/src/main/kotlin/com/mingliqiye/utils/resource/ResourceUtils.kt index 4aacae0..a2e03c1 100644 --- a/src/main/kotlin/com/mingliqiye/utils/resource/ResourceUtils.kt +++ b/src/main/kotlin/com/mingliqiye/utils/resource/ResourceUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,72 +16,120 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile ResourceUtils.kt - * LastUpdate 2025-09-20 10:26:47 + * LastUpdate 2026-02-05 11:09:16 * UpdateUser MingLiPro */ package com.mingliqiye.utils.resource import java.io.IOException -class ResourceUtils { - companion object { - @JvmStatic - @Throws(IOException::class) - fun getResource(resourceName: String): ByteArray { - return getResource(resourceName, ResourceUtils::class.java) - } +/** + * 工具类,用于从类路径中加载资源文件。 + */ +object ResourceUtils { - @JvmStatic - @Throws(IOException::class) - fun getResource(resourceName: String, clazz: Class<*>): ByteArray { - return clazz.getResourceAsStream(resourceName)?.use { - it.readBytes() - } ?: throw IOException("Resource not found: $resourceName") - } + /** + * 从默认类路径中加载指定名称的资源文件,并以字节数组形式返回。 + * + * @param resourceName 资源文件的名称(相对于类路径根目录)。 + * @return 资源文件的内容作为字节数组。 + * @throws IOException 如果资源未找到或读取失败时抛出。 + */ + @JvmStatic + @Throws(IOException::class) + fun getResource(resourceName: String): ByteArray { + return getResource(resourceName, ResourceUtils::class.java) + } + /** + * 从指定类的类路径中加载指定名称的资源文件,并以字节数组形式返回。 + * + * @param resourceName 资源文件的名称(相对于类路径根目录)。 + * @param clazz 用于定位资源的类。 + * @return 资源文件的内容作为字节数组。 + * @throws IOException 如果资源未找到或读取失败时抛出。 + */ + @JvmStatic + @Throws(IOException::class) + fun getResource(resourceName: String, clazz: Class<*>): ByteArray { + return clazz.getResourceAsStream(resourceName)?.use { + it.readBytes() + } ?: throw IOException("Resource not found: $resourceName") + } - @JvmStatic - @Throws(IOException::class) - fun getStringResource(resourceName: String): String { - return getStringResource(resourceName, ResourceUtils::class.java) - } + /** + * 从默认类路径中加载指定名称的资源文件,并以字符串形式返回。 + * + * @param resourceName 资源文件的名称(相对于类路径根目录)。 + * @return 资源文件的内容作为字符串(使用UTF-8编码)。 + * @throws IOException 如果资源未找到或读取失败时抛出。 + */ + @JvmStatic + @Throws(IOException::class) + fun getStringResource(resourceName: String): String { + return getStringResource(resourceName, ResourceUtils::class.java) + } + /** + * 从指定类的类路径中加载指定名称的资源文件,并以字符串形式返回。 + * + * @param resourceName 资源文件的名称(相对于类路径根目录)。 + * @param clazz 用于定位资源的类。 + * @return 资源文件的内容作为字符串(使用UTF-8编码)。 + * @throws IOException 如果资源未找到或读取失败时抛出。 + */ + @JvmStatic + @Throws(IOException::class) + fun getStringResource(resourceName: String, clazz: Class<*>): String { + return clazz.getResourceAsStream(resourceName)?.use { + it.readBytes().toString(charset = Charsets.UTF_8) + } ?: throw IOException("Resource not found: $resourceName") + } - @JvmStatic - @Throws(IOException::class) - fun getStringResource(resourceName: String, clazz: Class<*>): String { - return clazz.getResourceAsStream(resourceName)?.use { - it.readBytes().toString(charset = Charsets.UTF_8) - } ?: throw IOException("Resource not found: $resourceName") - } + /** + * 从调用者的类路径中加载指定名称的资源文件,并以字符串形式返回。 + * + * @param resourceName 资源文件的名称(相对于类路径根目录)。 + * @return 资源文件的内容作为字符串(使用UTF-8编码)。 + * @throws IOException 如果资源未找到或读取失败时抛出。 + */ + @JvmStatic + @Throws(IOException::class) + fun getStringResourceCallers(resourceName: String): String { + return getStringResource(resourceName, getCallerClass()) + } + /** + * 从调用者的类路径中加载指定名称的资源文件,并以字节数组形式返回。 + * + * @param resourceName 资源文件的名称(相对于类路径根目录)。 + * @return 资源文件的内容作为字节数组。 + * @throws IOException 如果资源未找到或读取失败时抛出。 + */ + @JvmStatic + @Throws(IOException::class) + fun getResourceCallers(resourceName: String): ByteArray { + return getResource(resourceName, getCallerClass()) + } - @JvmStatic - @Throws(IOException::class) - fun getStringResourceCallers(resourceName: String): String { - return getStringResource(resourceName, getCallerClass()) - } - - @JvmStatic - @Throws(IOException::class) - fun getResourceCallers(resourceName: String): ByteArray { - return getResource(resourceName, getCallerClass()) - } - - private fun getCallerClass(): Class<*> { - val stackTrace = Thread.currentThread().stackTrace - for (i in 2 until stackTrace.size) { - val className = stackTrace[i].className - try { - val clazz = Class.forName(className) - if (clazz != ResourceUtils::class.java && clazz != Companion::class.java) { - return clazz - } - } catch (e: ClassNotFoundException) { - continue + /** + * 获取当前调用栈中第一个非ResourceUtils类的调用者类。 + * + * @return 调用者类对象;如果未找到则返回ResourceUtils类本身。 + */ + private fun getCallerClass(): Class<*> { + val stackTrace = Thread.currentThread().stackTrace + for (i in 2 until stackTrace.size) { + val className = stackTrace[i].className + try { + val clazz = Class.forName(className) + if (clazz != ResourceUtils::class.java) { + return clazz } + } catch (e: ClassNotFoundException) { + continue } - return ResourceUtils::class.java } + return ResourceUtils::class.java } } diff --git a/src/main/kotlin/com/mingliqiye/utils/security/SecureUtils.kt b/src/main/kotlin/com/mingliqiye/utils/security/SecureUtils.kt index bc96628..e6bc3d0 100644 --- a/src/main/kotlin/com/mingliqiye/utils/security/SecureUtils.kt +++ b/src/main/kotlin/com/mingliqiye/utils/security/SecureUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile SecureUtils.kt - * LastUpdate 2025-09-15 22:32:50 + * LastUpdate 2026-02-05 11:12:36 * UpdateUser MingLiPro */ @@ -29,27 +29,64 @@ import java.security.SecureRandom import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec +/** + * 全局安全随机数生成器实例,用于生成加密安全的随机字节。 + */ internal val SECURE_RANDOM = SecureRandom() +/** + * 生成指定长度的加密安全随机字节数组。 + * + * @param length 需要生成的字节数组的长度。 + * @return 包含随机字节的 ByteArray。 + */ fun getRandomBytes(length: Int): ByteArray { val bytes = ByteArray(length) SECURE_RANDOM.nextBytes(bytes) return bytes } +/** + * 使用指定算法对输入数据进行哈希处理,生成密钥字节数组。 + * + * @param algorithm 哈希算法名称(如 SHA-256)。 + * @param data 输入的字节数组数据。 + * @return 经过哈希处理后的密钥字节数组。 + */ fun createSecretKey(algorithm: String, data: ByteArray): ByteArray { val md = MessageDigest.getInstance(algorithm) return md.digest(data) } +/** + * 使用指定算法对输入字符串进行哈希处理,生成密钥字节数组。 + * + * @param algorithm 哈希算法名称(如 SHA-256)。 + * @param data 输入的字符串数据。 + * @return 经过哈希处理后的密钥字节数组。 + */ fun createSecretKey(algorithm: String, data: String): ByteArray { return createSecretKey(algorithm, data.toByteArray()) } +/** + * 使用指定算法和输入字符串创建 SecretKeySpec 对象。 + * + * @param algorithm 哈希算法名称(如 AES)。 + * @param data 输入的字符串数据。 + * @return 根据输入数据和算法生成的 SecretKey 对象。 + */ fun createSecretKeySpec(algorithm: String, data: String): SecretKey { return SecretKeySpec(createSecretKey(algorithm, data), algorithm) } +/** + * 使用指定算法和输入字节数组创建 SecretKeySpec 对象。 + * + * @param algorithm 哈希算法名称(如 AES)。 + * @param data 输入的字节数组数据。 + * @return 根据输入数据和算法生成的 SecretKey 对象。 + */ fun createSecretKeySpec(algorithm: String, data: ByteArray): SecretKey { return SecretKeySpec(createSecretKey(algorithm, data), algorithm) } diff --git a/src/main/kotlin/com/mingliqiye/utils/sleep/SleepUtils.kt b/src/main/kotlin/com/mingliqiye/utils/sleep/SleepUtils.kt new file mode 100644 index 0000000..c1dd433 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/sleep/SleepUtils.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile SleepUtils.kt + * LastUpdate 2026-01-15 16:50:50 + * UpdateUser MingLiPro + */ +package com.mingliqiye.utils.sleep + +import java.util.concurrent.TimeUnit + +/** + * 等待工具类,提供多种等待和延迟执行的方式 + */ +object SleepUtils { + + /** + * 基于 Thread.sleep 的等待,单位毫秒 + * + * @param millis 等待时间(毫秒) + * @throws RuntimeException 当线程被中断时抛出 + */ + @JvmStatic + fun sleep(millis: Long) { + try { + Thread.sleep(millis) + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + throw RuntimeException("Sleep interrupted", e) + } + } + + /** + * 基于 Thread.sleep 的等待,单位毫秒 + * + * @param millis 等待时间(毫秒) + * @throws RuntimeException 当线程被中断时抛出 + */ + @JvmStatic + fun sleep(millis: Int) { + try { + Thread.sleep(millis.toLong()) + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + throw RuntimeException("Sleep interrupted", e) + } + } + + /** + * 基于 TimeUnit 的等待 + * + * @param timeout 等待时间 + * @param unit 时间单位 + * @throws RuntimeException 当线程被中断时抛出 + */ + @JvmStatic + fun sleep(timeout: Long, unit: TimeUnit) { + try { + unit.sleep(timeout) + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + throw RuntimeException("Sleep interrupted", e) + } + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/AutoConfiguration.kt b/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/AutoConfiguration.kt index cdc1520..8d96c71 100644 --- a/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/AutoConfiguration.kt +++ b/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/AutoConfiguration.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,13 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile AutoConfiguration.kt - * LastUpdate 2025-09-20 10:47:00 + * LastUpdate 2026-02-05 10:45:05 * UpdateUser MingLiPro */ package com.mingliqiye.utils.springboot.autoconfigure -import com.mingliqiye.utils.logger.mingLiLoggerFactory +import com.mingliqiye.utils.logger.MingLiLoggerFactory import com.mingliqiye.utils.system.computerName import com.mingliqiye.utils.system.getPid import com.mingliqiye.utils.system.jdkVersion @@ -32,14 +32,24 @@ import com.mingliqiye.utils.time.Formatter import org.springframework.context.annotation.ComponentScan import java.io.IOException +/** + * 自动配置类,用于Spring Boot应用启动时加载相关组件和打印启动横幅。 + * + * 该类通过@ComponentScan注解扫描指定包下的Bean,并在初始化时打印包含系统信息的启动横幅。 + */ @org.springframework.boot.autoconfigure.AutoConfiguration @ComponentScan( "com.mingliqiye.utils.springboot.bean", "com.mingliqiye.utils.springboot.converters" ) open class AutoConfiguration { + private val log = MingLiLoggerFactory.getLogger("MingliUtils-AutoConfiguration") + companion object { - private const val banner = + /** + * 启动横幅字符串,包含艺术字体和占位符。 + */ + private const val BANNER = "---------------------------------------------------------\n" + "| $$\\ $$\\ $$\\ $$\\ $$\\ $$$$$$$$\\ $$$$$$\\ |\n" + "| $$$\\ $$$ |$$ | $$ | $$ |\\__$$ __|$$ __$$\\ |\n" + @@ -49,25 +59,38 @@ open class AutoConfiguration { "| $$ |\\$ /$$ |$$ | $$ | $$ | $$ | $$\\ $$ | |\n" + "| $$ | \\_/ $$ |$$$$$$$$\\\\$$$$$$ | $$ | \\$$$$$$ | |\n" + "| \\__| \\__|\\________|\\______/ \\__| \\______/ |\n" - private val log = mingLiLoggerFactory.getLogger("MingliUtils-AutoConfiguration") + /** + * 打印启动横幅,包含系统元数据(如JDK版本、进程ID、计算机名等)。 + * + * 该方法从资源文件中读取元数据,并将其格式化后追加到横幅中进行输出。 + * 如果读取资源失败,则仅打印默认横幅。 + */ fun printBanner() { - val bannerBuilder = StringBuilder(banner) + val bannerBuilder = StringBuilder(BANNER) + + // 尝试从资源文件中读取元数据并拼接到横幅中 try { val inputStream = AutoConfiguration::class.java.getResourceAsStream("/META-INF/meta-data") ?: return inputStream.use { stream -> var readlen: Int val buffer = ByteArray(1024) val metaData = StringBuilder() + + // 逐块读取资源文件内容 while (stream.read(buffer).also { readlen = it } != -1) { metaData.append(String(buffer, 0, readlen)) } + + // 解析元数据并添加额外的系统信息 val da = metaData.toString().split("\n").toMutableList() da.add("jdkRuntime=$jdkVersion") da.add("pid=$getPid") da.add("computerName=$computerName") da.add("userName=$userName") - da.add("time=" + DateTime.now().format(Formatter.STANDARD_DATETIME_MILLISECOUND7, true)) + da.add("time=" + DateTime.now().format(Formatter.STANDARD_DATETIME_MILLISECOUND7, false)) + + // 格式化每条元数据并追加到横幅中 da.forEach { s: String -> val d = s.trim { it <= ' ' }.split("=".toRegex(), 2).toTypedArray() if (d.size >= 2) { @@ -89,15 +112,23 @@ open class AutoConfiguration { } } } catch (e: IOException) { + // 捕获IO异常并打印堆栈跟踪 e.printStackTrace() } + + // 输出最终构建的横幅 println(bannerBuilder.toString().trim()) println("---------------------------------------------------------") } } + /** + * 初始化块,在类实例化时执行。 + * + * 调用printBanner方法打印启动横幅,并记录日志表示自动配置成功。 + */ init { printBanner() - log.info("MingliUtils AutoConfiguration succeed") + log.info("MingliUtils AutoConfigurationBean succeed") } } diff --git a/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/GsonAutoConfiguration.kt b/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/GsonAutoConfiguration.kt deleted file mode 100644 index 0dc62c0..0000000 --- a/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/GsonAutoConfiguration.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2025 mingliqiye - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ProjectName mingli-utils - * ModuleName mingli-utils.main - * CurrentFile GsonAutoConfiguration.kt - * LastUpdate 2025-09-15 10:29:30 - * UpdateUser MingLiPro - */ - -package com.mingliqiye.utils.springboot.autoconfigure - -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import com.mingliqiye.utils.json.GsonJsonApi -import com.mingliqiye.utils.json.JsonApi -import com.mingliqiye.utils.json.converters.DateTimeJsonConverter -import com.mingliqiye.utils.json.converters.JsonStringConverter -import com.mingliqiye.utils.json.converters.UUIDJsonStringConverter -import com.mingliqiye.utils.logger.mingLiLoggerFactory -import com.mingliqiye.utils.time.DateTime -import com.mingliqiye.utils.uuid.UUID -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.boot.autoconfigure.AutoConfiguration -import org.springframework.boot.autoconfigure.AutoConfigureAfter -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean -import org.springframework.boot.autoconfigure.gson.GsonBuilderCustomizer -import org.springframework.context.annotation.Bean - -@ConditionalOnClass(Gson::class) -@AutoConfiguration -@AutoConfigureAfter( - name = ["org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration", - "com.mingliqiye.utils.springboot.autoconfigure.JacksonAutoConfiguration"] -) -open class GsonAutoConfiguration { - companion object { - private val log: Logger = LoggerFactory.getLogger("MingliUtils-GsonAutoConfiguration") - - fun addTypeAdapter(gsonBuilder: GsonBuilder): GsonBuilder { - val dateTimeJsonConverter: JsonStringConverter = DateTimeJsonConverter() - val uuidJsonStringConverter: JsonStringConverter = UUIDJsonStringConverter() - - try { - return gsonBuilder - .registerTypeAdapter( - uuidJsonStringConverter.tClass, - uuidJsonStringConverter - .gsonJsonStringConverterAdapter - .gsonTypeAdapter - ) - .registerTypeAdapter( - dateTimeJsonConverter.tClass, - dateTimeJsonConverter - .gsonJsonStringConverterAdapter - .gsonTypeAdapter - ) - } finally { - log.info("MingliUtils GsonBuilder TypeAdapter add") - } - } - } - - private val log: Logger = mingLiLoggerFactory.getLogger("MingliUtils-GsonAutoConfiguration") - - @Bean - open fun mingliGsonCustomizer(): GsonBuilderCustomizer { - return GsonBuilderCustomizer { gsonBuilder: GsonBuilder -> addTypeAdapter(gsonBuilder) } - } - - @Bean - @ConditionalOnMissingBean - open fun jsonApi(gson: Gson): JsonApi { - log.info("MingliUtils-JsonApiAutoConfiguration: GsonJsonApi bean is created.") - return GsonJsonApi(gson) - } -} diff --git a/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/JacksonAutoConfiguration.kt b/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/JacksonAutoConfiguration.kt index 7482e65..a16a6a1 100644 --- a/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/JacksonAutoConfiguration.kt +++ b/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/JacksonAutoConfiguration.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,61 +16,91 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile JacksonAutoConfiguration.kt - * LastUpdate 2025-09-15 10:29:02 + * LastUpdate 2026-02-05 10:45:19 * UpdateUser MingLiPro */ package com.mingliqiye.utils.springboot.autoconfigure import com.fasterxml.jackson.databind.ObjectMapper -import com.mingliqiye.utils.json.JacksonJsonApi -import com.mingliqiye.utils.json.JsonApi +import com.mingliqiye.utils.json.api.JSONA +import com.mingliqiye.utils.json.api.JacksonJsonApi +import com.mingliqiye.utils.json.api.base.JsonApi import com.mingliqiye.utils.json.converters.DateTimeJsonConverter -import com.mingliqiye.utils.json.converters.UUIDJsonStringConverter -import com.mingliqiye.utils.logger.mingLiLoggerFactory +import com.mingliqiye.utils.json.converters.UUIDJsonConverter +import com.mingliqiye.utils.json.converters.base.registerModule +import com.mingliqiye.utils.logger.MingLiLoggerFactory import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.boot.autoconfigure.AutoConfiguration import org.springframework.boot.autoconfigure.AutoConfigureAfter import org.springframework.boot.autoconfigure.condition.ConditionalOnClass import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Primary -@ConditionalOnClass(ObjectMapper::class) -@AutoConfiguration -@AutoConfigureAfter(org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration::class) +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile JacksonAutoConfiguration.kt + * LastUpdate 2026-02-05 10:39:54 + * UpdateUser MingLiPro + */ + +/* + * JacksonAutoConfiguration 是一个 Spring Boot 自动配置类,用于配置 Jackson 相关的序列化和反序列化功能。 + * 该类在检测到 ObjectMapper 和 Spring Boot 的 JacksonAutoConfiguration 存在时自动生效, + * 并注册自定义的 JSON 转换器模块(如 UUID 和 DateTime 转换器)。 + * + * @param objectMapper 用于 JSON 序列化和反序列化的 ObjectMapper 实例。 + */ +@ConditionalOnClass(name = ["com.fasterxml.jackson.databind.ObjectMapper", "org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration"]) +@AutoConfigureAfter(name = ["org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration"]) open class JacksonAutoConfiguration(objectMapper: ObjectMapper) { - companion object { - private val log: Logger = mingLiLoggerFactory.getLogger("MingliUtils-JacksonAutoConfiguration") - - fun addModules(objectMapper: ObjectMapper): ObjectMapper { - return objectMapper - .registerModule( - DateTimeJsonConverter() - .jacksonJsonStringConverterAdapter - .jacksonModule - ) - .registerModule( - UUIDJsonStringConverter() - .jacksonJsonStringConverterAdapter - .jacksonModule - ) - } - } - - private val log: Logger = LoggerFactory.getLogger("MingliUtils-JacksonAutoConfiguration") + private val log: Logger = MingLiLoggerFactory.getLogger("MingliUtils-JacksonAutoConfiguration") + /* + * 初始化块:在类实例化时执行。 + * 注册自定义的 UUID 和 DateTime JSON 转换器模块到 ObjectMapper 中。 + */ init { - addModules(objectMapper) log.info("MingliUtils Jackson Serializers created") + objectMapper + .registerModule() + .registerModule() } + /* + * 创建并返回一个 JsonApi Bean 实例。 + * 该方法会在没有其他 JsonApi Bean 存在时被调用,并将创建的 JacksonJsonApi 实例设置为全局默认的 JSON API。 + * + * @param objectMapper 用于 JSON 操作的 ObjectMapper 实例。 + * @return 返回配置好的 JsonApi 实例。 + */ @Bean @Primary @ConditionalOnMissingBean open fun jsonApi(objectMapper: ObjectMapper): JsonApi { log.info("MingliUtils-JsonApiAutoConfiguration: JacksonJsonApi bean is created.") - return JacksonJsonApi(objectMapper) + return JacksonJsonApi(objectMapper).also { + try { + JSONA.getJsonApi() + } catch (_: NullPointerException) { + JSONA.setJsonApi(it) + log.info("JSONA Use {}", it.javaClass.name) + } + } } } diff --git a/src/main/kotlin/com/mingliqiye/utils/springboot/bean/SpringBeanUtils.kt b/src/main/kotlin/com/mingliqiye/utils/springboot/bean/SpringBeanUtils.kt index 44ae843..741d4f3 100644 --- a/src/main/kotlin/com/mingliqiye/utils/springboot/bean/SpringBeanUtils.kt +++ b/src/main/kotlin/com/mingliqiye/utils/springboot/bean/SpringBeanUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile SpringBeanUtils.kt - * LastUpdate 2025-09-19 20:07:08 + * LastUpdate 2026-02-04 16:50:39 * UpdateUser MingLiPro */ @@ -39,6 +39,9 @@ class SpringBeanUtils : ApplicationContextAware { @JvmStatic private var applicationContext: ApplicationContext? = null + @Throws(BeansException::class) + inline fun getBean() = getBean(T::class.java) + /** * 根据Bean名称获取Bean实例 * @@ -48,11 +51,9 @@ class SpringBeanUtils : ApplicationContextAware { * @throws ClassCastException 当类型转换失败时抛出 */ @JvmStatic - @Throws(BeansException::class, ClassCastException::class) + @Throws(BeansException::class) @Suppress("UNCHECKED_CAST") - fun getBean(name: String): T { - return applicationContext!!.getBean(name) as T - } + fun getBean(name: String): Any = applicationContext!!.getBean(name) /** * 根据Bean类型获取Bean实例 diff --git a/src/main/kotlin/com/mingliqiye/utils/springboot/converters/DateTimeToStringConverter.kt b/src/main/kotlin/com/mingliqiye/utils/springboot/converters/DateTimeToStringConverter.kt new file mode 100644 index 0000000..506fe78 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/springboot/converters/DateTimeToStringConverter.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile DateTimeToStringConverter.kt + * LastUpdate 2026-02-04 21:57:43 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.springboot.converters + +import com.mingliqiye.utils.time.DateTime +import com.mingliqiye.utils.time.Formatter +import org.springframework.core.convert.converter.Converter +import org.springframework.stereotype.Component + +@Component +class DateTimeToStringConverter : Converter { + override fun convert(source: DateTime): String { + return source.format(Formatter.STANDARD_DATETIME) + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/springboot/converters/Converters.kt b/src/main/kotlin/com/mingliqiye/utils/springboot/converters/StringToDateTimeConverter.kt similarity index 62% rename from src/main/kotlin/com/mingliqiye/utils/springboot/converters/Converters.kt rename to src/main/kotlin/com/mingliqiye/utils/springboot/converters/StringToDateTimeConverter.kt index 3094705..02e1da5 100644 --- a/src/main/kotlin/com/mingliqiye/utils/springboot/converters/Converters.kt +++ b/src/main/kotlin/com/mingliqiye/utils/springboot/converters/StringToDateTimeConverter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ * * ProjectName mingli-utils * ModuleName mingli-utils.main - * CurrentFile Converters.kt - * LastUpdate 2025-09-15 09:19:48 + * CurrentFile StringToDateTimeConverter.kt + * LastUpdate 2026-02-04 21:57:43 * UpdateUser MingLiPro */ @@ -24,35 +24,12 @@ package com.mingliqiye.utils.springboot.converters import com.mingliqiye.utils.time.DateTime import com.mingliqiye.utils.time.Formatter -import com.mingliqiye.utils.uuid.UUID -import com.mingliqiye.utils.uuid.UUID.Companion.of import org.springframework.core.convert.converter.Converter import org.springframework.stereotype.Component -@Component -class DateTimeToStringConverter : Converter { - override fun convert(source: DateTime): String { - return source.format(Formatter.STANDARD_DATETIME) - } -} - -@Component -class UUIDToStringConverter : Converter { - override fun convert(source: UUID): String { - return source.getString() - } -} - @Component class StringToDateTimeConverter : Converter { override fun convert(source: String): DateTime { return DateTime.parse(source, Formatter.STANDARD_DATETIME_MILLISECOUND7, true) } } - -@Component -class StringToUUIDConverter : Converter { - override fun convert(source: String): UUID { - return of(source) - } -} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/JsonConverter.kt b/src/main/kotlin/com/mingliqiye/utils/springboot/converters/StringToUUIDConverter.kt similarity index 59% rename from src/main/kotlin/com/mingliqiye/utils/json/converters/JsonConverter.kt rename to src/main/kotlin/com/mingliqiye/utils/springboot/converters/StringToUUIDConverter.kt index 32f00f0..6e55306 100644 --- a/src/main/kotlin/com/mingliqiye/utils/json/converters/JsonConverter.kt +++ b/src/main/kotlin/com/mingliqiye/utils/springboot/converters/StringToUUIDConverter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,22 +15,20 @@ * * ProjectName mingli-utils * ModuleName mingli-utils.main - * CurrentFile JsonConverter.kt - * LastUpdate 2025-09-15 11:12:07 + * CurrentFile StringToUUIDConverter.kt + * LastUpdate 2026-02-04 21:58:01 * UpdateUser MingLiPro */ -package com.mingliqiye.utils.json.converters +package com.mingliqiye.utils.springboot.converters -interface JsonConverter { - fun convert(obj: F?): T? - fun deConvert(obj: T?): F? - val tClass: Class +import com.mingliqiye.utils.uuid.UUID +import org.springframework.core.convert.converter.Converter +import org.springframework.stereotype.Component - fun getStringConverter(): JsonStringConverter? { - if (this is JsonStringConverter<*>) { - return this as JsonStringConverter - } - return null +@Component +class StringToUUIDConverter : Converter { + override fun convert(source: String): UUID { + return UUID.of(source) } } diff --git a/src/main/kotlin/com/mingliqiye/utils/springboot/converters/UUIDToStringConverter.kt b/src/main/kotlin/com/mingliqiye/utils/springboot/converters/UUIDToStringConverter.kt new file mode 100644 index 0000000..0e4fc25 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/springboot/converters/UUIDToStringConverter.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile UUIDToStringConverter.kt + * LastUpdate 2026-02-04 21:57:43 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.springboot.converters + +import com.mingliqiye.utils.uuid.UUID +import org.springframework.core.convert.converter.Converter +import org.springframework.stereotype.Component + +@Component +class UUIDToStringConverter : Converter { + override fun convert(source: UUID): String { + return source.getString() + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/string/StringUtils.kt b/src/main/kotlin/com/mingliqiye/utils/string/StringUtils.kt index 18c387b..b86f178 100644 --- a/src/main/kotlin/com/mingliqiye/utils/string/StringUtils.kt +++ b/src/main/kotlin/com/mingliqiye/utils/string/StringUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,17 +16,26 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile StringUtils.kt - * LastUpdate 2025-09-18 09:26:41 + * LastUpdate 2026-02-05 11:05:33 * UpdateUser MingLiPro */ @file:JvmName("StringUtils") package com.mingliqiye.utils.string -import com.mingliqiye.utils.logger.mingLiLoggerFactory +import com.mingliqiye.utils.base.BASE16 +import com.mingliqiye.utils.logger.MingLiLoggerFactory +import com.mingliqiye.utils.objects.isNull +import java.net.URLDecoder +import java.net.URLEncoder +import java.security.MessageDigest +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract -val log = mingLiLoggerFactory.getLogger("StringUtils") +private val log = MingLiLoggerFactory.getLogger(Class.forName("com.mingliqiye.utils.string.StringUtils")) val NULLISH_STRINGS = setOf("null", "NaN", "undefined", "None", "none") @@ -36,11 +45,18 @@ val NULLISH_STRINGS = setOf("null", "NaN", "undefined", "None", "none") * @param str 待判断的字符串 * @return `true`: 空 `false`: 非空 */ +@OptIn(ExperimentalContracts::class) @JvmName("isEmpty") fun String?.isNullish(): Boolean { - return this == null || this.isBlank() || this in NULLISH_STRINGS + contract { + returns(false) implies (this@isNullish != null) + } + return this.isNullOrBlank() || this in NULLISH_STRINGS } +@JvmName("__Formatde") +fun String.formatd(vararg args: Any) = com.mingliqiye.utils.string.format(this, *args) + /** * 格式化字符串,将字符串中的占位符{}替换为对应的参数值 * @@ -82,8 +98,16 @@ fun format(str: String, vararg args: Any?): String { // 检查参数数量 val placeholderCount = matches.count() - if (argIndex != args.size) { + if (placeholderCount != args.size) { log.warn("Placeholder count: $placeholderCount, Argument count: ${args.size}") + log.warn("template : $str") + log.warn( + "Arguments : [${ + ", ".join(args) { + if (it.isNull()) return@join "null:null" + "${it.javaClass.simpleName}:$it" + } + }]") } return finalResult @@ -214,3 +238,65 @@ fun String.join(list: List, getstring: (T) -> String = { it.toString() }) return sb.toString() } +fun String.join(array: Array, getstring: (T) -> String = { it.toString() }): String { + // 使用StringBuilder构建结果字符串 + val sb = StringBuilder() + for (i in array.indices) { + sb.append(getstring(array[i])) + // 除了最后一个元素外,都在后面添加当前字符串作为分隔符 + if (i != array.size - 1) { + sb.append(this) + } + } + return sb.toString() +} + +fun String?.parserTemplate(template: String): List? { + if (this == null) return null + val regex: Regex = Regex( + "^" + + template + .replace("\\", "\\\\") + .replace("(", "\\(") + .replace(")", "\\)") + .replace("[", "\\[") + .replace("]", "\\]") + .replace("+", "\\+") + .replace("=", "\\=") + .replace("{}", "((?s).*)") + .toRegex() + + "$" + ) + val datas = regex.find(this)?.groupValues ?: return null + return List(datas.size - 1) { + datas[it + 1] + } +} + + +fun String.urlEncode() = URLEncoder.encode(this, Charsets.UTF_8.name()) +fun String.urlDecode() = URLDecoder.decode(this, Charsets.UTF_8.name()) + + +fun String.hmacSHA256String(keyS: String): String { + val instance = Mac.getInstance("HmacSHA256") + val key = keyS.toByteArray() + instance.init(SecretKeySpec(key, 0, key.size, "HmacSHA256")) + val bytes = instance.doFinal(this.toByteArray()) + return BASE16.encode(bytes) +} + +fun String.md5String(): String { + val instance = MessageDigest.getInstance("MD5") + instance.update(this.toByteArray()) + val bytes = instance.digest() + return BASE16.encode(bytes) +} + +fun String.hmacMd5String(keyS: String): String { + val instance = Mac.getInstance("HmacMD5") + val key = keyS.toByteArray() + instance.init(SecretKeySpec(key, 0, key.size, "HmacSHA256")) + val bytes = instance.doFinal(this.toByteArray()) + return BASE16.encode(bytes) +} diff --git a/src/main/kotlin/com/mingliqiye/utils/system/SystemUtil.kt b/src/main/kotlin/com/mingliqiye/utils/system/SystemUtil.kt index e0299c3..2add6d8 100644 --- a/src/main/kotlin/com/mingliqiye/utils/system/SystemUtil.kt +++ b/src/main/kotlin/com/mingliqiye/utils/system/SystemUtil.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +16,16 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile SystemUtil.kt - * LastUpdate 2025-09-16 17:36:11 + * LastUpdate 2026-01-31 20:47:59 * UpdateUser MingLiPro */ @file:JvmName("SystemUtils") package com.mingliqiye.utils.system -import com.mingliqiye.utils.random.randomByteSecure +import com.mingliqiye.utils.random.randomByte import java.lang.management.ManagementFactory -import java.net.Inet4Address -import java.net.InetAddress -import java.net.NetworkInterface -import java.net.SocketException +import java.net.* /** * 操作系统名称属性,延迟初始化 @@ -256,10 +253,7 @@ val computerName: String by lazy { */ val userName: String by lazy { try { - getEnvVar("USERNAME") - ?: getEnvVar("USER") - ?: System.getProperty("user.name") - ?: "unknown" + getEnvVar("USERNAME") ?: getEnvVar("USER") ?: System.getProperty("user.name") ?: "unknown" } catch (e: SecurityException) { "unknown" } catch (e: Exception) { @@ -297,11 +291,11 @@ val macAddressBytes: ByteArray by lazy { return@lazy mac } } - randomByteSecure(6) + randomByte(6) } catch (e: SocketException) { - randomByteSecure(6) + randomByte(6) } catch (e: Exception) { - randomByteSecure(6) + randomByte(6) } } @@ -375,3 +369,91 @@ val allMacAddressesStringList: Map> by lazy { entry.value.map { String.format("%02X", it) } } } + +/** + * 获取可用处理器数量的懒加载属性 + */ +val availableProcessors: Int by lazy { + Runtime.getRuntime().availableProcessors() +} + + +private var isLoadprotocol = false +private var protocol = "" + + +/** + * 判断是否为开发模式(file协议) + */ +val isDevMode: Boolean by lazy { + protocol == "file" +} + +/** + * 判断是否不为开发模式 + */ +val isNotDevMode: Boolean by lazy { + protocol != "file" +} + +/** + * 判断是否为JAR模式 + */ +val isJarMode: Boolean by lazy { + protocol == "jar" +} + +/** + * 判断是否不为JAR模式 + */ +val isNotJarMode: Boolean by lazy { + protocol != "jar" +} + +/** + * 判断是否为WAR模式 + */ +val isWarMode: Boolean by lazy { + protocol == "war" +} + +/** + * 判断是否不为WAR模式 + */ +val isNotMode: Boolean by lazy { + protocol != "war" +} + +/** + * 判断是否为JSWAR模式 + */ +val isJswarMode: Boolean by lazy { + protocol == "jswar" +} + +/** + * 判断是否不为JSWAR模式 + */ +val isNotJswarMode: Boolean by lazy { + protocol != "jswar" +} + +/** + * 加载并获取资源协议 + * @param clazz 要获取协议的类对象,默认为null + * @return 返回资源的协议字符串 + */ +fun loadProtocol(clazz: Class<*>? = null): String { + // 如果已经加载过协议且clazz为null,则直接返回已缓存的协议 + if (isLoadprotocol && clazz == null) { + return protocol + } + val resource: URL? = clazz!!.getResource( + clazz.getSimpleName() + ".class" + ) + protocol = resource!!.protocol + return protocol.let { + isLoadprotocol = true + return@let it + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/thread/ThreadEvent.kt b/src/main/kotlin/com/mingliqiye/utils/thread/ThreadEvent.kt new file mode 100644 index 0000000..9c14cef --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/thread/ThreadEvent.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile ThreadEvent.kt + * LastUpdate 2026-02-05 11:20:59 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.thread + +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +/** + * 线程事件同步工具类,用于线程间的事件通知和等待 + */ +open class ThreadEvent { + + private var latch = CountDownLatch(1) + + @Volatile + private var data: T + + constructor(data: T) { + this.data = data + } + + /** + * 获取当前事件状态 + * + * @return 当前事件的数据值,如果未设置则返回null + */ + fun get(): T = data + + /** + * 设置事件数据并释放等待的线程 + * + * @param data 要设置的事件数据 + */ + @Synchronized + fun set(data: T) { + this.data = data + latch.countDown() + } + + /** + * 重置事件状态,重新初始化CountDownLatch + */ + @Synchronized + fun reset() { + latch = CountDownLatch(1) + } + + /** + * 等待事件被设置,阻塞当前线程直到事件被触发 + */ + fun await() { + latch.await() + } + + /** + * 等待事件被设置并获取数据 + * + * @return 事件数据,当事件被设置后返回对应的数据 + */ + fun awaitAndGet(): T { + await() + return data + } + + /** + * 在指定超时时间内等待事件被设置并获取数据 + * + * @param timeout 等待超时时间 + * @param unit 时间单位 + * @return 事件数据,如果在超时时间内事件被设置则返回数据,否则返回null + */ + fun awaitAndGet(timeout: Long, unit: TimeUnit): T? { + await(timeout, unit) + return data + } + + /** + * 在指定超时时间内等待事件被设置 + * + * @param timeout 等待超时时间 + * @param unit 时间单位 + * @return 如果在超时时间内事件被设置则返回true,否则返回false + */ + fun await(timeout: Long, unit: TimeUnit): Boolean { + return latch.await(timeout, unit) + } + + fun setAndReset(data: T) { + set(data) + reset() + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/thread/ThreadRunner.kt b/src/main/kotlin/com/mingliqiye/utils/thread/ThreadRunner.kt new file mode 100644 index 0000000..01c0604 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/thread/ThreadRunner.kt @@ -0,0 +1,110 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile ThreadRunner.kt + * LastUpdate 2026-02-05 11:20:59 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.thread + +import com.mingliqiye.utils.netty.NamedThreadFactory +import com.mingliqiye.utils.system.availableProcessors +import java.util.concurrent.Callable +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import java.util.concurrent.Future + +/** + * 线程执行器工具类,用于管理线程池并提供异步任务执行功能 + */ +object ThreadRunner { + + var executorService: ExecutorService? = null + + /** + * 关闭线程池执行器 + * 首先尝试正常关闭,如果失败则强制关闭 + */ + @JvmStatic + fun close() { + try { + executorService?.shutdown() + } catch (_: Exception) { + executorService?.shutdownNow() + } + } + + /** + * 初始化线程池执行器 + * 创建固定大小的线程池,线程数量为可用处理器核心数的两倍 + * + * @param string 线程名称前缀,默认为"MingliUtilThread" + */ + @JvmStatic + fun init(string: String = "MingliUtilThread") { + executorService = Executors.newFixedThreadPool( + availableProcessors * 2, + NamedThreadFactory { clazz, poolNumber, threadNumber -> + "$string #$threadNumber" + }) + } + + /** + * 异步执行Runnable任务 + * + * @param runnable 要执行的Runnable任务 + * @return Future对象,可用于获取任务执行结果或控制任务状态 + */ + @JvmStatic + fun runThread(runnable: Runnable): Future<*> { + return executorService!!.submit(runnable) + } + + /** + * 异步执行Callable任务 + * + * @param runnable 要执行的Callable任务 + * @return Future对象,可用于获取任务执行结果或控制任务状态 + */ + @JvmStatic + fun runThread(runnable: Callable): Future { + return executorService!!.submit(runnable) + } + + /** + * 同步执行Runnable任务,阻塞等待任务完成 + * + * @param runnable 要执行的Runnable任务 + * @return 任务执行结果(通常为null) + */ + @JvmStatic + fun runThreadAwait(runnable: Runnable): Any { + return executorService!!.submit(runnable).get() + } + + /** + * 同步执行Callable任务,阻塞等待任务完成并返回结果 + * + * @param runnable 要执行的Callable任务 + * @return 任务执行结果 + */ + @JvmStatic + fun runThreadAwait(runnable: Callable): T { + return executorService!!.submit(runnable).get() + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/thread/ThreadUtils.kt b/src/main/kotlin/com/mingliqiye/utils/thread/ThreadUtils.kt new file mode 100644 index 0000000..f942b88 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/thread/ThreadUtils.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile ThreadUtils.kt + * LastUpdate 2026-01-31 20:48:19 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.thread + +/** + * 线程工具类,提供线程名称的获取和设置功能 + */ +object ThreadUtils { + /** + * 获取或设置当前线程的名称 + * + * Getter: 返回当前线程的名称 + * Setter: 设置当前线程的名称 + * + * @return 当前线程的名称字符串 + */ + @JvmStatic + var name: String + get() = Thread.currentThread().name + set(s) { + Thread.currentThread().name = s + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/time/DateTime.kt b/src/main/kotlin/com/mingliqiye/utils/time/DateTime.kt index 880e604..a274873 100644 --- a/src/main/kotlin/com/mingliqiye/utils/time/DateTime.kt +++ b/src/main/kotlin/com/mingliqiye/utils/time/DateTime.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile DateTime.kt - * LastUpdate 2025-09-17 19:06:39 + * LastUpdate 2026-02-04 21:54:04 * UpdateUser MingLiPro */ @@ -26,7 +26,7 @@ import com.mingliqiye.utils.jna.FILETIME_EPOCH_OFFSET import com.mingliqiye.utils.jna.NANOS_PER_100NS import com.mingliqiye.utils.jna.WinKernel32Api import com.mingliqiye.utils.jna.getWinKernel32Apis -import com.mingliqiye.utils.logger.mingLiLoggerFactory +import com.mingliqiye.utils.logger.MingLiLoggerFactory import com.mingliqiye.utils.system.isWindows import com.mingliqiye.utils.system.javaVersionAsInteger import org.slf4j.Logger @@ -40,131 +40,6 @@ import kotlin.time.ExperimentalTime import kotlin.time.Instant -/** - * 时间位移 类 - * - * @author MingLiPro - */ -class DateTimeOffset private constructor( - val offsetType: ChronoUnit, val offset: Long -) { - - companion object { - /** - * 创建一个新的DateTimeOffset实例 - * - * @param offsetType 偏移量的单位类型,指定偏移量的计算单位 - * @param offset 偏移量的数值,可以为正数、负数或零 - * @return 返回一个新的DateTimeOffset对象,包含指定的偏移量信息 - */ - @JvmStatic - fun of(offsetType: ChronoUnit, offset: Long): DateTimeOffset { - return DateTimeOffset(offsetType, offset) - } - - /** - * 创建一个 DateTimeOffset 实例 - * - * @param offset 偏移量数值 - * @param offsetType 偏移量的时间单位类型 - * @return 返回一个新的 DateTimeOffset 实例 - */ - @JvmStatic - fun of(offset: Long, offsetType: ChronoUnit): DateTimeOffset { - return DateTimeOffset(offsetType, offset) - } - } -} - - -/** - * 时间格式化枚举类 - * - * - * 定义了常用的时间格式化模式,用于日期时间的解析和格式化操作 - * 每个枚举常量包含对应的格式化字符串和字符串长度 - * - */ -enum class Formatter(private val value: String) { - /** - * 标准日期时间格式:yyyy-MM-dd HH:mm:ss - */ - STANDARD_DATETIME("yyyy-MM-dd HH:mm:ss"), - - /** - * 标准日期时间格式(7位毫秒):yyyy-MM-dd HH:mm:ss.SSSSSSS - */ - STANDARD_DATETIME_MILLISECOUND7("yyyy-MM-dd HH:mm:ss.SSSSSSS"), - STANDARD_DATETIME_MILLISECOUND8("yyyy-MM-dd HH:mm:ss.SSSSSSSS"), - STANDARD_DATETIME_MILLISECOUND9("yyyy-MM-dd HH:mm:ss.SSSSSSSSS"), - - /** - * 标准日期时间格式(6位毫秒):yyyy-MM-dd HH:mm:ss.SSSSSS - */ - STANDARD_DATETIME_MILLISECOUND6("yyyy-MM-dd HH:mm:ss.SSSSSS"), - - /** - * 标准日期时间格式(5位毫秒):yyyy-MM-dd HH:mm:ss.SSSSS - */ - STANDARD_DATETIME_MILLISECOUND5("yyyy-MM-dd HH:mm:ss.SSSSS"), - - /** - * 标准日期时间格式(4位毫秒):yyyy-MM-dd HH:mm:ss.SSSS - */ - STANDARD_DATETIME_MILLISECOUND4("yyyy-MM-dd HH:mm:ss.SSSS"), - - /** - * 标准日期时间格式(3位毫秒):yyyy-MM-dd HH:mm:ss.SSS - */ - STANDARD_DATETIME_MILLISECOUND3("yyyy-MM-dd HH:mm:ss.SSS"), - - /** - * 标准日期时间格式(2位毫秒):yyyy-MM-dd HH:mm:ss.SS - */ - STANDARD_DATETIME_MILLISECOUND2("yyyy-MM-dd HH:mm:ss.SS"), - - /** - * 标准日期时间格式(1位毫秒):yyyy-MM-dd HH:mm:ss.S - */ - STANDARD_DATETIME_MILLISECOUND1("yyyy-MM-dd HH:mm:ss.S"), - - /** - * 标准ISO格式:yyyy-MM-dd'T'HH:mm:ss.SSS'Z' - */ - STANDARD_ISO("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"), - - /** - * 标准日期时间秒格式:yyyy-MM-dd HH:mm:ss - */ - STANDARD_DATETIME_SECOUND("yyyy-MM-dd HH:mm:ss"), - - /** - * 标准日期格式:yyyy-MM-dd - */ - STANDARD_DATE("yyyy-MM-dd"), - - /** - * ISO8601格式:yyyy-MM-dd'T'HH:mm:ss.SSS'000' - */ - ISO8601("yyyy-MM-dd'T'HH:mm:ss.SSS'000'"), - - /** - * 紧凑型日期时间格式:yyyyMMddHHmmss - */ - COMPACT_DATETIME("yyyyMMddHHmmss"); - - - private val len: Int = value.length - - fun getLen(): Int { - return this.len - } - - fun getValue(): String { - return this.value - } -} - /** * 时间类,用于处理日期时间的转换、格式化等操作。 * 提供了多种静态方法来创建 DateTime 实例,并支持与 Date、LocalDateTime 等类型的互转。 @@ -186,7 +61,7 @@ class DateTime private constructor( companion object { private val WIN_KERNEL_32_API: WinKernel32Api? = if (javaVersionAsInteger == 8 && isWindows) { - val log: Logger = mingLiLoggerFactory.getLogger("mingli-utils DateTime") + val log: Logger = MingLiLoggerFactory.getLogger("mingli-utils DateTime") val a = getWinKernel32Apis() if (a.size > 1) { @@ -284,11 +159,10 @@ class DateTime private constructor( timestr: String ): DateTime { - val formatterString = Formatter.STANDARD_DATETIME_MILLISECOUND9.getValue() + val formatterString = Formatter.STANDARD_DATETIME_MILLISECOUND9.value return DateTime( LocalDateTime.parse( - getFillZeroByLen(timestr, formatterString), - DateTimeFormatter.ofPattern(formatterString) + getFillZeroByLen(timestr, formatterString), DateTimeFormatter.ofPattern(formatterString) ) ) } @@ -305,7 +179,7 @@ class DateTime private constructor( fun parse( timestr: String, formatter: Formatter, fillZero: Boolean ): DateTime { - return parse(timestr, formatter.getValue(), fillZero) + return parse(timestr, formatter.value, fillZero) } /** @@ -317,7 +191,7 @@ class DateTime private constructor( */ @JvmStatic fun parse(timestr: String, formatter: Formatter): DateTime { - return parse(timestr, formatter.getValue()) + return parse(timestr, formatter.value) } /** @@ -340,23 +214,24 @@ class DateTime private constructor( * @return 补零后的时间字符串 */ private fun getFillZeroByLen(dstr: String, formats: String): String { - if (dstr.length == formats.length) { + val formatslen = formats.replace("'", "").length + if (dstr.length == formatslen) { return dstr } - if (formats.length > dstr.length) { + if (formatslen > dstr.length) { var modifiedDstr = dstr if (dstr.length == 19) { modifiedDstr += "." } val sb = StringBuilder(modifiedDstr) - for (i in 0 until formats.length - sb.length) { + for (i in 0 until formatslen - sb.length) { sb.append("0") } return sb.toString() } throw IllegalArgumentException( String.format( - "Text: '%s' len %s < %s %s", dstr, dstr.length, formats, formats.length + "Text: '%s' len %s < %s %s", dstr, dstr.length, formats, formatslen ) ) } @@ -520,6 +395,20 @@ class DateTime private constructor( ) } + /** + * 重载加法运算符,将指定的DateTimeOffset添加到当前DateTime对象 + * @param dateTimeOffset 要添加的时间偏移量 + * @return 返回相加后的新DateTime对象 + */ + operator fun plus(dateTimeOffset: DateTimeOffset): DateTime = add(dateTimeOffset) + + /** + * 重载减法运算符,从当前DateTime对象中减去指定的DateTimeOffset + * @param dateTimeOffset 要减去的时间偏移量 + * @return 返回相减后的新DateTime对象 + */ + operator fun minus(dateTimeOffset: DateTimeOffset): DateTime = sub(dateTimeOffset) + /** * 在当前时间基础上减少指定的时间偏移量。 * @@ -547,11 +436,11 @@ class DateTime private constructor( * @return 返回格式化后的时间字符串 */ fun format(formatter: Formatter): String { - return format(formatter.getValue()) + return format(formatter.value) } fun format(): String { - return format(Formatter.STANDARD_DATETIME_MILLISECOUND9.getValue(), true) + return format(Formatter.STANDARD_DATETIME_MILLISECOUND9.value, true) } /** @@ -581,7 +470,7 @@ class DateTime private constructor( * @return 返回格式化后的时间字符串 */ fun format(formatter: Formatter, repcZero: Boolean): String { - return format(formatter.getValue(), repcZero) + return format(formatter.value, repcZero) } /** @@ -668,7 +557,6 @@ class DateTime private constructor( */ fun toNanoTime(): Long { val instant = toInstant() - return try { val secondsInNanos = Math.multiplyExact(instant.epochSecond, 1_000_000_000L) Math.addExact(secondsInNanos, instant.nano.toLong()) diff --git a/src/main/kotlin/com/mingliqiye/utils/time/DateTimeJsonFormat.kt b/src/main/kotlin/com/mingliqiye/utils/time/DateTimeJsonFormat.kt new file mode 100644 index 0000000..9ae7f31 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/time/DateTimeJsonFormat.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile DateTimeJsonFormat.kt + * LastUpdate 2026-02-04 22:14:47 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.time + +@Target(AnnotationTarget.FIELD) +annotation class DateTimeJsonFormat( + val value: Formatter = Formatter.NONE, + val formatter: String = "", + val repcZero: Boolean = true, +) diff --git a/src/main/kotlin/com/mingliqiye/utils/time/DateTimeOffset.kt b/src/main/kotlin/com/mingliqiye/utils/time/DateTimeOffset.kt new file mode 100644 index 0000000..15e9b7a --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/time/DateTimeOffset.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile DateTimeOffset.kt + * LastUpdate 2026-02-04 21:54:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.time + +import java.time.temporal.ChronoUnit + +/** + * 时间位移 类 + * + * @author MingLiPro + */ +class DateTimeOffset private constructor( + val offsetType: ChronoUnit, val offset: Long +) { + + companion object { + /** + * 创建一个新的DateTimeOffset实例 + * + * @param offsetType 偏移量的单位类型,指定偏移量的计算单位 + * @param offset 偏移量的数值,可以为正数、负数或零 + * @return 返回一个新的DateTimeOffset对象,包含指定的偏移量信息 + */ + @JvmStatic + fun of(offsetType: ChronoUnit, offset: Long): DateTimeOffset { + return DateTimeOffset(offsetType, offset) + } + + /** + * 创建一个 DateTimeOffset 实例 + * + * @param offset 偏移量数值 + * @param offsetType 偏移量的时间单位类型 + * @return 返回一个新的 DateTimeOffset 实例 + */ + @JvmStatic + fun of(offset: Long, offsetType: ChronoUnit): DateTimeOffset { + return DateTimeOffset(offsetType, offset) + } + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/time/Formatter.kt b/src/main/kotlin/com/mingliqiye/utils/time/Formatter.kt new file mode 100644 index 0000000..ca1397b --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/time/Formatter.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile Formatter.kt + * LastUpdate 2026-02-04 21:54:36 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.time + +/** + * 时间格式化枚举类 + * + * + * 定义了常用的时间格式化模式,用于日期时间的解析和格式化操作 + * 每个枚举常量包含对应的格式化字符串和字符串长度 + * + */ +enum class Formatter(val value: String) { + /** + * 标准日期时间格式:yyyy-MM-dd HH:mm:ss + */ + STANDARD_DATETIME("yyyy-MM-dd HH:mm:ss"), + NONE(""), + + /** + * 标准日期时间格式(7位毫秒):yyyy-MM-dd HH:mm:ss.SSSSSSS + */ + STANDARD_DATETIME_MILLISECOUND7("yyyy-MM-dd HH:mm:ss.SSSSSSS"), + STANDARD_DATETIME_MILLISECOUND8("yyyy-MM-dd HH:mm:ss.SSSSSSSS"), + STANDARD_DATETIME_MILLISECOUND9("yyyy-MM-dd HH:mm:ss.SSSSSSSSS"), + + /** + * 标准日期时间格式(6位毫秒):yyyy-MM-dd HH:mm:ss.SSSSSS + */ + STANDARD_DATETIME_MILLISECOUND6("yyyy-MM-dd HH:mm:ss.SSSSSS"), + + /** + * 标准日期时间格式(5位毫秒):yyyy-MM-dd HH:mm:ss.SSSSS + */ + STANDARD_DATETIME_MILLISECOUND5("yyyy-MM-dd HH:mm:ss.SSSSS"), + + /** + * 标准日期时间格式(4位毫秒):yyyy-MM-dd HH:mm:ss.SSSS + */ + STANDARD_DATETIME_MILLISECOUND4("yyyy-MM-dd HH:mm:ss.SSSS"), + + /** + * 标准日期时间格式(3位毫秒):yyyy-MM-dd HH:mm:ss.SSS + */ + STANDARD_DATETIME_MILLISECOUND3("yyyy-MM-dd HH:mm:ss.SSS"), + + /** + * 标准日期时间格式(2位毫秒):yyyy-MM-dd HH:mm:ss.SS + */ + STANDARD_DATETIME_MILLISECOUND2("yyyy-MM-dd HH:mm:ss.SS"), + + /** + * 标准日期时间格式(1位毫秒):yyyy-MM-dd HH:mm:ss.S + */ + STANDARD_DATETIME_MILLISECOUND1("yyyy-MM-dd HH:mm:ss.S"), + + /** + * 标准ISO格式:yyyy-MM-dd'T'HH:mm:ss.SSS'Z' + */ + STANDARD_ISO("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"), + + /** + * 标准日期时间秒格式:yyyy-MM-dd HH:mm:ss + */ + STANDARD_DATETIME_SECOUND("yyyy-MM-dd HH:mm:ss"), + + /** + * 标准日期格式:yyyy-MM-dd + */ + STANDARD_DATE("yyyy-MM-dd"), + + /** + * ISO8601格式:yyyy-MM-dd'T'HH:mm:ss.SSSSSS + */ + ISO8601("yyyy-MM-dd'T'HH:mm:ss.SSSSSS"), + + /** + * 紧凑型日期时间格式:yyyyMMddHHmmss + */ + COMPACT_DATETIME("yyyyMMddHHmmss"); + + + private val len: Int = value.replace("'", "").length + + fun getLen(): Int { + return this.len + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/uuid/UUID.kt b/src/main/kotlin/com/mingliqiye/utils/uuid/UUID.kt index 4d7fc71..1799555 100644 --- a/src/main/kotlin/com/mingliqiye/utils/uuid/UUID.kt +++ b/src/main/kotlin/com/mingliqiye/utils/uuid/UUID.kt @@ -16,16 +16,14 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile UUID.kt - * LastUpdate 2026-01-08 13:21:00 + * LastUpdate 2026-02-05 11:20:59 * UpdateUser MingLiPro */ package com.mingliqiye.utils.uuid -import com.mingliqiye.utils.base.BASE256 -import com.mingliqiye.utils.base.BASE64 -import com.mingliqiye.utils.base.BASE91 -import com.mingliqiye.utils.random.randomByteSecure +import com.mingliqiye.utils.base.* +import com.mingliqiye.utils.random.randomByte import com.mingliqiye.utils.random.secureRandom import com.mingliqiye.utils.system.macAddressBytes import com.mingliqiye.utils.time.DateTime @@ -109,7 +107,7 @@ class UUID : Serializable { */ @JvmStatic fun getV4(): UUID { - val randomBytes = randomByteSecure(16) + val randomBytes = randomByte(16) randomBytes[6] = (randomBytes[6].toInt() and 0x0F).toByte() randomBytes[6] = (randomBytes[6].toInt() or 0x40).toByte() randomBytes[8] = (randomBytes[8].toInt() and 0x3F).toByte() @@ -192,7 +190,7 @@ class UUID : Serializable { val buffer = ByteBuffer.allocate(16) buffer.putInt((instant shr 16).toInt()) buffer.putShort((instant).toShort()) - buffer.put(randomByteSecure(2)) + buffer.put(randomByte(2)) buffer.putLong(secureRandom.nextLong()) val bytes = buffer.array() bytes[6] = (bytes[6].toInt() and 0x0F or 0x70).toByte() @@ -363,6 +361,10 @@ class UUID : Serializable { return result } + fun of(str: String, base: BaseType): UUID { + return UUID(base.baseCodec.decode(str)) + } + } /** @@ -485,6 +487,18 @@ class UUID : Serializable { } } + fun getString(uuidFormatType: UUIDFormatType): String { + return getString(isUpper = uuidFormatType.isUpper, isnotSpace = uuidFormatType.isnotSpace) + } + + fun getString(baseType: BaseType): String { + return getString(baseType.baseCodec) + } + + fun getString(baseCodec: BaseCodec): String { + return baseCodec.encode(data) + } + /** * 返回标准格式的 UUID 字符串(带连字符)。 * diff --git a/src/main/kotlin/com/mingliqiye/utils/uuid/UUIDFormatType.kt b/src/main/kotlin/com/mingliqiye/utils/uuid/UUIDFormatType.kt new file mode 100644 index 0000000..d06cc08 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/uuid/UUIDFormatType.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile UUIDFormatType.kt + * LastUpdate 2026-02-04 21:54:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.uuid + +enum class UUIDFormatType(val isUpper: Boolean, val isnotSpace: Boolean) { + UPPER_SPACE(true, false), + NO_UPPER_SPACE(false, false), + NO_UPPER_NO_SPACE(false, true), + UPPER_NO_SPACE(true, true), +} diff --git a/src/main/kotlin/com/mingliqiye/utils/uuid/UUIDJsonFormat.kt b/src/main/kotlin/com/mingliqiye/utils/uuid/UUIDJsonFormat.kt new file mode 100644 index 0000000..7196474 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/uuid/UUIDJsonFormat.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile UUIDJsonFormat.kt + * LastUpdate 2026-02-04 22:14:47 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.uuid + +import com.mingliqiye.utils.base.BaseType + +@Target(AnnotationTarget.FIELD) +annotation class UUIDJsonFormat( + val value: UUIDFormatType = UUIDFormatType.NO_UPPER_SPACE, + val base: BaseType = BaseType.BASE16 +) diff --git a/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index db0d345..343fd55 100644 --- a/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,5 +1,5 @@ # -# Copyright 2025 mingliqiye +# Copyright 2026 mingliqiye # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,10 +16,9 @@ # ProjectName mingli-utils # ModuleName mingli-utils.main # CurrentFile org.springframework.boot.autoconfigure.AutoConfiguration.imports -# LastUpdate 2025-09-15 22:32:50 +# LastUpdate 2026-02-04 16:47:27 # UpdateUser MingLiPro # com.mingliqiye.utils.springboot.autoconfigure.AutoConfiguration com.mingliqiye.utils.springboot.autoconfigure.JacksonAutoConfiguration -com.mingliqiye.utils.springboot.autoconfigure.GsonAutoConfiguration diff --git a/src/test/kotlin/com/mingliqiye/utils/JsonTest.kt b/src/test/kotlin/com/mingliqiye/utils/JsonTest.kt new file mode 100644 index 0000000..a18dc25 --- /dev/null +++ b/src/test/kotlin/com/mingliqiye/utils/JsonTest.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.test + * CurrentFile JsonTest.kt + * LastUpdate 2026-02-05 10:57:18 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils + +import com.mingliqiye.utils.base.BaseType +import com.mingliqiye.utils.io.IO.println +import com.mingliqiye.utils.json.api.JSONA +import com.mingliqiye.utils.json.api.JacksonJsonApi +import com.mingliqiye.utils.json.converters.DateTimeJsonConverter +import com.mingliqiye.utils.json.converters.UUIDJsonConverter +import com.mingliqiye.utils.string.formatd +import com.mingliqiye.utils.time.DateTime +import com.mingliqiye.utils.time.DateTimeJsonFormat +import com.mingliqiye.utils.time.Formatter +import com.mingliqiye.utils.uuid.UUID +import com.mingliqiye.utils.uuid.UUIDFormatType +import com.mingliqiye.utils.uuid.UUIDJsonFormat +import org.junit.jupiter.api.Test + + +class JsonTest { + @Test + fun testJSONA() { + + JSONA.setJsonApi( + JacksonJsonApi( + JSONA.jacksonKotlinObjectMapper() + ) + ) + JSONA.addJsonConverter() + JSONA.addJsonConverter() + + "{} {} {} {}".formatd("0", 1).println() + } + + @Test + fun testBANNER() { + com.mingliqiye.utils.springboot.autoconfigure.AutoConfiguration.printBanner() + } +} + +data class AC( + var a: String = "AC", + @field:DateTimeJsonFormat(Formatter.ISO8601, repcZero = false) + var time: DateTime = DateTime.now(), + @field:UUIDJsonFormat( + base = BaseType.BASE256, + value = UUIDFormatType.UPPER_NO_SPACE + ) + var uuid: UUID = UUID.getV4(), + var b: T +) + +data class BC( + var a: String = "BC", + var b: T +) + +data class CC( + var a: String = "BC", + var b: T +) -- 2.47.2 From 450b4e1a93d2b8f19c138d8421923a3e0396fd69 Mon Sep 17 00:00:00 2001 From: minglipro Date: Sun, 8 Feb 2026 03:17:14 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat(utils):=20=E6=B7=BB=E5=8A=A0=E6=95=B0?= =?UTF-8?q?=E7=BB=84=E5=B7=A5=E5=85=B7=E7=B1=BB=E5=92=8C=E5=9B=BD=E9=99=85?= =?UTF-8?q?=E5=8C=96=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加ByteArray.toHexString和String.toHexByteArray扩展函数 - 实现多种数组类型的copyTo和copyFrom扩展方法 - 在AutoConfiguration中集成I18N国际化功能 - 添加AutoService注解及处理器用于自动服务注册 - 新增BadGatewayException HTTP状态异常类 - 更新启动横幅格式并添加国际化文本支持 --- build.gradle.kts | 18 +- gradle.properties | 4 +- settings.gradle.kts | 4 +- .../utils/annotation/AutoService.kt | 41 + .../DateTimeJsonFormat.kt | 13 +- .../utils/annotation/TestAnnotation.kt | 31 + .../{uuid => annotation}/UUIDJsonFormat.kt | 11 +- .../processor/AutoServiceProcessor.kt | 105 ++ .../com/mingliqiye/utils/array/ArrayUtils.kt | 598 +++++------ .../com/mingliqiye/utils/base/Base10.kt | 68 ++ .../com/mingliqiye/utils/base/Base16.kt | 6 +- .../kotlin/com/mingliqiye/utils/base/Base2.kt | 59 ++ .../com/mingliqiye/utils/base/Base256.kt | 6 +- .../com/mingliqiye/utils/base/Base64.kt | 6 +- .../com/mingliqiye/utils/base/Base91.kt | 6 +- .../com/mingliqiye/utils/base/BaseUtils.kt | 83 +- .../mingliqiye/utils/base/code/BaseCodes.kt | 81 ++ .../com/mingliqiye/utils/clone/CloneUtils.kt | 4 +- .../utils/exception/BadGatewayException.kt | 34 + .../utils/exception/BadRequestException.kt | 34 + .../utils/exception/ConflictException.kt | 34 + .../utils/exception/EmptyJsonException.kt | 36 + .../utils/exception/ForbiddenException.kt | 34 + .../utils/exception/FoundException.kt | 34 + .../exception/GatewayTimeoutException.kt | 34 + .../utils/exception/GoneException.kt | 34 + .../utils/exception/HttpException.kt | 36 + .../utils/exception/HttpStatusException.kt | 36 + .../utils/exception/HttpUrlException.kt | 36 + .../HttpVersionNotSupportedException.kt | 34 + .../exception/InternalServerErrorException.kt | 34 + .../utils/exception/InvalidJsonException.kt | 36 + .../{json/api => }/exception/JsonException.kt | 36 +- .../exception/JsonObjectCastException.kt | 36 + .../utils/exception/JsonParserException.kt | 36 + .../utils/exception/JsonTypeException.kt | 36 + .../exception/LengthRequiredException.kt | 34 + .../exception/MethodNotAllowedException.kt | 34 + .../exception/MingLiUtilsBaseException.kt | 37 + .../exception/MovedPermanentlyException.kt | 34 + .../exception/MultipleChoicesException.kt | 34 + .../utils/exception/NotAcceptableException.kt | 34 + .../utils/exception/NotFoundException.kt | 34 + .../exception/NotImplementedException.kt | 34 + .../utils/exception/NotModifiedException.kt | 34 + .../exception/PaymentRequiredException.kt | 34 + .../exception/PreconditionFailedException.kt | 34 + .../ProxyAuthenticationRequiredException.kt | 34 + .../RequestEntityTooLargeException.kt | 34 + .../exception/RequestTimeoutException.kt | 34 + .../exception/RequestUriTooLargeException.kt | 34 + .../RequestedRangeNotSatisfiableException.kt | 34 + .../utils/exception/SeeOtherException.kt | 34 + .../exception/ServiceUnavailableException.kt | 34 + .../exception/TemporaryRedirectException.kt | 34 + .../utils/exception/UnauthorizedException.kt | 34 + .../UnsupportedMediaTypeException.kt | 34 + .../utils/exception/UseProxyException.kt | 34 + .../mingliqiye/utils/functions/Pipeline.kt | 4 +- .../com/mingliqiye/utils/http/Response.kt | 37 +- .../utils/http/exception/Exceptions.kt | 372 ------- .../kotlin/com/mingliqiye/utils/i18n/I18N.kt | 964 ++++++++++++++++++ .../utils/i18n/Internationalization.kt | 144 +++ src/main/kotlin/com/mingliqiye/utils/io/IO.kt | 11 +- .../utils/json/api/JacksonJsonApi.kt | 6 +- .../json/converters/DateTimeJsonConverter.kt | 16 +- .../json/converters/UUIDJsonConverter.kt | 15 +- .../json/converters/base/AnnotationGetter.kt | 21 +- .../json/converters/base/BaseJsonConverter.kt | 192 +--- .../converters/base/JackJsonDeserializer.kt | 59 ++ .../converters/base/JackJsonSerializer.kt | 85 ++ .../converters/base/JackSonJsonConverter.kt | 57 ++ .../json/converters/base/JsonConverter.kt | 94 +- .../converters/base/JsonConverterUtils.kt | 61 ++ .../logger/{Loggers.kt => MingLiLogger.kt} | 18 +- .../MingLiLoggerLevel.kt} | 16 +- .../utils/mybatisplus/BaseMapperQuery.kt | 120 ++- .../com/mingliqiye/utils/require/Require.kt | 265 ++--- .../autoconfigure/AutoConfiguration.kt | 43 +- .../autoconfigure/JacksonAutoConfiguration.kt | 18 +- .../mingliqiye/utils/string/StringUtils.kt | 24 +- .../com/mingliqiye/utils/uuid/MysqlUUIDv1.kt | 55 +- .../kotlin/com/mingliqiye/utils/uuid/UUID.kt | 6 +- .../mingliqiye/utils/uuid/UUIDFormatType.kt | 28 +- src/main/resources/META-INF/meta-data | 14 +- .../javax.annotation.processing.Processor | 23 + .../resources/assets/mingli-utils/icon.png | Bin 0 -> 100851 bytes .../assets/mingli-utils/lang/en_US.json | 42 + .../assets/mingli-utils/lang/zh_CN.json | 42 + src/main/resources/fabric.mod.json | 27 + .../kotlin/com/mingliqiye/utils/BaseTest.kt | 65 ++ .../kotlin/com/mingliqiye/utils/JsonTest.kt | 71 +- src/test/resources/lang/zh_CN.json | 23 + 93 files changed, 4271 insertions(+), 1292 deletions(-) create mode 100644 src/main/kotlin/com/mingliqiye/utils/annotation/AutoService.kt rename src/main/kotlin/com/mingliqiye/utils/{time => annotation}/DateTimeJsonFormat.kt (69%) create mode 100644 src/main/kotlin/com/mingliqiye/utils/annotation/TestAnnotation.kt rename src/main/kotlin/com/mingliqiye/utils/{uuid => annotation}/UUIDJsonFormat.kt (73%) create mode 100644 src/main/kotlin/com/mingliqiye/utils/annotation/processor/AutoServiceProcessor.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/base/Base10.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/base/Base2.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/base/code/BaseCodes.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/BadGatewayException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/BadRequestException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/ConflictException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/EmptyJsonException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/ForbiddenException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/FoundException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/GatewayTimeoutException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/GoneException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/HttpException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/HttpStatusException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/HttpUrlException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/HttpVersionNotSupportedException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/InternalServerErrorException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/InvalidJsonException.kt rename src/main/kotlin/com/mingliqiye/utils/{json/api => }/exception/JsonException.kt (58%) create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/JsonObjectCastException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/JsonParserException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/JsonTypeException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/LengthRequiredException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/MethodNotAllowedException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/MingLiUtilsBaseException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/MovedPermanentlyException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/MultipleChoicesException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/NotAcceptableException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/NotFoundException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/NotImplementedException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/NotModifiedException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/PaymentRequiredException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/PreconditionFailedException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/ProxyAuthenticationRequiredException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/RequestEntityTooLargeException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/RequestTimeoutException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/RequestUriTooLargeException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/RequestedRangeNotSatisfiableException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/SeeOtherException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/ServiceUnavailableException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/TemporaryRedirectException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/UnauthorizedException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/UnsupportedMediaTypeException.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/exception/UseProxyException.kt delete mode 100644 src/main/kotlin/com/mingliqiye/utils/http/exception/Exceptions.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/i18n/I18N.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/i18n/Internationalization.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/base/JackJsonDeserializer.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/base/JackJsonSerializer.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/base/JackSonJsonConverter.kt create mode 100644 src/main/kotlin/com/mingliqiye/utils/json/converters/base/JsonConverterUtils.kt rename src/main/kotlin/com/mingliqiye/utils/logger/{Loggers.kt => MingLiLogger.kt} (97%) rename src/main/kotlin/com/mingliqiye/utils/{stream/StreamEmptyException.kt => logger/MingLiLoggerLevel.kt} (70%) create mode 100644 src/main/resources/META-INF/services/javax.annotation.processing.Processor create mode 100644 src/main/resources/assets/mingli-utils/icon.png create mode 100644 src/main/resources/assets/mingli-utils/lang/en_US.json create mode 100644 src/main/resources/assets/mingli-utils/lang/zh_CN.json create mode 100644 src/main/resources/fabric.mod.json create mode 100644 src/test/kotlin/com/mingliqiye/utils/BaseTest.kt create mode 100644 src/test/resources/lang/zh_CN.json diff --git a/build.gradle.kts b/build.gradle.kts index b5ad80c..42cdc30 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils * CurrentFile build.gradle.kts - * LastUpdate 2026-02-05 11:04:04 + * LastUpdate 2026-02-08 03:14:06 * UpdateUser MingLiPro */ @@ -31,6 +31,7 @@ plugins { `maven-publish` kotlin("jvm") version "2.2.20" id("org.jetbrains.dokka") version "2.0.0" + kotlin("kapt") version "2.2.20" } val GROUPSID = project.properties["GROUPSID"] as String val VERSIONS = project.properties["VERSIONS"] as String @@ -72,12 +73,15 @@ dependencies { implementation("org.slf4j:slf4j-api:2.0.17") implementation("com.mingliqiye.utils.jna:WinKernel32Api:1.0.1") + implementation(kotlin("reflect")) + compileOnly("org.mindrot:jbcrypt:0.4") compileOnly("com.squareup.okhttp3:okhttp:5.3.2") compileOnly("com.fasterxml.jackson.core:jackson-databind:2.21.0") compileOnly("com.fasterxml.jackson.module:jackson-module-kotlin:2.21.0") compileOnly("org.springframework.boot:spring-boot-starter-web:2.7.18") + compileOnly("com.google.code.gson:gson:2.13.2") compileOnly("org.mybatis:mybatis:3.5.19") compileOnly("io.netty:netty-all:4.1.130.Final") compileOnly("com.baomidou:mybatis-plus-core:3.5.15") @@ -87,6 +91,7 @@ dependencies { testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") // testImplementation("com.squareup.okhttp3:okhttp:5.3.2") testImplementation("com.mingliqiye.logger:logger-log4j2:1.0.5") + testImplementation("com.google.code.gson:gson:2.13.2") testImplementation("com.fasterxml.jackson.core:jackson-databind:2.21.0") testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.21.0") } @@ -103,6 +108,15 @@ tasks.withType().configureEach { ) } +kapt { + arguments { + arg( + "kapt.kotlin.generated", + project.layout.buildDirectory.dir("generated/source/kapt/main").get().asFile.absolutePath + ) + } +} + tasks.withType { duplicatesStrategy = DuplicatesStrategy.EXCLUDE from("LICENSE") { into("META-INF") } @@ -213,7 +227,7 @@ tasks.build { tasks.processResources { duplicatesStrategy = DuplicatesStrategy.EXCLUDE outputs.upToDateWhen { false } - filesMatching("META-INF/meta-data") { + filesMatching(listOf("META-INF/meta-data", "fabric.mod.json")) { expand( project.properties + mapOf( "buildTime" to LocalDateTime.now().format( diff --git a/gradle.properties b/gradle.properties index 6f73be9..2a0aab4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,13 +16,13 @@ # ProjectName mingli-utils # ModuleName mingli-utils # CurrentFile gradle.properties -# LastUpdate 2026-02-05 11:06:50 +# LastUpdate 2026-02-08 03:15:06 # UpdateUser MingLiPro # JDKVERSIONS=1.8 GROUPSID=com.mingliqiye.utils ARTIFACTID=mingli-utils -VERSIONS=4.6.0 +VERSIONS=4.6.4 signing.keyId=B22AA93B signing.password= signing.secretKeyRingFile=secret.gpg diff --git a/settings.gradle.kts b/settings.gradle.kts index dd6cf72..5c2ad46 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils * CurrentFile settings.gradle.kts - * LastUpdate 2025-09-16 12:32:52 + * LastUpdate 2026-02-07 17:00:59 * UpdateUser MingLiPro */ diff --git a/src/main/kotlin/com/mingliqiye/utils/annotation/AutoService.kt b/src/main/kotlin/com/mingliqiye/utils/annotation/AutoService.kt new file mode 100644 index 0000000..1eee2b2 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/annotation/AutoService.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile AutoService.kt + * LastUpdate 2026-02-07 17:00:39 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.annotation + +import kotlin.reflect.KClass + +/** + * 标记一个类为其提供自动服务注册功能的注解。 + * + * 此注解用于标识那些需要被自动注册为服务的类。通过指定[value]参数, + * 可以声明该类实现的服务接口类型。注解的作用范围限定为类(CLASS), + * 并且仅在源码级别保留(SOURCE),不会编译到字节码中。 + * @since 4.6.3 + * + * @param value 需要注册的服务接口类型数组,默认为空数组。 + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.SOURCE) +annotation class AutoService( + val value: Array> = [] +) diff --git a/src/main/kotlin/com/mingliqiye/utils/time/DateTimeJsonFormat.kt b/src/main/kotlin/com/mingliqiye/utils/annotation/DateTimeJsonFormat.kt similarity index 69% rename from src/main/kotlin/com/mingliqiye/utils/time/DateTimeJsonFormat.kt rename to src/main/kotlin/com/mingliqiye/utils/annotation/DateTimeJsonFormat.kt index 9ae7f31..a2b0466 100644 --- a/src/main/kotlin/com/mingliqiye/utils/time/DateTimeJsonFormat.kt +++ b/src/main/kotlin/com/mingliqiye/utils/annotation/DateTimeJsonFormat.kt @@ -16,12 +16,21 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile DateTimeJsonFormat.kt - * LastUpdate 2026-02-04 22:14:47 + * LastUpdate 2026-02-07 08:05:39 * UpdateUser MingLiPro */ -package com.mingliqiye.utils.time +package com.mingliqiye.utils.annotation +import com.mingliqiye.utils.time.Formatter + +/** + * 日期时间JSON格式化注解,用于标注字段的日期时间格式化方式 + * + * @property value 格式化器类型,默认为NONE + * @property formatter 自定义格式化字符串,默认为空字符串 + * @property repcZero 是否替换零值,默认为true + */ @Target(AnnotationTarget.FIELD) annotation class DateTimeJsonFormat( val value: Formatter = Formatter.NONE, diff --git a/src/main/kotlin/com/mingliqiye/utils/annotation/TestAnnotation.kt b/src/main/kotlin/com/mingliqiye/utils/annotation/TestAnnotation.kt new file mode 100644 index 0000000..75cfb13 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/annotation/TestAnnotation.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile TestAnnotation.kt + * LastUpdate 2026-02-07 08:44:25 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.annotation + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.SOURCE) +annotation class TestAnnotation(val value: String = "test") + +@Target(AnnotationTarget.FIELD) +@Retention(AnnotationRetention.SOURCE) +annotation class TestFieldAnnotation(val required: Boolean = true) diff --git a/src/main/kotlin/com/mingliqiye/utils/uuid/UUIDJsonFormat.kt b/src/main/kotlin/com/mingliqiye/utils/annotation/UUIDJsonFormat.kt similarity index 73% rename from src/main/kotlin/com/mingliqiye/utils/uuid/UUIDJsonFormat.kt rename to src/main/kotlin/com/mingliqiye/utils/annotation/UUIDJsonFormat.kt index 7196474..7492fab 100644 --- a/src/main/kotlin/com/mingliqiye/utils/uuid/UUIDJsonFormat.kt +++ b/src/main/kotlin/com/mingliqiye/utils/annotation/UUIDJsonFormat.kt @@ -16,14 +16,21 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile UUIDJsonFormat.kt - * LastUpdate 2026-02-04 22:14:47 + * LastUpdate 2026-02-07 08:05:58 * UpdateUser MingLiPro */ -package com.mingliqiye.utils.uuid +package com.mingliqiye.utils.annotation import com.mingliqiye.utils.base.BaseType +import com.mingliqiye.utils.uuid.UUIDFormatType +/** + * UUID JSON格式化注解,用于指定UUID字段在JSON序列化/反序列化时的格式 + * + * @property value UUID格式类型,默认为 NO_UPPER_SPACE + * @property base 基数类型,默认为 BASE16 + */ @Target(AnnotationTarget.FIELD) annotation class UUIDJsonFormat( val value: UUIDFormatType = UUIDFormatType.NO_UPPER_SPACE, diff --git a/src/main/kotlin/com/mingliqiye/utils/annotation/processor/AutoServiceProcessor.kt b/src/main/kotlin/com/mingliqiye/utils/annotation/processor/AutoServiceProcessor.kt new file mode 100644 index 0000000..aa549a9 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/annotation/processor/AutoServiceProcessor.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile AutoServiceProcessor.kt + * LastUpdate 2026-02-08 01:24:16 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.annotation.processor + +import com.mingliqiye.utils.annotation.AutoService +import com.mingliqiye.utils.io.IO.println +import javax.annotation.processing.AbstractProcessor +import javax.annotation.processing.ProcessingEnvironment +import javax.annotation.processing.RoundEnvironment +import javax.annotation.processing.SupportedAnnotationTypes +import javax.lang.model.element.TypeElement +import javax.lang.model.type.MirroredTypesException +import javax.lang.model.type.TypeMirror +import javax.tools.StandardLocation + + +@SupportedAnnotationTypes("com.mingliqiye.utils.annotation.AutoService") +class AutoServiceProcessor : AbstractProcessor() { + override fun init(processingEnv: ProcessingEnvironment) { + super.init(processingEnv) + } + + override fun process( + annotations: Set, + roundEnv: RoundEnvironment + ): Boolean { + + val service = mutableMapOf>() + val elements = roundEnv.getElementsAnnotatedWith(AutoService::class.java) + if (elements.isEmpty()) return false + for (element in elements) { + val autoServiceAnnotation = element.getAnnotation(AutoService::class.java) + var asd = (try { + autoServiceAnnotation!!.value.map { it.java.name } + } catch (e: MirroredTypesException) { + e.typeMirrors.map(TypeMirror::toString) + }) + + if (asd.isEmpty()) { + if (element is TypeElement) { + asd = element.interfaces.map { it.toString() } + } + } + for (data in asd) { + var ldata: MutableList? = service[data] + if (ldata == null) { + ldata = mutableListOf() + service[data] = ldata + } + ldata.add(element.toString()) + } + } + processClassAnnotation(service) + return true + } + + fun processClassAnnotation(map: Map>) { + map.forEach { (interfaceName, implementations) -> + if (implementations.isEmpty()) { + println("警告: $interfaceName 的实现列表为空!") + return@forEach + } + try { + val serviceFile = processingEnv.filer.createResource( + StandardLocation.CLASS_OUTPUT, + "", + "META-INF/services/$interfaceName" + ) + val content = StringBuilder() + serviceFile.openWriter().use { writer -> + implementations.forEach { impl -> + val line = "$impl\n" + content.append(line) + writer.write(line) + } + writer.flush() + } + + } catch (e: Exception) { + println("文件创建失败: ${e.javaClass.name} - ${e.message}") + e.printStackTrace() + } + } + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/array/ArrayUtils.kt b/src/main/kotlin/com/mingliqiye/utils/array/ArrayUtils.kt index a606e29..2aa5150 100644 --- a/src/main/kotlin/com/mingliqiye/utils/array/ArrayUtils.kt +++ b/src/main/kotlin/com/mingliqiye/utils/array/ArrayUtils.kt @@ -16,373 +16,327 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile ArrayUtils.kt - * LastUpdate 2026-01-28 08:03:28 + * LastUpdate 2026-02-06 14:01:56 * UpdateUser MingLiPro */ + + @file:JvmName("ArrayUtils") package com.mingliqiye.utils.array +import com.mingliqiye.utils.base.BASE16 + + /** - * 复制数组元素到目标数组 - * @param from 源数组 - * @param to 目标数组 - * @param fromPos 源数组起始位置,默认为0 - * @param toPos 目标数组起始位置,默认为0 - * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - * @throws ArrayStoreException 当类型不匹配时抛出 + * 将字节数组转换为十六进制字符串 + * + * @return 大写的十六进制字符串,每两个字符表示一个字节 + * @since 4.6.0 + * @see toHexByteArray */ -@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) -fun arrayCopy(from: Array, to: Array, fromPos: Int = 0, toPos: Int = 0, length: Int = from.size - fromPos) { - System.arraycopy(from, fromPos, to, toPos, length) +fun ByteArray.toHexString(): String = BASE16.encode(this).uppercase() + +/** + * 将十六进制字符串转换为字节数组 + * + * @return 对应的字节数组 + * @throws IllegalArgumentException 如果字符串包含非十六进制字符 + * @since 4.6.0 + * @see toHexString + */ +fun String.toHexByteArray(): ByteArray = BASE16.decode(this.lowercase()) + +/* ==================== 数组复制扩展函数 ==================== */ + +/** + * 将当前数组的指定范围复制到目标数组 + * + * @param dest 目标数组 + * @param srcPos 源数组起始位置(包含) + * @param distPos 目标数组起始位置(包含) + * @param len 要复制的元素数量 + * @return 源数组自身(支持链式调用) + * @throws IndexOutOfBoundsException 如果索引超出范围 + * @throws IllegalArgumentException 如果参数无效 + * @since 4.6.0 + */ +@JvmName("arrayCopy") +fun Array.copyTo(dest: Array, srcPos: Int, distPos: Int, len: Int): Array = this.also { + System.arraycopy(this, srcPos, dest, distPos, len) } /** - * 扩展函数:将当前数组元素复制到目标数组 - * @param to 目标数组 - * @param fromPos 源数组起始位置,默认为0 - * @param toPos 目标数组起始位置,默认为0 - * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - * @throws ArrayStoreException 当类型不匹配时抛出 + * 创建新数组并复制当前数组的指定范围 + * + * @param srcPos 源数组起始位置(包含) + * @param distPos 目标数组起始位置(包含) + * @param len 要复制的元素数量,也作为新数组的长度 + * @return 新创建的数组 + * @throws IndexOutOfBoundsException 如果索引超出范围 + * @since 4.6.2 */ -@JvmSynthetic -@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) -fun Array.copyTo(to: Array, fromPos: Int = 0, toPos: Int = 0, length: Int = this.size - fromPos) { - System.arraycopy(this, fromPos, to, toPos, length) +@JvmName("arrayCopy") +fun Array.copyTo(srcPos: Int, distPos: Int, len: Int): Array = + java.lang.reflect.Array.newInstance(this.javaClass.componentType, len).also { + System.arraycopy(this, srcPos, it, distPos, len) + }.let { + @Suppress("UNCHECKED_CAST") + it as Array + } + +/** + * 将源数组的指定范围复制到当前数组 + * + * @param src 源数组 + * @param srcPos 源数组起始位置(包含) + * @param distPos 当前数组起始位置(包含) + * @param len 要复制的元素数量 + * @return 当前数组自身(支持链式调用) + * @throws IndexOutOfBoundsException 如果索引超出范围 + * @since 4.6.1 + */ +@JvmName("arrayCopyFrom") +fun Array.copyFrom(src: Array, srcPos: Int, distPos: Int, len: Int): Array = this.also { + System.arraycopy(src, srcPos, this, distPos, len) +} + +/* ==================== BooleanArray 扩展 ==================== */ + +/** + * @see Array.copyTo + * @since 4.6.0 + */ +@JvmName("arrayCopy") +fun BooleanArray.copyTo(dest: BooleanArray, srcPos: Int, distPos: Int, len: Int): BooleanArray = this.also { + System.arraycopy(this, srcPos, dest, distPos, len) } /** - * 复制整个数组到目标数组(重载版本) - * @param from 源数组 - * @param to 目标数组 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - * @throws ArrayStoreException 当类型不匹配时抛出 + * @see Array.copyTo + * @since 4.6.2 */ -@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) -fun arrayCopy(from: Array, to: Array) { - return arrayCopy(from, to, 0, 0, from.size) +@JvmName("arrayCopy") +fun BooleanArray.copyTo(srcPos: Int, distPos: Int, len: Int): BooleanArray = + BooleanArray(len).also { newArray -> + System.arraycopy(this, srcPos, newArray, distPos, len) + } + +/** + * @see Array.copyFrom + * @since 4.6.1 + */ +@JvmName("arrayCopyFrom") +fun BooleanArray.copyFrom(src: BooleanArray, srcPos: Int, distPos: Int, len: Int): BooleanArray = this.also { + System.arraycopy(src, srcPos, this, distPos, len) +} + +/* ==================== ByteArray 扩展 ==================== */ + +/** + * @see Array.copyTo + * @since 4.6.0 + */ +@JvmName("arrayCopy") +fun ByteArray.copyTo(dest: ByteArray, srcPos: Int, distPos: Int, len: Int): ByteArray = this.also { + System.arraycopy(this, srcPos, dest, distPos, len) } /** - * 复制字节数组元素到目标数组 - * @param from 源字节数组 - * @param to 目标字节数组 - * @param fromPos 源数组起始位置,默认为0 - * @param toPos 目标数组起始位置,默认为0 - * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + * @see Array.copyTo + * @since 4.6.2 */ -@Throws(IndexOutOfBoundsException::class) -fun arrayCopy(from: ByteArray, to: ByteArray, fromPos: Int = 0, toPos: Int = 0, length: Int = from.size - fromPos) { - System.arraycopy(from, fromPos, to, toPos, length) +@JvmName("arrayCopy") +fun ByteArray.copyTo(srcPos: Int, distPos: Int, len: Int): ByteArray = + ByteArray(len).also { newArray -> + System.arraycopy(this, srcPos, newArray, distPos, len) + } + +/** + * @see Array.copyFrom + * @since 4.6.1 + */ +@JvmName("arrayCopyFrom") +fun ByteArray.copyFrom(src: ByteArray, srcPos: Int, distPos: Int, len: Int): ByteArray = this.also { + System.arraycopy(src, srcPos, this, distPos, len) +} + +/* ==================== CharArray 扩展 ==================== */ + +/** + * @see Array.copyTo + * @since 4.6.0 + */ +@JvmName("arrayCopy") +fun CharArray.copyTo(dest: CharArray, srcPos: Int, distPos: Int, len: Int): CharArray = this.also { + System.arraycopy(this, srcPos, dest, distPos, len) } /** - * 扩展函数:将当前字节数组元素复制到目标数组 - * @param to 目标字节数组 - * @param fromPos 源数组起始位置,默认为0 - * @param toPos 目标数组起始位置,默认为0 - * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - * @throws ArrayStoreException 当类型不匹配时抛出 + * @see Array.copyTo + * @since 4.6.2 */ -@JvmSynthetic -@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) -fun ByteArray.copyTo(to: ByteArray, fromPos: Int = 0, toPos: Int = 0, length: Int = this.size - fromPos) { - System.arraycopy(this, fromPos, to, toPos, length) +@JvmName("arrayCopy") +fun CharArray.copyTo(srcPos: Int, distPos: Int, len: Int): CharArray = + CharArray(len).also { newArray -> + System.arraycopy(this, srcPos, newArray, distPos, len) + } + +/** + * @see Array.copyFrom + * @since 4.6.1 + */ +@JvmName("arrayCopyFrom") +fun CharArray.copyFrom(src: CharArray, srcPos: Int, distPos: Int, len: Int): CharArray = this.also { + System.arraycopy(src, srcPos, this, distPos, len) +} + +/* ==================== DoubleArray 扩展 ==================== */ + +/** + * @see Array.copyTo + * @since 4.6.0 + */ +@JvmName("arrayCopy") +fun DoubleArray.copyTo(dest: DoubleArray, srcPos: Int, distPos: Int, len: Int): DoubleArray = this.also { + System.arraycopy(this, srcPos, dest, distPos, len) } /** - * 复制整个字节数组到目标数组(重载版本) - * @param from 源字节数组 - * @param to 目标字节数组 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + * @see Array.copyTo + * @since 4.6.2 */ -@Throws(IndexOutOfBoundsException::class) -fun arrayCopy(from: ByteArray, to: ByteArray) { - arrayCopy(from, to, 0, 0, from.size) +@JvmName("arrayCopy") +fun DoubleArray.copyTo(srcPos: Int, distPos: Int, len: Int): DoubleArray = + DoubleArray(len).also { newArray -> + System.arraycopy(this, srcPos, newArray, distPos, len) + } + +/** + * @see Array.copyFrom + * @since 4.6.1 + */ +@JvmName("arrayCopyFrom") +fun DoubleArray.copyFrom(src: DoubleArray, srcPos: Int, distPos: Int, len: Int): DoubleArray = this.also { + System.arraycopy(src, srcPos, this, distPos, len) +} + +/* ==================== FloatArray 扩展 ==================== */ + +/** + * @see Array.copyTo + * @since 4.6.0 + */ +@JvmName("arrayCopy") +fun FloatArray.copyTo(dest: FloatArray, srcPos: Int, distPos: Int, len: Int): FloatArray = this.also { + System.arraycopy(this, srcPos, dest, distPos, len) } /** - * 复制短整型数组元素到目标数组 - * @param from 源短整型数组 - * @param to 目标短整型数组 - * @param fromPos 源数组起始位置,默认为0 - * @param toPos 目标数组起始位置,默认为0 - * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + * @see Array.copyTo + * @since 4.6.2 */ -@Throws(IndexOutOfBoundsException::class) -fun arrayCopy(from: ShortArray, to: ShortArray, fromPos: Int = 0, toPos: Int = 0, length: Int = from.size - fromPos) { - System.arraycopy(from, fromPos, to, toPos, length) +@JvmName("arrayCopy") +fun FloatArray.copyTo(srcPos: Int, distPos: Int, len: Int): FloatArray = + FloatArray(len).also { newArray -> + System.arraycopy(this, srcPos, newArray, distPos, len) + } + +/** + * @see Array.copyFrom + * @since 4.6.1 + */ +@JvmName("arrayCopyFrom") +fun FloatArray.copyFrom(src: FloatArray, srcPos: Int, distPos: Int, len: Int): FloatArray = this.also { + System.arraycopy(src, srcPos, this, distPos, len) +} + +/* ==================== IntArray 扩展 ==================== */ + +/** + * @see Array.copyTo + * @since 4.6.0 + */ +@JvmName("arrayCopy") +fun IntArray.copyTo(dest: IntArray, srcPos: Int, distPos: Int, len: Int): IntArray = this.also { + System.arraycopy(this, srcPos, dest, distPos, len) } /** - * 扩展函数:将当前短整型数组元素复制到目标数组 - * @param to 目标短整型数组 - * @param fromPos 源数组起始位置,默认为0 - * @param toPos 目标数组起始位置,默认为0 - * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - * @throws ArrayStoreException 当类型不匹配时抛出 + * @see Array.copyTo + * @since 4.6.2 */ -@JvmSynthetic -@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) -fun ShortArray.copyTo(to: ShortArray, fromPos: Int = 0, toPos: Int = 0, length: Int = this.size - fromPos) { - System.arraycopy(this, fromPos, to, toPos, length) +@JvmName("arrayCopy") +fun IntArray.copyTo(srcPos: Int, distPos: Int, len: Int): IntArray = + IntArray(len).also { newArray -> + System.arraycopy(this, srcPos, newArray, distPos, len) + } + +/** + * @see Array.copyFrom + * @since 4.6.1 + */ +@JvmName("arrayCopyFrom") +fun IntArray.copyFrom(src: IntArray, srcPos: Int, distPos: Int, len: Int): IntArray = this.also { + System.arraycopy(src, srcPos, this, distPos, len) +} + +/* ==================== LongArray 扩展 ==================== */ + +/** + * @see Array.copyTo + * @since 4.6.0 + */ +@JvmName("arrayCopy") +fun LongArray.copyTo(dest: LongArray, srcPos: Int, distPos: Int, len: Int): LongArray = this.also { + System.arraycopy(this, srcPos, dest, distPos, len) } /** - * 复制整个短整型数组到目标数组(重载版本) - * @param from 源短整型数组 - * @param to 目标短整型数组 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + * @see Array.copyTo + * @since 4.6.2 */ -@Throws(IndexOutOfBoundsException::class) -fun arrayCopy(from: ShortArray, to: ShortArray) { - arrayCopy(from, to, 0, 0, from.size) +@JvmName("arrayCopy") +fun LongArray.copyTo(srcPos: Int, distPos: Int, len: Int): LongArray = + LongArray(len).also { newArray -> + System.arraycopy(this, srcPos, newArray, distPos, len) + } + +/** + * @see Array.copyFrom + * @since 4.6.1 + */ +@JvmName("arrayCopyFrom") +fun LongArray.copyFrom(src: LongArray, srcPos: Int, distPos: Int, len: Int): LongArray = this.also { + System.arraycopy(src, srcPos, this, distPos, len) +} + +/* ==================== ShortArray 扩展 ==================== */ + +/** + * @see Array.copyTo + * @since 4.6.0 + */ +@JvmName("arrayCopy") +fun ShortArray.copyTo(dest: ShortArray, srcPos: Int, distPos: Int, len: Int): ShortArray = this.also { + System.arraycopy(this, srcPos, dest, distPos, len) } /** - * 复制整型数组元素到目标数组 - * @param from 源整型数组 - * @param to 目标整型数组 - * @param fromPos 源数组起始位置,默认为0 - * @param toPos 目标数组起始位置,默认为0 - * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 + * @see Array.copyTo + * @since 4.6.2 */ -@Throws(IndexOutOfBoundsException::class) -fun arrayCopy(from: IntArray, to: IntArray, fromPos: Int = 0, toPos: Int = 0, length: Int = from.size - fromPos) { - System.arraycopy(from, fromPos, to, toPos, length) -} +@JvmName("arrayCopy") +fun ShortArray.copyTo(srcPos: Int, distPos: Int, len: Int): ShortArray = + ShortArray(len).also { newArray -> + System.arraycopy(this, srcPos, newArray, distPos, len) + } /** - * 扩展函数:将当前整型数组元素复制到目标数组 - * @param to 目标整型数组 - * @param fromPos 源数组起始位置,默认为0 - * @param toPos 目标数组起始位置,默认为0 - * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - * @throws ArrayStoreException 当类型不匹配时抛出 + * @see Array.copyFrom + * @since 4.6.1 */ -@JvmSynthetic -@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) -fun IntArray.copyTo(to: IntArray, fromPos: Int = 0, toPos: Int = 0, length: Int = this.size - fromPos) { - System.arraycopy(this, fromPos, to, toPos, length) -} - -/** - * 复制整个整型数组到目标数组(重载版本) - * @param from 源整型数组 - * @param to 目标整型数组 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - */ -@Throws(IndexOutOfBoundsException::class) -fun arrayCopy(from: IntArray, to: IntArray) { - arrayCopy(from, to, 0, 0, from.size) -} - -/** - * 复制长整型数组元素到目标数组 - * @param from 源长整型数组 - * @param to 目标长整型数组 - * @param fromPos 源数组起始位置,默认为0 - * @param toPos 目标数组起始位置,默认为0 - * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - */ -@Throws(IndexOutOfBoundsException::class) -fun arrayCopy(from: LongArray, to: LongArray, fromPos: Int = 0, toPos: Int = 0, length: Int = from.size - fromPos) { - System.arraycopy(from, fromPos, to, toPos, length) -} - -/** - * 扩展函数:将当前长整型数组元素复制到目标数组 - * @param to 目标长整型数组 - * @param fromPos 源数组起始位置,默认为0 - * @param toPos 目标数组起始位置,默认为0 - * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - * @throws ArrayStoreException 当类型不匹配时抛出 - */ -@JvmSynthetic -@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) -fun LongArray.copyTo(to: LongArray, fromPos: Int = 0, toPos: Int = 0, length: Int = this.size - fromPos) { - System.arraycopy(this, fromPos, to, toPos, length) -} - -/** - * 复制整个长整型数组到目标数组(重载版本) - * @param from 源长整型数组 - * @param to 目标长整型数组 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - */ -@Throws(IndexOutOfBoundsException::class) -fun arrayCopy(from: LongArray, to: LongArray) { - arrayCopy(from, to, 0, 0, from.size) -} - -/** - * 复制浮点型数组元素到目标数组 - * @param from 源浮点型数组 - * @param to 目标浮点型数组 - * @param fromPos 源数组起始位置,默认为0 - * @param toPos 目标数组起始位置,默认为0 - * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - */ -@Throws(IndexOutOfBoundsException::class) -fun arrayCopy(from: FloatArray, to: FloatArray, fromPos: Int = 0, toPos: Int = 0, length: Int = from.size - fromPos) { - System.arraycopy(from, fromPos, to, toPos, length) -} - -/** - * 扩展函数:将当前浮点型数组元素复制到目标数组 - * @param to 目标浮点型数组 - * @param fromPos 源数组起始位置,默认为0 - * @param toPos 目标数组起始位置,默认为0 - * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - * @throws ArrayStoreException 当类型不匹配时抛出 - */ -@JvmSynthetic -@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) -fun FloatArray.copyTo(to: FloatArray, fromPos: Int = 0, toPos: Int = 0, length: Int = this.size - fromPos) { - System.arraycopy(this, fromPos, to, toPos, length) -} - -/** - * 复制整个浮点型数组到目标数组(重载版本) - * @param from 源浮点型数组 - * @param to 目标浮点型数组 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - */ -@Throws(IndexOutOfBoundsException::class) -fun arrayCopy(from: FloatArray, to: FloatArray) { - arrayCopy(from, to, 0, 0, from.size) -} - -/** - * 复制双精度浮点型数组元素到目标数组 - * @param from 源双精度浮点型数组 - * @param to 目标双精度浮点型数组 - * @param fromPos 源数组起始位置,默认为0 - * @param toPos 目标数组起始位置,默认为0 - * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - */ -@Throws(IndexOutOfBoundsException::class) -fun arrayCopy(from: DoubleArray, to: DoubleArray, fromPos: Int = 0, toPos: Int = 0, length: Int = from.size - fromPos) { - System.arraycopy(from, fromPos, to, toPos, length) -} - -/** - * 扩展函数:将当前双精度浮点型数组元素复制到目标数组 - * @param to 目标双精度浮点型数组 - * @param fromPos 源数组起始位置,默认为0 - * @param toPos 目标数组起始位置,默认为0 - * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - * @throws ArrayStoreException 当类型不匹配时抛出 - */ -@JvmSynthetic -@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) -fun DoubleArray.copyTo(to: DoubleArray, fromPos: Int = 0, toPos: Int = 0, length: Int = this.size - fromPos) { - System.arraycopy(this, fromPos, to, toPos, length) -} - -/** - * 复制整个双精度浮点型数组到目标数组(重载版本) - * @param from 源双精度浮点型数组 - * @param to 目标双精度浮点型数组 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - */ -@Throws(IndexOutOfBoundsException::class) -fun arrayCopy(from: DoubleArray, to: DoubleArray) { - arrayCopy(from, to, 0, 0, from.size) -} - -/** - * 复制布尔型数组元素到目标数组 - * @param from 源布尔型数组 - * @param to 目标布尔型数组 - * @param fromPos 源数组起始位置,默认为0 - * @param toPos 目标数组起始位置,默认为0 - * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - */ -@Throws(IndexOutOfBoundsException::class) -fun arrayCopy( - from: BooleanArray, to: BooleanArray, fromPos: Int = 0, toPos: Int = 0, length: Int = from.size - fromPos -) { - System.arraycopy(from, fromPos, to, toPos, length) -} - -/** - * 扩展函数:将当前布尔型数组元素复制到目标数组 - * @param to 目标布尔型数组 - * @param fromPos 源数组起始位置,默认为0 - * @param toPos 目标数组起始位置,默认为0 - * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - * @throws ArrayStoreException 当类型不匹配时抛出 - */ -@JvmSynthetic -@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) -fun BooleanArray.copyTo(to: BooleanArray, fromPos: Int = 0, toPos: Int = 0, length: Int = this.size - fromPos) { - System.arraycopy(this, fromPos, to, toPos, length) -} - -/** - * 复制整个布尔型数组到目标数组(重载版本) - * @param from 源布尔型数组 - * @param to 目标布尔型数组 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - */ -@Throws(IndexOutOfBoundsException::class) -fun arrayCopy(from: BooleanArray, to: BooleanArray) { - arrayCopy(from, to, 0, 0, from.size) -} - -/** - * 复制字符型数组元素到目标数组 - * @param from 源字符型数组 - * @param to 目标字符型数组 - * @param fromPos 源数组起始位置,默认为0 - * @param toPos 目标数组起始位置,默认为0 - * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - */ -@Throws(IndexOutOfBoundsException::class) -fun arrayCopy(from: CharArray, to: CharArray, fromPos: Int = 0, toPos: Int = 0, length: Int = from.size - fromPos) { - System.arraycopy(from, fromPos, to, toPos, length) -} - -/** - * 扩展函数:将当前字符型数组元素复制到目标数组 - * @param to 目标字符型数组 - * @param fromPos 源数组起始位置,默认为0 - * @param toPos 目标数组起始位置,默认为0 - * @param length 要复制的元素数量,默认为源数组从起始位置到末尾的长度 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - * @throws ArrayStoreException 当类型不匹配时抛出 - */ -@JvmSynthetic -@Throws(IndexOutOfBoundsException::class, ArrayStoreException::class) -fun CharArray.copyTo(to: CharArray, fromPos: Int = 0, toPos: Int = 0, length: Int = this.size - fromPos) { - System.arraycopy(this, fromPos, to, toPos, length) -} - -/** - * 复制整个字符型数组到目标数组(重载版本) - * @param from 源字符型数组 - * @param to 目标字符型数组 - * @throws IndexOutOfBoundsException 当索引超出数组边界时抛出 - */ -@Throws(IndexOutOfBoundsException::class) -fun arrayCopy(from: CharArray, to: CharArray) { - arrayCopy(from, to, 0, 0, from.size) +@JvmName("arrayCopyFrom") +fun ShortArray.copyFrom(src: ShortArray, srcPos: Int, distPos: Int, len: Int): ShortArray = this.also { + System.arraycopy(src, srcPos, this, distPos, len) } diff --git a/src/main/kotlin/com/mingliqiye/utils/base/Base10.kt b/src/main/kotlin/com/mingliqiye/utils/base/Base10.kt new file mode 100644 index 0000000..8f3ff98 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/base/Base10.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile Base10.kt + * LastUpdate 2026-02-08 03:08:10 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.base + +import java.math.BigInteger + +internal class Base10 : BaseCodec { + + /** + * 将字节数组编码为Base10字符串(十进制) + * + * @param bytes 需要编码的字节数组 + * @return 编码后的Base10字符串(十进制数字) + */ + override fun encode(bytes: ByteArray): String { + if (bytes.isEmpty()) return "0" + + // 将字节数组转换为正的大整数 + val bigInt = BigInteger(1, bytes) // 参数1表示正数 + return bigInt.toString(10) // 转换为10进制字符串 + } + + /** + * 将Base10字符串解码为字节数组 + * + * @param string 需要解码的Base10字符串(十进制数字) + * @return 解码后的字节数组 + */ + override fun decode(string: String): ByteArray { + // 验证输入是否为有效的十进制数字 + if (!string.matches(Regex("\\d+"))) { + throw IllegalArgumentException("Base10字符串只能包含数字0-9") + } + + val bigInt = BigInteger(string, 10) // 从10进制解析 + + // 转换为字节数组,并确保保留前导零 + var byteArray = bigInt.toByteArray() + + // BigInteger.toByteArray() 可能会添加一个符号字节 + // 对于正数,如果第一个字节是0,需要移除它 + if (byteArray.isNotEmpty() && byteArray[0] == 0.toByte()) { + byteArray = byteArray.copyOfRange(1, byteArray.size) + } + + return byteArray + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/base/Base16.kt b/src/main/kotlin/com/mingliqiye/utils/base/Base16.kt index 1f0eb7b..f668711 100644 --- a/src/main/kotlin/com/mingliqiye/utils/base/Base16.kt +++ b/src/main/kotlin/com/mingliqiye/utils/base/Base16.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile Base16.kt - * LastUpdate 2025-09-17 10:56:07 + * LastUpdate 2026-02-08 03:08:10 * UpdateUser MingLiPro */ @@ -26,7 +26,7 @@ package com.mingliqiye.utils.base * Base16编解码器实现类 * 提供字节数组与十六进制字符串之间的相互转换功能 */ -class Base16 : BaseCodec { +internal class Base16 : BaseCodec { /** * 将字节数组编码为十六进制字符串 * @param bytes 待编码的字节数组 diff --git a/src/main/kotlin/com/mingliqiye/utils/base/Base2.kt b/src/main/kotlin/com/mingliqiye/utils/base/Base2.kt new file mode 100644 index 0000000..c5fc9e9 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/base/Base2.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile Base2.kt + * LastUpdate 2026-02-08 03:06:23 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.base + +internal class Base2 : BaseCodec { + override fun encode(bytes: ByteArray): String { + if (bytes.isEmpty()) return "" + val result = StringBuilder() + for ((index, byte) in bytes.withIndex()) { + val unsignedByte = byte.toInt() and 0xFF + val binary = unsignedByte.toString(2).padStart(8, '0') + result.append(binary) + } + return result.toString() + } + + override fun decode(string: String): ByteArray { + if (string.length % 8 != 0) { + throw IllegalArgumentException( + "BASE1字符串长度必须是8的倍数,当前长度: ${string.length}" + ) + } + if (!string.matches(Regex("[01]+"))) { + throw IllegalArgumentException( + "BASE1字符串只能包含字符'0'和'1'" + ) + } + val byteCount = string.length / 8 + val result = ByteArray(byteCount) + for (i in 0 until byteCount) { + val startIndex = i * 8 + val endIndex = startIndex + 8 + val binaryStr = string.substring(startIndex, endIndex) + val byteValue = binaryStr.toInt(2) + result[i] = byteValue.toByte() + } + return result + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/base/Base256.kt b/src/main/kotlin/com/mingliqiye/utils/base/Base256.kt index 77d9766..1c264ec 100644 --- a/src/main/kotlin/com/mingliqiye/utils/base/Base256.kt +++ b/src/main/kotlin/com/mingliqiye/utils/base/Base256.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile Base256.kt - * LastUpdate 2025-09-20 14:01:29 + * LastUpdate 2026-02-08 03:08:10 * UpdateUser MingLiPro */ @@ -30,7 +30,7 @@ package com.mingliqiye.utils.base * !#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~§±×÷←↑→↓⇒⇔∀∃∅∆∇∈∉∋∌∏∑−∓∕∗∘∙√∛∜∞∟∠∣∥∧∨∩∪∫∬∭∮∯∰∱∲∳∴∵∶∷≈≠≡≤≥≦≧≪≫≺≻⊂⊃⊆⊇⊈⊉⊊⊋⊕⊖⊗⊘⊙⊚⊛⊜⊝⊞⊟⊠⊡⊢⊣⊤⊥⊦⊧⊨⊩⊪⊫⊬⊭⊮⊯⋀⋁⋂⋃⋄⋅⋆⋇⋈⋉⋊⋋⋌⋍⋎⋏⋐⋑⋒⋓⋔⋕⋖⋗⋘⋙⋚⋛⋜⋝⋞⋟⋠⋡⋢⋣⋤⋥⋦⋧⋨⋩▁▂▃▄▅▆▇█▉▊▋▌▍▎▏▐░▒▓▔▕▖▗▘▙ * */ -class Base256 : BaseCodec { +internal class Base256 : BaseCodec { companion object { val code = arrayOf( diff --git a/src/main/kotlin/com/mingliqiye/utils/base/Base64.kt b/src/main/kotlin/com/mingliqiye/utils/base/Base64.kt index ca86f1c..6e3efcd 100644 --- a/src/main/kotlin/com/mingliqiye/utils/base/Base64.kt +++ b/src/main/kotlin/com/mingliqiye/utils/base/Base64.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile Base64.kt - * LastUpdate 2025-09-17 10:56:32 + * LastUpdate 2026-02-08 03:08:10 * UpdateUser MingLiPro */ @@ -26,7 +26,7 @@ package com.mingliqiye.utils.base * Base64编解码工具类 * 提供Base64编码和解码功能的实现 */ -class Base64 : BaseCodec { +internal class Base64 : BaseCodec { /* * Base64编码器实例 diff --git a/src/main/kotlin/com/mingliqiye/utils/base/Base91.kt b/src/main/kotlin/com/mingliqiye/utils/base/Base91.kt index ba31a2b..ef40578 100644 --- a/src/main/kotlin/com/mingliqiye/utils/base/Base91.kt +++ b/src/main/kotlin/com/mingliqiye/utils/base/Base91.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile Base91.kt - * LastUpdate 2025-09-19 20:08:46 + * LastUpdate 2026-02-08 03:08:10 * UpdateUser MingLiPro */ @@ -27,7 +27,7 @@ package com.mingliqiye.utils.base * * Base91 是一种高效的二进制到文本的编码方式,相较于 Base64,它使用更少的字符来表示相同的数据。 */ -class Base91 : BaseCodec { +internal class Base91 : BaseCodec { companion object { /** diff --git a/src/main/kotlin/com/mingliqiye/utils/base/BaseUtils.kt b/src/main/kotlin/com/mingliqiye/utils/base/BaseUtils.kt index ac4d998..1960288 100644 --- a/src/main/kotlin/com/mingliqiye/utils/base/BaseUtils.kt +++ b/src/main/kotlin/com/mingliqiye/utils/base/BaseUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 mingliqiye + * Copyright 2026 mingliqiye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile BaseUtils.kt - * LastUpdate 2025-09-19 20:18:09 + * LastUpdate 2026-02-08 03:00:37 * UpdateUser MingLiPro */ @@ -24,40 +24,49 @@ package com.mingliqiye.utils.base -/** - * Base64编解码器实例 - * 使用懒加载方式初始化Base64编解码器对象 - * 保证线程安全且只在首次访问时创建实例 - */ -val BASE64: BaseCodec by lazy { - Base64() -} - -/** - * Base91编解码器实例 - * 使用懒加载方式初始化Base91编解码器对象 - * 保证线程安全且只在首次访问时创建实例 - */ -val BASE91: BaseCodec by lazy { - Base91() -} - -/** - * Base91编解码器实例 - * 使用懒加载方式初始化Base91编解码器对象 - * 保证线程安全且只在首次访问时创建实例 - */ -val BASE16: BaseCodec by lazy { - Base16() -} - -/** - * Base256编解码器实例 - * 使用懒加载方式初始化Base256编解码器对象 - * 保证线程安全且只在首次访问时创建实例 - */ -val BASE256: BaseCodec by lazy { - Base256() -} +import com.mingliqiye.utils.base.code.Base10 +import com.mingliqiye.utils.base.code.Base16 +import com.mingliqiye.utils.base.code.Base256 +import com.mingliqiye.utils.base.code.Base64 +import com.mingliqiye.utils.base.code.Base91 +@Deprecated( + "重命名", replaceWith = ReplaceWith( + expression = "Base10", imports = ["com.mingliqiye.utils.base.code"] + ) +) +val BASE10: BaseCodec + get() = Base10 + +@Deprecated( + "重命名", replaceWith = ReplaceWith( + expression = "Base16", imports = ["com.mingliqiye.utils.base.code"] + ) +) +val BASE16: BaseCodec + get() = Base16 + +@Deprecated( + "重命名", replaceWith = ReplaceWith( + expression = "Base64", imports = ["com.mingliqiye.utils.base.code"] + ) +) +val BASE64: BaseCodec + get() = Base64 + +@Deprecated( + "重命名", replaceWith = ReplaceWith( + expression = "Base91", imports = ["com.mingliqiye.utils.base.code"] + ) +) +val BASE91: BaseCodec + get() = Base91 + +@Deprecated( + "重命名", replaceWith = ReplaceWith( + expression = "Base256", imports = ["com.mingliqiye.utils.base.code"] + ) +) +val BASE256: BaseCodec + get() = Base256 diff --git a/src/main/kotlin/com/mingliqiye/utils/base/code/BaseCodes.kt b/src/main/kotlin/com/mingliqiye/utils/base/code/BaseCodes.kt new file mode 100644 index 0000000..9e6e43e --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/base/code/BaseCodes.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile BaseCodes.kt + * LastUpdate 2026-02-08 03:10:03 + * UpdateUser MingLiPro + */ + +@file:JvmName("BaseCodes") + +package com.mingliqiye.utils.base.code + +import com.mingliqiye.utils.base.* + +/** + * 提供Base2编码器的懒加载实例。 + * 该编码器用于将数据编码为二进制格式。 + */ +@get:JvmName("Base2") +val Base2: BaseCodec by lazy { + Base2() +} + +/** + * 提供Base10编码器的懒加载实例。 + * 该编码器用于将数据编码为十进制格式。 + */ +@get:JvmName("Base10") +val Base10: BaseCodec by lazy { + Base10() +} + +/** + * 提供Base16编码器的懒加载实例。 + * 该编码器用于将数据编码为十六进制格式。 + */ +@get:JvmName("Base16") +val Base16: BaseCodec by lazy { + Base16() +} + +/** + * 提供Base64编码器的懒加载实例。 + * 该编码器用于将数据编码为Base64格式,常用于URL安全传输或存储。 + */ +@get:JvmName("Base64") +val Base64: BaseCodec by lazy { + Base64() +} + +/** + * 提供Base91编码器的懒加载实例。 + * 该编码器用于将数据编码为Base91格式,具有较高的压缩效率。 + */ +@get:JvmName("Base91") +val Base91: BaseCodec by lazy { + Base91() +} + +/** + * 提供Base256编码器的懒加载实例。 + * 该编码器用于将数据编码为Base256格式,适用于字节级数据处理。 + */ +@get:JvmName("Base256") +val Base256: BaseCodec by lazy { + Base256() +} diff --git a/src/main/kotlin/com/mingliqiye/utils/clone/CloneUtils.kt b/src/main/kotlin/com/mingliqiye/utils/clone/CloneUtils.kt index 22a1ed1..bb69204 100644 --- a/src/main/kotlin/com/mingliqiye/utils/clone/CloneUtils.kt +++ b/src/main/kotlin/com/mingliqiye/utils/clone/CloneUtils.kt @@ -16,15 +16,15 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile CloneUtils.kt - * LastUpdate 2026-02-04 21:00:48 + * LastUpdate 2026-02-05 14:41:27 * UpdateUser MingLiPro */ @file:JvmName("CloneUtils") package com.mingliqiye.utils.clone +import com.mingliqiye.utils.exception.JsonException import com.mingliqiye.utils.json.api.base.JsonApi -import com.mingliqiye.utils.json.api.exception.JsonException import java.io.* diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/BadGatewayException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/BadGatewayException.kt new file mode 100644 index 0000000..12383fb --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/BadGatewayException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile BadGatewayException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 502 Bad Gateway 异常。 + * + * @param message 异常信息,默认为 "Bad Gateway" + * @param cause 异常原因,默认为 null + */ +class BadGatewayException( + override val message: String? = "Bad Gateway", + override val cause: Throwable? = null, +) : HttpStatusException(502, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/BadRequestException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/BadRequestException.kt new file mode 100644 index 0000000..6740250 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/BadRequestException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile BadRequestException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 400 Bad Request 异常。 + * + * @param message 异常信息,默认为 "Bad Request" + * @param cause 异常原因,默认为 null + */ +class BadRequestException( + override val message: String? = "Bad Request", + override val cause: Throwable? = null, +) : HttpStatusException(400, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/ConflictException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/ConflictException.kt new file mode 100644 index 0000000..8027621 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/ConflictException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile ConflictException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 409 Conflict 异常。 + * + * @param message 异常信息,默认为 "Conflict" + * @param cause 异常原因,默认为 null + */ +class ConflictException( + override val message: String? = "Conflict", + override val cause: Throwable? = null, +) : HttpStatusException(409, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/EmptyJsonException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/EmptyJsonException.kt new file mode 100644 index 0000000..07a7bd7 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/EmptyJsonException.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile EmptyJsonException.kt + * LastUpdate 2026-02-07 14:45:12 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +class EmptyJsonException : JsonException { + constructor() + constructor(message: String) : super(message) + constructor(throwable: Throwable) : super(throwable) + constructor(message: String, throwable: Throwable) : super(message, throwable) + constructor( + message: String? = null, + throwable: Throwable? = null, + enableSuppression: Boolean = false, + writableStackTrace: Boolean = false + ) : super(message, throwable, enableSuppression, writableStackTrace) +} diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/ForbiddenException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/ForbiddenException.kt new file mode 100644 index 0000000..e0700c8 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/ForbiddenException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile ForbiddenException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 403 Forbidden 异常。 + * + * @param message 异常信息,默认为 "Forbidden" + * @param cause 异常原因,默认为 null + */ +class ForbiddenException( + override val message: String? = "Forbidden", + override val cause: Throwable? = null, +) : HttpStatusException(403, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/FoundException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/FoundException.kt new file mode 100644 index 0000000..5474ffc --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/FoundException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile FoundException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 302 Found 异常。 + * + * @param message 异常信息,默认为 "Found" + * @param cause 异常原因,默认为 null + */ +class FoundException( + override val message: String? = "Found", + override val cause: Throwable? = null, +) : HttpStatusException(302, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/GatewayTimeoutException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/GatewayTimeoutException.kt new file mode 100644 index 0000000..301411d --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/GatewayTimeoutException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile GatewayTimeoutException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 504 Gateway Timeout 异常。 + * + * @param message 异常信息,默认为 "Gateway Timeout" + * @param cause 异常原因,默认为 null + */ +class GatewayTimeoutException( + override val message: String? = "Gateway Timeout", + override val cause: Throwable? = null, +) : HttpStatusException(504, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/GoneException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/GoneException.kt new file mode 100644 index 0000000..bf40e6b --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/GoneException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile GoneException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 410 Gone 异常。 + * + * @param message 异常信息,默认为 "Gone" + * @param cause 异常原因,默认为 null + */ +class GoneException( + override val message: String? = "Gone", + override val cause: Throwable? = null, +) : HttpStatusException(410, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/HttpException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/HttpException.kt new file mode 100644 index 0000000..2e54cc8 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/HttpException.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile HttpException.kt + * LastUpdate 2026-02-05 14:44:15 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +open class HttpException : MingLiUtilsBaseException { + constructor() + constructor(message: String) : super(message) + constructor(throwable: Throwable) : super(throwable) + constructor(message: String, throwable: Throwable) : super(message, throwable) + constructor( + message: String? = null, + throwable: Throwable? = null, + enableSuppression: Boolean = false, + writableStackTrace: Boolean = false + ) : super(message, throwable, enableSuppression, writableStackTrace) +} diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/HttpStatusException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/HttpStatusException.kt new file mode 100644 index 0000000..0c35236 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/HttpStatusException.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile HttpStatusException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 异常的基类,继承自 [RuntimeException]。 + * + * @param statusCode HTTP 状态码 + * @param message 异常信息,默认为 null + * @param cause 异常原因,默认为 null + */ +sealed class HttpStatusException( + open val statusCode: Int, + override val message: String? = null, + override val cause: Throwable? = null +) : MingLiUtilsBaseException(message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/HttpUrlException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/HttpUrlException.kt new file mode 100644 index 0000000..e8d88d8 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/HttpUrlException.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile HttpUrlException.kt + * LastUpdate 2026-02-05 15:21:48 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +open class HttpUrlException : HttpException { + constructor() + constructor(message: String) : super(message) + constructor(throwable: Throwable) : super(throwable) + constructor(message: String, throwable: Throwable) : super(message, throwable) + constructor( + message: String? = null, + throwable: Throwable? = null, + enableSuppression: Boolean = false, + writableStackTrace: Boolean = false + ) : super(message, throwable, enableSuppression, writableStackTrace) +} diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/HttpVersionNotSupportedException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/HttpVersionNotSupportedException.kt new file mode 100644 index 0000000..3657716 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/HttpVersionNotSupportedException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile HttpVersionNotSupportedException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 505 HTTP Version Not Supported 异常。 + * + * @param message 异常信息,默认为 "HTTP Version Not Supported" + * @param cause 异常原因,默认为 null + */ +class HttpVersionNotSupportedException( + override val message: String? = "HTTP Version Not Supported", + override val cause: Throwable? = null, +) : HttpStatusException(505, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/InternalServerErrorException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/InternalServerErrorException.kt new file mode 100644 index 0000000..6b3e014 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/InternalServerErrorException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile InternalServerErrorException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 500 Internal Server Error 异常。 + * + * @param message 异常信息,默认为 "Internal Server Error" + * @param cause 异常原因,默认为 null + */ +class InternalServerErrorException( + override val message: String? = "Internal Server Error", + override val cause: Throwable? = null, +) : HttpStatusException(500, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/InvalidJsonException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/InvalidJsonException.kt new file mode 100644 index 0000000..b10c2aa --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/InvalidJsonException.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile InvalidJsonException.kt + * LastUpdate 2026-02-07 13:18:32 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +class InvalidJsonException : JsonException { + constructor() + constructor(message: String) : super(message) + constructor(throwable: Throwable) : super(throwable) + constructor(message: String, throwable: Throwable) : super(message, throwable) + constructor( + message: String? = null, + throwable: Throwable? = null, + enableSuppression: Boolean = false, + writableStackTrace: Boolean = false + ) : super(message, throwable, enableSuppression, writableStackTrace) +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/api/exception/JsonException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/JsonException.kt similarity index 58% rename from src/main/kotlin/com/mingliqiye/utils/json/api/exception/JsonException.kt rename to src/main/kotlin/com/mingliqiye/utils/exception/JsonException.kt index 4b3c831..da1b6a6 100644 --- a/src/main/kotlin/com/mingliqiye/utils/json/api/exception/JsonException.kt +++ b/src/main/kotlin/com/mingliqiye/utils/exception/JsonException.kt @@ -16,11 +16,11 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile JsonException.kt - * LastUpdate 2026-02-05 11:12:36 + * LastUpdate 2026-02-07 13:17:50 * UpdateUser MingLiPro */ -package com.mingliqiye.utils.json.api.exception +package com.mingliqiye.utils.exception /** * 自定义异常类,用于处理 JSON 相关操作中出现的错误。 @@ -30,27 +30,15 @@ package com.mingliqiye.utils.json.api.exception * 2. 包含错误信息和原因(Throwable)的构造函数。 * 3. 仅包含原因(Throwable)的构造函数。 */ -class JsonException : RuntimeException { - - /** - * 构造函数:创建一个带有指定错误信息的 [JsonException] 实例。 - * - * @param message 错误信息描述。 - */ +open class JsonException : MingLiUtilsBaseException { + constructor() constructor(message: String) : super(message) - - /** - * 构造函数:创建一个带有指定错误信息和原因的 [JsonException] 实例。 - * - * @param message 错误信息描述。 - * @param cause 导致此异常的根本原因。 - */ - constructor(message: String, cause: Throwable) : super(message, cause) - - /** - * 构造函数:创建一个由指定原因引发的 [JsonException] 实例。 - * - * @param cause 导致此异常的根本原因。 - */ - constructor(cause: Throwable) : super(cause) + constructor(throwable: Throwable) : super(throwable) + constructor(message: String, throwable: Throwable) : super(message, throwable) + constructor( + message: String? = null, + throwable: Throwable? = null, + enableSuppression: Boolean = false, + writableStackTrace: Boolean = false + ) : super(message, throwable, enableSuppression, writableStackTrace) } diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/JsonObjectCastException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/JsonObjectCastException.kt new file mode 100644 index 0000000..848c187 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/JsonObjectCastException.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile JsonObjectCastException.kt + * LastUpdate 2026-02-07 15:11:42 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +class JsonObjectCastException : JsonException { + constructor() + constructor(message: String) : super(message) + constructor(throwable: Throwable) : super(throwable) + constructor(message: String, throwable: Throwable) : super(message, throwable) + constructor( + message: String? = null, + throwable: Throwable? = null, + enableSuppression: Boolean = false, + writableStackTrace: Boolean = false + ) : super(message, throwable, enableSuppression, writableStackTrace) +} diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/JsonParserException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/JsonParserException.kt new file mode 100644 index 0000000..641da0a --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/JsonParserException.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile JsonParserException.kt + * LastUpdate 2026-02-07 14:45:12 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +class JsonParserException : JsonException { + constructor() + constructor(message: String) : super(message) + constructor(throwable: Throwable) : super(throwable) + constructor(message: String, throwable: Throwable) : super(message, throwable) + constructor( + message: String? = null, + throwable: Throwable? = null, + enableSuppression: Boolean = false, + writableStackTrace: Boolean = false + ) : super(message, throwable, enableSuppression, writableStackTrace) +} diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/JsonTypeException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/JsonTypeException.kt new file mode 100644 index 0000000..8d9a982 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/JsonTypeException.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile JsonTypeException.kt + * LastUpdate 2026-02-07 13:28:02 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +class JsonTypeException : JsonException { + constructor() + constructor(message: String) : super(message) + constructor(throwable: Throwable) : super(throwable) + constructor(message: String, throwable: Throwable) : super(message, throwable) + constructor( + message: String? = null, + throwable: Throwable? = null, + enableSuppression: Boolean = false, + writableStackTrace: Boolean = false + ) : super(message, throwable, enableSuppression, writableStackTrace) +} diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/LengthRequiredException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/LengthRequiredException.kt new file mode 100644 index 0000000..7b00c53 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/LengthRequiredException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile LengthRequiredException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 411 Length Required 异常。 + * + * @param message 异常信息,默认为 "Length Required" + * @param cause 异常原因,默认为 null + */ +class LengthRequiredException( + override val message: String? = "Length Required", + override val cause: Throwable? = null, +) : HttpStatusException(411, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/MethodNotAllowedException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/MethodNotAllowedException.kt new file mode 100644 index 0000000..9e49a0e --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/MethodNotAllowedException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile MethodNotAllowedException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 405 Method Not Allowed 异常。 + * + * @param message 异常信息,默认为 "Method Not Allowed" + * @param cause 异常原因,默认为 null + */ +class MethodNotAllowedException( + override val message: String? = "Method Not Allowed", + override val cause: Throwable? = null, +) : HttpStatusException(405, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/MingLiUtilsBaseException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/MingLiUtilsBaseException.kt new file mode 100644 index 0000000..d6dc063 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/MingLiUtilsBaseException.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile MingLiUtilsBaseException.kt + * LastUpdate 2026-02-05 14:41:27 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +open class MingLiUtilsBaseException : RuntimeException { + + constructor() + constructor(message: String) : super(message) + constructor(throwable: Throwable) : super(throwable) + constructor(message: String, throwable: Throwable) : super(message, throwable) + constructor( + message: String? = null, + throwable: Throwable? = null, + enableSuppression: Boolean = false, + writableStackTrace: Boolean = false + ) : super(message, throwable, enableSuppression, writableStackTrace) +} diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/MovedPermanentlyException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/MovedPermanentlyException.kt new file mode 100644 index 0000000..9e99733 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/MovedPermanentlyException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile MovedPermanentlyException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 301 Moved Permanently 异常。 + * + * @param message 异常信息,默认为 "Moved Permanently" + * @param cause 异常原因,默认为 null + */ +class MovedPermanentlyException( + override val message: String? = "Moved Permanently", + override val cause: Throwable? = null, +) : HttpStatusException(301, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/MultipleChoicesException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/MultipleChoicesException.kt new file mode 100644 index 0000000..03417eb --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/MultipleChoicesException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile MultipleChoicesException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 300 Multiple Choices 异常。 + * + * @param message 异常信息,默认为 "Multiple Choices" + * @param cause 异常原因,默认为 null + */ +class MultipleChoicesException( + override val message: String? = "Multiple Choices", + override val cause: Throwable? = null, +) : HttpStatusException(300, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/NotAcceptableException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/NotAcceptableException.kt new file mode 100644 index 0000000..0eab586 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/NotAcceptableException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile NotAcceptableException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 406 Not Acceptable 异常。 + * + * @param message 异常信息,默认为 "Not Acceptable" + * @param cause 异常原因,默认为 null + */ +class NotAcceptableException( + override val message: String? = "Not Acceptable", + override val cause: Throwable? = null, +) : HttpStatusException(406, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/NotFoundException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/NotFoundException.kt new file mode 100644 index 0000000..9918860 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/NotFoundException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile NotFoundException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 404 Not Found 异常。 + * + * @param message 异常信息,默认为 "Not Found" + * @param cause 异常原因,默认为 null + */ +class NotFoundException( + override val message: String? = "Not Found", + override val cause: Throwable? = null, +) : HttpStatusException(404, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/NotImplementedException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/NotImplementedException.kt new file mode 100644 index 0000000..7cbed2d --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/NotImplementedException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile NotImplementedException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 501 Not Implemented 异常。 + * + * @param message 异常信息,默认为 "Not Implemented" + * @param cause 异常原因,默认为 null + */ +class NotImplementedException( + override val message: String? = "Not Implemented", + override val cause: Throwable? = null, +) : HttpStatusException(501, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/NotModifiedException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/NotModifiedException.kt new file mode 100644 index 0000000..6ab4f46 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/NotModifiedException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile NotModifiedException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 304 Not Modified 异常。 + * + * @param message 异常信息,默认为 "Not Modified" + * @param cause 异常原因,默认为 null + */ +class NotModifiedException( + override val message: String? = "Not Modified", + override val cause: Throwable? = null, +) : HttpStatusException(304, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/PaymentRequiredException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/PaymentRequiredException.kt new file mode 100644 index 0000000..3c69664 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/PaymentRequiredException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile PaymentRequiredException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 402 Payment Required 异常。 + * + * @param message 异常信息,默认为 "Payment Required" + * @param cause 异常原因,默认为 null + */ +class PaymentRequiredException( + override val message: String? = "Payment Required", + override val cause: Throwable? = null, +) : HttpStatusException(402, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/PreconditionFailedException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/PreconditionFailedException.kt new file mode 100644 index 0000000..ce05382 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/PreconditionFailedException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile PreconditionFailedException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 412 Precondition Failed 异常。 + * + * @param message 异常信息,默认为 "Precondition Failed" + * @param cause 异常原因,默认为 null + */ +class PreconditionFailedException( + override val message: String? = "Precondition Failed", + override val cause: Throwable? = null, +) : HttpStatusException(412, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/ProxyAuthenticationRequiredException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/ProxyAuthenticationRequiredException.kt new file mode 100644 index 0000000..2c784f1 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/ProxyAuthenticationRequiredException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile ProxyAuthenticationRequiredException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 407 Proxy Authentication Required 异常。 + * + * @param message 异常信息,默认为 "Proxy Authentication Required" + * @param cause 异常原因,默认为 null + */ +class ProxyAuthenticationRequiredException( + override val message: String? = "Proxy Authentication Required", + override val cause: Throwable? = null, +) : HttpStatusException(407, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/RequestEntityTooLargeException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/RequestEntityTooLargeException.kt new file mode 100644 index 0000000..0ef8bc7 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/RequestEntityTooLargeException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile RequestEntityTooLargeException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 413 Request Entity Too Large 异常。 + * + * @param message 异常信息,默认为 "Request Entity Too Large" + * @param cause 异常原因,默认为 null + */ +class RequestEntityTooLargeException( + override val message: String? = "Request Entity Too Large", + override val cause: Throwable? = null, +) : HttpStatusException(413, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/RequestTimeoutException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/RequestTimeoutException.kt new file mode 100644 index 0000000..8c29bf1 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/RequestTimeoutException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile RequestTimeoutException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 408 Request Timeout 异常。 + * + * @param message 异常信息,默认为 "Request Timeout" + * @param cause 异常原因,默认为 null + */ +class RequestTimeoutException( + override val message: String? = "Request Timeout", + override val cause: Throwable? = null, +) : HttpStatusException(408, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/RequestUriTooLargeException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/RequestUriTooLargeException.kt new file mode 100644 index 0000000..d7c8bc6 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/RequestUriTooLargeException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile RequestUriTooLargeException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 414 Request URI Too Large 异常。 + * + * @param message 异常信息,默认为 "Request URI Too Large" + * @param cause 异常原因,默认为 null + */ +class RequestUriTooLargeException( + override val message: String? = "Request URI Too Large", + override val cause: Throwable? = null, +) : HttpStatusException(414, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/RequestedRangeNotSatisfiableException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/RequestedRangeNotSatisfiableException.kt new file mode 100644 index 0000000..64773a6 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/RequestedRangeNotSatisfiableException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile RequestedRangeNotSatisfiableException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 416 Requested Range Not Satisfiable 异常。 + * + * @param message 异常信息,默认为 "Requested Range Not Satisfiable" + * @param cause 异常原因,默认为 null + */ +class RequestedRangeNotSatisfiableException( + override val message: String? = "Requested Range Not Satisfiable", + override val cause: Throwable? = null, +) : HttpStatusException(416, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/SeeOtherException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/SeeOtherException.kt new file mode 100644 index 0000000..bcbe25e --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/SeeOtherException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile SeeOtherException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 303 See Other 异常。 + * + * @param message 异常信息,默认为 "See Other" + * @param cause 异常原因,默认为 null + */ +class SeeOtherException( + override val message: String? = "See Other", + override val cause: Throwable? = null, +) : HttpStatusException(303, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/ServiceUnavailableException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/ServiceUnavailableException.kt new file mode 100644 index 0000000..d231881 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/ServiceUnavailableException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile ServiceUnavailableException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 503 Service Unavailable 异常。 + * + * @param message 异常信息,默认为 "Service Unavailable" + * @param cause 异常原因,默认为 null + */ +class ServiceUnavailableException( + override val message: String? = "Service Unavailable", + override val cause: Throwable? = null, +) : HttpStatusException(503, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/TemporaryRedirectException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/TemporaryRedirectException.kt new file mode 100644 index 0000000..eaccf2f --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/TemporaryRedirectException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile TemporaryRedirectException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 307 Temporary Redirect 异常。 + * + * @param message 异常信息,默认为 "Temporary Redirect" + * @param cause 异常原因,默认为 null + */ +class TemporaryRedirectException( + override val message: String? = "Temporary Redirect", + override val cause: Throwable? = null, +) : HttpStatusException(307, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/UnauthorizedException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/UnauthorizedException.kt new file mode 100644 index 0000000..c1d936e --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/UnauthorizedException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile UnauthorizedException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 401 Unauthorized 异常。 + * + * @param message 异常信息,默认为 "Unauthorized" + * @param cause 异常原因,默认为 null + */ +class UnauthorizedException( + override val message: String? = "Unauthorized", + override val cause: Throwable? = null, +) : HttpStatusException(401, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/UnsupportedMediaTypeException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/UnsupportedMediaTypeException.kt new file mode 100644 index 0000000..c21f046 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/UnsupportedMediaTypeException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile UnsupportedMediaTypeException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 415 Unsupported Media Type 异常。 + * + * @param message 异常信息,默认为 "Unsupported Media Type" + * @param cause 异常原因,默认为 null + */ +class UnsupportedMediaTypeException( + override val message: String? = "Unsupported Media Type", + override val cause: Throwable? = null, +) : HttpStatusException(415, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/exception/UseProxyException.kt b/src/main/kotlin/com/mingliqiye/utils/exception/UseProxyException.kt new file mode 100644 index 0000000..870a815 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/exception/UseProxyException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile UseProxyException.kt + * LastUpdate 2026-02-05 14:55:04 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.exception + +/** + * 表示 HTTP 305 Use Proxy 异常。 + * + * @param message 异常信息,默认为 "Use Proxy" + * @param cause 异常原因,默认为 null + */ +class UseProxyException( + override val message: String? = "Use Proxy", + override val cause: Throwable? = null, +) : HttpStatusException(305, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/functions/Pipeline.kt b/src/main/kotlin/com/mingliqiye/utils/functions/Pipeline.kt index 7dfc794..2b44ca9 100644 --- a/src/main/kotlin/com/mingliqiye/utils/functions/Pipeline.kt +++ b/src/main/kotlin/com/mingliqiye/utils/functions/Pipeline.kt @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile Pipeline.kt - * LastUpdate 2026-02-05 09:53:44 + * LastUpdate 2026-02-05 15:22:21 * UpdateUser MingLiPro */ @@ -174,7 +174,7 @@ class Pipeline(private val value: T) { fun require( any: Any, message: String ): Pipeline { - Require.require(any == value, message) + Require.RequireLayz.require(any == value, message) return this } diff --git a/src/main/kotlin/com/mingliqiye/utils/http/Response.kt b/src/main/kotlin/com/mingliqiye/utils/http/Response.kt index fb3f483..1d7c5d0 100644 --- a/src/main/kotlin/com/mingliqiye/utils/http/Response.kt +++ b/src/main/kotlin/com/mingliqiye/utils/http/Response.kt @@ -16,12 +16,15 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile Response.kt - * LastUpdate 2026-02-03 20:09:10 + * LastUpdate 2026-02-07 22:18:42 * UpdateUser MingLiPro */ package com.mingliqiye.utils.http +import com.mingliqiye.utils.annotation.DateTimeJsonFormat +import com.mingliqiye.utils.json.converters.DateTimeJsonConverter +import com.mingliqiye.utils.json.converters.base.AnnotationGetter import com.mingliqiye.utils.time.DateTime /** @@ -35,8 +38,13 @@ import com.mingliqiye.utils.time.DateTime * @property statusCode 状态码 */ data class Response( - private var time: DateTime, private var message: String, private var data: T?, private var statusCode: Int + private var time: DateTime, + private var message: String, + private var data: T?, + private var statusCode: Int, ) { + private var timeFormat: DateTimeJsonFormat = DateTimeJsonFormat() + companion object { /** @@ -124,20 +132,22 @@ data class Response( /** - * 获取响应时间 + * 获取格式化后的时间字符串。 * - * @return DateTime 响应时间 + * @return 格式化后的时间字符串,使用 [DateTimeJsonConverter] 和 [timeFormat] 注解进行转换。 */ - fun getTime(): DateTime = time + fun getTime(): String = + DateTimeJsonConverter.getJsonConverter().convert(time, AnnotationGetter.oneGetter(timeFormat))!! /** - * 设置响应时间 + * 设置时间字段的值。 * - * @param dateTime 响应时间 - * @return Response 当前响应对象(用于链式调用) + * @param dateTime 格式化后的时间字符串,将被反序列化为内部时间对象。 + * @return 返回当前对象实例,支持链式调用。 */ - fun setTime(dateTime: DateTime): Response { - time = dateTime + fun setTime(dateTime: String): Response { + // 使用 DateTimeJsonConverter 将输入的字符串反序列化为时间对象,并更新内部 time 字段 + time = DateTimeJsonConverter.getJsonConverter().deConvert(dateTime, AnnotationGetter.oneGetter(timeFormat))!! return this } @@ -250,6 +260,13 @@ data class Response( return this } + fun writeTimeFormat(timeFormat: DateTimeJsonFormat): Response { + this.timeFormat = timeFormat + return this + } + + fun readTimeFormat(): DateTimeJsonFormat = timeFormat + /** * 返回响应对象的字符串表示 */ diff --git a/src/main/kotlin/com/mingliqiye/utils/http/exception/Exceptions.kt b/src/main/kotlin/com/mingliqiye/utils/http/exception/Exceptions.kt deleted file mode 100644 index 6710cba..0000000 --- a/src/main/kotlin/com/mingliqiye/utils/http/exception/Exceptions.kt +++ /dev/null @@ -1,372 +0,0 @@ -/* - * Copyright 2026 mingliqiye - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ProjectName mingli-utils - * ModuleName mingli-utils.main - * CurrentFile Exceptions.kt - * LastUpdate 2026-02-05 11:12:36 - * UpdateUser MingLiPro - */ - -package com.mingliqiye.utils.http.exception - -/** - * 表示 HTTP 异常的基类,继承自 [RuntimeException]。 - * - * @param statusCode HTTP 状态码 - * @param message 异常信息,默认为 null - * @param cause 异常原因,默认为 null - */ -sealed class HttpException( - open val statusCode: Int, - override val message: String? = null, - override val cause: Throwable? = null -) : RuntimeException(message, cause) - -// 3xx - 重定向异常 - -/** - * 表示 HTTP 300 Multiple Choices 异常。 - * - * @param message 异常信息,默认为 "Multiple Choices" - * @param cause 异常原因,默认为 null - */ -class MultipleChoicesException( - override val message: String? = "Multiple Choices", - override val cause: Throwable? = null, -) : HttpException(300, message, cause) - -/** - * 表示 HTTP 301 Moved Permanently 异常。 - * - * @param message 异常信息,默认为 "Moved Permanently" - * @param cause 异常原因,默认为 null - */ -class MovedPermanentlyException( - override val message: String? = "Moved Permanently", - override val cause: Throwable? = null, -) : HttpException(301, message, cause) - -/** - * 表示 HTTP 302 Found 异常。 - * - * @param message 异常信息,默认为 "Found" - * @param cause 异常原因,默认为 null - */ -class FoundException( - override val message: String? = "Found", - override val cause: Throwable? = null, -) : HttpException(302, message, cause) - -/** - * 表示 HTTP 303 See Other 异常。 - * - * @param message 异常信息,默认为 "See Other" - * @param cause 异常原因,默认为 null - */ -class SeeOtherException( - override val message: String? = "See Other", - override val cause: Throwable? = null, -) : HttpException(303, message, cause) - -/** - * 表示 HTTP 304 Not Modified 异常。 - * - * @param message 异常信息,默认为 "Not Modified" - * @param cause 异常原因,默认为 null - */ -class NotModifiedException( - override val message: String? = "Not Modified", - override val cause: Throwable? = null, -) : HttpException(304, message, cause) - -/** - * 表示 HTTP 305 Use Proxy 异常。 - * - * @param message 异常信息,默认为 "Use Proxy" - * @param cause 异常原因,默认为 null - */ -class UseProxyException( - override val message: String? = "Use Proxy", - override val cause: Throwable? = null, -) : HttpException(305, message, cause) - -/** - * 表示 HTTP 307 Temporary Redirect 异常。 - * - * @param message 异常信息,默认为 "Temporary Redirect" - * @param cause 异常原因,默认为 null - */ -class TemporaryRedirectException( - override val message: String? = "Temporary Redirect", - override val cause: Throwable? = null, -) : HttpException(307, message, cause) - -// 4xx - 客户端错误异常 - -/** - * 表示 HTTP 400 Bad Request 异常。 - * - * @param message 异常信息,默认为 "Bad Request" - * @param cause 异常原因,默认为 null - */ -class BadRequestException( - override val message: String? = "Bad Request", - override val cause: Throwable? = null, -) : HttpException(400, message, cause) - -/** - * 表示 HTTP 401 Unauthorized 异常。 - * - * @param message 异常信息,默认为 "Unauthorized" - * @param cause 异常原因,默认为 null - */ -class UnauthorizedException( - override val message: String? = "Unauthorized", - override val cause: Throwable? = null, -) : HttpException(401, message, cause) - -/** - * 表示 HTTP 402 Payment Required 异常。 - * - * @param message 异常信息,默认为 "Payment Required" - * @param cause 异常原因,默认为 null - */ -class PaymentRequiredException( - override val message: String? = "Payment Required", - override val cause: Throwable? = null, -) : HttpException(402, message, cause) - -/** - * 表示 HTTP 403 Forbidden 异常。 - * - * @param message 异常信息,默认为 "Forbidden" - * @param cause 异常原因,默认为 null - */ -class ForbiddenException( - override val message: String? = "Forbidden", - override val cause: Throwable? = null, -) : HttpException(403, message, cause) - -/** - * 表示 HTTP 404 Not Found 异常。 - * - * @param message 异常信息,默认为 "Not Found" - * @param cause 异常原因,默认为 null - */ -class NotFoundException( - override val message: String? = "Not Found", - override val cause: Throwable? = null, -) : HttpException(404, message, cause) - -/** - * 表示 HTTP 405 Method Not Allowed 异常。 - * - * @param message 异常信息,默认为 "Method Not Allowed" - * @param cause 异常原因,默认为 null - */ -class MethodNotAllowedException( - override val message: String? = "Method Not Allowed", - override val cause: Throwable? = null, -) : HttpException(405, message, cause) - -/** - * 表示 HTTP 406 Not Acceptable 异常。 - * - * @param message 异常信息,默认为 "Not Acceptable" - * @param cause 异常原因,默认为 null - */ -class NotAcceptableException( - override val message: String? = "Not Acceptable", - override val cause: Throwable? = null, -) : HttpException(406, message, cause) - -/** - * 表示 HTTP 407 Proxy Authentication Required 异常。 - * - * @param message 异常信息,默认为 "Proxy Authentication Required" - * @param cause 异常原因,默认为 null - */ -class ProxyAuthenticationRequiredException( - override val message: String? = "Proxy Authentication Required", - override val cause: Throwable? = null, -) : HttpException(407, message, cause) - -/** - * 表示 HTTP 408 Request Timeout 异常。 - * - * @param message 异常信息,默认为 "Request Timeout" - * @param cause 异常原因,默认为 null - */ -class RequestTimeoutException( - override val message: String? = "Request Timeout", - override val cause: Throwable? = null, -) : HttpException(408, message, cause) - -/** - * 表示 HTTP 409 Conflict 异常。 - * - * @param message 异常信息,默认为 "Conflict" - * @param cause 异常原因,默认为 null - */ -class ConflictException( - override val message: String? = "Conflict", - override val cause: Throwable? = null, -) : HttpException(409, message, cause) - -/** - * 表示 HTTP 410 Gone 异常。 - * - * @param message 异常信息,默认为 "Gone" - * @param cause 异常原因,默认为 null - */ -class GoneException( - override val message: String? = "Gone", - override val cause: Throwable? = null, -) : HttpException(410, message, cause) - -/** - * 表示 HTTP 411 Length Required 异常。 - * - * @param message 异常信息,默认为 "Length Required" - * @param cause 异常原因,默认为 null - */ -class LengthRequiredException( - override val message: String? = "Length Required", - override val cause: Throwable? = null, -) : HttpException(411, message, cause) - -/** - * 表示 HTTP 412 Precondition Failed 异常。 - * - * @param message 异常信息,默认为 "Precondition Failed" - * @param cause 异常原因,默认为 null - */ -class PreconditionFailedException( - override val message: String? = "Precondition Failed", - override val cause: Throwable? = null, -) : HttpException(412, message, cause) - -/** - * 表示 HTTP 413 Request Entity Too Large 异常。 - * - * @param message 异常信息,默认为 "Request Entity Too Large" - * @param cause 异常原因,默认为 null - */ -class RequestEntityTooLargeException( - override val message: String? = "Request Entity Too Large", - override val cause: Throwable? = null, -) : HttpException(413, message, cause) - -/** - * 表示 HTTP 414 Request URI Too Large 异常。 - * - * @param message 异常信息,默认为 "Request URI Too Large" - * @param cause 异常原因,默认为 null - */ -class RequestUriTooLargeException( - override val message: String? = "Request URI Too Large", - override val cause: Throwable? = null, -) : HttpException(414, message, cause) - -/** - * 表示 HTTP 415 Unsupported Media Type 异常。 - * - * @param message 异常信息,默认为 "Unsupported Media Type" - * @param cause 异常原因,默认为 null - */ -class UnsupportedMediaTypeException( - override val message: String? = "Unsupported Media Type", - override val cause: Throwable? = null, -) : HttpException(415, message, cause) - -/** - * 表示 HTTP 416 Requested Range Not Satisfiable 异常。 - * - * @param message 异常信息,默认为 "Requested Range Not Satisfiable" - * @param cause 异常原因,默认为 null - */ -class RequestedRangeNotSatisfiableException( - override val message: String? = "Requested Range Not Satisfiable", - override val cause: Throwable? = null, -) : HttpException(416, message, cause) - -// 5xx - 服务器错误异常 - -/** - * 表示 HTTP 500 Internal Server Error 异常。 - * - * @param message 异常信息,默认为 "Internal Server Error" - * @param cause 异常原因,默认为 null - */ -class InternalServerErrorException( - override val message: String? = "Internal Server Error", - override val cause: Throwable? = null, -) : HttpException(500, message, cause) - -/** - * 表示 HTTP 501 Not Implemented 异常。 - * - * @param message 异常信息,默认为 "Not Implemented" - * @param cause 异常原因,默认为 null - */ -class NotImplementedException( - override val message: String? = "Not Implemented", - override val cause: Throwable? = null, -) : HttpException(501, message, cause) - -/** - * 表示 HTTP 502 Bad Gateway 异常。 - * - * @param message 异常信息,默认为 "Bad Gateway" - * @param cause 异常原因,默认为 null - */ -class BadGatewayException( - override val message: String? = "Bad Gateway", - override val cause: Throwable? = null, -) : HttpException(502, message, cause) - -/** - * 表示 HTTP 503 Service Unavailable 异常。 - * - * @param message 异常信息,默认为 "Service Unavailable" - * @param cause 异常原因,默认为 null - */ -class ServiceUnavailableException( - override val message: String? = "Service Unavailable", - override val cause: Throwable? = null, -) : HttpException(503, message, cause) - -/** - * 表示 HTTP 504 Gateway Timeout 异常。 - * - * @param message 异常信息,默认为 "Gateway Timeout" - * @param cause 异常原因,默认为 null - */ -class GatewayTimeoutException( - override val message: String? = "Gateway Timeout", - override val cause: Throwable? = null, -) : HttpException(504, message, cause) - -/** - * 表示 HTTP 505 HTTP Version Not Supported 异常。 - * - * @param message 异常信息,默认为 "HTTP Version Not Supported" - * @param cause 异常原因,默认为 null - */ -class HttpVersionNotSupportedException( - override val message: String? = "HTTP Version Not Supported", - override val cause: Throwable? = null, -) : HttpException(505, message, cause) diff --git a/src/main/kotlin/com/mingliqiye/utils/i18n/I18N.kt b/src/main/kotlin/com/mingliqiye/utils/i18n/I18N.kt new file mode 100644 index 0000000..84d8ded --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/i18n/I18N.kt @@ -0,0 +1,964 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile I18N.kt + * LastUpdate 2026-02-05 22:39:07 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.i18n + +import com.fasterxml.jackson.databind.ObjectMapper +import org.slf4j.Logger + + +object I18N { + + @JvmStatic + private var _Internationalization: Internationalization = Internationalization( + clazz = I18N::class.java, + objectMapper = ObjectMapper(), + isloadSlef = true + ) + + fun setInternationalization(value: Internationalization) { + this._Internationalization = value + } + + fun getInternationalization() = _Internationalization + + @JvmStatic + fun getString(string: String, vararg any: Any): String = _Internationalization.getString(string, *any) + + @JvmStatic + fun getString(string: String): String = _Internationalization.getString(string) + + @JvmStatic + fun getKeyString(key: String): String = _Internationalization.getKeyString(key) + + + fun Logger.infoTranslater(string: String, vararg any: Any) = this.info(getString(string, *any)) + fun Logger.warnTranslater(string: String, vararg any: Any) = this.warn(getString(string, *any)) + fun Logger.debugTranslater(string: String, vararg any: Any) = this.debug(getString(string, *any)) + fun Logger.traceTranslater(string: String, vararg any: Any) = this.trace(getString(string, *any)) + + // 0 个参数 + fun Logger.errorTranslater(string: String, any1: Any) { + this.error(getString(string)) + } + + fun Logger.errorTranslater(string: String) { + this.error(getString(string)) + } + + // 2 个参数 + fun Logger.errorTranslater(string: String, any1: Any, any2: Any) { + this.error(getString(string, any1, any2)) + } + + // 3 个参数 + fun Logger.errorTranslater(string: String, any1: Any, any2: Any, any3: Any) { + this.error(getString(string, any1, any2, any3)) + } + + // 4 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + + ) { + this.error(getString(string, any1, any2, any3, any4)) + } + + // 5 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + + ) { + this.error(getString(string, any1, any2, any3, any4, any5)) + } + + // 6 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + + ) { + this.error(getString(string, any1, any2, any3, any4, any5, any6)) + } + + // 7 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + + ) { + this.error(getString(string, any1, any2, any3, any4, any5, any6, any7)) + } + + // 8 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + + ) { + this.error(getString(string, any1, any2, any3, any4, any5, any6, any7, any8)) + } + + // 9 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + + ) { + this.error(getString(string, any1, any2, any3, any4, any5, any6, any7, any8, any9)) + } + + // 10 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + any10: Any, + + ) { + this.error(getString(string, any1, any2, any3, any4, any5, any6, any7, any8, any9, any10)) + } + + // 11 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, any2: Any, any3: Any, any4: Any, any5: Any, + any6: Any, any7: Any, any8: Any, any9: Any, any10: Any, any11: Any, + + ) { + this.error(getString(string, any1, any2, any3, any4, any5, any6, any7, any8, any9, any10, any11)) + } + + // 12 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, any2: Any, any3: Any, any4: Any, any5: Any, + any6: Any, any7: Any, any8: Any, any9: Any, any10: Any, any11: Any, any12: Any, + + ) { + this.error( + getString(string, any1, any2, any3, any4, any5, any6, any7, any8, any9, any10, any11, any12) + ) + } + + // 13 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, any2: Any, any3: Any, any4: Any, any5: Any, + any6: Any, any7: Any, any8: Any, any9: Any, any10: Any, any11: Any, any12: Any, any13: Any, + + ) { + this.error( + getString(string, any1, any2, any3, any4, any5, any6, any7, any8, any9, any10, any11, any12, any13), + + ) + } + + // 14 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, any2: Any, any3: Any, any4: Any, any5: Any, + any6: Any, any7: Any, any8: Any, any9: Any, any10: Any, any11: Any, any12: Any, any13: Any, any14: Any, + + ) { + this.error( + getString( + string, any1, any2, any3, any4, any5, any6, any7, any8, any9, any10, any11, any12, any13, any14 + ) + ) + } + + // 15 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + any10: Any, + any11: Any, + any12: Any, + any13: Any, + any14: Any, + any15: Any, + + ) { + this.error( + getString( + string, any1, any2, any3, any4, any5, any6, any7, any8, any9, any10, any11, any12, any13, any14, any15 + ) + ) + } + + // 16 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + any10: Any, + any11: Any, + any12: Any, + any13: Any, + any14: Any, + any15: Any, + any16: Any, + + ) { + this.error( + getString( + string, + any1, + any2, + any3, + any4, + any5, + any6, + any7, + any8, + any9, + any10, + any11, + any12, + any13, + any14, + any15, + any16 + ) + ) + } + + // 17 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + any10: Any, + any11: Any, + any12: Any, + any13: Any, + any14: Any, + any15: Any, + any16: Any, + any17: Any, + + ) { + this.error( + getString( + string, + any1, + any2, + any3, + any4, + any5, + any6, + any7, + any8, + any9, + any10, + any11, + any12, + any13, + any14, + any15, + any16, + any17 + ) + ) + } + + // 18 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + any10: Any, + any11: Any, + any12: Any, + any13: Any, + any14: Any, + any15: Any, + any16: Any, + any17: Any, + any18: Any, + + ) { + this.error( + getString( + string, + any1, + any2, + any3, + any4, + any5, + any6, + any7, + any8, + any9, + any10, + any11, + any12, + any13, + any14, + any15, + any16, + any17, + any18 + ) + ) + } + + // 19 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + any10: Any, + any11: Any, + any12: Any, + any13: Any, + any14: Any, + any15: Any, + any16: Any, + any17: Any, + any18: Any, + any19: Any, + + ) { + this.error( + getString( + string, + any1, + any2, + any3, + any4, + any5, + any6, + any7, + any8, + any9, + any10, + any11, + any12, + any13, + any14, + any15, + any16, + any17, + any18, + any19 + ) + ) + } + + // 20 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + any10: Any, + any11: Any, + any12: Any, + any13: Any, + any14: Any, + any15: Any, + any16: Any, + any17: Any, + any18: Any, + any19: Any, + any20: Any, + ) { + this.error( + getString( + string, + any1, + any2, + any3, + any4, + any5, + any6, + any7, + any8, + any9, + any10, + any11, + any12, + any13, + any14, + any15, + any16, + any17, + any18, + any19, + any20 + ) + ) + } + + + fun Logger.errorTranslater(string: String, throwable: Throwable) { + this.error(getString(string), throwable) + } + + // 1 个参数 + fun Logger.errorTranslater(string: String, any1: Any, throwable: Throwable) { + this.error(getString(string, any1), throwable) + } + + // 2 个参数 + fun Logger.errorTranslater(string: String, any1: Any, any2: Any, throwable: Throwable) { + this.error(getString(string, any1, any2), throwable) + } + + // 3 个参数 + fun Logger.errorTranslater(string: String, any1: Any, any2: Any, any3: Any, throwable: Throwable) { + this.error(getString(string, any1, any2, any3), throwable) + } + + // 4 个参数 + fun Logger.errorTranslater( + string: String, any1: Any, any2: Any, any3: Any, any4: Any, throwable: Throwable + ) { + this.error(getString(string, any1, any2, any3, any4), throwable) + } + + // 5 个参数 + fun Logger.errorTranslater( + string: String, any1: Any, any2: Any, any3: Any, any4: Any, any5: Any, throwable: Throwable + ) { + this.error(getString(string, any1, any2, any3, any4, any5), throwable) + } + + // 6 个参数 + fun Logger.errorTranslater( + string: String, any1: Any, any2: Any, any3: Any, any4: Any, any5: Any, any6: Any, throwable: Throwable + ) { + this.error(getString(string, any1, any2, any3, any4, any5, any6), throwable) + } + + // 7 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + throwable: Throwable + ) { + this.error(getString(string, any1, any2, any3, any4, any5, any6, any7), throwable) + } + + // 8 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + throwable: Throwable + ) { + this.error(getString(string, any1, any2, any3, any4, any5, any6, any7, any8), throwable) + } + + // 9 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + throwable: Throwable + ) { + this.error(getString(string, any1, any2, any3, any4, any5, any6, any7, any8, any9), throwable) + } + + // 10 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + any10: Any, + throwable: Throwable + ) { + this.error(getString(string, any1, any2, any3, any4, any5, any6, any7, any8, any9, any10), throwable) + } + + // 11 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + any10: Any, + any11: Any, + throwable: Throwable + ) { + this.error(getString(string, any1, any2, any3, any4, any5, any6, any7, any8, any9, any10, any11), throwable) + } + + // 12 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + any10: Any, + any11: Any, + any12: Any, + throwable: Throwable + ) { + this.error( + getString(string, any1, any2, any3, any4, any5, any6, any7, any8, any9, any10, any11, any12), throwable + ) + } + + // 13 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + any10: Any, + any11: Any, + any12: Any, + any13: Any, + throwable: Throwable + ) { + this.error( + getString(string, any1, any2, any3, any4, any5, any6, any7, any8, any9, any10, any11, any12, any13), + throwable + ) + } + + // 14 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + any10: Any, + any11: Any, + any12: Any, + any13: Any, + any14: Any, + throwable: Throwable + ) { + this.error( + getString( + string, any1, any2, any3, any4, any5, any6, any7, any8, any9, any10, any11, any12, any13, any14 + ), throwable + ) + } + + // 15 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + any10: Any, + any11: Any, + any12: Any, + any13: Any, + any14: Any, + any15: Any, + throwable: Throwable + ) { + this.error( + getString( + string, any1, any2, any3, any4, any5, any6, any7, any8, any9, any10, any11, any12, any13, any14, any15 + ), throwable + ) + } + + // 16 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + any10: Any, + any11: Any, + any12: Any, + any13: Any, + any14: Any, + any15: Any, + any16: Any, + throwable: Throwable + ) { + this.error( + getString( + string, + any1, + any2, + any3, + any4, + any5, + any6, + any7, + any8, + any9, + any10, + any11, + any12, + any13, + any14, + any15, + any16 + ), throwable + ) + } + + // 17 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + any10: Any, + any11: Any, + any12: Any, + any13: Any, + any14: Any, + any15: Any, + any16: Any, + any17: Any, + throwable: Throwable + ) { + this.error( + getString( + string, + any1, + any2, + any3, + any4, + any5, + any6, + any7, + any8, + any9, + any10, + any11, + any12, + any13, + any14, + any15, + any16, + any17 + ), throwable + ) + } + + // 18 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + any10: Any, + any11: Any, + any12: Any, + any13: Any, + any14: Any, + any15: Any, + any16: Any, + any17: Any, + any18: Any, + throwable: Throwable + ) { + this.error( + getString( + string, + any1, + any2, + any3, + any4, + any5, + any6, + any7, + any8, + any9, + any10, + any11, + any12, + any13, + any14, + any15, + any16, + any17, + any18 + ), throwable + ) + } + + // 19 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + any10: Any, + any11: Any, + any12: Any, + any13: Any, + any14: Any, + any15: Any, + any16: Any, + any17: Any, + any18: Any, + any19: Any, + throwable: Throwable + ) { + this.error( + getString( + string, + any1, + any2, + any3, + any4, + any5, + any6, + any7, + any8, + any9, + any10, + any11, + any12, + any13, + any14, + any15, + any16, + any17, + any18, + any19 + ), throwable + ) + } + + // 20 个参数 + fun Logger.errorTranslater( + string: String, + any1: Any, + any2: Any, + any3: Any, + any4: Any, + any5: Any, + any6: Any, + any7: Any, + any8: Any, + any9: Any, + any10: Any, + any11: Any, + any12: Any, + any13: Any, + any14: Any, + any15: Any, + any16: Any, + any17: Any, + any18: Any, + any19: Any, + any20: Any, + throwable: Throwable + ) { + this.error( + getString( + string, + any1, + any2, + any3, + any4, + any5, + any6, + any7, + any8, + any9, + any10, + any11, + any12, + any13, + any14, + any15, + any16, + any17, + any18, + any19, + any20 + ), throwable + ) + } + + +} diff --git a/src/main/kotlin/com/mingliqiye/utils/i18n/Internationalization.kt b/src/main/kotlin/com/mingliqiye/utils/i18n/Internationalization.kt new file mode 100644 index 0000000..dc1446b --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/i18n/Internationalization.kt @@ -0,0 +1,144 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile Internationalization.kt + * LastUpdate 2026-02-06 08:47:26 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.i18n + +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.ObjectMapper +import com.mingliqiye.utils.logger.MingLiLoggerFactory +import org.slf4j.Logger +import java.net.URL +import java.util.* + +class Internationalization { + var clazz: Class<*> + var objectMapper: ObjectMapper + var locale: Locale + var backLocale: Locale + val langPath: String + val thisName: String + val thisBackName: String + private var log: Logger = MingLiLoggerFactory.getLogger() + + + constructor( + clazz: Class<*>, + objectMapper: ObjectMapper, + locale: Locale = Locale.getDefault(), + backLocale: Locale = Locale.US, + langPath: String = "/lang", + isloadSlef: Boolean = false + ) { + this.clazz = clazz + this.objectMapper = objectMapper + this.locale = locale + this.backLocale = backLocale + this.langPath = langPath + thisName = getLanguageName(locale) + thisBackName = getLanguageName(backLocale) + readJson("/assets/mingli-utils/lang/${thisName}.json", Internationalization::class.java, thisName) + if (!isloadSlef) { + if (!readJson(fileName = "$langPath/${thisName}.json", lang = thisName)) { + log.warn(getString("com.mingliqiye.utils.i18n.readjson.error", "$langPath/${thisName}.json")) + } + } + if (thisName != thisBackName) { + readJson("/assets/mingli-utils/lang/${thisBackName}.json", Internationalization::class.java, thisBackName) + if (!isloadSlef) { + if (!readJson(fileName = "$langPath/${thisBackName}.json", lang = thisBackName)) { + log.warn(getString("com.mingliqiye.utils.i18n.readjson.error", "$langPath/${thisBackName}.json")) + } + } + } + } + + fun getString(string: String, vararg any: Any): String { + return getString(string).format(*any) + } + + fun getString(string: String): String { + return getKeyString(string) + } + + fun getKeyString(key: String): String { + val s = localesData[thisName]?.get(key) ?: localesData[thisBackName]?.get(key) + if (s == null) { + return key + } + return s + } + + fun getLanguageName(locale: Locale): String = + if (locale.country == null || locale.language == null) locale.country + locale.language else locale.language + '_' + locale.country + + constructor( + clazz: Class<*>, + objectMapper: ObjectMapper, + ) : this( + locale = Locale.getDefault(), clazz = clazz, objectMapper = objectMapper + ) + + constructor( + clazz: Class<*>, objectMapper: ObjectMapper, langPath: String = "/lang" + ) : this( + locale = Locale.getDefault(), clazz = clazz, langPath = langPath, objectMapper = objectMapper + ) + + val localesData: MutableMap> = mutableMapOf() + + fun getlangFile( + clazzd: Class<*>, fileName: String + ): URL? { + return clazzd.getResource(fileName) + } + + fun readJson( + fileName: String, clazzd: Class<*> = clazz, lang: String + ): Boolean { + val byteArray: ByteArray? = getlangFile(clazzd, fileName)?.openStream()?.use { it.readBytes() } + if (byteArray == null) return false + val node: JsonNode = objectMapper.readTree(byteArray) + if (node.isArray) return false + if (node.isEmpty) return false + var m = localesData[lang] + if (m == null) { + m = mutableMapOf() + localesData[lang] = m + } + readJsonNode(node, "", m) + return true + } + + fun readJsonNode(node: JsonNode, path: String, map: MutableMap) { + val fields = node.properties().iterator() + while (fields.hasNext()) { + val (k, v) = fields.next() + val pathName = if (path.isEmpty()) path + k else "$path.$k" + if (v.isTextual) { + map[pathName] = v.asText() + } else if (v.isObject) { + readJsonNode(v, pathName, map) + } + } + } + +} diff --git a/src/main/kotlin/com/mingliqiye/utils/io/IO.kt b/src/main/kotlin/com/mingliqiye/utils/io/IO.kt index 9cf7ca6..8165627 100644 --- a/src/main/kotlin/com/mingliqiye/utils/io/IO.kt +++ b/src/main/kotlin/com/mingliqiye/utils/io/IO.kt @@ -16,12 +16,13 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile IO.kt - * LastUpdate 2026-02-04 13:12:57 + * LastUpdate 2026-02-06 13:21:33 * UpdateUser MingLiPro */ package com.mingliqiye.utils.io +import com.mingliqiye.utils.array.toHexString import com.mingliqiye.utils.logger.MingLiLoggerFactory import com.mingliqiye.utils.string.join import org.slf4j.Logger @@ -35,7 +36,13 @@ import java.io.PrintStream object IO { @JvmStatic - fun > T.println(): T { + fun ByteArray.println(): ByteArray { + this.toHexString().chunked(2).println() + return this + } + + @JvmStatic + fun List.println(): List { println("{" + ",".join(this) + "}") return this } diff --git a/src/main/kotlin/com/mingliqiye/utils/json/api/JacksonJsonApi.kt b/src/main/kotlin/com/mingliqiye/utils/json/api/JacksonJsonApi.kt index fc88f4f..319d244 100644 --- a/src/main/kotlin/com/mingliqiye/utils/json/api/JacksonJsonApi.kt +++ b/src/main/kotlin/com/mingliqiye/utils/json/api/JacksonJsonApi.kt @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile JacksonJsonApi.kt - * LastUpdate 2026-02-05 10:31:14 + * LastUpdate 2026-02-05 14:41:27 * UpdateUser MingLiPro */ @@ -28,8 +28,8 @@ import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectReader import com.fasterxml.jackson.databind.node.ObjectNode +import com.mingliqiye.utils.exception.JsonException import com.mingliqiye.utils.json.api.base.JsonApi -import com.mingliqiye.utils.json.api.exception.JsonException import com.mingliqiye.utils.json.api.type.JsonTypeReference import com.mingliqiye.utils.json.converters.base.BaseJsonConverter @@ -63,7 +63,7 @@ class JacksonJsonApi : JsonApi { * @param clazz 目标对象类型 * @param 泛型参数,表示目标对象类型 * @return 解析后的对象 - * @throws com.mingliqiye.utils.json.api.exception.JsonException 当解析失败时抛出异常 + * @throws JsonException 当解析失败时抛出异常 */ override fun parse(json: String, clazz: Class): T { return try { diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/DateTimeJsonConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/DateTimeJsonConverter.kt index 7e2000b..53e7319 100644 --- a/src/main/kotlin/com/mingliqiye/utils/json/converters/DateTimeJsonConverter.kt +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/DateTimeJsonConverter.kt @@ -16,12 +16,13 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile DateTimeJsonConverter.kt - * LastUpdate 2026-02-05 11:18:57 + * LastUpdate 2026-02-07 22:13:41 * UpdateUser MingLiPro */ package com.mingliqiye.utils.json.converters +import com.mingliqiye.utils.annotation.DateTimeJsonFormat import com.mingliqiye.utils.json.api.type.JsonTypeReference import com.mingliqiye.utils.json.converters.base.AnnotationGetter import com.mingliqiye.utils.json.converters.base.AnnotationGetter.Companion.get @@ -29,7 +30,6 @@ import com.mingliqiye.utils.json.converters.base.BaseJsonStringConverter import com.mingliqiye.utils.objects.isNull import com.mingliqiye.utils.string.isNullish import com.mingliqiye.utils.time.DateTime -import com.mingliqiye.utils.time.DateTimeJsonFormat import com.mingliqiye.utils.time.Formatter @@ -37,7 +37,17 @@ import com.mingliqiye.utils.time.Formatter * DateTimeJsonConverter 是一个用于处理 DateTime 类型与 JSON 字符串之间转换的类。 * 它继承自 BaseJsonStringConverter,提供了序列化(convert)和反序列化(deConvert)的功能。 */ -class DateTimeJsonConverter : BaseJsonStringConverter { +class DateTimeJsonConverter private constructor() : BaseJsonStringConverter { + + + companion object { + private val dateTimeJsonConverter by lazy { + DateTimeJsonConverter() + } + + @JvmStatic + fun getJsonConverter(): DateTimeJsonConverter = dateTimeJsonConverter + } /** * 将 DateTime 对象转换为 JSON 字符串。 diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/UUIDJsonConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/UUIDJsonConverter.kt index 724349a..1a20a80 100644 --- a/src/main/kotlin/com/mingliqiye/utils/json/converters/UUIDJsonConverter.kt +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/UUIDJsonConverter.kt @@ -16,12 +16,13 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile UUIDJsonConverter.kt - * LastUpdate 2026-02-05 11:19:45 + * LastUpdate 2026-02-08 01:37:00 * UpdateUser MingLiPro */ package com.mingliqiye.utils.json.converters +import com.mingliqiye.utils.annotation.UUIDJsonFormat import com.mingliqiye.utils.base.BaseType import com.mingliqiye.utils.json.api.type.JsonTypeReference import com.mingliqiye.utils.json.converters.base.AnnotationGetter @@ -31,14 +32,22 @@ import com.mingliqiye.utils.objects.isNull import com.mingliqiye.utils.string.isNullish import com.mingliqiye.utils.uuid.UUID import com.mingliqiye.utils.uuid.UUIDFormatType -import com.mingliqiye.utils.uuid.UUIDJsonFormat /** * UUIDJsonConverter 是一个用于处理 UUID 类型与 JSON 字符串之间转换的类。 * 它继承自 BaseJsonStringConverter,并实现了 convert 和 deConvert 方法, * 分别用于将 UUID 对象序列化为字符串以及反序列化字符串为 UUID 对象。 */ -class UUIDJsonConverter : BaseJsonStringConverter { +class UUIDJsonConverter private constructor() : BaseJsonStringConverter { + + companion object { + private val uuidJsonConverter by lazy { + UUIDJsonConverter() + } + + @JvmStatic + fun getJsonConverter(): UUIDJsonConverter = uuidJsonConverter + } /** * 将 UUID 对象转换为字符串形式。 diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/AnnotationGetter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/AnnotationGetter.kt index c8c905e..747c563 100644 --- a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/AnnotationGetter.kt +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/AnnotationGetter.kt @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile AnnotationGetter.kt - * LastUpdate 2026-02-05 11:12:36 + * LastUpdate 2026-02-06 16:57:45 * UpdateUser MingLiPro */ @@ -58,5 +58,24 @@ interface AnnotationGetter { */ override fun get(clazz: Class): T? = null } + + + /** + * 创建一个AnnotationGetter对象,用于根据指定的注解类型获取对应的注解实例。 + * + * @param annotation 要包装的注解实例,作为内部逻辑的来源。 + * @return 返回一个实现了AnnotationGetter接口的匿名对象。 + */ + fun oneGetter(annotation: Annotation) = object : AnnotationGetter { + /** + * 根据传入的注解类型clazz,尝试返回与annotation匹配的注解实例。 + * + * @param clazz 目标注解类型的Class对象。 + * @return 如果annotation的类型与clazz一致,则返回annotation转换为T类型的实例;否则返回null。 + */ + override fun get(clazz: Class): T? = + if (annotation.javaClass == clazz) annotation as T else null + } + } } diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonConverter.kt index 6e2eefc..0c92f26 100644 --- a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonConverter.kt +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/BaseJsonConverter.kt @@ -16,195 +16,19 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile BaseJsonConverter.kt - * LastUpdate 2026-02-05 11:16:12 + * LastUpdate 2026-02-08 01:22:35 * UpdateUser MingLiPro */ package com.mingliqiye.utils.json.converters.base -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.core.JsonToken -import com.fasterxml.jackson.databind.* -import com.fasterxml.jackson.databind.deser.ContextualDeserializer -import com.fasterxml.jackson.databind.module.SimpleModule -import com.fasterxml.jackson.databind.ser.ContextualSerializer -import com.mingliqiye.utils.json.api.type.JsonTypeReference -import java.math.BigDecimal -import java.math.BigInteger - /** - * BaseJsonConverter 是一个通用的 JSON 转换器接口,用于定义对象之间的双向转换逻辑。 + * BaseJsonConverter 是一个泛型接口,用于定义 JSON 转换器的基本行为。 * - * @param F 源类型,表示需要被转换的对象类型。 - * @param T 目标类型,表示转换后的对象类型。 + * 该接口继承自 JackSonJsonConverter,并通过泛型参数 F 和 T 定义了转换的源类型和目标类型。 + * 实现该接口的类需要提供具体的 JSON 转换逻辑。 + * + * @param F 源类型,表示需要被转换的数据类型。 + * @param T 目标类型,表示转换后的数据类型。 */ -interface BaseJsonConverter { - - /** - * 将源对象转换为目标对象。 - * - * @param obj 源对象,可能为 null。 - * @param annotationGetter 注解获取器,用于获取字段上的注解信息。 - * @return 转换后的目标对象,可能为 null。 - * @throws Exception 转换过程中可能抛出的异常。 - */ - @Throws(Exception::class) - fun convert(obj: F?, annotationGetter: AnnotationGetter): T? - - /** - * 将目标对象反向转换为源对象。 - * - * @param obj 目标对象,可能为 null。 - * @param annotationGetter 注解获取器,用于获取字段上的注解信息。 - * @return 反向转换后的源对象,可能为 null。 - * @throws Exception 转换过程中可能抛出的异常。 - */ - @Throws(Exception::class) - fun deConvert(obj: T?, annotationGetter: AnnotationGetter): F? - - /** - * 获取源类型的类型引用。 - * - * @return 源类型的 JsonTypeReference 对象。 - * @throws Exception 获取过程中可能抛出的异常。 - */ - @Throws(Exception::class) - fun getFromType(): JsonTypeReference - - /** - * 获取目标类型的类型引用。 - * - * @return 目标类型的 JsonTypeReference 对象。 - * @throws Exception 获取过程中可能抛出的异常。 - */ - @Throws(Exception::class) - fun getToType(): JsonTypeReference - - /** - * 获取源类型的原始类。 - * - * @return 源类型的 Class 对象。 - * @throws Exception 获取过程中可能抛出的异常。 - */ - @Throws(Exception::class) - fun getFromClass() = getFromType().getRawType() - - /** - * 获取目标类型的原始类。 - * - * @return 目标类型的 Class 对象。 - * @throws Exception 获取过程中可能抛出的异常。 - */ - @Throws(Exception::class) - fun getToClass() = getToType().getRawType() - - /** - * 创建并返回一个 Jackson 模块,该模块包含自定义的序列化器和反序列化器。 - * - * @return 配置了自定义序列化器和反序列化器的 SimpleModule 对象。 - */ - fun getJacksonModule(): SimpleModule { - // 创建一个 Jackson 模块,名称由源类型和目标类型组成 - val module = SimpleModule("${getFromClass().name}To${getToClass().name}") - - // 定义自定义序列化器 - val serializer = object : JsonSerializer(), ContextualSerializer { - var property: BeanProperty? = null - - /** - * 序列化方法:将源对象转换为目标对象,并根据目标对象的类型写入 JSON。 - * - * @param value 源对象,可能为 null。 - * @param gen JSON 生成器,用于写入 JSON 数据。 - * @param provider 序列化提供者,可选参数。 - */ - override fun serialize( - value: F?, - gen: JsonGenerator, - provider: SerializerProvider? - ) { - // 执行转换逻辑 - val data: T? = convert(value, object : AnnotationGetter { - override fun get(clazz: Class): T? = property?.getAnnotation(clazz) - }) - - // 根据目标对象的类型写入对应的 JSON 值 - when (data) { - null -> gen.writeNull() - is String -> gen.writeString(data) - is Long -> gen.writeNumber(data) - is Short -> gen.writeNumber(data) - is Byte -> gen.writeNumber(data.toShort()) - is Int -> gen.writeNumber(data) - is BigDecimal -> gen.writeNumber(data) - is BigInteger -> gen.writeNumber(data) - is Float -> gen.writeNumber(data) - is Double -> gen.writeNumber(data) - is Boolean -> gen.writeBoolean(data) - else -> throw IllegalArgumentException("not sport data type ${data.javaClass} $data") - } - } - - /** - * 上下文感知方法:设置当前属性信息。 - * - * @param prov 序列化提供者,可选参数。 - * @param property 当前属性信息。 - * @return 返回当前序列化器实例。 - */ - override fun createContextual( - prov: SerializerProvider?, - property: BeanProperty? - ): JsonSerializer { - this.property = property - return this - } - } - - // 定义自定义反序列化器 - val deserializer = object : JsonDeserializer(), ContextualDeserializer { - /** - * 反序列化方法:从 JSON 解析数据并转换回源对象。 - * - * @param p JSON 解析器。 - * @param ctxt 反序列化上下文,可选参数。 - * @return 转换后的源对象,可能为 null。 - */ - override fun deserialize( - p: JsonParser, - ctxt: DeserializationContext? - ): F? { - // 处理 null 值情况 - if (p.currentToken == JsonToken.VALUE_NULL) return null - - // 执行反向转换逻辑 - return deConvert(p.readValueAs(getToClass()), object : AnnotationGetter { - override fun get(clazz: Class): T? = property?.getAnnotation(clazz) - }) - } - - var property: BeanProperty? = null - - /** - * 上下文感知方法:设置当前属性信息。 - * - * @param ctxt 反序列化上下文,可选参数。 - * @param property 当前属性信息。 - * @return 返回当前反序列化器实例。 - */ - override fun createContextual( - ctxt: DeserializationContext?, - property: BeanProperty? - ): JsonDeserializer { - this.property = property - return this - } - } - - // 将自定义序列化器和反序列化器注册到模块中 - return module - .addSerializer(getFromClass(), serializer) - .addDeserializer(getFromClass(), deserializer) - } -} +interface BaseJsonConverter : JackSonJsonConverter diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JackJsonDeserializer.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JackJsonDeserializer.kt new file mode 100644 index 0000000..854bf6f --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JackJsonDeserializer.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile JackJsonDeserializer.kt + * LastUpdate 2026-02-08 02:22:46 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.converters.base + +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.core.JsonToken +import com.fasterxml.jackson.databind.BeanProperty +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.deser.ContextualDeserializer + +class JackJsonDeserializer( + val property: BeanProperty? = null, + val jsonConverter: JsonConverter +) : JsonDeserializer(), ContextualDeserializer { + override fun deserialize( + p: JsonParser, + ctxt: DeserializationContext? + ): F? { + if (p.currentToken == JsonToken.VALUE_NULL) return null + return jsonConverter.deConvert(p.readValueAs(jsonConverter.getToClass()), object : AnnotationGetter { + override fun get(clazz: Class): T? = property?.getAnnotation(clazz) + }) + } + + /** + * 上下文感知方法:设置当前属性信息。 + * + * @param ctxt 反序列化上下文,可选参数。 + * @param property 当前属性信息。 + * @return 返回当前反序列化器实例。 + */ + override fun createContextual( + ctxt: DeserializationContext?, + property: BeanProperty? + ): JackJsonDeserializer { + return JackJsonDeserializer(property, jsonConverter) + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JackJsonSerializer.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JackJsonSerializer.kt new file mode 100644 index 0000000..a96d9c9 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JackJsonSerializer.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile JackJsonSerializer.kt + * LastUpdate 2026-02-08 02:27:52 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.converters.base + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.BeanProperty +import com.fasterxml.jackson.databind.JsonSerializer +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.ser.ContextualSerializer +import java.math.BigDecimal +import java.math.BigInteger + +class JackJsonSerializer( + val property: BeanProperty?, + val jsonConverter: JsonConverter +) : JsonSerializer(), ContextualSerializer { + + /** + * 序列化方法:将源对象转换为目标对象,并根据目标对象的类型写入 JSON。 + * + * @param value 源对象,可能为 null。 + * @param gen JSON 生成器,用于写入 JSON 数据。 + * @param provider 序列化提供者,可选参数。 + */ + override fun serialize( + value: F?, + gen: JsonGenerator, + provider: SerializerProvider? + ) { + // 执行转换逻辑 + val data: T? = jsonConverter.convert(value, object : AnnotationGetter { + override fun get(clazz: Class): T? = property?.getAnnotation(clazz) + }) + + // 根据目标对象的类型写入对应的 JSON 值 + when (data) { + null -> gen.writeNull() + is String -> gen.writeString(data) + is Long -> gen.writeNumber(data) + is Short -> gen.writeNumber(data) + is Byte -> gen.writeNumber(data.toShort()) + is Int -> gen.writeNumber(data) + is BigDecimal -> gen.writeNumber(data) + is BigInteger -> gen.writeNumber(data) + is Float -> gen.writeNumber(data) + is Double -> gen.writeNumber(data) + is Boolean -> gen.writeBoolean(data) + else -> throw IllegalArgumentException("not sport data type ${data.javaClass} $data") + } + } + + /** + * 上下文感知方法:设置当前属性信息。 + * + * @param prov 序列化提供者,可选参数。 + * @param property 当前属性信息。 + * @return 返回当前序列化器实例。 + */ + override fun createContextual( + prov: SerializerProvider?, + property: BeanProperty? + ): JsonSerializer { + return JackJsonSerializer(property, jsonConverter) + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JackSonJsonConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JackSonJsonConverter.kt new file mode 100644 index 0000000..7214231 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JackSonJsonConverter.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile JackSonJsonConverter.kt + * LastUpdate 2026-02-08 02:28:49 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.json.converters.base + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.module.SimpleModule + +interface JackSonJsonConverter : JsonConverter { + + companion object { + /** + * 扩展ObjectMapper,用于注册指定类型的模块。 + * + * 此函数通过反射创建指定类型的实例,并调用其getJacksonModule方法来获取Jackson模块, + * 然后将该模块注册到当前ObjectMapper实例中。 + * + * @param T 必须继承自BaseJsonConverter的泛型类型。 + * @return 返回注册了模块后的ObjectMapper实例。 + */ + inline fun > ObjectMapper.addJsonConverter(): ObjectMapper = + this.registerModule(getJsonConverter().getJacksonModule()) + + @JvmStatic + fun > ObjectMapper.addJsonConverter(clazz: Class): ObjectMapper = + this.registerModule(getJsonConverter(clazz).getJacksonModule()) + } + + /** + * 创建并返回一个 Jackson 模块,该模块包含自定义的序列化器和反序列化器。 + * + * @return 配置了自定义序列化器和反序列化器的 SimpleModule 对象。 + */ + fun getJacksonModule(): SimpleModule = + SimpleModule("${getFromClass().name}To${getToClass().name}") + .addSerializer(getFromClass(), JackJsonSerializer(null, this)) + .addDeserializer(getFromClass(), JackJsonDeserializer(null, this)) +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JsonConverter.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JsonConverter.kt index 1a6d26b..d14ed29 100644 --- a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JsonConverter.kt +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JsonConverter.kt @@ -16,44 +16,76 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile JsonConverter.kt - * LastUpdate 2026-02-05 11:18:33 + * LastUpdate 2026-02-07 22:30:18 * UpdateUser MingLiPro */ -@file:JvmName("JsonConverter") package com.mingliqiye.utils.json.converters.base - -import com.fasterxml.jackson.databind.ObjectMapper -import java.lang.reflect.ParameterizedType -import java.lang.reflect.Type +import com.mingliqiye.utils.json.api.type.JsonTypeReference /** - * 获取给定类型的实际类对象。 + * BaseJsonConverter 是一个通用的 JSON 转换器接口,用于定义对象之间的双向转换逻辑。 * - * @param type 类型对象,可以是Class、ParameterizedType或其他Type的实现。 - * @return 返回与给定类型对应的Class对象;如果无法解析,则返回null。 + * @param F 源类型,表示需要被转换的对象类型。 + * @param T 目标类型,表示转换后的对象类型。 */ -fun getClass(type: Type?): Class<*>? { - // 尝试将type直接转换为Class类型,如果失败则检查是否为ParameterizedType - val clazz: Class<*>? = type as? Class<*> ?: if (type is ParameterizedType) { - // 如果是ParameterizedType,则递归获取其原始类型 - getClass(type.rawType) - } else { - // 其他情况返回null - null - } - return clazz +interface JsonConverter { + /** + * 将源对象转换为目标对象。 + * + * @param obj 源对象,可能为 null。 + * @param annotationGetter 注解获取器,用于获取字段上的注解信息。 + * @return 转换后的目标对象,可能为 null。 + * @throws Exception 转换过程中可能抛出的异常。 + */ + @Throws(Exception::class) + fun convert(obj: F?, annotationGetter: AnnotationGetter): T? + + /** + * 将目标对象反向转换为源对象。 + * + * @param obj 目标对象,可能为 null。 + * @param annotationGetter 注解获取器,用于获取字段上的注解信息。 + * @return 反向转换后的源对象,可能为 null。 + * @throws Exception 转换过程中可能抛出的异常。 + */ + @Throws(Exception::class) + fun deConvert(obj: T?, annotationGetter: AnnotationGetter): F? + + /** + * 获取源类型的类型引用。 + * + * @return 源类型的 JsonTypeReference 对象。 + * @throws Exception 获取过程中可能抛出的异常。 + */ + @Throws(Exception::class) + fun getFromType(): JsonTypeReference + + /** + * 获取目标类型的类型引用。 + * + * @return 目标类型的 JsonTypeReference 对象。 + * @throws Exception 获取过程中可能抛出的异常。 + */ + @Throws(Exception::class) + fun getToType(): JsonTypeReference + + /** + * 获取源类型的原始类。 + * + * @return 源类型的 Class 对象。 + * @throws Exception 获取过程中可能抛出的异常。 + */ + @Throws(Exception::class) + fun getFromClass() = getFromType().getRawType() + + /** + * 获取目标类型的原始类。 + * + * @return 目标类型的 Class 对象。 + * @throws Exception 获取过程中可能抛出的异常。 + */ + @Throws(Exception::class) + fun getToClass() = getToType().getRawType() } - -/** - * 扩展ObjectMapper,用于注册指定类型的模块。 - * - * 此函数通过反射创建指定类型的实例,并调用其getJacksonModule方法来获取Jackson模块, - * 然后将该模块注册到当前ObjectMapper实例中。 - * - * @param T 必须继承自BaseJsonConverter的泛型类型。 - * @return 返回注册了模块后的ObjectMapper实例。 - */ -inline fun > ObjectMapper.registerModule(): ObjectMapper = - this.registerModule(T::class.java.newInstance().getJacksonModule()) diff --git a/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JsonConverterUtils.kt b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JsonConverterUtils.kt new file mode 100644 index 0000000..9ea6890 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/json/converters/base/JsonConverterUtils.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2026 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile JsonConverterUtils.kt + * LastUpdate 2026-02-07 22:33:09 + * UpdateUser MingLiPro + */ +@file:JvmName("JsonConverterUtils") + +package com.mingliqiye.utils.json.converters.base + + +import com.mingliqiye.utils.logger.MingLiLoggerFactory +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type + +val log = MingLiLoggerFactory.getLogger("com.mingliqiye.utils.json.converters.base.JsonConverterUtils") + +/** + * 获取给定类型的实际类对象。 + * + * @param type 类型对象,可以是Class、ParameterizedType或其他Type的实现。 + * @return 返回与给定类型对应的Class对象;如果无法解析,则返回null。 + */ +fun getClass(type: Type?): Class<*>? { + // 尝试将type直接转换为Class类型,如果失败则检查是否为ParameterizedType + val clazz: Class<*>? = type as? Class<*> ?: if (type is ParameterizedType) { + // 如果是ParameterizedType,则递归获取其原始类型 + getClass(type.rawType) + } else { + null + } + return clazz +} + +inline fun > getJsonConverter(): T = getJsonConverter(T::class.java) + +fun > getJsonConverter(clazz: Class): T { + try { + return clazz.getDeclaredMethod( + "getJsonConverter" + ).invoke(null) as T + } catch (e: Exception) { + log.error("实现于 JsonConverter<*, *> 的类 必须实现静态方法 JsonConverter<*, *> getJsonConverter()", e) + throw e + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/logger/Loggers.kt b/src/main/kotlin/com/mingliqiye/utils/logger/MingLiLogger.kt similarity index 97% rename from src/main/kotlin/com/mingliqiye/utils/logger/Loggers.kt rename to src/main/kotlin/com/mingliqiye/utils/logger/MingLiLogger.kt index 5e54fc1..a5605fc 100644 --- a/src/main/kotlin/com/mingliqiye/utils/logger/Loggers.kt +++ b/src/main/kotlin/com/mingliqiye/utils/logger/MingLiLogger.kt @@ -15,27 +15,16 @@ * * ProjectName mingli-utils * ModuleName mingli-utils.main - * CurrentFile Loggers.kt - * LastUpdate 2026-02-05 10:20:31 + * CurrentFile MingLiLogger.kt + * LastUpdate 2026-02-06 08:27:44 * UpdateUser MingLiPro */ -@file:JvmName("Loggers") - package com.mingliqiye.utils.logger - import org.slf4j.Logger import org.slf4j.Marker -enum class MingLiLoggerLevel { - TRACE, - DEBUG, - INFO, - WARN, - ERROR -} - class MingLiLogger(private val name: String) : Logger { override fun getName(): String { return name @@ -312,8 +301,7 @@ class MingLiLogger(private val name: String) : Logger { override fun error(msg: String?, t: Throwable?) { msg?.let { - val message = if (t != null) "$it: ${t.message}" else it - toPrintln(message, MingLiLoggerLevel.ERROR) + toPrintln(it, MingLiLoggerLevel.ERROR) } } diff --git a/src/main/kotlin/com/mingliqiye/utils/stream/StreamEmptyException.kt b/src/main/kotlin/com/mingliqiye/utils/logger/MingLiLoggerLevel.kt similarity index 70% rename from src/main/kotlin/com/mingliqiye/utils/stream/StreamEmptyException.kt rename to src/main/kotlin/com/mingliqiye/utils/logger/MingLiLoggerLevel.kt index 66e4955..5eee5ec 100644 --- a/src/main/kotlin/com/mingliqiye/utils/stream/StreamEmptyException.kt +++ b/src/main/kotlin/com/mingliqiye/utils/logger/MingLiLoggerLevel.kt @@ -15,15 +15,17 @@ * * ProjectName mingli-utils * ModuleName mingli-utils.main - * CurrentFile StreamEmptyException.kt - * LastUpdate 2026-01-07 19:13:29 + * CurrentFile MingLiLoggerLevel.kt + * LastUpdate 2026-02-06 08:27:44 * UpdateUser MingLiPro */ -package com.mingliqiye.utils.stream +package com.mingliqiye.utils.logger -class StreamEmptyException : RuntimeException { - constructor(message: String) : super(message) - - constructor(message: String, cause: Throwable) : super(message, cause) +enum class MingLiLoggerLevel { + TRACE, + DEBUG, + INFO, + WARN, + ERROR } diff --git a/src/main/kotlin/com/mingliqiye/utils/mybatisplus/BaseMapperQuery.kt b/src/main/kotlin/com/mingliqiye/utils/mybatisplus/BaseMapperQuery.kt index a866e57..5f5f6cd 100644 --- a/src/main/kotlin/com/mingliqiye/utils/mybatisplus/BaseMapperQuery.kt +++ b/src/main/kotlin/com/mingliqiye/utils/mybatisplus/BaseMapperQuery.kt @@ -15,8 +15,8 @@ * * ProjectName mingli-utils * ModuleName mingli-utils.main - * CurrentFile QueryWrapper.kt - * LastUpdate 2026-02-03 12:03:27 + * CurrentFile BaseMapperQuery.kt + * LastUpdate 2026-02-06 13:15:11 * UpdateUser MingLiPro */ @@ -27,41 +27,52 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper import com.baomidou.mybatisplus.core.mapper.BaseMapper +import com.baomidou.mybatisplus.extension.kotlin.KtQueryChainWrapper import com.baomidou.mybatisplus.extension.kotlin.KtQueryWrapper import com.baomidou.mybatisplus.extension.kotlin.KtUpdateChainWrapper import com.baomidou.mybatisplus.extension.kotlin.KtUpdateWrapper + +@Deprecated( + "rename to FastBaseMapper", + replaceWith = ReplaceWith( + expression = "FastBaseMapper<*>", + imports = ["com.mingliqiye.utils.mybatisplus.FastBaseMapper"] + ), + level = DeprecationLevel.WARNING +) /** - * BaseMapperQuery接口扩展了BaseMapper,提供了通用的查询包装器功能 - * - * @param T 实体类类型 + * @since 4.6.1 + * 已经重命名 [FastBaseMapper] */ -interface BaseMapperQuery : BaseMapper { +interface BaseMapperQuery : FastBaseMapper + +/** + * 扩展[BaseMapper]接口,提供便捷的查询和更新包装器创建方法 + * 包含普通包装器、`Lambda`包装器和`Kotlin`专用包装器的创建方法 + */ +interface FastBaseMapper : BaseMapper { /** - * 创建并返回一个新的QueryWrapper实例 - * - * @return QueryWrapper 返回类型化的查询包装器实例 + * 创建[QueryWrapper]实例 + * @return QueryWrapper 查询包装器实例 */ fun queryWrapper() = QueryWrapper() /** - * 创建并返回一个新的UpdateWrapper实例 - * - * @return UpdateWrapper 返回类型化的更新包装器实例 + * 创建[UpdateWrapper]实例 + * @return UpdateWrapper 更新包装器实例 */ fun updateWrapper() = UpdateWrapper() /** - * 创建并返回一个新的LambdaQueryWrapper实例 - * - * @return LambdaQueryWrapper 返回类型化的Lambda查询包装器实例 + * 创建[LambdaQueryWrapper]实例 + * @return LambdaQueryWrapper Lambda查询包装器实例 */ fun lambdaQueryWrapper() = LambdaQueryWrapper() /** - * 创建并返回一个新的LambdaUpdateWrapper实例 - * - * @return LambdaUpdateWrapper 返回类型化的Lambda更新包装器实例 + * 创建[LambdaUpdateWrapper]实例 + * @return LambdaUpdateWrapper Lambda更新包装器实例 */ fun lambdaUpdateWrapper() = LambdaUpdateWrapper() @@ -69,56 +80,77 @@ interface BaseMapperQuery : BaseMapper { companion object { /** - * 创建并返回一个新的QueryWrapper实例 - * - * @return QueryWrapper 返回类型化的查询包装器实例 + * 为[BaseMapper]创建[QueryWrapper]实例 + * @param T 范型类型,通过reified获取实际类型 + * @receiver BaseMapper 基础映射器实例 + * @return QueryWrapper 查询包装器实例,使用T类作为参数 */ - inline fun BaseMapper.queryWrapper(): QueryWrapper = QueryWrapper() + inline fun BaseMapper.queryWrapper(): QueryWrapper = QueryWrapper(T::class.java) /** - * 创建并返回一个新的UpdateWrapper实例 - * - * @return UpdateWrapper 返回类型化的更新包装器实例 + * 为[BaseMapper]创建[UpdateWrapper]实例 + * @param T 范型类型,通过reified获取实际类型 + * @receiver BaseMapper 基础映射器实例 + * @return UpdateWrapper 更新包装器实例 */ inline fun BaseMapper.updateWrapper(): UpdateWrapper = UpdateWrapper() /** - * 创建并返回一个新的LambdaQueryWrapper实例 - * - * @return LambdaQueryWrapper 返回类型化的Lambda查询包装器实例 + * 为[BaseMapper]创建[LambdaQueryWrapper]实例 + * @param T 范型类型,通过reified获取实际类型 + * @receiver BaseMapper 基础映射器实例 + * @return LambdaQueryWrapper Lambda查询包装器实例,使用T类作为参数 */ inline fun BaseMapper.lambdaQueryWrapper(): LambdaQueryWrapper = LambdaQueryWrapper(T::class.java) /** - * 创建并返回一个新的LambdaUpdateWrapper实例 - * - * @return LambdaUpdateWrapper 返回类型化的Lambda更新包装器实例 + * 为[BaseMapper]创建[LambdaUpdateWrapper]实例 + * @param T 范型类型,通过reified获取实际类型 + * @receiver BaseMapper 基础映射器实例 + * @return LambdaUpdateWrapper Lambda更新包装器实例,使用T类作为参数 */ inline fun BaseMapper.lambdaUpdateWrapper(): LambdaUpdateWrapper = LambdaUpdateWrapper(T::class.java) + /** - * 创建并返回一个新的KtUpdateWrapper实例 - * - * @return KtUpdateWrapper 返回类型化的Kotlin更新包装器实例 + * 为[BaseMapper]创建[KtQueryWrapper]实例 + * @param T 范型类型,必须继承自Any,通过reified获取实际类型 + * @receiver BaseMapper 基础映射器实例 + * @return KtQueryWrapper Kotlin查询包装器实例,使用T类作为参数 + */ + inline fun BaseMapper.ktQueryWrapper(): KtQueryWrapper = KtQueryWrapper(T::class.java) + + /** + * 为[BaseMapper]创建[KtQueryChainWrapper]实例 + * @param T 范型类型,必须继承自Any,通过reified获取实际类型 + * @receiver BaseMapper 基础映射器实例 + * @return KtQueryChainWrapper Kotlin查询链式包装器实例,使用T类作为参数 + */ + inline fun BaseMapper.ktQueryChainWrapper(): KtQueryChainWrapper = + KtQueryChainWrapper(T::class.java) + + /** + * 为[BaseMapper]创建[KtUpdateWrapper]实例 + * @param T 范型类型,必须继承自Any,通过reified获取实际类型 + * @receiver BaseMapper 基础映射器实例 + * @return KtUpdateWrapper Kotlin更新包装器实例,使用T类作为参数 */ inline fun BaseMapper.ktUpdateWrapper(): KtUpdateWrapper = KtUpdateWrapper(T::class.java) /** - * 创建并返回一个新的KtQueryWrapper实例 - * - * @return KtQueryWrapper 返回类型化的Kotlin查询包装器实例 - */ - inline fun BaseMapper.ktQueryWrapper(): KtQueryWrapper = KtQueryWrapper(T::class.java) - - /** - * 创建并返回一个新的KtUpdateChainWrapper实例 - * - * @return KtUpdateChainWrapper 返回类型化的Kotlin更新链式包装器实例 + * 为[BaseMapper]创建[KtUpdateChainWrapper]实例 + * @param T 范型类型,必须继承自Any,通过reified获取实际类型 + * @receiver BaseMapper 基础映射器实例 + * @return KtUpdateChainWrapper Kotlin更新链式包装器实例,使用T类作为参数 */ inline fun BaseMapper.ktUpdateChainWrapper(): KtUpdateChainWrapper = KtUpdateChainWrapper(T::class.java) + + } + + } diff --git a/src/main/kotlin/com/mingliqiye/utils/require/Require.kt b/src/main/kotlin/com/mingliqiye/utils/require/Require.kt index 21c23e5..0533a05 100644 --- a/src/main/kotlin/com/mingliqiye/utils/require/Require.kt +++ b/src/main/kotlin/com/mingliqiye/utils/require/Require.kt @@ -16,12 +16,13 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile Require.kt - * LastUpdate 2026-02-05 11:08:03 + * LastUpdate 2026-02-06 08:27:44 * UpdateUser MingLiPro */ package com.mingliqiye.utils.require +import com.mingliqiye.utils.exception.InternalServerErrorException import com.mingliqiye.utils.logger.MingLiLoggerFactory import org.slf4j.Logger import java.lang.reflect.Constructor @@ -32,19 +33,136 @@ import kotlin.contracts.contract * 工具对象,用于提供条件检查功能。 * 支持抛出自定义异常、延迟消息构造以及日志记录等功能。 */ -object Require { +class Require(val defaultException: Class) { - /** - * 日志记录器实例,用于记录错误信息。 - */ - @JvmStatic - var logger: Logger? = MingLiLoggerFactory.getLogger() + companion object { + @JvmStatic + val RequireLayz by lazy { + Require(IllegalArgumentException::class.java) + } - /** - * 控制是否在抛出异常时记录错误日志。 - */ - @JvmStatic - var isLogError = true + @JvmStatic + val RequireHttpLayz by lazy { + Require(InternalServerErrorException::class.java) + } + + @JvmStatic + var logger: Logger? = MingLiLoggerFactory.getLogger() + + @JvmStatic + var isLogError = true + + /** + * 检查给定条件是否为真。如果为假,则使用延迟消息构造器生成异常并抛出。 + * + * @param value 需要检查的布尔值。 + * @param throwable 异常类型。 + * @param layzMessage 延迟消息构造器。 + */ + @OptIn(ExperimentalContracts::class) + fun require(value: Boolean, throwable: Class, layzMessage: RequireLayzMessageConstructor) { + contract { + returns() implies value + } + require(value, layzMessage.call(), throwable) + } + + /** + * 检查给定条件是否为真。如果为假,则使用延迟异常构造器生成异常并抛出。 + * + * @param value 需要检查的布尔值。 + * @param layzThrowable 延迟异常构造器。 + */ + @JvmStatic + @OptIn(ExperimentalContracts::class) + fun require(value: Boolean, layzThrowable: RequireLayzExceptionConstructor) { + contract { + returns() implies value + } + if (!value) throwThrowable(layzThrowable.call()) + } + + /** + * 检查给定条件是否为真。如果为假,则根据指定的异常类型和消息生成异常并抛出。 + * + * @param value 需要检查的布尔值。 + * @param message 异常消息。 + * @param throwable 异常类型。 + */ + @JvmStatic + @OptIn(ExperimentalContracts::class) + fun require(value: Boolean, message: String, throwable: Class) { + contract { + returns() implies value + } + if (!value) throwThrowable(getExceptionConstructor(throwable).newInstance(message)) + } + + /** + * 抛出指定的异常,并在启用日志记录时记录错误信息。 + * + * @param throwable 需要抛出的异常。 + */ + @JvmStatic + fun throwThrowable(throwable: Throwable) { + if (isLogError && logger != null) logger!!.error(throwable.message, throwable) + throw throwable + } + + /** + * 检查给定条件是否为真。如果为假,则根据指定的异常类型抛出异常。 + * + * @param value 需要检查的布尔值。 + * @param message 异常消息。 + * @param T 异常类型。 + */ + @OptIn(ExperimentalContracts::class) + @JvmName("__inline_Require") + inline fun require(value: Boolean, message: String) { + contract { + returns() implies value + } + require(value, message, T::class.java) + } + + /** + * 检查给定条件是否为真。如果为假,则根据指定的异常类型和延迟消息构造器生成异常并抛出。 + * + * @param value 需要检查的布尔值。 + * @param layzMessage 延迟消息构造器。 + * @param T 异常类型。 + */ + @OptIn(ExperimentalContracts::class) + @JvmName("__inline_Require") + inline fun require(value: Boolean, layzMessage: RequireLayzMessageConstructor) { + contract { + returns() implies value + } + if (!value) throwThrowable(getExceptionConstructor().newInstance(layzMessage.call())) + } + + /** + * 获取指定异常类型的构造函数(带字符串参数)。 + * + * @param T 异常类型。 + * @return 异常类型的构造函数。 + */ + @JvmName("__inline_GetExceptionConstructor") + inline fun getExceptionConstructor(): Constructor = + getExceptionConstructor(T::class.java) + + /** + * 获取指定异常类型的构造函数(带字符串参数)。 + * + * @param throwable 异常类型。 + * @return 异常类型的构造函数。 + */ + + @JvmStatic + @JvmName("__GetExceptionConstructor") + fun getExceptionConstructor(throwable: Class): Constructor = + throwable.getConstructor(String::class.java) + } /** * 检查给定条件是否为真。如果为假,则抛出 [IllegalArgumentException] 异常。 @@ -52,28 +170,25 @@ object Require { * @param value 需要检查的布尔值。 */ @OptIn(ExperimentalContracts::class) - @JvmStatic fun require(value: Boolean) { contract { returns() implies value } - require(value, "the require conditions are not met.") + require(value, "the require conditions are not met.") } /** - * 检查给定条件是否为真。如果为假,则根据指定的异常类型抛出异常。 + * 检查给定条件是否为真。如果为假,则使用延迟消息构造器生成 [IllegalArgumentException] 并抛出。 * * @param value 需要检查的布尔值。 - * @param message 异常消息。 - * @param T 异常类型。 + * @param layzMessage 延迟消息构造器。 */ @OptIn(ExperimentalContracts::class) - @JvmName("__inline_Require") - inline fun require(value: Boolean, message: String) { + fun requireLayzMessage(value: Boolean, layzMessage: RequireLayzMessageConstructor) { contract { returns() implies value } - require(value, message, T::class.java) + require(value, defaultException, layzMessage) } /** @@ -87,114 +202,6 @@ object Require { contract { returns() implies value } - if (!value) throwThrowable(getExceptionConstructor().newInstance(message)) + if (!value) throwThrowable(getExceptionConstructor(defaultException).newInstance(message)) } - - /** - * 检查给定条件是否为真。如果为假,则使用延迟消息构造器生成异常并抛出。 - * - * @param value 需要检查的布尔值。 - * @param throwable 异常类型。 - * @param layzMessage 延迟消息构造器。 - */ - @OptIn(ExperimentalContracts::class) - @JvmStatic - fun require(value: Boolean, throwable: Class, layzMessage: RequireLayzMessageConstructor) { - contract { - returns() implies value - } - require(value, layzMessage.call(), throwable) - } - - /** - * 检查给定条件是否为真。如果为假,则使用延迟异常构造器生成异常并抛出。 - * - * @param value 需要检查的布尔值。 - * @param layzThrowable 延迟异常构造器。 - */ - @OptIn(ExperimentalContracts::class) - @JvmStatic - fun require(value: Boolean, layzThrowable: RequireLayzExceptionConstructor) { - contract { - returns() implies value - } - if (!value) throwThrowable(layzThrowable.call()) - } - - /** - * 检查给定条件是否为真。如果为假,则使用延迟消息构造器生成 [IllegalArgumentException] 并抛出。 - * - * @param value 需要检查的布尔值。 - * @param layzMessage 延迟消息构造器。 - */ - @OptIn(ExperimentalContracts::class) - @JvmStatic - fun requireLayzMessage(value: Boolean, layzMessage: RequireLayzMessageConstructor) { - contract { - returns() implies value - } - require(value, layzMessage) - } - - /** - * 检查给定条件是否为真。如果为假,则根据指定的异常类型和延迟消息构造器生成异常并抛出。 - * - * @param value 需要检查的布尔值。 - * @param layzMessage 延迟消息构造器。 - * @param T 异常类型。 - */ - @OptIn(ExperimentalContracts::class) - @JvmName("__inline_Require") - inline fun require(value: Boolean, layzMessage: RequireLayzMessageConstructor) { - contract { - returns() implies value - } - if (!value) throwThrowable(getExceptionConstructor().newInstance(layzMessage.call())) - } - - /** - * 检查给定条件是否为真。如果为假,则根据指定的异常类型和消息生成异常并抛出。 - * - * @param value 需要检查的布尔值。 - * @param message 异常消息。 - * @param throwable 异常类型。 - */ - @OptIn(ExperimentalContracts::class) - @JvmStatic - fun require(value: Boolean, message: String, throwable: Class) { - contract { - returns() implies value - } - if (!value) throwThrowable(getExceptionConstructor(throwable).newInstance(message)) - } - - /** - * 抛出指定的异常,并在启用日志记录时记录错误信息。 - * - * @param throwable 需要抛出的异常。 - */ - fun throwThrowable(throwable: Throwable) { - if (isLogError && logger != null) logger!!.error(throwable.message, throwable) - throw throwable - } - - /** - * 获取指定异常类型的构造函数(带字符串参数)。 - * - * @param T 异常类型。 - * @return 异常类型的构造函数。 - */ - @JvmName("__inline_GetExceptionConstructor") - inline fun getExceptionConstructor(): Constructor = - getExceptionConstructor(T::class.java) - - /** - * 获取指定异常类型的构造函数(带字符串参数)。 - * - * @param throwable 异常类型。 - * @return 异常类型的构造函数。 - */ - @JvmName("__GetExceptionConstructor") - fun getExceptionConstructor(throwable: Class): Constructor = - throwable.getConstructor(String::class.java) } diff --git a/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/AutoConfiguration.kt b/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/AutoConfiguration.kt index 8d96c71..646bb14 100644 --- a/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/AutoConfiguration.kt +++ b/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/AutoConfiguration.kt @@ -16,12 +16,14 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile AutoConfiguration.kt - * LastUpdate 2026-02-05 10:45:05 + * LastUpdate 2026-02-06 15:15:46 * UpdateUser MingLiPro */ package com.mingliqiye.utils.springboot.autoconfigure +import com.mingliqiye.utils.i18n.I18N +import com.mingliqiye.utils.i18n.I18N.infoTranslater import com.mingliqiye.utils.logger.MingLiLoggerFactory import com.mingliqiye.utils.system.computerName import com.mingliqiye.utils.system.getPid @@ -50,15 +52,14 @@ open class AutoConfiguration { * 启动横幅字符串,包含艺术字体和占位符。 */ private const val BANNER = - "---------------------------------------------------------\n" + - "| $$\\ $$\\ $$\\ $$\\ $$\\ $$$$$$$$\\ $$$$$$\\ |\n" + - "| $$$\\ $$$ |$$ | $$ | $$ |\\__$$ __|$$ __$$\\ |\n" + - "| $$$$\\ $$$$ |$$ | $$ | $$ | $$ | $$ / \\__| |\n" + - "| $$\\$$\\$$ $$ |$$ | $$ | $$ | $$ | \\$$$$$$\\ |\n" + - "| $$ \\$$$ $$ |$$ | $$ | $$ | $$ | \\____$$\\ |\n" + - "| $$ |\\$ /$$ |$$ | $$ | $$ | $$ | $$\\ $$ | |\n" + - "| $$ | \\_/ $$ |$$$$$$$$\\\\$$$$$$ | $$ | \\$$$$$$ | |\n" + - "| \\__| \\__|\\________|\\______/ \\__| \\______/ |\n" + " | $$\\ $$\\ $$\\ $$\\ $$\\ $$$$$$$$\\ $$$$$$\\ \n" + + "| $$$\\ $$$ |$$ | $$ | $$ |\\__$$ __|$$ __$$\\ \n" + + "| $$$$\\ $$$$ |$$ | $$ | $$ | $$ | $$ / \\__| \n" + + "| $$\\$$\\$$ $$ |$$ | $$ | $$ | $$ | \\$$$$$$\\ \n" + + "| $$ \\$$$ $$ |$$ | $$ | $$ | $$ | \\____$$\\ \n" + + "| $$ |\\$ /$$ |$$ | $$ | $$ | $$ | $$\\ $$ | \n" + + "| $$ | \\_/ $$ |$$$$$$$$\\\\$$$$$$ | $$ | \\$$$$$$ \n" + + "| \\__| \\__|\\________|\\______/ \\__| \\______/ \n|\n" /** * 打印启动横幅,包含系统元数据(如JDK版本、进程ID、计算机名等)。 @@ -84,22 +85,25 @@ open class AutoConfiguration { // 解析元数据并添加额外的系统信息 val da = metaData.toString().split("\n").toMutableList() - da.add("jdkRuntime=$jdkVersion") - da.add("pid=$getPid") - da.add("computerName=$computerName") - da.add("userName=$userName") - da.add("time=" + DateTime.now().format(Formatter.STANDARD_DATETIME_MILLISECOUND7, false)) + da.add("${I18N.getString("com.mingliqiye.utils.springboot.autoconfigure.AutoConfiguration.jdkRuntime")}=$jdkVersion") + da.add("${I18N.getString("com.mingliqiye.utils.springboot.autoconfigure.AutoConfiguration.pid")}=$getPid") + da.add("${I18N.getString("com.mingliqiye.utils.springboot.autoconfigure.AutoConfiguration.computerName")}=$computerName") + da.add("${I18N.getString("com.mingliqiye.utils.springboot.autoconfigure.AutoConfiguration.userName")}=$userName") + da.add( + "${I18N.getString("com.mingliqiye.utils.springboot.autoconfigure.AutoConfiguration.time")}=" + DateTime.now() + .format(Formatter.STANDARD_DATETIME_MILLISECOUND7, false) + ) // 格式化每条元数据并追加到横幅中 da.forEach { s: String -> val d = s.trim { it <= ' ' }.split("=".toRegex(), 2).toTypedArray() if (d.size >= 2) { - val content = "| " + d[0] + ": " + d[1] + val content = "| -> " + I18N.getString(d[0]) + ": " + d[1] val targetLength = 56 if (content.length < targetLength) { bannerBuilder.append( String.format( - "%-" + targetLength + "s|\n", + "%-${targetLength}s\n", content ) ) @@ -117,8 +121,9 @@ open class AutoConfiguration { } // 输出最终构建的横幅 + println("") println(bannerBuilder.toString().trim()) - println("---------------------------------------------------------") + println("") } } @@ -129,6 +134,6 @@ open class AutoConfiguration { */ init { printBanner() - log.info("MingliUtils AutoConfigurationBean succeed") + log.infoTranslater("com.mingliqiye.utils.springboot.autoconfigure.AutoConfiguration.bean") } } diff --git a/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/JacksonAutoConfiguration.kt b/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/JacksonAutoConfiguration.kt index a16a6a1..eadfbe4 100644 --- a/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/JacksonAutoConfiguration.kt +++ b/src/main/kotlin/com/mingliqiye/utils/springboot/autoconfigure/JacksonAutoConfiguration.kt @@ -16,19 +16,20 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile JacksonAutoConfiguration.kt - * LastUpdate 2026-02-05 10:45:19 + * LastUpdate 2026-02-08 01:29:21 * UpdateUser MingLiPro */ package com.mingliqiye.utils.springboot.autoconfigure import com.fasterxml.jackson.databind.ObjectMapper +import com.mingliqiye.utils.i18n.I18N.infoTranslater import com.mingliqiye.utils.json.api.JSONA import com.mingliqiye.utils.json.api.JacksonJsonApi import com.mingliqiye.utils.json.api.base.JsonApi import com.mingliqiye.utils.json.converters.DateTimeJsonConverter import com.mingliqiye.utils.json.converters.UUIDJsonConverter -import com.mingliqiye.utils.json.converters.base.registerModule +import com.mingliqiye.utils.json.converters.base.JackSonJsonConverter.Companion.addJsonConverter import com.mingliqiye.utils.logger.MingLiLoggerFactory import org.slf4j.Logger import org.springframework.boot.autoconfigure.AutoConfigureAfter @@ -76,10 +77,10 @@ open class JacksonAutoConfiguration(objectMapper: ObjectMapper) { * 注册自定义的 UUID 和 DateTime JSON 转换器模块到 ObjectMapper 中。 */ init { - log.info("MingliUtils Jackson Serializers created") + log.infoTranslater("com.mingliqiye.utils.springboot.autoconfigure.JsonApiAutoConfiguration.jacksonserializers") objectMapper - .registerModule() - .registerModule() + .addJsonConverter() + .addJsonConverter() } /* @@ -93,13 +94,16 @@ open class JacksonAutoConfiguration(objectMapper: ObjectMapper) { @Primary @ConditionalOnMissingBean open fun jsonApi(objectMapper: ObjectMapper): JsonApi { - log.info("MingliUtils-JsonApiAutoConfiguration: JacksonJsonApi bean is created.") return JacksonJsonApi(objectMapper).also { + log.infoTranslater("com.mingliqiye.utils.springboot.autoconfigure.JsonApiAutoConfiguration.jsonapiconfiged") try { JSONA.getJsonApi() } catch (_: NullPointerException) { JSONA.setJsonApi(it) - log.info("JSONA Use {}", it.javaClass.name) + log.infoTranslater( + "com.mingliqiye.utils.springboot.autoconfigure.JsonApiAutoConfiguration.jsonause", + it.javaClass.name + ) } } } diff --git a/src/main/kotlin/com/mingliqiye/utils/string/StringUtils.kt b/src/main/kotlin/com/mingliqiye/utils/string/StringUtils.kt index b86f178..60792ec 100644 --- a/src/main/kotlin/com/mingliqiye/utils/string/StringUtils.kt +++ b/src/main/kotlin/com/mingliqiye/utils/string/StringUtils.kt @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile StringUtils.kt - * LastUpdate 2026-02-05 11:05:33 + * LastUpdate 2026-02-06 08:35:07 * UpdateUser MingLiPro */ @file:JvmName("StringUtils") @@ -24,6 +24,7 @@ package com.mingliqiye.utils.string import com.mingliqiye.utils.base.BASE16 +import com.mingliqiye.utils.i18n.I18N.warnTranslater import com.mingliqiye.utils.logger.MingLiLoggerFactory import com.mingliqiye.utils.objects.isNull import java.net.URLDecoder @@ -99,15 +100,18 @@ fun format(str: String, vararg args: Any?): String { // 检查参数数量 val placeholderCount = matches.count() if (placeholderCount != args.size) { - log.warn("Placeholder count: $placeholderCount, Argument count: ${args.size}") - log.warn("template : $str") - log.warn( - "Arguments : [${ - ", ".join(args) { - if (it.isNull()) return@join "null:null" - "${it.javaClass.simpleName}:$it" - } - }]") + log.warnTranslater( + "com.mingliqiye.utils.string.StringUtils.format.warn.placeholder", + placeholderCount, args.size + ) + log.warnTranslater("com.mingliqiye.utils.string.StringUtils.format.warn.template", str) + log.warnTranslater( + "com.mingliqiye.utils.string.StringUtils.format.warn.arguments", + ", ".join(args) { + if (it.isNull()) return@join "null:null" + "${it.javaClass.simpleName}:$it" + } + ) } return finalResult diff --git a/src/main/kotlin/com/mingliqiye/utils/uuid/MysqlUUIDv1.kt b/src/main/kotlin/com/mingliqiye/utils/uuid/MysqlUUIDv1.kt index a8319b1..80831ec 100644 --- a/src/main/kotlin/com/mingliqiye/utils/uuid/MysqlUUIDv1.kt +++ b/src/main/kotlin/com/mingliqiye/utils/uuid/MysqlUUIDv1.kt @@ -16,38 +16,47 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile MysqlUUIDv1.kt - * LastUpdate 2026-01-08 08:22:14 + * LastUpdate 2026-02-06 14:52:40 * UpdateUser MingLiPro */ @file:JvmName("MysqlUUIDConvertor") package com.mingliqiye.utils.uuid +import com.mingliqiye.utils.array.copyTo +/** + * 将标准UUID字节数组转换为MySQL存储格式的字节数组 + * MySQL中UUID的存储格式与标准UUID的字节顺序不同,需要重新排列字节顺序 + * + * @param uuid 标准UUID格式的字节数组(16字节) + * @return 转换后的MySQL存储格式字节数组(16字节) + * @since 4.6.2 + */ fun uuidToMysql(uuid: ByteArray): ByteArray { - val reuuid = ByteArray(16) - reuuid[4] = uuid[0] - reuuid[5] = uuid[1] - reuuid[6] = uuid[2] - reuuid[7] = uuid[3] - reuuid[2] = uuid[4] - reuuid[3] = uuid[5] - reuuid[0] = uuid[6] - reuuid[1] = uuid[7] - System.arraycopy(uuid, 8, reuuid, 8, 8) - return reuuid + return ByteArray(16).also { + // 按照MySQL UUID存储格式重新排列字节:前4字节、中间2字节、后2字节、最后8字节 + uuid.copyTo(it, 0, 4, 4) + .copyTo(it, 4, 2, 2) + .copyTo(it, 6, 0, 2) + .copyTo(it, 8, 8, 8) + } } +/** + * 将MySQL存储格式的UUID字节数组转换回标准UUID格式 + * MySQL中UUID的存储格式与标准UUID的字节顺序不同,需要恢复原始字节顺序 + * + * @param uuid MySQL存储格式的字节数组(16字节) + * @return 转换后的标准UUID格式字节数组(16字节) + * @since 4.6.2 + */ fun mysqlToUuid(uuid: ByteArray): ByteArray { - val reuuid = ByteArray(16) - reuuid[6] = uuid[0] - reuuid[7] = uuid[1] - reuuid[4] = uuid[2] - reuuid[5] = uuid[3] - reuuid[0] = uuid[4] - reuuid[1] = uuid[5] - reuuid[2] = uuid[6] - reuuid[3] = uuid[7] - System.arraycopy(uuid, 8, reuuid, 8, 8) - return reuuid + return ByteArray(16).also { + // 按照标准UUID格式恢复字节顺序:第6-7字节、第4-5字节、第0-3字节、第8-15字节 + uuid.copyTo(it, 0, 6, 2) + .copyTo(it, 2, 4, 2) + .copyTo(it, 4, 0, 4) + .copyTo(it, 8, 8, 8) + } } diff --git a/src/main/kotlin/com/mingliqiye/utils/uuid/UUID.kt b/src/main/kotlin/com/mingliqiye/utils/uuid/UUID.kt index 1799555..4ef0295 100644 --- a/src/main/kotlin/com/mingliqiye/utils/uuid/UUID.kt +++ b/src/main/kotlin/com/mingliqiye/utils/uuid/UUID.kt @@ -16,13 +16,14 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile UUID.kt - * LastUpdate 2026-02-05 11:20:59 + * LastUpdate 2026-02-06 14:33:24 * UpdateUser MingLiPro */ package com.mingliqiye.utils.uuid import com.mingliqiye.utils.base.* +import com.mingliqiye.utils.io.IO.println import com.mingliqiye.utils.random.randomByte import com.mingliqiye.utils.random.secureRandom import com.mingliqiye.utils.system.macAddressBytes @@ -570,8 +571,11 @@ class UUID : Serializable { * @return 如果相等则返回 true,否则返回 false */ override fun equals(other: Any?): Boolean { + if (this === other) return true return when (other) { is UUID -> { + this.println() + other.println() this.data.contentEquals(other.data) } diff --git a/src/main/kotlin/com/mingliqiye/utils/uuid/UUIDFormatType.kt b/src/main/kotlin/com/mingliqiye/utils/uuid/UUIDFormatType.kt index d06cc08..4138464 100644 --- a/src/main/kotlin/com/mingliqiye/utils/uuid/UUIDFormatType.kt +++ b/src/main/kotlin/com/mingliqiye/utils/uuid/UUIDFormatType.kt @@ -16,15 +16,41 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile UUIDFormatType.kt - * LastUpdate 2026-02-04 21:54:04 + * LastUpdate 2026-02-06 14:53:47 * UpdateUser MingLiPro */ package com.mingliqiye.utils.uuid +/** + * UUID格式类型枚举 + * 定义了四种不同的UUID格式化选项,包括大小写和是否包含空格的组合 + * + * @property isUpper 是否使用大写字母 + * @property isnotSpace 是否不包含空格(true表示无空格,false表示有空格) + */ enum class UUIDFormatType(val isUpper: Boolean, val isnotSpace: Boolean) { + /** + * 大写带分隔符 + * 使用大写字母并保留空格分隔符 + */ UPPER_SPACE(true, false), + + /** + * 小写带分隔符 + * 使用小写字母并保留空格分隔符 + */ NO_UPPER_SPACE(false, false), + + /** + * 小写无分隔符 + * 使用小写字母且不包含空格分隔符 + */ NO_UPPER_NO_SPACE(false, true), + + /** + * 大写无分隔符 + * 使用大写字母且不包含空格分隔符 + */ UPPER_NO_SPACE(true, true), } diff --git a/src/main/resources/META-INF/meta-data b/src/main/resources/META-INF/meta-data index f65167e..d5e33d9 100644 --- a/src/main/resources/META-INF/meta-data +++ b/src/main/resources/META-INF/meta-data @@ -1,7 +1,7 @@ -buildTime=$buildTime -groupId=$GROUPSID -artifactId=$ARTIFACTID -version=$VERSIONS -buildJdkVersion=$JDKVERSIONS -author=MingLiPro -website=https://mingli-utils.mingliqiye.com +com.mingliqiye.utils.springboot.autoconfigure.AutoConfiguration.buildTime=$buildTime +com.mingliqiye.utils.springboot.autoconfigure.AutoConfiguration.groupId=$GROUPSID +com.mingliqiye.utils.springboot.autoconfigure.AutoConfiguration.artifactId=$ARTIFACTID +com.mingliqiye.utils.springboot.autoconfigure.AutoConfiguration.version=$VERSIONS +com.mingliqiye.utils.springboot.autoconfigure.AutoConfiguration.buildJdkVersion=$JDKVERSIONS +com.mingliqiye.utils.springboot.autoconfigure.AutoConfiguration.author=MingLiPro +com.mingliqiye.utils.springboot.autoconfigure.AutoConfiguration.website=https://mingli-utils.mingliqiye.com diff --git a/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 0000000..0ff6f7d --- /dev/null +++ b/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1,23 @@ +# +# Copyright 2026 mingliqiye +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ProjectName mingli-utils +# ModuleName mingli-utils.main +# CurrentFile javax.annotation.processing.Processor +# LastUpdate 2026-02-07 09:22:22 +# UpdateUser MingLiPro +# + +com.mingliqiye.utils.annotation.processor.AutoServiceProcessor diff --git a/src/main/resources/assets/mingli-utils/icon.png b/src/main/resources/assets/mingli-utils/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..af567d4997251c71d2a27c138fa627a3060d9440 GIT binary patch literal 100851 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