ForcePilot/backend/server/routers/channels/analytics_router.py
Kris e5e9f45411 refactor(channel-routers): 批量优化各渠道路由代码与契约对齐
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相关端点,重构路由路径与校验逻辑
2026-07-04 00:16:00 +08:00

260 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""聚合视图域 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)}