b5ab92a57e
来自 xhigh code review 的正确性/健壮性修复,覆盖全部五端: - server:鉴权 fail-closed、计量交叉校验与配额扣穿处理、WS 网关并发与关闭顺序、 billing 行锁、redis Lua 过期与设备槽刷新、config 解析 - desktop:会话 epoch 防串话、WS 重连与 401 处理、api 客户端复用、统一 usePoll 轮询 - android:握手时序、请求头封装、账户状态派生、按需重组 - ios:finalize 宽限、串行采集、错误文案服务端优先、删除死代码 CommitController Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
78 lines
2.6 KiB
Rust
78 lines
2.6 KiB
Rust
//! 客户端打点(10H):内存队列,满 20 条或 60s 批量上报;失败丢弃不影响功能。
|
||
|
||
use parking_lot::Mutex;
|
||
use serde_json::{json, Value};
|
||
use std::sync::Arc;
|
||
use std::time::Duration;
|
||
use tauri::{AppHandle, Manager};
|
||
|
||
/// 协议平台标识(18H):mac / win / linux(区别于 std::env::consts::OS 的 macos/windows)。
|
||
const PLATFORM: &str = if cfg!(target_os = "macos") {
|
||
"mac"
|
||
} else if cfg!(target_os = "windows") {
|
||
"win"
|
||
} else {
|
||
"linux"
|
||
};
|
||
|
||
#[derive(Clone)]
|
||
pub struct Metrics {
|
||
queue: Arc<Mutex<Vec<Value>>>,
|
||
}
|
||
|
||
impl Metrics {
|
||
pub fn record(&self, event: &str, props: Value) {
|
||
let mut q = self.queue.lock();
|
||
q.push(json!({
|
||
"event": event,
|
||
"props": props,
|
||
"client_ts": std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH).map(|d| d.as_millis() as i64).unwrap_or(0),
|
||
}));
|
||
if q.len() > 1000 {
|
||
let overflow = q.len() - 1000;
|
||
q.drain(..overflow); // 上限丢最旧
|
||
}
|
||
}
|
||
}
|
||
|
||
pub fn spawn(app: AppHandle) -> Metrics {
|
||
let m = Metrics {
|
||
queue: Arc::new(Mutex::new(Vec::new())),
|
||
};
|
||
let queue = m.queue.clone();
|
||
tauri::async_runtime::spawn(async move {
|
||
let client = reqwest::Client::new();
|
||
let mut tick = tokio::time::interval(Duration::from_secs(10));
|
||
loop {
|
||
tick.tick().await;
|
||
let (events, url, device_id) = {
|
||
let mut q = queue.lock();
|
||
if q.is_empty() || (q.len() < 20 && tickcount_skip(&q)) {
|
||
continue;
|
||
}
|
||
let events: Vec<Value> = q.drain(..).collect();
|
||
let s = app.state::<crate::settings::SettingsStore>().get();
|
||
(events, format!("{}/v1/metrics/batch", s.server_url), s.device_id)
|
||
};
|
||
let body = json!({
|
||
"device_id": device_id,
|
||
// 协议平台标识 mac/win/linux(18H,区别于 std::env::consts::OS 的 macos/windows)。
|
||
"platform": PLATFORM,
|
||
"app_version": env!("CARGO_PKG_VERSION"),
|
||
"os_version": "",
|
||
"events": events,
|
||
});
|
||
let _ = client.post(&url).json(&body).send().await; // 失败即丢弃
|
||
}
|
||
});
|
||
m
|
||
}
|
||
|
||
// 不满 20 条时按 60s 节奏(tick 10s × 6 次取模近似)上报;保持实现极简。
|
||
fn tickcount_skip(_q: &[Value]) -> bool {
|
||
use std::sync::atomic::{AtomicU64, Ordering};
|
||
static N: AtomicU64 = AtomicU64::new(0);
|
||
N.fetch_add(1, Ordering::Relaxed) % 6 != 0
|
||
}
|