订单需管理员审核后才开通节点,并更新登录页文案。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-10 23:04:24 +08:00
parent 8b55b9cf55
commit f329f647c7
22 changed files with 583 additions and 81 deletions
+117
View File
@@ -0,0 +1,117 @@
"""节点授权订单。付款凭证先入库,管理员通过后才开通节点。"""
from __future__ import annotations
import json
import secrets
from sqlalchemy import case, select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session, selectinload
from app.clock import utcnow
from app.config import settings
from app.errors import AppError
from app.models import LicenseOrder, OrderStatus
from app.money import money
from app.services.registry import register_node
def place_order(session: Session, user_id: int, tx_id: str) -> LicenseOrder:
digest = tx_id.strip()
if session.scalar(select(LicenseOrder.id).where(LicenseOrder.tx_id == digest)) is not None:
raise AppError("这笔交易已经提交过", 409)
order = LicenseOrder(
user_id=user_id,
amount=money(settings.license_price),
asset_symbol=settings.license_asset.strip().upper() or "USDT",
status=OrderStatus.PENDING,
tx_id=digest,
created_at=utcnow(),
)
session.add(order)
try:
session.flush()
except IntegrityError as exc:
session.rollback()
raise AppError("这笔交易已经提交过", 409) from exc
return order
def approve_order(session: Session, order_id: int) -> LicenseOrder:
order = _pending_order(session, order_id)
token = secrets.token_urlsafe(24)
node = register_node(
session,
order.user.wallet_address,
f"HN{order.id:06d}",
order.amount,
node_token=token,
)
order.status = OrderStatus.APPROVED
order.node = node
order.reviewed_at = utcnow()
session.flush()
return order
def reject_order(session: Session, order_id: int, note: str | None = None) -> LicenseOrder:
order = _pending_order(session, order_id)
order.status = OrderStatus.REJECTED
order.note = note
order.reviewed_at = utcnow()
session.flush()
return order
def list_orders(session: Session, user_id: int | None = None) -> list[LicenseOrder]:
pending_first = case((LicenseOrder.status == OrderStatus.PENDING, 0), else_=1)
query = select(LicenseOrder).options(selectinload(LicenseOrder.user), selectinload(LicenseOrder.node))
if user_id is not None:
query = query.where(LicenseOrder.user_id == user_id)
return list(session.scalars(query.order_by(pending_first, LicenseOrder.id.desc())).all())
def render_client(sn: str, token: str, base_url: str) -> str:
config = json.dumps({"sn": sn, "token": token, "base": base_url.rstrip("/")}, ensure_ascii=True)
return f"""# HyperNode 挂机客户端。这个文件里有节点 Token,不要发给别人。
import json
import time
import urllib.error
import urllib.request
CONFIG = json.loads({config!r})
def beat():
body = json.dumps({{"sn": CONFIG["sn"], "token": CONFIG["token"]}}).encode()
request = urllib.request.Request(
CONFIG["base"] + "/api/v1/node/heartbeat",
data=body,
headers={{"Content-Type": "application/json"}},
)
with urllib.request.urlopen(request, timeout=20) as response:
print(response.read().decode())
if __name__ == "__main__":
while True:
try:
beat()
except (urllib.error.URLError, TimeoutError, OSError) as exc:
print(exc)
time.sleep(60)
"""
def _pending_order(session: Session, order_id: int) -> LicenseOrder:
order = session.scalar(
select(LicenseOrder)
.options(selectinload(LicenseOrder.user), selectinload(LicenseOrder.node))
.where(LicenseOrder.id == order_id)
)
if order is None:
raise AppError("订单不存在", 404)
if order.status != OrderStatus.PENDING:
raise AppError("只有待审核的订单可以处理", 409)
return order