feat(ad): 穿山甲广告配置后台化(app_id/各位ID/验签密钥/各场景开关)
- app_config 存 ad_config(默认=客户端当前内置 ID,空库维持现状) - GET /platform/ad-config 下发客户端(不含验签密钥 reward_mkey) - admin GET/PATCH /admin/api/ad-config 读写(operator/finance + 审计,mkey 不记明文) - pangle 回调验签 m-key 优先读后台配置、.env 兜底兼容 - 比价/领券信息流位拆分(compare/coupon_feed_code_id) - 客户端需发版接入 /platform/ad-config 才生效;本次后端就绪、不影响现状 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+11
-2
@@ -24,6 +24,7 @@ from app.repositories import ad_ecpm as crud_ecpm
|
||||
from app.repositories import ad_feed_reward as crud_feed
|
||||
from app.repositories import ad_reward as crud_ad
|
||||
from app.repositories import ad_watch as crud_watch
|
||||
from app.repositories import app_config
|
||||
from app.repositories import signin as crud_signin
|
||||
from app.schemas.ad import (
|
||||
AdRewardStatusOut,
|
||||
@@ -80,14 +81,22 @@ def pangle_callback(request: Request, db: DbSession) -> PangleCallbackOut:
|
||||
验签失败 403(留给真请求重试);参数缺/坏或 user 不存在 → is_verify=false + reason(不发,不重试)。
|
||||
granted / capped → is_verify=true + reason=0。
|
||||
"""
|
||||
if not settings.pangle_callback_configured:
|
||||
if not settings.PANGLE_CALLBACK_ENABLED:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="pangle callback not configured"
|
||||
)
|
||||
|
||||
params = dict(request.query_params)
|
||||
|
||||
if not pangle.verify_callback_sign_any(params, settings.pangle_reward_secrets):
|
||||
# 验签密钥:admin 后台配的 reward_mkey 优先,.env 的 PANGLE_REWARD_SECRET* 兜底(兼容/过渡)。
|
||||
# 换激励位时后台同步换 mkey 即可,无需改 .env。两者都空才视为未配置。
|
||||
ad_mkey = app_config.get_ad_config(db).get("reward_mkey") or ""
|
||||
secrets = ([ad_mkey] if ad_mkey else []) + settings.pangle_reward_secrets
|
||||
if not secrets:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="pangle callback not configured"
|
||||
)
|
||||
if not pangle.verify_callback_sign_any(params, secrets):
|
||||
logger.warning("pangle callback bad sign trans_id=%s", params.get("trans_id"))
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="bad sign")
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ from app.repositories import app_config
|
||||
from app.repositories import ops_marquee as marquee_crud
|
||||
from app.repositories import ops_stat as crud
|
||||
from app.schemas.platform import (
|
||||
AdConfigPublicOut,
|
||||
AppFlagsOut,
|
||||
AppVersionOut,
|
||||
PlatformStatsOut,
|
||||
@@ -54,6 +55,22 @@ def flags(db: DbSession) -> AppFlagsOut:
|
||||
)
|
||||
|
||||
|
||||
@router.get("/ad-config", response_model=AdConfigPublicOut, summary="客户端拉广告配置(穿山甲ID+场景开关,不鉴权)")
|
||||
def ad_config(db: DbSession) -> AdConfigPublicOut:
|
||||
"""客户端启动/每场广告前拉,缓存后用:app_id + 各位ID + 各场景开关。
|
||||
不含验签密钥;空库回退默认(=客户端内置值,维持现状)。"""
|
||||
c = app_config.get_ad_config(db)
|
||||
return AdConfigPublicOut(
|
||||
app_id=c["app_id"],
|
||||
reward_code_id=c["reward_code_id"],
|
||||
compare_feed_code_id=c["compare_feed_code_id"],
|
||||
coupon_feed_code_id=c["coupon_feed_code_id"],
|
||||
reward_enabled=c["reward_enabled"],
|
||||
compare_ad_enabled=c["compare_ad_enabled"],
|
||||
coupon_ad_enabled=c["coupon_ad_enabled"],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/app-version", response_model=AppVersionOut, summary="最新 App 版本(OTA 检查更新,不鉴权)")
|
||||
def app_version(db: DbSession) -> AppVersionOut:
|
||||
"""客户端启动 / 手动检查更新时拉取。不鉴权:版本信息非敏感,且检查更新可能在登录前。
|
||||
|
||||
Reference in New Issue
Block a user