Files
water-management-system/src/bi/controllers.py
T
bot_dev1 61acfd8f8b feat: 实现自助BI看板功能,支持Superset/Metabase集成
- 新增BI模块(src/bi/),包含数据模型、服务和控制器
- 支持数据源管理、图表创建、看板配置
- 实现多图表类型:折线图、柱状图、饼图、散点图、面积图、仪表盘、表格
- 提供REST API(/bi/)和前端API(/bi-api/)接口
- 创建响应式前端界面,支持拖拽和实时数据展示
- 默认包含运营总览、设备管理、安全监控看板
- 支持与Superset和Metabase集成

🤖 Generated with [OpenClaw](https://github.com/robocomp/openclaw)
2026-06-15 12:29:34 +08:00

273 lines
10 KiB
Python

"""
BI控制器模块
提供REST API接口,支持自助BI看板和数据可视化
"""
from fastapi import APIRouter, HTTPException, Depends, Query, Path
from fastapi.responses import JSONResponse
from typing import List, Optional, Dict, Any
from datetime import datetime
from .services import BIService
from .models import ChartType, DataSourceType
# 创建路由器
router = APIRouter(prefix="/bi", tags=["BI"])
# 创建BI服务实例
bi_service = BIService()
@router.get("/")
async def get_bi_info():
"""获取BI系统信息"""
return {
"message": "Water Management System BI API",
"version": "1.0.0",
"endpoints": {
"charts": "/bi/charts",
"dashboards": "/bi/dashboards",
"data_sources": "/bi/data-sources",
"datasets": "/bi/datasets"
}
}
# 图表相关接口
@router.get("/charts", response_model=List[Dict[str, Any]])
async def get_all_charts():
"""获取所有图表"""
charts = bi_service.get_all_charts()
return [chart.to_dict() for chart in charts]
@router.get("/charts/{chart_id}", response_model=Dict[str, Any])
async def get_chart(chart_id: str = Path(..., description="图表ID")):
"""获取单个图表"""
chart = bi_service.get_chart(chart_id)
if not chart:
raise HTTPException(status_code=404, detail="Chart not found")
return chart.to_dict()
@router.post("/charts", response_model=Dict[str, Any])
async def create_chart(chart_data: Dict[str, Any]):
"""创建图表"""
try:
chart = bi_service.create_chart(chart_data)
return chart.to_dict()
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
@router.put("/charts/{chart_id}", response_model=Dict[str, Any])
async def update_chart(chart_id: str = Path(..., description="图表ID"), chart_data: Dict[str, Any] = None):
"""更新图表"""
if not chart_data:
raise HTTPException(status_code=400, detail="Chart data is required")
chart = bi_service.update_chart(chart_id, chart_data)
if not chart:
raise HTTPException(status_code=404, detail="Chart not found")
return chart.to_dict()
@router.delete("/charts/{chart_id}")
async def delete_chart(chart_id: str = Path(..., description="图表ID")):
"""删除图表"""
success = bi_service.delete_chart(chart_id)
if not success:
raise HTTPException(status_code=404, detail="Chart not found")
return {"message": "Chart deleted successfully"}
@router.get("/charts/{chart_id}/data", response_model=Dict[str, Any])
async def get_chart_data(chart_id: str = Path(..., description="图表ID")):
"""获取图表数据"""
data = bi_service.get_chart_data_api(chart_id)
if "error" in data:
raise HTTPException(status_code=404, detail=data["error"])
return data
# 看板相关接口
@router.get("/dashboards", response_model=List[Dict[str, Any]])
async def get_all_dashboards():
"""获取所有看板"""
dashboards = bi_service.get_all_dashboards()
return [dashboard.to_dict() for dashboard in dashboards]
@router.get("/dashboards/{dashboard_id}", response_model=Dict[str, Any])
async def get_dashboard(dashboard_id: str = Path(..., description="看板ID")):
"""获取单个看板"""
dashboard = bi_service.get_dashboard(dashboard_id)
if not dashboard:
raise HTTPException(status_code=404, detail="Dashboard not found")
return dashboard.to_dict()
@router.post("/dashboards", response_model=Dict[str, Any])
async def create_dashboard(dashboard_data: Dict[str, Any]):
"""创建看板"""
try:
dashboard = bi_service.create_dashboard(dashboard_data)
return dashboard.to_dict()
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
@router.put("/dashboards/{dashboard_id}", response_model=Dict[str, Any])
async def update_dashboard(dashboard_id: str = Path(..., description="看板ID"), dashboard_data: Dict[str, Any] = None):
"""更新看板"""
if not dashboard_data:
raise HTTPException(status_code=400, detail="Dashboard data is required")
dashboard = bi_service.update_dashboard(dashboard_id, dashboard_data)
if not dashboard:
raise HTTPException(status_code=404, detail="Dashboard not found")
return dashboard.to_dict()
@router.delete("/dashboards/{dashboard_id}")
async def delete_dashboard(dashboard_id: str = Path(..., description="看板ID")):
"""删除看板"""
success = bi_service.delete_dashboard(dashboard_id)
if not success:
raise HTTPException(status_code=404, detail="Dashboard not found")
return {"message": "Dashboard deleted successfully"}
@router.get("/dashboards/{dashboard_id}/data", response_model=Dict[str, Any])
async def get_dashboard_data(dashboard_id: str = Path(..., description="看板ID")):
"""获取看板数据"""
data = bi_service.get_dashboard_data_api(dashboard_id)
if "error" in data:
raise HTTPException(status_code=404, detail=data["error"])
return data
# 数据源相关接口
@router.get("/data-sources", response_model=List[Dict[str, Any]])
async def get_all_data_sources():
"""获取所有数据源"""
data_sources = bi_service.get_all_data_sources()
return [source.to_dict() for source in data_sources]
@router.get("/data-sources/{source_id}", response_model=Dict[str, Any])
async def get_data_source(source_id: str = Path(..., description="数据源ID")):
"""获取单个数据源"""
source = bi_service.get_data_source(source_id)
if not source:
raise HTTPException(status_code=404, detail="Data source not found")
return source.to_dict()
# 数据集相关接口
@router.get("/datasets", response_model=List[Dict[str, Any]])
async def get_all_datasets():
"""获取所有数据集"""
datasets = bi_service.get_all_datasets()
return [dataset.to_dict() for dataset in datasets]
@router.get("/datasets/{dataset_id}", response_model=Dict[str, Any])
async def get_dataset(dataset_id: str = Path(..., description="数据集ID")):
"""获取单个数据集"""
dataset = bi_service.get_dataset(dataset_id)
if not dataset:
raise HTTPException(status_code=404, detail="Dataset not found")
return dataset.to_dict()
# 搜索相关接口
@router.get("/search/charts")
async def search_charts(keyword: str = Query(..., description="搜索关键词")):
"""搜索图表"""
charts = bi_service.search_charts(keyword)
return [chart.to_dict() for chart in charts]
@router.get("/search/dashboards")
async def search_dashboards(keyword: str = Query(..., description="搜索关键词")):
"""搜索看板"""
dashboards = bi_service.search_dashboards(keyword)
return [dashboard.to_dict() for dashboard in dashboards]
# 标签相关接口
@router.get("/charts/tag/{tag}")
async def get_charts_by_tag(tag: str = Path(..., description="标签")):
"""根据标签获取图表"""
charts = bi_service.get_charts_by_tag(tag)
return [chart.to_dict() for chart in charts]
@router.get("/dashboards/tag/{tag}")
async def get_dashboards_by_tag(tag: str = Path(..., description="标签")):
"""根据标签获取看板"""
dashboards = bi_service.get_dashboards_by_tag(tag)
return [dashboard.to_dict() for dashboard in dashboards]
# 公开看板接口
@router.get("/public/dashboards")
async def get_public_dashboards():
"""获取公开看板"""
dashboards = bi_service.get_public_dashboards()
return [dashboard.to_dict() for dashboard in dashboards]
# 图表类型和枚举
@router.get("/types/chart-types")
async def get_chart_types():
"""获取支持的图表类型"""
return [{"value": ct.value, "label": ct.name} for ct in ChartType]
@router.get("/types/data-source-types")
async def get_data_source_types():
"""获取支持的数据源类型"""
return [{"value": dst.value, "label": dst.name} for dst in DataSourceType]
# 默认数据接口
@router.get("/default-charts")
async def get_default_charts():
"""获取默认图表"""
# 运营总览相关的图表
default_chart_ids = ["flow_trend", "device_status_distribution", "alert_level_stats", "system_performance"]
charts = [bi_service.get_chart(chart_id).to_dict() for chart_id in default_chart_ids if bi_service.get_chart(chart_id)]
return charts
@router.get("/default-dashboards")
async def get_default_dashboards():
"""获取默认看板"""
# 运营总览看板
default_dashboard_ids = ["operation_overview", "device_management", "security_monitoring"]
dashboards = [bi_service.get_dashboard(db_id).to_dict() for db_id in default_dashboard_ids if bi_service.get_dashboard(db_id)]
return dashboards
# Superset集成接口
@router.post("/integrations/superset")
async def setup_superset_integration(integration_data: Dict[str, Any]):
"""设置Superset集成"""
try:
integration = bi_service.setup_superset_integration(integration_data)
return integration.to_dict()
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
@router.get("/integrations/superset")
async def get_superset_integration():
"""获取Superset集成配置"""
if not bi_service.superset_integration:
raise HTTPException(status_code=404, detail="Superset integration not configured")
return bi_service.superset_integration.to_dict()
# Metabase集成接口
@router.post("/integrations/metabase")
async def setup_metabase_integration(integration_data: Dict[str, Any]):
"""设置Metabase集成"""
try:
integration = bi_service.setup_metabase_integration(integration_data)
return integration.to_dict()
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
@router.get("/integrations/metabase")
async def get_metabase_integration():
"""获取Metabase集成配置"""
if not bi_service.metabase_integration:
raise HTTPException(status_code=404, detail="Metabase integration not configured")
return bi_service.metabase_integration.to_dict()
# 统计信息接口
@router.get("/stats")
async def get_bi_stats():
"""获取BI系统统计信息"""
return {
"total_charts": len(bi_service.charts),
"total_dashboards": len(bi_service.dashboards),
"total_data_sources": len(bi_service.data_sources),
"total_datasets": len(bi_service.datasets),
"public_dashboards": len(bi_service.get_public_dashboards()),
"integrations": {
"superset_configured": bi_service.superset_integration is not None,
"metabase_configured": bi_service.metabase_integration is not None
}
}