feat(build): 重构项目并添加 Maven 发布支持

- 更新 .gitignore 文件,添加 secret.gpg
- 移除 .prettierrc 文件
- 重构 AES 加密工具类,使用新的 Base64 实现
- 新增 Base16、Base64 和 Base91 编解码工具类
- 新增 BaseCodec接口和 BaseUtils 工具类
- 更新 build.gradle.kts 配置,添加 Maven 发布和签名
- 修复 ByteUtils 和 DateTime 工具类中的问题
- 重构 UUID 使用自己的实现 不依赖标准库以及其他的 UUID库
This commit is contained in:
Armamem0t 2025-09-17 11:12:10 +08:00
parent 541a8a82b4
commit fcd528a821
Signed by: minglipro
GPG Key ID: 5F355A77B22AA93B
24 changed files with 1433 additions and 417 deletions

1
.gitignore vendored
View File

@ -45,3 +45,4 @@ log
node_modules
*lock*
.kotlin
secret.gpg

View File

@ -1,8 +0,0 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"plugins": [
"prettier-plugin-java"
],
"tabWidth": 4,
"useTabs": true
}

View File

View File

@ -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<JavaExec>().configureEach {
tasks.withType<org.gradle.jvm.tasks.Jar> {
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<MavenPublication>("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 {
)
}
}

View File

@ -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

View File

@ -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<MavenPublication>("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))

23
jdk8/gradle.properties Normal file
View File

@ -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

View File

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

View File

@ -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
*/

View File

@ -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")

View File

@ -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<String?> = 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
}

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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..<ENCODING_TABLE.size) {
DECODING_TABLE[ENCODING_TABLE[i].code] = i;
}
}
}
/**
* 将字节数组编码为 Base91 字符串
*
* @param bytes 待编码的字节数组
* @return 编码后的 Base91 字符串
*/
override fun encode(bytes: ByteArray): String {
if (bytes.isEmpty()) return ""
val sb = StringBuilder()
var ebq = 0 // 编码缓冲区,用于暂存待处理的位数据
var en = 0 // 当前缓冲区中的有效位数
for (b in bytes) {
// 将当前字节加入缓冲区
ebq = ebq or ((b.toInt() and 0xFF) shl en)
en += 8
// 每当缓冲区中有超过 13 位的数据时,尝试进行编码
if (en > 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)
}
}

View File

@ -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
}
}
}

View File

@ -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()
}

View File

@ -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
}
}

View File

@ -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<String> {
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<String>
}
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()
}

View File

@ -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 <K, V> MapType(keyType: Class<K>, valueType: Class<V>): JsonTypeReference<Ma
return null
}
override fun equals(obj: Any?): Boolean {
if (this === obj) return true
if (obj !is ParameterizedType) return false
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ParameterizedType) return false
val that = obj
val that = other
return (Objects.equals(
rawType,
that.rawType

View File

@ -0,0 +1,100 @@
/*
* 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 NumberUtils.kt
* LastUpdate 2025-09-16 15:59:45
* UpdateUser MingLiPro
*/
@file:JvmName("NumberUtils")
package com.mingliqiye.utils.number
import java.io.IOException
import java.io.InputStream
/**
* 从输入流中读取一个变长整数VarNumber
*
* 变长整数使用可变长度编码方式每个字节的最高位表示是否还有后续字节
* - 如果最高位为1则表示还有下一个字节
* - 如果最高位为0则表示当前字节是最后一个字节
*
* @param input 输入流用于读取数据
* @param size 最大允许读取的字节数默认为8即Long类型的最大长度
* @return 解码后的长整型数值
* @throws IOException 当读取过程中发生IO异常或到达流末尾时抛出
*/
@Throws(IOException::class)
fun readVarNumber(input: InputStream, size: Int = 10): Long {
var numRead = 0
var result: Long = 0
var read: Byte
do {
read = input.read().let {
if (it == -1) {
throw IOException("Reached end of stream")
}
it.toByte()
}
// 将当前字节的有效7位数据左移相应位数并与结果进行或运算
result = result or ((read.toLong() and 127) shl (7 * numRead))
numRead++
if (numRead > 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)
}

View File

@ -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)

View File

@ -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<String> 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<String, ByteArray> by lazy {
try {
val result = mutableMapOf<String, ByteArray>()
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<String, List<String>> by lazy {
allMacAddresses.mapValues { entry ->
entry.value.map { String.format("%02X", it) }
}
}

View File

@ -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)
}
}
}

View File

@ -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()
}
}