feat(webhook/content-review): 新增功能与查询过滤能力
1. 为webhook入站请求合并URL query参数到headers,支持微信公众号等渠道的验签 2. 为内容审核列表接口新增resource_type和trace_id查询过滤支持
This commit is contained in:
parent
b8ac375e8e
commit
b7ceeb11f3
@ -158,6 +158,23 @@ def _parse_verdict(raw: str) -> ContentReviewVerdict:
|
||||
) from exc
|
||||
|
||||
|
||||
def _parse_resource_type(raw: str) -> ContentReviewResourceType:
|
||||
"""string → ContentReviewResourceType,失败抛 ValidationError。
|
||||
|
||||
将 CR-02 的 ``resource_type`` 查询参数(string)翻译为
|
||||
``ContentReviewResourceType`` 枚举;非法值由全局异常处理器映射为
|
||||
400 ``VALIDATION_ERROR``。
|
||||
"""
|
||||
try:
|
||||
return ContentReviewResourceType(raw)
|
||||
except ValueError as exc:
|
||||
raise ValidationError(
|
||||
"resource_type",
|
||||
f"unsupported resource_type: {raw} "
|
||||
"(expected: message_text | message_attachment | user_profile)",
|
||||
) from exc
|
||||
|
||||
|
||||
# ---------------- 静态路径端点(须先于动态路径声明) ----------------
|
||||
|
||||
|
||||
@ -211,6 +228,14 @@ async def list_review_history(
|
||||
default=None,
|
||||
description="按审核结论过滤:pass / review / block",
|
||||
),
|
||||
resource_type: str | None = Query(
|
||||
default=None,
|
||||
description="按资源类型过滤:message_text / message_attachment / user_profile",
|
||||
),
|
||||
trace_id: str | None = Query(
|
||||
default=None,
|
||||
description="按链路追踪 ID 过滤",
|
||||
),
|
||||
start_time: str | None = Query(
|
||||
default=None,
|
||||
description="起始时间过滤(ISO 8601,含)",
|
||||
@ -228,11 +253,13 @@ async def list_review_history(
|
||||
|
||||
非控制面管道路径(模板 B):由 ``ContentReviewQueryService`` 直接读取
|
||||
``ContentReviewRepositoryPort``,不经控制面管道,不写审计日志(查询
|
||||
操作无副作用)。``verdict`` / ``start_time`` / ``end_time`` 查询参数
|
||||
经协议翻译辅助函数转换为枚举 / datetime,失败抛 ``ValidationError``。
|
||||
操作无副作用)。``verdict`` / ``resource_type`` / ``start_time`` /
|
||||
``end_time`` 查询参数经协议翻译辅助函数转换为枚举 / datetime,失败抛
|
||||
``ValidationError``。
|
||||
|
||||
编排链路:HTTP 入参 → 协议翻译(``_parse_verdict`` /
|
||||
``parse_datetime``)→ 构造 ``ContentReviewHistoryQueryCmd``
|
||||
``_parse_resource_type`` / ``parse_datetime``)→ 构造
|
||||
``ContentReviewHistoryQueryCmd``
|
||||
(DTO ``__post_init__`` 校验 limit/offset/time_range)→
|
||||
``use_cases.content_review_query.listHistory`` →
|
||||
``dataclass_to_dict`` 序列化 ``ContentReviewHistoryList`` 返回。
|
||||
@ -242,6 +269,8 @@ async def list_review_history(
|
||||
channel_type=channel_type,
|
||||
account_id=account_id,
|
||||
verdict=_parse_verdict(verdict) if verdict is not None else None,
|
||||
resource_type=_parse_resource_type(resource_type) if resource_type is not None else None,
|
||||
trace_id=trace_id,
|
||||
start_time=parse_datetime("start_time", start_time),
|
||||
end_time=parse_datetime("end_time", end_time),
|
||||
limit=limit,
|
||||
|
||||
@ -23,6 +23,7 @@ from yuxi.channels.contract.dtos.channel import ChannelType, WebhookTestCmd
|
||||
from yuxi.channels.contract.dtos.inbound import ReceiveInboundCmd
|
||||
from yuxi.channels.contract.errors import InternalError, ValidationError
|
||||
from yuxi.storage.postgres.models_business import User
|
||||
from yuxi.utils.crypto import SENSITIVE_HTTP_HEADERS
|
||||
from yuxi.utils.trace_context import get_trace_id
|
||||
|
||||
from server.routers.channels import (
|
||||
@ -140,6 +141,16 @@ async def receive_webhook(
|
||||
"webhook body is not valid UTF-8",
|
||||
) from exc
|
||||
sanitized_headers = sanitize_headers(request.headers)
|
||||
# 合并 URL query 参数到 headers(HTTP headers 优先,query 参数补充)。
|
||||
# 部分渠道(如微信公众号)将签名 / 时间戳 / nonce 等放在 URL query 中
|
||||
# 而非 HTTP headers,合并后供入站管道 ``verifySignature`` 校验。
|
||||
# 合并时跳过敏感字段(如 token / authorization),避免 query 参数绕过
|
||||
# ``sanitize_headers`` 过滤导致敏感信息泄露到日志。
|
||||
for key, value in request.query_params.items():
|
||||
if key.lower() in SENSITIVE_HTTP_HEADERS:
|
||||
continue
|
||||
if key not in sanitized_headers:
|
||||
sanitized_headers[key] = value
|
||||
cmd = ReceiveInboundCmd(
|
||||
channel_type=channel_type,
|
||||
raw_event=raw_event,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user