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#[derive(Debug, Clone)]
31pub struct PreheatingConfig {
32 pub check_interval_seconds: u64,
34 pub preheat_threshold_seconds: u64,
36 pub enable_tenant_preheating: bool,
38 pub max_concurrent_preheat: usize,
40}
41
42impl Default for PreheatingConfig {
43 fn default() -> Self {
44 Self {
45 check_interval_seconds: 1800, preheat_threshold_seconds: 900, enable_tenant_preheating: true,
48 max_concurrent_preheat: 3,
49 }
50 }
51}
52
53#[derive(Debug, Default)]
55pub struct TokenMetrics {
56 pub app_cache_hits: AtomicU64,
58 pub app_cache_misses: AtomicU64,
60 pub tenant_cache_hits: AtomicU64,
62 pub tenant_cache_misses: AtomicU64,
64 pub refresh_success: AtomicU64,
66 pub refresh_failures: AtomicU64,
68 pub read_lock_acquisitions: AtomicU64,
70 pub write_lock_acquisitions: AtomicU64,
72}
73
74impl TokenMetrics {
75 pub fn new() -> Self {
76 Self::default()
77 }
78
79 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 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 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 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 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 pub fn metrics(&self) -> &Arc<TokenMetrics> {
167 &self.metrics
168 }
169
170 pub fn get_cache(&self) -> Arc<RwLock<QuickCache<String>>> {
172 self.cache.clone()
173 }
174
175 pub fn get_metrics(&self) -> Arc<TokenMetrics> {
177 self.metrics.clone()
178 }
179
180 pub fn log_performance_metrics(&self) {
182 log::info!("{}", self.metrics.performance_report());
183 }
184
185 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 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 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 }
243 }
244 });
245
246 self.preheating_handle = Some(handle);
247 log::info!("✅ Token后台预热任务已启动并注册到TokenManager");
248 }
249
250 #[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 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 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 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 #[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 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 if cache_read.get(key).is_none_or(|token| token.is_empty()) {
358 log::debug!("🔍 Token {key} 不存在,需要预热");
359 return true;
360 }
361
362 if let Some(expiry_info) = cache_read.get_with_expiry(key) {
364 let remaining_seconds = expiry_info.expiry_seconds();
365 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 async fn get_cached_tenant_keys(
379 _cache: &Arc<RwLock<QuickCache<String>>>,
380 _app_id: &str,
381 ) -> Vec<String> {
382 vec![]
386 }
387
388 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 let temp_manager = TokenManager {
396 cache: cache.clone(),
397 metrics: Arc::new(TokenMetrics::new()), 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 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 let temp_manager = TokenManager {
424 cache: cache.clone(),
425 metrics: Arc::new(TokenMetrics::new()), 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 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 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 {
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 self.metrics
483 .app_cache_misses
484 .fetch_add(1, Ordering::Relaxed);
485
486 self.metrics
488 .write_lock_acquisitions
489 .fetch_add(1, Ordering::Relaxed);
490 let cache = self.cache.write().await;
491
492 if let Some(token) = cache.get(&key) {
494 if !token.is_empty() {
495 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 drop(cache); 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 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 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 {
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 self.metrics
635 .tenant_cache_misses
636 .fetch_add(1, Ordering::Relaxed);
637
638 self.metrics
640 .write_lock_acquisitions
641 .fetch_add(1, Ordering::Relaxed);
642 let cache = self.cache.write().await;
643
644 if let Some(token) = cache.get(&key) {
646 if !token.is_empty() {
647 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 drop(cache); 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 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 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 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 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 let result = manager
917 .get_app_access_token(&config, "", &app_ticket_manager)
918 .await;
919
920 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 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 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); 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); }
956
957 #[test]
958 fn test_token_metrics_refresh_success_rate() {
959 let metrics = TokenMetrics::new();
960
961 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); }
967
968 #[test]
969 fn test_token_metrics_performance_report() {
970 let metrics = TokenMetrics::new();
971
972 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 assert!(report.contains("80.00%")); assert!(report.contains("95.00%")); assert!(report.contains("80 hits, 20 misses")); }
985
986 #[tokio::test]
987 async fn test_token_manager_metrics_integration() {
988 let manager = TokenManager::new();
989
990 let metrics = manager.metrics();
992 assert_eq!(metrics.read_lock_acquisitions.load(Ordering::Relaxed), 0);
993
994 manager.log_performance_metrics(); }
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); assert_eq!(config.preheat_threshold_seconds, 900); 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 assert!(TokenManager::should_preheat_token_with_threshold(&manager.cache, key, 600).await);
1014
1015 {
1017 let mut cache = manager.cache.write().await;
1018 cache.set(key, "test_token_value".to_string(), 3600); }
1020
1021 assert!(!TokenManager::should_preheat_token_with_threshold(&manager.cache, key, 600).await);
1023
1024 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 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 assert_eq!(entry.expiry_seconds(), 600);
1054
1055 assert!(entry.expires_within(700)); assert!(!entry.expires_within(500)); }
1059}