open_lark/core/
token_manager.rs

1use log::warn;
2use serde::{Deserialize, Serialize};
3use std::{
4    sync::{
5        atomic::{AtomicU64, Ordering},
6        Arc,
7    },
8    time::{Duration, Instant},
9};
10use tokio::{
11    sync::{Mutex, RwLock},
12    time::interval,
13};
14
15use crate::core::{
16    api_resp::{ApiResponseTrait, RawResponse, ResponseFormat},
17    app_ticket_manager::AppTicketManager,
18    cache::QuickCache,
19    config::Config,
20    constants::{
21        AppType, APP_ACCESS_TOKEN_INTERNAL_URL_PATH, APP_ACCESS_TOKEN_KEY_PREFIX,
22        APP_ACCESS_TOKEN_URL_PATH, EXPIRY_DELTA, TENANT_ACCESS_TOKEN_INTERNAL_URL_PATH,
23        TENANT_ACCESS_TOKEN_URL_PATH,
24    },
25    error::LarkAPIError,
26    SDKResult,
27};
28
29/// Token预热配置
30#[derive(Debug, Clone)]
31pub struct PreheatingConfig {
32    /// 检查间隔(秒)
33    pub check_interval_seconds: u64,
34    /// 预热阈值(秒)- 当token剩余时间少于此值时开始预热
35    pub preheat_threshold_seconds: u64,
36    /// 是否启用tenant token预热
37    pub enable_tenant_preheating: bool,
38    /// 最大并发预热任务数
39    pub max_concurrent_preheat: usize,
40}
41
42impl Default for PreheatingConfig {
43    fn default() -> Self {
44        Self {
45            check_interval_seconds: 1800,   // 30分钟
46            preheat_threshold_seconds: 900, // 15分钟
47            enable_tenant_preheating: true,
48            max_concurrent_preheat: 3,
49        }
50    }
51}
52
53/// Token管理器性能监控指标
54#[derive(Debug, Default)]
55pub struct TokenMetrics {
56    /// App token缓存命中次数
57    pub app_cache_hits: AtomicU64,
58    /// App token缓存未命中次数
59    pub app_cache_misses: AtomicU64,
60    /// Tenant token缓存命中次数
61    pub tenant_cache_hits: AtomicU64,
62    /// Tenant token缓存未命中次数
63    pub tenant_cache_misses: AtomicU64,
64    /// Token刷新成功次数
65    pub refresh_success: AtomicU64,
66    /// Token刷新失败次数
67    pub refresh_failures: AtomicU64,
68    /// 总的读锁获取次数
69    pub read_lock_acquisitions: AtomicU64,
70    /// 总的写锁获取次数
71    pub write_lock_acquisitions: AtomicU64,
72}
73
74impl TokenMetrics {
75    pub fn new() -> Self {
76        Self::default()
77    }
78
79    /// 获取app token缓存命中率 (0.0-1.0)
80    pub fn app_cache_hit_rate(&self) -> f64 {
81        let hits = self.app_cache_hits.load(Ordering::Relaxed) as f64;
82        let misses = self.app_cache_misses.load(Ordering::Relaxed) as f64;
83        let total = hits + misses;
84        if total > 0.0 {
85            hits / total
86        } else {
87            0.0
88        }
89    }
90
91    /// 获取tenant token缓存命中率 (0.0-1.0)
92    pub fn tenant_cache_hit_rate(&self) -> f64 {
93        let hits = self.tenant_cache_hits.load(Ordering::Relaxed) as f64;
94        let misses = self.tenant_cache_misses.load(Ordering::Relaxed) as f64;
95        let total = hits + misses;
96        if total > 0.0 {
97            hits / total
98        } else {
99            0.0
100        }
101    }
102
103    /// 获取token刷新成功率 (0.0-1.0)
104    pub fn refresh_success_rate(&self) -> f64 {
105        let success = self.refresh_success.load(Ordering::Relaxed) as f64;
106        let failures = self.refresh_failures.load(Ordering::Relaxed) as f64;
107        let total = success + failures;
108        if total > 0.0 {
109            success / total
110        } else {
111            0.0
112        }
113    }
114
115    /// 生成性能报告
116    pub fn performance_report(&self) -> String {
117        format!(
118            "TokenManager Performance Metrics:\n\
119             - App Cache Hit Rate: {:.2}%\n\
120             - Tenant Cache Hit Rate: {:.2}%\n\
121             - Refresh Success Rate: {:.2}%\n\
122             - Total Read Locks: {}\n\
123             - Total Write Locks: {}\n\
124             - App Cache: {} hits, {} misses\n\
125             - Tenant Cache: {} hits, {} misses\n\
126             - Refreshes: {} success, {} failures",
127            self.app_cache_hit_rate() * 100.0,
128            self.tenant_cache_hit_rate() * 100.0,
129            self.refresh_success_rate() * 100.0,
130            self.read_lock_acquisitions.load(Ordering::Relaxed),
131            self.write_lock_acquisitions.load(Ordering::Relaxed),
132            self.app_cache_hits.load(Ordering::Relaxed),
133            self.app_cache_misses.load(Ordering::Relaxed),
134            self.tenant_cache_hits.load(Ordering::Relaxed),
135            self.tenant_cache_misses.load(Ordering::Relaxed),
136            self.refresh_success.load(Ordering::Relaxed),
137            self.refresh_failures.load(Ordering::Relaxed)
138        )
139    }
140}
141
142#[derive(Debug)]
143pub struct TokenManager {
144    cache: Arc<RwLock<QuickCache<String>>>,
145    metrics: Arc<TokenMetrics>,
146    /// 后台预热任务句柄
147    preheating_handle: Option<tokio::task::JoinHandle<()>>,
148}
149
150impl Default for TokenManager {
151    fn default() -> Self {
152        Self::new()
153    }
154}
155
156impl TokenManager {
157    pub fn new() -> Self {
158        Self {
159            cache: Arc::new(RwLock::new(QuickCache::new())),
160            metrics: Arc::new(TokenMetrics::new()),
161            preheating_handle: None,
162        }
163    }
164
165    /// 获取性能指标的只读引用
166    pub fn metrics(&self) -> &Arc<TokenMetrics> {
167        &self.metrics
168    }
169
170    /// 获取缓存的克隆引用(用于预热功能)
171    pub fn get_cache(&self) -> Arc<RwLock<QuickCache<String>>> {
172        self.cache.clone()
173    }
174
175    /// 获取性能指标的克隆引用(用于预热功能)
176    pub fn get_metrics(&self) -> Arc<TokenMetrics> {
177        self.metrics.clone()
178    }
179
180    /// 打印性能报告到日志
181    pub fn log_performance_metrics(&self) {
182        log::info!("{}", self.metrics.performance_report());
183    }
184
185    /// 启动后台token预热机制
186    ///
187    /// 这个方法会启动一个后台任务,定期检查即将过期的token并预先刷新它们
188    ///
189    /// # 参数
190    /// - `config`: 应用配置,用于token刷新
191    /// - `app_ticket_manager`: App ticket管理器的引用
192    pub fn start_background_preheating(
193        &mut self,
194        config: Config,
195        app_ticket_manager: Arc<Mutex<AppTicketManager>>,
196    ) {
197        self.start_background_preheating_with_config(
198            config,
199            app_ticket_manager,
200            PreheatingConfig::default(),
201        )
202    }
203
204    /// 启动带自定义配置的后台token预热机制
205    pub fn start_background_preheating_with_config(
206        &mut self,
207        config: Config,
208        app_ticket_manager: Arc<Mutex<AppTicketManager>>,
209        preheat_config: PreheatingConfig,
210    ) {
211        // 如果已有预热任务在运行,先停止它
212        if self.preheating_handle.is_some() {
213            log::info!("🔄 停止现有预热任务,启动新配置的预热任务");
214            self.stop_background_preheating();
215        }
216
217        let cache = self.cache.clone();
218        let metrics = self.metrics.clone();
219
220        let handle = tokio::spawn(async move {
221            let mut interval = interval(Duration::from_secs(preheat_config.check_interval_seconds));
222            log::info!(
223                "🔄 Token后台预热机制已启动,检查间隔: {}分钟,预热阈值: {}分钟",
224                preheat_config.check_interval_seconds / 60,
225                preheat_config.preheat_threshold_seconds / 60
226            );
227
228            loop {
229                interval.tick().await;
230
231                if let Err(e) = Self::preheat_tokens_if_needed_with_config(
232                    &cache,
233                    &metrics,
234                    &config,
235                    &app_ticket_manager,
236                    &preheat_config,
237                )
238                .await
239                {
240                    log::warn!("⚠️ Token预热过程中发生错误: {e:?}");
241                    // 记录错误但继续运行
242                }
243            }
244        });
245
246        self.preheating_handle = Some(handle);
247        log::info!("✅ Token后台预热任务已启动并注册到TokenManager");
248    }
249
250    /// 检查并预热即将过期的token(使用默认配置)
251    #[allow(dead_code)]
252    async fn preheat_tokens_if_needed(
253        cache: &Arc<RwLock<QuickCache<String>>>,
254        metrics: &Arc<TokenMetrics>,
255        config: &Config,
256        app_ticket_manager: &Arc<Mutex<AppTicketManager>>,
257    ) -> SDKResult<()> {
258        Self::preheat_tokens_if_needed_with_config(
259            cache,
260            metrics,
261            config,
262            app_ticket_manager,
263            &PreheatingConfig::default(),
264        )
265        .await
266    }
267
268    /// 检查并预热即将过期的token(使用自定义配置)
269    async fn preheat_tokens_if_needed_with_config(
270        cache: &Arc<RwLock<QuickCache<String>>>,
271        metrics: &Arc<TokenMetrics>,
272        config: &Config,
273        app_ticket_manager: &Arc<Mutex<AppTicketManager>>,
274        preheat_config: &PreheatingConfig,
275    ) -> SDKResult<()> {
276        log::debug!("🔍 检查需要预热的token...");
277
278        let mut preheated_count = 0;
279
280        // 检查app access token是否需要预热
281        let app_key = app_access_token_key(&config.app_id);
282        if Self::should_preheat_token_with_threshold(
283            cache,
284            &app_key,
285            preheat_config.preheat_threshold_seconds,
286        )
287        .await
288        {
289            log::info!("🔄 开始预热 app access token");
290            if let Err(e) = Self::preheat_app_token(cache, config, app_ticket_manager).await {
291                log::warn!("❌ App token预热失败: {e:?}");
292                metrics.refresh_failures.fetch_add(1, Ordering::Relaxed);
293            } else {
294                log::info!("✅ App token预热成功");
295                metrics.refresh_success.fetch_add(1, Ordering::Relaxed);
296                preheated_count += 1;
297            }
298        }
299
300        // 预热tenant token(如果启用)
301        if preheat_config.enable_tenant_preheating {
302            let tenant_keys = Self::get_cached_tenant_keys(cache, &config.app_id).await;
303            for tenant_key in tenant_keys
304                .into_iter()
305                .take(preheat_config.max_concurrent_preheat)
306            {
307                let tenant_cache_key = tenant_access_token_key(&config.app_id, &tenant_key);
308                if Self::should_preheat_token_with_threshold(
309                    cache,
310                    &tenant_cache_key,
311                    preheat_config.preheat_threshold_seconds,
312                )
313                .await
314                {
315                    log::info!("🔄 开始预热 tenant access token: {tenant_key}");
316                    if let Err(e) =
317                        Self::preheat_tenant_token(cache, config, &tenant_key, app_ticket_manager)
318                            .await
319                    {
320                        log::warn!("❌ Tenant token预热失败 ({tenant_key}): {e:?}");
321                        metrics.refresh_failures.fetch_add(1, Ordering::Relaxed);
322                    } else {
323                        log::info!("✅ Tenant token预热成功: {tenant_key}");
324                        metrics.refresh_success.fetch_add(1, Ordering::Relaxed);
325                        preheated_count += 1;
326                    }
327                }
328            }
329        }
330
331        if preheated_count > 0 {
332            log::info!("🎯 本轮预热完成,共刷新了 {preheated_count} 个token");
333        } else {
334            log::debug!("✨ 所有token状态良好,无需预热");
335        }
336
337        Ok(())
338    }
339
340    /// 判断指定的token是否需要预热(使用默认15分钟阈值)
341    #[allow(dead_code)]
342    async fn should_preheat_token(cache: &Arc<RwLock<QuickCache<String>>>, key: &str) -> bool {
343        Self::should_preheat_token_with_threshold(cache, key, 900).await
344    }
345
346    /// 判断指定的token是否需要预热(自定义阈值)
347    ///
348    /// 如果token不存在或即将在指定时间内过期,则需要预热
349    async fn should_preheat_token_with_threshold(
350        cache: &Arc<RwLock<QuickCache<String>>>,
351        key: &str,
352        threshold_seconds: u64,
353    ) -> bool {
354        let cache_read = cache.read().await;
355
356        // 如果token不存在,需要预热
357        if cache_read.get(key).is_none_or(|token| token.is_empty()) {
358            log::debug!("🔍 Token {key} 不存在,需要预热");
359            return true;
360        }
361
362        // 检查是否即将过期
363        if let Some(expiry_info) = cache_read.get_with_expiry(key) {
364            let remaining_seconds = expiry_info.expiry_seconds();
365            // 如果剩余时间少于阈值,需要预热
366            if remaining_seconds < threshold_seconds {
367                log::debug!(
368                    "🔍 Token {key} 将在{remaining_seconds}秒后过期,阈值{threshold_seconds}秒,需要预热"
369                );
370                return true;
371            }
372        }
373
374        false
375    }
376
377    /// 获取缓存中所有的tenant keys
378    async fn get_cached_tenant_keys(
379        _cache: &Arc<RwLock<QuickCache<String>>>,
380        _app_id: &str,
381    ) -> Vec<String> {
382        // 注意:这里需要QuickCache提供遍历功能,目前简化为空列表
383        // 在实际实现中,可以维护一个单独的tenant_keys集合
384        // 或者在QuickCache中添加keys()方法来遍历所有键
385        vec![]
386    }
387
388    /// 预热app access token (直接更新主缓存)
389    async fn preheat_app_token(
390        cache: &Arc<RwLock<QuickCache<String>>>,
391        config: &Config,
392        app_ticket_manager: &Arc<Mutex<AppTicketManager>>,
393    ) -> SDKResult<String> {
394        // 直接使用主缓存实例进行预热
395        let temp_manager = TokenManager {
396            cache: cache.clone(),
397            metrics: Arc::new(TokenMetrics::new()), // 临时指标,只用于API调用
398            preheating_handle: None,
399        };
400
401        match config.app_type {
402            AppType::SelfBuild => {
403                temp_manager
404                    .get_custom_app_access_token_then_cache(config)
405                    .await
406            }
407            _ => {
408                temp_manager
409                    .get_marketplace_app_access_token_then_cache(config, "", app_ticket_manager)
410                    .await
411            }
412        }
413    }
414
415    /// 预热tenant access token (直接更新主缓存)
416    async fn preheat_tenant_token(
417        cache: &Arc<RwLock<QuickCache<String>>>,
418        config: &Config,
419        tenant_key: &str,
420        app_ticket_manager: &Arc<Mutex<AppTicketManager>>,
421    ) -> SDKResult<String> {
422        // 直接使用主缓存实例进行预热
423        let temp_manager = TokenManager {
424            cache: cache.clone(),
425            metrics: Arc::new(TokenMetrics::new()), // 临时指标,只用于API调用
426            preheating_handle: None,
427        };
428
429        if config.app_type == AppType::SelfBuild {
430            temp_manager
431                .get_custom_tenant_access_token_then_cache(config, tenant_key)
432                .await
433        } else {
434            temp_manager
435                .get_marketplace_tenant_access_token_then_cache(
436                    config,
437                    tenant_key,
438                    "",
439                    app_ticket_manager,
440                )
441                .await
442        }
443    }
444
445    /// 停止后台预热任务
446    pub fn stop_background_preheating(&mut self) {
447        if let Some(handle) = self.preheating_handle.take() {
448            handle.abort();
449            log::info!("🛑 Token后台预热机制已停止");
450        }
451    }
452
453    /// 检查预热任务是否正在运行 (用于测试)
454    pub fn is_preheating_active(&self) -> bool {
455        self.preheating_handle.is_some()
456    }
457    pub async fn get_app_access_token(
458        &self,
459        config: &Config,
460        app_ticket: &str,
461        app_ticket_manager: &Arc<Mutex<AppTicketManager>>,
462    ) -> SDKResult<String> {
463        let start_time = Instant::now();
464        let key = app_access_token_key(&config.app_id);
465
466        // 快速路径:使用读锁尝试获取缓存的token
467        {
468            self.metrics
469                .read_lock_acquisitions
470                .fetch_add(1, Ordering::Relaxed);
471            let cache = self.cache.read().await;
472            if let Some(token) = cache.get(&key) {
473                if !token.is_empty() {
474                    self.metrics.app_cache_hits.fetch_add(1, Ordering::Relaxed);
475                    log::debug!("App token cache hit in {:?}", start_time.elapsed());
476                    return Ok(token);
477                }
478            }
479        }
480
481        // 记录缓存未命中
482        self.metrics
483            .app_cache_misses
484            .fetch_add(1, Ordering::Relaxed);
485
486        // 慢速路径:需要刷新token,使用写锁
487        self.metrics
488            .write_lock_acquisitions
489            .fetch_add(1, Ordering::Relaxed);
490        let cache = self.cache.write().await;
491
492        // 双重检查:可能其他线程已经刷新了token
493        if let Some(token) = cache.get(&key) {
494            if !token.is_empty() {
495                // 双重检查命中,更新为缓存命中统计
496                self.metrics
497                    .app_cache_misses
498                    .fetch_sub(1, Ordering::Relaxed);
499                self.metrics.app_cache_hits.fetch_add(1, Ordering::Relaxed);
500                log::debug!("App token double-check hit in {:?}", start_time.elapsed());
501                return Ok(token);
502            }
503        }
504
505        // 实际执行token刷新
506        drop(cache); // 释放写锁,避免在HTTP请求期间持有锁
507        log::debug!("App token cache miss, refreshing token");
508
509        let app_type = config.app_type;
510        let result = if app_type == AppType::SelfBuild {
511            self.get_custom_app_access_token_then_cache(config).await
512        } else {
513            self.get_marketplace_app_access_token_then_cache(config, app_ticket, app_ticket_manager)
514                .await
515        };
516
517        // 记录刷新结果
518        match &result {
519            Ok(_) => {
520                self.metrics.refresh_success.fetch_add(1, Ordering::Relaxed);
521                log::debug!("App token refresh succeeded in {:?}", start_time.elapsed());
522            }
523            Err(e) => {
524                self.metrics
525                    .refresh_failures
526                    .fetch_add(1, Ordering::Relaxed);
527                log::warn!(
528                    "App token refresh failed in {:?}: {:?}",
529                    start_time.elapsed(),
530                    e
531                );
532            }
533        }
534
535        result
536    }
537
538    async fn get_custom_app_access_token_then_cache(&self, config: &Config) -> SDKResult<String> {
539        let url = format!("{}{}", config.base_url, APP_ACCESS_TOKEN_INTERNAL_URL_PATH);
540
541        let body = SelfBuiltAppAccessTokenReq {
542            app_id: config.app_id.clone(),
543            app_secret: config.app_secret.clone(),
544        };
545
546        let response = config.http_client.post(&url).json(&body).send().await?;
547
548        let resp: AppAccessTokenResp = response.json().await?;
549        self.handle_app_access_token_response(resp, &config.app_id)
550            .await
551    }
552
553    /// 通用的app access token响应处理逻辑
554    async fn handle_app_access_token_response(
555        &self,
556        resp: AppAccessTokenResp,
557        app_id: &str,
558    ) -> SDKResult<String> {
559        if resp.raw_response.code == 0 {
560            let expire = resp.expire - EXPIRY_DELTA;
561            {
562                let mut cache = self.cache.write().await;
563                cache.set(
564                    &app_access_token_key(app_id),
565                    resp.app_access_token.clone(),
566                    expire,
567                );
568            }
569            Ok(resp.app_access_token)
570        } else {
571            warn!("app access token response error: {:#?}", resp.raw_response);
572            Err(LarkAPIError::illegal_param(resp.raw_response.msg.clone()))
573        }
574    }
575    async fn get_marketplace_app_access_token_then_cache(
576        &self,
577        config: &Config,
578        app_ticket: &str,
579        app_ticket_manager: &Arc<Mutex<AppTicketManager>>,
580    ) -> SDKResult<String> {
581        let mut app_ticket = app_ticket.to_string();
582        if app_ticket.is_empty() {
583            match app_ticket_manager.lock().await.get(config).await {
584                None => return Err(LarkAPIError::illegal_param("App ticket is empty")),
585                Some(ticket) => {
586                    app_ticket = ticket;
587                }
588            }
589        }
590
591        let url = format!("{}{}", config.base_url, APP_ACCESS_TOKEN_URL_PATH);
592
593        let body = MarketplaceAppAccessTokenReq {
594            app_id: config.app_id.clone(),
595            app_secret: config.app_secret.clone(),
596            app_ticket,
597        };
598
599        let response = config.http_client.post(&url).json(&body).send().await?;
600
601        let resp: AppAccessTokenResp = response.json().await?;
602        self.handle_app_access_token_response(resp, &config.app_id)
603            .await
604    }
605
606    pub async fn get_tenant_access_token(
607        &self,
608        config: &Config,
609        tenant_key: &str,
610        app_ticket: &str,
611        app_ticket_manager: &Arc<Mutex<AppTicketManager>>,
612    ) -> SDKResult<String> {
613        let start_time = Instant::now();
614        let key = tenant_access_token_key(&config.app_id, tenant_key);
615
616        // 快速路径:使用读锁尝试获取缓存的token
617        {
618            self.metrics
619                .read_lock_acquisitions
620                .fetch_add(1, Ordering::Relaxed);
621            let cache = self.cache.read().await;
622            if let Some(token) = cache.get(&key) {
623                if !token.is_empty() {
624                    self.metrics
625                        .tenant_cache_hits
626                        .fetch_add(1, Ordering::Relaxed);
627                    log::debug!("Tenant token cache hit in {:?}", start_time.elapsed());
628                    return Ok(token);
629                }
630            }
631        }
632
633        // 记录缓存未命中
634        self.metrics
635            .tenant_cache_misses
636            .fetch_add(1, Ordering::Relaxed);
637
638        // 慢速路径:需要刷新token,使用写锁
639        self.metrics
640            .write_lock_acquisitions
641            .fetch_add(1, Ordering::Relaxed);
642        let cache = self.cache.write().await;
643
644        // 双重检查:可能其他线程已经刷新了token
645        if let Some(token) = cache.get(&key) {
646            if !token.is_empty() {
647                // 双重检查命中,更新为缓存命中统计
648                self.metrics
649                    .tenant_cache_misses
650                    .fetch_sub(1, Ordering::Relaxed);
651                self.metrics
652                    .tenant_cache_hits
653                    .fetch_add(1, Ordering::Relaxed);
654                log::debug!(
655                    "Tenant token double-check hit in {:?}",
656                    start_time.elapsed()
657                );
658                return Ok(token);
659            }
660        }
661
662        // 实际执行token刷新
663        drop(cache); // 释放写锁,避免在HTTP请求期间持有锁
664        log::debug!("Tenant token cache miss, refreshing token");
665
666        let result = if config.app_type == AppType::SelfBuild {
667            self.get_custom_tenant_access_token_then_cache(config, tenant_key)
668                .await
669        } else {
670            self.get_marketplace_tenant_access_token_then_cache(
671                config,
672                tenant_key,
673                app_ticket,
674                app_ticket_manager,
675            )
676            .await
677        };
678
679        // 记录刷新结果
680        match &result {
681            Ok(_) => {
682                self.metrics.refresh_success.fetch_add(1, Ordering::Relaxed);
683                log::debug!(
684                    "Tenant token refresh succeeded in {:?}",
685                    start_time.elapsed()
686                );
687            }
688            Err(e) => {
689                self.metrics
690                    .refresh_failures
691                    .fetch_add(1, Ordering::Relaxed);
692                log::warn!(
693                    "Tenant token refresh failed in {:?}: {:?}",
694                    start_time.elapsed(),
695                    e
696                );
697            }
698        }
699
700        result
701    }
702
703    async fn get_custom_tenant_access_token_then_cache(
704        &self,
705        config: &Config,
706        tenant_key: &str,
707    ) -> SDKResult<String> {
708        let url = format!(
709            "{}{}",
710            config.base_url, TENANT_ACCESS_TOKEN_INTERNAL_URL_PATH
711        );
712
713        let body = SelfBuiltTenantAccessTokenReq {
714            app_id: config.app_id.clone(),
715            app_secret: config.app_secret.clone(),
716        };
717
718        let response = config.http_client.post(&url).json(&body).send().await?;
719
720        let resp: TenantAccessTokenResp = response.json().await?;
721        self.handle_tenant_access_token_response(resp, &config.app_id, tenant_key)
722            .await
723    }
724
725    /// 通用的tenant access token响应处理逻辑
726    async fn handle_tenant_access_token_response(
727        &self,
728        resp: TenantAccessTokenResp,
729        app_id: &str,
730        tenant_key: &str,
731    ) -> SDKResult<String> {
732        if resp.raw_response.code == 0 {
733            let expire = resp.expire - EXPIRY_DELTA;
734            {
735                let mut cache = self.cache.write().await;
736                cache.set(
737                    &tenant_access_token_key(app_id, tenant_key),
738                    resp.tenant_access_token.clone(),
739                    expire,
740                );
741            }
742            Ok(resp.tenant_access_token)
743        } else {
744            warn!(
745                "tenant access token response error: {:#?}",
746                resp.raw_response
747            );
748            Err(LarkAPIError::illegal_param(resp.raw_response.msg.clone()))
749        }
750    }
751
752    async fn get_marketplace_tenant_access_token_then_cache(
753        &self,
754        config: &Config,
755        tenant_key: &str,
756        app_ticket: &str,
757        app_ticket_manager: &Arc<Mutex<AppTicketManager>>,
758    ) -> SDKResult<String> {
759        let app_access_token = self
760            .get_marketplace_app_access_token_then_cache(config, app_ticket, app_ticket_manager)
761            .await?;
762
763        let url = format!("{}{}", config.base_url, TENANT_ACCESS_TOKEN_URL_PATH);
764
765        let body = MarketplaceTenantAccessTokenReq {
766            app_access_token,
767            tenant_key: tenant_key.to_string(),
768        };
769
770        let response = config
771            .http_client
772            .post(&url)
773            .json(&body)
774            .header(
775                "Authorization",
776                &format!("Bearer {}", &body.app_access_token),
777            )
778            .send()
779            .await?;
780
781        let resp: TenantAccessTokenResp = response.json().await?;
782        self.handle_tenant_access_token_response(resp, &config.app_id, tenant_key)
783            .await
784    }
785}
786
787fn app_access_token_key(app_id: &str) -> String {
788    format!("{APP_ACCESS_TOKEN_KEY_PREFIX}-{app_id}")
789}
790
791fn tenant_access_token_key(app_id: &str, tenant_key: &str) -> String {
792    format!("{APP_ACCESS_TOKEN_KEY_PREFIX}-{app_id}-{tenant_key}")
793}
794
795#[derive(Debug, Serialize, Deserialize)]
796struct SelfBuiltAppAccessTokenReq {
797    app_id: String,
798    app_secret: String,
799}
800
801#[derive(Debug, Serialize, Deserialize)]
802struct SelfBuiltTenantAccessTokenReq {
803    app_id: String,
804    app_secret: String,
805}
806
807#[derive(Serialize, Deserialize, Debug)]
808struct AppAccessTokenResp {
809    #[serde(flatten)]
810    raw_response: RawResponse,
811    expire: i32,
812    app_access_token: String,
813}
814
815impl ApiResponseTrait for AppAccessTokenResp {
816    fn data_format() -> ResponseFormat {
817        ResponseFormat::Flatten
818    }
819}
820
821#[derive(Serialize, Deserialize)]
822struct MarketplaceAppAccessTokenReq {
823    app_id: String,
824    app_secret: String,
825    app_ticket: String,
826}
827
828#[derive(Serialize, Deserialize)]
829struct MarketplaceTenantAccessTokenReq {
830    app_access_token: String,
831    tenant_key: String,
832}
833
834#[derive(Serialize, Deserialize, Debug)]
835struct TenantAccessTokenResp {
836    #[serde(flatten)]
837    raw_response: RawResponse,
838    expire: i32,
839    tenant_access_token: String,
840}
841
842impl ApiResponseTrait for TenantAccessTokenResp {
843    fn data_format() -> ResponseFormat {
844        ResponseFormat::Flatten
845    }
846}
847
848#[cfg(test)]
849mod tests {
850    use super::*;
851    use crate::core::{config::Config, constants::AppType};
852    use std::{collections::HashMap, sync::Arc, time::Duration};
853    use tokio::sync::Mutex;
854
855    #[test]
856    fn test_token_manager_creation() {
857        let manager = TokenManager::new();
858        // 测试创建TokenManager不会panic
859        // 注意:QuickCache没有len方法,我们只测试创建不panic
860        let _cache = &manager.cache;
861    }
862
863    #[test]
864    fn test_app_access_token_key_generation() {
865        let app_id = "test_app_id";
866        let key = app_access_token_key(app_id);
867        assert_eq!(key, format!("{APP_ACCESS_TOKEN_KEY_PREFIX}-{app_id}"));
868    }
869
870    #[test]
871    fn test_tenant_access_token_key_generation() {
872        let app_id = "test_app_id";
873        let tenant_key = "test_tenant";
874        let key = tenant_access_token_key(app_id, tenant_key);
875        assert_eq!(
876            key,
877            format!("{APP_ACCESS_TOKEN_KEY_PREFIX}-{app_id}-{tenant_key}")
878        );
879    }
880
881    #[tokio::test]
882    async fn test_cache_miss_returns_empty_string() {
883        let manager = TokenManager::new();
884        let key = "non_existent_key";
885
886        // 验证缓存miss时返回空字符串而不是panic
887        let cache = manager.cache.read().await;
888        let result = cache.get(key).unwrap_or_default();
889        assert_eq!(result, String::new());
890    }
891
892    #[tokio::test]
893    async fn test_get_app_access_token_cache_miss_does_not_error() {
894        let manager = TokenManager::new();
895        let config = Config {
896            app_id: "test_app".to_string(),
897            app_secret: "test_secret".to_string(),
898            app_type: AppType::SelfBuild,
899            base_url: "https://open.feishu.cn".to_string(),
900            http_client: reqwest::Client::new(),
901            enable_token_cache: true,
902            req_timeout: Some(Duration::from_secs(30)),
903            header: HashMap::new(),
904            token_manager: Arc::new(Mutex::new(TokenManager::new())),
905            app_ticket_manager: Arc::new(Mutex::new(
906                crate::core::app_ticket_manager::AppTicketManager::new(),
907            )),
908        };
909
910        let app_ticket_manager = Arc::new(Mutex::new(
911            crate::core::app_ticket_manager::AppTicketManager::new(),
912        ));
913
914        // 测试缓存miss时不会立即返回错误,而是尝试获取新token
915        // 注意:此测试会因为实际API调用失败而失败,但不应该因为缓存逻辑失败
916        let result = manager
917            .get_app_access_token(&config, "", &app_ticket_manager)
918            .await;
919
920        // 这里我们期望的是网络错误或API错误,而不是"cache error"
921        if let Err(error) = result {
922            let error_msg = format!("{error:?}");
923            assert!(
924                !error_msg.contains("cache error"),
925                "应该不再出现'cache error',而是实际的API调用错误: {error_msg}"
926            );
927        }
928    }
929
930    #[test]
931    fn test_token_metrics_creation() {
932        let metrics = TokenMetrics::new();
933
934        // 测试初始状态
935        assert_eq!(metrics.app_cache_hit_rate(), 0.0);
936        assert_eq!(metrics.tenant_cache_hit_rate(), 0.0);
937        assert_eq!(metrics.refresh_success_rate(), 0.0);
938    }
939
940    #[test]
941    fn test_token_metrics_cache_hit_rate_calculation() {
942        let metrics = TokenMetrics::new();
943
944        // 模拟一些缓存命中和未命中
945        metrics.app_cache_hits.store(8, Ordering::Relaxed);
946        metrics.app_cache_misses.store(2, Ordering::Relaxed);
947
948        assert_eq!(metrics.app_cache_hit_rate(), 0.8); // 8/(8+2) = 0.8
949
950        // 测试tenant缓存
951        metrics.tenant_cache_hits.store(9, Ordering::Relaxed);
952        metrics.tenant_cache_misses.store(1, Ordering::Relaxed);
953
954        assert_eq!(metrics.tenant_cache_hit_rate(), 0.9); // 9/(9+1) = 0.9
955    }
956
957    #[test]
958    fn test_token_metrics_refresh_success_rate() {
959        let metrics = TokenMetrics::new();
960
961        // 模拟刷新成功和失败
962        metrics.refresh_success.store(19, Ordering::Relaxed);
963        metrics.refresh_failures.store(1, Ordering::Relaxed);
964
965        assert_eq!(metrics.refresh_success_rate(), 0.95); // 19/(19+1) = 0.95
966    }
967
968    #[test]
969    fn test_token_metrics_performance_report() {
970        let metrics = TokenMetrics::new();
971
972        // 设置一些测试数据
973        metrics.app_cache_hits.store(80, Ordering::Relaxed);
974        metrics.app_cache_misses.store(20, Ordering::Relaxed);
975        metrics.refresh_success.store(95, Ordering::Relaxed);
976        metrics.refresh_failures.store(5, Ordering::Relaxed);
977
978        let report = metrics.performance_report();
979
980        // 验证报告包含关键信息
981        assert!(report.contains("80.00%")); // App cache hit rate
982        assert!(report.contains("95.00%")); // Refresh success rate
983        assert!(report.contains("80 hits, 20 misses")); // Cache statistics
984    }
985
986    #[tokio::test]
987    async fn test_token_manager_metrics_integration() {
988        let manager = TokenManager::new();
989
990        // 验证metrics可以正常访问
991        let metrics = manager.metrics();
992        assert_eq!(metrics.read_lock_acquisitions.load(Ordering::Relaxed), 0);
993
994        // 验证性能报告功能
995        manager.log_performance_metrics(); // 应该不会panic
996    }
997
998    #[tokio::test]
999    async fn test_preheating_config_default_values() {
1000        let config = PreheatingConfig::default();
1001        assert_eq!(config.check_interval_seconds, 1800); // 30分钟
1002        assert_eq!(config.preheat_threshold_seconds, 900); // 15分钟
1003        assert!(config.enable_tenant_preheating);
1004        assert_eq!(config.max_concurrent_preheat, 3);
1005    }
1006
1007    #[tokio::test]
1008    async fn test_should_preheat_token_with_custom_threshold() {
1009        let manager = TokenManager::new();
1010        let key = "test_token_key";
1011
1012        // 测试不存在的token应该需要预热
1013        assert!(TokenManager::should_preheat_token_with_threshold(&manager.cache, key, 600).await);
1014
1015        // 添加一个token到缓存(模拟刚刷新的token)
1016        {
1017            let mut cache = manager.cache.write().await;
1018            cache.set(key, "test_token_value".to_string(), 3600); // 1小时后过期
1019        }
1020
1021        // 测试长阈值应该不需要预热
1022        assert!(!TokenManager::should_preheat_token_with_threshold(&manager.cache, key, 600).await);
1023
1024        // 测试短阈值应该需要预热
1025        assert!(TokenManager::should_preheat_token_with_threshold(&manager.cache, key, 3700).await);
1026    }
1027
1028    #[tokio::test]
1029    async fn test_get_cached_tenant_keys() {
1030        let manager = TokenManager::new();
1031
1032        // 测试空缓存时返回空列表
1033        let tenant_keys = TokenManager::get_cached_tenant_keys(&manager.cache, "test_app").await;
1034        assert!(tenant_keys.is_empty());
1035    }
1036
1037    #[test]
1038    fn test_cache_entry_expiry_calculations() {
1039        use crate::core::cache::CacheEntry;
1040        use std::time::Duration;
1041        use tokio::time::Instant;
1042
1043        let now = Instant::now();
1044        let expires_in_10_mins = now + Duration::from_secs(600);
1045
1046        let entry = CacheEntry {
1047            value: "test_value".to_string(),
1048            expires_at: expires_in_10_mins,
1049            current_time: now,
1050        };
1051
1052        // 测试过期时间计算
1053        assert_eq!(entry.expiry_seconds(), 600);
1054
1055        // 测试即将过期判断
1056        assert!(entry.expires_within(700)); // 10分钟 < 700秒
1057        assert!(!entry.expires_within(500)); // 10分钟 > 500秒
1058    }
1059}