- Base platform from base.git (ERP base: auth, core, config, message, workflow, plugin) - Created erp-diary module skeleton (lib.rs, dto.rs, error.rs, event.rs, state.rs) - Integrated erp-diary into workspace and erp-server - Added DiaryModule registration in main.rs - Added DiaryState FromRef in state.rs - Diary routes mounted (empty routes, ready for implementation) - Product design spec v1.2 preserved in docs/ - Implementation plan preserved in plans/ Cargo check: OK Cargo test: OK (78+ base tests passing)
66 lines
2.0 KiB
Rust
66 lines
2.0 KiB
Rust
use axum::Json;
|
|
use axum::extract::Extension;
|
|
use serde::Deserialize;
|
|
use tracing;
|
|
|
|
use erp_core::error::AppError;
|
|
use erp_core::rbac::require_permission;
|
|
use erp_core::types::{ApiResponse, TenantContext};
|
|
|
|
const MAX_EVENTS_PER_BATCH: usize = 100;
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
#[allow(dead_code)] // 客户端上报结构体,字段后续接入分析表时使用
|
|
pub struct AnalyticsEvent {
|
|
pub event: String,
|
|
pub properties: Option<serde_json::Value>,
|
|
#[serde(deserialize_with = "deserialize_flexible_timestamp")]
|
|
pub timestamp: Option<String>,
|
|
pub page: Option<String>,
|
|
pub user_id: Option<String>,
|
|
pub patient_id: Option<String>,
|
|
}
|
|
|
|
fn deserialize_flexible_timestamp<'de, D>(de: D) -> Result<Option<String>, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
use serde::de;
|
|
let val = Option::<serde_json::Value>::deserialize(de)?;
|
|
match val {
|
|
None => Ok(None),
|
|
Some(serde_json::Value::String(s)) => Ok(Some(s)),
|
|
Some(serde_json::Value::Number(n)) => Ok(Some(n.to_string())),
|
|
_ => Err(de::Error::custom("timestamp must be string or number")),
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct BatchRequest {
|
|
pub events: Vec<AnalyticsEvent>,
|
|
}
|
|
|
|
/// 接收小程序批量埋点事件。
|
|
/// 当前为日志记录模式 — 后续可接入 ClickHouse/PostgreSQL 分析表。
|
|
pub async fn batch(
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Json(req): Json<BatchRequest>,
|
|
) -> Result<Json<ApiResponse<()>>, AppError> {
|
|
require_permission(&ctx, "system.analytics.submit")?;
|
|
if req.events.len() > MAX_EVENTS_PER_BATCH {
|
|
return Err(AppError::Validation(format!(
|
|
"批量埋点事件数不能超过 {} 条",
|
|
MAX_EVENTS_PER_BATCH
|
|
)));
|
|
}
|
|
for evt in &req.events {
|
|
tracing::info!(
|
|
event = %evt.event,
|
|
page = ?evt.page,
|
|
properties = ?evt.properties,
|
|
"Analytics event received"
|
|
);
|
|
}
|
|
Ok(Json(ApiResponse::ok(())))
|
|
}
|