"""新手引导视频(领券等候浮层前 N 次替代广告)。 路由前缀 `/api/v1/guide-video`(均需 Bearer): POST /start 这次浮层放引导视频还是放广告?命中则**当场计次**并下发 play_token POST /reward 播完 / 中途关闭都调,按 play_token 幂等发固定金币 发币额度以**服务端配置**为准(运营后台可改),客户端只报"播完/关闭",报不了金额, 所以被破解也刷不到超额金币;次数上限由 guide_video_play 行数(按账号)硬卡。 """ from __future__ import annotations import logging from fastapi import APIRouter, Depends from app.api.deps import CurrentUser, DbSession from app.core.ratelimit import rate_limit from app.repositories import guide_video as crud_guide from app.schemas.guide_video import ( GuideVideoRewardIn, GuideVideoRewardOut, GuideVideoStartIn, GuideVideoStartOut, ) logger = logging.getLogger("shagua.guide_video") router = APIRouter(prefix="/api/v1/guide-video", tags=["guide-video"]) @router.post( "/start", response_model=GuideVideoStartOut, summary="领券浮层是否放新手引导视频(命中即计次)", dependencies=[Depends(rate_limit(60, 60, "guide-video-start"))], ) def start(payload: GuideVideoStartIn, user: CurrentUser, db: DbSession) -> GuideVideoStartOut: """开播即计数:返回 should_play=True 时服务端已写下这一次,客户端必须真的播。 没配视频 / 开关关 / 次数用完 → should_play=False,客户端照旧走广告链路(行为不变)。 """ result = crud_guide.start_play(db, user.id, scene=payload.scene or "coupon") logger.info( "guide video start user_id=%d scene=%s should_play=%s seq=%d remaining=%d", user.id, payload.scene, result["should_play"], result["seq"], result["remaining"], ) return GuideVideoStartOut(**result) @router.post( "/reward", response_model=GuideVideoRewardOut, summary="引导视频发金币(播完/中途关闭都发,play_token 幂等)", dependencies=[Depends(rate_limit(60, 60, "guide-video-reward"))], ) def reward(payload: GuideVideoRewardIn, user: CurrentUser, db: DbSession) -> GuideVideoRewardOut: result = crud_guide.grant_play( db, user.id, play_token=payload.play_token, completed=payload.completed ) logger.info( "guide video reward user_id=%d token=%s completed=%s granted=%s coin=%d", user.id, payload.play_token[:12], payload.completed, result["granted"], result["coin"], ) return GuideVideoRewardOut(**result)