From 450b4e1a93d2b8f19c138d8421923a3e0396fd69 Mon Sep 17 00:00:00 2001 From: minglipro Date: Sun, 8 Feb 2026 03:17:14 +0800 Subject: [PATCH] =?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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

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" + } + } + } +}