feat: 信息流广告改"所见即所得"发奖——直接发客户端小球显示金币 (#86)
Reviewed-on: #86 Co-authored-by: wuqi <wuqi@wonderable.ai> Co-committed-by: wuqi <wuqi@wonderable.ai>
This commit was merged in pull request #86.
This commit is contained in:
@@ -411,6 +411,7 @@ def feed_reward(payload: FeedRewardIn, user: CurrentUser, db: DbSession) -> Feed
|
|||||||
app_env=payload.app_env,
|
app_env=payload.app_env,
|
||||||
our_code_id=payload.our_code_id,
|
our_code_id=payload.our_code_id,
|
||||||
aborted=payload.aborted,
|
aborted=payload.aborted,
|
||||||
|
display_coin=payload.display_coin,
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
"feed ad reward user_id=%d event=%s status=%s units=%d coin=%d",
|
"feed ad reward user_id=%d event=%s status=%s units=%d coin=%d",
|
||||||
|
|||||||
@@ -73,16 +73,19 @@ def grant_feed_reward(
|
|||||||
app_env: str | None = None,
|
app_env: str | None = None,
|
||||||
our_code_id: str | None = None,
|
our_code_id: str | None = None,
|
||||||
aborted: bool = False,
|
aborted: bool = False,
|
||||||
|
display_coin: int = 0,
|
||||||
) -> AdFeedRewardRecord:
|
) -> AdFeedRewardRecord:
|
||||||
"""**每条**信息流广告(客户端每条各上报一次)结算奖励。client_event_id 幂等,同号重试不重复发。
|
"""**每条**信息流广告(客户端每条各上报一次)结算奖励。client_event_id 幂等,同号重试不重复发。
|
||||||
|
|
||||||
发奖规则:**一条广告 = 一个单次公式值**(rewards.calculate_ad_reward_coin),因子2(LT)按账号累计
|
发奖规则(所见即所得, 2026-06-27 用户拍板「显示多少给多少」):优先**直接发客户端小球显示的金币
|
||||||
**条**数递进;看满一份时长(unit_count>=1, 即 ≥10 秒)才发,**不逐份累加**。
|
display_coin**;防刷钳到本条「1 份满额」(eCPM 已钳 AD_ECPM_MAX_FEN, 因子2 按账号累计已发条数取档),
|
||||||
|
合法显示(实际因子2 × 进度 p ≤ 1 份)不被砍, 只挡伪造天价值。旧客户端不传 display_coin 时退回
|
||||||
|
「看满 10 秒发整份」(兼容不断币)。因子2(LT)由**客户端**按 granted 行 COUNT(拉自 /feed-reward/units)
|
||||||
|
算进 display_coin, 后端只记 granted 行让该计数自增, 不再服务端重算份值。
|
||||||
- aborted=True(用户中途 ✕ 关闭这条):本条不发,记 status='closed_early'。
|
- aborted=True(用户中途 ✕ 关闭这条):本条不发,记 status='closed_early'。
|
||||||
- 时长不足 10 秒(unit_count==0):记 status='too_short' 不发。
|
- display_coin 为 0 且时长不足一份:记 status='too_short' 不发(不计 LT / 当日上限)。
|
||||||
- 命中当日条数上限:记 status='capped' 不发。
|
- 命中当日条数上限:记 status='capped' 不发。
|
||||||
duration_seconds 是**这一条**的观看秒数。服务端两道硬闸防刷:时长钳到 FEED_MAX_DURATION_SECONDS、
|
duration_seconds 落库留痕(unit_count 字段), 旧端兼容路径据它判是否满 1 份。
|
||||||
eCPM 在 calculate_ad_reward_coin 内钳到 AD_ECPM_MAX_FEN;叠加每日 get_ad_daily_limit 条数上限。
|
|
||||||
feed_scene:点位场景(comparison/coupon/welfare),仅归类落库,不参与计算。
|
feed_scene:点位场景(comparison/coupon/welfare),仅归类落库,不参与计算。
|
||||||
ad_type:广告形态(feed 信息流 / draw Draw 信息流),仅归类落库;**每日上限与因子2(LT)仍按本表
|
ad_type:广告形态(feed 信息流 / draw Draw 信息流),仅归类落库;**每日上限与因子2(LT)仍按本表
|
||||||
全表 unit 累计(feed+draw 共享同一发奖池/上限),不按 ad_type 拆分**。
|
全表 unit 累计(feed+draw 共享同一发奖池/上限),不按 ad_type 拆分**。
|
||||||
@@ -139,14 +142,26 @@ def grant_feed_reward(
|
|||||||
)
|
)
|
||||||
return _commit_record(db, rec, client_event_id)
|
return _commit_record(db, rec, client_event_id)
|
||||||
|
|
||||||
# 整场总时长不足 10 秒,凑不满一份 → 不发,记 too_short 留痕。
|
# 所见即所得(用户 2026-06-27「显示多少给多少」): 优先发**客户端小球显示**的金币 display_coin,
|
||||||
if unit_count == 0:
|
# 钳到本条「1 份满额」防刷(eCPM 已钳 AD_ECPM_MAX_FEN; 合法显示=因子2×p≤1份, 不会被砍)。
|
||||||
|
# 因子2(LT)按账号累计已发条数(granted 行 COUNT), 第 existing_ads+1 条。
|
||||||
|
existing_ads = granted_unit_total(db, user_id)
|
||||||
|
unit_cap = rewards.calculate_ad_reward_coin(ecpm, existing_ads + 1)
|
||||||
|
if display_coin > 0:
|
||||||
|
coin = min(display_coin, unit_cap) # 新端: 所见即所得(直接发小球显示金币)
|
||||||
|
elif unit_count >= 1:
|
||||||
|
coin = unit_cap # 旧端没传 display_coin: 退回「看满 1 份发整份」(兼容)
|
||||||
|
else:
|
||||||
|
coin = 0
|
||||||
|
|
||||||
|
# 显示金币为 0 且没满一份 → 不发, 记 too_short 留痕(不写 granted 行 → 不计 LT / 当日上限)。
|
||||||
|
if coin <= 0:
|
||||||
rec = AdFeedRewardRecord(
|
rec = AdFeedRewardRecord(
|
||||||
client_event_id=client_event_id,
|
client_event_id=client_event_id,
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
reward_date=today,
|
reward_date=today,
|
||||||
duration_seconds=safe_duration,
|
duration_seconds=safe_duration,
|
||||||
unit_count=0,
|
unit_count=unit_count,
|
||||||
ad_session_id=ad_session_id,
|
ad_session_id=ad_session_id,
|
||||||
ecpm_raw=ecpm,
|
ecpm_raw=ecpm,
|
||||||
adn=adn,
|
adn=adn,
|
||||||
@@ -161,15 +176,11 @@ def grant_feed_reward(
|
|||||||
)
|
)
|
||||||
return _commit_record(db, rec, client_event_id)
|
return _commit_record(db, rec, client_event_id)
|
||||||
|
|
||||||
# 一条广告 = 一个「单次公式值」(因子2 按账号累计**条**数, 即第 existing_ads+1 条);看满一份(unit_count>=1)即发,不逐份累加。
|
crud_wallet.grant_coins(
|
||||||
existing_ads = granted_unit_total(db, user_id)
|
db, user_id, coin,
|
||||||
coin = rewards.calculate_ad_reward_coin(ecpm, existing_ads + 1)
|
biz_type="feed_ad_reward", ref_id=client_event_id,
|
||||||
if coin > 0:
|
remark="信息流广告奖励",
|
||||||
crud_wallet.grant_coins(
|
)
|
||||||
db, user_id, coin,
|
|
||||||
biz_type="feed_ad_reward", ref_id=client_event_id,
|
|
||||||
remark="信息流广告奖励",
|
|
||||||
)
|
|
||||||
rec = AdFeedRewardRecord(
|
rec = AdFeedRewardRecord(
|
||||||
client_event_id=client_event_id,
|
client_event_id=client_event_id,
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
|
|||||||
@@ -167,6 +167,11 @@ class FeedRewardIn(BaseModel):
|
|||||||
aborted: bool = Field(
|
aborted: bool = Field(
|
||||||
False, description="用户中途 ✕ 关闭广告(未走完比价):整场不发,记 closed_early"
|
False, description="用户中途 ✕ 关闭广告(未走完比价):整场不发,记 closed_early"
|
||||||
)
|
)
|
||||||
|
display_coin: int = Field(
|
||||||
|
0, ge=0,
|
||||||
|
description="客户端金币小球**本条显示**的金币(所见即所得):后端直接发这个数,钳到本条最大 1 份"
|
||||||
|
"满额防刷。缺省 0 = 旧客户端不传,退回服务端「看满 10 秒发整份」",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FeedRewardOut(BaseModel):
|
class FeedRewardOut(BaseModel):
|
||||||
|
|||||||
Reference in New Issue
Block a user