From cadd336dee9c553139aebbe8d25056b8a9083c24 Mon Sep 17 00:00:00 2001 From: ayou <550244300@qq.com> Date: Tue, 23 Sep 2025 00:22:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(ids):=20=E5=AE=9E=E7=8E=B0=E5=9F=BA?= =?UTF-8?q?=E4=BA=8ESnowflake=E7=9A=84=E5=88=86=E5=B8=83=E5=BC=8FID?= =?UTF-8?q?=E7=94=9F=E6=88=90=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增rs-snowflake依赖并实现分布式ID生成工具 在utils模块中添加ids子模块,提供业务ID生成与解析功能 替换原有UUID生成方式为分布式ID生成器 --- backend/Cargo.lock | 7 ++ backend/Cargo.toml | 2 + backend/src/main.rs | 3 + backend/src/services/flow_run_log_service.rs | 2 +- backend/src/services/flow_service.rs | 2 +- backend/src/services/log_service.rs | 2 +- backend/src/utils/ids.rs | 83 ++++++++++++++++++++ backend/src/utils/mod.rs | 5 +- 8 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 backend/src/utils/ids.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index b36c091..8d63ecb 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -2552,6 +2552,12 @@ dependencies = [ "cc", ] +[[package]] +name = "rs-snowflake" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e60ef3b82994702bbe4e134d98aadca4b49ed04440148985678d415c68127666" + [[package]] name = "rsa" version = "0.9.8" @@ -3911,6 +3917,7 @@ dependencies = [ "reqwest", "rhai", "rquickjs", + "rs-snowflake", "sea-orm", "serde", "serde_json", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 93e89c9..d05b756 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -43,6 +43,8 @@ percent-encoding = "2.3" rquickjs = "0.9.0" # 新增: 用于将 mpsc::Receiver 封装为 Stream(SSE) tokio-stream = "0.1.17" +# 新增: 分布式ID生成(Snowflake) +rs-snowflake = "0.6.0" [dependencies.migration] path = "migration" diff --git a/backend/src/main.rs b/backend/src/main.rs index 5a70d18..32ef5b6 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -64,6 +64,9 @@ async fn main() -> anyhow::Result<()> { let redis_pool = redis::init_redis().await?; redis::set_redis_pool(redis_pool)?; + // 初始化分布式ID生成器(读取 ID_MACHINE_ID / ID_NODE_ID) + crate::utils::init_from_env(); + // run migrations migration::Migrator::up(&db, None).await.expect("migration up"); diff --git a/backend/src/services/flow_run_log_service.rs b/backend/src/services/flow_run_log_service.rs index 47f2fd3..d7ebeba 100644 --- a/backend/src/services/flow_run_log_service.rs +++ b/backend/src/services/flow_run_log_service.rs @@ -44,7 +44,7 @@ pub struct CreateRunLogInput { pub async fn create(db: &Db, input: CreateRunLogInput) -> anyhow::Result { let am = flow_run_log::ActiveModel { - id: Default::default(), + id: Set(crate::utils::generate_flow_run_log_id()), flow_id: Set(input.flow_id), flow_code: Set(input.flow_code), input: Set(input.input), diff --git a/backend/src/services/flow_service.rs b/backend/src/services/flow_service.rs index 630ff89..131f968 100644 --- a/backend/src/services/flow_service.rs +++ b/backend/src/services/flow_service.rs @@ -98,7 +98,7 @@ pub async fn create(db: &Db, req: FlowCreateReq) -> anyhow::Result { let _parsed: FlowDSL = serde_yaml::from_str(yaml).context("invalid flow yaml")?; info!(target: "udmin", "flow.create: yaml parsed ok"); } - let id = uuid::Uuid::new_v4().to_string(); + let id = crate::utils::generate_flow_id(); let name = req .name .clone() diff --git a/backend/src/services/log_service.rs b/backend/src/services/log_service.rs index 46b4e2f..ebbaea7 100644 --- a/backend/src/services/log_service.rs +++ b/backend/src/services/log_service.rs @@ -17,7 +17,7 @@ pub struct CreateLogInput { pub async fn create(db: &Db, input: CreateLogInput) -> anyhow::Result { let am = request_log::ActiveModel { - id: Default::default(), + id: Set(crate::utils::generate_request_log_id()), path: Set(input.path), method: Set(input.method), request_params: Set(input.request_params), diff --git a/backend/src/utils/ids.rs b/backend/src/utils/ids.rs new file mode 100644 index 0000000..dc603cc --- /dev/null +++ b/backend/src/utils/ids.rs @@ -0,0 +1,83 @@ +use std::sync::Mutex; +use once_cell::sync::Lazy; + +// 采用 rs-snowflake 提供的 SnowflakeIdGenerator +// machine_id 与 node_id 取值范围建议 < 32 +use snowflake::SnowflakeIdGenerator; + +// 全局生成器(按需初始化),以避免重复构造与时序问题 +static GENERATOR: Lazy> = Lazy::new(|| { + let (machine_id, node_id) = read_ids_from_env(); + Mutex::new(SnowflakeIdGenerator::new(machine_id, node_id)) +}); + +fn read_ids_from_env() -> (i32, i32) { + let machine_id = std::env::var("ID_MACHINE_ID").ok().and_then(|s| s.parse::().ok()).unwrap_or(1); + let node_id = std::env::var("ID_NODE_ID").ok().and_then(|s| s.parse::().ok()).unwrap_or(1); + // 保护:按 0..32 范围裁剪,避免越界(生成器要求 machine_id/node_id < 32) + let clamp = |v: i32| if v < 0 { 0 } else if v > 31 { 31 } else { v }; + (clamp(machine_id), clamp(node_id)) +} + +/// 可选:在启动时主动初始化并打印日志,便于观测 +pub fn init_from_env() { + let (machine_id, node_id) = read_ids_from_env(); + // 触发 Lazy 初始化(不获取锁,避免非绑定锁 lint) + Lazy::force(&GENERATOR); + tracing::info!(target: "udmin", "snowflake init: machine_id={} node_id={}", machine_id, node_id); +} + +/// 业务ID生成器(按示例:主业务16位,子业务8位,Snowflake保留低39位 => 共63位,最高符号位为0) +pub struct BizIdConfig { + pub main_id: u16, // 16 bits + pub sub_id: u8, // 8 bits +} + +impl BizIdConfig { + pub const fn new(main_id: u16, sub_id: u8) -> Self { Self { main_id, sub_id } } +} + +/// 生成带业务前缀的分布式ID(返回 i64) +pub fn generate_biz_id(cfg: BizIdConfig) -> i64 { + let mut g = GENERATOR.lock().expect("gen snowflake id"); + let base_id = g.real_time_generate(); + // 只保留低 39 位 + let snowflake_bits = base_id & ((1i64 << 39) - 1); + let main_bits = (cfg.main_id as i64) << (39 + 8); + let sub_bits = (cfg.sub_id as i64) << 39; + main_bits | sub_bits | snowflake_bits +} + +/// 解析业务ID -> (main_id, sub_id, base_id) +pub fn parse_biz_id(id: i64) -> (u16, u8, i64) { + let main_id = ((id >> (39 + 8)) & 0xFFFF) as u16; + let sub_id = ((id >> 39) & 0xFF) as u8; + let base_id = id & ((1i64 << 39) - 1); + (main_id, sub_id, base_id) +} + +// --- 具体业务场景:Flow 使用的一组常量(可按需扩展/调整) --- +// 你可以把这些常量提到配置或用枚举维护各业务的 main/sub 编码 +const FLOW_MAIN_ID: u16 = 1; +const FLOW_SUB_ID: u8 = 1; + +/// 生成 Flow 的 ID,返回十进制字符串,便于与原先 string 类型主键兼容 +pub fn generate_flow_id() -> String { + let id = generate_biz_id(BizIdConfig::new(FLOW_MAIN_ID, FLOW_SUB_ID)); + id.to_string() +} + +// --- 日志类 ID 的业务位定义与生成 --- +const FLOW_RUN_LOG_MAIN_ID: u16 = 2; +const FLOW_RUN_LOG_SUB_ID: u8 = 1; + +pub fn generate_flow_run_log_id() -> i64 { + generate_biz_id(BizIdConfig::new(FLOW_RUN_LOG_MAIN_ID, FLOW_RUN_LOG_SUB_ID)) +} + +const REQUEST_LOG_MAIN_ID: u16 = 3; +const REQUEST_LOG_SUB_ID: u8 = 1; + +pub fn generate_request_log_id() -> i64 { + generate_biz_id(BizIdConfig::new(REQUEST_LOG_MAIN_ID, REQUEST_LOG_SUB_ID)) +} \ No newline at end of file diff --git a/backend/src/utils/mod.rs b/backend/src/utils/mod.rs index 5b4e5bc..a050448 100644 --- a/backend/src/utils/mod.rs +++ b/backend/src/utils/mod.rs @@ -1 +1,4 @@ -pub mod password; \ No newline at end of file +pub mod password; +pub mod ids; + +pub use ids::{init_from_env, generate_biz_id, parse_biz_id, generate_flow_id, BizIdConfig, generate_flow_run_log_id, generate_request_log_id}; \ No newline at end of file