weixin_agent/api/
config_cache.rs1use std::sync::Arc;
4
5use dashmap::DashMap;
6
7use crate::api::client::HttpApiClient;
8use crate::types::CONFIG_CACHE_TTL_MS;
9
10const INITIAL_RETRY_MS: u64 = 2_000;
11const MAX_RETRY_MS: u64 = 3_600_000;
12
13struct CacheEntry {
14 typing_ticket: String,
15 next_fetch_at: u64,
16 retry_delay_ms: u64,
17}
18
19pub(crate) struct ConfigCache {
21 api: Arc<HttpApiClient>,
22 cache: DashMap<String, CacheEntry>,
23}
24
25impl ConfigCache {
26 pub fn new(api: Arc<HttpApiClient>) -> Self {
28 Self {
29 api,
30 cache: DashMap::new(),
31 }
32 }
33
34 pub async fn get_typing_ticket(
36 &self,
37 user_id: &str,
38 context_token: Option<&str>,
39 ) -> Option<String> {
40 let now = now_ms();
41 let should_fetch = self
42 .cache
43 .get(user_id)
44 .is_none_or(|e| now >= e.next_fetch_at);
45
46 if should_fetch {
47 match self.api.get_config(user_id, context_token).await {
48 Ok(resp) if resp.ret.unwrap_or(-1) == 0 => {
49 let ticket = resp.typing_ticket.unwrap_or_default();
50 #[allow(
52 clippy::cast_possible_truncation,
53 clippy::cast_sign_loss,
54 clippy::cast_precision_loss
55 )]
56 let jitter = (rand::random::<f64>() * CONFIG_CACHE_TTL_MS as f64) as u64;
57 self.cache.insert(
58 user_id.to_owned(),
59 CacheEntry {
60 typing_ticket: ticket,
61 next_fetch_at: now + jitter,
62 retry_delay_ms: INITIAL_RETRY_MS,
63 },
64 );
65 }
66 _ => {
67 let mut entry = self.cache.entry(user_id.to_owned()).or_insert(CacheEntry {
69 typing_ticket: String::new(),
70 next_fetch_at: now + INITIAL_RETRY_MS,
71 retry_delay_ms: INITIAL_RETRY_MS,
72 });
73 let next_delay = (entry.retry_delay_ms * 2).min(MAX_RETRY_MS);
74 entry.next_fetch_at = now + next_delay;
75 entry.retry_delay_ms = next_delay;
76 }
77 }
78 }
79
80 self.cache.get(user_id).map(|e| e.typing_ticket.clone())
81 }
82}
83
84use crate::util::now_ms;