b50495bebe
短信(SMS_MOCK 切 mock/real): - integrations/sms.py 重写: real 模式走极光短信 REST /v1/messages 自定义验证码(本服务 secrets 生成 6 位码 + 进程内存 + 本地校验一次性/防爆破), 鉴权复用极光一键登录 JG_APP_KEY/MASTER_SECRET (同一极光应用, 上线只需 SMS_MOCK=false); mock 仍"任意6位通过"不动其余测试 - 防刷四层: 单号冷却 + 单号每日上限 + 单IP rate_limit(/sms/send 10/min、/sms/login 20/min) + 单码失败次数作废; SmsError 带 status_code 映射 429/503/400 - config 增 SMS_SEND_ENDPOINT/SIGN_ID/TEMPLATE_ID/CODE_LENGTH/DAILY_LIMIT/MAX_VERIFY_ATTEMPTS; test_auth 加 real 模式单测; sms.md/后端技术实现/待办账本同步 admin 后台(app/admin/ 独立子应用, uvicorn app.admin.main:admin_app :8771): - 复用主仓 models/repositories/integrations + 同库, 鉴权完全隔离(ADMIN_JWT_SECRET≠JWT_SECRET_KEY + payload typ=admin + bcrypt 密码 + 可选 IP 白名单); 主 app 不 import 本包, admin 崩不影响主进程 - 路由: 登录 / 账号管理(RBAC: super_admin·finance·operator) / 用户列表+360详情+封禁+手动调币 / 钱包流水 / 提现重试对账 / 反馈工单 / 数据大盘; 全写操作落 admin_audit_log(涉钱与业务写同事务) - 涉钱逻辑(调微信/退款/对账)复用 app.repositories.wallet 不重写 - 新增 models/admin.py(AdminUser/AdminAuditLog) + admin_tables 迁移 + create_admin.py + deploy/shaguabijia-admin.service; 依赖加 bcrypt Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
92 lines
3.4 KiB
Python
92 lines
3.4 KiB
Python
"""admin 大盘聚合查询(全局 count/sum/DAU/成功率)。全部只读、不改任何数据。
|
|
|
|
⚠️ 性能:这些是全表 count/sum,P0 数据量小够用;用户量上来后热点字段(user.created_at /
|
|
user.last_login_at / comparison_record.status / withdraw_order.status)要加索引,或改增量统计表。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
from sqlalchemy import func, select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.models.comparison import ComparisonRecord
|
|
from app.models.feedback import Feedback
|
|
from app.models.user import User
|
|
from app.models.wallet import CoinTransaction, WithdrawOrder
|
|
|
|
_BEIJING = timezone(timedelta(hours=8))
|
|
|
|
|
|
def _beijing_today_start_utc() -> datetime:
|
|
"""北京时间今天 0 点对应的 UTC 时刻(DAU / 今日新增按北京时区切天)。"""
|
|
now_bj = datetime.now(_BEIJING)
|
|
start_bj = now_bj.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
return start_bj.astimezone(timezone.utc)
|
|
|
|
|
|
def dashboard_overview(db: Session) -> dict:
|
|
today_start = _beijing_today_start_utc()
|
|
|
|
def _count(model, *conds) -> int:
|
|
stmt = select(func.count(model.id))
|
|
if conds:
|
|
stmt = stmt.where(*conds)
|
|
return db.execute(stmt).scalar_one()
|
|
|
|
def _sum(col, *conds) -> int:
|
|
stmt = select(func.coalesce(func.sum(col), 0))
|
|
if conds:
|
|
stmt = stmt.where(*conds)
|
|
return db.execute(stmt).scalar_one()
|
|
|
|
# ===== 用户 =====
|
|
by_status = dict(
|
|
db.execute(select(User.status, func.count(User.id)).group_by(User.status)).all()
|
|
)
|
|
|
|
# ===== 提现状态分布 =====
|
|
wd_by_status = dict(
|
|
db.execute(
|
|
select(WithdrawOrder.status, func.count(WithdrawOrder.id)).group_by(
|
|
WithdrawOrder.status
|
|
)
|
|
).all()
|
|
)
|
|
|
|
# ===== 比价 =====
|
|
comparison_total = _count(ComparisonRecord)
|
|
comparison_success = _count(ComparisonRecord, ComparisonRecord.status == "success")
|
|
success_rate = round(comparison_success / comparison_total, 4) if comparison_total else 0.0
|
|
|
|
return {
|
|
"users": {
|
|
"total": _count(User),
|
|
"active": by_status.get("active", 0),
|
|
"disabled": by_status.get("disabled", 0),
|
|
"deleted": by_status.get("deleted", 0),
|
|
"new_today": _count(User, User.created_at >= today_start),
|
|
"dau": _count(User, User.last_login_at >= today_start),
|
|
},
|
|
"coins": {
|
|
# 累计发放金币(coin_transaction 里所有 amount>0 之和;负数是兑换/扣减不计)
|
|
"granted_total": _sum(CoinTransaction.amount, CoinTransaction.amount > 0),
|
|
},
|
|
"cash": {
|
|
"withdraw_success_cents": _sum(
|
|
WithdrawOrder.amount_cents, WithdrawOrder.status == "success"
|
|
),
|
|
"withdraw_pending_count": wd_by_status.get("pending", 0),
|
|
"withdraw_success_count": wd_by_status.get("success", 0),
|
|
"withdraw_failed_count": wd_by_status.get("failed", 0),
|
|
},
|
|
"comparison": {
|
|
"total": comparison_total,
|
|
"success": comparison_success,
|
|
"success_rate": success_rate,
|
|
},
|
|
"feedback": {"new": _count(Feedback, Feedback.status == "new")},
|
|
# CPS 收入数据源未接(referral-link 只换链接,转化/佣金未回收)→ 前端显示"待接入"。
|
|
"cps": {"available": False, "note": "CPS 转化数据未接入(P2)"},
|
|
}
|