feat(alerts): 添加微信告警、WebSocket实时推送和地图功能

- 新增微信告警服务(wechat_service)和告警任务(alert_tasks)
- 新增 WebSocket 实时推送端点
- 新增监控管理模块(monitor)
- 增强统计仪表板:趋势图、区域分布、光功率历史
- 设备管理:添加坐标信息、标签系统、复合索引优化
- 前端:重构Dashboard/Charts页面,新增业务组件
- 新增5个数据库迁移(坐标、复合索引、标签、光功率历史、显示名)
- 更新部署配置和脚本
- 新增测试框架基础结构
This commit is contained in:
2026-06-11 15:16:29 +08:00
parent 3453441754
commit b2c20ec43d
45 changed files with 2089 additions and 107 deletions
@@ -0,0 +1,25 @@
"""add latitude and longitude columns to onu_devices
Revision ID: i9j0k1l2m3n4
Revises: h8i9j0k1l2m3
Create Date: 2026-06-02 12:00:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = 'i9j0k1l2m3n4'
down_revision: Union[str, None] = 'h8i9j0k1l2m3'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.add_column('onu_devices', sa.Column('latitude', sa.Float(), nullable=True))
op.add_column('onu_devices', sa.Column('longitude', sa.Float(), nullable=True))
def downgrade() -> None:
op.drop_column('onu_devices', 'longitude')
op.drop_column('onu_devices', 'latitude')
@@ -0,0 +1,29 @@
"""add composite indexes for performance
Revision ID: j0k1l2m3n4o5
Revises: i9j0k1l2m3n4
Create Date: 2026-06-02 15:00:00.000000
"""
from typing import Sequence, Union
from alembic import op
revision: str = 'j0k1l2m3n4o5'
down_revision: Union[str, None] = 'i9j0k1l2m3n4'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# FK 索引 — 每次状态检查都要按 OLT 查询设备
op.create_index('ix_onu_devices_olt_id', 'onu_devices', ['olt_id'])
# 复合索引 — Dashboard 按区域+学校聚合
op.create_index('ix_onu_devices_region_school', 'onu_devices', ['region', 'school_name'])
# FK 索引 — DeviceStatusHistory 按设备查最新状态(最频繁的查询)
op.create_index('ix_device_status_history_onu_device_id_checked', 'device_status_history', ['onu_device_id', 'checked_at'])
def downgrade() -> None:
op.drop_index('ix_device_status_history_onu_device_id_checked', table_name='device_status_history')
op.drop_index('ix_onu_devices_region_school', table_name='onu_devices')
op.drop_index('ix_onu_devices_olt_id', table_name='onu_devices')
@@ -0,0 +1,23 @@
"""add tags column to onu_devices
Revision ID: k0l1m2n3o4p5
Revises: j0k1l2m3n4o5
Create Date: 2026-06-03 10:00:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = 'k0l1m2n3o4p5'
down_revision: Union[str, None] = 'j0k1l2m3n4o5'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.add_column('onu_devices', sa.Column('tags', sa.Text(), nullable=True))
def downgrade() -> None:
op.drop_column('onu_devices', 'tags')
@@ -0,0 +1,34 @@
"""add optical_power_history table
Revision ID: l1m2n3o4p5q6
Revises: k0l1m2n3o4p5
Create Date: 2026-06-03 11:00:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = 'l1m2n3o4p5q6'
down_revision: Union[str, None] = 'k0l1m2n3o4p5'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.create_table('optical_power_history',
sa.Column('id', sa.BigInteger(), nullable=False),
sa.Column('onu_device_id', sa.BigInteger(), sa.ForeignKey('onu_devices.id'), nullable=False),
sa.Column('power_in', sa.String(20), nullable=True),
sa.Column('power_out', sa.String(20), nullable=True),
sa.Column('recorded_at', sa.TIMESTAMP(), server_default=sa.text('now()'), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index('ix_optical_power_history_id', 'optical_power_history', ['id'])
op.create_index('ix_optical_power_history_onu_device_id', 'optical_power_history', ['onu_device_id'])
def downgrade() -> None:
op.drop_index('ix_optical_power_history_onu_device_id', table_name='optical_power_history')
op.drop_index('ix_optical_power_history_id', table_name='optical_power_history')
op.drop_table('optical_power_history')
@@ -0,0 +1,23 @@
"""add display_name to users
Revision ID: m1n2o3p4q5r6
Revises: l1m2n3o4p5q6
Create Date: 2026-06-03 20:00:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = 'm1n2o3p4q5r6'
down_revision: Union[str, None] = 'l1m2n3o4p5q6'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.add_column('users', sa.Column('display_name', sa.String(100), nullable=True))
def downgrade() -> None:
op.drop_column('users', 'display_name')