diff --git a/.gitignore b/.gitignore index 36c3ab5..2be46cb 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ log node_modules *lock* .kotlin +secret.gpg diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 796a9cc..0000000 --- a/.prettierrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/prettierrc", - "plugins": [ - "prettier-plugin-java" - ], - "tabWidth": 4, - "useTabs": true -} diff --git a/README.md b/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/build.gradle.kts b/build.gradle.kts index 41091c7..f29ee61 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils * CurrentFile build.gradle.kts - * LastUpdate 2025-09-15 22:22:00 + * LastUpdate 2025-09-17 11:11:57 * UpdateUser MingLiPro */ @@ -26,8 +26,9 @@ import java.time.format.DateTimeFormatter plugins { idea java - id("java-library") - id("maven-publish") + signing + `java-library` + `maven-publish` kotlin("jvm") version "2.2.20" id("org.jetbrains.dokka") version "2.0.0" } @@ -73,7 +74,6 @@ dependencies { compileOnly("com.alibaba.fastjson2:fastjson2:2.0.58") compileOnly("org.projectlombok:lombok:1.18.38") implementation("org.bouncycastle:bcprov-jdk18on:1.81") - implementation("com.github.f4b6a3:uuid-creator:6.1.0") implementation("org.mindrot:jbcrypt:0.4") implementation("org.jetbrains:annotations:24.0.0") compileOnly("net.java.dev.jna:jna:5.17.0") @@ -96,6 +96,7 @@ tasks.withType().configureEach { tasks.withType { duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from("LICENSE") { into(".") } manifest { attributes( mapOf( @@ -149,6 +150,10 @@ publishing { name = "MavenRepositoryRaw" url = uri("C:/data/git/maven-repository-raw") } + maven { + name = "OSSRepository" + url = uri("C:/data/git/maven-repository-raw-utils") + } } publications { create("mavenJava") { @@ -157,8 +162,34 @@ publishing { artifact(tasks.named("kotlinDocJar")) artifactId = ARTIFACTID java.toolchain.languageVersion.set(JavaLanguageVersion.of(8)) + pom { + name = "mingli-utils" + url = "https://mingli-utils.mingliqiye.com" + description = "A Java/kotlin Utils" + licenses { + license { + name = "The Apache License, Version 2.0" + url = "http://www.apache.org/licenses/LICENSE-2.0.txt" + } + } + developers { + developer { + id = "minglipro" + name = "mingli" + email = "minglipro@163.com" + } + } + scm { + connection = "scm:git:https://git.mingliqiye.com/minglipro/mingli-utils.git" + developerConnection = "scm:git:https://git.mingliqiye.com:minglipro/mingli-utils.git" + url = "https://git.mingliqiye.com/minglipro/mingli-utils" + } + } } } + signing { + sign(publishing.publications) + } } tasks.build { @@ -183,3 +214,5 @@ tasks.processResources { ) } } + + diff --git a/gradle.properties b/gradle.properties index 50a8c72..bb84200 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,10 +16,13 @@ # ProjectName mingli-utils # ModuleName mingli-utils # CurrentFile gradle.properties -# LastUpdate 2025-09-15 22:32:50 +# LastUpdate 2025-09-17 11:07:06 # UpdateUser MingLiPro # JDKVERSIONS=1.8 GROUPSID=com.mingliqiye.utils ARTIFACTID=mingli-utils -VERSIONS=4.0.7 +VERSIONS=4.1.0 +signing.keyId=B22AA93B +signing.password= +signing.secretKeyRingFile=secret.gpg diff --git a/jdk8/build.gradle.kts b/jdk8/build.gradle.kts index 8d9980f..1c8393c 100644 --- a/jdk8/build.gradle.kts +++ b/jdk8/build.gradle.kts @@ -16,12 +16,14 @@ * ProjectName mingli-utils * ModuleName mingli-utils.jdk8 * CurrentFile build.gradle.kts - * LastUpdate 2025-09-15 22:32:50 + * LastUpdate 2025-09-17 11:07:31 * UpdateUser MingLiPro */ + plugins { id("java-library") id("maven-publish") + signing } val GROUPSID = project.properties["GROUPSID"] as String val VERSIONS = project.properties["VERSIONS"] as String @@ -39,15 +41,44 @@ publishing { name = "MavenRepositoryRaw" url = uri("C:/data/git/maven-repository-raw") } + maven { + name = "OSSRepository" + url = uri("C:/data/git/maven-repository-raw-utils") + } } publications { create("mavenJava") { from(components["java"]) artifactId = "$ARTIFACTID-win-jdk8" - groupId = GROUPSID - version = VERSIONS + java.toolchain.languageVersion.set(JavaLanguageVersion.of(8)) + pom { + name = "mingli-utils-win-jdk8" + url = "https://mingli-utils.mingliqiye.com" + description = "A Java/kotlin Utils" + licenses { + license { + name = "The Apache License, Version 2.0" + url = "http://www.apache.org/licenses/LICENSE-2.0.txt" + } + } + developers { + developer { + id = "minglipro" + name = "mingli" + email = "minglipro@163.com" + } + } + scm { + connection = "scm:git:https://git.mingliqiye.com/minglipro/mingli-utils.git" + developerConnection = "scm:git:https://git.mingliqiye.com:minglipro/mingli-utils.git" + url = "https://git.mingliqiye.com/minglipro/mingli-utils" + } + } } } + signing { + sign(publishing.publications) + } } java.toolchain.languageVersion.set(JavaLanguageVersion.of(8)) diff --git a/jdk8/gradle.properties b/jdk8/gradle.properties new file mode 100644 index 0000000..bd802ce --- /dev/null +++ b/jdk8/gradle.properties @@ -0,0 +1,23 @@ +# +# Copyright 2025 mingliqiye +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ProjectName mingli-utils +# ModuleName mingli-utils.jdk8 +# CurrentFile gradle.properties +# LastUpdate 2025-09-16 12:14:37 +# UpdateUser MingLiPro +# + +signing.secretKeyRingFile=../secret.gpg diff --git a/package.json b/package.json deleted file mode 100644 index e14f42e..0000000 --- a/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "maven-repository", - "version": "1.0.0", - "scripts": { - "build": "gradle build-jar", - "buildw": "gradlew build-jar", - "format": "prettier --write \"**/*.{js,ts,jsx,tsx,cjs,cts,mjs,mts,vue,astro,json,java}\"", - "f": "prettier --write \"**/*.{js,ts,jsx,tsx,cjs,cts,mjs,mts,vue,astro,json,java}\"" - }, - "devDependencies": { - "prettier-plugin-java": "^2.7.1", - "prettier": "^3.6.2" - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index b55a8d5..dd6cf72 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils * CurrentFile settings.gradle.kts - * LastUpdate 2025-09-15 22:32:50 + * LastUpdate 2025-09-16 12:32:52 * UpdateUser MingLiPro */ diff --git a/src/main/kotlin/com/mingliqiye/utils/Main.kt b/src/main/kotlin/com/mingliqiye/utils/Main.kt index 2f4c274..f139fea 100644 --- a/src/main/kotlin/com/mingliqiye/utils/Main.kt +++ b/src/main/kotlin/com/mingliqiye/utils/Main.kt @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile Main.kt - * LastUpdate 2025-09-15 22:31:33 + * LastUpdate 2025-09-17 10:59:04 * UpdateUser MingLiPro */ @file:JvmName("Main") diff --git a/src/main/kotlin/com/mingliqiye/utils/aes/AesUtils.kt b/src/main/kotlin/com/mingliqiye/utils/aes/AesUtils.kt index 56762ed..1ed9757 100644 --- a/src/main/kotlin/com/mingliqiye/utils/aes/AesUtils.kt +++ b/src/main/kotlin/com/mingliqiye/utils/aes/AesUtils.kt @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile AesUtils.kt - * LastUpdate 2025-09-15 22:32:50 + * LastUpdate 2025-09-17 10:40:03 * UpdateUser MingLiPro */ @@ -25,8 +25,7 @@ package com.mingliqiye.utils.aes -import com.mingliqiye.utils.base64.decode -import com.mingliqiye.utils.base64.encode +import com.mingliqiye.utils.base.BASE64 import java.nio.charset.StandardCharsets import java.security.GeneralSecurityException import java.security.MessageDigest @@ -72,8 +71,8 @@ fun encrypt(sSrc: String, sKey: String?): String? { val encrypted = cipher.doFinal( sSrc.toByteArray(StandardCharsets.UTF_8) ) - return encode( - "${encode(iv)}:${encode(encrypted)}".toByteArray() + return BASE64.encode( + "${BASE64.encode(iv)}:${BASE64.encode(encrypted)}".toByteArray() ) } @@ -86,13 +85,13 @@ fun encrypt(sSrc: String, sKey: String?): String? { fun decrypt(sSrc: String, sKey: String): String? { try { // 分割IV和加密数据 - val sSrcs = String(decode(sSrc)) + val sSrcs = String(BASE64.decode(sSrc)) val parts: Array = sSrcs.split(":".toRegex(), limit = 2).toTypedArray() if (parts.size != 2) { return null } - val iv = decode(parts[0]!!) - val encryptedData = decode(parts[1]!!) + val iv = BASE64.decode(parts[0]!!) + val encryptedData = BASE64.decode(parts[1]!!) if (iv.size != GCM_IV_LENGTH) { return null } diff --git a/src/main/kotlin/com/mingliqiye/utils/base/Base16.kt b/src/main/kotlin/com/mingliqiye/utils/base/Base16.kt new file mode 100644 index 0000000..1f0eb7b --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/base/Base16.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2025 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile Base16.kt + * LastUpdate 2025-09-17 10:56:07 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.base + +/** + * Base16编解码器实现类 + * 提供字节数组与十六进制字符串之间的相互转换功能 + */ +class Base16 : BaseCodec { + /** + * 将字节数组编码为十六进制字符串 + * @param bytes 待编码的字节数组 + * @return 编码后的十六进制字符串,每个字节对应两位十六进制字符 + */ + override fun encode(bytes: ByteArray): String { + // 将每个字节转换为两位十六进制字符串并拼接 + return bytes.joinToString("") { + it.toInt().and(0xff).toString(16).padStart(2, '0') + } + } + + /** + * 将十六进制字符串解码为字节数组 + * @param string 待解码的十六进制字符串 + * @return 解码后的字节数组 + */ + override fun decode(string: String): ByteArray { + // 按每两个字符分组,转换为字节 + return string.chunked(2).map { + it.toInt(16).toByte() + }.toByteArray() + } + +} diff --git a/src/main/kotlin/com/mingliqiye/utils/base/Base64.kt b/src/main/kotlin/com/mingliqiye/utils/base/Base64.kt new file mode 100644 index 0000000..ca86f1c --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/base/Base64.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2025 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile Base64.kt + * LastUpdate 2025-09-17 10:56:32 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.base + +/* + * Base64编解码工具类 + * 提供Base64编码和解码功能的实现 + */ +class Base64 : BaseCodec { + + /* + * Base64编码器实例 + * 用于执行字节数组到Base64字符串的编码操作 + */ + val BASE_64_ENCODER: java.util.Base64.Encoder = java.util.Base64.getEncoder() + + /* + * Base64解码器实例 + * 用于执行Base64字符串到字节数组的解码操作 + */ + val BASE_64_DECODER: java.util.Base64.Decoder = java.util.Base64.getDecoder() + + /* + * 将字节数组编码为Base64字符串 + * + * @param bytes 待编码的字节数组 + * @return 编码后的Base64字符串 + */ + override fun encode(bytes: ByteArray): String { + return BASE_64_ENCODER.encodeToString(bytes) + } + + /* + * 将Base64字符串解码为字节数组 + * + * @param string 待解码的Base64字符串 + * @return 解码后的字节数组 + */ + override fun decode(string: String): ByteArray { + return BASE_64_DECODER.decode(string) + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/base/Base91.kt b/src/main/kotlin/com/mingliqiye/utils/base/Base91.kt new file mode 100644 index 0000000..4a88e09 --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/base/Base91.kt @@ -0,0 +1,158 @@ +/* + * Copyright 2025 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile Base91.kt + * LastUpdate 2025-09-17 10:57:36 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.base + +import java.util.* + +/** + * Base91 编解码工具类,用于将字节数组编码为 Base91 字符串,或将 Base91 字符串解码为原始字节数组。 + * + * Base91 是一种高效的二进制到文本的编码方式,相较于 Base64,它使用更少的字符来表示相同的数据。 + */ +class Base91 : BaseCodec { + + companion object { + /** + * Base91 编码表,共 91 个可打印 ASCII 字符。 + */ + val ENCODING_TABLE: CharArray = charArrayOf( + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '#', '$', + '%', '&', '(', ')', '*', '+', ',', '.', '/', ':', ';', '<', '=', + '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~', '"' + ) + + /** + * Base91 解码表,大小为 256,用于快速查找字符对应的数值。 + * 初始化时将所有元素设为 -1,表示无效字符;然后根据 ENCODING_TABLE 填充有效字符的索引。 + */ + val DECODING_TABLE: IntArray = IntArray(256) + + init { + // 初始化解码表,默认值为 -1 表示该字符不在编码表中 + Arrays.fill(DECODING_TABLE, -1); + // 构建解码映射表 + for (i in 0.. 13) { + var ev = ebq and 0x1FFF // 取出低 13 位作为候选值 + if (ev > 88) { + // 如果候选值大于 88,则使用 13 位编码 + ebq = ebq shr 13 + en -= 13 + } else { + // 否则使用 14 位编码 + ev = ebq and 0x3FFF + ebq = ebq shr 14 + en -= 14 + } + // 将两个字符追加到结果中 + sb.append(ENCODING_TABLE[ev % 91]) + sb.append(ENCODING_TABLE[ev / 91]) + } + } + + // 处理剩余未编码的数据 + if (en > 0) { + sb.append(ENCODING_TABLE[ebq % 91]) + if (en > 7 || ebq > 90) { + sb.append(ENCODING_TABLE[ebq / 91]) + } + } + + return sb.toString() + } + + /** + * 将 Base91 字符串解码为原始字节数组。 + * + * @param string 待解码的 Base91 字符串 + * @return 解码后的字节数组 + */ + override fun decode(string: String): ByteArray { + if (string.isEmpty()) return ByteArray(0) + var dbq = 0 // 解码缓冲区,用于暂存待处理的位数据 + var dn = 0 // 当前缓冲区中的有效位数 + var dv = -1 // 当前读取到的 Base91 值 + val buffer = ByteArray(string.length * 13 / 8) // 预分配输出缓冲区 + var index = 0 // 输出缓冲区写入位置 + + for (c in string.toCharArray()) { + // 忽略不在编码表中的字符 + if (DECODING_TABLE[c.code] == -1) continue + + if (dv == -1) { + // 第一次读取字符,保存为 dv + dv = DECODING_TABLE[c.code] + } else { + // 第二次读取字符,组合成完整的 Base91 值 + dv += DECODING_TABLE[c.code] * 91 + dbq = dbq or (dv shl dn) + // 根据值大小判断是 13 位还是 14 位编码 + dn += if ((dv and 0x1FFF) > 88) 13 else 14 + + // 将缓冲区中完整的字节写入输出数组 + do { + buffer[index++] = (dbq and 0xFF).toByte() + dbq = dbq shr 8 + dn -= 8 + } while (dn > 7) + + dv = -1 // 重置 dv,准备下一轮读取 + } + } + + // 处理最后剩余的一个字符(如果存在) + if (dv != -1) { + buffer[index++] = ((dbq or (dv shl dn)) and 0xFF).toByte() + } + + // 返回实际使用的部分 + return buffer.copyOf(index) + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/base/BaseCodec.kt b/src/main/kotlin/com/mingliqiye/utils/base/BaseCodec.kt new file mode 100644 index 0000000..802be9f --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/base/BaseCodec.kt @@ -0,0 +1,153 @@ +/* + * Copyright 2025 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile BaseCodec.kt + * LastUpdate 2025-09-17 10:35:23 + * UpdateUser MingLiPro + */ + +package com.mingliqiye.utils.base + +import java.io.File +import java.io.IOException +import java.nio.file.Path + +interface BaseCodec { + /** + * 将字节数组编码为Base64字符串 + * + * @param bytes 需要编码的字节数组 + * @return 编码后的Base64字符串 + */ + fun encode(bytes: ByteArray): String + + /** + * 将Base64字符串解码为字节数组 + * + * @param string 需要解码的Base64字符串 + * @return 解码后的字节数组 + */ + fun decode(string: String): ByteArray + + /** + * 将文件内容编码为Base64字符串 + * + * @param file 需要编码的文件 + * @return 文件内容的Base64编码字符串 + * @throws IOException 当文件读取失败时抛出 + */ + @Throws(IOException::class) + fun encode(file: File): String { + return encode(file.readBytes()) + } + + /** + * 将Base64字符串解码并写入文件 + * + * @param file 目标文件 + * @param string 需要解码的Base64字符串 + * @throws IOException 当文件写入失败时抛出 + */ + @Throws(IOException::class) + fun decode(file: File, string: String) { + file.writeBytes(decode(string)) + } + + /** + * 安全地将文件内容编码为Base64字符串,出现异常时返回null + * + * @param file 需要编码的文件 + * @return 文件内容的Base64编码字符串,失败时返回null + */ + fun encodeSafe(file: File): String? { + return try { + encode(file) + } catch (_: Exception) { + null + } + } + + /** + * 安全地将Base64字符串解码并写入文件,返回操作是否成功 + * + * @param file 目标文件 + * @param string 需要解码的Base64字符串 + * @return 操作成功返回true,失败返回false + */ + fun decodeSafe(file: File, string: String): Boolean { + return try { + decode(file, string) + true + } catch (_: Exception) { + false + } + } + + /** + * 将路径对应的文件内容编码为Base64字符串 + * + * @param path 需要编码的文件路径 + * @return 文件内容的Base64编码字符串 + * @throws IOException 当文件读取失败时抛出 + */ + @Throws(IOException::class) + fun encode(path: Path): String { + return encode(path.toFile().readBytes()) + } + + /** + * 将Base64字符串解码并写入路径指定的文件 + * + * @param path 目标文件路径 + * @param string 需要解码的Base64字符串 + * @throws IOException 当文件写入失败时抛出 + */ + @Throws(IOException::class) + fun decode(path: Path, string: String) { + path.toFile().writeBytes(decode(string)) + } + + /** + * 安全地将路径对应的文件内容编码为Base64字符串,出现异常时返回null + * + * @param path 需要编码的文件路径 + * @return 文件内容的Base64编码字符串,失败时返回null + */ + fun encodeSafe(path: Path): String? { + return try { + encode(path) + } catch (_: Exception) { + null + } + } + + /** + * 安全地将Base64字符串解码并写入路径指定的文件,返回操作是否成功 + * + * @param path 目标文件路径 + * @param string 需要解码的Base64字符串 + * @return 操作成功返回true,失败返回false + */ + fun decodeSafe(path: Path, string: String): Boolean { + return try { + decode(path, string) + true + } catch (_: Exception) { + false + } + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/base/BaseUtils.kt b/src/main/kotlin/com/mingliqiye/utils/base/BaseUtils.kt new file mode 100644 index 0000000..22c3c0e --- /dev/null +++ b/src/main/kotlin/com/mingliqiye/utils/base/BaseUtils.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2025 mingliqiye + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ProjectName mingli-utils + * ModuleName mingli-utils.main + * CurrentFile BaseUtils.kt + * LastUpdate 2025-09-17 10:54:46 + * UpdateUser MingLiPro + */ + +@file:JvmName("BaseUtils") + +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() +} + + diff --git a/src/main/kotlin/com/mingliqiye/utils/base64/Base64Utils.kt b/src/main/kotlin/com/mingliqiye/utils/base64/Base64Utils.kt deleted file mode 100644 index 980492a..0000000 --- a/src/main/kotlin/com/mingliqiye/utils/base64/Base64Utils.kt +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2025 mingliqiye - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ProjectName mingli-utils - * ModuleName mingli-utils.main - * CurrentFile Base64Utils.kt - * LastUpdate 2025-09-15 22:32:50 - * UpdateUser MingLiPro - */ -@file:JvmName("Base64Utils") - -package com.mingliqiye.utils.base64 - -import java.io.File -import java.io.IOException -import java.nio.file.Path -import java.util.* - -val BASE_64_ENCODER: Base64.Encoder = Base64.getEncoder() -val BASE_64_DECODER: Base64.Decoder = Base64.getDecoder() - -/** - * 将字节数组编码为Base64字符串 - * - * @param bytes 需要编码的字节数组 - * @return 编码后的Base64字符串 - */ -fun encode(bytes: ByteArray): String { - return BASE_64_ENCODER.encodeToString(bytes) -} - -/** - * 将Base64字符串解码为字节数组 - * - * @param string 需要解码的Base64字符串 - * @return 解码后的字节数组 - */ -fun decode(string: String): ByteArray { - return BASE_64_DECODER.decode(string) -} - -/** - * 将文件内容编码为Base64字符串 - * - * @param file 需要编码的文件 - * @return 文件内容的Base64编码字符串 - * @throws IOException 当文件读取失败时抛出 - */ -@Throws(IOException::class) -fun encode(file: File): String { - return encode(file.readBytes()) -} - -/** - * 将Base64字符串解码并写入文件 - * - * @param file 目标文件 - * @param string 需要解码的Base64字符串 - * @throws IOException 当文件写入失败时抛出 - */ -@Throws(IOException::class) -fun decode(file: File, string: String) { - file.writeBytes(decode(string)) -} - -/** - * 安全地将文件内容编码为Base64字符串,出现异常时返回null - * - * @param file 需要编码的文件 - * @return 文件内容的Base64编码字符串,失败时返回null - */ -fun encodeSafe(file: File): String? { - return try { - encode(file) - } catch (_: Exception) { - null - } -} - -/** - * 安全地将Base64字符串解码并写入文件,返回操作是否成功 - * - * @param file 目标文件 - * @param string 需要解码的Base64字符串 - * @return 操作成功返回true,失败返回false - */ -fun decodeSafe(file: File, string: String): Boolean { - return try { - decode(file, string) - true - } catch (_: Exception) { - false - } -} - -/** - * 将路径对应的文件内容编码为Base64字符串 - * - * @param path 需要编码的文件路径 - * @return 文件内容的Base64编码字符串 - * @throws IOException 当文件读取失败时抛出 - */ -@Throws(IOException::class) -fun encode(path: Path): String { - return encode(path.toFile().readBytes()) -} - -/** - * 将Base64字符串解码并写入路径指定的文件 - * - * @param path 目标文件路径 - * @param string 需要解码的Base64字符串 - * @throws IOException 当文件写入失败时抛出 - */ -@Throws(IOException::class) -fun decode(path: Path, string: String) { - path.toFile().writeBytes(decode(string)) -} - -/** - * 安全地将路径对应的文件内容编码为Base64字符串,出现异常时返回null - * - * @param path 需要编码的文件路径 - * @return 文件内容的Base64编码字符串,失败时返回null - */ -fun encodeSafe(path: Path): String? { - return try { - encode(path) - } catch (_: Exception) { - null - } -} - -/** - * 安全地将Base64字符串解码并写入路径指定的文件,返回操作是否成功 - * - * @param path 目标文件路径 - * @param string 需要解码的Base64字符串 - * @return 操作成功返回true,失败返回false - */ -fun decodeSafe(path: Path, string: String): Boolean { - return try { - decode(path, string) - true - } catch (_: Exception) { - false - } -} diff --git a/src/main/kotlin/com/mingliqiye/utils/bytes/ByteUtils.kt b/src/main/kotlin/com/mingliqiye/utils/bytes/ByteUtils.kt index 6e64407..47e2366 100644 --- a/src/main/kotlin/com/mingliqiye/utils/bytes/ByteUtils.kt +++ b/src/main/kotlin/com/mingliqiye/utils/bytes/ByteUtils.kt @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile ByteUtils.kt - * LastUpdate 2025-09-15 17:26:34 + * LastUpdate 2025-09-16 16:55:36 * UpdateUser MingLiPro */ @file:JvmName("ByteUtils") @@ -39,8 +39,20 @@ const val ESC_RESERVED: Byte = 0x06 * @return 包含每个字节对应十六进制字符串的列表 */ fun ByteArray.getByteArrayString(): MutableList { - return this.toList().stream() - .map { a -> String.format("0X%02X", a!!.toInt() and 0xFF) } + return this.toList().stream().map { a -> String.format("0X%02X", a!!.toInt() and 0xFF) } .collect(com.mingliqiye.utils.stream.toList()) as MutableList } + +fun Char.hexDigitToValue(): Int { + return when (this) { + in '0'..'9' -> this - '0' + in 'A'..'F' -> this - 'A' + 10 + in 'a'..'f' -> this - 'a' + 10 + else -> throw NumberFormatException("Invalid hex character: $this") + } +} + +private fun hexStringToByteArray(string: String): ByteArray { + return string.hexToByteArray() +} diff --git a/src/main/kotlin/com/mingliqiye/utils/json/JsonTypeUtils.kt b/src/main/kotlin/com/mingliqiye/utils/json/JsonTypeUtils.kt index 79b5c97..b7dfe42 100644 --- a/src/main/kotlin/com/mingliqiye/utils/json/JsonTypeUtils.kt +++ b/src/main/kotlin/com/mingliqiye/utils/json/JsonTypeUtils.kt @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile JsonTypeUtils.kt - * LastUpdate 2025-09-15 22:04:54 + * LastUpdate 2025-09-17 11:12:06 * UpdateUser MingLiPro */ @file:JvmName("JsonTypeUtils") @@ -177,11 +177,11 @@ fun MapType(keyType: Class, valueType: Class): JsonTypeReference size) { + throw IOException("VarNumber is too big") + } + } while ((read.toLong() and 128) != 0L) + return result +} + +/** + * 从输入流中读取一个变长整数(VarInt),最大长度限制为4个字节。 + * + * @param input 输入流,用于读取数据。 + * @return 解码后的整型数值。 + * @throws IOException 当读取过程中发生IO异常时抛出。 + */ +@Throws(IOException::class) +fun readVarInt(input: InputStream): Int { + return readVarNumber(input, size = 4).toInt() +} + +/** + * 从输入流中读取一个变长短整数(VarShort),最大长度限制为2个字节。 + * + * @param input 输入流,用于读取数据。 + * @return 解码后的短整型数值。 + * @throws IOException 当读取过程中发生IO异常时抛出。 + */ +@Throws(IOException::class) +fun readVarShort(input: InputStream): Short { + return readVarNumber(input, size = 2).toShort() +} + +/** + * 从输入流中读取一个变长长整数(VarLong),最大长度默认为8个字节。 + * + * @param input 输入流,用于读取数据。 + * @return 解码后的长整型数值。 + * @throws IOException 当读取过程中发生IO异常时抛出。 + */ +@Throws(IOException::class) +fun readVarLong(input: InputStream): Long { + return readVarNumber(input) +} diff --git a/src/main/kotlin/com/mingliqiye/utils/random/RandomBytes.kt b/src/main/kotlin/com/mingliqiye/utils/random/RandomBytes.kt index 89a236c..cfdb33b 100644 --- a/src/main/kotlin/com/mingliqiye/utils/random/RandomBytes.kt +++ b/src/main/kotlin/com/mingliqiye/utils/random/RandomBytes.kt @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile RandomBytes.kt - * LastUpdate 2025-09-15 22:27:36 + * LastUpdate 2025-09-16 17:42:26 * UpdateUser MingLiPro */ @file:JvmName("RandomBytes") @@ -82,9 +82,10 @@ fun randomByteNoHave(from: Byte, to: Byte): Byte { } val secureRandom: SecureRandom by lazy { - SecureRandom() + SecureRandom.getInstanceStrong() } + fun randomByteSecure(size: Int): ByteArray { val bytes = ByteArray(size) secureRandom.nextBytes(bytes) diff --git a/src/main/kotlin/com/mingliqiye/utils/system/SystemUtil.kt b/src/main/kotlin/com/mingliqiye/utils/system/SystemUtil.kt index 41aac96..e0299c3 100644 --- a/src/main/kotlin/com/mingliqiye/utils/system/SystemUtil.kt +++ b/src/main/kotlin/com/mingliqiye/utils/system/SystemUtil.kt @@ -16,13 +16,14 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile SystemUtil.kt - * LastUpdate 2025-09-15 22:19:57 + * LastUpdate 2025-09-16 17:36:11 * UpdateUser MingLiPro */ @file:JvmName("SystemUtils") package com.mingliqiye.utils.system +import com.mingliqiye.utils.random.randomByteSecure import java.lang.management.ManagementFactory import java.net.Inet4Address import java.net.InetAddress @@ -273,3 +274,104 @@ private fun getEnvVar(name: String): String? { null } } + +/** + * 获取本机MAC地址的字节数组形式 + * + * @return MAC地址字节数组,如果无法获取则返回空数组 + */ +val macAddressBytes: ByteArray by lazy { + try { + val interfaces = NetworkInterface.getNetworkInterfaces() + + while (interfaces.hasMoreElements()) { + val networkInterface = interfaces.nextElement() + + // 跳过回环接口和虚拟接口 + if (networkInterface.isLoopback || networkInterface.isVirtual || !networkInterface.isUp) { + continue + } + + val mac = networkInterface.hardwareAddress + if (mac != null && mac.isNotEmpty()) { + return@lazy mac + } + } + randomByteSecure(6) + } catch (e: SocketException) { + randomByteSecure(6) + } catch (e: Exception) { + randomByteSecure(6) + } +} + +/** + * 获取本机MAC地址的十六进制字符串列表形式 + * + * @return MAC地址字符串列表,每个元素表示一个字节的十六进制值(大写),如果无法获取则返回空列表 + */ +val macAddressStringList: List by lazy { + val macBytes = macAddressBytes + if (macBytes.isEmpty()) { + return@lazy emptyList() + } + + macBytes.map { String.format("%02X", it) } +} + +/** + * 获取本机MAC地址的格式化字符串形式(如 "00:11:22:33:44:55") + * + * @return 格式化的MAC地址字符串,如果无法获取则返回空字符串 + */ +val macAddressFormattedString: String by lazy { + val macBytes = macAddressBytes + if (macBytes.isEmpty()) { + return@lazy "" + } + + macBytes.joinToString(":") { String.format("%02X", it) } +} + +/** + * 获取所有网络接口的MAC地址映射 + * + * @return Map结构,key为网络接口名称,value为对应的MAC地址字节数组 + */ +val allMacAddresses: Map by lazy { + try { + val result = mutableMapOf() + val interfaces = NetworkInterface.getNetworkInterfaces() + + while (interfaces.hasMoreElements()) { + val networkInterface = interfaces.nextElement() + + // 跳过回环接口和虚拟接口 + if (networkInterface.isLoopback || networkInterface.isVirtual) { + continue + } + + val mac = networkInterface.hardwareAddress + if (mac != null && mac.isNotEmpty()) { + result[networkInterface.name] = mac + } + } + + result + } catch (e: SocketException) { + emptyMap() + } catch (e: Exception) { + emptyMap() + } +} + +/** + * 获取所有网络接口的MAC地址字符串列表映射 + * + * @return Map结构,key为网络接口名称,value为对应的MAC地址字符串列表 + */ +val allMacAddressesStringList: Map> by lazy { + allMacAddresses.mapValues { entry -> + entry.value.map { String.format("%02X", it) } + } +} diff --git a/src/main/kotlin/com/mingliqiye/utils/time/DateTime.kt b/src/main/kotlin/com/mingliqiye/utils/time/DateTime.kt index 80e65cc..b0ef70a 100644 --- a/src/main/kotlin/com/mingliqiye/utils/time/DateTime.kt +++ b/src/main/kotlin/com/mingliqiye/utils/time/DateTime.kt @@ -16,7 +16,7 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile DateTime.kt - * LastUpdate 2025-09-15 22:32:50 + * LastUpdate 2025-09-17 08:40:14 * UpdateUser MingLiPro */ @@ -46,8 +46,7 @@ import kotlin.time.Instant * @author MingLiPro */ class DateTimeOffset private constructor( - val offsetType: ChronoUnit, - val offset: Long + val offsetType: ChronoUnit, val offset: Long ) { companion object { @@ -179,15 +178,11 @@ enum class Formatter(private val value: String) { * @see Instant */ class DateTime private constructor( - private var localDateTime: LocalDateTime, - private val zoneId: ZoneId = ZoneId.systemDefault() + private var localDateTime: LocalDateTime, private val zoneId: ZoneId = ZoneId.systemDefault() ) : Serializable { companion object { - private val WIN_KERNEL_32_API: WinKernel32Api? = if ( - javaVersionAsInteger == 8 && - isWindows - ) { + private val WIN_KERNEL_32_API: WinKernel32Api? = if (javaVersionAsInteger == 8 && isWindows) { val log: Logger = mingLiLoggerFactory.getLogger("mingli-utils DateTime") val a = getWinKernel32Apis() @@ -219,9 +214,7 @@ class DateTime private constructor( fun now(): DateTime { if (WIN_KERNEL_32_API != null) { return DateTime( - WIN_KERNEL_32_API.getTime() - .atZone(ZoneId.systemDefault()) - .toLocalDateTime() + WIN_KERNEL_32_API.getTime().atZone(ZoneId.systemDefault()).toLocalDateTime() ) } return DateTime(LocalDateTime.now()) @@ -273,9 +266,7 @@ class DateTime private constructor( */ @JvmStatic fun parse( - timestr: String, - formatter: String, - fillZero: Boolean + timestr: String, formatter: String, fillZero: Boolean ): DateTime { return DateTime( LocalDateTime.parse( @@ -295,9 +286,7 @@ class DateTime private constructor( */ @JvmStatic fun parse( - timestr: String, - formatter: Formatter, - fillZero: Boolean + timestr: String, formatter: Formatter, fillZero: Boolean ): DateTime { return parse(timestr, formatter.getValue(), fillZero) } @@ -350,11 +339,7 @@ class DateTime private constructor( } throw IllegalArgumentException( String.format( - "Text: '%s' len %s < %s %s", - dstr, - dstr.length, - formats, - formats.length + "Text: '%s' len %s < %s %s", dstr, dstr.length, formats, formats.length ) ) } @@ -384,11 +369,7 @@ class DateTime private constructor( */ @JvmStatic fun of( - year: Int, - month: Int, - day: Int, - hour: Int, - minute: Int + year: Int, month: Int, day: Int, hour: Int, minute: Int ): DateTime { return DateTime(LocalDateTime.of(year, month, day, hour, minute)) } @@ -407,8 +388,7 @@ class DateTime private constructor( // 2. 从纳秒时间戳创建 Instant val instant = java.time.Instant.ofEpochSecond( - unixNanos / 1_000_000_000L, - unixNanos % 1_000_000_000L + unixNanos / 1_000_000_000L, unixNanos % 1_000_000_000L ) // 3. 转换为系统默认时区的 LocalDateTime @@ -428,12 +408,7 @@ class DateTime private constructor( */ @JvmStatic fun of( - year: Int, - month: Int, - day: Int, - hour: Int, - minute: Int, - second: Int + year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int ): DateTime { return DateTime( LocalDateTime.of(year, month, day, hour, minute, second) @@ -454,13 +429,7 @@ class DateTime private constructor( */ @JvmStatic fun of( - year: Int, - month: Int, - day: Int, - hour: Int, - minute: Int, - second: Int, - nano: Int + year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int, nano: Int ): DateTime { return DateTime( LocalDateTime.of(year, month, day, hour, minute, second, nano) @@ -476,9 +445,14 @@ class DateTime private constructor( @JvmStatic fun of(epochMilli: Long): DateTime { return DateTime( - java.time.Instant.ofEpochMilli(epochMilli) - .atZone(ZoneId.systemDefault()) - .toLocalDateTime() + java.time.Instant.ofEpochMilli(epochMilli).atZone(ZoneId.systemDefault()).toLocalDateTime() + ) + } + + @JvmStatic + fun of(seconds: Long, nanos: Long): DateTime { + return DateTime( + java.time.Instant.ofEpochSecond(seconds, nanos).atZone(ZoneId.systemDefault()).toLocalDateTime() ) } @@ -492,8 +466,7 @@ class DateTime private constructor( @JvmStatic fun of(epochMilli: Long, zoneId: ZoneId): DateTime { return DateTime( - java.time.Instant.ofEpochMilli(epochMilli).atZone(zoneId).toLocalDateTime(), - zoneId + java.time.Instant.ofEpochMilli(epochMilli).atZone(zoneId).toLocalDateTime(), zoneId ) } } @@ -525,8 +498,7 @@ class DateTime private constructor( fun add(dateTimeOffset: DateTimeOffset): DateTime { return DateTime( this.localDateTime.plus( - dateTimeOffset.offset, - dateTimeOffset.offsetType + dateTimeOffset.offset, dateTimeOffset.offsetType ) ) } @@ -538,12 +510,7 @@ class DateTime private constructor( * @return 返回修改后的 DateTime 实例 */ fun sub(dateTimeOffset: DateTimeOffset): DateTime { - return DateTime( - this.localDateTime.plus( - -dateTimeOffset.offset, - dateTimeOffset.offsetType - ) - ) + return add(DateTimeOffset.of(-dateTimeOffset.offset, dateTimeOffset.offsetType)) } /** @@ -603,8 +570,7 @@ class DateTime private constructor( */ override fun toString(): String { return String.format( - "DateTime(%s)", - format(Formatter.STANDARD_DATETIME_MILLISECOUND7, true) + "DateTime(%s)", format(Formatter.STANDARD_DATETIME_MILLISECOUND7, true) ) } @@ -675,4 +641,34 @@ class DateTime private constructor( fun getZoneId(): ZoneId { return zoneId } + + + /** + * 将 Instant 转换为纳秒时间戳 + * @throws ArithmeticException 如果结果超出 Long 范围 (-2^63 到 2^63-1) + */ + fun toNanoTime(): Long { + val instant = toInstant() + + return try { + val secondsInNanos = Math.multiplyExact(instant.epochSecond, 1_000_000_000L) + Math.addExact(secondsInNanos, instant.nano.toLong()) + } catch (e: ArithmeticException) { + throw ArithmeticException( + "无法将 Instant(${instant.epochSecond}s, ${instant.nano}ns) 转换为纳秒: ${e.message}" + ) + } + } + + fun to100NanoTime(): Long { + return toInstant().let { + (it.epochSecond * 10_000_000L) + (it.nano / 100L) + } + } + + fun toMillisecondTime(): Long { + return toInstant().let { + (it.epochSecond * 1000L) + (it.nano / 1_000_000L) + } + } } diff --git a/src/main/kotlin/com/mingliqiye/utils/uuid/UUID.kt b/src/main/kotlin/com/mingliqiye/utils/uuid/UUID.kt index f6cb42a..b01a3bc 100644 --- a/src/main/kotlin/com/mingliqiye/utils/uuid/UUID.kt +++ b/src/main/kotlin/com/mingliqiye/utils/uuid/UUID.kt @@ -16,70 +16,190 @@ * ProjectName mingli-utils * ModuleName mingli-utils.main * CurrentFile UUID.kt - * LastUpdate 2025-09-15 18:01:30 + * LastUpdate 2025-09-17 11:04:08 * UpdateUser MingLiPro */ + package com.mingliqiye.utils.uuid -import com.github.f4b6a3.uuid.UuidCreator +import com.mingliqiye.utils.base.BASE64 +import com.mingliqiye.utils.base.BASE91 +import com.mingliqiye.utils.random.randomByteSecure +import com.mingliqiye.utils.random.secureRandom +import com.mingliqiye.utils.system.macAddressBytes import com.mingliqiye.utils.time.DateTime import com.mingliqiye.utils.time.DateTimeOffset import java.io.Serializable import java.nio.ByteBuffer +import java.security.MessageDigest import java.time.temporal.ChronoUnit -import java.util.* import java.util.UUID as JUUID +/** + * UUID 类用于生成和操作不同版本的 UUID(通用唯一标识符)。 + * 支持 UUID 的序列化、转换、解析和时间/版本信息提取。 + */ class UUID : Serializable { - private val uuid: JUUID + private val data: ByteArray + private val mostSigBits: Long + private val leastSigBits: Long + private val version: Int companion object { + /** - * 获取 UUIDV1 版本的随机实例 - * @return UUID + * 预期 UUID 字符串中连字符的位置数组。 + */ + @JvmStatic + val expectedHyphenPositions = intArrayOf(8, 13, 18, 23) + + /** + * UUID 纪元偏移量(以天为单位)。 + */ + @JvmStatic + val UUID_EPOCH_OFFSET = 141427L + + + @JvmStatic + fun ofBase64ShortString(baseShortString: String): UUID { + return UUID(BASE64.decode(baseShortString)) + } + + @JvmStatic + fun ofBase91ShortString(baseShortString: String): UUID { + return UUID(BASE91.decode(baseShortString)) + } + + /** + * 生成一个 UUID V1 版本,使用系统时钟、随机数和 MAC 地址。 + * 如果 MAC 地址为空,则使用随机生成的 MAC 地址。 + * + * @return UUID V1 实例 */ @JvmStatic fun getV1(): UUID { - return UUID(UuidCreator.getTimeBased()) + val time = DateTime.now().add(DateTimeOffset.of(UUID_EPOCH_OFFSET, ChronoUnit.DAYS)).to100NanoTime() + + val timeLow = (time and 0xFFFFFFFFL).toInt() + val timeMid = ((time shr 32) and 0xFFFFL).toShort() + val timeHighAndVersion = (((time shr 48) and 0x0FFFL) or 0x1000L).toShort() + val clockSeq = (secureRandom.nextInt(16384)) and 0x3FFF + + val byteBuffer = ByteBuffer.wrap(ByteArray(16)) + byteBuffer.putInt(timeLow) + byteBuffer.putShort(timeMid) + byteBuffer.putShort(timeHighAndVersion) + byteBuffer.putShort(clockSeq.toShort()) + byteBuffer.put(macAddressBytes) + + return UUID(byteBuffer.array()) } - @Deprecated("使用 getV1()", ReplaceWith("getV1()"), level = DeprecationLevel.WARNING) - fun getTimeBased(): UUID = getV1() - /** - * 获取 UUIDV4 版本的随机实例 - * @return UUID + * 生成一个 UUID V4 版本,使用加密安全的随机数。 + * + * @return UUID V4 实例 */ @JvmStatic fun getV4(): UUID { - return UUID(UuidCreator.getRandomBased()) + val randomBytes = randomByteSecure(16) + randomBytes[6] = (randomBytes[6].toInt() and 0x0F).toByte() + randomBytes[6] = (randomBytes[6].toInt() or 0x40).toByte() + randomBytes[8] = (randomBytes[8].toInt() and 0x3F).toByte() + randomBytes[8] = (randomBytes[8].toInt() or 0x80).toByte() + return UUID(randomBytes) } /** - * 获取 UUIDV1Fast 版本的随机实例 - * @return UUID + * 生成一个 UUID V3 版本,基于命名空间和名称的 MD5 哈希值。 + * + * @param namepath 命名空间 UUID + * @param user 用户提供的字符串 + * @return UUID V3 实例 */ @JvmStatic - fun getV4Fast(): UUID { - return UUID(UuidCreator.getRandomBasedFast()) + fun getV3(namepath: UUID, user: String): UUID { + val md = MessageDigest.getInstance("MD5") + val userB = user.toByteArray() + val array = md.digest( + ByteBuffer.wrap(ByteArray(16 + userB.size)).put(namepath.data).put(userB).array() + ) + array[6] = (array[6].toInt() and 0x0F or 0x30).toByte() + array[8] = (array[8].toInt() and 0x3F or 0x80).toByte() + return UUID(array) } /** - * 从2个8个字节转换到UUID - * @param lsb 高位 8 字节的 Long - * @param msb 低位 8 字节的 Long - * @return UUID + * 生成一个 UUID V5 版本,基于命名空间和名称的 SHA-1 哈希值。 + * + * @param namepath 命名空间 UUID + * @param user 用户提供的字符串 + * @return UUID V5 实例 */ @JvmStatic - fun of(msb: Long, lsb: Long): UUID { - return UUID(msb, lsb) + fun getV5(namepath: UUID, user: String): UUID { + val sha1 = MessageDigest.getInstance("SHA-1") + val userB = user.toByteArray() + val array = sha1.digest( + ByteBuffer.wrap(ByteArray(namepath.data.size + userB.size)).put(namepath.data).put(userB).array() + ) + array[6] = (array[6].toInt() and 0x0F or 0x50).toByte() + array[8] = (array[8].toInt() and 0x3F or 0x80).toByte() + return UUID(ByteBuffer.wrap(ByteArray(16)).put(array, 0, 16).array()) } /** - * 从字符串格式化 - * @param uuid 字符串 - * @return UUID + * 生成一个 UUID V6 版本,使用时间戳和随机节点信息。 + * + * @return UUID V6 实例 + */ + @JvmStatic + fun getV6(): UUID { + val timestamp = DateTime.now() + .add(DateTimeOffset.of(UUID_EPOCH_OFFSET, ChronoUnit.DAYS)) + .to100NanoTime() and 0x0FFFFFFFFFFFFFFFL + val timeHigh = (timestamp ushr 12) and 0xFFFFFFFFFFFFL + val timeMid = timestamp and 0x0FFFL + val clockSeq = secureRandom.nextInt(16384) and 0x3FFF + val node = secureRandom.nextLong() and 0x0000FFFFFFFFFFFFL + val buffer = ByteBuffer.allocate(16) + buffer.putLong((timeHigh shl 16) or (0x6000L) or (timeMid and 0x0FFF)) + buffer.putShort((0x8000 or (clockSeq and 0x3FFF)).toShort()) + buffer.put((node shr 40).toByte()) + buffer.put((node shr 32).toByte()) + buffer.put((node shr 24).toByte()) + buffer.put((node shr 16).toByte()) + buffer.put((node shr 8).toByte()) + buffer.put(node.toByte()) + return UUID(buffer.array()) + } + + /** + * 生成一个 UUID V7 版本,使用毫秒级时间戳和随机数。 + * + * @return UUID V7 实例 + */ + @JvmStatic + fun getV7(): UUID { + val instant = DateTime.now().toMillisecondTime() + println(instant.toString(16)) + val buffer = ByteBuffer.allocate(16) + buffer.putInt((instant shr 16).toInt()) + buffer.putShort((instant).toShort()) + buffer.put(randomByteSecure(2)) + buffer.putLong(secureRandom.nextLong()) + val bytes = buffer.array() + bytes[6] = (bytes[6].toInt() and 0x0F or 0x70).toByte() + bytes[8] = (bytes[8].toInt() and 0x3F or 0x80).toByte() + return UUID(bytes) + } + + /** + * 根据字符串创建 UUID 实例。 + * + * @param uuid UUID 字符串 + * @return UUID 实例 */ @JvmStatic fun of(uuid: String): UUID { @@ -87,135 +207,466 @@ class UUID : Serializable { } /** - * 从Java的UUID - * @param uuid 字符串 - * @return UUID + * 根据字节数组创建 UUID 实例。 + * + * @param uuid UUID 字节数组 + * @return UUID 实例 */ @JvmStatic - fun of(uuid: JUUID): UUID { + fun of(uuid: ByteArray): UUID { return UUID(uuid) } /** - * 从字节码转换到UUID - * @param array 16字节 - * @return UUID + * 根据高位和低位长整型创建 UUID 实例。 + * + * @param msb 高位长整型 + * @param lsb 低位长整型 + * @return UUID 实例 */ @JvmStatic - fun of(array: ByteArray): UUID { - return UUID(array) + fun of(msb: Long, lsb: Long): UUID { + return UUID(msb, lsb) } - fun JUUID.toMlUUID(): UUID { - return of(this) + /** + * 根据 MySQL UUID 字节数组创建 UUID 实例。 + * + * @param byteArray MySQL UUID 字节数组 + * @return UUID 实例 + */ + @JvmStatic + fun ofMysqlUUID(byteArray: ByteArray): UUID { + return UUID(mysqlToUuid(byteArray)) } - } - internal constructor(msb: Long, lsb: Long) { - uuid = JUUID(msb, lsb) - } + /** + * 根据 MySQL UUID 实例创建 UUID 实例。 + * + * @param uuid MySQL UUID 实例 + * @return UUID 实例 + */ + @JvmStatic + fun ofMysqlUUID(uuid: UUID): UUID { + return UUID(mysqlToUuid(uuid.data)) + } - internal constructor(uuid: JUUID) { - this.uuid = uuid - } + /** + * 获取最大 UUID(所有字节为 0xFF)。 + * + * @return 最大 UUID 实例 + */ + @JvmStatic + fun getMaxUUID(): UUID { + return UUID( + ByteArray( + 16 + ) { 0xFF.toByte() } + ) + } - internal constructor(array: ByteArray) { - val bb = ByteBuffer.wrap(array) - this.uuid = JUUID(bb.getLong(), bb.getLong()) - } + /** + * 获取最小 UUID(所有字节为 0x00)。 + * + * @return 最小 UUID 实例 + */ + @JvmStatic + fun getMinUUID(): UUID { + return UUID( + ByteArray( + 16 + ) { 0x00.toByte() } + ) + } - constructor(uuid: String) { - this.uuid = JUUID.fromString(uuid) - } + /** + * 将 Java UUID 转换为自定义 UUID 实例。 + * + * @receiver Java UUID 实例 + * @return 自定义 UUID 实例 + */ + @JvmStatic + @JvmName("ofJUUID") + fun JUUID.toMLUUID(): UUID { + return UUID(this) + } + /** + * 从字符串解析 UUID 字节数组。 + * + * @param uuidString UUID 字符串 + * @return UUID 字节数组 + */ + private fun fromString(uuidString: String): ByteArray { + val cleanStr = when (uuidString.length) { + 36 -> { + for (i in expectedHyphenPositions) { + if (uuidString[i] != '-') { + throw IllegalArgumentException("Invalid UUID string: $uuidString at index $i") + } + } + uuidString.replace("-", "") + } + + 32 -> uuidString + else -> throw IllegalArgumentException("Invalid UUID string: $uuidString") + } + + // 直接按索引提取各段并校验 + val segments = arrayOf( + cleanStr.take(8), + cleanStr.substring(8, 12), + cleanStr.substring(12, 16), + cleanStr.substring(16, 20), + cleanStr.substring(20, 32) + ) + + for (segment in segments) { + if (!segment.matches(Regex("^[0-9A-Fa-f]+$"))) { + throw IllegalArgumentException("Invalid UUID segment: $segment") + } + } + + return cleanStr.chunked(2).map { it.toInt(16).toByte() }.toByteArray() + } + + /** + * 将 MAC 地址字节数组转换为长整型。 + * + * @param mac MAC 地址字节数组(必须为 6 字节) + * @return MAC 地址对应的长整型值 + */ + private fun macBytesToLong(mac: ByteArray): Long { + require(mac.size == 6) { "MAC地址必须是6字节" } + var result = 0L + for (i in 0 until 6) { + result = (result shl 8) or (mac[i].toLong() and 0xFF) + } + return result + } + + } + /** - * 获取对应的字节码 - * @return 字节码 + * 构造函数,根据字节数组初始化 UUID。 + * + * @param data UUID 字节数组(必须为 16 字节) + */ + constructor(data: ByteArray) { + require(data.size == 16) { "UUID byte array length must be 16" } + this.data = data + val bb: ByteBuffer = ByteBuffer.wrap(data) + mostSigBits = bb.long + leastSigBits = bb.long + version = (mostSigBits shr 12 and 0xF).toInt() + } + + /** + * 构造函数,根据高位和低位长整型初始化 UUID。 + * + * @param msb 高位长整型 + * @param lsb 低位长整型 + */ + constructor(msb: Long, lsb: Long) { + mostSigBits = msb + leastSigBits = lsb + val bb: ByteBuffer = ByteBuffer.wrap(ByteArray(16)) + bb.putLong(msb) + bb.putLong(lsb) + this.data = bb.array() + version = (mostSigBits shr 12 and 0xF).toInt() + } + + /** + * 构造函数,根据 Java UUID 初始化自定义 UUID。 + * + * @param juuid Java UUID 实例 + */ + constructor(juuid: JUUID) { + mostSigBits = juuid.mostSignificantBits + leastSigBits = juuid.leastSignificantBits + val bb: ByteBuffer = ByteBuffer.wrap(ByteArray(16)) + bb.putLong(mostSigBits) + bb.putLong(leastSigBits) + this.data = bb.array() + version = (mostSigBits shr 12 and 0xF).toInt() + } + + /** + * 构造函数,根据字符串初始化 UUID。 + * + * @param uuid UUID 字符串 + */ + constructor(uuid: String) { + fromString(uuid).let { + val bb: ByteBuffer = ByteBuffer.wrap(it) + mostSigBits = bb.long + leastSigBits = bb.long + this.data = it + version = (mostSigBits shr 12 and 0xF).toInt() + } + } + + /** + * 返回 UUID 的字节数组表示。 + * + * @return UUID 字节数组 */ fun toBytes(): ByteArray { - val bb = ByteBuffer.wrap(ByteArray(16)) - bb.putLong(uuid.mostSignificantBits) - bb.putLong(uuid.leastSignificantBits) - - return bb.array() + return data } /** - * 获取Java的UUID对象 - * @return Java的UUID对象 + * 返回 UUID 的高位长整型部分。 + * + * @return 高位长整型 + */ + fun getMostSignificantBits(): Long { + return mostSigBits + } + + /** + * 返回 UUID 的低位长整型部分。 + * + * @return 低位长整型 + */ + fun getLeastSignificantBits(): Long { + return leastSigBits + } + + /** + * 返回 UUID 的字符串表示。 + * + * @param isUpper 是否使用大写字母(默认为 false) + * @return UUID 字符串 + */ + fun getString(isUpper: Boolean = false): String { + return getString().let { + if (isUpper) { + it.uppercase() + } else it + } + } + + /** + * 返回 UUID 的字符串表示。 + * + * @param isUpper 是否使用大写字母(默认为 false) + * @param isnotSpace 是否移除连字符(默认为 false) + * @return UUID 字符串 + */ + fun getString(isUpper: Boolean = false, isnotSpace: Boolean = false): String { + return getString(isUpper).let { + if (isnotSpace) { + it.replace("-", "") + } else it + } + } + + /** + * 返回标准格式的 UUID 字符串(带连字符)。 + * + * @return 标准格式的 UUID 字符串 + */ + // 优化后的 toString 方法 + fun getString(): String { + return buildString(36) { + val msbHigh = (mostSigBits ushr 32).toInt() + val msbMid = ((mostSigBits ushr 16) and 0xFFFF).toInt() + val msbLow = (mostSigBits and 0xFFFF).toInt() + val lsbHigh = ((leastSigBits ushr 48) and 0xFFFF).toInt() + val lsbLow = (leastSigBits and 0xFFFFFFFFFFFFL) + + append(msbHigh.toHexString()) + append('-') + append(msbMid.toShort().toHexString(4)) + append('-') + append(msbLow.toShort().toHexString(4)) + append('-') + append(lsbHigh.toShort().toHexString(4)) + append('-') + append(lsbLow.toHexString(12)) + } + } + + /** + * 将长整型转换为指定长度的十六进制字符串。 + * + * @param length 字符串长度 + * @return 十六进制字符串 + */ + private fun Long.toHexString(length: Int): String { + return this.toString(16).padStart(length, '0') + } + + /** + * 将短整型转换为指定长度的十六进制字符串。 + * + * @param length 字符串长度 + * @return 十六进制字符串 + */ + private fun Short.toHexString(length: Int): String { + return this.toLong().and(0xFFFF).toString(16).padStart(length, '0') + } + + /** + * 返回 MySQL 格式的 UUID 字节数组。 + * + * @return MySQL 格式的 UUID 字节数组 + */ + fun toMysql(): ByteArray { + return mysqlToUuid(this.data) + } + + /** + * 返回 MySQL 格式的 UUID 实例。 + * + * @return MySQL 格式的 UUID 实例 + */ + fun toMysqlUUID(): UUID { + return of(mysqlToUuid(this.data)) + } + + /** + * 比较两个 UUID 是否相等。 + * + * @param other 另一个对象 + * @return 如果相等则返回 true,否则返回 false + */ + override fun equals(other: Any?): Boolean { + return when (other) { + is UUID -> { + this.data.contentEquals(other.data) + } + + is JUUID -> { + other.mostSignificantBits == this.getMostSignificantBits() && other.leastSignificantBits == this.getLeastSignificantBits() + } + + else -> { + false + } + } + } + + /** + * 返回对应的 Java UUID 实例。 + * + * @return Java UUID 实例 */ fun getUuid(): JUUID { - return uuid + return JUUID(mostSigBits, leastSigBits) } /** - * 将 UUID 转换为字符串表示,默认使用小写格式。 - * @param u 是否大写 - * @return UUID 字符串 + * 返回 UUID 的版本号。 + * + * @return 版本号 */ - fun getString(u: Boolean): String { - return if (u) { - uuid.toString().uppercase(Locale.ROOT) - } else { - uuid.toString() + fun getVersion(): Int { + return version + } + + /** + * 计算 UUID 的哈希码。 + * + * @return 哈希码 + */ + override fun hashCode(): Int { + return data.contentHashCode() + } + + /** + * 返回 UUID 的字符串表示。 + * + * @return 包含 UUID 和版本号的字符串 + */ + override fun toString(): String { + return "UUID(uuid=${getString()},version=${version})" + } + + /** + * 根据 UUID 版本提取对应的时间信息。 + * + * @return 对应的时间信息 + */ + fun getDateTime(): DateTime { + + when (version) { + 1 -> { + val timestamp = + ((mostSigBits and 0x0FFFL) shl 48 or (((mostSigBits shr 16) and 0x0FFFFL) shl 32) or (mostSigBits ushr 32)) + val timestampBigInt = java.math.BigInteger.valueOf(timestamp) + val nanosecondsBigInt = timestampBigInt.multiply(java.math.BigInteger.valueOf(100L)) + val divisor = java.math.BigInteger.valueOf(1_000_000_000L) + val seconds = nanosecondsBigInt.divide(divisor) + val nanos = nanosecondsBigInt.remainder(divisor) + return DateTime.of(seconds.toLong(), nanos.toLong()) + .sub(DateTimeOffset.of(ChronoUnit.DAYS, UUID_EPOCH_OFFSET)) + } + + 6 -> { + val timeHigh = ( + ((data[0].toLong() and 0xFF) shl 40) or + ((data[1].toLong() and 0xFF) shl 32) or + ((data[2].toLong() and 0xFF) shl 24) or + ((data[3].toLong() and 0xFF) shl 16) or + ((data[4].toLong() and 0xFF) shl 8) or + (data[5].toLong() and 0xFF) + ) + val timeMidAndVersion = ((data[6].toInt() and 0xFF) shl 8) or (data[7].toInt() and 0xFF) + val timeMid = timeMidAndVersion and 0x0FFF + val hundredNanosSinceUuidEpoch = (timeHigh shl 12) or timeMid.toLong() + val seconds = hundredNanosSinceUuidEpoch / 10_000_000 + val nanos = (hundredNanosSinceUuidEpoch % 10_000_000) * 100 + return DateTime.of(seconds, nanos).sub(DateTimeOffset.of(ChronoUnit.DAYS, UUID_EPOCH_OFFSET)) + } + + 7 -> { + val times = + (data[0].toLong() and 0xFF shl 40) or + (data[1].toLong() and 0xFF shl 32) or + (data[2].toLong() and 0xFF shl 24) or + (data[3].toLong() and 0xFF shl 16) or + (data[4].toLong() and 0xFF shl 8) or + (data[5].toLong() and 0xFF) + return DateTime.of(times) + } + + else -> { + throw IllegalArgumentException("UUID version is $version not v1 or v6 or v7 : not supported ") + } } } /** - * 将 UUID 转换为字符串表示,默认使用小写格式。 - * - * @return UUID 字符串 - */ - fun getString(): String { - return getString(false) - } - - @Deprecated("使用 getString()", ReplaceWith("getString"), level = DeprecationLevel.WARNING) - fun toUUIDString(): String { - return this.getString() - } - - @Deprecated("使用 getString(u:Boolean)", ReplaceWith("getString(u)"), level = DeprecationLevel.WARNING) - fun toUUIDString(u: Boolean): String { - return this.getString(u) - } - - /** - * 从时间戳型 UUID 中提取时间戳并转换为 DateTime 对象。 - * - * @return 对应的 DateTime 对象;如果 不是 时间戳V1版本 返回 null - */ - fun getDateTime(): DateTime? { - if (uuid.version() != 1) { - return null - } - return DateTime.of(uuid.timestamp() / 10000).add( - DateTimeOffset.of(-141427L, ChronoUnit.DAYS) - ) - } - - /** - * 从时间戳型 UUID 中提取 MAC 地址,默认使用冒号分隔符。 + * 提取 UUID V1 中的 MAC 地址,默认使用冒号分隔符。 * * @return MAC 地址字符串 */ - fun extractMACFromUUID(): String { - return extractMACFromUUID(null) + fun getMac(): String { + return getMac(":") + } + + fun getBase64ShortString(): String { + return BASE64.encode(data).substring(0, 22) + } + + fun getBase91ShortString(): String { + return BASE91.encode(data) } /** - * 从时间戳型 UUID 中提取 MAC 地址,并允许自定义分隔符。 + * 提取 UUID V1 中的 MAC 地址。 * - * @param spec 分隔符字符,默认为 ":" + * @param spec 分隔符(默认为冒号) * @return MAC 地址字符串 */ - fun extractMACFromUUID(spec: String?): String { - var spec = spec - if (spec == null) { - spec = ":" + fun getMac(spec: String = ":"): String { + if (version != 1) { + throw IllegalArgumentException("UUID version is $version not v1 : not supported ") } - val leastSigBits = uuid.leastSignificantBits val macLong = leastSigBits and 0xFFFFFFFFFFFFL val macBytes = ByteArray(6) for (i in 0..5) { @@ -230,39 +681,4 @@ class UUID : Serializable { } return mac.toString() } - - /** - * 返回此 UUID 的字符串表示,包含版本信息和时间戳(如果是版本1)。 - * - * @return UUID 的详细字符串表示 - */ - override fun toString(): String { - return "UUID(uuid=${getString()},version=${uuid.version()})" - } - - /** - * 判断两个 UUID 是否相等。 - * - * @param other 比较对象 - * @return 如果相等返回 true,否则返回 false - */ - override fun equals(other: Any?): Boolean { - if (other is UUID) { - return uuid == other.uuid - } else if (other is JUUID) { - return uuid == other - } - return false - } - - /** - * 计算此 UUID 的哈希码。 - * - * @return 哈希码值 - */ - override fun hashCode(): Int { - return uuid.hashCode() - } } - -