diff --git a/app/engine/routes/chat.py b/app/engine/routes/chat.py index bf3acb5..272bb99 100644 --- a/app/engine/routes/chat.py +++ b/app/engine/routes/chat.py @@ -80,7 +80,9 @@ async def ws_chat(websocket: WebSocket): ) merged_info = {**client_info, **(init_msg.get("client_info") or {})} # 服务端 XFF 优先于客户端自报 ip(客户端自报可伪造,可伪造面最小); - # 无 XFF 时保留客户端自报,user_agent 等其他字段不受影响 + # 无 XFF 时保留客户端自报,user_agent 等其他字段不受影响。 + # (REST 侧已同步上游用直连 host,WS 保持自报——见 + # tests/test_chat_rest_ip.py) if forwarded: merged_info["ip"] = forwarded # Task 10 复审: 专用 site 字段同样赢过 init_msg.client_info diff --git a/app/engine/routes/chat_rest.py b/app/engine/routes/chat_rest.py index 96fcb04..c976efd 100644 --- a/app/engine/routes/chat_rest.py +++ b/app/engine/routes/chat_rest.py @@ -34,16 +34,16 @@ class SessionRequest(BaseModel): def _server_ip(request: Request) -> str: - """服务端可信 IP:XFF 第一段优先,否则直连 host;全空返回空串。 + """服务端可信 IP:XFF 第一段优先,否则直连 host;全空返回空串。 - 同步自上游 ai-kefu(app/api/routes/chat_rest.py):REST 路径原先只在有 - XFF 时覆盖自报 ip,直连(无代理)场景自报假 IP 原样落库。""" + 同步自上游 ai-kefu(app/api/routes/chat_rest.py):REST 路径原先只在有 + XFF 时覆盖自报 ip,直连(无代理)场景自报假 IP 原样落库。""" xff = request.headers.get("x-forwarded-for", "").split(",")[0].strip() return xff or (request.client.host if request.client else "") def _merge_client_info(client_info: dict | None, server_ip: str) -> dict: - """服务端 IP 覆盖自报 ip 键;服务端为空时保留自报(空串不得冲掉客户端值)。""" + """服务端 IP 覆盖自报 ip 键;服务端为空时保留自报(空串不得冲掉客户端值)。""" merged = dict(client_info or {}) if server_ip: merged["ip"] = server_ip @@ -58,8 +58,8 @@ async def chat_session(req: SessionRequest, request: Request): `raw_info` (only when non-None). `client_info` is stored for analytics only (ip/ua/device-id/lang) and never fed to the AI prompt. """ - # 服务端可信 IP 同步自上游 ai-kefu:XFF 第一段优先,无 XFF 用直连 host - # 覆盖客户端自报(可伪造面最小);全空保留自报。?site= 处理不变。 + # 服务端可信 IP 同步自上游 ai-kefu:XFF 第一段优先,无 XFF 用直连 host + # 覆盖客户端自报(可伪造面最小);全空保留自报。?site= 处理不变。 client_info = _merge_client_info(req.client_info, _server_ip(request)) # Task 10: ?site= 记入 client_info 作会话站点标(与 WS init 的 diff --git a/tests/test_lead_push_cc.py b/tests/test_lead_push_cc.py index f92673f..16f9467 100644 --- a/tests/test_lead_push_cc.py +++ b/tests/test_lead_push_cc.py @@ -6,7 +6,8 @@ monkeypatch settings.PUSH_ENABLED → dispatch_lead_push → 查 request_payload 断言按 jc(Task 6)主线重锚,语义等价: - 派生键名: 上游 "区号未确认" → jc "号码补全说明"(号码补全的推送披露口); - 条目措辞: 上游 "渠道(标注)" → jc completion_note 的 "原文→e164+标注" - (标注常量与 cc_completion 同源,双钉防漂移); + (标注常量与 cc_completion 同源;字节级措辞钉在 test_lead_push_service.py / + test_email_rendering.py,跨文件互补); - NOTE_CUSTOMER 已在 jc 并入 NOTE_UNCONFIRMED(customer_stated 未确认与 IP 推测同口径标注,上游的措辞区分被主线统一); - confirmed 入参传裸号原文(jc 生产契约: dispatch 收 normalize_contacts @@ -73,7 +74,8 @@ def test_push_payload_uses_e164_and_filters_underscore(async_db, monkeypatch): p = log.request_payload assert p["contact"]["whatsapp"] == "+254712345678" # 契约值=e164(Task6投影) assert not [k for k in p["profile"] if str(k).startswith("_")] # _cc/_secret不外泄 - # 双钉: 与常量同源 + 字节级措辞(改常量必须联动,措辞本身不得漂移) + # 常量同源钉在本文件,字节级措辞钉在 test_lead_push_service.py / + # test_email_rendering.py(跨文件互补) assert p["profile"]["号码补全说明"] == f"0712345678→+254712345678{NOTE_UNCONFIRMED}" @@ -118,7 +120,8 @@ def test_push_payload_customer_stated_note(async_db, monkeypatch): e164="+256712345678")}}) log = _dispatch(async_db, {"whatsapp": "0712345678"}) p = log.request_payload - # 双钉: 与常量同源 + 字节级措辞(改常量必须联动,措辞本身不得漂移) + # 常量同源钉在本文件,字节级措辞钉在 test_lead_push_service.py / + # test_email_rendering.py(跨文件互补) assert p["profile"]["号码补全说明"] == f"0712345678→+256712345678{NOTE_UNCONFIRMED}" assert p["contact"]["whatsapp"] == "+256712345678"