perf(auth): JWT 权限缓存 RwLock 替换为 DashMap

USER_SCOPE_CACHE 从 LazyLock<RwLock<HashMap>> 改为 LazyLock<DashMap>,
消除读写锁竞争,提升高并发场景下的认证中间件吞吐量。
过期条目淘汰逻辑改用 DashMap::retain,无需手动获取 write lock。
This commit is contained in:
iven
2026-05-17 12:54:34 +08:00
parent c2c7f2d967
commit e8bbc36364
2 changed files with 20 additions and 18 deletions

View File

@@ -26,3 +26,4 @@ cbc.workspace = true
hex.workspace = true
base64 = "0.22"
redis.workspace = true
dashmap.workspace = true

View File

@@ -2,6 +2,7 @@ use axum::body::Body;
use axum::http::Request;
use axum::middleware::Next;
use axum::response::Response;
use dashmap::DashMap;
use erp_core::error::AppError;
use erp_core::request_info::REQUEST_INFO;
use erp_core::request_info::RequestInfo;
@@ -12,11 +13,11 @@ use crate::service::token_service::TokenService;
type DeptIds = Vec<uuid::Uuid>;
type DataScopes = std::collections::HashMap<String, DataScope>;
type ScopeCacheEntry = (DeptIds, DataScopes, std::time::Instant);
type ScopeCacheMap = std::collections::HashMap<uuid::Uuid, ScopeCacheEntry>;
/// 用户权限数据缓存user_id -> (department_ids, data_scopes, cached_at)
static USER_SCOPE_CACHE: std::sync::LazyLock<std::sync::RwLock<ScopeCacheMap>> =
std::sync::LazyLock::new(|| std::sync::RwLock::new(std::collections::HashMap::new()));
/// DashMap 分片并发,读写无锁竞争
static USER_SCOPE_CACHE: std::sync::LazyLock<DashMap<uuid::Uuid, ScopeCacheEntry>> =
std::sync::LazyLock::new(DashMap::new);
const SCOPE_CACHE_TTL: std::time::Duration = std::time::Duration::from_secs(60);
@@ -76,16 +77,17 @@ pub async fn jwt_auth_middleware_fn(
}
// 查询用户所属部门 ID 列表 + 权限数据范围(带 60 秒缓存)
let cached = {
let cache = USER_SCOPE_CACHE.read().unwrap();
cache.get(&claims.sub).and_then(|(depts, scopes, at)| {
if at.elapsed() < SCOPE_CACHE_TTL {
Some((depts.clone(), scopes.clone()))
} else {
None
}
})
};
let cached = USER_SCOPE_CACHE.get(&claims.sub).and_then(|entry| {
let (_, _, at) = entry.value();
if at.elapsed() < SCOPE_CACHE_TTL {
let (depts, scopes, _) = entry.value();
Some((depts.clone(), scopes.clone()))
} else {
drop(entry);
USER_SCOPE_CACHE.remove(&claims.sub);
None
}
});
let (department_ids, permission_data_scopes) = match cached {
Some(hit) => hit,
None => fetch_and_cache_scopes(claims.sub, claims.tid, &db).await,
@@ -207,15 +209,14 @@ async fn fetch_and_cache_scopes(
Some(conn) => fetch_permission_data_scopes(user_id, tenant_id, conn).await,
None => std::collections::HashMap::new(),
};
let mut cache = USER_SCOPE_CACHE.write().unwrap();
cache.insert(
USER_SCOPE_CACHE.insert(
user_id,
(depts.clone(), scopes.clone(), std::time::Instant::now()),
);
// 惰性淘汰过期条目,防止 HashMap 无限增长
if cache.len() > 500 {
// 惰性淘汰过期条目,防止 DashMap 无限增长
if USER_SCOPE_CACHE.len() > 500 {
let now = std::time::Instant::now();
cache.retain(|_, (_, _, at)| now.duration_since(*at) < SCOPE_CACHE_TTL);
USER_SCOPE_CACHE.retain(|_, (_, _, at)| now.duration_since(*at) < SCOPE_CACHE_TTL);
}
(depts, scopes)
}