From 1ed59cff8348bf68df3a71b46f51216c65f07741 Mon Sep 17 00:00:00 2001 From: zzhyyyyy <2685922758@qq.com> Date: Sat, 27 Jun 2026 22:17:26 +0800 Subject: [PATCH] =?UTF-8?q?fix(ad):=20eCPM=20=E4=B8=8A=E6=8A=A5=E6=92=9E?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E5=94=AF=E4=B8=80=E7=BA=A6=E6=9D=9F=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E6=8A=9B=20500,=E6=8C=89=E5=85=A8=E5=B1=80=E5=8F=A3?= =?UTF-8?q?=E5=BE=84=E5=85=9C=E5=BA=95=E5=8E=BB=E9=87=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit create_ecpm_record 撞 uq_ad_ecpm_record_session(只含 ad_session_id、全局唯一) 时,改用与约束同口径的全局查找 _find_by_session_global(仅按 ad_session_id)兜底, 并去掉末尾的 raise。原逻辑兜底用带 user_id 的 find_by_session:不同 user 上报了 同一 ad_session_id 时二次查找仍为 None → raise → 接口 500,违背该接口 best-effort / fire-and-forget 约定(丢一两条不影响业务,穿山甲后台才是结算权威)。 find_by_session 签名与行为不变,另两个调用方(ad_reward、ad.py)不受影响。 Co-Authored-By: Claude Opus 4.8 --- app/repositories/ad_ecpm.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) 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(