Compare commits

...

5 Commits

Author SHA1 Message Date
徐涛
21892e977f feat(time): 替换 chrono 为 time 库以优化时间处理
- 移除 chrono 依赖,引入 time 库并启用相关特性
- 重构时间工具模块,使用 OffsetDateTime 替代 DateTime
- 更新时间生成、转换和解析逻辑,提升代码一致性与可维护性
- 调整日期构造函数参数类型,增强类型安全
- 修正时间戳生成方式,确保东八区时间正确表示
2025-10-09 15:04:44 +08:00
徐涛
cc05fc9a40 docs(README): 更新 Base36 算法完成状态
将 README.md 中的 Base36 算法状态从未完成更新为已完成
2025-10-09 13:55:35 +08:00
徐涛
7b1834479f feat(serialize): 添加Base36编解码模块
新增Base36编码和解码功能,支持字节数据与Base36字符串之间的相互转换,
并提供对i64整数的Base36编解码支持。同时更新了相关依赖版本以提升安全性
和性能。

- 新增 `base36` 模块,实现完整的Base36编解码逻辑
- 支持去除填充字符 `=` 后的容错处理
- 提供 `encode`, `decode`, `encode_int64`, `decode_to_int64` 等公共接口
- 更新 Cargo.toml 中多项依赖至最新稳定版本
2025-10-09 13:38:20 +08:00
徐涛
ea194a1fd1 docs(readme): 更新已实现的校验和算法列表
将 BLAKE2b 和 BLAKE3 校验和算法标记为已完成状态,
反映这些算法的便捷封装已在工具箱中实现。
2025-10-09 13:16:52 +08:00
徐涛
5b4dff402c feat(hash): 添加 Blake3 哈希算法支持
新增 blake3 模块,提供多种长度的 Blake3 哈希计算功能,
包括 224、256、384 和 512 位输出格式,支持字节数组和文件输入,
并可选择返回字节数组或十六进制字符串。同时在 hash 模块中导出该功能。
2025-10-09 13:14:43 +08:00
8 changed files with 335 additions and 89 deletions

View File

@@ -9,23 +9,31 @@ crate-type = ["dylib", "rlib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
aes = "0.8.3"
base64 = "0.21.2"
aes = "0.8.4"
base64 = "0.22.1"
blake2b_simd = "1.0.3"
blockhash = "0.5.0"
blake3 = { version = "1.8.2", features = ["serde", "digest"] }
blockhash = "1.0.0"
cbc = { version = "0.1.2", features = ["std"] }
chrono = "0.4.26"
cipher = "0.4.4"
des = "0.8.1"
hex = "0.4.3"
hmac-sha256 = "1.1.7"
hmac-sha512 = "1.1.5"
image = "0.24.6"
md-5 = "0.10.5"
once_cell = "1.18.0"
hmac-sha256 = "1.1.12"
hmac-sha512 = "1.1.7"
image = "0.25.8"
md-5 = "0.10.6"
rand = "0.8.5"
rsa = { version = "0.9.2", features = ["sha2"] }
sha1 = "0.10.5"
sha2 = "0.10.7"
thiserror = "1.0.40"
uuid = { version = "1.4.0", features = ["v4", "fast-rng"] }
sha1 = "0.10.6"
sha2 = "0.10.9"
thiserror = "2.0.17"
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"] }

View File

@@ -25,8 +25,8 @@ Rust 中可以使用的常用辅助功能工具箱。主要配备以下功能:
- [x] Sha1 散列算法(便捷封装)
- [x] MD5 散列算法(便捷封装)
- [x] 图像感知散列算法(便捷封装)
- [ ] BLAKE2b 校验和算法(便捷封装)
- [ ] BLAKE3 校验和算法(便捷封装)
- [x] BLAKE2b 校验和算法(便捷封装)
- [x] BLAKE3 校验和算法(便捷封装)
- 唯一序列号生成器
- [x] 冰雹 ID 生成器(短主机精简日期版雪花 ID)
- [x] UUIDv4 生成器
@@ -40,7 +40,7 @@ Rust 中可以使用的常用辅助功能工具箱。主要配备以下功能:
- [x] 随机验证码生成算法
- 序列化算法
- [x] Base64 算法
- [ ] Base36 算法
- [x] Base36 算法
- [x] Hex 直转
- 常用工具函数
- [x] 日期时间函数

109
src/hash/blake3/mod.rs Normal file
View 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))
}

View File

@@ -95,3 +95,4 @@ pub mod image_hash {
}
pub mod blake2b;
pub mod blake3;

View File

@@ -1,22 +1,20 @@
use core::time;
use std::sync::{Arc, Mutex};
use std::sync::{Arc, LazyLock, Mutex, OnceLock};
use chrono::NaiveDateTime;
use once_cell::sync::{Lazy, OnceCell};
use ::time::{macros::datetime, OffsetDateTime};
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)
.map(|d| d.and_hms_opt(22, 22, 22))
.flatten()
.map(|dt| crate::time::attach_asia_shanghai(dt))
.map(|dt| dt.timestamp())
.unwrap_or_else(|| NaiveDateTime::MIN.timestamp())
.map(|d| d.with_hms_nano(22, 22, 22, 222_222_222).unwrap())
.map(crate::time::attach_asia_shanghai)
.map(OffsetDateTime::unix_timestamp)
.unwrap_or_else(|| datetime!(1970-01-01 0:00 +8).unix_timestamp())
});
type TimestampValidator = fn(i64) -> bool;
type TimestampGenerator = fn() -> i64;
static INSTANCE: OnceCell<HailSerialCodeAlgorithm> = OnceCell::new();
static INSTANCE: OnceLock<HailSerialCodeAlgorithm> = OnceLock::new();
#[derive(Debug, Error)]
pub enum HailSerialCodeAlgorithmError {
@@ -65,7 +63,7 @@ impl HailSerialCodeAlgorithm {
/// 生成一个自计时起点以来的时间戳。
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
}

144
src/serialize/base36.rs Normal file
View 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))
}

View File

@@ -1,6 +1,8 @@
use base64::Engine;
use thiserror::Error;
pub mod base36;
#[derive(Debug, Error)]
pub enum HexSerializeError {
#[error("Invalid hex char: {0}")]

View File

@@ -1,46 +1,36 @@
use chrono::{DateTime, Datelike, Duration, FixedOffset, NaiveDate, NaiveDateTime, TimeZone, Utc};
use std::i64;
use time::{macros::offset, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset};
/// 获取一个类型为`chrono::DateTime<chrono::FixedOffset>`类型的当前日期时间的实例。时间时区将自动被设置为东八区。
pub fn now_asia_shanghai() -> DateTime<FixedOffset> {
let utc_now = Utc::now();
pub fn now_asia_shanghai() -> OffsetDateTime {
let utc_now = OffsetDateTime::now_utc();
shift_to_asia_shanghai(utc_now)
}
/// 将一个类型为`chrono::DateTime<chrono::Utc>`类型的日期时间转换到指定时区的时间实例。
pub fn shift_tz<T: TimeZone>(datetime: DateTime<T>, zone: i64) -> DateTime<FixedOffset> {
if zone.is_positive() {
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(),
)
}
pub fn shift_tz(datetime: OffsetDateTime, zone: i8) -> OffsetDateTime {
datetime.to_offset(UtcOffset::from_hms(zone.clamp(-25, 25), 0, 0).unwrap())
}
/// 将一个类型为`chrono::DateTime<chrono::Utc>`类型的日期时间转换到东八区的时间实例。
pub fn shift_to_asia_shanghai<T: TimeZone>(datetime: DateTime<T>) -> DateTime<FixedOffset> {
shift_tz(datetime, 8)
pub fn shift_to_asia_shanghai(datetime: OffsetDateTime) -> OffsetDateTime {
datetime.to_offset(offset!(+8))
}
/// 直接给一个原生日期时间附加东八区的时区信息。
pub fn attach_asia_shanghai(datetime: NaiveDateTime) -> DateTime<FixedOffset> {
DateTime::<FixedOffset>::from_local(
datetime,
FixedOffset::east_opt(Duration::hours(8).num_seconds() as i32).unwrap(),
)
pub fn attach_asia_shanghai(datetime: PrimitiveDateTime) -> OffsetDateTime {
let utc_date_time = datetime.as_utc();
let offseted_date_time = OffsetDateTime::from(utc_date_time);
offseted_date_time.replace_offset(offset!(+8))
}
/// 从一个64位时间戳生成东八区的时间实例。这个函数主要用于处理使用`timestamp`方法直接返回的时间戳。
///
/// - `timestamp`64位时间戳。
pub fn from_utc_timestamp(timestamp: i64) -> DateTime<FixedOffset> {
let request_time = NaiveDateTime::from_timestamp_micros(timestamp).unwrap();
DateTime::<FixedOffset>::from_utc(
request_time,
FixedOffset::east_opt(Duration::hours(8).num_seconds() as i32).unwrap(),
)
pub fn from_utc_timestamp(timestamp: i64) -> OffsetDateTime {
let request_time = OffsetDateTime::from_unix_timestamp(timestamp.clamp(0, i64::MAX)).unwrap();
request_time.to_offset(offset!(+8))
}
/// 根据指定的日期生成一个时间对象,如果给定的日期不合法将返回空白内容。
@@ -48,8 +38,8 @@ pub fn from_utc_timestamp(timestamp: i64) -> DateTime<FixedOffset> {
/// - `year`:日期的年份。
/// - `month`:日期的月份,从`1`开始。
/// - `day`:日期的天数。
pub fn date(year: i32, month: u32, day: u32) -> Option<NaiveDate> {
NaiveDate::from_ymd_opt(year, month, day)
pub fn date(year: i32, month: u8, day: u8) -> Option<Date> {
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`:指定日期的年份。
/// - `month`:指定日期的月份,从`1`开始。
/// - `day`:指定日期的天数。
pub fn date_beginning(year: i32, month: u32, day: u32) -> Option<DateTime<FixedOffset>> {
let timezone = FixedOffset::east_opt(Duration::hours(8).num_seconds() as i32).unwrap();
NaiveDate::from_ymd_opt(year, month, day)
.map(|d| d.and_hms_micro_opt(0, 0, 0, 0).unwrap())
.map(|dt| DateTime::<FixedOffset>::from_local(dt, timezone))
pub fn date_beginning(year: i32, month: u8, day: u8) -> OffsetDateTime {
OffsetDateTime::new_in_offset(
Date::from_calendar_date(year, Month::try_from(month.clamp(1, 12)).unwrap(), day).unwrap(),
Time::MIDNIGHT,
offset!(+8),
)
}
/// 根据给定的日期,返回其当天最开始的时间,精度为毫秒。
///
/// - `date`:给定的原始日期,注意:原始日期将被消耗掉。
pub fn begin_of_date(date: NaiveDate) -> Option<DateTime<FixedOffset>> {
let timezone = FixedOffset::east_opt(Duration::hours(8).num_seconds() as i32).unwrap();
date.and_hms_micro_opt(0, 0, 0, 0)
.map(|dt| DateTime::<FixedOffset>::from_local(dt, timezone))
pub fn begin_of_date(date: Date) -> OffsetDateTime {
OffsetDateTime::new_in_offset(date, Time::MIDNIGHT, offset!(+8))
}
/// 根据给定的日期,返回其当天即将结束的时间,精度为毫秒。
///
/// - `date`:给定的原始日期,注意:原始日期将被消耗掉。
pub fn end_of_date(date: NaiveDate) -> Option<DateTime<FixedOffset>> {
let timezone = FixedOffset::east_opt(Duration::hours(8).num_seconds() as i32).unwrap();
date.and_hms_micro_opt(23, 59, 59, 999_999)
.map(|dt| DateTime::<FixedOffset>::from_local(dt, timezone))
pub fn end_of_date(date: Date) -> OffsetDateTime {
OffsetDateTime::new_in_offset(date, Time::MAX, offset!(+8))
}
/// 根据指定日期生成一个指定日期结束时间的时间,精度为毫秒。
@@ -87,20 +74,19 @@ pub fn end_of_date(date: NaiveDate) -> Option<DateTime<FixedOffset>> {
/// - `year`:指定日期的年份。
/// - `month`:指定日期的月份,从`1`开始。
/// - `day`:指定日期的天数。
pub fn date_ending(year: i32, month: u32, day: u32) -> Option<DateTime<FixedOffset>> {
let timezone = FixedOffset::east_opt(Duration::hours(8).num_seconds() as i32).unwrap();
NaiveDate::from_ymd_opt(year, month, day)
.map(|d| d.and_hms_micro_opt(23, 59, 59, 999_999).unwrap())
.map(|dt| DateTime::<FixedOffset>::from_local(dt, timezone))
pub fn date_ending(year: i32, month: u8, day: u8) -> Option<OffsetDateTime> {
Date::from_calendar_date(year, Month::try_from(month.clamp(1, 12)).unwrap(), day)
.ok()
.map(end_of_date)
}
/// 返回两个日期之间的月份差值。
///
/// - `control`:基准月份。
/// - `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_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
}
@@ -108,40 +94,38 @@ pub fn difference_month(control: NaiveDate, test: NaiveDate) -> i32 {
///
/// - `control`:基准月份。
/// - `test`:待测试的指定月份。
pub fn is_previous_month(control: NaiveDate, test: NaiveDate) -> bool {
difference_month(control, test) == 1
pub fn is_previous_month(control: Date, test: Date) -> bool {
control.month().previous() == test.month()
}
/// 测试指定月份是否是基准月份的下一个月份。
///
/// - `control`:基准月份。
/// - `test`:待测试的指定月份。
pub fn is_next_month(control: NaiveDate, test: NaiveDate) -> bool {
difference_month(control, test) == -1
pub fn is_next_month(control: Date, test: Date) -> bool {
control.month().next() == test.month()
}
/// 生成符合Postgresql中日期类型最小值的日期。
pub fn min_date() -> NaiveDate {
NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()
pub fn min_date() -> Date {
Date::from_calendar_date(1970, Month::January, 1).unwrap()
}
/// 生成符合Postgresql中日期类型最小值的日期时间。
pub fn min_datetime() -> DateTime<FixedOffset> {
NaiveDate::from_ymd_opt(1970, 1, 1)
pub fn min_datetime() -> OffsetDateTime {
Date::from_calendar_date(1970, Month::January, 1)
.map(begin_of_date)
.flatten()
.unwrap()
}
/// 生成符合Postgresql中日期类型最大值的日期。
pub fn max_date() -> NaiveDate {
NaiveDate::from_ymd_opt(2099, 12, 31).unwrap()
pub fn max_date() -> Date {
Date::from_calendar_date(2099, Month::December, 31).unwrap()
}
/// 生成符合Postgresql中日期类型最大值的日期时间。
pub fn max_datetime() -> DateTime<FixedOffset> {
NaiveDate::from_ymd_opt(2099, 12, 31)
pub fn max_datetime() -> OffsetDateTime {
Date::from_calendar_date(2099, Month::December, 31)
.map(end_of_date)
.flatten()
.unwrap()
}