Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c72e052528 |
@@ -1171,24 +1171,30 @@ def user_reward_stats(
|
|||||||
acc = db.get(CoinAccount, user_id) # 现金余额:当前快照,不随窗口
|
acc = db.get(CoinAccount, user_id) # 现金余额:当前快照,不随窗口
|
||||||
cash_balance = acc.cash_balance_cents if acc else 0
|
cash_balance = acc.cash_balance_cents if acc else 0
|
||||||
|
|
||||||
rv = list(db.execute(
|
# 只投影本统计实际使用的列。避免滚动发布或旧本地库尚未补齐无关新列时,
|
||||||
select(AdRewardRecord).where(
|
# SQLAlchemy 因 select(ORM) 自动展开整表字段而让提现详情整体 500。
|
||||||
|
rv = db.execute(
|
||||||
|
select(AdRewardRecord.ecpm_raw, AdRewardRecord.coin).where(
|
||||||
AdRewardRecord.user_id == user_id,
|
AdRewardRecord.user_id == user_id,
|
||||||
AdRewardRecord.reward_scene == "reward_video",
|
AdRewardRecord.reward_scene == "reward_video",
|
||||||
AdRewardRecord.status == "granted",
|
AdRewardRecord.status == "granted",
|
||||||
*_window_conds(AdRewardRecord.created_at, date_from, date_to),
|
*_window_conds(AdRewardRecord.created_at, date_from, date_to),
|
||||||
)
|
)
|
||||||
).scalars())
|
).all()
|
||||||
rv_ecpms = [rewards.parse_ecpm_fen(r.ecpm_raw) for r in rv if r.ecpm_raw]
|
rv_ecpms = [rewards.parse_ecpm_fen(r.ecpm_raw) for r in rv if r.ecpm_raw]
|
||||||
rv_coins = sum(r.coin for r in rv)
|
rv_coins = sum(r.coin for r in rv)
|
||||||
|
|
||||||
feed = list(db.execute(
|
feed = db.execute(
|
||||||
select(AdFeedRewardRecord).where(
|
select(
|
||||||
|
AdFeedRewardRecord.unit_count,
|
||||||
|
AdFeedRewardRecord.ecpm_raw,
|
||||||
|
AdFeedRewardRecord.coin,
|
||||||
|
).where(
|
||||||
AdFeedRewardRecord.user_id == user_id,
|
AdFeedRewardRecord.user_id == user_id,
|
||||||
AdFeedRewardRecord.status == "granted",
|
AdFeedRewardRecord.status == "granted",
|
||||||
*_window_conds(AdFeedRewardRecord.created_at, date_from, date_to),
|
*_window_conds(AdFeedRewardRecord.created_at, date_from, date_to),
|
||||||
)
|
)
|
||||||
).scalars())
|
).all()
|
||||||
feed_ecpms = [rewards.parse_ecpm_fen(f.ecpm_raw) for f in feed if f.ecpm_raw]
|
feed_ecpms = [rewards.parse_ecpm_fen(f.ecpm_raw) for f in feed if f.ecpm_raw]
|
||||||
feed_coins = sum(f.coin for f in feed)
|
feed_coins = sum(f.coin for f in feed)
|
||||||
|
|
||||||
@@ -1246,8 +1252,14 @@ def user_coin_records(
|
|||||||
signin_from = date_from + timedelta(hours=8) if date_from is not None else None
|
signin_from = date_from + timedelta(hours=8) if date_from is not None else None
|
||||||
signin_to = date_to + timedelta(hours=8) if date_to is not None else None
|
signin_to = date_to + timedelta(hours=8) if date_to is not None else None
|
||||||
|
|
||||||
|
# 三类来源都只取页面需要的列,避免无关 ORM 新列造成旧库查询失败。
|
||||||
for rec in db.execute(
|
for rec in db.execute(
|
||||||
select(AdRewardRecord)
|
select(
|
||||||
|
AdRewardRecord.reward_scene,
|
||||||
|
AdRewardRecord.created_at,
|
||||||
|
AdRewardRecord.ecpm_raw,
|
||||||
|
AdRewardRecord.coin,
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
AdRewardRecord.user_id == user_id,
|
AdRewardRecord.user_id == user_id,
|
||||||
AdRewardRecord.status == "granted",
|
AdRewardRecord.status == "granted",
|
||||||
@@ -1255,7 +1267,7 @@ def user_coin_records(
|
|||||||
)
|
)
|
||||||
.order_by(AdRewardRecord.created_at.desc())
|
.order_by(AdRewardRecord.created_at.desc())
|
||||||
.limit(fetch)
|
.limit(fetch)
|
||||||
).scalars():
|
).all():
|
||||||
is_video = rec.reward_scene == "reward_video"
|
is_video = rec.reward_scene == "reward_video"
|
||||||
rows.append({
|
rows.append({
|
||||||
"source": rec.reward_scene,
|
"source": rec.reward_scene,
|
||||||
@@ -1266,7 +1278,12 @@ def user_coin_records(
|
|||||||
})
|
})
|
||||||
|
|
||||||
for rec in db.execute(
|
for rec in db.execute(
|
||||||
select(AdFeedRewardRecord)
|
select(
|
||||||
|
AdFeedRewardRecord.feed_scene,
|
||||||
|
AdFeedRewardRecord.created_at,
|
||||||
|
AdFeedRewardRecord.ecpm_raw,
|
||||||
|
AdFeedRewardRecord.coin,
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
AdFeedRewardRecord.user_id == user_id,
|
AdFeedRewardRecord.user_id == user_id,
|
||||||
AdFeedRewardRecord.status == "granted",
|
AdFeedRewardRecord.status == "granted",
|
||||||
@@ -1274,7 +1291,7 @@ def user_coin_records(
|
|||||||
)
|
)
|
||||||
.order_by(AdFeedRewardRecord.created_at.desc())
|
.order_by(AdFeedRewardRecord.created_at.desc())
|
||||||
.limit(fetch)
|
.limit(fetch)
|
||||||
).scalars():
|
).all():
|
||||||
rows.append({
|
rows.append({
|
||||||
"source": "feed",
|
"source": "feed",
|
||||||
"source_label": _FEED_SCENE_LABEL.get(rec.feed_scene, "信息流广告"),
|
"source_label": _FEED_SCENE_LABEL.get(rec.feed_scene, "信息流广告"),
|
||||||
@@ -1284,7 +1301,7 @@ def user_coin_records(
|
|||||||
})
|
})
|
||||||
|
|
||||||
for rec in db.execute(
|
for rec in db.execute(
|
||||||
select(CoinTransaction)
|
select(CoinTransaction.created_at, CoinTransaction.amount)
|
||||||
.where(
|
.where(
|
||||||
CoinTransaction.user_id == user_id,
|
CoinTransaction.user_id == user_id,
|
||||||
CoinTransaction.biz_type == "signin",
|
CoinTransaction.biz_type == "signin",
|
||||||
@@ -1292,7 +1309,7 @@ def user_coin_records(
|
|||||||
)
|
)
|
||||||
.order_by(CoinTransaction.created_at.desc())
|
.order_by(CoinTransaction.created_at.desc())
|
||||||
.limit(fetch)
|
.limit(fetch)
|
||||||
).scalars():
|
).all():
|
||||||
rows.append({
|
rows.append({
|
||||||
"source": "signin",
|
"source": "signin",
|
||||||
"source_label": "签到",
|
"source_label": "签到",
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ from datetime import datetime
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
from sqlalchemy import event
|
||||||
|
|
||||||
from app.admin.main import admin_app
|
from app.admin.main import admin_app
|
||||||
from app.admin.repositories import admin_user as admin_repo
|
from app.admin.repositories import admin_user as admin_repo
|
||||||
from app.db.session import SessionLocal
|
from app.db.session import SessionLocal, engine
|
||||||
from app.models.comparison import ComparisonRecord
|
from app.models.comparison import ComparisonRecord
|
||||||
from app.models.feedback import Feedback
|
from app.models.feedback import Feedback
|
||||||
from app.models.wallet import CashTransaction, WithdrawOrder
|
from app.models.wallet import CashTransaction, WithdrawOrder
|
||||||
@@ -132,6 +133,35 @@ def test_user_list_and_detail(admin_client: TestClient, admin_token: str) -> Non
|
|||||||
assert admin_client.get("/admin/api/users/999999", headers=_auth(admin_token)).status_code == 404
|
assert admin_client.get("/admin/api/users/999999", headers=_auth(admin_token)).status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_reward_detail_does_not_select_unrelated_new_ad_columns(
|
||||||
|
admin_client: TestClient, admin_token: str
|
||||||
|
) -> None:
|
||||||
|
"""旧库缺少无关新列时,提现详情的统计和金币记录仍应可读。"""
|
||||||
|
uid = _seed_user_with_data("13800000022")
|
||||||
|
|
||||||
|
def reject_full_ad_reward_projection(
|
||||||
|
_conn, _cursor, statement: str, _parameters, _context, _executemany
|
||||||
|
) -> None:
|
||||||
|
if "ad_reward_record.boost_round_id" in statement:
|
||||||
|
raise AssertionError("提现详情不应查询未使用的 boost_round_id")
|
||||||
|
|
||||||
|
event.listen(engine, "before_cursor_execute", reject_full_ad_reward_projection)
|
||||||
|
try:
|
||||||
|
stats = admin_client.get(
|
||||||
|
f"/admin/api/users/{uid}/reward-stats", headers=_auth(admin_token)
|
||||||
|
)
|
||||||
|
records = admin_client.get(
|
||||||
|
f"/admin/api/users/{uid}/coin-records",
|
||||||
|
params={"limit": 10, "cursor": 0},
|
||||||
|
headers=_auth(admin_token),
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
event.remove(engine, "before_cursor_execute", reject_full_ad_reward_projection)
|
||||||
|
|
||||||
|
assert stats.status_code == 200, stats.text
|
||||||
|
assert records.status_code == 200, records.text
|
||||||
|
|
||||||
|
|
||||||
def test_user_filter_by_status(admin_client: TestClient, admin_token: str) -> None:
|
def test_user_filter_by_status(admin_client: TestClient, admin_token: str) -> None:
|
||||||
_seed_user_with_data("13800000003")
|
_seed_user_with_data("13800000003")
|
||||||
r = admin_client.get("/admin/api/users", params={"status": "active"}, headers=_auth(admin_token))
|
r = admin_client.get("/admin/api/users", params={"status": "active"}, headers=_auth(admin_token))
|
||||||
|
|||||||
Reference in New Issue
Block a user