Compare commits
13 Commits
e6447fdd43
...
0.1.4
Author | SHA1 | Date | |
---|---|---|---|
|
c94d95f5fb | ||
|
07e0d3938a | ||
|
21892e977f | ||
|
cc05fc9a40 | ||
|
7b1834479f | ||
|
ea194a1fd1 | ||
|
5b4dff402c | ||
|
0697e61f35 | ||
|
a89f9aff15 | ||
|
51a7a48962 | ||
|
3f86e75518 | ||
|
dbcdfc426e | ||
|
d1c19355a5 |
35
Cargo.toml
35
Cargo.toml
@@ -9,22 +9,31 @@ crate-type = ["dylib", "rlib"]
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aes = "0.8.3"
|
aes = "0.8.4"
|
||||||
base64 = "0.21.2"
|
base64 = "0.22.1"
|
||||||
blockhash = "0.5.0"
|
blake2b_simd = "1.0.3"
|
||||||
|
blake3 = { version = "1.8.2", features = ["serde", "digest"] }
|
||||||
|
blockhash = "1.0.0"
|
||||||
cbc = { version = "0.1.2", features = ["std"] }
|
cbc = { version = "0.1.2", features = ["std"] }
|
||||||
chrono = "0.4.26"
|
|
||||||
cipher = "0.4.4"
|
cipher = "0.4.4"
|
||||||
des = "0.8.1"
|
des = "0.8.1"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
hmac-sha256 = "1.1.7"
|
hmac-sha256 = "1.1.12"
|
||||||
hmac-sha512 = "1.1.5"
|
hmac-sha512 = "1.1.7"
|
||||||
image = "0.24.6"
|
image = "0.25.8"
|
||||||
md-5 = "0.10.5"
|
md-5 = "0.10.6"
|
||||||
once_cell = "1.18.0"
|
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rsa = { version = "0.9.2", features = ["sha2"] }
|
rsa = { version = "0.9.2", features = ["sha2"] }
|
||||||
sha1 = "0.10.5"
|
sha1 = "0.10.6"
|
||||||
sha2 = "0.10.7"
|
sha2 = "0.10.9"
|
||||||
thiserror = "1.0.40"
|
thiserror = "2.0.17"
|
||||||
uuid = { version = "1.4.0", features = ["v4", "fast-rng"] }
|
time = { version = "0.3.44", features = [
|
||||||
|
"formatting",
|
||||||
|
"local-offset",
|
||||||
|
"macros",
|
||||||
|
"parsing",
|
||||||
|
"rand",
|
||||||
|
"serde",
|
||||||
|
"serde-human-readable",
|
||||||
|
] }
|
||||||
|
uuid = { version = "1.18.1", features = ["v4", "fast-rng"] }
|
||||||
|
16
README.md
16
README.md
@@ -21,13 +21,18 @@ Rust 中可以使用的常用辅助功能工具箱。主要配备以下功能:
|
|||||||
- [x] 2048 位长
|
- [x] 2048 位长
|
||||||
- [x] KeyPair 生成器
|
- [x] KeyPair 生成器
|
||||||
- 散列算法。
|
- 散列算法。
|
||||||
- [x] Sha512 散列算法
|
- [x] Sha512 散列算法(便捷封装)
|
||||||
- [x] Sha1 散列算法
|
- [x] Sha1 散列算法(便捷封装)
|
||||||
- [x] MD5 散列算法
|
- [x] MD5 散列算法(便捷封装)
|
||||||
- [x] 图像感知散列算法
|
- [x] 图像感知散列算法(便捷封装)
|
||||||
|
- [x] BLAKE2b 校验和算法(便捷封装)
|
||||||
|
- [x] BLAKE3 校验和算法(便捷封装)
|
||||||
- 唯一序列号生成器
|
- 唯一序列号生成器
|
||||||
- [x] 冰雹 ID 生成器(短主机精简日期版雪花 ID)
|
- [x] 冰雹 ID 生成器(短主机精简日期版雪花 ID)
|
||||||
- [x] UUID 生成器
|
- [x] UUIDv4 生成器
|
||||||
|
- [x] UUIDv7 生成器(自定义时间戳分布式版本)
|
||||||
|
- [x] UUIDv7 比较及排序
|
||||||
|
- [x] 基于 Base36 的 short UUIDv7 转换器
|
||||||
- [x] short UUID 生成器
|
- [x] short UUID 生成器
|
||||||
- 签名算法
|
- 签名算法
|
||||||
- [x] RSA 签名算法
|
- [x] RSA 签名算法
|
||||||
@@ -35,6 +40,7 @@ Rust 中可以使用的常用辅助功能工具箱。主要配备以下功能:
|
|||||||
- [x] 随机验证码生成算法
|
- [x] 随机验证码生成算法
|
||||||
- 序列化算法
|
- 序列化算法
|
||||||
- [x] Base64 算法
|
- [x] Base64 算法
|
||||||
|
- [x] Base36 算法
|
||||||
- [x] Hex 直转
|
- [x] Hex 直转
|
||||||
- 常用工具函数
|
- 常用工具函数
|
||||||
- [x] 日期时间函数
|
- [x] 日期时间函数
|
||||||
|
@@ -40,7 +40,7 @@ pub fn encrypt(data: String) -> String {
|
|||||||
/// - `data` 待解密的内容
|
/// - `data` 待解密的内容
|
||||||
pub fn decrypt(data: String) -> Result<String, SpiralCipherError> {
|
pub fn decrypt(data: String) -> Result<String, SpiralCipherError> {
|
||||||
if !data.starts_with("[") || data.len() <= 21 {
|
if !data.starts_with("[") || data.len() <= 21 {
|
||||||
return Err(SpiralCipherError::CorruptedCipherData);
|
return Ok(data);
|
||||||
}
|
}
|
||||||
let data = data[1..].to_string();
|
let data = data[1..].to_string();
|
||||||
let key_seed = data[0..20].to_string();
|
let key_seed = data[0..20].to_string();
|
||||||
|
97
src/hash/blake2b/mod.rs
Normal file
97
src/hash/blake2b/mod.rs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
use blake2b_simd::{blake2b as blake2b_hasher, Params, State};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, Read};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// 根据给定的位数返回一个Blake2b散列实例。
|
||||||
|
fn hasher_select(bit_size: usize) -> State {
|
||||||
|
match bit_size {
|
||||||
|
224 => Params::new().hash_length(28).to_state(), // 224 bits = 28 bytes
|
||||||
|
384 => Params::new().hash_length(48).to_state(), // 384 bits = 48 bytes
|
||||||
|
512 => Params::new().hash_length(64).to_state(), // 512 bits = 64 bytes
|
||||||
|
256 | _ => Params::new().hash_length(32).to_state(), // 256 bits = 32 bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 计算给定字节数组的Blake2b校验和,返回字节数组。
|
||||||
|
pub fn blake2b(data: &[u8]) -> Vec<u8> {
|
||||||
|
blake2b_hasher(data).as_bytes().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 计算给定字节数组的Blake2b/256校验和,返回字节数组。
|
||||||
|
pub fn blake2b_256(data: &[u8]) -> Vec<u8> {
|
||||||
|
let mut hasher = hasher_select(256);
|
||||||
|
hasher.update(data);
|
||||||
|
hasher.finalize().as_bytes().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 计算给定字节数组的Blake2b/384校验和,返回字节数组。
|
||||||
|
pub fn blake2b_384(data: &[u8]) -> Vec<u8> {
|
||||||
|
let mut hasher = hasher_select(384);
|
||||||
|
hasher.update(data);
|
||||||
|
hasher.finalize().as_bytes().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 计算给定字节数组的Blake2b/224校验和,返回字节数组。
|
||||||
|
pub fn blake2b_224(data: &[u8]) -> Vec<u8> {
|
||||||
|
let mut hasher = hasher_select(224);
|
||||||
|
hasher.update(data);
|
||||||
|
hasher.finalize().as_bytes().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 计算给定字节数组的Blake2b校验和,返回十六进制字符串。
|
||||||
|
pub fn blake2b_hex(data: &[u8]) -> String {
|
||||||
|
hex::encode(blake2b(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 计算给定字节数组的Blake2b/256校验和,返回十六进制字符串。
|
||||||
|
pub fn blake2b_256_hex(data: &[u8]) -> String {
|
||||||
|
hex::encode(blake2b_256(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 计算给定字节数组的Blake2b/384校验和,返回十六进制字符串。
|
||||||
|
pub fn blake2b_384_hex(data: &[u8]) -> String {
|
||||||
|
hex::encode(blake2b_384(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 计算给定字节数组的Blake2b/224校验和,返回十六进制字符串。
|
||||||
|
pub fn blake2b_224_hex(data: &[u8]) -> String {
|
||||||
|
hex::encode(blake2b_224(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据给定位数计算一个字节数组的Blake2b校验和,返回字节数组。
|
||||||
|
pub fn sum(data: &[u8], bit_size: Option<usize>) -> Vec<u8> {
|
||||||
|
let size = bit_size.unwrap_or(512);
|
||||||
|
let mut hasher = hasher_select(size);
|
||||||
|
hasher.update(data);
|
||||||
|
hasher.finalize().as_bytes().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据给定位数计算一个字节数组的Blake2b校验和,返回十六进制字符串。
|
||||||
|
pub fn sum_hex(data: &[u8], bit_size: Option<usize>) -> String {
|
||||||
|
hex::encode(sum(data, bit_size))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据给定位数计算一个文件的Blake2b校验和,返回字节数组。
|
||||||
|
pub fn sum_file<P: AsRef<Path>>(file_path: P, bit_size: Option<usize>) -> io::Result<Vec<u8>> {
|
||||||
|
let size = bit_size.unwrap_or(512);
|
||||||
|
let mut file = File::open(file_path)?;
|
||||||
|
let mut hasher = hasher_select(size);
|
||||||
|
|
||||||
|
let mut buffer = [0; 8192];
|
||||||
|
loop {
|
||||||
|
let bytes_read = file.read(&mut buffer)?;
|
||||||
|
if bytes_read == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
hasher.update(&buffer[..bytes_read]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(hasher.finalize().as_bytes().to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据给定位数计算一个文件的Blake2b校验和,返回十六进制字符串。
|
||||||
|
pub fn sum_file_hex<P: AsRef<Path>>(file_path: P, bit_size: Option<usize>) -> io::Result<String> {
|
||||||
|
let hash = sum_file(file_path, bit_size)?;
|
||||||
|
Ok(hex::encode(hash))
|
||||||
|
}
|
109
src/hash/blake3/mod.rs
Normal file
109
src/hash/blake3/mod.rs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
use blake3::Hasher;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Read, Result as IoResult};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// 计算给定字节数组的Blake3/512校验和,返回字节数组。
|
||||||
|
pub fn blake3(data: &[u8]) -> Vec<u8> {
|
||||||
|
let mut hasher = Hasher::new();
|
||||||
|
hasher.update(data);
|
||||||
|
let mut out = vec![0u8, 64];
|
||||||
|
hasher.finalize_xof().fill(&mut out);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 计算给定字节数组的Blake3/256校验和,返回字节数组。
|
||||||
|
pub fn blake3_256(data: &[u8]) -> Vec<u8> {
|
||||||
|
let mut hasher = Hasher::new();
|
||||||
|
hasher.update(data);
|
||||||
|
hasher.finalize().as_bytes().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 计算给定字节数组的Blake3/384校验和,返回字节数组。
|
||||||
|
pub fn blake3_384(data: &[u8]) -> Vec<u8> {
|
||||||
|
let mut hasher = Hasher::new();
|
||||||
|
hasher.update(data);
|
||||||
|
let mut out = vec![0u8, 48];
|
||||||
|
hasher.finalize_xof().fill(&mut out);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 计算给定字节数组的Blake3/224校验和,返回字节数组。
|
||||||
|
pub fn blake3_224(data: &[u8]) -> Vec<u8> {
|
||||||
|
let mut hasher = Hasher::new();
|
||||||
|
hasher.update(data);
|
||||||
|
let mut out = vec![0u8, 28];
|
||||||
|
hasher.finalize_xof().fill(&mut out);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 计算给定字节数组的Blake3校验和,返回十六进制字符串。
|
||||||
|
pub fn blake3_hex(data: &[u8]) -> String {
|
||||||
|
hex::encode(blake3(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 计算给定字节数组的Blake3/256校验和,返回十六进制字符串。
|
||||||
|
pub fn blake3_256_hex(data: &[u8]) -> String {
|
||||||
|
hex::encode(blake3_256(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 计算给定字节数组的Blake3/384校验和,返回十六进制字符串。
|
||||||
|
pub fn blake3_384_hex(data: &[u8]) -> String {
|
||||||
|
hex::encode(blake3_384(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 计算给定字节数组的Blake3/224校验和,返回十六进制字符串。
|
||||||
|
pub fn blake3_224_hex(data: &[u8]) -> String {
|
||||||
|
hex::encode(blake3_224(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据给定位数计算一个字节数组的Blake3校验和,返回字节数组。
|
||||||
|
pub fn sum(data: &[u8], bit_size: Option<usize>) -> Vec<u8> {
|
||||||
|
match bit_size {
|
||||||
|
Some(bit_size) => match bit_size {
|
||||||
|
224 => blake3_224(data),
|
||||||
|
256 => blake3_256(data),
|
||||||
|
384 => blake3_384(data),
|
||||||
|
512 | _ => blake3(data),
|
||||||
|
},
|
||||||
|
None => blake3(data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据给定位数计算一个字节数组的Blake3校验和,返回十六进制字符串。
|
||||||
|
pub fn sum_hex(data: &[u8], bit_size: Option<usize>) -> String {
|
||||||
|
hex::encode(sum(data, bit_size))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据给定位数计算一个文件的Blake3校验和,返回字节数组。
|
||||||
|
pub fn sum_file<P: AsRef<Path>>(file: P, bit_size: Option<usize>) -> IoResult<Vec<u8>> {
|
||||||
|
let mut f = File::open(file)?;
|
||||||
|
let mut hasher = Hasher::new();
|
||||||
|
|
||||||
|
let mut buffer = [0; 8192];
|
||||||
|
loop {
|
||||||
|
let bytes_read = f.read(&mut buffer)?;
|
||||||
|
if bytes_read == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
hasher.update(&buffer[..bytes_read]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out = match bit_size {
|
||||||
|
Some(bit_size) => match bit_size {
|
||||||
|
224 => vec![0u8, 28],
|
||||||
|
256 => vec![0u8, 32],
|
||||||
|
384 => vec![0u8, 48],
|
||||||
|
512 | _ => vec![0u8, 64],
|
||||||
|
},
|
||||||
|
None => vec![0u8, 64],
|
||||||
|
};
|
||||||
|
hasher.finalize_xof().fill(&mut out);
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据给定位数计算一个文件的Blake3校验和,返回十六进制字符串。
|
||||||
|
pub fn sum_file_hex<P: AsRef<Path>>(file: P, bit_size: Option<usize>) -> IoResult<String> {
|
||||||
|
let hash = sum_file(file, bit_size)?;
|
||||||
|
Ok(hex::encode(hash))
|
||||||
|
}
|
@@ -1,5 +1,9 @@
|
|||||||
pub mod md5 {
|
pub mod md5 {
|
||||||
use md5::Digest;
|
use md5::Digest;
|
||||||
|
|
||||||
|
/// 计算输入内容的MD5哈希值,返回十六进制字符串。
|
||||||
|
///
|
||||||
|
/// - `input` 输入内容。
|
||||||
pub fn hash<T: AsRef<[u8]>>(input: T) -> String {
|
pub fn hash<T: AsRef<[u8]>>(input: T) -> String {
|
||||||
let mut hasher = md5::Md5::new();
|
let mut hasher = md5::Md5::new();
|
||||||
hasher.update(input);
|
hasher.update(input);
|
||||||
@@ -7,6 +11,9 @@ pub mod md5 {
|
|||||||
crate::serialize::to_hex(result.as_slice())
|
crate::serialize::to_hex(result.as_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 计算输入内容的MD5哈希值,返回字节向量。
|
||||||
|
///
|
||||||
|
/// - `input` 输入内容。
|
||||||
pub fn hash_hex<T: AsRef<[u8]>>(input: T) -> Vec<u8> {
|
pub fn hash_hex<T: AsRef<[u8]>>(input: T) -> Vec<u8> {
|
||||||
let mut hasher = md5::Md5::new();
|
let mut hasher = md5::Md5::new();
|
||||||
hasher.update(input);
|
hasher.update(input);
|
||||||
@@ -17,6 +24,10 @@ pub mod md5 {
|
|||||||
|
|
||||||
pub mod sha1 {
|
pub mod sha1 {
|
||||||
use sha1::Digest;
|
use sha1::Digest;
|
||||||
|
|
||||||
|
/// 计算输入内容的SHA1哈希值,返回十六进制字符串。
|
||||||
|
///
|
||||||
|
/// - `input` 输入内容。
|
||||||
pub fn hash<T: AsRef<[u8]>>(input: T) -> String {
|
pub fn hash<T: AsRef<[u8]>>(input: T) -> String {
|
||||||
let mut hasher = sha1::Sha1::new();
|
let mut hasher = sha1::Sha1::new();
|
||||||
hasher.update(input);
|
hasher.update(input);
|
||||||
@@ -24,6 +35,9 @@ pub mod sha1 {
|
|||||||
crate::serialize::to_hex(result.as_slice())
|
crate::serialize::to_hex(result.as_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 计算输入内容的SHA1哈希值,返回字节向量。
|
||||||
|
///
|
||||||
|
/// - `input` 输入内容。
|
||||||
pub fn hash_hex<T: AsRef<[u8]>>(input: T) -> Vec<u8> {
|
pub fn hash_hex<T: AsRef<[u8]>>(input: T) -> Vec<u8> {
|
||||||
let mut hasher = sha1::Sha1::new();
|
let mut hasher = sha1::Sha1::new();
|
||||||
hasher.update(input);
|
hasher.update(input);
|
||||||
@@ -33,6 +47,10 @@ pub mod sha1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod sha512 {
|
pub mod sha512 {
|
||||||
|
|
||||||
|
/// 计算输入内容的SHA512哈希值,返回十六进制字符串。
|
||||||
|
///
|
||||||
|
/// - `input` 输入内容。
|
||||||
pub fn hash<T: AsRef<[u8]>>(input: T) -> String {
|
pub fn hash<T: AsRef<[u8]>>(input: T) -> String {
|
||||||
let mut hasher = hmac_sha512::Hash::new();
|
let mut hasher = hmac_sha512::Hash::new();
|
||||||
hasher.update(input);
|
hasher.update(input);
|
||||||
@@ -40,6 +58,9 @@ pub mod sha512 {
|
|||||||
crate::serialize::to_hex(result.as_slice())
|
crate::serialize::to_hex(result.as_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 计算输入内容的SHA512哈希值,返回字节向量。
|
||||||
|
///
|
||||||
|
/// - `input` 输入内容。
|
||||||
pub fn hash_hex<T: AsRef<[u8]>>(input: T) -> Vec<u8> {
|
pub fn hash_hex<T: AsRef<[u8]>>(input: T) -> Vec<u8> {
|
||||||
let mut hasher = hmac_sha512::Hash::new();
|
let mut hasher = hmac_sha512::Hash::new();
|
||||||
hasher.update(input);
|
hasher.update(input);
|
||||||
@@ -56,6 +77,10 @@ pub mod image_hash {
|
|||||||
Detailed = 32,
|
Detailed = 32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 计算输入图片的区块感知哈希值,返回十六进制字符串。
|
||||||
|
///
|
||||||
|
/// - `input` 输入图片。
|
||||||
|
/// - `precision` 感知精度,感知精度越高,越能区分图片的细节,但是计算时间也越长。
|
||||||
pub fn hash_image<T: image::GenericImage<Pixel = image::Rgb<u8>>>(
|
pub fn hash_image<T: image::GenericImage<Pixel = image::Rgb<u8>>>(
|
||||||
input: &T,
|
input: &T,
|
||||||
precision: Precision,
|
precision: Precision,
|
||||||
@@ -68,3 +93,6 @@ pub mod image_hash {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod blake2b;
|
||||||
|
pub mod blake3;
|
||||||
|
@@ -1,22 +1,20 @@
|
|||||||
use core::time;
|
use core::time;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, LazyLock, Mutex, OnceLock};
|
||||||
|
|
||||||
use chrono::NaiveDateTime;
|
use ::time::{macros::datetime, OffsetDateTime};
|
||||||
use once_cell::sync::{Lazy, OnceCell};
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
const HAIL_PERIOD_START: Lazy<i64> = Lazy::new(|| {
|
const HAIL_PERIOD_START: LazyLock<i64> = LazyLock::new(|| {
|
||||||
crate::time::date(2022, 2, 22)
|
crate::time::date(2022, 2, 22)
|
||||||
.map(|d| d.and_hms_opt(22, 22, 22))
|
.map(|d| d.with_hms_nano(22, 22, 22, 222_222_222).unwrap())
|
||||||
.flatten()
|
.map(crate::time::attach_asia_shanghai)
|
||||||
.map(|dt| crate::time::attach_asia_shanghai(dt))
|
.map(OffsetDateTime::unix_timestamp)
|
||||||
.map(|dt| dt.timestamp())
|
.unwrap_or_else(|| datetime!(1970-01-01 0:00 +8).unix_timestamp())
|
||||||
.unwrap_or_else(|| NaiveDateTime::MIN.timestamp())
|
|
||||||
});
|
});
|
||||||
type TimestampValidator = fn(i64) -> bool;
|
type TimestampValidator = fn(i64) -> bool;
|
||||||
type TimestampGenerator = fn() -> i64;
|
type TimestampGenerator = fn() -> i64;
|
||||||
|
|
||||||
static INSTANCE: OnceCell<HailSerialCodeAlgorithm> = OnceCell::new();
|
static INSTANCE: OnceLock<HailSerialCodeAlgorithm> = OnceLock::new();
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum HailSerialCodeAlgorithmError {
|
pub enum HailSerialCodeAlgorithmError {
|
||||||
@@ -65,17 +63,10 @@ impl HailSerialCodeAlgorithm {
|
|||||||
|
|
||||||
/// 生成一个自计时起点以来的时间戳。
|
/// 生成一个自计时起点以来的时间戳。
|
||||||
fn generate_timestamp(&self) -> i64 {
|
fn generate_timestamp(&self) -> i64 {
|
||||||
let current_time = crate::time::now_asia_shanghai().timestamp();
|
let current_time = crate::time::now_asia_shanghai().unix_timestamp();
|
||||||
current_time - *HAIL_PERIOD_START
|
current_time - *HAIL_PERIOD_START
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 判断指定时间戳是否比已经存储的最后一次使用的时间戳要大。否则时间发生了回拨。
|
|
||||||
fn validate_timestamp(&self, timestamp: i64) -> bool {
|
|
||||||
let last_timestamp = self.last_timestamp.clone();
|
|
||||||
let last_timestamp = last_timestamp.lock().unwrap();
|
|
||||||
timestamp >= *last_timestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 生成一个64位长整型序列ID。
|
/// 生成一个64位长整型序列ID。
|
||||||
pub fn generate_serial(&self) -> i64 {
|
pub fn generate_serial(&self) -> i64 {
|
||||||
let last_timestamp = self.last_timestamp.clone();
|
let last_timestamp = self.last_timestamp.clone();
|
||||||
@@ -93,7 +84,7 @@ impl HailSerialCodeAlgorithm {
|
|||||||
std::thread::sleep(time::Duration::from_secs(1));
|
std::thread::sleep(time::Duration::from_secs(1));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else if !self.validate_timestamp(timestamp) {
|
} else if timestamp < *last_timestamp {
|
||||||
std::thread::sleep(time::Duration::from_secs(1));
|
std::thread::sleep(time::Duration::from_secs(1));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
pub mod hail;
|
pub mod hail;
|
||||||
|
pub mod uuidv7;
|
||||||
|
|
||||||
pub mod uuid {
|
pub mod uuid {
|
||||||
|
/// 生成一个UUID v4字符串。
|
||||||
pub fn new() -> Box<String> {
|
pub fn new() -> Box<String> {
|
||||||
Box::from(uuid::Uuid::new_v4().to_string())
|
Box::from(uuid::Uuid::new_v4().to_string())
|
||||||
}
|
}
|
||||||
@@ -8,6 +10,8 @@ pub mod uuid {
|
|||||||
|
|
||||||
pub mod short_uuid {
|
pub mod short_uuid {
|
||||||
const STR_SRC: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
const STR_SRC: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
||||||
|
|
||||||
|
/// 生成一个基于UUID v4的短UUID字符串。
|
||||||
pub fn new(length: i16) -> Box<String> {
|
pub fn new(length: i16) -> Box<String> {
|
||||||
let length = if length < 2 {
|
let length = if length < 2 {
|
||||||
2
|
2
|
||||||
|
291
src/serial_code/uuidv7.rs
Normal file
291
src/serial_code/uuidv7.rs
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
use std::{
|
||||||
|
sync::{Mutex, OnceLock},
|
||||||
|
thread,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
use time::UtcDateTime;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum UuidV7Error {
|
||||||
|
#[error("UUIDv7 Generator not initialized")]
|
||||||
|
GeneratorNotInitialized,
|
||||||
|
#[error("UUIDv7 Node ID exceeded the maximum supports")]
|
||||||
|
NodeIdExceeded,
|
||||||
|
#[error("Invalid UUIDv7 string format")]
|
||||||
|
InvalidStringFormat,
|
||||||
|
#[error("Invalid Base64 format string")]
|
||||||
|
InvalidBase64Format,
|
||||||
|
}
|
||||||
|
|
||||||
|
const EPOCH: i64 = 1645566142222; // 自定义纪元:2022-02-22 22:22:22.222 UTC
|
||||||
|
const NODE_BITS: u8 = 5; // 主机编号容量为0~32
|
||||||
|
const SEQUENCE_BITS: u8 = 18; // 每毫秒生成序列号最大为2^18-1,即262144个
|
||||||
|
const MAX_NODE_ID: u16 = (1 << NODE_BITS) - 1;
|
||||||
|
const MAX_SEQUENCE: u32 = (1 << SEQUENCE_BITS) - 1;
|
||||||
|
const TIMESTAMP_SHIFTS: u8 = NODE_BITS + SEQUENCE_BITS;
|
||||||
|
const NODE_SHIFTS: u8 = SEQUENCE_BITS;
|
||||||
|
|
||||||
|
pub struct Uuidv7Generator {
|
||||||
|
node_id: u16,
|
||||||
|
last_timestamp: i64,
|
||||||
|
sequence: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct Uuidv7Components {
|
||||||
|
timestamp: i64, // 毫秒时间戳(相对于自定义纪元)
|
||||||
|
node_id: u16, // 5位节点ID
|
||||||
|
sequence: u32, // 18位序列号
|
||||||
|
version: u8, // 版本号(应为7)
|
||||||
|
variant: u8, // 变体(应为2,表示10xx)
|
||||||
|
raw_bytes: [u8; 16], // 原始字节
|
||||||
|
}
|
||||||
|
|
||||||
|
static GENERATOR: OnceLock<Mutex<Uuidv7Generator>> = OnceLock::new();
|
||||||
|
|
||||||
|
/// 获取UUIDv7生成器实例,如果实例未初始化,则返回错误。
|
||||||
|
pub fn generator<'a>() -> Result<&'a Mutex<Uuidv7Generator>, UuidV7Error> {
|
||||||
|
GENERATOR.get().ok_or(UuidV7Error::GeneratorNotInitialized)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 初始化UUIDv7生成器实例。
|
||||||
|
///
|
||||||
|
/// - `node_id`:节点ID,取值范围:0~32。
|
||||||
|
pub fn init_generator(node_id: u16) -> Result<(), UuidV7Error> {
|
||||||
|
if node_id > MAX_NODE_ID {
|
||||||
|
return Err(UuidV7Error::NodeIdExceeded);
|
||||||
|
}
|
||||||
|
GENERATOR
|
||||||
|
.set(Mutex::new(Uuidv7Generator::new(node_id)))
|
||||||
|
.map_err(|_| UuidV7Error::GeneratorNotInitialized)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Uuidv7Generator {
|
||||||
|
pub fn new(node_id: u16) -> Self {
|
||||||
|
Self {
|
||||||
|
node_id,
|
||||||
|
last_timestamp: EPOCH,
|
||||||
|
sequence: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn now(&self) -> i64 {
|
||||||
|
(UtcDateTime::now().unix_timestamp_nanos() / 1_000_000 - EPOCH as i128) as i64
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) -> Uuidv7Components {
|
||||||
|
let mut now = self.now();
|
||||||
|
if now < self.last_timestamp {
|
||||||
|
now = self.last_timestamp;
|
||||||
|
}
|
||||||
|
if now == self.last_timestamp {
|
||||||
|
self.sequence += 1;
|
||||||
|
if self.sequence > MAX_SEQUENCE {
|
||||||
|
thread::sleep(Duration::from_secs(1));
|
||||||
|
now = self.now();
|
||||||
|
self.sequence = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.sequence = 0;
|
||||||
|
}
|
||||||
|
self.last_timestamp = now;
|
||||||
|
Uuidv7Components {
|
||||||
|
timestamp: now,
|
||||||
|
node_id: self.node_id,
|
||||||
|
sequence: self.sequence,
|
||||||
|
version: 7,
|
||||||
|
variant: 2,
|
||||||
|
raw_bytes: [0; 16],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Uuidv7Components {
|
||||||
|
pub fn bytes(&self) -> [u8; 16] {
|
||||||
|
let mut uuid = [0u8; 16];
|
||||||
|
|
||||||
|
// 时间戳(48 位)
|
||||||
|
let timestamp = (self.timestamp as u64) << TIMESTAMP_SHIFTS;
|
||||||
|
let seq_and_node = ((self.sequence as u64) << NODE_SHIFTS) | (self.node_id as u64);
|
||||||
|
|
||||||
|
// 写入高位时间戳
|
||||||
|
uuid[0] = (timestamp >> 56) as u8;
|
||||||
|
uuid[1] = (timestamp >> 48) as u8;
|
||||||
|
uuid[2] = (timestamp >> 40) as u8;
|
||||||
|
uuid[3] = (timestamp >> 32) as u8;
|
||||||
|
uuid[4] = (timestamp >> 24) as u8;
|
||||||
|
uuid[5] = (timestamp >> 16) as u8;
|
||||||
|
|
||||||
|
// 写入低位时间戳 + 版本号(4 位)
|
||||||
|
uuid[6] = ((timestamp >> 8) as u8) | 0x70; // version 7
|
||||||
|
|
||||||
|
// 写入 seq + node
|
||||||
|
uuid[7] = timestamp as u8;
|
||||||
|
uuid[8] = ((seq_and_node >> 16) as u8) | 0x80; // variant 10xx
|
||||||
|
uuid[9] = (seq_and_node >> 8) as u8;
|
||||||
|
uuid[10] = seq_and_node as u8;
|
||||||
|
|
||||||
|
// 剩余位使用随机数填充
|
||||||
|
use rand::RngCore;
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
rng.fill_bytes(&mut uuid[11..16]);
|
||||||
|
|
||||||
|
uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_base64(&self) -> String {
|
||||||
|
let uuid = self.bytes();
|
||||||
|
crate::serialize::to_base64_str(uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_from_base64<S: AsRef<str>>(uuid_str: S) -> Result<Self, UuidV7Error> {
|
||||||
|
let bytes = crate::serialize::from_base64_str(uuid_str.as_ref())
|
||||||
|
.map_err(|_| UuidV7Error::InvalidBase64Format)?;
|
||||||
|
|
||||||
|
if bytes.len() != 16 {
|
||||||
|
return Err(UuidV7Error::InvalidStringFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut uuid_bytes = [0u8; 16];
|
||||||
|
uuid_bytes.copy_from_slice(&bytes);
|
||||||
|
|
||||||
|
Uuidv7Components::try_from(uuid_bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_base36(&self) -> String {
|
||||||
|
let uuid = self.bytes();
|
||||||
|
crate::serialize::base36::encode(&uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_from_base36<S: AsRef<str>>(uuid_str: S) -> Result<Self, UuidV7Error> {
|
||||||
|
let bytes = crate::serialize::base36::decode(uuid_str.as_ref())
|
||||||
|
.map_err(|_| UuidV7Error::InvalidStringFormat)?;
|
||||||
|
|
||||||
|
if bytes.len() != 16 {
|
||||||
|
return Err(UuidV7Error::InvalidStringFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut uuid_bytes = [0u8; 16];
|
||||||
|
uuid_bytes.copy_from_slice(&bytes);
|
||||||
|
|
||||||
|
Uuidv7Components::try_from(uuid_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for Uuidv7Components {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
let uuid = self.bytes();
|
||||||
|
format!(
|
||||||
|
"{}-{}-{}-{}-{}",
|
||||||
|
hex::encode(&uuid[0..4]),
|
||||||
|
hex::encode(&uuid[4..6]),
|
||||||
|
hex::encode(&uuid[6..8]),
|
||||||
|
hex::encode(&uuid[8..10]),
|
||||||
|
hex::encode(&uuid[10..16])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Uuidv7Components {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.timestamp == other.timestamp
|
||||||
|
&& self.node_id == other.node_id
|
||||||
|
&& self.sequence == other.sequence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Uuidv7Components {}
|
||||||
|
|
||||||
|
impl PartialOrd for Uuidv7Components {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
if self.timestamp != other.timestamp {
|
||||||
|
return Some(self.timestamp.cmp(&other.timestamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.sequence != other.sequence {
|
||||||
|
return Some(self.sequence.cmp(&other.sequence));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.node_id != other.node_id {
|
||||||
|
// 注意:这里与时间戳和序列号不同,node_id的比较逻辑是反的
|
||||||
|
// 这与Go代码中的Compare方法保持一致
|
||||||
|
return Some(other.node_id.cmp(&self.node_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(std::cmp::Ordering::Equal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Uuidv7Components {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.partial_cmp(other).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<[u8; 16]> for Uuidv7Components {
|
||||||
|
type Error = UuidV7Error;
|
||||||
|
|
||||||
|
fn try_from(value: [u8; 16]) -> Result<Self, Self::Error> {
|
||||||
|
// 提取时间戳(前48位)
|
||||||
|
let timestamp = ((value[0] as i64) << 40)
|
||||||
|
| ((value[1] as i64) << 32)
|
||||||
|
| ((value[2] as i64) << 24)
|
||||||
|
| ((value[3] as i64) << 16)
|
||||||
|
| ((value[4] as i64) << 8)
|
||||||
|
| (value[5] as i64);
|
||||||
|
|
||||||
|
// 提取版本号(第6字节的高4位)
|
||||||
|
let version = (value[6] >> 4) as u8;
|
||||||
|
|
||||||
|
// 提取变体(第8字节的高2位)
|
||||||
|
let variant = (value[8] >> 6) as u8;
|
||||||
|
|
||||||
|
// 提取序列号和节点ID
|
||||||
|
// 第8字节的低6位 + 第9字节 + 第10字节组成23位
|
||||||
|
let seq_and_node = (((value[8] & 0x3F) as i64) << 16) // 第8字节低6位
|
||||||
|
| ((value[9] as i64) << 8) // 第9字节
|
||||||
|
| (value[10] as i64); // 第10字节
|
||||||
|
|
||||||
|
// 分离序列号(高18位)和节点ID(低5位)
|
||||||
|
let sequence = (seq_and_node >> 5) as u32;
|
||||||
|
let node_id = (seq_and_node & 0x1F) as u16;
|
||||||
|
|
||||||
|
Ok(Uuidv7Components {
|
||||||
|
timestamp,
|
||||||
|
node_id,
|
||||||
|
sequence,
|
||||||
|
version,
|
||||||
|
variant,
|
||||||
|
raw_bytes: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for Uuidv7Components {
|
||||||
|
type Error = UuidV7Error;
|
||||||
|
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
// 移除连字符
|
||||||
|
let clean_str = value.replace("-", "");
|
||||||
|
|
||||||
|
// 验证长度
|
||||||
|
if clean_str.len() != 32 {
|
||||||
|
return Err(UuidV7Error::InvalidStringFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码十六进制字符串
|
||||||
|
let bytes = hex::decode(&clean_str).map_err(|_| UuidV7Error::InvalidStringFormat)?;
|
||||||
|
|
||||||
|
// 转换为数组
|
||||||
|
if bytes.len() != 16 {
|
||||||
|
return Err(UuidV7Error::InvalidStringFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut uuid_bytes = [0u8; 16];
|
||||||
|
uuid_bytes.copy_from_slice(&bytes);
|
||||||
|
|
||||||
|
Uuidv7Components::try_from(uuid_bytes)
|
||||||
|
}
|
||||||
|
}
|
144
src/serialize/base36.rs
Normal file
144
src/serialize/base36.rs
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
const BASE36_CHARS: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Base36Error {
|
||||||
|
#[error("number too large for i64")]
|
||||||
|
NumberTooLarge,
|
||||||
|
#[error("invalid character '{0}'")]
|
||||||
|
InvalidCharacter(char),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 将字节数据编码为Base36字符串(大写),使用=作为padding字符
|
||||||
|
pub fn encode(src: &[u8]) -> String {
|
||||||
|
if src.is_empty() {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将字节转换为大数表示,这里使用Vec<u8>模拟大整数运算
|
||||||
|
let mut num = src.to_vec();
|
||||||
|
|
||||||
|
// 去除前导零但保留至少一个字节
|
||||||
|
while num.len() > 1 && num[0] == 0 {
|
||||||
|
num.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果输入为全零字节,则直接返回"0"
|
||||||
|
if num.iter().all(|&b| b == 0) {
|
||||||
|
return "0".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 进行Base36编码
|
||||||
|
let mut result = Vec::new();
|
||||||
|
let base = 36u8;
|
||||||
|
|
||||||
|
// 重复除以36,获取余数作为字符
|
||||||
|
while !num.is_empty() && !(num.len() == 1 && num[0] == 0) {
|
||||||
|
let mut remainder: u16 = 0;
|
||||||
|
let mut new_num = Vec::new();
|
||||||
|
let mut first = true;
|
||||||
|
|
||||||
|
for &byte in &num {
|
||||||
|
remainder = remainder * 256 + byte as u16;
|
||||||
|
let quotient = remainder / base as u16;
|
||||||
|
remainder %= base as u16;
|
||||||
|
|
||||||
|
if quotient > 0 || !first {
|
||||||
|
new_num.push(quotient as u8);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果new_num为空,说明这是最后一次除法
|
||||||
|
if !new_num.is_empty() || num.len() > 1 {
|
||||||
|
num = new_num;
|
||||||
|
} else {
|
||||||
|
num.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(BASE36_CHARS[remainder as usize]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反转字符串,因为我们是从低位开始计算的
|
||||||
|
result.reverse();
|
||||||
|
String::from_utf8(result).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 将Base36字符串(可为任意大小写)解码为字节数组
|
||||||
|
pub fn decode(src: &str) -> Result<Vec<u8>, Base36Error> {
|
||||||
|
if src.is_empty() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除padding字符并转换为大写
|
||||||
|
let cleaned: String = src.chars().filter(|&c| c != '=').collect();
|
||||||
|
let cleaned = cleaned.to_uppercase();
|
||||||
|
|
||||||
|
// 使用Vec<u8>模拟大整数进行解码
|
||||||
|
let mut num = Vec::new();
|
||||||
|
let base = 36u16;
|
||||||
|
|
||||||
|
for c in cleaned.chars() {
|
||||||
|
let digit = match c {
|
||||||
|
'0'..='9' => c as u16 - '0' as u16,
|
||||||
|
'A'..='Z' => c as u16 - 'A' as u16 + 10,
|
||||||
|
_ => return Err(Base36Error::InvalidCharacter(c)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// num = num * 36 + digit
|
||||||
|
let mut carry = digit as u32;
|
||||||
|
for byte in num.iter_mut() {
|
||||||
|
let product = *byte as u32 * base as u32 + carry;
|
||||||
|
*byte = (product % 256) as u8;
|
||||||
|
carry = product / 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
while carry > 0 {
|
||||||
|
num.push((carry % 256) as u8);
|
||||||
|
carry /= 256;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反转字节数组,因为我们的计算是从低位开始的
|
||||||
|
num.reverse();
|
||||||
|
|
||||||
|
// 如果结果为空,返回单个零字节
|
||||||
|
if num.is_empty() {
|
||||||
|
num.push(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(num)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encode的别名,用于保持与标准库一致的命名
|
||||||
|
pub fn encode_to_string(src: &[u8]) -> String {
|
||||||
|
encode(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode的别名,用于保持与标准库一致的命名
|
||||||
|
pub fn decode_string(src: &str) -> Result<Vec<u8>, Base36Error> {
|
||||||
|
decode(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 将int64转换为Base36字符串
|
||||||
|
pub fn encode_int64(num: i64) -> String {
|
||||||
|
let bytes = num.to_be_bytes().to_vec();
|
||||||
|
encode(&bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 将Base36字符串解码为int64
|
||||||
|
pub fn decode_to_int64(src: &str) -> Result<i64, Base36Error> {
|
||||||
|
let bytes = decode(src)?;
|
||||||
|
if bytes.len() > 8 {
|
||||||
|
return Err(Base36Error::NumberTooLarge);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut array = [0u8; 8];
|
||||||
|
let start = 8 - bytes.len();
|
||||||
|
for (i, &byte) in bytes.iter().enumerate() {
|
||||||
|
array[start + i] = byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(i64::from_be_bytes(array))
|
||||||
|
}
|
@@ -1,6 +1,8 @@
|
|||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub mod base36;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum HexSerializeError {
|
pub enum HexSerializeError {
|
||||||
#[error("Invalid hex char: {0}")]
|
#[error("Invalid hex char: {0}")]
|
||||||
|
108
src/time/mod.rs
108
src/time/mod.rs
@@ -1,46 +1,36 @@
|
|||||||
use chrono::{DateTime, Datelike, Duration, FixedOffset, NaiveDate, NaiveDateTime, Utc};
|
use std::i64;
|
||||||
|
|
||||||
|
use time::{macros::offset, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset};
|
||||||
|
|
||||||
/// 获取一个类型为`chrono::DateTime<chrono::FixedOffset>`类型的当前日期时间的实例。时间时区将自动被设置为东八区。
|
/// 获取一个类型为`chrono::DateTime<chrono::FixedOffset>`类型的当前日期时间的实例。时间时区将自动被设置为东八区。
|
||||||
pub fn now_asia_shanghai() -> DateTime<FixedOffset> {
|
pub fn now_asia_shanghai() -> OffsetDateTime {
|
||||||
let utc_now = Utc::now();
|
let utc_now = OffsetDateTime::now_utc();
|
||||||
shift_to_asia_shanghai(utc_now)
|
shift_to_asia_shanghai(utc_now)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 将一个类型为`chrono::DateTime<chrono::Utc>`类型的日期时间转换到指定时区的时间实例。
|
/// 将一个类型为`chrono::DateTime<chrono::Utc>`类型的日期时间转换到指定时区的时间实例。
|
||||||
pub fn shift_tz(datetime: DateTime<Utc>, zone: i64) -> DateTime<FixedOffset> {
|
pub fn shift_tz(datetime: OffsetDateTime, zone: i8) -> OffsetDateTime {
|
||||||
if zone.is_positive() {
|
datetime.to_offset(UtcOffset::from_hms(zone.clamp(-25, 25), 0, 0).unwrap())
|
||||||
datetime.with_timezone(
|
|
||||||
&FixedOffset::east_opt(Duration::hours(zone.abs()).num_seconds() as i32).unwrap(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
datetime.with_timezone(
|
|
||||||
&FixedOffset::west_opt(Duration::hours(zone.abs()).num_seconds() as i32).unwrap(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 将一个类型为`chrono::DateTime<chrono::Utc>`类型的日期时间转换到东八区的时间实例。
|
/// 将一个类型为`chrono::DateTime<chrono::Utc>`类型的日期时间转换到东八区的时间实例。
|
||||||
pub fn shift_to_asia_shanghai(datetime: DateTime<Utc>) -> DateTime<FixedOffset> {
|
pub fn shift_to_asia_shanghai(datetime: OffsetDateTime) -> OffsetDateTime {
|
||||||
shift_tz(datetime, 8)
|
datetime.to_offset(offset!(+8))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 直接给一个原生日期时间附加东八区的时区信息。
|
/// 直接给一个原生日期时间附加东八区的时区信息。
|
||||||
pub fn attach_asia_shanghai(datetime: NaiveDateTime) -> DateTime<FixedOffset> {
|
pub fn attach_asia_shanghai(datetime: PrimitiveDateTime) -> OffsetDateTime {
|
||||||
DateTime::<FixedOffset>::from_local(
|
let utc_date_time = datetime.as_utc();
|
||||||
datetime,
|
let offseted_date_time = OffsetDateTime::from(utc_date_time);
|
||||||
FixedOffset::east_opt(Duration::hours(8).num_seconds() as i32).unwrap(),
|
offseted_date_time.replace_offset(offset!(+8))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 从一个64位时间戳生成东八区的时间实例。这个函数主要用于处理使用`timestamp`方法直接返回的时间戳。
|
/// 从一个64位时间戳生成东八区的时间实例。这个函数主要用于处理使用`timestamp`方法直接返回的时间戳。
|
||||||
///
|
///
|
||||||
/// - `timestamp`:64位时间戳。
|
/// - `timestamp`:64位时间戳。
|
||||||
pub fn from_utc_timestamp(timestamp: i64) -> DateTime<FixedOffset> {
|
pub fn from_utc_timestamp(timestamp: i64) -> OffsetDateTime {
|
||||||
let request_time = NaiveDateTime::from_timestamp_micros(timestamp).unwrap();
|
let request_time = OffsetDateTime::from_unix_timestamp(timestamp.clamp(0, i64::MAX)).unwrap();
|
||||||
DateTime::<FixedOffset>::from_utc(
|
request_time.to_offset(offset!(+8))
|
||||||
request_time,
|
|
||||||
FixedOffset::east_opt(Duration::hours(8).num_seconds() as i32).unwrap(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 根据指定的日期生成一个时间对象,如果给定的日期不合法将返回空白内容。
|
/// 根据指定的日期生成一个时间对象,如果给定的日期不合法将返回空白内容。
|
||||||
@@ -48,8 +38,8 @@ pub fn from_utc_timestamp(timestamp: i64) -> DateTime<FixedOffset> {
|
|||||||
/// - `year`:日期的年份。
|
/// - `year`:日期的年份。
|
||||||
/// - `month`:日期的月份,从`1`开始。
|
/// - `month`:日期的月份,从`1`开始。
|
||||||
/// - `day`:日期的天数。
|
/// - `day`:日期的天数。
|
||||||
pub fn date(year: i32, month: u32, day: u32) -> Option<NaiveDate> {
|
pub fn date(year: i32, month: u8, day: u8) -> Option<Date> {
|
||||||
NaiveDate::from_ymd_opt(year, month, day)
|
Date::from_calendar_date(year, Month::try_from(month.clamp(1, 12)).unwrap(), day).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 根据指定日期生成一个指定日期最开始时间的时间,精度为毫秒。
|
/// 根据指定日期生成一个指定日期最开始时间的时间,精度为毫秒。
|
||||||
@@ -57,29 +47,26 @@ pub fn date(year: i32, month: u32, day: u32) -> Option<NaiveDate> {
|
|||||||
/// - `year`:指定日期的年份。
|
/// - `year`:指定日期的年份。
|
||||||
/// - `month`:指定日期的月份,从`1`开始。
|
/// - `month`:指定日期的月份,从`1`开始。
|
||||||
/// - `day`:指定日期的天数。
|
/// - `day`:指定日期的天数。
|
||||||
pub fn date_beginning(year: i32, month: u32, day: u32) -> Option<DateTime<FixedOffset>> {
|
pub fn date_beginning(year: i32, month: u8, day: u8) -> OffsetDateTime {
|
||||||
let timezone = FixedOffset::east_opt(Duration::hours(8).num_seconds() as i32).unwrap();
|
OffsetDateTime::new_in_offset(
|
||||||
NaiveDate::from_ymd_opt(year, month, day)
|
Date::from_calendar_date(year, Month::try_from(month.clamp(1, 12)).unwrap(), day).unwrap(),
|
||||||
.map(|d| d.and_hms_micro_opt(0, 0, 0, 0).unwrap())
|
Time::MIDNIGHT,
|
||||||
.map(|dt| DateTime::<FixedOffset>::from_local(dt, timezone))
|
offset!(+8),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 根据给定的日期,返回其当天最开始的时间,精度为毫秒。
|
/// 根据给定的日期,返回其当天最开始的时间,精度为毫秒。
|
||||||
///
|
///
|
||||||
/// - `date`:给定的原始日期,注意:原始日期将被消耗掉。
|
/// - `date`:给定的原始日期,注意:原始日期将被消耗掉。
|
||||||
pub fn begin_of_date(date: NaiveDate) -> Option<DateTime<FixedOffset>> {
|
pub fn begin_of_date(date: Date) -> OffsetDateTime {
|
||||||
let timezone = FixedOffset::east_opt(Duration::hours(8).num_seconds() as i32).unwrap();
|
OffsetDateTime::new_in_offset(date, Time::MIDNIGHT, offset!(+8))
|
||||||
date.and_hms_micro_opt(0, 0, 0, 0)
|
|
||||||
.map(|dt| DateTime::<FixedOffset>::from_local(dt, timezone))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 根据给定的日期,返回其当天即将结束的时间,精度为毫秒。
|
/// 根据给定的日期,返回其当天即将结束的时间,精度为毫秒。
|
||||||
///
|
///
|
||||||
/// - `date`:给定的原始日期,注意:原始日期将被消耗掉。
|
/// - `date`:给定的原始日期,注意:原始日期将被消耗掉。
|
||||||
pub fn end_of_date(date: NaiveDate) -> Option<DateTime<FixedOffset>> {
|
pub fn end_of_date(date: Date) -> OffsetDateTime {
|
||||||
let timezone = FixedOffset::east_opt(Duration::hours(8).num_seconds() as i32).unwrap();
|
OffsetDateTime::new_in_offset(date, Time::MAX, offset!(+8))
|
||||||
date.and_hms_micro_opt(23, 59, 59, 999_999)
|
|
||||||
.map(|dt| DateTime::<FixedOffset>::from_local(dt, timezone))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 根据指定日期生成一个指定日期结束时间的时间,精度为毫秒。
|
/// 根据指定日期生成一个指定日期结束时间的时间,精度为毫秒。
|
||||||
@@ -87,20 +74,19 @@ pub fn end_of_date(date: NaiveDate) -> Option<DateTime<FixedOffset>> {
|
|||||||
/// - `year`:指定日期的年份。
|
/// - `year`:指定日期的年份。
|
||||||
/// - `month`:指定日期的月份,从`1`开始。
|
/// - `month`:指定日期的月份,从`1`开始。
|
||||||
/// - `day`:指定日期的天数。
|
/// - `day`:指定日期的天数。
|
||||||
pub fn date_ending(year: i32, month: u32, day: u32) -> Option<DateTime<FixedOffset>> {
|
pub fn date_ending(year: i32, month: u8, day: u8) -> Option<OffsetDateTime> {
|
||||||
let timezone = FixedOffset::east_opt(Duration::hours(8).num_seconds() as i32).unwrap();
|
Date::from_calendar_date(year, Month::try_from(month.clamp(1, 12)).unwrap(), day)
|
||||||
NaiveDate::from_ymd_opt(year, month, day)
|
.ok()
|
||||||
.map(|d| d.and_hms_micro_opt(23, 59, 59, 999_999).unwrap())
|
.map(end_of_date)
|
||||||
.map(|dt| DateTime::<FixedOffset>::from_local(dt, timezone))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 返回两个日期之间的月份差值。
|
/// 返回两个日期之间的月份差值。
|
||||||
///
|
///
|
||||||
/// - `control`:基准月份。
|
/// - `control`:基准月份。
|
||||||
/// - `test`:测试月份。
|
/// - `test`:测试月份。
|
||||||
pub fn difference_month(control: NaiveDate, test: NaiveDate) -> i32 {
|
pub fn difference_month(control: Date, test: Date) -> i32 {
|
||||||
let difference_year = test.year() - control.year();
|
let difference_year = test.year() - control.year();
|
||||||
let difference_month = (test.month() - control.month()) as i32;
|
let difference_month = u8::from(test.month()) as i32 - u8::from(control.month()) as i32;
|
||||||
difference_year * 12 + difference_month
|
difference_year * 12 + difference_month
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,40 +94,38 @@ pub fn difference_month(control: NaiveDate, test: NaiveDate) -> i32 {
|
|||||||
///
|
///
|
||||||
/// - `control`:基准月份。
|
/// - `control`:基准月份。
|
||||||
/// - `test`:待测试的指定月份。
|
/// - `test`:待测试的指定月份。
|
||||||
pub fn is_previous_month(control: NaiveDate, test: NaiveDate) -> bool {
|
pub fn is_previous_month(control: Date, test: Date) -> bool {
|
||||||
difference_month(control, test) == 1
|
control.month().previous() == test.month()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 测试指定月份是否是基准月份的下一个月份。
|
/// 测试指定月份是否是基准月份的下一个月份。
|
||||||
///
|
///
|
||||||
/// - `control`:基准月份。
|
/// - `control`:基准月份。
|
||||||
/// - `test`:待测试的指定月份。
|
/// - `test`:待测试的指定月份。
|
||||||
pub fn is_next_month(control: NaiveDate, test: NaiveDate) -> bool {
|
pub fn is_next_month(control: Date, test: Date) -> bool {
|
||||||
difference_month(control, test) == -1
|
control.month().next() == test.month()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 生成符合Postgresql中日期类型最小值的日期。
|
/// 生成符合Postgresql中日期类型最小值的日期。
|
||||||
pub fn min_date() -> NaiveDate {
|
pub fn min_date() -> Date {
|
||||||
NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()
|
Date::from_calendar_date(1970, Month::January, 1).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 生成符合Postgresql中日期类型最小值的日期时间。
|
/// 生成符合Postgresql中日期类型最小值的日期时间。
|
||||||
pub fn min_datetime() -> DateTime<FixedOffset> {
|
pub fn min_datetime() -> OffsetDateTime {
|
||||||
NaiveDate::from_ymd_opt(1970, 1, 1)
|
Date::from_calendar_date(1970, Month::January, 1)
|
||||||
.map(begin_of_date)
|
.map(begin_of_date)
|
||||||
.flatten()
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 生成符合Postgresql中日期类型最大值的日期。
|
/// 生成符合Postgresql中日期类型最大值的日期。
|
||||||
pub fn max_date() -> NaiveDate {
|
pub fn max_date() -> Date {
|
||||||
NaiveDate::from_ymd_opt(2099, 12, 31).unwrap()
|
Date::from_calendar_date(2099, Month::December, 31).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 生成符合Postgresql中日期类型最大值的日期时间。
|
/// 生成符合Postgresql中日期类型最大值的日期时间。
|
||||||
pub fn max_datetime() -> DateTime<FixedOffset> {
|
pub fn max_datetime() -> OffsetDateTime {
|
||||||
NaiveDate::from_ymd_opt(2099, 12, 31)
|
Date::from_calendar_date(2099, Month::December, 31)
|
||||||
.map(end_of_date)
|
.map(end_of_date)
|
||||||
.flatten()
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user