feat(accounts): 添加账号cookies存储及支付链接国家选择功能

- 在账号详情页添加cookies编辑与保存功能,用于支付请求
- 支付页面新增国家选择下拉框,支持多国货币计费
- 优化无痕打开浏览器功能,支持注入账号cookies
- 更新数据库模型、API路由及前端界面
This commit is contained in:
cnlimiter
2026-03-17 13:59:00 +08:00
parent d1e37aff56
commit 036a66d72b
10 changed files with 274 additions and 79 deletions

View File

@@ -39,6 +39,7 @@ class AccountResponse(BaseModel):
proxy_used: Optional[str] = None
cpa_uploaded: bool = False
cpa_uploaded_at: Optional[str] = None
cookies: Optional[str] = None
created_at: Optional[str] = None
updated_at: Optional[str] = None
@@ -56,6 +57,7 @@ class AccountUpdateRequest(BaseModel):
"""账号更新请求"""
status: Optional[str] = None
metadata: Optional[dict] = None
cookies: Optional[str] = None # 完整 cookie 字符串,用于支付请求
class BatchDeleteRequest(BaseModel):
@@ -116,6 +118,7 @@ def account_to_response(account: Account) -> AccountResponse:
proxy_used=account.proxy_used,
cpa_uploaded=account.cpa_uploaded or False,
cpa_uploaded_at=account.cpa_uploaded_at.isoformat() if account.cpa_uploaded_at else None,
cookies=account.cookies,
created_at=account.created_at.isoformat() if account.created_at else None,
updated_at=account.updated_at.isoformat() if account.updated_at else None,
)
@@ -216,10 +219,24 @@ async def update_account(account_id: int, request: AccountUpdateRequest):
current_metadata.update(request.metadata)
update_data["metadata"] = current_metadata
if request.cookies is not None:
# 留空则清空,非空则更新
update_data["cookies"] = request.cookies or None
account = crud.update_account(db, account_id, **update_data)
return account_to_response(account)
@router.get("/{account_id}/cookies")
async def get_account_cookies(account_id: int):
"""获取账号的 cookie 字符串(仅供支付使用)"""
with get_db() as db:
account = crud.get_account_by_id(db, account_id)
if not account:
raise HTTPException(status_code=404, detail="账号不存在")
return {"account_id": account_id, "cookies": account.cookies or ""}
@router.delete("/{account_id}")
async def delete_account(account_id: int):
"""删除单个账号"""

View File

@@ -38,10 +38,12 @@ class GenerateLinkRequest(BaseModel):
seat_quantity: int = 5
proxy: Optional[str] = None
auto_open: bool = False # 生成后是否自动无痕打开
country: str = "SG" # 计费国家,决定货币 # 生成后是否自动无痕打开
class OpenIncognitoRequest(BaseModel):
url: str
account_id: Optional[int] = None # 可选,用于注入账号 cookie
class MarkSubscriptionRequest(BaseModel):
@@ -83,7 +85,7 @@ def generate_payment_link(request: GenerateLinkRequest):
try:
if request.plan_type == "plus":
link = generate_plus_link(account, proxy)
link = generate_plus_link(account, proxy, country=request.country)
elif request.plan_type == "team":
link = generate_team_link(
account,
@@ -91,6 +93,7 @@ def generate_payment_link(request: GenerateLinkRequest):
price_interval=request.price_interval,
seat_quantity=request.seat_quantity,
proxy=proxy,
country=request.country,
)
else:
raise HTTPException(status_code=400, detail="plan_type 必须为 plus 或 team")
@@ -102,7 +105,8 @@ def generate_payment_link(request: GenerateLinkRequest):
opened = False
if request.auto_open and link:
opened = open_url_incognito(link)
cookies_str = account.cookies if account else None
opened = open_url_incognito(link, cookies_str)
return {
"success": True,
@@ -114,13 +118,21 @@ def generate_payment_link(request: GenerateLinkRequest):
@router.post("/open-incognito")
def open_browser_incognito(request: OpenIncognitoRequest):
"""后端命令行以无痕模式打开指定 URL"""
"""后端以无痕模式打开指定 URL,可注入账号 cookie"""
if not request.url:
raise HTTPException(status_code=400, detail="URL 不能为空")
success = open_url_incognito(request.url)
cookies_str = None
if request.account_id:
with get_db() as db:
account = db.query(Account).filter(Account.id == request.account_id).first()
if account:
cookies_str = account.cookies
success = open_url_incognito(request.url, cookies_str)
if success:
return {"success": True, "message": "已在无痕模式打开浏览器"}
return {"success": False, "message": "未找到可用的 Chrome/Edge,请手动复制链接"}
return {"success": False, "message": "未找到可用的浏览器,请手动复制链接"}
# ============== 订阅状态 ==============