refactor(core): complete model and util move
This commit is contained in:
parent
b34c898089
commit
a1e2536844
|
|
@ -18,9 +18,9 @@ use std::{
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
use plc_platform_core::model::{PointSubscriptionInfo, ScanMode};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
model::{PointSubscriptionInfo, ScanMode},
|
|
||||||
telemetry::PointMonitorInfo,
|
telemetry::PointMonitorInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1057,7 +1057,7 @@ impl ConnectionManager {
|
||||||
quality: crate::telemetry::PointQuality::Good,
|
quality: crate::telemetry::PointQuality::Good,
|
||||||
protocol: "opcua".to_string(),
|
protocol: "opcua".to_string(),
|
||||||
timestamp: Some(Utc::now()),
|
timestamp: Some(Utc::now()),
|
||||||
scan_mode: crate::model::ScanMode::Poll,
|
scan_mode: ScanMode::Poll,
|
||||||
},
|
},
|
||||||
)) {
|
)) {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,10 @@ async fn unit_task(state: AppState, store: Arc<ControlRuntimeStore>, unit_id: Uu
|
||||||
// Wait run_time_sec. run_time_sec == 0 means run without a time limit
|
// 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.
|
// (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 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 {
|
if !wait_phase(&state, &store, &unit_for_wait, &all_roles, ¬ify, &mut fault_tick).await {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -228,7 +231,7 @@ async fn unit_task(state: AppState, store: Arc<ControlRuntimeStore>, unit_id: Uu
|
||||||
async fn wait_phase(
|
async fn wait_phase(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
store: &ControlRuntimeStore,
|
store: &ControlRuntimeStore,
|
||||||
unit: &crate::model::ControlUnit,
|
unit: &plc_platform_core::model::ControlUnit,
|
||||||
all_roles: &[(Uuid, HashMap<String, EquipmentRolePoint>)],
|
all_roles: &[(Uuid, HashMap<String, EquipmentRolePoint>)],
|
||||||
notify: &Arc<Notify>,
|
notify: &Arc<Notify>,
|
||||||
fault_tick: &mut tokio::time::Interval,
|
fault_tick: &mut tokio::time::Interval,
|
||||||
|
|
@ -279,7 +282,7 @@ async fn push_ws(state: &AppState, runtime: &UnitRuntime) {
|
||||||
async fn check_fault_comm(
|
async fn check_fault_comm(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
runtime: &mut UnitRuntime,
|
runtime: &mut UnitRuntime,
|
||||||
unit: &crate::model::ControlUnit,
|
unit: &plc_platform_core::model::ControlUnit,
|
||||||
all_roles: &[(Uuid, HashMap<String, EquipmentRolePoint>)],
|
all_roles: &[(Uuid, HashMap<String, EquipmentRolePoint>)],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let monitor = state
|
let monitor = state
|
||||||
|
|
@ -431,7 +434,7 @@ async fn load_equipment_maps(state: &AppState, unit_id: Uuid) -> Result<EquipMap
|
||||||
|
|
||||||
fn build_equipment_maps(
|
fn build_equipment_maps(
|
||||||
unit_id: Uuid,
|
unit_id: Uuid,
|
||||||
equipment_list: &[crate::model::Equipment],
|
equipment_list: &[plc_platform_core::model::Equipment],
|
||||||
mut role_points_by_equipment: HashMap<Uuid, Vec<EquipmentRolePoint>>,
|
mut role_points_by_equipment: HashMap<Uuid, Vec<EquipmentRolePoint>>,
|
||||||
) -> EquipMaps {
|
) -> EquipMaps {
|
||||||
let mut kind_roles: HashMap<String, HashMap<String, EquipmentRolePoint>> = HashMap::new();
|
let mut kind_roles: HashMap<String, HashMap<String, EquipmentRolePoint>> = HashMap::new();
|
||||||
|
|
@ -496,7 +499,7 @@ fn find_cmd(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::build_equipment_maps;
|
use super::build_equipment_maps;
|
||||||
use crate::model::Equipment;
|
use plc_platform_core::model::Equipment;
|
||||||
use crate::service::EquipmentRolePoint;
|
use crate::service::EquipmentRolePoint;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@ pub async fn patch_signal(state: &AppState, point_id: Uuid, value_on: bool) {
|
||||||
source_id: Uuid::nil(),
|
source_id: Uuid::nil(),
|
||||||
point_id,
|
point_id,
|
||||||
client_handle: 0,
|
client_handle: 0,
|
||||||
scan_mode: crate::model::ScanMode::Poll,
|
scan_mode: plc_platform_core::model::ScanMode::Poll,
|
||||||
timestamp: Some(chrono::Utc::now()),
|
timestamp: Some(chrono::Utc::now()),
|
||||||
quality: PointQuality::Good,
|
quality: PointQuality::Good,
|
||||||
value: Some(value),
|
value: Some(value),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
use plc_platform_core::model::EventRecord;
|
||||||
|
|
||||||
const CONTROL_EVENT_CHANNEL_CAPACITY: usize = 1024;
|
const CONTROL_EVENT_CHANNEL_CAPACITY: usize = 1024;
|
||||||
const TELEMETRY_EVENT_CHANNEL_CAPACITY: usize = 4096;
|
const TELEMETRY_EVENT_CHANNEL_CAPACITY: usize = 4096;
|
||||||
|
|
@ -448,7 +449,7 @@ async fn persist_event_if_needed(
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted = sqlx::query_as::<_, crate::model::EventRecord>(
|
let inserted = sqlx::query_as::<_, EventRecord>(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO event (event_type, level, unit_id, equipment_id, source_id, message, payload)
|
INSERT INTO event (event_type, level, unit_id, equipment_id, source_id, message, payload)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||||
|
|
|
||||||
|
|
@ -50,14 +50,14 @@ pub struct GetUnitListQuery {
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
pub struct UnitEquipmentItem {
|
pub struct UnitEquipmentItem {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub equipment: crate::model::Equipment,
|
pub equipment: plc_platform_core::model::Equipment,
|
||||||
pub role_points: Vec<crate::handler::equipment::SignalRolePoint>,
|
pub role_points: Vec<crate::handler::equipment::SignalRolePoint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
pub struct UnitWithRuntime {
|
pub struct UnitWithRuntime {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub unit: crate::model::ControlUnit,
|
pub unit: plc_platform_core::model::ControlUnit,
|
||||||
pub runtime: Option<crate::control::runtime::UnitRuntime>,
|
pub runtime: Option<crate::control::runtime::UnitRuntime>,
|
||||||
pub equipments: Vec<UnitEquipmentItem>,
|
pub equipments: Vec<UnitEquipmentItem>,
|
||||||
}
|
}
|
||||||
|
|
@ -250,21 +250,21 @@ pub async fn get_unit(
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
pub struct PointDetail {
|
pub struct PointDetail {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub point: crate::model::Point,
|
pub point: plc_platform_core::model::Point,
|
||||||
pub point_monitor: Option<crate::telemetry::PointMonitorInfo>,
|
pub point_monitor: Option<crate::telemetry::PointMonitorInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
pub struct EquipmentDetail {
|
pub struct EquipmentDetail {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub equipment: crate::model::Equipment,
|
pub equipment: plc_platform_core::model::Equipment,
|
||||||
pub points: Vec<PointDetail>,
|
pub points: Vec<PointDetail>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
pub struct UnitDetail {
|
pub struct UnitDetail {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub unit: crate::model::ControlUnit,
|
pub unit: plc_platform_core::model::ControlUnit,
|
||||||
pub runtime: Option<crate::control::runtime::UnitRuntime>,
|
pub runtime: Option<crate::control::runtime::UnitRuntime>,
|
||||||
pub equipments: Vec<EquipmentDetail>,
|
pub equipments: Vec<EquipmentDetail>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use axum::{
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::util::response::ApiErr;
|
use plc_platform_core::util::response::ApiErr;
|
||||||
|
|
||||||
pub async fn get_api_md() -> Result<impl IntoResponse, ApiErr> {
|
pub async fn get_api_md() -> Result<impl IntoResponse, ApiErr> {
|
||||||
let content = tokio::fs::read_to_string("API.md")
|
let content = tokio::fs::read_to_string("API.md")
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use crate::util::{
|
use plc_platform_core::util::{
|
||||||
pagination::{PaginatedResponse, PaginationParams},
|
pagination::{PaginatedResponse, PaginationParams},
|
||||||
response::ApiErr,
|
response::ApiErr,
|
||||||
};
|
};
|
||||||
|
|
@ -44,7 +44,7 @@ pub struct SignalRolePoint {
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct EquipmentListItem {
|
pub struct EquipmentListItem {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub equipment: crate::model::Equipment,
|
pub equipment: plc_platform_core::model::Equipment,
|
||||||
pub point_count: i64,
|
pub point_count: i64,
|
||||||
pub role_points: Vec<SignalRolePoint>,
|
pub role_points: Vec<SignalRolePoint>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ use tokio::{
|
||||||
time::{Duration, interval},
|
time::{Duration, interval},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::util::response::ApiErr;
|
use plc_platform_core::util::response::ApiErr;
|
||||||
|
|
||||||
const LOG_DIR: &str = "./logs";
|
const LOG_DIR: &str = "./logs";
|
||||||
const DEFAULT_TAIL_LINES: usize = 200;
|
const DEFAULT_TAIL_LINES: usize = 200;
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ use sqlx::types::Json as SqlxJson;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use crate::model::Page;
|
use plc_platform_core::model::Page;
|
||||||
use crate::util::response::ApiErr;
|
use plc_platform_core::util::response::ApiErr;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
|
|
||||||
#[derive(Deserialize, Validate)]
|
#[derive(Deserialize, Validate)]
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use std::collections::{HashMap, HashSet};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use crate::util::{
|
use plc_platform_core::util::{
|
||||||
pagination::{PaginatedResponse, PaginationParams},
|
pagination::{PaginatedResponse, PaginationParams},
|
||||||
response::ApiErr,
|
response::ApiErr,
|
||||||
};
|
};
|
||||||
|
|
@ -56,7 +56,7 @@ pub struct GetPointHistoryQuery {
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct PointHistoryItem {
|
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<chrono::DateTime<chrono::Utc>>,
|
pub timestamp: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
pub quality: crate::telemetry::PointQuality,
|
pub quality: crate::telemetry::PointQuality,
|
||||||
pub value: Option<crate::telemetry::DataValue>,
|
pub value: Option<crate::telemetry::DataValue>,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use opcua::types::ReferenceTypeId;
|
||||||
use opcua::client::Session;
|
use opcua::client::Session;
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
|
||||||
use crate::util::response::ApiErr;
|
use plc_platform_core::util::response::ApiErr;
|
||||||
|
|
||||||
use crate::{AppState, model::{Node, Source}};
|
use crate::{AppState, model::{Node, Source}};
|
||||||
use anyhow::{Context};
|
use anyhow::{Context};
|
||||||
|
|
@ -61,7 +61,7 @@ pub struct SourceWithStatus {
|
||||||
pub source: SourcePublic,
|
pub source: SourcePublic,
|
||||||
pub is_connected: bool,
|
pub is_connected: bool,
|
||||||
pub last_error: Option<String>,
|
pub last_error: Option<String>,
|
||||||
#[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<DateTime<Utc>>,
|
pub last_time: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,9 +74,9 @@ pub struct SourcePublic {
|
||||||
pub security_policy: Option<String>,
|
pub security_policy: Option<String>,
|
||||||
pub security_mode: Option<String>,
|
pub security_mode: Option<String>,
|
||||||
pub enabled: bool,
|
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<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
#[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<Utc>,
|
pub updated_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,10 @@ use serde::Deserialize;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use crate::util::{response::ApiErr, pagination::{PaginatedResponse, PaginationParams}};
|
use plc_platform_core::util::{
|
||||||
|
pagination::{PaginatedResponse, PaginationParams},
|
||||||
|
response::ApiErr,
|
||||||
|
};
|
||||||
use crate::{AppState};
|
use crate::{AppState};
|
||||||
|
|
||||||
/// 获取所有标签
|
/// 获取所有标签
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,8 @@ mod db;
|
||||||
mod event;
|
mod event;
|
||||||
mod handler;
|
mod handler;
|
||||||
mod middleware;
|
mod middleware;
|
||||||
mod model;
|
|
||||||
mod service;
|
mod service;
|
||||||
mod telemetry;
|
mod telemetry;
|
||||||
mod util;
|
|
||||||
mod websocket;
|
mod websocket;
|
||||||
use axum::{
|
use axum::{
|
||||||
routing::{get, post, put},
|
routing::{get, post, put},
|
||||||
|
|
@ -48,8 +46,9 @@ pub struct AppState {
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
dotenv::dotenv().ok();
|
dotenv::dotenv().ok();
|
||||||
util::log::init_logger();
|
plc_platform_core::util::log::init_logger();
|
||||||
let _single_instance = match util::single_instance::try_acquire("PLCControl.Gateway") {
|
let _single_instance =
|
||||||
|
match plc_platform_core::util::single_instance::try_acquire("PLCControl.Gateway") {
|
||||||
Ok(guard) => guard,
|
Ok(guard) => guard,
|
||||||
Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {
|
Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {
|
||||||
tracing::warn!("Another PLC Control instance is already running");
|
tracing::warn!("Another PLC Control instance is already running");
|
||||||
|
|
|
||||||
178
src/model.rs
178
src/model.rs
|
|
@ -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<ScanMode> 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<Self, Self::Err> {
|
|
||||||
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<String>,
|
|
||||||
pub security_mode: Option<String>,
|
|
||||||
pub username: Option<String>,
|
|
||||||
pub password: Option<String>,
|
|
||||||
pub enabled: bool,
|
|
||||||
#[serde(serialize_with = "utc_to_local_str")]
|
|
||||||
pub created_at: DateTime<Utc>,
|
|
||||||
#[serde(serialize_with = "utc_to_local_str")]
|
|
||||||
pub updated_at: DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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<String>,
|
|
||||||
pub namespace_index: Option<i32>,
|
|
||||||
pub identifier_type: Option<String>, // i/s/g/b
|
|
||||||
pub identifier: Option<String>,
|
|
||||||
|
|
||||||
pub browse_name: String,
|
|
||||||
pub display_name: Option<String>,
|
|
||||||
pub node_class: String, // Object/Variable/Method coil/input topic
|
|
||||||
pub parent_id: Option<Uuid>,
|
|
||||||
#[serde(serialize_with = "utc_to_local_str")]
|
|
||||||
pub created_at: DateTime<Utc>,
|
|
||||||
#[serde(serialize_with = "utc_to_local_str")]
|
|
||||||
pub updated_at: DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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<String>,
|
|
||||||
pub unit: Option<String>,
|
|
||||||
pub tag_id: Option<Uuid>,
|
|
||||||
pub equipment_id: Option<Uuid>,
|
|
||||||
pub signal_role: Option<String>,
|
|
||||||
#[serde(serialize_with = "utc_to_local_str")]
|
|
||||||
pub created_at: DateTime<Utc>,
|
|
||||||
#[serde(serialize_with = "utc_to_local_str")]
|
|
||||||
pub updated_at: DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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<String>,
|
|
||||||
#[serde(serialize_with = "utc_to_local_str")]
|
|
||||||
pub created_at: DateTime<Utc>,
|
|
||||||
#[serde(serialize_with = "utc_to_local_str")]
|
|
||||||
pub updated_at: DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, FromRow, Clone)]
|
|
||||||
pub struct Equipment {
|
|
||||||
pub id: Uuid,
|
|
||||||
pub unit_id: Option<Uuid>,
|
|
||||||
pub code: String,
|
|
||||||
pub name: String,
|
|
||||||
pub kind: Option<String>,
|
|
||||||
pub description: Option<String>,
|
|
||||||
#[serde(serialize_with = "utc_to_local_str")]
|
|
||||||
pub created_at: DateTime<Utc>,
|
|
||||||
#[serde(serialize_with = "utc_to_local_str")]
|
|
||||||
pub updated_at: DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, FromRow, Clone)]
|
|
||||||
pub struct ControlUnit {
|
|
||||||
pub id: Uuid,
|
|
||||||
pub code: String,
|
|
||||||
pub name: String,
|
|
||||||
pub description: Option<String>,
|
|
||||||
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<Utc>,
|
|
||||||
#[serde(serialize_with = "utc_to_local_str")]
|
|
||||||
pub updated_at: DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, FromRow, Clone)]
|
|
||||||
pub struct EventRecord {
|
|
||||||
pub id: Uuid,
|
|
||||||
pub event_type: String,
|
|
||||||
pub level: String,
|
|
||||||
pub unit_id: Option<Uuid>,
|
|
||||||
pub equipment_id: Option<Uuid>,
|
|
||||||
pub source_id: Option<Uuid>,
|
|
||||||
pub message: String,
|
|
||||||
pub payload: Option<Json<serde_json::Value>>,
|
|
||||||
#[serde(serialize_with = "utc_to_local_str")]
|
|
||||||
pub created_at: DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, FromRow, Clone)]
|
|
||||||
pub struct Page {
|
|
||||||
pub id: Uuid,
|
|
||||||
pub name: String,
|
|
||||||
pub data: Json<HashMap<String, Uuid>>,
|
|
||||||
#[serde(serialize_with = "utc_to_local_str")]
|
|
||||||
pub created_at: DateTime<Utc>,
|
|
||||||
#[serde(serialize_with = "utc_to_local_str")]
|
|
||||||
pub updated_at: DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::model::{ControlUnit, EventRecord};
|
use plc_platform_core::model::{ControlUnit, EventRecord};
|
||||||
use sqlx::{PgPool, QueryBuilder, Row};
|
use sqlx::{PgPool, QueryBuilder, Row};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
|
@ -337,7 +337,7 @@ pub async fn get_all_enabled_units(pool: &PgPool) -> Result<Vec<ControlUnit>, sq
|
||||||
pub async fn get_equipment_by_unit_ids(
|
pub async fn get_equipment_by_unit_ids(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
unit_ids: &[Uuid],
|
unit_ids: &[Uuid],
|
||||||
) -> Result<Vec<crate::model::Equipment>, sqlx::Error> {
|
) -> Result<Vec<plc_platform_core::model::Equipment>, sqlx::Error> {
|
||||||
if unit_ids.is_empty() {
|
if unit_ids.is_empty() {
|
||||||
return Ok(vec![]);
|
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 {}",
|
"SELECT * FROM equipment WHERE unit_id = ANY($1) ORDER BY {}",
|
||||||
equipment_order_clause_with_unit()
|
equipment_order_clause_with_unit()
|
||||||
);
|
);
|
||||||
sqlx::query_as::<_, crate::model::Equipment>(&sql)
|
sqlx::query_as::<_, plc_platform_core::model::Equipment>(&sql)
|
||||||
.bind(unit_ids)
|
.bind(unit_ids)
|
||||||
.fetch_all(pool)
|
.fetch_all(pool)
|
||||||
.await
|
.await
|
||||||
|
|
@ -354,12 +354,12 @@ pub async fn get_equipment_by_unit_ids(
|
||||||
pub async fn get_equipment_by_unit_id(
|
pub async fn get_equipment_by_unit_id(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
unit_id: Uuid,
|
unit_id: Uuid,
|
||||||
) -> Result<Vec<crate::model::Equipment>, sqlx::Error> {
|
) -> Result<Vec<plc_platform_core::model::Equipment>, sqlx::Error> {
|
||||||
let sql = format!(
|
let sql = format!(
|
||||||
"SELECT * FROM equipment WHERE unit_id = $1 ORDER BY {}",
|
"SELECT * FROM equipment WHERE unit_id = $1 ORDER BY {}",
|
||||||
unit_order_clause()
|
unit_order_clause()
|
||||||
);
|
);
|
||||||
sqlx::query_as::<_, crate::model::Equipment>(&sql)
|
sqlx::query_as::<_, plc_platform_core::model::Equipment>(&sql)
|
||||||
.bind(unit_id)
|
.bind(unit_id)
|
||||||
.fetch_all(pool)
|
.fetch_all(pool)
|
||||||
.await
|
.await
|
||||||
|
|
@ -368,11 +368,11 @@ pub async fn get_equipment_by_unit_id(
|
||||||
pub async fn get_points_by_equipment_ids(
|
pub async fn get_points_by_equipment_ids(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
equipment_ids: &[Uuid],
|
equipment_ids: &[Uuid],
|
||||||
) -> Result<Vec<crate::model::Point>, sqlx::Error> {
|
) -> Result<Vec<plc_platform_core::model::Point>, sqlx::Error> {
|
||||||
if equipment_ids.is_empty() {
|
if equipment_ids.is_empty() {
|
||||||
return Ok(vec![]);
|
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"#,
|
r#"SELECT * FROM point WHERE equipment_id = ANY($1) ORDER BY equipment_id, created_at"#,
|
||||||
)
|
)
|
||||||
.bind(equipment_ids)
|
.bind(equipment_ids)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::model::{Point, PointSubscriptionInfo};
|
use plc_platform_core::model::{Point, PointSubscriptionInfo};
|
||||||
use sqlx::{query_as, PgPool, Row};
|
use sqlx::{query_as, PgPool, Row};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::model::Source;
|
use plc_platform_core::model::Source;
|
||||||
use sqlx::{query_as, PgPool};
|
use sqlx::{query_as, PgPool};
|
||||||
|
|
||||||
pub async fn get_enabled_source(
|
pub async fn get_enabled_source(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::model::{Point, Tag};
|
use plc_platform_core::model::{Point, Tag};
|
||||||
use sqlx::{query_as, PgPool};
|
use sqlx::{query_as, PgPool};
|
||||||
|
|
||||||
pub async fn get_tags_count(pool: &PgPool) -> Result<i64, sqlx::Error> {
|
pub async fn get_tags_count(pool: &PgPool) -> Result<i64, sqlx::Error> {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use plc_platform_core::model::ScanMode;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::model::ScanMode;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ValueType {
|
pub enum ValueType {
|
||||||
|
|
@ -80,14 +79,14 @@ pub struct PointMonitorInfo {
|
||||||
pub point_id: Uuid,
|
pub point_id: Uuid,
|
||||||
pub client_handle: u32,
|
pub client_handle: u32,
|
||||||
pub scan_mode: ScanMode,
|
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<DateTime<Utc>>,
|
pub timestamp: Option<DateTime<Utc>>,
|
||||||
pub quality: PointQuality,
|
pub quality: PointQuality,
|
||||||
pub value: Option<DataValue>,
|
pub value: Option<DataValue>,
|
||||||
pub value_type: Option<ValueType>,
|
pub value_type: Option<ValueType>,
|
||||||
pub value_text: Option<String>,
|
pub value_text: Option<String>,
|
||||||
pub old_value: Option<DataValue>,
|
pub old_value: Option<DataValue>,
|
||||||
#[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<DateTime<Utc>>,
|
pub old_timestamp: Option<DateTime<Utc>>,
|
||||||
pub value_changed: bool,
|
pub value_changed: bool,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
pub mod datetime;
|
|
||||||
pub mod log;
|
|
||||||
pub mod pagination;
|
|
||||||
pub mod response;
|
|
||||||
pub mod single_instance;
|
|
||||||
pub mod validator;
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
use chrono::{DateTime, Local, Utc};
|
|
||||||
use serde::Serializer;
|
|
||||||
|
|
||||||
pub fn utc_to_local_string(date: &DateTime<Utc>) -> String {
|
|
||||||
date.with_timezone(&Local).format("%Y-%m-%d %H:%M:%S%.3f").to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn utc_to_local_str<S>(date: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
let formatted = utc_to_local_string(date);
|
|
||||||
serializer.serialize_str(&formatted)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn option_utc_to_local_str<S>(date: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
match date {
|
|
||||||
Some(d) => utc_to_local_str(d, serializer),
|
|
||||||
None => serializer.serialize_none(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<non_blocking::WorkerGuard> = 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();
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_with::serde_as;
|
|
||||||
use validator::Validate;
|
|
||||||
|
|
||||||
/// 分页响应结构
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct PaginatedResponse<T> {
|
|
||||||
pub data: Vec<T>,
|
|
||||||
pub total: i64,
|
|
||||||
pub page: u32,
|
|
||||||
pub page_size: i32,
|
|
||||||
pub total_pages: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> PaginatedResponse<T> {
|
|
||||||
/// 创建分页响应
|
|
||||||
pub fn new(data: Vec<T>, 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ErrResp {
|
|
||||||
pub fn new(err_code: i32, err_msg: impl Into<String>, detail: Option<Value>) -> Self {
|
|
||||||
Self {
|
|
||||||
err_code,
|
|
||||||
err_msg: err_msg.into(),
|
|
||||||
err_detail: detail,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum ApiErr {
|
|
||||||
Unauthorized(String, Option<Value>),
|
|
||||||
Forbidden(String, Option<Value>),
|
|
||||||
BadRequest(String, Option<Value>),
|
|
||||||
NotFound(String, Option<Value>),
|
|
||||||
Internal(String, Option<Value>),
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Error> 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<SqlxError> 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<QueryRejection> 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<PathRejection> 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<JsonRejection> 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<FormRejection> 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()
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
use fs2::FileExt;
|
|
||||||
use std::{
|
|
||||||
fs::{File, OpenOptions},
|
|
||||||
io,
|
|
||||||
path::PathBuf,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn try_acquire(name: &str) -> io::Result<SingleInstanceGuard> {
|
|
||||||
SingleInstanceGuard::acquire(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SingleInstanceGuard {
|
|
||||||
_file: File,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SingleInstanceGuard {
|
|
||||||
fn acquire(name: &str) -> io::Result<Self> {
|
|
||||||
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"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
use crate::util::response::ApiErr;
|
|
||||||
use serde_json::{json, Value};
|
|
||||||
use validator::ValidationErrors;
|
|
||||||
|
|
||||||
impl From<ValidationErrors> 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<String> = 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)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -17,7 +17,7 @@ use uuid::Uuid;
|
||||||
pub enum WsMessage {
|
pub enum WsMessage {
|
||||||
PointNewValue(crate::telemetry::PointMonitorInfo),
|
PointNewValue(crate::telemetry::PointMonitorInfo),
|
||||||
PointSetValueBatchResult(crate::connection::BatchSetPointValueRes),
|
PointSetValueBatchResult(crate::connection::BatchSetPointValueRes),
|
||||||
EventCreated(crate::model::EventRecord),
|
EventCreated(plc_platform_core::model::EventRecord),
|
||||||
UnitRuntimeChanged(crate::control::runtime::UnitRuntime),
|
UnitRuntimeChanged(crate::control::runtime::UnitRuntime),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue