feat: 服务层配置接线——email/push/rate_limit/AI凭据/行为开关/映射表/种子键

Task 9: 服务层对全局 settings 的业务引用清零,每个 DEFAULT_CONFIG 组
都有真实消费方。resolve_*_settings(email/push/ai)以空槽回退 env
(平台默认层);contact 行为开关/种子白名单/国名扩表/限流上限经
manager 的 runtime.config 接线。收口: lead_push session_factory 必填、
config.py 死代码清理、PromptProvider frozen、no_contact 视图直取、
AIService wall 参数定型 CompiledWall。403 测试全绿(389+14)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-12 21:37:48 +08:00
parent 8453d3cc56
commit 32b582aac8
15 changed files with 676 additions and 164 deletions
+11 -5
View File
@@ -8,7 +8,8 @@ Red lines (see spec):
- WS path (`app/api/routes/chat.py`) shares `ConversationManager` with these
routes; both paths stay drift-free.
- `context` seeding is conditional + whitelist (handled in
`ConversationManager.create_conversation`).
`ConversationManager.create_conversation`; Task 9 起白名单 =
租户 config.engine.context_seed_keys——jiancai product_*/auto vehicle_*).
- The message route consumes `stream_process_message`; lead email is
triggered inside `ConversationManager` when contact info is detected
(contact-driven-lead-email plan).
@@ -20,6 +21,7 @@ from sqlalchemy import select
from app.engine.models import ConversationSource, ConversationStatus, Message
from app.engine.routes.runtime import get_runtime
from app.engine.services.conversation import ConversationManager
from app.engine.services.rate_limit import DEFAULT_MAX
from app.engine.services.rate_limit import check as rate_check
router = APIRouter(prefix="/api/chat", tags=["chat-rest"])
@@ -111,15 +113,19 @@ async def chat_message(req: MessageRequest, request: Request):
`ConversationManager` when contact info is detected
(contact-driven-lead-email plan) — this route carries no email logic.
A light in-memory rate limit (per ip + X-Device-Id, 30/h) guards GLM
spend; only this route is throttled.
A light in-memory rate limit (per tenant + ip + X-Device-Id, rolling 1h)
guards GLM spend; only this route is throttled. Task 9: 桶键加租户维度
(两租户同出口 IP 不互挤配额),上限读租户 config.engine.rate_limit_per_hour
(默认 30,与出厂一致)。
"""
runtime = get_runtime(request)
ip = request.client.host if request.client else ""
dev = request.headers.get("X-Device-Id", "")
if not rate_check(ip, dev):
engine_cfg = runtime.config.data.get("engine") or {}
limit = engine_cfg.get("rate_limit_per_hour") or DEFAULT_MAX
if not rate_check(runtime.config.slug, ip, dev, max_requests=limit):
raise HTTPException(status_code=429, detail="rate limit exceeded")
runtime = get_runtime(request)
async with runtime.session_factory() as db:
manager = ConversationManager(db, runtime=runtime)
conv = await manager.get_by_session(req.session_id)