diff --git a/app/repositories/ad_ecpm.py b/app/repositories/ad_ecpm.py index 8be3d20..7ffa08a 100644 --- a/app/repositories/ad_ecpm.py +++ b/app/repositories/ad_ecpm.py @@ -53,11 +53,16 @@ def create_ecpm_record( db.commit() except IntegrityError: db.rollback() - if ad_session_id: - existing = find_by_session(db, user_id=user_id, ad_session_id=ad_session_id) - if existing is not None: - return existing - raise + # 撞唯一约束 uq_ad_ecpm_record_session(全局按 ad_session_id、不含 user_id):并发同会话重复上报, + # 或同一 ad_session_id 已被先到的上报占用。本接口 fire-and-forget、best-effort —— 丢一条不影响业务 + # (穿山甲后台才是结算权威),绝不向客户端抛 500。兜底查找须与唯一约束**同口径**(只按 ad_session_id、 + # 不带 user_id):否则不同 user 上报了同一 ad_session_id 时,带 user_id 的查找会漏掉那条别人的记录 → + # 旧逻辑在此 raise 成 500(本应静默吞掉)。 + existing = _find_by_session_global(db, ad_session_id) + if existing is not None: + return existing + # 极少:rollback 后既存记录又查不到(并发删除 / 竞态)。吞掉、返回未入库的内存对象(调用方不读返回值)。 + return rec db.refresh(rec) return rec @@ -76,6 +81,20 @@ def find_by_session( ).scalar_one_or_none() +def _find_by_session_global(db: Session, ad_session_id: str | None) -> AdEcpmRecord | None: + """按 ad_session_id **全局**查找(与唯一约束 uq_ad_ecpm_record_session 同口径,不含 user_id)。 + + 仅 create_ecpm_record 撞约束后兜底用:此时撞的是全局会话约束,既存记录可能属于**另一个 user**, + 带 user_id 的 find_by_session 会漏掉它、导致误判「查无 → raise 500」。其它业务查「某 user 的某次 + 展示 eCPM」仍用 find_by_session(带 user_id,语义更准),不走这里。 + """ + if not ad_session_id: + return None + return db.execute( + select(AdEcpmRecord).where(AdEcpmRecord.ad_session_id == ad_session_id) + ).scalar_one_or_none() + + def count_today(db: Session, user_id: int) -> int: """该用户今日(北京时间)上报的 eCPM 条数,排查/对账辅助用。""" return db.execute(