User edition: prune docs, license gate, obfuscate core lib.

Keep deploy/basic docs only; integrate sq.bz121.com license client; encrypt strategy/trade/key_monitor/options/hedge_plan for release.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-17 16:30:58 +08:00
parent 53863559f4
commit 16ef0f31e3
154 changed files with 1998 additions and 27914 deletions
+115
View File
@@ -0,0 +1,115 @@
"""FastAPI 中控许可中间件与 /license 页。"""
from __future__ import annotations
import os
from pathlib import Path
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
from lib.license.license_lib import (
get_device_id,
get_license_status,
is_license_valid,
redeem_code,
validate_license,
)
_TEMPLATE_PATH = Path(__file__).resolve().parent / "templates" / "license.html"
def _allowed_path(path: str) -> bool:
if path in (
"/license",
"/api/license/status",
"/api/license/redeem",
"/api/license/validate",
"/health",
):
return True
if path.startswith("/assets/") or path.startswith("/static/"):
return True
if path.startswith("/favicon"):
return True
return False
def install_license_middleware(app: FastAPI) -> None:
@app.get("/health")
async def _license_health():
st = get_license_status(skip_remote=True)
return {"ok": True, "license_valid": bool(st.get("valid")), "service": "manual_trading_hub"}
@app.get("/api/license/status")
async def _license_status_api():
return get_license_status()
@app.post("/api/license/redeem")
async def _license_redeem_api(request: Request):
try:
data = await request.json()
except Exception:
data = {}
code = (data.get("code") if isinstance(data, dict) else "") or ""
return redeem_code(str(code))
@app.post("/api/license/validate")
async def _license_validate_api():
return validate_license(force=True)
@app.api_route("/license", methods=["GET", "POST"])
async def _license_page(request: Request):
msg = ""
err = ""
if request.method == "POST":
form = await request.form()
code = str(form.get("code") or "").strip()
result = redeem_code(code)
if result.get("ok"):
msg = result.get("message") or "激活成功"
else:
err = result.get("message") or "激活失败"
status = get_license_status()
html = _TEMPLATE_PATH.read_text(encoding="utf-8")
# 简单替换,避免 Jinja 依赖差异
filled = (
html.replace("{{ device_id }}", get_device_id())
.replace("{{ api_url }}", str(status.get("api_url") or ""))
.replace("{{ wechat }}", "dekun03")
.replace("{{ message }}", msg)
.replace("{{ error }}", err)
.replace("{{ status_message }}", str(status.get("message") or ""))
.replace("{{ expires_at }}", str(status.get("expires_at") or ""))
.replace("{{ plan }}", str(status.get("plan") or ""))
.replace("{{ valid_text }}", "已授权" if status.get("valid") else "未授权")
)
# Flask 模板用 JinjaFastAPI 路径用占位符版本
if "{%" in filled or "{{" in filled:
from jinja2 import Template
filled = Template(html).render(
device_id=get_device_id(),
status=status,
message=msg,
error=err,
api_url=status.get("api_url") or "",
wechat="dekun03",
)
return HTMLResponse(filled)
@app.middleware("http")
async def _license_http_middleware(request: Request, call_next):
if os.getenv("LICENSE_DISABLED", "").strip().lower() in ("1", "true", "yes", "on"):
return await call_next(request)
path = request.url.path or "/"
if _allowed_path(path):
return await call_next(request)
if is_license_valid():
return await call_next(request)
if path.startswith("/api/"):
return JSONResponse(
{"ok": False, "error": "license_required", "message": "请先激活许可"},
status_code=403,
)
return RedirectResponse(url="/license", status_code=302)