731 lines
22 KiB
Python
731 lines
22 KiB
Python
"""微博微豆:CK 导入、账号刷新与绑定二维码。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from loguru import logger
|
|
from sqlalchemy import or_
|
|
from sqlalchemy.orm import Session, defer
|
|
|
|
from core.weibo import WeiboAuthError, WeiboError, WeiboExchangeUncertainError
|
|
|
|
from ..database import get_db
|
|
from ..deps import get_current_user
|
|
from ..models import User, WeiboAccount
|
|
from ..permissions import user_has_permission
|
|
from ..schemas import (
|
|
WeiboAccountDisableRequest,
|
|
WeiboAccountOut,
|
|
WeiboAccountUpdate,
|
|
WeiboBindQrOut,
|
|
WeiboBindSchemeOut,
|
|
WeiboCookieImport,
|
|
WeiboExchangeOut,
|
|
WeiboExchangeRecordItem,
|
|
WeiboExchangeRecordsOut,
|
|
WeiboExchangeRequest,
|
|
WeiboGoodsOut,
|
|
WeiboImportError,
|
|
WeiboImportResult,
|
|
WeiboMallListOut,
|
|
WeiboPaymentStatusOut,
|
|
WeiboPaymentStatusRequest,
|
|
WeiboPointHistoryOut,
|
|
WeiboPointRecordItem,
|
|
WeiboRechargeOrderOut,
|
|
WeiboRechargeRequest,
|
|
)
|
|
from ..services.audit_service import record_audit
|
|
from ..services.weibo_service import (
|
|
create_weibo_recharge_order,
|
|
disable_weibo_account,
|
|
enable_weibo_account,
|
|
exchange_weibo_goods,
|
|
generate_weibo_bind_qrcode,
|
|
generate_weibo_bind_schemes,
|
|
import_weibo_cookies,
|
|
query_weibo_exchange_records,
|
|
query_weibo_mall,
|
|
query_weibo_payment_status,
|
|
query_weibo_point_history,
|
|
refresh_weibo_account,
|
|
update_weibo_account_metadata,
|
|
)
|
|
|
|
router = APIRouter(prefix="/api/weibo", tags=["微博微豆"])
|
|
WEIBO_DEFAULT_ACTIVITY = os.getenv("WEIBO_DEFAULT_ACTIVITY", "").strip()
|
|
|
|
|
|
def _require_weibo_perm(user: User, permission: str) -> None:
|
|
if not user_has_permission(user, permission):
|
|
raise HTTPException(status_code=403, detail="权限不足")
|
|
|
|
|
|
def _query_str(value) -> str:
|
|
return value.strip() if isinstance(value, str) else ""
|
|
|
|
|
|
def _int_or_none(value: object) -> int | None:
|
|
if value is None or value == "":
|
|
return None
|
|
try:
|
|
return int(str(value))
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
def _get_account(db: Session, account_id: int) -> WeiboAccount:
|
|
account = db.query(WeiboAccount).filter(WeiboAccount.id == account_id).first()
|
|
if account is None:
|
|
raise HTTPException(status_code=404, detail="微博账号不存在")
|
|
return account
|
|
|
|
|
|
def _account_out(account: WeiboAccount) -> WeiboAccountOut:
|
|
return WeiboAccountOut.model_validate(account)
|
|
|
|
|
|
def _ensure_enabled(account: WeiboAccount) -> None:
|
|
if not account.is_enabled:
|
|
raise HTTPException(status_code=409, detail="微博账号已停用,请先启用")
|
|
|
|
|
|
def _refresh_or_http(account: WeiboAccount, db: Session):
|
|
try:
|
|
return refresh_weibo_account(
|
|
db,
|
|
account,
|
|
activity=WEIBO_DEFAULT_ACTIVITY,
|
|
)
|
|
except WeiboAuthError as exc:
|
|
raise HTTPException(status_code=400, detail=f"CK 已失效: {exc}") from exc
|
|
except WeiboError as exc:
|
|
raise HTTPException(status_code=502, detail=f"刷新微博账号失败: {exc}") from exc
|
|
|
|
|
|
def _manual_http_error(exc: Exception) -> HTTPException:
|
|
if isinstance(exc, WeiboAuthError):
|
|
return HTTPException(status_code=400, detail=f"CK 已失效: {exc}")
|
|
return HTTPException(status_code=502, detail=str(exc))
|
|
|
|
|
|
def _record_audit_safely(
|
|
db: Session,
|
|
current: User,
|
|
*,
|
|
action: str,
|
|
target: str,
|
|
detail: dict,
|
|
) -> None:
|
|
"""审计是旁路,失败只记 warning,不回滚已经完成的账号操作。"""
|
|
|
|
try:
|
|
record_audit(
|
|
db,
|
|
current,
|
|
action=action,
|
|
target=target,
|
|
detail=detail,
|
|
)
|
|
db.commit()
|
|
except Exception as exc: # noqa: BLE001 旁路写入不能带崩主流程
|
|
db.rollback()
|
|
logger.warning(f"记录微博审计失败: {exc}")
|
|
|
|
|
|
@router.get("/accounts")
|
|
def list_accounts(
|
|
search: str = Query(""),
|
|
bind_status: str = Query(""),
|
|
enabled: bool | None = Query(None),
|
|
page: int = Query(1, ge=1),
|
|
page_size: int = Query(20, ge=1, le=200),
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""查看已导入的微博账号。"""
|
|
|
|
_require_weibo_perm(current, "weibo:view")
|
|
query = db.query(WeiboAccount)
|
|
search_text = _query_str(search)
|
|
if search_text:
|
|
pattern = f"%{search_text}%"
|
|
query = query.filter(
|
|
or_(
|
|
WeiboAccount.uid.ilike(pattern),
|
|
WeiboAccount.nickname.ilike(pattern),
|
|
WeiboAccount.game_role_name.ilike(pattern),
|
|
WeiboAccount.tag.ilike(pattern),
|
|
WeiboAccount.remark.ilike(pattern),
|
|
)
|
|
)
|
|
if isinstance(enabled, bool):
|
|
query = query.filter(WeiboAccount.is_enabled.is_(enabled))
|
|
bind_value = _query_str(bind_status)
|
|
if bind_value == "unbound":
|
|
query = query.filter(WeiboAccount.is_bound.is_(False))
|
|
elif bind_value == "bound":
|
|
query = query.filter(WeiboAccount.is_bound.is_(True))
|
|
elif bind_value == "rebindable":
|
|
query = query.filter(
|
|
WeiboAccount.is_bound.is_(True),
|
|
WeiboAccount.can_rebind.is_(True),
|
|
)
|
|
|
|
total = query.count()
|
|
rows = (
|
|
query.options(defer(WeiboAccount.cookie))
|
|
.order_by(WeiboAccount.id.desc())
|
|
.offset((page - 1) * page_size)
|
|
.limit(page_size)
|
|
.all()
|
|
)
|
|
return {
|
|
"items": [_account_out(account) for account in rows],
|
|
"total": total,
|
|
"page": page,
|
|
"page_size": page_size,
|
|
}
|
|
|
|
|
|
@router.get("/accounts/{account_id}", response_model=WeiboAccountOut)
|
|
def get_account(
|
|
account_id: int,
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""查看一个微博账号的本地资料。"""
|
|
|
|
_require_weibo_perm(current, "weibo:view")
|
|
return _account_out(_get_account(db, account_id))
|
|
|
|
|
|
@router.put("/accounts/{account_id}", response_model=WeiboAccountOut)
|
|
def update_account(
|
|
account_id: int,
|
|
req: WeiboAccountUpdate,
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""更新账号的人工标签和备注。"""
|
|
|
|
_require_weibo_perm(current, "weibo:manage")
|
|
account = _get_account(db, account_id)
|
|
payload = req.model_dump(exclude_unset=True)
|
|
changes = update_weibo_account_metadata(
|
|
db,
|
|
account,
|
|
tag=payload.get("tag"),
|
|
remark=payload.get("remark"),
|
|
)
|
|
if changes:
|
|
_record_audit_safely(
|
|
db,
|
|
current,
|
|
action="weibo:account:update",
|
|
target=f"weibo_account:{account.id}",
|
|
detail={"account_id": account.id, "uid": account.uid, "changes": changes},
|
|
)
|
|
return _account_out(account)
|
|
|
|
|
|
@router.post("/accounts/{account_id}/disable", response_model=WeiboAccountOut)
|
|
def disable_account(
|
|
account_id: int,
|
|
req: WeiboAccountDisableRequest,
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""停用账号,保留 CK 和历史资料。"""
|
|
|
|
_require_weibo_perm(current, "weibo:manage")
|
|
account = _get_account(db, account_id)
|
|
disable_weibo_account(db, account, reason=req.reason)
|
|
_record_audit_safely(
|
|
db,
|
|
current,
|
|
action="weibo:account:disable",
|
|
target=f"weibo_account:{account.id}",
|
|
detail={
|
|
"account_id": account.id,
|
|
"uid": account.uid,
|
|
"reason": account.disabled_reason,
|
|
},
|
|
)
|
|
return _account_out(account)
|
|
|
|
|
|
@router.post("/accounts/{account_id}/enable", response_model=WeiboAccountOut)
|
|
def enable_account(
|
|
account_id: int,
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""重新检测 CK 与支付风险,通过后启用账号。"""
|
|
|
|
_require_weibo_perm(current, "weibo:manage")
|
|
account = _get_account(db, account_id)
|
|
_refresh_or_http(account, db)
|
|
enable_weibo_account(db, account)
|
|
_record_audit_safely(
|
|
db,
|
|
current,
|
|
action="weibo:account:enable",
|
|
target=f"weibo_account:{account.id}",
|
|
detail={"account_id": account.id, "uid": account.uid},
|
|
)
|
|
return _account_out(account)
|
|
|
|
|
|
@router.post("/accounts/import-cookies", response_model=WeiboImportResult)
|
|
def import_cookies(
|
|
req: WeiboCookieImport,
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""导入微博 CK;每个非空行一条完整 Cookie。"""
|
|
|
|
_require_weibo_perm(current, "weibo:import")
|
|
accounts, errors, skipped = import_weibo_cookies(
|
|
db,
|
|
req.text,
|
|
req.tag,
|
|
WEIBO_DEFAULT_ACTIVITY,
|
|
)
|
|
typed_errors = [
|
|
WeiboImportError(line=item.line, message=item.message) for item in errors
|
|
]
|
|
if accounts:
|
|
_record_audit_safely(
|
|
db,
|
|
current,
|
|
action="weibo:account:import",
|
|
target=f"count:{len(accounts)}",
|
|
detail={
|
|
"uids": [account.uid for account in accounts],
|
|
"count": len(accounts),
|
|
"skipped": skipped,
|
|
"errors": len(errors),
|
|
},
|
|
)
|
|
message = f"成功导入/更新 {len(accounts)} 个微博账号"
|
|
if errors:
|
|
message += f",失败 {len(errors)} 条"
|
|
if skipped:
|
|
message += f",跳过 {skipped} 条重复"
|
|
return WeiboImportResult(
|
|
success=bool(accounts),
|
|
message=message,
|
|
count=len(accounts),
|
|
skipped=skipped,
|
|
errors=typed_errors,
|
|
accounts=[_account_out(account) for account in accounts],
|
|
)
|
|
|
|
|
|
@router.post("/accounts/{account_id}/refresh", response_model=WeiboAccountOut)
|
|
def refresh_account(
|
|
account_id: int,
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""立即刷新一个微博账号的 CK 状态和资料。"""
|
|
|
|
_require_weibo_perm(current, "weibo:view")
|
|
account = _get_account(db, account_id)
|
|
_ensure_enabled(account)
|
|
_refresh_or_http(account, db)
|
|
_record_audit_safely(
|
|
db,
|
|
current,
|
|
action="weibo:account:refresh",
|
|
target=f"weibo_account:{account.id}",
|
|
detail={"account_id": account.id, "uid": account.uid},
|
|
)
|
|
return _account_out(account)
|
|
|
|
|
|
@router.post("/accounts/{account_id}/bind-qrcodes", response_model=WeiboBindQrOut)
|
|
def create_bind_qrcodes(
|
|
account_id: int,
|
|
channel: str = Query("", pattern="^(wx|qq)?$"),
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""生成微信和 QQ 绑定 Scheme,供前端直接渲染二维码。"""
|
|
|
|
_require_weibo_perm(current, "weibo:bind")
|
|
selected_channel = channel.strip() if isinstance(channel, str) else ""
|
|
account = _get_account(db, account_id)
|
|
_ensure_enabled(account)
|
|
snapshot = _refresh_or_http(account, db)
|
|
if snapshot.is_bound and not snapshot.can_rebind:
|
|
detail = (
|
|
f"账号已绑定角色「{snapshot.role_name}」,当前不可换绑"
|
|
if snapshot.role_name
|
|
else "账号已绑定角色,当前不可换绑"
|
|
)
|
|
if snapshot.rebind_remain_days:
|
|
detail += f",还需等待 {snapshot.rebind_remain_days} 天"
|
|
raise HTTPException(status_code=409, detail=detail)
|
|
if not snapshot.bind_url:
|
|
raise HTTPException(status_code=409, detail="微博没有返回当前账号的绑定链接")
|
|
|
|
if selected_channel:
|
|
items = [
|
|
generate_weibo_bind_qrcode(account, snapshot.bind_url, selected_channel)
|
|
]
|
|
else:
|
|
# 不传 channel 时保留旧接口:一次返回微信、QQ 两个 Scheme。
|
|
items = generate_weibo_bind_schemes(account, snapshot.bind_url)
|
|
schemes = [WeiboBindSchemeOut(**item) for item in items]
|
|
successful = [
|
|
item
|
|
for item in schemes
|
|
if (item.qrcode_image if selected_channel else item.scheme)
|
|
]
|
|
if not successful:
|
|
prefix = (
|
|
f"{schemes[0].label}绑定二维码生成失败"
|
|
if selected_channel and schemes
|
|
else "微信和 QQ 绑定二维码都生成失败"
|
|
)
|
|
raise HTTPException(
|
|
status_code=502,
|
|
detail=prefix
|
|
+ ": "
|
|
+ "; ".join(f"{item.label}={item.error}" for item in schemes if item.error),
|
|
)
|
|
|
|
_record_audit_safely(
|
|
db,
|
|
current,
|
|
action="weibo:account:bind_qrcode",
|
|
target=f"weibo_account:{account.id}",
|
|
detail={
|
|
"account_id": account.id,
|
|
"uid": account.uid,
|
|
"channels": [
|
|
{
|
|
"channel": item.channel,
|
|
"success": bool(item.scheme),
|
|
"error": item.error,
|
|
}
|
|
for item in schemes
|
|
],
|
|
},
|
|
)
|
|
return WeiboBindQrOut(
|
|
account_id=account.id,
|
|
is_bound=snapshot.is_bound,
|
|
can_rebind=snapshot.can_rebind,
|
|
rebind_remain_days=snapshot.rebind_remain_days,
|
|
schemes=schemes,
|
|
)
|
|
|
|
|
|
@router.post("/accounts/{account_id}/mall-list", response_model=WeiboMallListOut)
|
|
def list_mall_goods(
|
|
account_id: int,
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""手动查询微豆商城商品列表。"""
|
|
|
|
_require_weibo_perm(current, "weibo:view")
|
|
account = _get_account(db, account_id)
|
|
_ensure_enabled(account)
|
|
try:
|
|
data = query_weibo_mall(
|
|
db,
|
|
account,
|
|
activity=WEIBO_DEFAULT_ACTIVITY,
|
|
)
|
|
except (WeiboError, ValueError) as exc:
|
|
raise _manual_http_error(exc) from exc
|
|
goods: list[WeiboGoodsOut] = []
|
|
for item in data.get("list") or []:
|
|
if not isinstance(item, dict):
|
|
continue
|
|
try:
|
|
goods_id = int(item.get("id") or 0)
|
|
except (TypeError, ValueError):
|
|
continue
|
|
if goods_id <= 0:
|
|
continue
|
|
goods.append(
|
|
WeiboGoodsOut(
|
|
id=goods_id,
|
|
goods_name=str(item.get("goods_name") or ""),
|
|
goods_image=str(item.get("goods_image") or ""),
|
|
price=_int_or_none(item.get("price")) or 0,
|
|
remain_count=_int_or_none(item.get("remain_count")),
|
|
is_sold_out=bool(item.get("is_sold_out")),
|
|
goods_desc=str(item.get("goods_desc") or ""),
|
|
)
|
|
)
|
|
return WeiboMallListOut(
|
|
account_id=account.id,
|
|
weidou_balance=int(data.get("weidou_balance") or 0),
|
|
goods=goods,
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/accounts/{account_id}/exchange",
|
|
response_model=WeiboExchangeOut,
|
|
)
|
|
def exchange_goods(
|
|
account_id: int,
|
|
req: WeiboExchangeRequest,
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""手动兑换商品;服务端只提交一次,不自动重试。"""
|
|
|
|
_require_weibo_perm(current, "weibo:exchange")
|
|
if not req.confirm:
|
|
raise HTTPException(status_code=400, detail="请先确认兑换商品")
|
|
account = _get_account(db, account_id)
|
|
_ensure_enabled(account)
|
|
try:
|
|
result = exchange_weibo_goods(
|
|
db,
|
|
account,
|
|
goods_id=req.goods_id,
|
|
expected_price=req.expected_price,
|
|
activity=WEIBO_DEFAULT_ACTIVITY,
|
|
)
|
|
except WeiboExchangeUncertainError as exc:
|
|
raise HTTPException(status_code=502, detail=str(exc)) from exc
|
|
except (WeiboError, ValueError) as exc:
|
|
raise _manual_http_error(exc) from exc
|
|
_record_audit_safely(
|
|
db,
|
|
current,
|
|
action="weibo:mall:exchange",
|
|
target=f"weibo_account:{account.id}",
|
|
detail={
|
|
"account_id": account.id,
|
|
"uid": account.uid,
|
|
"goods_id": result.goods_id,
|
|
"goods_name": result.goods_name,
|
|
"price": result.price,
|
|
"balance_before": result.balance_before,
|
|
"balance_after": result.balance_after,
|
|
"verified": result.verified,
|
|
},
|
|
)
|
|
return WeiboExchangeOut(
|
|
account_id=account.id,
|
|
goods_id=result.goods_id,
|
|
goods_name=result.goods_name,
|
|
price=result.price,
|
|
balance_before=result.balance_before,
|
|
balance_after=result.balance_after,
|
|
remain_before=result.remain_before,
|
|
remain_after=result.remain_after,
|
|
verified=result.verified,
|
|
verify_error=result.verify_error,
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/accounts/{account_id}/exchange-records",
|
|
response_model=WeiboExchangeRecordsOut,
|
|
)
|
|
def list_exchange_records(
|
|
account_id: int,
|
|
page: int = Query(1, ge=1),
|
|
page_size: int = Query(20, ge=1, le=100),
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""手动查询微豆兑换记录。"""
|
|
|
|
_require_weibo_perm(current, "weibo:view")
|
|
account = _get_account(db, account_id)
|
|
_ensure_enabled(account)
|
|
try:
|
|
data = query_weibo_exchange_records(
|
|
db,
|
|
account,
|
|
page=page,
|
|
page_size=page_size,
|
|
activity=WEIBO_DEFAULT_ACTIVITY,
|
|
)
|
|
except (WeiboError, ValueError) as exc:
|
|
raise _manual_http_error(exc) from exc
|
|
items: list[WeiboExchangeRecordItem] = []
|
|
for item in data.get("list") or []:
|
|
if not isinstance(item, dict):
|
|
continue
|
|
items.append(
|
|
WeiboExchangeRecordItem(
|
|
goods_name=str(item.get("goods_name") or ""),
|
|
game_account=str(item.get("game_account") or ""),
|
|
cost_weidou=_int_or_none(item.get("cost_weidou")),
|
|
exchange_time=str(item.get("exchange_time") or ""),
|
|
reward_status=_int_or_none(item.get("reward_status")),
|
|
raw=item,
|
|
)
|
|
)
|
|
return WeiboExchangeRecordsOut(
|
|
account_id=account.id,
|
|
page=int(data.get("page") or data.get("page_no") or page),
|
|
page_size=int(data.get("pageSize") or data.get("page_size") or page_size),
|
|
total=int(data.get("totalCount") or data.get("total") or 0),
|
|
items=items,
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/accounts/{account_id}/point-history",
|
|
response_model=WeiboPointHistoryOut,
|
|
)
|
|
def list_point_history(
|
|
account_id: int,
|
|
page: int = Query(1, ge=1),
|
|
page_size: int = Query(20, ge=1, le=100),
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""手动查询微豆获取和充值明细。"""
|
|
|
|
_require_weibo_perm(current, "weibo:view")
|
|
account = _get_account(db, account_id)
|
|
_ensure_enabled(account)
|
|
try:
|
|
data = query_weibo_point_history(
|
|
db,
|
|
account,
|
|
page=page,
|
|
page_size=page_size,
|
|
activity=WEIBO_DEFAULT_ACTIVITY,
|
|
)
|
|
except (WeiboError, ValueError) as exc:
|
|
raise _manual_http_error(exc) from exc
|
|
items: list[WeiboPointRecordItem] = []
|
|
for item in data.get("list") or []:
|
|
if not isinstance(item, dict):
|
|
continue
|
|
items.append(
|
|
WeiboPointRecordItem(
|
|
id=str(item.get("id") or ""),
|
|
source_type=_int_or_none(item.get("source_type")),
|
|
record_type=_int_or_none(item.get("record_type")),
|
|
source_id=str(item.get("source_id") or ""),
|
|
source_name=str(item.get("source_name") or ""),
|
|
point_num=_int_or_none(item.get("point_num")) or 0,
|
|
create_time=str(item.get("create_time") or ""),
|
|
expire_time=str(item.get("expire_time") or ""),
|
|
)
|
|
)
|
|
return WeiboPointHistoryOut(
|
|
account_id=account.id,
|
|
page=int(data.get("page_num") or page),
|
|
page_size=int(data.get("page_size") or page_size),
|
|
total=int(data.get("total") or 0),
|
|
items=items,
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/accounts/{account_id}/recharge/orders",
|
|
response_model=WeiboRechargeOrderOut,
|
|
)
|
|
def create_recharge_order(
|
|
account_id: int,
|
|
req: WeiboRechargeRequest,
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""手动创建助威充值订单并返回支付二维码。"""
|
|
|
|
_require_weibo_perm(current, "weibo:recharge")
|
|
account = _get_account(db, account_id)
|
|
_ensure_enabled(account)
|
|
try:
|
|
order = create_weibo_recharge_order(
|
|
db,
|
|
account,
|
|
amount_yuan=req.amount_yuan,
|
|
activity=WEIBO_DEFAULT_ACTIVITY,
|
|
)
|
|
except (WeiboError, ValueError) as exc:
|
|
raise _manual_http_error(exc) from exc
|
|
_record_audit_safely(
|
|
db,
|
|
current,
|
|
action="weibo:recharge:create_order",
|
|
target=f"weibo_account:{account.id}",
|
|
detail={
|
|
"account_id": account.id,
|
|
"uid": account.uid,
|
|
"amount_yuan": order.amount_yuan,
|
|
"single_reward": order.single_reward,
|
|
"order_id": order.order_id,
|
|
},
|
|
)
|
|
return WeiboRechargeOrderOut(
|
|
account_id=account.id,
|
|
order_id=order.order_id,
|
|
pay_id=order.pay_id,
|
|
amount_yuan=order.amount_yuan,
|
|
single_reward=order.single_reward,
|
|
alipay_qr_code=order.alipay_qr_code,
|
|
desktop_pay_url=order.desktop_pay_url,
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/accounts/{account_id}/recharge/status",
|
|
response_model=WeiboPaymentStatusOut,
|
|
)
|
|
def get_recharge_status(
|
|
account_id: int,
|
|
req: WeiboPaymentStatusRequest,
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""手动查询支付状态;支付动作仍由用户在微信/支付宝完成。"""
|
|
|
|
_require_weibo_perm(current, "weibo:recharge")
|
|
account = _get_account(db, account_id)
|
|
_ensure_enabled(account)
|
|
try:
|
|
status = query_weibo_payment_status(db, account, pay_id=req.pay_id)
|
|
except (WeiboError, ValueError) as exc:
|
|
raise _manual_http_error(exc) from exc
|
|
return WeiboPaymentStatusOut(
|
|
pay_id=status.pay_id,
|
|
out_pay_id=status.out_pay_id,
|
|
status=status.status,
|
|
sub_status=status.sub_status,
|
|
)
|
|
|
|
|
|
@router.delete("/accounts/{account_id}")
|
|
def delete_account(
|
|
account_id: int,
|
|
db: Session = Depends(get_db),
|
|
current: User = Depends(get_current_user),
|
|
):
|
|
"""删除一个微博账号。"""
|
|
|
|
_require_weibo_perm(current, "weibo:delete")
|
|
account = _get_account(db, account_id)
|
|
uid = account.uid
|
|
db.delete(account)
|
|
db.commit()
|
|
_record_audit_safely(
|
|
db,
|
|
current,
|
|
action="weibo:account:delete",
|
|
target=f"weibo_account:{account_id}",
|
|
detail={"account_id": account_id, "uid": uid},
|
|
)
|
|
return {"message": "微博账号已删除", "success": True}
|