1. 为渠道账户ID查询添加最小长度校验,统一分析模块常量引用 2. 新增扫码登录向导端点,完善文档说明 3. 优化配对统计接口,移除无效参数 4. 为出站箱接口添加批量上限与202状态码 5. 新增测试用例、访问规则、配额等模块的查询与校验参数 6. 新增适配器健康批量查询、健康检查触发接口 7. 统一告警、审计日志的错误处理方式 8. 新增插件配置账户ID支持,优化批量操作响应 9. 新增环境健康批量查询、Webhook限流与参数校验 10. 完善会话管理、审计日志的参数与文档说明 11. 修复导入模块的校验错误处理逻辑
230 lines
10 KiB
Python
230 lines
10 KiB
Python
"""Dashboard 聚合视图域 Router(DSB-01~DSB-04 + DSB-DELIVERY + DSB-TODOS + DSB-REALTIME)。
|
||
|
||
统一采用模板 A(控制面端口路由),鉴权依赖 get_admin_user,调用
|
||
use_cases.dashboard_view.<method>,raiseOnControlFailure 转译失败,
|
||
serialize_control_data 序列化响应。
|
||
|
||
路径设计:7 个端点均为静态路径(/dashboard/overview / /dashboard/accounts
|
||
/dashboard/messages / /dashboard/sessions / /dashboard/delivery
|
||
/dashboard/todos / /dashboard/realtime)。子 router 自身不设置 prefix,根前缀 ``/channels``
|
||
由 ``channels_router`` 聚合 router 统一追加。
|
||
|
||
端点清单:
|
||
- GET /dashboard/overview DSB-01 getDashboardOverview
|
||
- GET /dashboard/accounts DSB-02 getAccountStats
|
||
- GET /dashboard/messages DSB-03 getMessageStats
|
||
- GET /dashboard/sessions DSB-04 getDashboardSessionStats
|
||
- GET /dashboard/delivery DSB-DELIVERY getDashboardDelivery
|
||
- GET /dashboard/todos DSB-TODOS getDashboardTodos
|
||
- GET /dashboard/realtime DSB-REALTIME getRealtimeMetrics
|
||
|
||
DSB-01~04 对应《16-聚合视图域-dashboard-router-设计方案》(仅覆盖 4 个
|
||
KPI 快照端点)。DSB-DELIVERY / DSB-TODOS / DSB-REALTIME 为后续扩展端点,
|
||
分别提供投递 KPI 总览、工作台待办计数与实时监控指标,不在上述设计方案范围内。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
from fastapi import APIRouter, Depends, Query, Request
|
||
from yuxi.channels.contract.dtos.channel import ChannelType
|
||
from yuxi.channels.contract.dtos.dashboard import (
|
||
DEFAULT_WINDOW_SECONDS,
|
||
MAX_WINDOW_SECONDS,
|
||
MIN_WINDOW_SECONDS,
|
||
RealtimeQuery,
|
||
)
|
||
from yuxi.storage.postgres.models_business import User
|
||
|
||
from server.routers.channels import (
|
||
build_operator,
|
||
get_channel_use_cases,
|
||
parse_datetime,
|
||
raiseOnControlFailure,
|
||
serialize_control_data,
|
||
)
|
||
from server.utils.auth_middleware import get_admin_user
|
||
|
||
dashboard_router = APIRouter(tags=["channels-dashboard"])
|
||
|
||
|
||
@dashboard_router.get("/dashboard/overview", response_model=dict)
|
||
async def get_dashboard_overview(
|
||
request: Request,
|
||
channel_type: ChannelType | None = Query(default=None, description="按渠道类型过滤"),
|
||
start_time: str | None = Query(default=None, description="消息统计起始时间(ISO 8601);仅 messages 子域受影响"),
|
||
end_time: str | None = Query(default=None, description="消息统计截止时间(ISO 8601);仅 messages 子域受影响"),
|
||
use_cases=Depends(get_channel_use_cases),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""查询全局总览(DSB-01)。对应控制面操作 dashboard/overview。
|
||
|
||
聚合账户 / 消息 / 会话 / 投递四类子域 KPI 计数。
|
||
``start_time`` / ``end_time`` 仅影响消息统计;账户、会话、投递为当前
|
||
快照,不受时间范围影响。
|
||
"""
|
||
operator = build_operator(current_user, request)
|
||
start_time_dt = parse_datetime("start_time", start_time)
|
||
end_time_dt = parse_datetime("end_time", end_time)
|
||
result = await use_cases.dashboard_view.getDashboardOverview(
|
||
channel_type=channel_type,
|
||
start_time=start_time_dt.isoformat() if start_time_dt else None,
|
||
end_time=end_time_dt.isoformat() if end_time_dt else None,
|
||
operator=operator,
|
||
)
|
||
raiseOnControlFailure(result)
|
||
return {"success": True, "data": serialize_control_data(result.data)}
|
||
|
||
|
||
@dashboard_router.get("/dashboard/accounts", response_model=dict)
|
||
async def get_dashboard_accounts(
|
||
request: Request,
|
||
channel_type: ChannelType | None = Query(default=None, description="按渠道类型过滤"),
|
||
use_cases=Depends(get_channel_use_cases),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""查询账户总览(DSB-02)。对应控制面操作 dashboard/accounts。
|
||
|
||
返回账户按渠道 / 状态分组的计数快照。
|
||
"""
|
||
operator = build_operator(current_user, request)
|
||
result = await use_cases.dashboard_view.getAccountStats(
|
||
channel_type=channel_type,
|
||
operator=operator,
|
||
)
|
||
raiseOnControlFailure(result)
|
||
return {"success": True, "data": serialize_control_data(result.data)}
|
||
|
||
|
||
@dashboard_router.get("/dashboard/messages", response_model=dict)
|
||
async def get_dashboard_messages(
|
||
request: Request,
|
||
channel_type: ChannelType | None = Query(default=None, description="按渠道类型过滤"),
|
||
start_time: str | None = Query(default=None, description="起始时间(ISO 8601)"),
|
||
end_time: str | None = Query(default=None, description="截止时间(ISO 8601)"),
|
||
use_cases=Depends(get_channel_use_cases),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""查询消息总览(DSB-03)。对应控制面操作 dashboard/messages。
|
||
|
||
返回指定时间范围内的消息按渠道 / 角色 / 投递状态分组的计数快照。
|
||
|
||
时间参数由 parse_datetime 在 Router 层翻译为 UTC datetime,再转回
|
||
ISO 8601 字符串传给端口(端口签名为 str)。非法格式抛
|
||
ValidationError(400)。
|
||
"""
|
||
operator = build_operator(current_user, request)
|
||
start_time_dt = parse_datetime("start_time", start_time)
|
||
end_time_dt = parse_datetime("end_time", end_time)
|
||
result = await use_cases.dashboard_view.getMessageStats(
|
||
channel_type=channel_type,
|
||
start_time=start_time_dt.isoformat() if start_time_dt else None,
|
||
end_time=end_time_dt.isoformat() if end_time_dt else None,
|
||
operator=operator,
|
||
)
|
||
raiseOnControlFailure(result)
|
||
return {"success": True, "data": serialize_control_data(result.data)}
|
||
|
||
|
||
@dashboard_router.get("/dashboard/sessions", response_model=dict)
|
||
async def get_dashboard_sessions(
|
||
request: Request,
|
||
channel_type: ChannelType | None = Query(default=None, description="按渠道类型过滤"),
|
||
use_cases=Depends(get_channel_use_cases),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""查询会话总览(DSB-04)。对应控制面操作 dashboard/sessions。
|
||
|
||
返回未软删除会话按渠道 / 是否临时会话分组的计数快照。
|
||
"""
|
||
operator = build_operator(current_user, request)
|
||
result = await use_cases.dashboard_view.getDashboardSessionStats(
|
||
channel_type=channel_type,
|
||
operator=operator,
|
||
)
|
||
raiseOnControlFailure(result)
|
||
return {"success": True, "data": serialize_control_data(result.data)}
|
||
|
||
|
||
@dashboard_router.get("/dashboard/delivery", response_model=dict)
|
||
async def get_dashboard_delivery(
|
||
request: Request,
|
||
channel_type: ChannelType | None = Query(default=None, description="按渠道类型过滤"),
|
||
start_time: str | None = Query(default=None, description="起始时间(ISO 8601)"),
|
||
end_time: str | None = Query(default=None, description="截止时间(ISO 8601)"),
|
||
use_cases=Depends(get_channel_use_cases),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""查询投递总览(DSB-DELIVERY)。对应控制面操作 dashboard/delivery。
|
||
|
||
聚合投递 KPI 指标(总数、成功率、延迟分位数、死信数、按渠道切片)。
|
||
|
||
时间参数由 parse_datetime 在 Router 层翻译为 UTC datetime,再转回
|
||
ISO 8601 字符串传给端口(端口签名为 str)。非法格式抛 ValidationError
|
||
(400)。
|
||
"""
|
||
operator = build_operator(current_user, request)
|
||
start_time_dt = parse_datetime("start_time", start_time)
|
||
end_time_dt = parse_datetime("end_time", end_time)
|
||
result = await use_cases.dashboard_view.getDashboardDelivery(
|
||
channel_type=channel_type,
|
||
start_time=start_time_dt.isoformat() if start_time_dt else None,
|
||
end_time=end_time_dt.isoformat() if end_time_dt else None,
|
||
operator=operator,
|
||
)
|
||
raiseOnControlFailure(result)
|
||
return {"success": True, "data": serialize_control_data(result.data)}
|
||
|
||
|
||
@dashboard_router.get("/dashboard/todos", response_model=dict)
|
||
async def get_dashboard_todos(
|
||
request: Request,
|
||
channel_type: ChannelType | None = Query(default=None, description="按渠道类型过滤"),
|
||
use_cases=Depends(get_channel_use_cases),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""查询工作台待办计数(DSB-TODOS)。对应控制面操作 dashboard/todos。
|
||
|
||
聚合待审批配对数、待复核内容审核数与当前死信积压数,按渠道类型过滤。
|
||
"""
|
||
operator = build_operator(current_user, request)
|
||
result = await use_cases.dashboard_view.getDashboardTodos(
|
||
channel_type=channel_type,
|
||
operator=operator,
|
||
)
|
||
raiseOnControlFailure(result)
|
||
return {"success": True, "data": serialize_control_data(result.data)}
|
||
|
||
|
||
@dashboard_router.get("/dashboard/realtime", response_model=dict)
|
||
async def get_dashboard_realtime(
|
||
request: Request,
|
||
channel_type: ChannelType | None = Query(default=None, description="按渠道类型过滤"),
|
||
window_seconds: int = Query(
|
||
default=DEFAULT_WINDOW_SECONDS,
|
||
ge=MIN_WINDOW_SECONDS,
|
||
le=MAX_WINDOW_SECONDS,
|
||
description="滑动窗口秒数(10-300,默认 60)",
|
||
),
|
||
use_cases=Depends(get_channel_use_cases),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""查询实时监控指标(DSB-REALTIME)。对应控制面操作 dashboard/realtime。
|
||
|
||
纯内存态聚合,业务不开启 DB 事务(``db_tx_required=False``),高频查询
|
||
场景使用。审计走 INDEPENDENT 独立事务 best-effort 写入,每请求一次
|
||
审计记录,不阻断响应。
|
||
|
||
``channel_type`` 为空(全局查询)时,``by_channel`` 返回全部已注册
|
||
渠道的切片;非空时仅返回该渠道一条。
|
||
"""
|
||
operator = build_operator(current_user, request)
|
||
query = RealtimeQuery(
|
||
channel_type=channel_type,
|
||
window_seconds=window_seconds,
|
||
)
|
||
result = await use_cases.dashboard_view.getRealtimeMetrics(query, operator=operator)
|
||
raiseOnControlFailure(result)
|
||
return {"success": True, "data": serialize_control_data(result.data)}
|