"""CPS 群发短链 + 点击 数据访问(用户侧跳转端点 + admin 生成/统计 共用)。 短码生成去掉易混字符;点击按 (ip, ua) 近似去重 UV。点击量级小、admin 低频,统计在 Python 侧聚合,跨 PG/SQLite 无方言坑(与 admin cps repo 同风格)。 """ from __future__ import annotations import secrets from datetime import datetime from sqlalchemy import select from sqlalchemy.orm import Session from app.models.cps_link import CpsClick, CpsLink # 去掉易混字符 0/O/1/l/I,运营/用户肉眼复制不易错 _CODE_ALPHABET = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789" def _gen_code(n: int = 8) -> str: return "".join(secrets.choice(_CODE_ALPHABET) for _ in range(n)) def get_by_code(db: Session, code: str) -> CpsLink | None: return db.execute(select(CpsLink).where(CpsLink.code == code)).scalar_one_or_none() def create_link( db: Session, *, group_id: int, activity_id: int, sid: str | None, target_url: str, platform: str = "meituan", commit: bool = True, ) -> CpsLink: """生成唯一短码并存库。冲突重试 5 次,仍冲突用 12 位兜底(概率近 0)。""" code = _gen_code() for _ in range(5): if get_by_code(db, code) is None: break code = _gen_code() else: code = _gen_code(12) link = CpsLink( code=code, group_id=group_id, activity_id=activity_id, sid=sid, target_url=target_url, platform=platform, ) db.add(link) if commit: db.commit() db.refresh(link) else: db.flush() return link def record_click( db: Session, *, link: CpsLink, ip: str | None = None, ua: str | None = None, event_type: str = "visit", openid: str | None = None, ) -> None: """记一条点击。event_type: visit(进落地页/被跳转) | copy(淘宝点了"复制口令")。 openid: 微信网页授权拿到的用户标识(非微信/未授权为空),做用户级群统计。""" db.add(CpsClick( link_id=link.id, group_id=link.group_id, sid=link.sid, event_type=event_type, ip=ip, ua=(ua[:500] if ua else None), openid=openid, )) db.commit() def click_stats_by_group( db: Session, *, date_from: datetime | None = None, date_to: datetime | None = None, ) -> dict[int, dict]: """按 group_id 聚合点击 → {group_id: {pv, uv, copy_pv, copy_uv}}。 visit 计入 pv/uv,copy(淘宝复制口令) 计入 copy_pv/copy_uv;uv 按 (ip, ua) 近似去重。""" stmt = select(CpsClick) if date_from is not None: stmt = stmt.where(CpsClick.clicked_at >= date_from) if date_to is not None: stmt = stmt.where(CpsClick.clicked_at <= date_to) clicks = list(db.execute(stmt).scalars().all()) agg: dict[int, dict] = {} seen_visit: dict[int, set] = {} seen_copy: dict[int, set] = {} for c in clicks: a = agg.setdefault(c.group_id, {"pv": 0, "uv": 0, "copy_pv": 0, "copy_uv": 0}) key = (c.ip, c.ua) if c.event_type == "copy": a["copy_pv"] += 1 s = seen_copy.setdefault(c.group_id, set()) if key not in s: s.add(key) a["copy_uv"] += 1 else: a["pv"] += 1 s = seen_visit.setdefault(c.group_id, set()) if key not in s: s.add(key) a["uv"] += 1 return agg