feat(internal): 启动确认窗 agent 兜底样本落盘(pricebot 回写)
- 新增内部端点 POST /internal/launch-confirm-sample(X-Internal-Secret 鉴权): pricebot 对没见过文案的启动确认窗用 LLM 兜底放行后,把样本(host 包 / 弹窗树 / LLM plan / 设备 locale 机型)回写落 launch_confirm_sample 表,供研发人工沉淀回 pricebot 规则 yaml - 配套: model LaunchConfirmSample + schema + repo(都上报不去重) + alembic 迁移 launch_confirm_sample_table(down_revision=cps_wx_user) + main 注册 router + models/__init__ 登记 - docs: 后端技术实现.md 加 §6.5 pricebot 回写内部端点说明 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
"""启动确认窗兜底样本内部上报端点(pricebot → app-server)。
|
||||
|
||||
pricebot 的 launch_confirm_agent 每次靠 LLM 兜底放行一个"静态 PROFILES 没认出"的跨 App
|
||||
启动确认窗时,把完整样本 POST 到这里落库。**不是给客户端的接口**:不走用户 JWT,靠
|
||||
server 间共享密钥头 `X-Internal-Secret` 校验(== settings.INTERNAL_API_SECRET)。密钥未配置
|
||||
(默认空)时直接 503,避免裸奔写端点。
|
||||
|
||||
研发定期人工把 exec_success=true 的样本沉淀进 pricebot 的 launch_confirm.PROFILES。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hmac
|
||||
import logging
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Header, HTTPException, status
|
||||
|
||||
from app.api.deps import DbSession
|
||||
from app.core.config import settings
|
||||
from app.repositories import launch_confirm_sample as repo
|
||||
from app.schemas.launch_confirm_sample import (
|
||||
LaunchConfirmSampleIn,
|
||||
LaunchConfirmSampleOut,
|
||||
)
|
||||
|
||||
logger = logging.getLogger("shagua.internal.launch_confirm")
|
||||
|
||||
router = APIRouter(prefix="/internal", tags=["internal"])
|
||||
|
||||
|
||||
def _check_secret(x_internal_secret: str | None) -> None:
|
||||
"""共享密钥校验。未配置 → 503(挡裸奔写端点);不匹配 → 401(常量时间比较)。"""
|
||||
configured = settings.INTERNAL_API_SECRET
|
||||
if not configured:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="internal api not configured",
|
||||
)
|
||||
if not x_internal_secret or not hmac.compare_digest(x_internal_secret, configured):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="invalid internal secret",
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/launch-confirm-sample",
|
||||
response_model=LaunchConfirmSampleOut,
|
||||
summary="启动确认窗兜底样本上报(pricebot→app-server,落 launch_confirm_sample)",
|
||||
)
|
||||
def report_launch_confirm_sample(
|
||||
payload: LaunchConfirmSampleIn,
|
||||
db: DbSession,
|
||||
x_internal_secret: Annotated[str | None, Header()] = None,
|
||||
) -> LaunchConfirmSampleOut:
|
||||
_check_secret(x_internal_secret)
|
||||
sid = repo.insert_sample(db, payload)
|
||||
logger.info(
|
||||
"launch_confirm_sample id=%d host=%s locale=%s success=%s trace=%s",
|
||||
sid, payload.host_package, payload.system_locale,
|
||||
payload.exec_success, payload.trace_id,
|
||||
)
|
||||
return LaunchConfirmSampleOut(id=sid)
|
||||
Reference in New Issue
Block a user