feat: license page uses per-customer client token wording.
Align redeem/validate UX with per-code tokens from the auth server. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+12
-12
@@ -7,8 +7,8 @@
|
|||||||
1. 部署并启动本系统(三所或中控任一页面)。
|
1. 部署并启动本系统(三所或中控任一页面)。
|
||||||
2. 浏览器打开 **`/license`**(未授权时会自动跳转)。
|
2. 浏览器打开 **`/license`**(未授权时会自动跳转)。
|
||||||
3. 复制页面上的 **设备 ID**。
|
3. 复制页面上的 **设备 ID**。
|
||||||
4. 联系微信 **dekun03** 购买(备注设备 ID),按约定支付 **USDT** 后获得 **CLIENT_API_KEY** 与激活码。
|
4. 联系微信 **dekun03** 购买(备注设备 ID),按约定支付 **USDT** 后获得 **激活码 + 客户密钥**(每张码一把密钥)。
|
||||||
5. 在 `/license` 填写 **CLIENT_API_KEY** 与激活码并兑换(密钥会写入本地 `data/license_state.json`,下次可留空)。
|
5. 在 `/license` 填写 **客户密钥** 与激活码并兑换(密钥写入本地 `data/license_state.json`;续费一般可留空)。
|
||||||
|
|
||||||
## 定价(整机授权)
|
## 定价(整机授权)
|
||||||
|
|
||||||
@@ -18,19 +18,21 @@
|
|||||||
| 部署费 | 一次性代部署/装机(自装是否收取以约定为准) | **100 USDT** |
|
| 部署费 | 一次性代部署/装机(自装是否收取以约定为准) | **100 USDT** |
|
||||||
|
|
||||||
- 仅售年卡,无月卡/季卡。
|
- 仅售年卡,无月卡/季卡。
|
||||||
- 续费:年卡 **1000 USDT**(一般不再收部署费)。
|
- 续费:年卡 **1000 USDT**(一般不再收部署费);续费码沿用原客户密钥。
|
||||||
- 换机:旧设备立即失效,需换机码(向卖家申请)。
|
- 换机:旧设备立即失效,需换机码(向卖家申请);密钥不变。
|
||||||
- USDT 收款地址由卖家提供(链与地址以当时通知为准)。
|
- USDT 收款地址由卖家提供(链与地址以当时通知为准)。
|
||||||
|
|
||||||
## CLIENT_API_KEY(推荐)
|
## 客户密钥(Client Token)
|
||||||
|
|
||||||
卖家在授权站后台 **系统设置** 可查看/复制 `CLIENT_API_KEY`,发给用户后填入软件 `/license` 页即可,**不必再手改 `license.env`**。
|
卖家在授权站后台 **新激活码** Tab 可复制「激活码 + 客户密钥」。
|
||||||
|
每张新购码绑定独立密钥;兑换后归该订阅长期使用。
|
||||||
|
**不要**再使用后台「系统设置」里的全局 `CLIENT_API_KEY` 发给新用户(那仅兼容旧数据)。
|
||||||
|
|
||||||
优先级:环境变量 `LICENSE_CLIENT_KEY` > 授权页保存的本地密钥。
|
本地优先级:环境变量 `LICENSE_CLIENT_KEY` > 授权页保存的密钥。
|
||||||
|
|
||||||
## 环境变量(可选)
|
## 环境变量(可选)
|
||||||
|
|
||||||
在**仓库根目录**创建 **`license.env`**(勿提交 Git),可预先写好密钥:
|
在**仓库根目录**创建 **`license.env`**(勿提交 Git):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/crypto_monitor_user # 或你的安装目录
|
cd /opt/crypto_monitor_user # 或你的安装目录
|
||||||
@@ -39,16 +41,14 @@ cp license.env.example license.env
|
|||||||
|
|
||||||
```env
|
```env
|
||||||
LICENSE_API_URL=https://sq.bz121.com
|
LICENSE_API_URL=https://sq.bz121.com
|
||||||
# 可选:与授权站 CLIENT_API_KEY 相同;也可只在 /license 页填写
|
# 可选:也可只在 /license 页填写卖家发的客户密钥
|
||||||
# LICENSE_CLIENT_KEY=
|
# LICENSE_CLIENT_KEY=
|
||||||
# 可选:断网宽限小时数(默认 72)
|
|
||||||
# LICENSE_OFFLINE_GRACE_HOURS=72
|
# LICENSE_OFFLINE_GRACE_HOURS=72
|
||||||
# 本地调试可临时关闭门禁(生产勿开)
|
|
||||||
# LICENSE_DISABLED=false
|
# LICENSE_DISABLED=false
|
||||||
```
|
```
|
||||||
|
|
||||||
改完环境变量后**重启** Flask / 中控进程。也可在各实例 `.env` 里写同样的 `LICENSE_*`(不要留空键)。
|
改完环境变量后**重启** Flask / 中控进程。也可在各实例 `.env` 里写同样的 `LICENSE_*`(不要留空键)。
|
||||||
|
|
||||||
许可状态保存在仓库根目录 **`data/license_state.json`**(三所 + 中控共用,含本地保存的 `client_api_key`)。
|
许可状态保存在仓库根目录 **`data/license_state.json`**(三所 + 中控共用,含本地保存的客户密钥)。
|
||||||
|
|
||||||
系统约每 **3 天** 向授权站校验一次;明确过期或换机后旧设备将无法使用。
|
系统约每 **3 天** 向授权站校验一次;明确过期或换机后旧设备将无法使用。
|
||||||
|
|||||||
@@ -72,16 +72,16 @@ def client_key() -> str:
|
|||||||
|
|
||||||
|
|
||||||
def set_client_api_key(key: str) -> dict[str, Any]:
|
def set_client_api_key(key: str) -> dict[str, Any]:
|
||||||
"""保存用户在授权页填写的 CLIENT_API_KEY(写入 license_state.json)。"""
|
"""保存用户在授权页填写的客户密钥 / client token(写入 license_state.json)。"""
|
||||||
key = (key or "").strip()
|
key = (key or "").strip()
|
||||||
if len(key) < 8:
|
if len(key) < 8:
|
||||||
return {"ok": False, "message": "CLIENT_API_KEY 太短"}
|
return {"ok": False, "message": "客户密钥太短"}
|
||||||
with _lock:
|
with _lock:
|
||||||
st = _read_state()
|
st = _read_state()
|
||||||
st["client_api_key"] = key
|
st["client_api_key"] = key
|
||||||
_write_state(st)
|
_write_state(st)
|
||||||
os.environ["LICENSE_CLIENT_KEY"] = key
|
os.environ["LICENSE_CLIENT_KEY"] = key
|
||||||
return {"ok": True, "message": "CLIENT_API_KEY 已保存"}
|
return {"ok": True, "message": "客户密钥已保存"}
|
||||||
|
|
||||||
|
|
||||||
def offline_grace_hours() -> float:
|
def offline_grace_hours() -> float:
|
||||||
@@ -180,7 +180,7 @@ def _parse_expires_at(value: str | None) -> float | None:
|
|||||||
def _http_json(method: str, path: str, body: dict[str, Any]) -> dict[str, Any]:
|
def _http_json(method: str, path: str, body: dict[str, Any]) -> dict[str, Any]:
|
||||||
key = client_key()
|
key = client_key()
|
||||||
if not key:
|
if not key:
|
||||||
return {"ok": False, "message": "未配置 CLIENT_API_KEY。请在软件授权页填写卖家提供的密钥,或在 license.env 中设置 LICENSE_CLIENT_KEY。"}
|
return {"ok": False, "message": "未配置客户密钥。请在软件授权页填写卖家提供的客户密钥(与激活码一起发放)。"}
|
||||||
url = f"{api_base_url()}{path}"
|
url = f"{api_base_url()}{path}"
|
||||||
payload = json.dumps(body).encode("utf-8")
|
payload = json.dumps(body).encode("utf-8")
|
||||||
req = Request(
|
req = Request(
|
||||||
@@ -221,7 +221,7 @@ def redeem_code(code: str, client_api_key: str | None = None) -> dict[str, Any]:
|
|||||||
if not client_key():
|
if not client_key():
|
||||||
return {
|
return {
|
||||||
"ok": False,
|
"ok": False,
|
||||||
"message": "请先填写 CLIENT_API_KEY(向卖家索取,与授权站后台「系统设置」中的密钥一致)",
|
"message": "请先填写客户密钥(与激活码一起由卖家提供;续费时可留空沿用本地已保存的密钥)",
|
||||||
}
|
}
|
||||||
device_id = get_device_id()
|
device_id = get_device_id()
|
||||||
result = _http_json("POST", "/v1/redeem", {"device_id": device_id, "code": code})
|
result = _http_json("POST", "/v1/redeem", {"device_id": device_id, "code": code})
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h1>软件授权</h1>
|
<h1>软件授权</h1>
|
||||||
<p class="muted">复制设备 ID,联系微信 <strong>{{ wechat }}</strong> 获取 <strong>CLIENT_API_KEY</strong> 与激活码。授权站:{{ api_url }}</p>
|
<p class="muted">复制设备 ID,联系微信 <strong>{{ wechat }}</strong> 获取 <strong>客户密钥</strong> 与激活码。授权站:{{ api_url }}</p>
|
||||||
|
|
||||||
{% if message %}<p class="ok">{{ message }}</p>{% endif %}
|
{% if message %}<p class="ok">{{ message }}</p>{% endif %}
|
||||||
{% if error %}<p class="err">{{ error }}</p>{% endif %}
|
{% if error %}<p class="err">{{ error }}</p>{% endif %}
|
||||||
@@ -85,9 +85,9 @@
|
|||||||
|
|
||||||
<form method="post" action="/license">
|
<form method="post" action="/license">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label>CLIENT_API_KEY</label>
|
<label>客户密钥</label>
|
||||||
<input type="text" name="client_api_key" placeholder="{% if status.has_client_key %}已保存 {{ status.client_key_masked }},可留空或填写新密钥{% else %}粘贴卖家提供的 CLIENT_API_KEY{% endif %}" autocomplete="off" {% if not status.has_client_key %}required{% endif %}>
|
<input type="text" name="client_api_key" placeholder="{% if status.has_client_key %}已保存 {{ status.client_key_masked }},可留空或填写新密钥{% else %}粘贴卖家提供的客户密钥(与激活码一起发放){% endif %}" autocomplete="off" {% if not status.has_client_key %}required{% endif %}>
|
||||||
<div class="hint">与授权站后台「系统设置」中的密钥一致,本地保存后下次可留空。</div>
|
<div class="hint">每张激活码绑定独立密钥;兑换后本地保存,续费一般可留空。</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label>激活码</label>
|
<label>激活码</label>
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# LICENSE_API_URL / LICENSE_CLIENT_KEY(可选)
|
# LICENSE_API_URL / LICENSE_CLIENT_KEY(可选)
|
||||||
# 复制为仓库根目录 license.env(已 gitignore)。
|
# 复制为仓库根目录 license.env(已 gitignore)。
|
||||||
# CLIENT_API_KEY 也可直接在软件 /license 页填写,不必改本文件。
|
# 客户密钥也可直接在软件 /license 页填写(与激活码一起由卖家发放)。
|
||||||
|
|
||||||
LICENSE_API_URL=https://sq.bz121.com
|
LICENSE_API_URL=https://sq.bz121.com
|
||||||
# LICENSE_CLIENT_KEY=
|
# LICENSE_CLIENT_KEY=
|
||||||
|
|||||||
Reference in New Issue
Block a user