diff --git a/src/connection.rs b/src/connection.rs index 9a980f8..ef8b34b 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -18,9 +18,9 @@ use std::{ use tokio::task::JoinHandle; use tokio::sync::RwLock; use uuid::Uuid; +use plc_platform_core::model::{PointSubscriptionInfo, ScanMode}; use crate::{ - model::{PointSubscriptionInfo, ScanMode}, telemetry::PointMonitorInfo, }; @@ -1057,7 +1057,7 @@ impl ConnectionManager { quality: crate::telemetry::PointQuality::Good, protocol: "opcua".to_string(), timestamp: Some(Utc::now()), - scan_mode: crate::model::ScanMode::Poll, + scan_mode: ScanMode::Poll, }, )) { tracing::warn!( diff --git a/src/control/engine.rs b/src/control/engine.rs index e0b506c..883e78a 100644 --- a/src/control/engine.rs +++ b/src/control/engine.rs @@ -138,7 +138,10 @@ async fn unit_task(state: AppState, store: Arc, unit_id: Uu // Wait run_time_sec. run_time_sec == 0 means run without a time limit // (relies on acc_time_sec to eventually stop). Treat as a very long phase. let secs = if unit.run_time_sec > 0 { unit.run_time_sec } else { i32::MAX }; - let unit_for_wait = crate::model::ControlUnit { run_time_sec: secs, ..unit.clone() }; + let unit_for_wait = plc_platform_core::model::ControlUnit { + run_time_sec: secs, + ..unit.clone() + }; if !wait_phase(&state, &store, &unit_for_wait, &all_roles, ¬ify, &mut fault_tick).await { continue; } @@ -228,7 +231,7 @@ async fn unit_task(state: AppState, store: Arc, unit_id: Uu async fn wait_phase( state: &AppState, store: &ControlRuntimeStore, - unit: &crate::model::ControlUnit, + unit: &plc_platform_core::model::ControlUnit, all_roles: &[(Uuid, HashMap)], notify: &Arc, fault_tick: &mut tokio::time::Interval, @@ -279,7 +282,7 @@ async fn push_ws(state: &AppState, runtime: &UnitRuntime) { async fn check_fault_comm( state: &AppState, runtime: &mut UnitRuntime, - unit: &crate::model::ControlUnit, + unit: &plc_platform_core::model::ControlUnit, all_roles: &[(Uuid, HashMap)], ) -> bool { let monitor = state @@ -431,7 +434,7 @@ async fn load_equipment_maps(state: &AppState, unit_id: Uuid) -> Result>, ) -> EquipMaps { let mut kind_roles: HashMap> = HashMap::new(); @@ -496,7 +499,7 @@ fn find_cmd( #[cfg(test)] mod tests { use super::build_equipment_maps; - use crate::model::Equipment; + use plc_platform_core::model::Equipment; use crate::service::EquipmentRolePoint; use chrono::Utc; use std::collections::HashMap; diff --git a/src/control/simulate.rs b/src/control/simulate.rs index fdebdd6..61129c3 100644 --- a/src/control/simulate.rs +++ b/src/control/simulate.rs @@ -170,7 +170,7 @@ pub async fn patch_signal(state: &AppState, point_id: Uuid, value_on: bool) { source_id: Uuid::nil(), point_id, client_handle: 0, - scan_mode: crate::model::ScanMode::Poll, + scan_mode: plc_platform_core::model::ScanMode::Poll, timestamp: Some(chrono::Utc::now()), quality: PointQuality::Good, value: Some(value), diff --git a/src/event.rs b/src/event.rs index bc8c4c4..ccd7cb0 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use tokio::sync::mpsc; use uuid::Uuid; +use plc_platform_core::model::EventRecord; const CONTROL_EVENT_CHANNEL_CAPACITY: usize = 1024; const TELEMETRY_EVENT_CHANNEL_CAPACITY: usize = 4096; @@ -448,7 +449,7 @@ async fn persist_event_if_needed( return; }; - let inserted = sqlx::query_as::<_, crate::model::EventRecord>( + let inserted = sqlx::query_as::<_, EventRecord>( r#" INSERT INTO event (event_type, level, unit_id, equipment_id, source_id, message, payload) VALUES ($1, $2, $3, $4, $5, $6, $7) diff --git a/src/handler/control.rs b/src/handler/control.rs index ab85448..4cd8b94 100644 --- a/src/handler/control.rs +++ b/src/handler/control.rs @@ -50,14 +50,14 @@ pub struct GetUnitListQuery { #[derive(serde::Serialize)] pub struct UnitEquipmentItem { #[serde(flatten)] - pub equipment: crate::model::Equipment, + pub equipment: plc_platform_core::model::Equipment, pub role_points: Vec, } #[derive(serde::Serialize)] pub struct UnitWithRuntime { #[serde(flatten)] - pub unit: crate::model::ControlUnit, + pub unit: plc_platform_core::model::ControlUnit, pub runtime: Option, pub equipments: Vec, } @@ -250,21 +250,21 @@ pub async fn get_unit( #[derive(serde::Serialize)] pub struct PointDetail { #[serde(flatten)] - pub point: crate::model::Point, + pub point: plc_platform_core::model::Point, pub point_monitor: Option, } #[derive(serde::Serialize)] pub struct EquipmentDetail { #[serde(flatten)] - pub equipment: crate::model::Equipment, + pub equipment: plc_platform_core::model::Equipment, pub points: Vec, } #[derive(serde::Serialize)] pub struct UnitDetail { #[serde(flatten)] - pub unit: crate::model::ControlUnit, + pub unit: plc_platform_core::model::ControlUnit, pub runtime: Option, pub equipments: Vec, } diff --git a/src/handler/doc.rs b/src/handler/doc.rs index 4f7c44e..49429f9 100644 --- a/src/handler/doc.rs +++ b/src/handler/doc.rs @@ -3,7 +3,7 @@ use axum::{ response::IntoResponse, }; -use crate::util::response::ApiErr; +use plc_platform_core::util::response::ApiErr; pub async fn get_api_md() -> Result { let content = tokio::fs::read_to_string("API.md") diff --git a/src/handler/equipment.rs b/src/handler/equipment.rs index c2472d9..7ad5232 100644 --- a/src/handler/equipment.rs +++ b/src/handler/equipment.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; use validator::Validate; -use crate::util::{ +use plc_platform_core::util::{ pagination::{PaginatedResponse, PaginationParams}, response::ApiErr, }; @@ -44,7 +44,7 @@ pub struct SignalRolePoint { #[derive(Serialize)] pub struct EquipmentListItem { #[serde(flatten)] - pub equipment: crate::model::Equipment, + pub equipment: plc_platform_core::model::Equipment, pub point_count: i64, pub role_points: Vec, } diff --git a/src/handler/log.rs b/src/handler/log.rs index f697e83..a08eaa0 100644 --- a/src/handler/log.rs +++ b/src/handler/log.rs @@ -20,7 +20,7 @@ use tokio::{ time::{Duration, interval}, }; -use crate::util::response::ApiErr; +use plc_platform_core::util::response::ApiErr; const LOG_DIR: &str = "./logs"; const DEFAULT_TAIL_LINES: usize = 200; diff --git a/src/handler/page.rs b/src/handler/page.rs index b9a89fe..0aa37fa 100644 --- a/src/handler/page.rs +++ b/src/handler/page.rs @@ -5,8 +5,8 @@ use sqlx::types::Json as SqlxJson; use uuid::Uuid; use validator::Validate; -use crate::model::Page; -use crate::util::response::ApiErr; +use plc_platform_core::model::Page; +use plc_platform_core::util::response::ApiErr; use crate::AppState; #[derive(Deserialize, Validate)] diff --git a/src/handler/point.rs b/src/handler/point.rs index 6f5e219..ed3b469 100644 --- a/src/handler/point.rs +++ b/src/handler/point.rs @@ -11,7 +11,7 @@ use std::collections::{HashMap, HashSet}; use uuid::Uuid; use validator::Validate; -use crate::util::{ +use plc_platform_core::util::{ pagination::{PaginatedResponse, PaginationParams}, response::ApiErr, }; @@ -56,7 +56,7 @@ pub struct GetPointHistoryQuery { #[derive(Serialize)] pub struct PointHistoryItem { - #[serde(serialize_with = "crate::util::datetime::option_utc_to_local_str")] + #[serde(serialize_with = "plc_platform_core::util::datetime::option_utc_to_local_str")] pub timestamp: Option>, pub quality: crate::telemetry::PointQuality, pub value: Option, diff --git a/src/handler/source.rs b/src/handler/source.rs index 52bae8f..630a98d 100644 --- a/src/handler/source.rs +++ b/src/handler/source.rs @@ -11,7 +11,7 @@ use opcua::types::ReferenceTypeId; use opcua::client::Session; use std::collections::{HashMap, VecDeque}; -use crate::util::response::ApiErr; +use plc_platform_core::util::response::ApiErr; use crate::{AppState, model::{Node, Source}}; use anyhow::{Context}; @@ -61,7 +61,7 @@ pub struct SourceWithStatus { pub source: SourcePublic, pub is_connected: bool, pub last_error: Option, - #[serde(serialize_with = "crate::util::datetime::option_utc_to_local_str")] + #[serde(serialize_with = "plc_platform_core::util::datetime::option_utc_to_local_str")] pub last_time: Option>, } @@ -74,9 +74,9 @@ pub struct SourcePublic { pub security_policy: Option, pub security_mode: Option, pub enabled: bool, - #[serde(serialize_with = "crate::util::datetime::utc_to_local_str")] + #[serde(serialize_with = "plc_platform_core::util::datetime::utc_to_local_str")] pub created_at: DateTime, - #[serde(serialize_with = "crate::util::datetime::utc_to_local_str")] + #[serde(serialize_with = "plc_platform_core::util::datetime::utc_to_local_str")] pub updated_at: DateTime, } diff --git a/src/handler/tag.rs b/src/handler/tag.rs index 65e3d4a..168f945 100644 --- a/src/handler/tag.rs +++ b/src/handler/tag.rs @@ -3,7 +3,10 @@ use serde::Deserialize; use uuid::Uuid; use validator::Validate; -use crate::util::{response::ApiErr, pagination::{PaginatedResponse, PaginationParams}}; +use plc_platform_core::util::{ + pagination::{PaginatedResponse, PaginationParams}, + response::ApiErr, +}; use crate::{AppState}; /// 获取所有标签 diff --git a/src/main.rs b/src/main.rs index f8a2047..3ff7f87 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,10 +7,8 @@ mod db; mod event; mod handler; mod middleware; -mod model; mod service; mod telemetry; -mod util; mod websocket; use axum::{ routing::{get, post, put}, @@ -48,8 +46,9 @@ pub struct AppState { #[tokio::main] async fn main() { dotenv::dotenv().ok(); - util::log::init_logger(); - let _single_instance = match util::single_instance::try_acquire("PLCControl.Gateway") { + plc_platform_core::util::log::init_logger(); + let _single_instance = + match plc_platform_core::util::single_instance::try_acquire("PLCControl.Gateway") { Ok(guard) => guard, Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => { tracing::warn!("Another PLC Control instance is already running"); diff --git a/src/model.rs b/src/model.rs deleted file mode 100644 index cc7c97f..0000000 --- a/src/model.rs +++ /dev/null @@ -1,178 +0,0 @@ -use crate::util::datetime::utc_to_local_str; -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use sqlx::types::Json; -use sqlx::FromRow; -use std::collections::HashMap; -use uuid::Uuid; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "lowercase")] -pub enum ScanMode { - Poll, - Subscribe, -} - -impl ScanMode { - pub fn as_str(&self) -> &'static str { - match self { - ScanMode::Poll => "poll", - ScanMode::Subscribe => "subscribe", - } - } -} - -impl From for String { - fn from(mode: ScanMode) -> Self { - mode.as_str().to_string() - } -} - -impl std::fmt::Display for ScanMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.as_str()) - } -} - -impl std::str::FromStr for ScanMode { - type Err = String; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "poll" => Ok(ScanMode::Poll), - "subscribe" => Ok(ScanMode::Subscribe), - _ => Err(format!("Invalid scan mode: {}", s)), - } - } -} - -#[derive(Debug, Serialize, Deserialize, FromRow, Clone)] -pub struct Source { - pub id: Uuid, - pub name: String, - pub protocol: String, // opcua, modbus - pub endpoint: String, - pub security_policy: Option, - pub security_mode: Option, - pub username: Option, - pub password: Option, - pub enabled: bool, - #[serde(serialize_with = "utc_to_local_str")] - pub created_at: DateTime, - #[serde(serialize_with = "utc_to_local_str")] - pub updated_at: DateTime, -} - -#[derive(Debug, Serialize, Deserialize, FromRow)] -#[allow(dead_code)] -pub struct Node { - pub id: Uuid, - pub source_id: Uuid, - pub external_id: String, // ns=2;s=Temperature - - // comment fixed - pub namespace_uri: Option, - pub namespace_index: Option, - pub identifier_type: Option, // i/s/g/b - pub identifier: Option, - - pub browse_name: String, - pub display_name: Option, - pub node_class: String, // Object/Variable/Method coil/input topic - pub parent_id: Option, - #[serde(serialize_with = "utc_to_local_str")] - pub created_at: DateTime, - #[serde(serialize_with = "utc_to_local_str")] - pub updated_at: DateTime, -} - -#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] -#[allow(dead_code)] -pub struct Point { - pub id: Uuid, - pub node_id: Uuid, - pub name: String, - pub description: Option, - pub unit: Option, - pub tag_id: Option, - pub equipment_id: Option, - pub signal_role: Option, - #[serde(serialize_with = "utc_to_local_str")] - pub created_at: DateTime, - #[serde(serialize_with = "utc_to_local_str")] - pub updated_at: DateTime, -} - -#[derive(Debug, Clone)] -pub struct PointSubscriptionInfo { - pub point_id: Uuid, - pub external_id: String, -} - -#[derive(Debug, Serialize, Deserialize, FromRow, Clone)] -pub struct Tag { - pub id: Uuid, - pub name: String, - pub description: Option, - #[serde(serialize_with = "utc_to_local_str")] - pub created_at: DateTime, - #[serde(serialize_with = "utc_to_local_str")] - pub updated_at: DateTime, -} - -#[derive(Debug, Serialize, Deserialize, FromRow, Clone)] -pub struct Equipment { - pub id: Uuid, - pub unit_id: Option, - pub code: String, - pub name: String, - pub kind: Option, - pub description: Option, - #[serde(serialize_with = "utc_to_local_str")] - pub created_at: DateTime, - #[serde(serialize_with = "utc_to_local_str")] - pub updated_at: DateTime, -} - -#[derive(Debug, Serialize, Deserialize, FromRow, Clone)] -pub struct ControlUnit { - pub id: Uuid, - pub code: String, - pub name: String, - pub description: Option, - pub enabled: bool, - pub run_time_sec: i32, - pub stop_time_sec: i32, - pub acc_time_sec: i32, - pub bl_time_sec: i32, - pub require_manual_ack_after_fault: bool, - #[serde(serialize_with = "utc_to_local_str")] - pub created_at: DateTime, - #[serde(serialize_with = "utc_to_local_str")] - pub updated_at: DateTime, -} - -#[derive(Debug, Serialize, Deserialize, FromRow, Clone)] -pub struct EventRecord { - pub id: Uuid, - pub event_type: String, - pub level: String, - pub unit_id: Option, - pub equipment_id: Option, - pub source_id: Option, - pub message: String, - pub payload: Option>, - #[serde(serialize_with = "utc_to_local_str")] - pub created_at: DateTime, -} - -#[derive(Debug, Serialize, Deserialize, FromRow, Clone)] -pub struct Page { - pub id: Uuid, - pub name: String, - pub data: Json>, - #[serde(serialize_with = "utc_to_local_str")] - pub created_at: DateTime, - #[serde(serialize_with = "utc_to_local_str")] - pub updated_at: DateTime, -} diff --git a/src/service/control.rs b/src/service/control.rs index f0266f6..6666636 100644 --- a/src/service/control.rs +++ b/src/service/control.rs @@ -1,4 +1,4 @@ -use crate::model::{ControlUnit, EventRecord}; +use plc_platform_core::model::{ControlUnit, EventRecord}; use sqlx::{PgPool, QueryBuilder, Row}; use uuid::Uuid; @@ -337,7 +337,7 @@ pub async fn get_all_enabled_units(pool: &PgPool) -> Result, sq pub async fn get_equipment_by_unit_ids( pool: &PgPool, unit_ids: &[Uuid], -) -> Result, sqlx::Error> { +) -> Result, sqlx::Error> { if unit_ids.is_empty() { return Ok(vec![]); } @@ -345,7 +345,7 @@ pub async fn get_equipment_by_unit_ids( "SELECT * FROM equipment WHERE unit_id = ANY($1) ORDER BY {}", equipment_order_clause_with_unit() ); - sqlx::query_as::<_, crate::model::Equipment>(&sql) + sqlx::query_as::<_, plc_platform_core::model::Equipment>(&sql) .bind(unit_ids) .fetch_all(pool) .await @@ -354,12 +354,12 @@ pub async fn get_equipment_by_unit_ids( pub async fn get_equipment_by_unit_id( pool: &PgPool, unit_id: Uuid, -) -> Result, sqlx::Error> { +) -> Result, sqlx::Error> { let sql = format!( "SELECT * FROM equipment WHERE unit_id = $1 ORDER BY {}", unit_order_clause() ); - sqlx::query_as::<_, crate::model::Equipment>(&sql) + sqlx::query_as::<_, plc_platform_core::model::Equipment>(&sql) .bind(unit_id) .fetch_all(pool) .await @@ -368,11 +368,11 @@ pub async fn get_equipment_by_unit_id( pub async fn get_points_by_equipment_ids( pool: &PgPool, equipment_ids: &[Uuid], -) -> Result, sqlx::Error> { +) -> Result, sqlx::Error> { if equipment_ids.is_empty() { return Ok(vec![]); } - sqlx::query_as::<_, crate::model::Point>( + sqlx::query_as::<_, plc_platform_core::model::Point>( r#"SELECT * FROM point WHERE equipment_id = ANY($1) ORDER BY equipment_id, created_at"#, ) .bind(equipment_ids) diff --git a/src/service/point.rs b/src/service/point.rs index f3e7de1..44d253b 100644 --- a/src/service/point.rs +++ b/src/service/point.rs @@ -1,4 +1,4 @@ -use crate::model::{Point, PointSubscriptionInfo}; +use plc_platform_core::model::{Point, PointSubscriptionInfo}; use sqlx::{query_as, PgPool, Row}; use std::collections::HashMap; diff --git a/src/service/source.rs b/src/service/source.rs index 5a8ef91..43b5972 100644 --- a/src/service/source.rs +++ b/src/service/source.rs @@ -1,4 +1,4 @@ -use crate::model::Source; +use plc_platform_core::model::Source; use sqlx::{query_as, PgPool}; pub async fn get_enabled_source( diff --git a/src/service/tag.rs b/src/service/tag.rs index aa08e67..9094177 100644 --- a/src/service/tag.rs +++ b/src/service/tag.rs @@ -1,4 +1,4 @@ -use crate::model::{Point, Tag}; +use plc_platform_core::model::{Point, Tag}; use sqlx::{query_as, PgPool}; pub async fn get_tags_count(pool: &PgPool) -> Result { diff --git a/src/telemetry.rs b/src/telemetry.rs index 0968fd7..ce5a2ab 100644 --- a/src/telemetry.rs +++ b/src/telemetry.rs @@ -1,9 +1,8 @@ use chrono::{DateTime, Utc}; +use plc_platform_core::model::ScanMode; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::model::ScanMode; - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ValueType { @@ -80,14 +79,14 @@ pub struct PointMonitorInfo { pub point_id: Uuid, pub client_handle: u32, pub scan_mode: ScanMode, - #[serde(serialize_with = "crate::util::datetime::option_utc_to_local_str")] + #[serde(serialize_with = "plc_platform_core::util::datetime::option_utc_to_local_str")] pub timestamp: Option>, pub quality: PointQuality, pub value: Option, pub value_type: Option, pub value_text: Option, pub old_value: Option, - #[serde(serialize_with = "crate::util::datetime::option_utc_to_local_str")] + #[serde(serialize_with = "plc_platform_core::util::datetime::option_utc_to_local_str")] pub old_timestamp: Option>, pub value_changed: bool, } diff --git a/src/util.rs b/src/util.rs deleted file mode 100644 index edb3ea5..0000000 --- a/src/util.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod datetime; -pub mod log; -pub mod pagination; -pub mod response; -pub mod single_instance; -pub mod validator; diff --git a/src/util/datetime.rs b/src/util/datetime.rs deleted file mode 100644 index 20ec5ce..0000000 --- a/src/util/datetime.rs +++ /dev/null @@ -1,24 +0,0 @@ -use chrono::{DateTime, Local, Utc}; -use serde::Serializer; - -pub fn utc_to_local_string(date: &DateTime) -> String { - date.with_timezone(&Local).format("%Y-%m-%d %H:%M:%S%.3f").to_string() -} - -pub fn utc_to_local_str(date: &DateTime, serializer: S) -> Result -where - S: Serializer, -{ - let formatted = utc_to_local_string(date); - serializer.serialize_str(&formatted) -} - -pub fn option_utc_to_local_str(date: &Option>, serializer: S) -> Result -where - S: Serializer, -{ - match date { - Some(d) => utc_to_local_str(d, serializer), - None => serializer.serialize_none(), - } -} diff --git a/src/util/log.rs b/src/util/log.rs deleted file mode 100644 index 7ab9159..0000000 --- a/src/util/log.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::sync::OnceLock; -use tracing_subscriber::{fmt, prelude::*, EnvFilter}; -use tracing_appender::{rolling, non_blocking}; -use time::UtcOffset; - -static LOG_GUARD: OnceLock = OnceLock::new(); - -pub fn init_logger() { - std::fs::create_dir_all("./logs").ok(); - - let file_appender = rolling::daily("./logs", "app.log"); - let (file_writer, guard) = non_blocking(file_appender); - LOG_GUARD.set(guard).ok(); - let timer = fmt::time::OffsetTime::new( - UtcOffset::from_hms(8, 0, 0).unwrap(), - time::format_description::well_known::Rfc3339, - ); - - tracing_subscriber::registry() - .with(EnvFilter::from_default_env()) - .with( - fmt::layer() - .compact() - .with_timer(timer.clone()) - .with_writer(std::io::stdout), - ) - .with( - fmt::layer() - .json() - .with_timer(timer) - .with_writer(file_writer) - .with_ansi(false) - .with_current_span(false) - .with_span_list(false), - ) - .init(); -} diff --git a/src/util/pagination.rs b/src/util/pagination.rs deleted file mode 100644 index 16d884a..0000000 --- a/src/util/pagination.rs +++ /dev/null @@ -1,61 +0,0 @@ -use serde::{Deserialize, Serialize}; -use serde_with::serde_as; -use validator::Validate; - -/// 分页响应结构 -#[derive(Serialize)] -pub struct PaginatedResponse { - pub data: Vec, - pub total: i64, - pub page: u32, - pub page_size: i32, - pub total_pages: u32, -} - -impl PaginatedResponse { - /// 创建分页响应 - pub fn new(data: Vec, total: i64, page: u32, page_size: i32) -> Self { - let total_pages = if page_size > 0 { - ((total as f64) / (page_size as f64)).ceil() as u32 - } else { - 0 - }; - - Self { - data, - total, - page, - page_size, - total_pages, - } - } -} - -/// 分页查询参数 -#[serde_as] -#[derive(Debug, Deserialize, Validate)] -pub struct PaginationParams { - #[validate(range(min = 1))] - #[serde_as(as = "serde_with::DisplayFromStr")] - #[serde(default = "default_page")] - pub page: u32, - #[validate(range(min = -1, max = 100))] - #[serde_as(as = "serde_with::DisplayFromStr")] - #[serde(default = "default_page_size")] - pub page_size: i32, -} - -fn default_page() -> u32 { - 1 -} - -fn default_page_size() -> i32 { - 20 -} - -impl PaginationParams { - /// 计算偏移量 - pub fn offset(&self) -> u32 { - (self.page - 1) * self.page_size.max(0) as u32 - } -} diff --git a/src/util/response.rs b/src/util/response.rs deleted file mode 100644 index ab59fe8..0000000 --- a/src/util/response.rs +++ /dev/null @@ -1,144 +0,0 @@ -use anyhow::Error; -use axum::{ - Json, - http::StatusCode, - response::IntoResponse, - extract::rejection::{ - QueryRejection, - PathRejection, - JsonRejection, - FormRejection, - }, -}; -use serde::Serialize; -use serde_json::Value; -use sqlx::Error as SqlxError; - -#[derive(Debug, Serialize)] -pub struct ErrResp { - pub err_code: i32, - pub err_msg: String, - pub err_detail: Option, -} - -impl ErrResp { - pub fn new(err_code: i32, err_msg: impl Into, detail: Option) -> Self { - Self { - err_code, - err_msg: err_msg.into(), - err_detail: detail, - } - } -} - -#[derive(Debug)] -#[allow(dead_code)] -pub enum ApiErr { - Unauthorized(String, Option), - Forbidden(String, Option), - BadRequest(String, Option), - NotFound(String, Option), - Internal(String, Option), -} - -impl IntoResponse for ApiErr { - fn into_response(self) -> axum::response::Response { - match self { - ApiErr::Unauthorized(msg, detail) => { - (StatusCode::UNAUTHORIZED, Json(ErrResp::new(401, msg, detail))).into_response() - } - ApiErr::Forbidden(msg, detail) => { - (StatusCode::FORBIDDEN, Json(ErrResp::new(403, msg, detail))).into_response() - } - ApiErr::BadRequest(msg, detail) => { - (StatusCode::BAD_REQUEST, Json(ErrResp::new(400, msg, detail))).into_response() - } - ApiErr::NotFound(msg, detail) => { - (StatusCode::NOT_FOUND, Json(ErrResp::new(404, msg, detail))).into_response() - } - ApiErr::Internal(msg, detail) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ErrResp::new(500, msg, detail)), - ) - .into_response(), - } - } -} - -impl From for ApiErr { - fn from(err: Error) -> Self { - tracing::error!("Error: {:?}; root_cause: {}", err, err.root_cause()); - ApiErr::Internal("internal server error".to_string(), None) - } -} - -impl From for ApiErr { - fn from(err: SqlxError) -> Self { - match err { - SqlxError::RowNotFound => { - ApiErr::NotFound("Resource not found".into(), None) - } - SqlxError::Database(db_err) => { - if db_err.code().as_deref() == Some("23505") { - ApiErr::BadRequest("数据已存在".into(), None) - } else { - tracing::error!("Database error: {}", db_err); - ApiErr::Internal("Database error".into(), None) - } - } - _ => { - tracing::error!("Database error: {}", err); - ApiErr::Internal("Database error".into(), None) - } - } - } -} - -impl From for ApiErr { - fn from(rejection: QueryRejection) -> Self { - tracing::warn!("Query parameter error: {}", rejection); - ApiErr::BadRequest( - "Invalid query parameters".to_string(), - Some(serde_json::json!({ - "detail": rejection.to_string() - })) - ) - } -} - -impl From for ApiErr { - fn from(rejection: PathRejection) -> Self { - tracing::warn!("Path parameter error: {}", rejection); - ApiErr::BadRequest( - "Invalid path parameter".to_string(), - Some(serde_json::json!({ - "detail": rejection.to_string() - })) - ) - } -} - -impl From for ApiErr { - fn from(rejection: JsonRejection) -> Self { - tracing::warn!("JSON parsing error: {}", rejection); - ApiErr::BadRequest( - "Invalid JSON format".to_string(), - Some(serde_json::json!({ - "detail": rejection.to_string() - })) - ) - } -} - -impl From for ApiErr { - fn from(rejection: FormRejection) -> Self { - tracing::warn!("Form data error: {}", rejection); - ApiErr::BadRequest( - "Invalid form data".to_string(), - Some(serde_json::json!({ - "detail": rejection.to_string() - })) - ) - } -} - diff --git a/src/util/single_instance.rs b/src/util/single_instance.rs deleted file mode 100644 index efc1d35..0000000 --- a/src/util/single_instance.rs +++ /dev/null @@ -1,65 +0,0 @@ -use fs2::FileExt; -use std::{ - fs::{File, OpenOptions}, - io, - path::PathBuf, -}; - -pub fn try_acquire(name: &str) -> io::Result { - SingleInstanceGuard::acquire(name) -} - -pub struct SingleInstanceGuard { - _file: File, -} - -impl SingleInstanceGuard { - fn acquire(name: &str) -> io::Result { - let lock_path = lock_file_path(name); - let file = OpenOptions::new() - .create(true) - .read(true) - .write(true) - .truncate(false) - .open(lock_path)?; - - if let Err(err) = file.try_lock_exclusive() { - if err.kind() == io::ErrorKind::WouldBlock { - return Err(io::Error::new( - io::ErrorKind::AlreadyExists, - "another PLC Control instance is already running", - )); - } - return Err(io::Error::new( - err.kind(), - format!("failed to lock single-instance file: {}", err), - )); - } - - Ok(Self { _file: file }) - } -} - -fn lock_file_path(name: &str) -> PathBuf { - let sanitized: String = name - .chars() - .map(|ch| match ch { - 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' => ch, - _ => '_', - }) - .collect(); - - std::env::temp_dir().join(format!("{}.lock", sanitized)) -} - -#[cfg(test)] -mod tests { - use super::lock_file_path; - - #[test] - fn lock_path_is_sanitized() { - let path = lock_file_path("PLCControl/Gateway:test"); - let path_str = path.to_string_lossy(); - assert!(path_str.ends_with("PLCControl_Gateway_test.lock")); - } -} diff --git a/src/util/validator.rs b/src/util/validator.rs deleted file mode 100644 index 2fe0337..0000000 --- a/src/util/validator.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::util::response::ApiErr; -use serde_json::{json, Value}; -use validator::ValidationErrors; - -impl From for ApiErr { - fn from(errors: ValidationErrors) -> Self { - // 构建详细的错误信息 - let mut error_details = serde_json::Map::new(); - let mut first_error_msg = String::from("请求参数验证失败"); - - for (field, field_errors) in errors.field_errors() { - let error_list: Vec = field_errors - .iter() - .map(|e| { - e.message.as_ref() - .map(|m| m.to_string()) - .unwrap_or_else(|| e.code.to_string()) - }) - .collect(); - error_details.insert(field.to_string(), json!(error_list)); - - // 获取第一个字段的第一个错误信息 - if first_error_msg == "请求参数验证失败" && !error_list.is_empty() { - if let Some(msg) = field_errors[0].message.as_ref() { - first_error_msg = format!("{}: {}", field, msg); - } else { - first_error_msg = format!("{}: {}", field, field_errors[0].code); - } - } - } - - ApiErr::BadRequest(first_error_msg, Some(Value::Object(error_details))) - } -} diff --git a/src/websocket.rs b/src/websocket.rs index c82bb0f..de773f2 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -17,7 +17,7 @@ use uuid::Uuid; pub enum WsMessage { PointNewValue(crate::telemetry::PointMonitorInfo), PointSetValueBatchResult(crate::connection::BatchSetPointValueRes), - EventCreated(crate::model::EventRecord), + EventCreated(plc_platform_core::model::EventRecord), UnitRuntimeChanged(crate::control::runtime::UnitRuntime), }