1. 为渠道账户ID查询添加最小长度校验,统一分析模块常量引用 2. 新增扫码登录向导端点,完善文档说明 3. 优化配对统计接口,移除无效参数 4. 为出站箱接口添加批量上限与202状态码 5. 新增测试用例、访问规则、配额等模块的查询与校验参数 6. 新增适配器健康批量查询、健康检查触发接口 7. 统一告警、审计日志的错误处理方式 8. 新增插件配置账户ID支持,优化批量操作响应 9. 新增环境健康批量查询、Webhook限流与参数校验 10. 完善会话管理、审计日志的参数与文档说明 11. 修复导入模块的校验错误处理逻辑
261 lines
11 KiB
Python
261 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 (
|
||
ANALYTICS_LIMIT,
|
||
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, min_length=1, description="账户 ID 过滤"),
|
||
limit: int = ANALYTICS_LIMIT,
|
||
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)}
|