from urllib.parse import urlparse from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.models.visitor import VisitorInfo from app.models.domain import MonitoredDomain from app.schemas.visitor import VisitorInfoCreate from app.services.sms import send_sms_code, generate_verify_code import logging logger = logging.getLogger(__name__) def _extract_domain_from_referer(referer: str | None) -> str | None: """从 Referer 头提取域名""" if not referer: return None try: parsed = urlparse(referer) host = parsed.hostname if not host: host = referer.strip().rstrip("/") if not host: return None netloc = parsed.netloc if parsed.netloc else host return host, netloc except Exception: return None async def store_visitor_info( db: AsyncSession, referer: str | None, payload: VisitorInfoCreate ) -> dict: """通过 Referer 匹配域名,存储联系人信息。同一域名覆盖更新。""" result = _extract_domain_from_referer(referer) if not result: return {"success": False, "message": "缺少或无效的 Referer 头"} referer_host, referer_netloc = result # 匹配监控域名(支持多种存储格式) domain_result = await db.execute( select(MonitoredDomain).where( MonitoredDomain.is_active == True, MonitoredDomain.domain.in_([ referer_netloc, referer_host, f"http://{referer_netloc}", f"https://{referer_netloc}", ]), ) ) domain = domain_result.scalars().first() if not domain: return {"success": False, "message": "未找到匹配的域名"} # Upsert 联系人信息 existing = await db.execute( select(VisitorInfo).where(VisitorInfo.domain_id == domain.id) ) visitor = existing.scalar_one_or_none() if visitor: visitor.name = payload.name visitor.phone = payload.phone visitor.email = payload.email else: visitor = VisitorInfo( domain_id=domain.id, name=payload.name, phone=payload.phone, email=payload.email, ) db.add(visitor) await db.commit() # 发送验证码短信,并将验证码返回给请求者 code = None if payload.phone: code = generate_verify_code() logger.info("发送验证码短信: phone=%s", payload.phone) ok, reason = await send_sms_code(payload.phone, code) logger.info("验证码短信发送结果: ok=%s, reason=%s", ok, reason) return {"success": True, "message": "联系人信息已保存", "domain": domain.domain, "code": code}