use cache::Cache; use serde::Serialize; use serde::de::DeserializeOwned; use std::sync::Arc; #[derive(Clone)] pub struct ObjectCache { pub prefix: String, pub cache: Arc, pub expire_ttl: u64, } pub(crate) fn sanitize_key(k: &str) -> String { let mut out = String::with_capacity(k.len()); for b in k.bytes() { if b.is_ascii_alphanumeric() || b == b'-' || b == b'_' || b == b'.' { out.push(b as char); } else { out.push('_'); } } out } impl ObjectCache { pub async fn get(&self, k: &str) -> Option { let k = format!("{}.{}", self.prefix, sanitize_key(k)); serde_json::from_slice(self.cache.get(&k).await?.as_slice()).ok() } pub async fn set(&self, k: &str, v: V) -> Option<()> { let k = format!("{}.{}", self.prefix, sanitize_key(k)); let bytes = serde_json::to_vec(&v).ok()?; self.cache.set(&k, bytes, self.expire_ttl).await; Some(()) } pub async fn delete(&self, k: &str) -> Option<()> { let k = format!("{}.{}", self.prefix, sanitize_key(k)); self.cache.delete(&k).await; Some(()) } pub async fn object_pool_get(&self, k: &str) -> Option { let k = format!("object.pool.{}", sanitize_key(k)); serde_json::from_slice(self.cache.get(&k).await?.as_slice()).ok() } pub async fn object_pool_set(&self, k: &str, v: T) -> Option<()> { let k = format!("object.pool.{}", sanitize_key(k)); let bytes = serde_json::to_vec(&v).ok()?; self.cache.set(&k, bytes, self.expire_ttl).await; Some(()) } pub async fn object_pool_delete(&self, k: &str) -> Option<()> { let k = format!("object.pool.{}", sanitize_key(k)); self.cache.delete(&k).await; Some(()) } pub fn get_sync(&self, k: &str) -> Option { let cache = self.cache.clone(); let prefix = self.prefix.clone(); let key = format!("{}.{}", prefix, sanitize_key(k)); block_on_safe(move || { let cache = cache.clone(); let key = key.clone(); async move { let bytes = cache.get(&key).await?; serde_json::from_slice(bytes.as_slice()).ok() } }) } pub fn set_sync(&self, k: &str, v: V) { let cache = self.cache.clone(); let prefix = self.prefix.clone(); let key = format!("{}.{}", prefix, sanitize_key(k)); let bytes = match serde_json::to_vec(&v) { Ok(b) => b, Err(_) => return, }; let ttl = self.expire_ttl; block_on_safe(move || { let cache = cache.clone(); let key = key.clone(); async move { cache.set(&key, bytes, ttl).await; } }); } pub fn delete_sync(&self, k: &str) { let cache = self.cache.clone(); let prefix = self.prefix.clone(); let key = format!("{}.{}", prefix, sanitize_key(k)); block_on_safe(move || { let cache = cache.clone(); let key = key.clone(); async move { cache.delete(&key).await; } }); } pub fn object_pool_get_sync(&self, k: &str) -> Option { let cache = self.cache.clone(); let key = format!("object.pool.{}", sanitize_key(k)); block_on_safe(move || { let cache = cache.clone(); let key = key.clone(); async move { let bytes = cache.get(&key).await?; serde_json::from_slice(bytes.as_slice()).ok() } }) } pub fn object_pool_set_sync(&self, k: &str, v: T) { let cache = self.cache.clone(); let key = format!("object.pool.{}", sanitize_key(k)); let bytes = match serde_json::to_vec(&v) { Ok(b) => b, Err(_) => return, }; let ttl = self.expire_ttl; block_on_safe(move || { let cache = cache.clone(); let key = key.clone(); async move { cache.set(&key, bytes, ttl).await; } }); } pub fn set_sync_with_ttl(&self, k: &str, v: V, ttl: u64) { let cache = self.cache.clone(); let prefix = self.prefix.clone(); let key = format!("{}.{}", prefix, sanitize_key(k)); let bytes = match serde_json::to_vec(&v) { Ok(b) => b, Err(_) => return, }; block_on_safe(move || { let cache = cache.clone(); let key = key.clone(); async move { cache.set(&key, bytes, ttl).await; } }); } } fn block_on_safe(f: impl FnOnce() -> F) -> T where F: std::future::Future, { match tokio::runtime::Handle::try_current() { Ok(handle) => match handle.runtime_flavor() { tokio::runtime::RuntimeFlavor::MultiThread => { // 已在 multi-thread runtime 的异步上下文(如 GraphQL resolver)内: // 直接 handle.block_on 会 panic,改用 block_in_place 迁出 worker 线程执行 tokio::task::block_in_place(|| handle.block_on(f())) } _ => tokio::runtime::Builder::new_current_thread() .enable_all() .build() .expect("cache runtime") .block_on(f()), }, Err(_) => tokio::runtime::Builder::new_current_thread() .enable_all() .build() .expect("cache runtime") .block_on(f()), } }