//! 设备日志 API — 分页列表查询 use axum::extract::{Query, State}; use axum::Json; use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use crate::error::AppError; use crate::middleware::auth::{self, CurrentUser}; /// 设备日志查询参数 #[derive(Debug, Deserialize)] pub struct DeviceLogQuery { pub cabinet_id: Option, pub log_type: Option, pub start_time: Option, pub end_time: Option, #[serde(default = "default_page")] pub page: i64, #[serde(default = "default_page_size")] pub page_size: i64, } /// 设备日志列表项 #[derive(Debug, Serialize)] pub struct DeviceLogItem { pub id: i64, pub cabinet_id: i64, pub cabinet_name: Option, pub log_type: i8, pub log_type_text: String, pub content: Option, pub created_at: Option, } fn default_page() -> i64 { 1 } fn default_page_size() -> i64 { 20 } /// 日志类型码转中文 fn log_type_text(log_type: i8) -> &'static str { match log_type { 1 => "状态上报", 2 => "故障", 3 => "告警", 4 => "停电", _ => "未知", } } /// 数据库行映射 #[derive(Debug, sqlx::FromRow)] struct DeviceLogRow { id: i64, cabinet_id: i64, cabinet_name: Option, log_type: i8, content: Option, created_at: Option, } /// GET /api/device-logs — 设备日志分页列表 pub async fn list_device_logs( user: CurrentUser, State(state): State, Query(params): Query, ) -> Result, AppError> { auth::check_permission(&user, "device_log:view")?; let offset = (params.page - 1) * params.page_size; // 构建 WHERE 条件 let mut conditions = Vec::new(); let mut bind_values: Vec = Vec::new(); if let Some(cabinet_id) = params.cabinet_id { conditions.push("dl.cabinet_id = ?"); bind_values.push(cabinet_id.to_string()); } if let Some(log_type) = params.log_type { conditions.push("dl.log_type = ?"); bind_values.push(log_type.to_string()); } if let Some(ref start_time) = params.start_time { conditions.push("dl.created_at >= ?"); bind_values.push(start_time.clone()); } if let Some(ref end_time) = params.end_time { conditions.push("dl.created_at <= ?"); bind_values.push(end_time.clone()); } // 追加组织数据隔离 let (org_cond, org_binds) = auth::org_condition(&user, "dl"); if !org_cond.is_empty() { conditions.push(org_cond.trim_start_matches(" AND ")); bind_values.extend(org_binds); } let where_clause = if conditions.is_empty() { String::new() } else { format!("WHERE {}", conditions.join(" AND ")) }; // 查询总数 let count_sql = format!( "SELECT COUNT(*) FROM device_logs dl {}", where_clause ); let mut count_query = sqlx::query_scalar::<_, i64>(&count_sql); for val in &bind_values { count_query = count_query.bind(val); } let total = count_query.fetch_one(&state.mysql).await?; // 查询列表 let list_sql = format!( "SELECT dl.id, dl.cabinet_id, c.name AS cabinet_name, \ dl.log_type, dl.content, dl.created_at \ FROM device_logs dl \ LEFT JOIN cabinets c ON dl.cabinet_id = c.id \ {} ORDER BY dl.created_at DESC LIMIT ? OFFSET ?", where_clause ); let mut list_query = sqlx::query_as::<_, DeviceLogRow>(&list_sql); for val in &bind_values { list_query = list_query.bind(val); } list_query = list_query.bind(params.page_size).bind(offset); let rows = list_query.fetch_all(&state.mysql).await?; let list: Vec = rows .into_iter() .map(|r| DeviceLogItem { id: r.id, cabinet_id: r.cabinet_id, cabinet_name: r.cabinet_name, log_type: r.log_type, log_type_text: log_type_text(r.log_type).to_string(), content: r.content, created_at: r.created_at.map(|t| t.to_string()), }) .collect(); Ok(Json(json!({ "code": 0, "data": { "list": list, "total": total, "page": params.page, "page_size": params.page_size, }, "message": "ok" }))) }