225 lines
13 KiB
Python
225 lines
13 KiB
Python
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
|
||
from sqlalchemy import JSON, BigInteger, Boolean, Column, DateTime, Float, ForeignKey, Index, String, Table, text
|
||
from sqlalchemy.dialects.postgresql import JSONB
|
||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||
|
||
|
||
class Base(DeclarativeBase):
|
||
pass
|
||
|
||
|
||
class Simulation(Base):
|
||
__tablename__ = "simulations"
|
||
|
||
simulation_id: Mapped[str] = mapped_column(String(64), primary_key=True, comment="仿真 ID")
|
||
status: Mapped[str] = mapped_column(String(32), index=True, comment="仿真状态(running/stopped 等)")
|
||
started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True, comment="开始时间(UTC)")
|
||
ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True, comment="结束时间(UTC)")
|
||
scenario_name: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True, comment="仿真场景名称")
|
||
scenario_config: Mapped[dict] = mapped_column(JSON, default=dict, comment="仿真场景配置(JSON)")
|
||
config_created_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True, comment="配置创建时间(UTC)")
|
||
operator: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True, comment="仿真操作员")
|
||
archived: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否归档")
|
||
|
||
|
||
vehicle_signals = Table(
|
||
"sim_vehicle_signals",
|
||
Base.metadata,
|
||
Column("ts", DateTime(timezone=True), nullable=False, index=True, comment="信号采样时间(UTC)"),
|
||
Column("simulation_id", String(64), nullable=False, index=True, comment="仿真 ID"),
|
||
Column("device_id", String(64), nullable=False, index=True, comment="设备 ID"),
|
||
Column("seq", BigInteger, nullable=False, comment="信号序列号(单仿真内递增)"),
|
||
Column("signals", JSONB, nullable=False, comment="车辆信号载荷(JSONB)"),
|
||
Index("idx_vehicle_signals_sim_ts", "simulation_id", "ts"),
|
||
comment="车辆信号时序数据(TimescaleDB hypertable)",
|
||
)
|
||
|
||
unity_vehicle_frames = Table(
|
||
"sim_unity_vehicle_frames",
|
||
Base.metadata,
|
||
Column("ts", DateTime(timezone=True), nullable=False, index=True, comment="帧时间(UTC)"),
|
||
Column("simulation_id", String(64), nullable=False, index=True, comment="仿真 ID"),
|
||
Column("vehicle_id", String(64), nullable=False, index=True, comment="虚拟车辆 ID"),
|
||
Column("seq", BigInteger, nullable=False, comment="帧序号(单仿真单车内递增)"),
|
||
Column("pos_x", Float, nullable=False, comment="位置 X(世界坐标)"),
|
||
Column("pos_y", Float, nullable=False, comment="位置 Y(世界坐标)"),
|
||
Column("pos_z", Float, nullable=False, comment="位置 Z(世界坐标)"),
|
||
Column("rot_x", Float, nullable=False, comment="旋转四元数 X"),
|
||
Column("rot_y", Float, nullable=False, comment="旋转四元数 Y"),
|
||
Column("rot_z", Float, nullable=False, comment="旋转四元数 Z"),
|
||
Column("rot_w", Float, nullable=False, comment="旋转四元数 W"),
|
||
Column("lin_vel_x", Float, nullable=True, comment="线速度 X(可选)"),
|
||
Column("lin_vel_y", Float, nullable=True, comment="线速度 Y(可选)"),
|
||
Column("lin_vel_z", Float, nullable=True, comment="线速度 Z(可选)"),
|
||
Column("ang_vel_x", Float, nullable=True, comment="角速度 X(可选)"),
|
||
Column("ang_vel_y", Float, nullable=True, comment="角速度 Y(可选)"),
|
||
Column("ang_vel_z", Float, nullable=True, comment="角速度 Z(可选)"),
|
||
Column("controls", JSONB, nullable=True, comment="控制量(油门/刹车/方向/档位等,JSONB)"),
|
||
Column("extra", JSONB, nullable=True, comment="扩展字段(仿真引擎自定义,JSONB)"),
|
||
Index("idx_unity_frames_sim_vehicle_ts", "simulation_id", "vehicle_id", "ts"),
|
||
comment="虚拟车辆驱动仿真帧数据(用于 Unity 车辆模型运动与回放,TimescaleDB hypertable)",
|
||
)
|
||
|
||
screen_recordings = Table(
|
||
"sim_screen_videos",
|
||
Base.metadata,
|
||
Column("video_id", String(64), primary_key=True, comment="录制文件记录 ID"),
|
||
Column("simulation_id", String(64), nullable=False, index=True, comment="仿真 ID"),
|
||
Column("screen_type", String(32), nullable=False, index=True, comment="屏幕类型(big_screen/vehicle_screen 等)"),
|
||
Column("source_name", String(64), nullable=True, index=True, comment="录制源名称(可选,如设备号/通道号)"),
|
||
Column("status", String(32), nullable=False, index=True, comment="状态(recording/ready/failed 等)"),
|
||
Column("relative_path", String(1024), nullable=False, comment="相对文件根目录的路径(用于下载/归档)"),
|
||
Column("file_name", String(255), nullable=True, comment="文件名(可选)"),
|
||
Column("format", String(32), nullable=True, comment="容器格式(mp4/mkv 等)"),
|
||
Column("codec", String(64), nullable=True, comment="编码信息(H264/H265 等)"),
|
||
Column("width", BigInteger, nullable=True, comment="视频宽度(像素)"),
|
||
Column("height", BigInteger, nullable=True, comment="视频高度(像素)"),
|
||
Column("fps", Float, nullable=True, comment="帧率(可选)"),
|
||
Column("duration_ms", BigInteger, nullable=True, comment="时长(毫秒,可选)"),
|
||
Column("size_bytes", BigInteger, nullable=True, comment="文件大小(字节,可选)"),
|
||
Column("recorded_started_at", DateTime(timezone=True), nullable=True, index=True, comment="录制开始时间(UTC,可选)"),
|
||
Column("recorded_ended_at", DateTime(timezone=True), nullable=True, index=True, comment="录制结束时间(UTC,可选)"),
|
||
Column("created_at", DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="记录创建时间(UTC)"),
|
||
Column("extra", JSONB, nullable=True, comment="扩展信息(JSONB)"),
|
||
Index("idx_screen_recordings_sim_screen_created", "simulation_id", "screen_type", "created_at"),
|
||
Index("idx_screen_recordings_sim_screen_time", "simulation_id", "screen_type", "recorded_started_at"),
|
||
comment="仿真过程屏幕录制文件元数据(显示大屏/车载屏等)",
|
||
)
|
||
|
||
sys_role = Table(
|
||
"sys_role",
|
||
Base.metadata,
|
||
Column("role_id", String(64), primary_key=True, comment="角色 ID"),
|
||
Column("role_name", String(64), nullable=False, unique=True, index=True, comment="角色名称(唯一)"),
|
||
Column("role_desc", String(255), nullable=True, comment="角色描述"),
|
||
Column("is_active", Boolean, nullable=False, server_default=text("TRUE"), index=True, comment="是否启用"),
|
||
Column("created_at", DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="创建时间(UTC)"),
|
||
Column("updated_at", DateTime(timezone=True), nullable=True, comment="更新时间(UTC)"),
|
||
Column("extra", JSONB, nullable=True, comment="扩展信息(JSONB)"),
|
||
comment="系统角色",
|
||
)
|
||
|
||
sys_permission = Table(
|
||
"sys_permission",
|
||
Base.metadata,
|
||
Column("perm_code", String(128), primary_key=True, comment="权限编码(唯一)"),
|
||
Column("perm_name", String(128), nullable=False, index=True, comment="权限名称"),
|
||
Column("perm_group", String(64), nullable=True, index=True, comment="权限分组(可选)"),
|
||
Column("perm_desc", String(255), nullable=True, comment="权限描述"),
|
||
Column("created_at", DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="创建时间(UTC)"),
|
||
comment="系统功能权限",
|
||
)
|
||
|
||
sys_role_permission = Table(
|
||
"sys_role_permission",
|
||
Base.metadata,
|
||
Column("role_id", String(64), ForeignKey("sys_role.role_id", ondelete="CASCADE"), primary_key=True, comment="角色 ID"),
|
||
Column("perm_code", String(128), ForeignKey("sys_permission.perm_code", ondelete="CASCADE"), primary_key=True, comment="权限编码"),
|
||
Column("created_at", DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="创建时间(UTC)"),
|
||
Index("idx_sys_role_permission_role", "role_id"),
|
||
Index("idx_sys_role_permission_perm", "perm_code"),
|
||
comment="角色功能权限关联表",
|
||
)
|
||
|
||
sys_user = Table(
|
||
"sys_user",
|
||
Base.metadata,
|
||
Column("user_id", String(64), primary_key=True, comment="用户 ID"),
|
||
Column("username", String(64), nullable=False, unique=True, index=True, comment="登录名(唯一)"),
|
||
Column("display_name", String(64), nullable=True, index=True, comment="显示名称"),
|
||
Column("password_hash", String(255), nullable=False, comment="密码哈希"),
|
||
Column("role_id", String(64), ForeignKey("sys_role.role_id"), nullable=False, index=True, comment="所属角色 ID"),
|
||
Column("is_active", Boolean, nullable=False, server_default=text("TRUE"), index=True, comment="是否启用"),
|
||
Column("last_login_at", DateTime(timezone=True), nullable=True, index=True, comment="最近登录时间(UTC)"),
|
||
Column("created_at", DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="创建时间(UTC)"),
|
||
Column("updated_at", DateTime(timezone=True), nullable=True, comment="更新时间(UTC)"),
|
||
Column("extra", JSONB, nullable=True, comment="扩展信息(JSONB)"),
|
||
comment="系统用户(含所属角色)",
|
||
)
|
||
|
||
sys_logs = Table(
|
||
"sys_logs",
|
||
Base.metadata,
|
||
Column("log_id", BigInteger, primary_key=True, comment="日志 ID"),
|
||
Column("ts", DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="操作时间(UTC)"),
|
||
Column("user_id", String(64), nullable=True, index=True, comment="用户 ID(可为空,如匿名)"),
|
||
Column("username", String(64), nullable=True, index=True, comment="登录名快照(可选)"),
|
||
Column("role_id", String(64), nullable=True, index=True, comment="角色 ID 快照(可选)"),
|
||
Column("action", String(128), nullable=False, index=True, comment="操作动作(如 login/start_simulation)"),
|
||
Column("resource", String(255), nullable=True, index=True, comment="资源标识(如 URL/对象 ID)"),
|
||
Column("success", Boolean, nullable=False, server_default=text("TRUE"), index=True, comment="是否成功"),
|
||
Column("ip", String(64), nullable=True, comment="客户端 IP(可选)"),
|
||
Column("user_agent", String(512), nullable=True, comment="User-Agent(可选)"),
|
||
Column("detail", JSONB, nullable=True, comment="操作明细(JSONB,可选)"),
|
||
Index("idx_sys_logs_user_ts", "user_id", "ts"),
|
||
Index("idx_sys_logs_action_ts", "action", "ts"),
|
||
comment="系统操作日志",
|
||
)
|
||
|
||
|
||
server_metrics = Table(
|
||
"server_metrics",
|
||
Base.metadata,
|
||
Column("ts", DateTime(timezone=True), nullable=False, index=True, comment="采样时间(UTC)"),
|
||
Column("host_name", String(64), nullable=False, index=True, comment="主机名"),
|
||
Column("cpu_usage_percent", JSONB, nullable=False, comment="CPU 使用率(百分比,JSONB)"),
|
||
Column("memory_usage_bytes", JSONB, nullable=False, comment="内存使用情况(字节,JSONB)"),
|
||
Column("disk_usage_bytes", JSONB, nullable=True, comment="磁盘使用情况(字节,JSONB)"),
|
||
Index("idx_server_metrics_host_ts", "host_name", "ts"),
|
||
comment="服务器监控指标时序数据(TimescaleDB hypertable)",
|
||
)
|
||
|
||
|
||
async def init_schema(engine) -> None:
|
||
from sqlalchemy.ext.asyncio import AsyncEngine
|
||
|
||
if not isinstance(engine, AsyncEngine):
|
||
raise TypeError("engine must be AsyncEngine")
|
||
|
||
async with engine.begin() as conn:
|
||
await conn.run_sync(Base.metadata.create_all)
|
||
await conn.execute(text("ALTER TABLE simulations ADD COLUMN IF NOT EXISTS scenario_name VARCHAR(255)"))
|
||
await conn.execute(text("ALTER TABLE simulations ADD COLUMN IF NOT EXISTS config_created_at TIMESTAMPTZ"))
|
||
await conn.execute(text("ALTER TABLE simulations ADD COLUMN IF NOT EXISTS operator VARCHAR(64)"))
|
||
await conn.execute(text("CREATE INDEX IF NOT EXISTS idx_simulations_scenario_name ON simulations (scenario_name)"))
|
||
await conn.execute(text("CREATE INDEX IF NOT EXISTS idx_simulations_config_created_at ON simulations (config_created_at)"))
|
||
await conn.execute(text("CREATE INDEX IF NOT EXISTS idx_simulations_operator ON simulations (operator)"))
|
||
|
||
|
||
async def init_timescaledb(engine) -> None:
|
||
async with engine.begin() as conn:
|
||
await conn.execute(text("CREATE EXTENSION IF NOT EXISTS timescaledb"))
|
||
await conn.execute(
|
||
text(
|
||
"SELECT create_hypertable('sim_vehicle_signals', 'ts', if_not_exists => TRUE)"
|
||
)
|
||
)
|
||
await conn.execute(
|
||
text(
|
||
"CREATE INDEX IF NOT EXISTS idx_vehicle_signals_sim_ts_desc ON sim_vehicle_signals (simulation_id, ts DESC)"
|
||
)
|
||
)
|
||
await conn.execute(
|
||
text(
|
||
"SELECT create_hypertable('server_metrics', 'ts', if_not_exists => TRUE)"
|
||
)
|
||
)
|
||
await conn.execute(
|
||
text(
|
||
"CREATE INDEX IF NOT EXISTS idx_server_metrics_host_ts_desc ON server_metrics (host_name, ts DESC)"
|
||
)
|
||
)
|
||
await conn.execute(
|
||
text(
|
||
"SELECT create_hypertable('sim_unity_vehicle_frames', 'ts', if_not_exists => TRUE)"
|
||
)
|
||
)
|
||
await conn.execute(
|
||
text(
|
||
"CREATE INDEX IF NOT EXISTS idx_unity_frames_sim_vehicle_ts_desc ON sim_unity_vehicle_frames (simulation_id, vehicle_id, ts DESC)"
|
||
)
|
||
)
|