实现数据接入层功能(REST API + WebSocket + 批量导入)
- 实现REST API服务器,支持IoT数据、手动录入和批量导入接口 - 实现WebSocket服务器,支持实时数据推送和连接管理 - 实现批量导入模块,支持CSV、Excel、JSON多种格式 - 实现数据处理工具,包含字段映射和单位转换功能 - 实现数据模型定义和数据验证机制 - 创建主程序入口和配置文件 - 添加详细的使用文档和API说明
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
"""
|
||||
数据模型定义
|
||||
定义水务管理系统的各种数据结构
|
||||
"""
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List, Optional, Any
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
class DataType(Enum):
|
||||
"""数据类型枚举"""
|
||||
LL = "LL" # 流量
|
||||
YL = "YL" # 压力
|
||||
SW = "SW" # 水位
|
||||
ZD = "ZD" # 浊度
|
||||
PH = "PH" # pH值
|
||||
WD = "WD" # 温度
|
||||
DD = "DD" # 电导率
|
||||
YD = "YD" # 硬度
|
||||
|
||||
class AlertLevel(Enum):
|
||||
"""警报级别枚举"""
|
||||
INFO = "info"
|
||||
WARNING = "warning"
|
||||
ERROR = "error"
|
||||
CRITICAL = "critical"
|
||||
|
||||
@dataclass
|
||||
class Device:
|
||||
"""设备模型"""
|
||||
id: str
|
||||
name: str
|
||||
device_type: str
|
||||
location: str
|
||||
description: Optional[str] = None
|
||||
install_date: Optional[datetime] = None
|
||||
status: str = "active" # active, inactive, maintenance
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@dataclass
|
||||
class SensorData:
|
||||
"""传感器数据模型"""
|
||||
id: str
|
||||
device_id: str
|
||||
data_type: DataType
|
||||
value: float
|
||||
unit: str
|
||||
timestamp: datetime
|
||||
location: str
|
||||
quality_score: float = 1.0
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""转换为字典"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"device_id": self.device_id,
|
||||
"data_type": self.data_type.value,
|
||||
"value": self.value,
|
||||
"unit": self.unit,
|
||||
"timestamp": self.timestamp.isoformat(),
|
||||
"location": self.location,
|
||||
"quality_score": self.quality_score,
|
||||
"metadata": self.metadata
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> 'SensorData':
|
||||
"""从字典创建对象"""
|
||||
return cls(
|
||||
id=data["id"],
|
||||
device_id=data["device_id"],
|
||||
data_type=DataType(data["data_type"]),
|
||||
value=float(data["value"]),
|
||||
unit=data.get("unit", ""),
|
||||
timestamp=datetime.fromisoformat(data["timestamp"]),
|
||||
location=data.get("location", ""),
|
||||
quality_score=float(data.get("quality_score", 1.0)),
|
||||
metadata=data.get("metadata", {})
|
||||
)
|
||||
|
||||
@dataclass
|
||||
class Alert:
|
||||
"""警报模型"""
|
||||
id: str
|
||||
device_id: str
|
||||
alert_type: str
|
||||
level: AlertLevel
|
||||
message: str
|
||||
timestamp: datetime
|
||||
resolved: bool = False
|
||||
resolved_by: Optional[str] = None
|
||||
resolved_at: Optional[datetime] = None
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""转换为字典"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"device_id": self.device_id,
|
||||
"alert_type": self.alert_type,
|
||||
"level": self.level.value,
|
||||
"message": self.message,
|
||||
"timestamp": self.timestamp.isoformat(),
|
||||
"resolved": self.resolved,
|
||||
"resolved_by": self.resolved_by,
|
||||
"resolved_at": self.resolved_at.isoformat() if self.resolved_at else None,
|
||||
"metadata": self.metadata
|
||||
}
|
||||
|
||||
@dataclass
|
||||
class BatchImport:
|
||||
"""批量导入记录模型"""
|
||||
id: str
|
||||
batch_id: str
|
||||
data_source: str
|
||||
total_records: int
|
||||
successful_records: int
|
||||
failed_records: int
|
||||
status: str # pending, processing, completed, failed
|
||||
file_name: Optional[str] = None
|
||||
import_time: Optional[datetime] = None
|
||||
completed_time: Optional[datetime] = None
|
||||
error_messages: List[str] = field(default_factory=list)
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""转换为字典"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"batch_id": self.batch_id,
|
||||
"data_source": self.data_source,
|
||||
"total_records": self.total_records,
|
||||
"successful_records": self.successful_records,
|
||||
"failed_records": self.failed_records,
|
||||
"status": self.status,
|
||||
"file_name": self.file_name,
|
||||
"import_time": self.import_time.isoformat() if self.import_time else None,
|
||||
"completed_time": self.completed_time.isoformat() if self.completed_time else None,
|
||||
"error_messages": self.error_messages,
|
||||
"metadata": self.metadata
|
||||
}
|
||||
|
||||
@dataclass
|
||||
class APIRequest:
|
||||
"""API请求模型"""
|
||||
id: str
|
||||
method: str
|
||||
endpoint: str
|
||||
params: Dict[str, Any]
|
||||
headers: Dict[str, Any]
|
||||
body: Optional[Any] = None
|
||||
timestamp: datetime = field(default_factory=datetime.now)
|
||||
response_code: Optional[int] = None
|
||||
response_time_ms: Optional[float] = None
|
||||
response_body: Optional[Any] = None
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""转换为字典"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"method": self.method,
|
||||
"endpoint": self.endpoint,
|
||||
"params": self.params,
|
||||
"headers": self.headers,
|
||||
"body": self.body,
|
||||
"timestamp": self.timestamp.isoformat(),
|
||||
"response_code": self.response_code,
|
||||
"response_time_ms": self.response_time_ms,
|
||||
"response_body": self.response_body
|
||||
}
|
||||
|
||||
@dataclass
|
||||
class WebSocketConnection:
|
||||
"""WebSocket连接模型"""
|
||||
id: str
|
||||
client_ip: str
|
||||
connected_at: datetime
|
||||
disconnected_at: Optional[datetime] = None
|
||||
subscriptions: List[str] = field(default_factory=list)
|
||||
message_count: int = 0
|
||||
last_message_at: Optional[datetime] = None
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""转换为字典"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"client_ip": self.client_ip,
|
||||
"connected_at": self.connected_at.isoformat(),
|
||||
"disconnected_at": self.disconnected_at.isoformat() if self.disconnected_at else None,
|
||||
"subscriptions": self.subscriptions,
|
||||
"message_count": self.message_count,
|
||||
"last_message_at": self.last_message_at.isoformat() if self.last_message_at else None,
|
||||
"metadata": self.metadata
|
||||
}
|
||||
|
||||
@dataclass
|
||||
class SystemStats:
|
||||
"""系统统计模型"""
|
||||
timestamp: datetime
|
||||
total_records: int
|
||||
total_devices: int
|
||||
active_connections: int
|
||||
api_requests_count: int
|
||||
alerts_count: int
|
||||
data_quality_score: float
|
||||
memory_usage_mb: float
|
||||
cpu_usage_percent: float
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""转换为字典"""
|
||||
return {
|
||||
"timestamp": self.timestamp.isoformat(),
|
||||
"total_records": self.total_records,
|
||||
"total_devices": self.total_devices,
|
||||
"active_connections": self.active_connections,
|
||||
"api_requests_count": self.api_requests_count,
|
||||
"alerts_count": self.alerts_count,
|
||||
"data_quality_score": self.data_quality_score,
|
||||
"memory_usage_mb": self.memory_usage_mb,
|
||||
"cpu_usage_percent": self.cpu_usage_percent
|
||||
}
|
||||
|
||||
class DataValidator:
|
||||
"""数据验证器"""
|
||||
|
||||
@staticmethod
|
||||
def validate_sensor_data(data: Dict[str, Any]) -> List[str]:
|
||||
"""验证传感器数据"""
|
||||
errors = []
|
||||
|
||||
# 必需字段检查
|
||||
required_fields = ["device_id", "data_type", "value", "location"]
|
||||
for field in required_fields:
|
||||
if field not in data:
|
||||
errors.append(f"缺少必需字段: {field}")
|
||||
|
||||
# 数据类型验证
|
||||
if "data_type" in data:
|
||||
try:
|
||||
DataType(data["data_type"])
|
||||
except ValueError:
|
||||
errors.append(f"无效的数据类型: {data['data_type']}")
|
||||
|
||||
# 数值验证
|
||||
if "value" in data:
|
||||
try:
|
||||
value = float(data["value"])
|
||||
# 根据数据类型进行数值范围检查
|
||||
data_type = data.get("data_type")
|
||||
if data_type == "LL" and value < 0:
|
||||
errors.append("流量不能为负数")
|
||||
elif data_type == "YL" and value < 0:
|
||||
errors.append("压力不能为负数")
|
||||
elif data_type == "SW" and value < 0:
|
||||
errors.append("水位不能为负数")
|
||||
except (ValueError, TypeError):
|
||||
errors.append(f"无效的数值: {data['value']}")
|
||||
|
||||
# 时间戳验证
|
||||
if "timestamp" in data:
|
||||
try:
|
||||
if isinstance(data["timestamp"], str):
|
||||
datetime.fromisoformat(data["timestamp"])
|
||||
except (ValueError, TypeError):
|
||||
errors.append(f"无效的时间戳格式: {data['timestamp']}")
|
||||
|
||||
return errors
|
||||
|
||||
@staticmethod
|
||||
def validate_device_data(data: Dict[str, Any]) -> List[str]:
|
||||
"""验证设备数据"""
|
||||
errors = []
|
||||
|
||||
# 必需字段检查
|
||||
required_fields = ["id", "name", "device_type", "location"]
|
||||
for field in required_fields:
|
||||
if field not in data:
|
||||
errors.append(f"缺少必需字段: {field}")
|
||||
|
||||
# 设备ID格式验证
|
||||
if "id" in data:
|
||||
device_id = data["id"]
|
||||
if not isinstance(device_id, str) or not device_id.strip():
|
||||
errors.append("设备ID不能为空")
|
||||
elif len(device_id) > 50:
|
||||
errors.append("设备ID长度不能超过50个字符")
|
||||
|
||||
# 状态验证
|
||||
if "status" in data and data["status"] not in ["active", "inactive", "maintenance"]:
|
||||
errors.append("设备状态必须是: active, inactive, maintenance")
|
||||
|
||||
return errors
|
||||
|
||||
@staticmethod
|
||||
def validate_alert_data(data: Dict[str, Any]) -> List[str]:
|
||||
"""验证警报数据"""
|
||||
errors = []
|
||||
|
||||
# 必需字段检查
|
||||
required_fields = ["device_id", "alert_type", "level", "message"]
|
||||
for field in required_fields:
|
||||
if field not in data:
|
||||
errors.append(f"缺少必需字段: {field}")
|
||||
|
||||
# 警报级别验证
|
||||
if "level" in data:
|
||||
try:
|
||||
AlertLevel(data["level"])
|
||||
except ValueError:
|
||||
errors.append(f"无效的警报级别: {data['level']}")
|
||||
|
||||
return errors
|
||||
|
||||
# 全局验证器实例
|
||||
validator = DataValidator()
|
||||
Reference in New Issue
Block a user