fix: hide /license after activation; keep gate before activate.
EOF Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+3
-2
@@ -5,10 +5,11 @@
|
||||
## 激活流程
|
||||
|
||||
1. 部署并启动本系统(三所或中控任一页面)。
|
||||
2. 浏览器打开 **`/license`**(未授权时会自动跳转)。
|
||||
2. **未授权时**访问任意业务页会强制跳到 **`/license`**;已授权后默认无法再打开该页(会跳回首页)。
|
||||
3. 复制页面上的 **设备 ID**。
|
||||
4. 联系微信 **dekun03** 购买(备注设备 ID),按约定支付 **USDT** 后获得 **激活码 + 客户密钥**(每张码一把密钥)。
|
||||
5. 在 `/license` 填写 **客户密钥** 与激活码并兑换(密钥写入本地 `data/license_state.json`;续费一般可留空)。
|
||||
5. 在 `/license` 填写 **客户密钥** 与激活码并兑换(密钥写入本地 `data/license_state.json`;续费一般可留空)。激活成功后自动进入系统。
|
||||
6. **续费**(设备仍在授权期内):打开 **`/license?renew=1`** 兑换续费码。换机在新设备上直接进 `/license` 即可(新设备尚未授权)。
|
||||
|
||||
## 定价(整机授权)
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ from lib.license.license_lib import (
|
||||
_TEMPLATE_PATH = Path(__file__).resolve().parent / "templates" / "license.html"
|
||||
|
||||
|
||||
def _allowed_path(path: str) -> bool:
|
||||
def _license_public_path(path: str) -> bool:
|
||||
"""未授权时仍可访问的路径(授权页 / 接口 / 静态资源)。"""
|
||||
if path in (
|
||||
"/license",
|
||||
"/api/license/status",
|
||||
@@ -35,6 +36,17 @@ def _allowed_path(path: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _license_manage_requested(request: Request) -> bool:
|
||||
"""已授权时默认禁止进入 /license;续费/换机用 ?renew=1。"""
|
||||
q = request.query_params
|
||||
return (q.get("renew") or q.get("manage") or "").strip().lower() in (
|
||||
"1",
|
||||
"true",
|
||||
"yes",
|
||||
"on",
|
||||
)
|
||||
|
||||
|
||||
def install_license_middleware(app: FastAPI) -> None:
|
||||
@app.get("/health")
|
||||
async def _license_health():
|
||||
@@ -63,6 +75,10 @@ def install_license_middleware(app: FastAPI) -> None:
|
||||
|
||||
@app.api_route("/license", methods=["GET", "POST"])
|
||||
async def _license_page(request: Request):
|
||||
# 已授权:默认不可再进授权页(续费/换机:/license?renew=1)
|
||||
if is_license_valid() and request.method == "GET" and not _license_manage_requested(request):
|
||||
return RedirectResponse(url="/", status_code=302)
|
||||
|
||||
msg = ""
|
||||
err = ""
|
||||
if request.method == "POST":
|
||||
@@ -71,12 +87,10 @@ def install_license_middleware(app: FastAPI) -> None:
|
||||
ckey = str(form.get("client_api_key") or "").strip()
|
||||
result = redeem_code(code, client_api_key=ckey or None)
|
||||
if result.get("ok"):
|
||||
msg = result.get("message") or "激活成功"
|
||||
else:
|
||||
err = result.get("message") or "激活失败"
|
||||
return RedirectResponse(url="/", status_code=302)
|
||||
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 ""))
|
||||
@@ -88,7 +102,6 @@ def install_license_middleware(app: FastAPI) -> None:
|
||||
.replace("{{ plan }}", str(status.get("plan") or "—"))
|
||||
.replace("{{ valid_text }}", "已授权" if status.get("valid") else "未授权")
|
||||
)
|
||||
# Flask 模板用 Jinja;FastAPI 路径用占位符版本
|
||||
if "{%" in filled or "{{" in filled:
|
||||
from jinja2 import Template
|
||||
|
||||
@@ -107,7 +120,7 @@ def install_license_middleware(app: FastAPI) -> None:
|
||||
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):
|
||||
if _license_public_path(path):
|
||||
return await call_next(request)
|
||||
if is_license_valid():
|
||||
return await call_next(request)
|
||||
|
||||
@@ -18,7 +18,8 @@ from lib.license.license_lib import (
|
||||
_TEMPLATE_PATH = Path(__file__).resolve().parent / "templates" / "license.html"
|
||||
|
||||
|
||||
def _allowed_path(path: str) -> bool:
|
||||
def _license_public_path(path: str) -> bool:
|
||||
"""未授权时仍可访问的路径(授权页 / 接口 / 静态资源)。"""
|
||||
if path in ("/license", "/api/license/status", "/api/license/redeem", "/api/license/validate", "/health"):
|
||||
return True
|
||||
if path.startswith("/static/"):
|
||||
@@ -28,6 +29,16 @@ def _allowed_path(path: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _license_manage_requested() -> bool:
|
||||
"""已授权时默认禁止进入 /license;续费/换机用 ?renew=1。"""
|
||||
return (request.args.get("renew") or request.args.get("manage") or "").strip() in (
|
||||
"1",
|
||||
"true",
|
||||
"yes",
|
||||
"on",
|
||||
)
|
||||
|
||||
|
||||
def install_license_gate(app: Flask) -> None:
|
||||
"""注册 /license 与 before_request 门禁。三所 Flask 共用。"""
|
||||
|
||||
@@ -53,6 +64,10 @@ def install_license_gate(app: Flask) -> None:
|
||||
|
||||
@app.route("/license", methods=["GET", "POST"])
|
||||
def _license_page():
|
||||
# 已授权:默认不可再进授权页(续费/换机:/license?renew=1)
|
||||
if is_license_valid() and request.method == "GET" and not _license_manage_requested():
|
||||
return redirect("/")
|
||||
|
||||
msg = ""
|
||||
err = ""
|
||||
if request.method == "POST":
|
||||
@@ -60,9 +75,8 @@ def install_license_gate(app: Flask) -> None:
|
||||
ckey = (request.form.get("client_api_key") or "").strip()
|
||||
result = redeem_code(code, client_api_key=ckey or None)
|
||||
if result.get("ok"):
|
||||
msg = result.get("message") or "激活成功"
|
||||
else:
|
||||
err = result.get("message") or "激活失败"
|
||||
return redirect("/")
|
||||
err = result.get("message") or "激活失败"
|
||||
status = get_license_status()
|
||||
html = _TEMPLATE_PATH.read_text(encoding="utf-8")
|
||||
return render_template_string(
|
||||
@@ -80,9 +94,8 @@ def install_license_gate(app: Flask) -> None:
|
||||
if os.getenv("LICENSE_DISABLED", "").strip().lower() in ("1", "true", "yes", "on"):
|
||||
return None
|
||||
path = request.path or "/"
|
||||
if _allowed_path(path):
|
||||
if _license_public_path(path):
|
||||
return None
|
||||
# hub bridge 内部调用:仍要求已授权(整机许可)
|
||||
if is_license_valid():
|
||||
return None
|
||||
if path.startswith("/api/"):
|
||||
|
||||
Reference in New Issue
Block a user