dudu MVP:五端语音输入法初始提交
- server:Go 网关(WS 流式识别中继/计费配额/微信登录支付 mock/反馈/埋点),gummy provider 已真实联调 - desktop:Tauri 2(全局快捷键 push-to-talk/浮层/托盘/设置/登录购买/反馈/首启引导) - android:Compose 主 App + IME(键盘内录音直传) - ios:App + 键盘扩展(1A spike 实证键盘内不可录音,走 deep link 听写) - design/design-pipeline:设计系统 + token 导出 iOS/Android 主题 - doc:前后端设计文档(HTML);web:官网宣传页;todo:任务看板 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
//! 客户端打点(10H):内存队列,满 20 条或 60s 批量上报;失败丢弃不影响功能。
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use serde_json::{json, Value};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
#[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,
|
||||
"platform": std::env::consts::OS,
|
||||
"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
|
||||
}
|
||||
Reference in New Issue
Block a user