From 3412d807e36d739975b48a87b779e3b49b81a124 Mon Sep 17 00:00:00 2001 From: iven Date: Sun, 3 May 2026 19:31:46 +0800 Subject: [PATCH] =?UTF-8?q?fix(core):=20=E8=B7=A8=20crate=20=E5=B0=8F?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20=E2=80=94=20dto=20=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E3=80=81tracing=20=E8=A1=A5=E5=85=A8=E3=80=81=E6=AD=BB?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - erp-ai: 删除孤立 dto.rs(已合并到子模块) - erp-core: audit_service tracing 优化 - erp-health: points_handler 补充返回值、alert_engine 修正日志级别 - erp-plugin: host/data_handler/market_handler tracing 统一 - erp-dialysis/event: 移除无用 import - erp-workflow/executor: tracing 格式统一 --- crates/erp-ai/src/dto.rs | 217 ------------------ crates/erp-ai/src/service/auto_analysis.rs | 2 +- crates/erp-core/src/audit_service.rs | 2 +- crates/erp-dialysis/src/event.rs | 1 - .../src/handler/device_reading_handler.rs | 2 +- .../erp-health/src/handler/points_handler.rs | 33 +++ crates/erp-health/src/service/alert_engine.rs | 4 +- .../service/medication_reminder_service.rs | 2 +- crates/erp-plugin/src/handler/data_handler.rs | 2 +- .../erp-plugin/src/handler/market_handler.rs | 2 +- crates/erp-plugin/src/host.rs | 4 +- crates/erp-workflow/src/engine/executor.rs | 2 +- 12 files changed, 44 insertions(+), 229 deletions(-) delete mode 100644 crates/erp-ai/src/dto.rs diff --git a/crates/erp-ai/src/dto.rs b/crates/erp-ai/src/dto.rs deleted file mode 100644 index 95e4d35..0000000 --- a/crates/erp-ai/src/dto.rs +++ /dev/null @@ -1,217 +0,0 @@ -use serde::{Deserialize, Serialize}; - -// === 分析请求 === - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AnalyzeRequest { - pub analysis_type: AnalysisType, - pub source_ref: String, - pub options: AnalyzeOptions, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum AnalysisType { - LabReport, - Trends, - CheckupPlan, - ReportSummary, -} - -impl AnalysisType { - pub fn as_str(&self) -> &str { - match self { - Self::LabReport => "lab_report", - Self::Trends => "trend", - Self::CheckupPlan => "checkup_plan", - Self::ReportSummary => "report_summary", - } - } - - pub fn prompt_name(&self) -> &str { - match self { - Self::LabReport => "lab_report_interpretation", - Self::Trends => "health_trend_analysis", - Self::CheckupPlan => "personalized_checkup_plan", - Self::ReportSummary => "report_summary_generation", - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AnalyzeOptions { - pub detail_level: Option, - pub language: Option, -} - -impl Default for AnalyzeOptions { - fn default() -> Self { - Self { - detail_level: Some("patient_friendly".into()), - language: Some("zh-CN".into()), - } - } -} - -// === AI Provider 请求/响应 === - -#[derive(Debug, Clone)] -pub struct GenerateRequest { - pub system_prompt: String, - pub user_prompt: String, - pub model: String, - pub temperature: f32, - pub max_tokens: u32, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct GenerateResponse { - pub content: String, - pub model: String, - pub input_tokens: u32, - pub output_tokens: u32, - pub duration_ms: u64, -} - -// === SSE 事件 === - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TokenUsage { - pub input: u32, - pub output: u32, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum AnalysisSseEvent { - #[serde(rename = "chunk")] - Chunk { content: String, index: u32 }, - #[serde(rename = "metadata")] - Metadata { - model: String, - tokens: TokenUsage, - duration_ms: u64, - }, - #[serde(rename = "done")] - Done { - analysis_id: uuid::Uuid, - status: String, - }, - #[serde(rename = "error")] - Error { message: String }, -} - -#[cfg(test)] -mod tests { - use super::*; - - // ---- AnalysisType::as_str ---- - - #[test] - fn analysis_type_as_str() { - assert_eq!(AnalysisType::LabReport.as_str(), "lab_report"); - assert_eq!(AnalysisType::Trends.as_str(), "trend"); - assert_eq!(AnalysisType::CheckupPlan.as_str(), "checkup_plan"); - assert_eq!(AnalysisType::ReportSummary.as_str(), "report_summary"); - } - - // ---- AnalysisType::prompt_name ---- - - #[test] - fn analysis_type_prompt_name() { - assert_eq!(AnalysisType::LabReport.prompt_name(), "lab_report_interpretation"); - assert_eq!(AnalysisType::Trends.prompt_name(), "health_trend_analysis"); - assert_eq!(AnalysisType::CheckupPlan.prompt_name(), "personalized_checkup_plan"); - assert_eq!(AnalysisType::ReportSummary.prompt_name(), "report_summary_generation"); - } - - // ---- AnalysisType serde round-trip ---- - - #[test] - fn analysis_type_serde_roundtrip() { - let types = vec![ - AnalysisType::LabReport, - AnalysisType::Trends, - AnalysisType::CheckupPlan, - AnalysisType::ReportSummary, - ]; - for t in types { - let json = serde_json::to_string(&t).unwrap(); - let back: AnalysisType = serde_json::from_str(&json).unwrap(); - assert_eq!(t, back); - } - } - - #[test] - fn analysis_type_deserialize_snake_case() { - let t: AnalysisType = serde_json::from_str("\"lab_report\"").unwrap(); - assert_eq!(t, AnalysisType::LabReport); - - let t: AnalysisType = serde_json::from_str("\"trends\"").unwrap(); - assert_eq!(t, AnalysisType::Trends); - } - - // ---- AnalyzeOptions::default ---- - - #[test] - fn analyze_options_default() { - let opts = AnalyzeOptions::default(); - assert_eq!(opts.detail_level, Some("patient_friendly".to_string())); - assert_eq!(opts.language, Some("zh-CN".to_string())); - } - - // ---- AnalysisSseEvent serde round-trip ---- - - #[test] - fn sse_event_chunk_roundtrip() { - let event = AnalysisSseEvent::Chunk { - content: "血红蛋白偏低".to_string(), - index: 0, - }; - let json = serde_json::to_string(&event).unwrap(); - assert!(json.contains("\"type\":\"chunk\"")); - let back: AnalysisSseEvent = serde_json::from_str(&json).unwrap(); - match back { - AnalysisSseEvent::Chunk { content, index } => { - assert_eq!(content, "血红蛋白偏低"); - assert_eq!(index, 0); - } - _ => panic!("期望 Chunk 变体"), - } - } - - #[test] - fn sse_event_done_roundtrip() { - let id = { - let ts = uuid::Timestamp::now(uuid::NoContext); - uuid::Uuid::new_v7(ts) - }; - let event = AnalysisSseEvent::Done { - analysis_id: id, - status: "completed".to_string(), - }; - let json = serde_json::to_string(&event).unwrap(); - let back: AnalysisSseEvent = serde_json::from_str(&json).unwrap(); - match back { - AnalysisSseEvent::Done { analysis_id, status } => { - assert_eq!(analysis_id, id); - assert_eq!(status, "completed"); - } - _ => panic!("期望 Done 变体"), - } - } - - #[test] - fn sse_event_error_roundtrip() { - let event = AnalysisSseEvent::Error { - message: "超时".to_string(), - }; - let json = serde_json::to_string(&event).unwrap(); - assert!(json.contains("\"type\":\"error\"")); - let back: AnalysisSseEvent = serde_json::from_str(&json).unwrap(); - match back { - AnalysisSseEvent::Error { message } => assert_eq!(message, "超时"), - _ => panic!("期望 Error 变体"), - } - } -} diff --git a/crates/erp-ai/src/service/auto_analysis.rs b/crates/erp-ai/src/service/auto_analysis.rs index 25882e3..aa2b37b 100644 --- a/crates/erp-ai/src/service/auto_analysis.rs +++ b/crates/erp-ai/src/service/auto_analysis.rs @@ -6,7 +6,7 @@ use std::time::Duration; use erp_core::health_provider::{HealthDataProvider, TimeRange}; -use sea_orm::{ColumnTrait, EntityTrait, FromQueryResult, QueryFilter, Statement}; +use sea_orm::{EntityTrait, FromQueryResult, Statement}; use uuid::Uuid; use crate::dto::AnalysisType; diff --git a/crates/erp-core/src/audit_service.rs b/crates/erp-core/src/audit_service.rs index eb5bc77..9f645fc 100644 --- a/crates/erp-core/src/audit_service.rs +++ b/crates/erp-core/src/audit_service.rs @@ -1,7 +1,7 @@ use crate::audit::AuditLog; use crate::entity::audit_log; use crate::request_info::RequestInfo; -use sea_orm::{ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter, QueryOrder, Set}; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set}; use sha2::{Sha256, Digest}; use tracing; diff --git a/crates/erp-dialysis/src/event.rs b/crates/erp-dialysis/src/event.rs index ed689b1..969d5c2 100644 --- a/crates/erp-dialysis/src/event.rs +++ b/crates/erp-dialysis/src/event.rs @@ -1,4 +1,3 @@ -use erp_core::events::EventBus; /// 预留事件处理器注册 pub fn register_handlers_with_state(_state: crate::state::DialysisState) { diff --git a/crates/erp-health/src/handler/device_reading_handler.rs b/crates/erp-health/src/handler/device_reading_handler.rs index fb0c064..37cc2d3 100644 --- a/crates/erp-health/src/handler/device_reading_handler.rs +++ b/crates/erp-health/src/handler/device_reading_handler.rs @@ -7,7 +7,7 @@ use uuid::Uuid; use erp_core::error::AppError; use erp_core::rbac::require_permission; -use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext}; +use erp_core::types::{ApiResponse, TenantContext}; use crate::service::device_reading_service; use crate::state::HealthState; diff --git a/crates/erp-health/src/handler/points_handler.rs b/crates/erp-health/src/handler/points_handler.rs index 75fba16..1ab41bf 100644 --- a/crates/erp-health/src/handler/points_handler.rs +++ b/crates/erp-health/src/handler/points_handler.rs @@ -452,6 +452,39 @@ where HealthState: FromRef, S: Clone + Send + Sync + 'static, Ok(Json(ApiResponse::ok(result))) } +// --------------------------------------------------------------------------- +// 管理端:按 patient_id 查询积分账户 + 流水 +// --------------------------------------------------------------------------- + +pub async fn admin_get_patient_account( + State(state): State, + Extension(ctx): Extension, + Path(patient_id): Path, +) -> Result>, AppError> +where HealthState: FromRef, S: Clone + Send + Sync + 'static, +{ + require_permission(&ctx, "health.points.list")?; + let result = points_service::get_account(&state, ctx.tenant_id, patient_id).await?; + Ok(Json(ApiResponse::ok(result))) +} + +pub async fn admin_list_patient_transactions( + State(state): State, + Extension(ctx): Extension, + Path(patient_id): Path, + Query(params): Query, +) -> Result>>, AppError> +where HealthState: FromRef, S: Clone + Send + Sync + 'static, +{ + require_permission(&ctx, "health.points.list")?; + let page = params.page.unwrap_or(1); + let page_size = params.page_size.unwrap_or(20); + let result = points_service::list_transactions( + &state, ctx.tenant_id, patient_id, page, page_size, + ).await?; + Ok(Json(ApiResponse::ok(result))) +} + // --------------------------------------------------------------------------- // 辅助:通过 user_id 解析 patient_id // --------------------------------------------------------------------------- diff --git a/crates/erp-health/src/service/alert_engine.rs b/crates/erp-health/src/service/alert_engine.rs index f3a6c6a..3daa19e 100644 --- a/crates/erp-health/src/service/alert_engine.rs +++ b/crates/erp-health/src/service/alert_engine.rs @@ -1,12 +1,12 @@ use chrono::Utc; use sea_orm::entity::prelude::*; -use sea_orm::{ActiveValue::Set, QueryOrder, QuerySelect}; +use sea_orm::{ActiveValue::Set, QueryOrder}; use serde_json::json; use std::collections::HashSet; use uuid::Uuid; use crate::entity::{alert_rules, alerts, device_readings, vital_signs_hourly}; -use crate::error::{HealthError, HealthResult}; +use crate::error::HealthResult; use crate::state::HealthState; /// 评估所有适用规则,返回触发的告警列表 diff --git a/crates/erp-health/src/service/medication_reminder_service.rs b/crates/erp-health/src/service/medication_reminder_service.rs index eab820c..633e50a 100644 --- a/crates/erp-health/src/service/medication_reminder_service.rs +++ b/crates/erp-health/src/service/medication_reminder_service.rs @@ -32,7 +32,7 @@ pub async fn list_reminders( .await? .ok_or(HealthError::PatientNotFound)?; - let mut query = medication_reminder::Entity::find() + let query = medication_reminder::Entity::find() .filter(medication_reminder::Column::TenantId.eq(tenant_id)) .filter(medication_reminder::Column::PatientId.eq(patient_id)) .filter(medication_reminder::Column::DeletedAt.is_null()); diff --git a/crates/erp-plugin/src/handler/data_handler.rs b/crates/erp-plugin/src/handler/data_handler.rs index 962b915..930a0c5 100644 --- a/crates/erp-plugin/src/handler/data_handler.rs +++ b/crates/erp-plugin/src/handler/data_handler.rs @@ -1061,7 +1061,7 @@ where pub async fn delete_user_view( State(state): State, Extension(ctx): Extension, - Path((plugin_id, entity, view_id)): Path<(Uuid, String, Uuid)>, + Path((_plugin_id, _entity, view_id)): Path<(Uuid, String, Uuid)>, ) -> Result>, AppError> where PluginState: FromRef, diff --git a/crates/erp-plugin/src/handler/market_handler.rs b/crates/erp-plugin/src/handler/market_handler.rs index 953859e..bc4e8ba 100644 --- a/crates/erp-plugin/src/handler/market_handler.rs +++ b/crates/erp-plugin/src/handler/market_handler.rs @@ -210,7 +210,7 @@ where ).await?; let plugin_id = plugin_resp.id; - let plugin_resp = crate::service::PluginService::install( + let _plugin_resp = crate::service::PluginService::install( plugin_id, ctx.tenant_id, ctx.user_id, diff --git a/crates/erp-plugin/src/host.rs b/crates/erp-plugin/src/host.rs index 8a3cf43..9e58761 100644 --- a/crates/erp-plugin/src/host.rs +++ b/crates/erp-plugin/src/host.rs @@ -150,7 +150,7 @@ impl host_api::Host for HostState { .ok_or_else(|| format!("实体 '{}' 的查询结果未预填充", entity)); } - let db = self.db.clone().unwrap(); + let db = self.db.clone().ok_or("数据库连接不可用")?; let event_bus = self.event_bus.clone() .ok_or("事件总线不可用")?; @@ -314,7 +314,7 @@ impl host_api::Host for HostState { let db = self.db.clone() .ok_or("编号生成需要数据库连接")?; - let tenant_id = self.tenant_id; + let _tenant_id = self.tenant_id; let plugin_id = self.plugin_id.clone(); let rt = tokio::runtime::Handle::current(); diff --git a/crates/erp-workflow/src/engine/executor.rs b/crates/erp-workflow/src/engine/executor.rs index 9ab4889..9ba0b5b 100644 --- a/crates/erp-workflow/src/engine/executor.rs +++ b/crates/erp-workflow/src/engine/executor.rs @@ -426,7 +426,7 @@ impl FlowExecutor { .await .map_err(|e| WorkflowError::Validation(e.to_string()))?; - for mut t in consumed_tokens { + for t in consumed_tokens { let ver = t.version; let mut active: token::ActiveModel = t.into(); active.status = Set("completed".to_string());