generated from mingliqiye/lib-tem
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:
parent
541a8a82b4
commit
fcd528a821
1
.gitignore
vendored
1
.gitignore
vendored
@ -45,3 +45,4 @@ log
|
||||
node_modules
|
||||
*lock*
|
||||
.kotlin
|
||||
secret.gpg
|
||||
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"plugins": [
|
||||
"prettier-plugin-java"
|
||||
],
|
||||
"tabWidth": 4,
|
||||
"useTabs": true
|
||||
}
|
@ -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 {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
23
jdk8/gradle.properties
Normal 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
|
14
package.json
14
package.json
@ -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"
|
||||
}
|
||||
}
|
@ -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
|
||||
*/
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
}
|
||||
|
54
src/main/kotlin/com/mingliqiye/utils/base/Base16.kt
Normal file
54
src/main/kotlin/com/mingliqiye/utils/base/Base16.kt
Normal 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()
|
||||
}
|
||||
|
||||
}
|
62
src/main/kotlin/com/mingliqiye/utils/base/Base64.kt
Normal file
62
src/main/kotlin/com/mingliqiye/utils/base/Base64.kt
Normal 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)
|
||||
}
|
||||
}
|
158
src/main/kotlin/com/mingliqiye/utils/base/Base91.kt
Normal file
158
src/main/kotlin/com/mingliqiye/utils/base/Base91.kt
Normal 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)
|
||||
}
|
||||
}
|
153
src/main/kotlin/com/mingliqiye/utils/base/BaseCodec.kt
Normal file
153
src/main/kotlin/com/mingliqiye/utils/base/BaseCodec.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
54
src/main/kotlin/com/mingliqiye/utils/base/BaseUtils.kt
Normal file
54
src/main/kotlin/com/mingliqiye/utils/base/BaseUtils.kt
Normal 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()
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
|
100
src/main/kotlin/com/mingliqiye/utils/number/NumberUtils.kt
Normal file
100
src/main/kotlin/com/mingliqiye/utils/number/NumberUtils.kt
Normal 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)
|
||||
}
|
@ -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)
|
||||
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user