Spaces:
Running
Running
优化流水记录
Browse files- models_sql.py +7 -0
- router_wallet.py +75 -1
models_sql.py
CHANGED
|
@@ -68,6 +68,13 @@ class Transaction(Base):
|
|
| 68 |
prev_hash = Column(String) # 上一笔订单的哈希
|
| 69 |
tx_hash = Column(String) # 本笔订单的哈希 (防篡改)
|
| 70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
# 🚀 P1性能优化:复合索引
|
| 72 |
__table_args__ = (
|
| 73 |
Index('ix_transactions_account_type', 'account', 'tx_type'),
|
|
|
|
| 68 |
prev_hash = Column(String) # 上一笔订单的哈希
|
| 69 |
tx_hash = Column(String) # 本笔订单的哈希 (防篡改)
|
| 70 |
|
| 71 |
+
# 💰 提现相关字段 (仅 WITHDRAW_APPLY 类型交易使用)
|
| 72 |
+
alipay_account = Column(String, nullable=True) # 收款支付宝账号
|
| 73 |
+
real_name = Column(String, nullable=True) # 收款人姓名
|
| 74 |
+
withdraw_status = Column(String, nullable=True) # pending / completed
|
| 75 |
+
payment_order_id = Column(String, nullable=True) # 管理员填写的打款订单号
|
| 76 |
+
net_amount = Column(Integer, nullable=True) # 实际到账金额(用于解冻时准确扣减)
|
| 77 |
+
|
| 78 |
# 🚀 P1性能优化:复合索引
|
| 79 |
__table_args__ = (
|
| 80 |
Index('ix_transactions_account_type', 'account', 'tx_type'),
|
router_wallet.py
CHANGED
|
@@ -13,6 +13,7 @@
|
|
| 13 |
from fastapi import APIRouter, Depends, HTTPException, Request
|
| 14 |
from fastapi.responses import Response
|
| 15 |
from sqlalchemy.orm import Session
|
|
|
|
| 16 |
import time
|
| 17 |
import uuid
|
| 18 |
import hashlib
|
|
@@ -24,6 +25,7 @@ from models_sql import Wallet, Transaction, Ownership, Refund
|
|
| 24 |
from models import RechargeRequest, WithdrawRequest, PurchaseRequest, TipRequest
|
| 25 |
import 数据库连接 as json_db
|
| 26 |
from notifications import add_notification
|
|
|
|
| 27 |
|
| 28 |
# 🔒 P0安全优化:API限流
|
| 29 |
from slowapi import Limiter
|
|
@@ -536,6 +538,10 @@ async def withdraw(request: Request, req: WithdrawRequest, db: Session = Depends
|
|
| 536 |
|
| 537 |
new_tx = Transaction(
|
| 538 |
tx_id=tx_id, account=req.account, tx_type="WITHDRAW", amount=-actual_withdraw,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 539 |
prev_hash=prev_hash, tx_hash=tx_hash
|
| 540 |
)
|
| 541 |
db.add(new_tx)
|
|
@@ -859,4 +865,72 @@ async def refund_purchase(request: Request, account: str, item_id: str, db: Sess
|
|
| 859 |
except Exception as e:
|
| 860 |
db.rollback()
|
| 861 |
logger.error(f"REFUND_ERROR | buyer={account} | item={item_id} | amount={refund_amount} | error={str(e)}")
|
| 862 |
-
raise HTTPException(status_code=500, detail="退款处理失败,请稍后重试")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
from fastapi import APIRouter, Depends, HTTPException, Request
|
| 14 |
from fastapi.responses import Response
|
| 15 |
from sqlalchemy.orm import Session
|
| 16 |
+
from pydantic import BaseModel
|
| 17 |
import time
|
| 18 |
import uuid
|
| 19 |
import hashlib
|
|
|
|
| 25 |
from models import RechargeRequest, WithdrawRequest, PurchaseRequest, TipRequest
|
| 26 |
import 数据库连接 as json_db
|
| 27 |
from notifications import add_notification
|
| 28 |
+
from 安全认证 import require_auth, is_admin
|
| 29 |
|
| 30 |
# 🔒 P0安全优化:API限流
|
| 31 |
from slowapi import Limiter
|
|
|
|
| 538 |
|
| 539 |
new_tx = Transaction(
|
| 540 |
tx_id=tx_id, account=req.account, tx_type="WITHDRAW", amount=-actual_withdraw,
|
| 541 |
+
alipay_account=req.alipayAccount,
|
| 542 |
+
real_name=req.real_name,
|
| 543 |
+
withdraw_status="pending",
|
| 544 |
+
net_amount=net_amount, # 记录实际到账金额,用于解冻时准确扣减
|
| 545 |
prev_hash=prev_hash, tx_hash=tx_hash
|
| 546 |
)
|
| 547 |
db.add(new_tx)
|
|
|
|
| 865 |
except Exception as e:
|
| 866 |
db.rollback()
|
| 867 |
logger.error(f"REFUND_ERROR | buyer={account} | item={item_id} | amount={refund_amount} | error={str(e)}")
|
| 868 |
+
raise HTTPException(status_code=500, detail="退款处理失败,请稍后重试")
|
| 869 |
+
|
| 870 |
+
# ==========================================
|
| 871 |
+
# 💰 管理员提现管理 API
|
| 872 |
+
# ==========================================
|
| 873 |
+
|
| 874 |
+
@router.get("/api/admin/withdrawals")
|
| 875 |
+
async def get_admin_withdrawals(
|
| 876 |
+
status: str = None,
|
| 877 |
+
current_user: str = Depends(require_auth),
|
| 878 |
+
db: Session = Depends(get_db)
|
| 879 |
+
):
|
| 880 |
+
"""管理员查看提现记录列表"""
|
| 881 |
+
if not is_admin(current_user):
|
| 882 |
+
raise HTTPException(status_code=403, detail="需要管理员权限")
|
| 883 |
+
|
| 884 |
+
query = db.query(Transaction).filter(Transaction.tx_type == "WITHDRAW")
|
| 885 |
+
if status:
|
| 886 |
+
query = query.filter(Transaction.withdraw_status == status)
|
| 887 |
+
|
| 888 |
+
records = query.order_by(Transaction.created_at.desc()).all()
|
| 889 |
+
|
| 890 |
+
return {
|
| 891 |
+
"status": "success",
|
| 892 |
+
"data": [{
|
| 893 |
+
"tx_id": r.tx_id,
|
| 894 |
+
"account": r.account,
|
| 895 |
+
"amount": r.amount,
|
| 896 |
+
"alipay_account": r.alipay_account,
|
| 897 |
+
"real_name": r.real_name,
|
| 898 |
+
"withdraw_status": r.withdraw_status,
|
| 899 |
+
"payment_order_id": r.payment_order_id,
|
| 900 |
+
"created_at": r.created_at.isoformat() if r.created_at else None
|
| 901 |
+
} for r in records]
|
| 902 |
+
}
|
| 903 |
+
|
| 904 |
+
class CompleteWithdrawRequest(BaseModel):
|
| 905 |
+
payment_order_id: str
|
| 906 |
+
|
| 907 |
+
@router.post("/api/admin/withdrawals/{tx_id}/complete")
|
| 908 |
+
async def complete_withdrawal(
|
| 909 |
+
tx_id: str,
|
| 910 |
+
req: CompleteWithdrawRequest,
|
| 911 |
+
current_user: str = Depends(require_auth),
|
| 912 |
+
db: Session = Depends(get_db)
|
| 913 |
+
):
|
| 914 |
+
"""管理员确认打款完成"""
|
| 915 |
+
if not is_admin(current_user):
|
| 916 |
+
raise HTTPException(status_code=403, detail="需要管理员权限")
|
| 917 |
+
|
| 918 |
+
tx = db.query(Transaction).filter(Transaction.tx_id == tx_id, Transaction.tx_type == "WITHDRAW").first()
|
| 919 |
+
if not tx:
|
| 920 |
+
raise HTTPException(status_code=404, detail="提现记录不存在")
|
| 921 |
+
if tx.withdraw_status == "completed":
|
| 922 |
+
raise HTTPException(status_code=400, detail="该提现已完成打款")
|
| 923 |
+
|
| 924 |
+
# 更新提现状态
|
| 925 |
+
tx.withdraw_status = "completed"
|
| 926 |
+
tx.payment_order_id = req.payment_order_id
|
| 927 |
+
|
| 928 |
+
# 解冻余额(使用 net_amount 字段,确保解冻金额与冻结时一致)
|
| 929 |
+
wallet = db.query(Wallet).filter(Wallet.account == tx.account).first()
|
| 930 |
+
if wallet:
|
| 931 |
+
net_amount = tx.net_amount if tx.net_amount is not None else abs(tx.amount) # 优先使用 net_amount,兼容旧数据
|
| 932 |
+
wallet.frozen_balance = max(0, wallet.frozen_balance - net_amount)
|
| 933 |
+
|
| 934 |
+
db.commit()
|
| 935 |
+
|
| 936 |
+
return {"status": "success", "message": f"提现 {tx_id} 已确认打款完成"}
|