d7c29c0883
- rewards: 激励视频实发改按 calculate_ad_reward_coin(eCPM, 当日第N次) 公式;AD_REWARD_COIN/MAX_AD_REWARD_COIN 降为历史兼容口径;新增 SIGNIN_BOOST_COIN=2000 - 签到膨胀: Day1-13 看完激励视频额外发固定金币、Day14 不允许 (signin.py / signin-boost) - ad_session_id 幂等: ad_ecpm/ad_reward/ad_feed_reward 记录加 ad_session_id 列 + 唯一索引(新迁移 coin_reward_phase2 / ad_feed_reward_session) - ad.py + schemas + repositories: ecpm-report / feed-reward / reward-status / test-grant 改造;config_schema 增 signin_boost_coin;admin stats overview 补充 - 同步 docs/api + docs/database + docs/integrations/pangle 及 tests(test_ad_reward / test_welfare) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: OuYingJun1024 <1034284404@qq.com> Reviewed-on: #28 Co-authored-by: ouzhou <ouzhou@wonderable.ai> Co-committed-by: ouzhou <ouzhou@wonderable.ai>
130 lines
4.0 KiB
Python
130 lines
4.0 KiB
Python
"""信息流广告奖励 CRUD。
|
|
|
|
点位 2:每展示满 10 秒累计一份奖励,视频完成后一次性入账。当前一期由客户端在
|
|
完成回调后上报;后续若 SDK/S2S 能提供更强确认信号,可继续复用本表的幂等键。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from sqlalchemy import func, select
|
|
from sqlalchemy.exc import IntegrityError
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core import rewards
|
|
from app.core.rewards import cn_today
|
|
from app.models.ad_feed_reward import AdFeedRewardRecord
|
|
from app.repositories import wallet as crud_wallet
|
|
|
|
|
|
FEED_REWARD_UNIT_SECONDS = 10
|
|
|
|
|
|
def _find_by_event(db: Session, client_event_id: str) -> AdFeedRewardRecord | None:
|
|
return db.execute(
|
|
select(AdFeedRewardRecord).where(
|
|
AdFeedRewardRecord.client_event_id == client_event_id
|
|
)
|
|
).scalar_one_or_none()
|
|
|
|
|
|
def _granted_today(db: Session, user_id: int, reward_date: str) -> int:
|
|
return db.execute(
|
|
select(func.count())
|
|
.select_from(AdFeedRewardRecord)
|
|
.where(
|
|
AdFeedRewardRecord.user_id == user_id,
|
|
AdFeedRewardRecord.reward_date == reward_date,
|
|
AdFeedRewardRecord.status == "granted",
|
|
)
|
|
).scalar_one()
|
|
|
|
|
|
def _unit_reward_total(db: Session, user_id: int, ecpm: str, unit_count: int, today: str) -> int:
|
|
"""按每个 10 秒单位逐份计算奖励,LT 使用当天累计奖励份序号。"""
|
|
if unit_count <= 0:
|
|
return 0
|
|
existing_units = db.execute(
|
|
select(func.coalesce(func.sum(AdFeedRewardRecord.unit_count), 0))
|
|
.where(
|
|
AdFeedRewardRecord.user_id == user_id,
|
|
AdFeedRewardRecord.reward_date == today,
|
|
AdFeedRewardRecord.status == "granted",
|
|
)
|
|
).scalar_one()
|
|
total = 0
|
|
for offset in range(1, unit_count + 1):
|
|
total += rewards.calculate_ad_reward_coin(ecpm, int(existing_units) + offset)
|
|
return total
|
|
|
|
|
|
def grant_feed_reward(
|
|
db: Session,
|
|
user_id: int,
|
|
*,
|
|
client_event_id: str,
|
|
ecpm: str,
|
|
duration_seconds: int,
|
|
ad_session_id: str | None = None,
|
|
adn: str | None = None,
|
|
slot_id: str | None = None,
|
|
) -> AdFeedRewardRecord:
|
|
"""完成一条信息流广告后结算奖励。client_event_id 幂等,同号重试不重复发。"""
|
|
existing = _find_by_event(db, client_event_id)
|
|
if existing is not None:
|
|
return existing
|
|
|
|
today = cn_today().isoformat()
|
|
safe_duration = max(0, min(duration_seconds, 24 * 60 * 60))
|
|
unit_count = safe_duration // FEED_REWARD_UNIT_SECONDS
|
|
|
|
if _granted_today(db, user_id, today) >= rewards.get_ad_daily_limit(db):
|
|
rec = AdFeedRewardRecord(
|
|
client_event_id=client_event_id,
|
|
user_id=user_id,
|
|
reward_date=today,
|
|
duration_seconds=safe_duration,
|
|
unit_count=unit_count,
|
|
ad_session_id=ad_session_id,
|
|
ecpm_raw=ecpm,
|
|
adn=adn,
|
|
slot_id=slot_id,
|
|
coin=0,
|
|
status="capped",
|
|
)
|
|
return _commit_record(db, rec, client_event_id)
|
|
|
|
coin = _unit_reward_total(db, user_id, ecpm, unit_count, today)
|
|
if coin > 0:
|
|
crud_wallet.grant_coins(
|
|
db, user_id, coin,
|
|
biz_type="feed_ad_reward", ref_id=client_event_id,
|
|
remark=f"信息流广告奖励 {unit_count}份",
|
|
)
|
|
rec = AdFeedRewardRecord(
|
|
client_event_id=client_event_id,
|
|
user_id=user_id,
|
|
reward_date=today,
|
|
duration_seconds=safe_duration,
|
|
unit_count=unit_count,
|
|
ad_session_id=ad_session_id,
|
|
ecpm_raw=ecpm,
|
|
adn=adn,
|
|
slot_id=slot_id,
|
|
coin=coin,
|
|
status="granted",
|
|
)
|
|
return _commit_record(db, rec, client_event_id)
|
|
|
|
|
|
def _commit_record(db: Session, rec: AdFeedRewardRecord, client_event_id: str) -> AdFeedRewardRecord:
|
|
db.add(rec)
|
|
try:
|
|
db.commit()
|
|
except IntegrityError:
|
|
db.rollback()
|
|
existing = _find_by_event(db, client_event_id)
|
|
if existing is not None:
|
|
return existing
|
|
raise
|
|
db.refresh(rec)
|
|
return rec
|