1. config_router: 为expected_version添加ge=1校验 2. directory_router: 补充scope校验逻辑与注释 3. login_router: 拆分强制下线权限,添加参数校验与注释更新 4. reports_router: 统一时间参数处理,修复分页限制使用契约常量 5. dashboard_router: 更新文档与响应格式,修正参数传递逻辑 6. health_router: 缩减健康检查响应字段,修复响应结构与参数校验 7. plugin_router: 新增插件目录端点,补充枚举校验与注释 8. pairing_router: 新增时间过滤参数,补充参数校验 9. __init__.py: 修复异常映射,更新trace_id获取逻辑与工具类 10. doctor_router: 重构单项检查端点,修正注释与校验逻辑 11. account_router: 新增恢复降级账户端点,补充批量操作校验 12. webhook_router: 优化webhook处理逻辑,修复流式读取与响应逻辑 13. content_review_router: 补充批量审核端点,完善参数校验与注释 14. analytics_router: 修正管道阶段描述,统一参数传递 15. wizard_router: 新增OAuth相关端点,重构路由路径与校验逻辑
260 lines
11 KiB
Python
260 lines
11 KiB
Python
"""聚合视图域 analytics 子域 Router。
|
||
|
||
实现 9 个 GET 端点,统一采用模板 A(控制面端口路由),通过
|
||
``use_cases.analytics_view.<method>(...)`` 调用类型化方法,经控制面管道
|
||
五阶段(鉴权 → 权限 → 限流 → dispatch → 审计)处理。
|
||
|
||
端点清单:
|
||
- ANL-01: GET /analytics/messages 消息深度分析
|
||
- ANL-02: GET /analytics/messages/distribution 消息类型分布
|
||
- ANL-04: GET /analytics/sessions 会话分析
|
||
- ANL-05: GET /analytics/delivery 投递链路分析
|
||
- ANL-06: GET /analytics/delivery/latency 投递延迟分布
|
||
- ANL-07: GET /analytics/delivery/funnel 投递漏斗分析
|
||
- ANL-ACCOUNTS: GET /analytics/accounts 账户活跃度分析
|
||
- ANL-PEERS: GET /analytics/peers 对端活跃度分析
|
||
- ANL-CR: GET /analytics/content-review 内容审核分析
|
||
|
||
设计依据:《18-聚合视图域-analytics-router-设计方案.md》v1.2(前 6 端点),
|
||
ANL-ACCOUNTS/PEERS/CR 为后续扩展端点。
|
||
"""
|
||
|
||
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.storage.postgres.models_business import User
|
||
|
||
from server.routers.channels import (
|
||
Granularity,
|
||
build_operator,
|
||
get_channel_use_cases,
|
||
parse_datetime,
|
||
raiseOnControlFailure,
|
||
serialize_control_data,
|
||
)
|
||
from server.utils.auth_middleware import get_admin_user
|
||
|
||
analytics_router = APIRouter(tags=["channels-analytics"])
|
||
|
||
|
||
@analytics_router.get("/analytics/messages", response_model=dict)
|
||
async def analyze_messages(
|
||
request: Request,
|
||
start_time: str = Query(..., description="开始时间(ISO 8601)"),
|
||
end_time: str = Query(..., description="结束时间(ISO 8601)"),
|
||
granularity: Granularity = Query(default="day", description="时间粒度"),
|
||
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]:
|
||
"""消息深度分析(ANL-01)。"""
|
||
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.analytics_view.analyzeMessages(
|
||
channel_type=channel_type,
|
||
start_time=start_time_dt.isoformat(),
|
||
end_time=end_time_dt.isoformat(),
|
||
granularity=granularity,
|
||
operator=operator,
|
||
)
|
||
raiseOnControlFailure(result)
|
||
return {"success": True, "data": serialize_control_data(result.data)}
|
||
|
||
|
||
@analytics_router.get("/analytics/messages/distribution", response_model=dict)
|
||
async def get_message_distribution(
|
||
request: Request,
|
||
start_time: str = Query(..., description="开始时间(ISO 8601)"),
|
||
end_time: str = Query(..., description="结束时间(ISO 8601)"),
|
||
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]:
|
||
"""消息类型分布分析(ANL-02)。"""
|
||
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.analytics_view.getMessageDistribution(
|
||
channel_type=channel_type,
|
||
start_time=start_time_dt.isoformat(),
|
||
end_time=end_time_dt.isoformat(),
|
||
operator=operator,
|
||
)
|
||
raiseOnControlFailure(result)
|
||
return {"success": True, "data": serialize_control_data(result.data)}
|
||
|
||
|
||
@analytics_router.get("/analytics/sessions", response_model=dict)
|
||
async def analyze_sessions(
|
||
request: Request,
|
||
start_time: str = Query(..., description="开始时间(ISO 8601)"),
|
||
end_time: str = Query(..., description="结束时间(ISO 8601)"),
|
||
granularity: Granularity = Query(default="day", description="时间粒度"),
|
||
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]:
|
||
"""会话分析(ANL-04)。"""
|
||
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.analytics_view.analyzeSessions(
|
||
channel_type=channel_type,
|
||
start_time=start_time_dt.isoformat(),
|
||
end_time=end_time_dt.isoformat(),
|
||
granularity=granularity,
|
||
operator=operator,
|
||
)
|
||
raiseOnControlFailure(result)
|
||
return {"success": True, "data": serialize_control_data(result.data)}
|
||
|
||
|
||
@analytics_router.get("/analytics/delivery", response_model=dict)
|
||
async def analyze_delivery(
|
||
request: Request,
|
||
start_time: str = Query(..., description="开始时间(ISO 8601)"),
|
||
end_time: str = Query(..., description="结束时间(ISO 8601)"),
|
||
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]:
|
||
"""投递链路分析(ANL-05)。"""
|
||
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.analytics_view.analyzeDelivery(
|
||
channel_type=channel_type,
|
||
start_time=start_time_dt.isoformat(),
|
||
end_time=end_time_dt.isoformat(),
|
||
operator=operator,
|
||
)
|
||
raiseOnControlFailure(result)
|
||
return {"success": True, "data": serialize_control_data(result.data)}
|
||
|
||
|
||
@analytics_router.get("/analytics/delivery/latency", response_model=dict)
|
||
async def get_delivery_latency_distribution(
|
||
request: Request,
|
||
start_time: str = Query(..., description="开始时间(ISO 8601)"),
|
||
end_time: str = Query(..., description="结束时间(ISO 8601)"),
|
||
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]:
|
||
"""投递延迟分布(ANL-06)。"""
|
||
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.analytics_view.getDeliveryLatencyDistribution(
|
||
channel_type=channel_type,
|
||
start_time=start_time_dt.isoformat(),
|
||
end_time=end_time_dt.isoformat(),
|
||
operator=operator,
|
||
)
|
||
raiseOnControlFailure(result)
|
||
return {"success": True, "data": serialize_control_data(result.data)}
|
||
|
||
|
||
@analytics_router.get("/analytics/delivery/funnel", response_model=dict)
|
||
async def get_delivery_funnel(
|
||
request: Request,
|
||
start_time: str = Query(..., description="开始时间(ISO 8601)"),
|
||
end_time: str = Query(..., description="结束时间(ISO 8601)"),
|
||
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]:
|
||
"""投递漏斗分析(ANL-07)。"""
|
||
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.analytics_view.getDeliveryFunnel(
|
||
channel_type=channel_type,
|
||
start_time=start_time_dt.isoformat(),
|
||
end_time=end_time_dt.isoformat(),
|
||
operator=operator,
|
||
)
|
||
raiseOnControlFailure(result)
|
||
return {"success": True, "data": serialize_control_data(result.data)}
|
||
|
||
|
||
@analytics_router.get("/analytics/accounts", response_model=dict)
|
||
async def analyze_accounts(
|
||
request: Request,
|
||
start_time: str = Query(..., description="开始时间(ISO 8601)"),
|
||
end_time: str = Query(..., description="结束时间(ISO 8601)"),
|
||
granularity: Granularity = Query(default="day", description="时间粒度"),
|
||
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]:
|
||
"""账户活跃度分析(ANL-ACCOUNTS)。"""
|
||
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.analytics_view.analyzeAccounts(
|
||
channel_type=channel_type,
|
||
start_time=start_time_dt.isoformat(),
|
||
end_time=end_time_dt.isoformat(),
|
||
granularity=granularity,
|
||
operator=operator,
|
||
)
|
||
raiseOnControlFailure(result)
|
||
return {"success": True, "data": serialize_control_data(result.data)}
|
||
|
||
|
||
@analytics_router.get("/analytics/peers", response_model=dict)
|
||
async def analyze_peers(
|
||
request: Request,
|
||
start_time: str = Query(..., description="开始时间(ISO 8601)"),
|
||
end_time: str = Query(..., description="结束时间(ISO 8601)"),
|
||
channel_type: ChannelType | None = Query(default=None, description="渠道类型过滤"),
|
||
account_id: str | None = Query(default=None, description="账户 ID 过滤"),
|
||
limit: int = Query(default=100, ge=1, le=500, description="Top N 上限(1-500)"),
|
||
use_cases=Depends(get_channel_use_cases),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""对端活跃度分析(ANL-PEERS)。"""
|
||
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.analytics_view.analyzePeers(
|
||
channel_type=channel_type,
|
||
start_time=start_time_dt.isoformat(),
|
||
end_time=end_time_dt.isoformat(),
|
||
operator=operator,
|
||
account_id=account_id,
|
||
limit=limit,
|
||
)
|
||
raiseOnControlFailure(result)
|
||
return {"success": True, "data": serialize_control_data(result.data)}
|
||
|
||
|
||
@analytics_router.get("/analytics/content-review", response_model=dict)
|
||
async def analyze_content_review(
|
||
request: Request,
|
||
start_time: str = Query(..., description="开始时间(ISO 8601)"),
|
||
end_time: str = Query(..., description="结束时间(ISO 8601)"),
|
||
granularity: Granularity = Query(default="day", description="时间粒度"),
|
||
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]:
|
||
"""内容审核分析(ANL-CR)。"""
|
||
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.analytics_view.analyzeContentReview(
|
||
channel_type=channel_type,
|
||
start_time=start_time_dt.isoformat(),
|
||
end_time=end_time_dt.isoformat(),
|
||
granularity=granularity,
|
||
operator=operator,
|
||
)
|
||
raiseOnControlFailure(result)
|
||
return {"success": True, "data": serialize_control_data(result.data)}
|