文档修改
This commit is contained in:
@@ -98,13 +98,15 @@ cd crypto_monitor_user
|
|||||||
|
|
||||||
## 六、整机许可(月卡 / 季卡 / 年卡)
|
## 六、整机许可(月卡 / 季卡 / 年卡)
|
||||||
|
|
||||||
|
- 云端授权地址(已预置):**https://sq.bz121.com/** — 配置见仓库根目录 **`license.env`**(四个子项目启动时自动加载)。
|
||||||
|
- 服务器 `crypto_monitor_web` 的 `CLIENT_API_KEY` 须与 `license.env` 里 `LICENSE_CLIENT_KEY` **完全一致**。
|
||||||
- 用户自备电脑部署;**整机**一个设备 ID,四个 `crypto_monitor_*` 共用仓库根目录 **`.license/`** 缓存。
|
- 用户自备电脑部署;**整机**一个设备 ID,四个 `crypto_monitor_*` 共用仓库根目录 **`.license/`** 缓存。
|
||||||
- 未授权或已过期:关键位监控、复盘、统计等 **全部不可用**(后台监控线程亦停止)。
|
- 未授权或已过期:关键位监控、复盘、统计等 **全部不可用**(后台监控线程亦停止)。
|
||||||
- 每 **3** 天联网校验一次(`LICENSE_CHECK_INTERVAL_DAYS`);断网宽限默认 **7** 天。
|
- 每 **3** 天联网校验一次(`LICENSE_CHECK_INTERVAL_DAYS`);断网宽限默认 **7** 天。
|
||||||
- 购买:微信 **`dekun03`**,添加好友时 **备注必须填写设备 ID**(打开任意实例的 **`/license`** 页复制)。
|
- 购买:微信 **`dekun03`**,添加好友时 **备注必须填写设备 ID**(打开任意实例的 **`/license`** 页复制)。
|
||||||
- 定价:月卡 ¥199 / 季卡 ¥399 / 年卡 ¥699;续费在剩余天数上叠加(由云端实现)。
|
- 定价:月卡 ¥199 / 季卡 ¥399 / 年卡 ¥699;续费在剩余天数上叠加(由云端实现)。
|
||||||
- 签发与管理 Web 为 **独立云端项目**,不在本仓库;用户端 API 约定见 **[docs/LICENSE_API.md](./docs/LICENSE_API.md)**。
|
- 签发与管理 Web 为 **独立云端项目**,不在本仓库;用户端 API 约定见 **[docs/LICENSE_API.md](./docs/LICENSE_API.md)**。
|
||||||
- 配置:各子目录 `.env` 或仓库根 `.env` 中的 `LICENSE_API_URL`、`LICENSE_CLIENT_KEY` 等(见各 `.env.example`)。本地开发可设 `LICENSE_DISABLED=true`。
|
- 配置:优先 **`license.env`**(可提交 Git);本地覆盖可写仓库根 `.env` 的 `LICENSE_*`。本地调试可设 `LICENSE_DISABLED=true`。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -29,18 +29,12 @@ APP_AUTH_DISABLED=true
|
|||||||
# Flask 会话密钥(必须替换为长随机字符串)
|
# Flask 会话密钥(必须替换为长随机字符串)
|
||||||
FLASK_SECRET_KEY=CHANGE_TO_LONG_RANDOM_SECRET
|
FLASK_SECRET_KEY=CHANGE_TO_LONG_RANDOM_SECRET
|
||||||
|
|
||||||
# ---------- 整机许可(也可写在仓库根目录 .env 的 LICENSE_* 变量)----------
|
# ---------- 整机许可(默认由仓库根目录 license.env 提供,此处可留空或覆盖)----------
|
||||||
# 云端授权服务根地址(独立项目部署,见 docs/LICENSE_API.md)
|
# LICENSE_API_URL=https://sq.bz121.com
|
||||||
LICENSE_API_URL=https://license.example.com
|
# LICENSE_CLIENT_KEY=见 license.env,须与云端 CLIENT_API_KEY 一致
|
||||||
LICENSE_CLIENT_KEY=REPLACE_WITH_CLIENT_KEY
|
# LICENSE_CHECK_INTERVAL_DAYS=3
|
||||||
# 联网校验间隔(天),默认 3
|
# LICENSE_OFFLINE_GRACE_DAYS=7
|
||||||
LICENSE_CHECK_INTERVAL_DAYS=3
|
# LICENSE_WECHAT_ID=dekun03
|
||||||
# 断网后仍可使用的天数(基于上次校验成功时间),默认 7
|
|
||||||
LICENSE_OFFLINE_GRACE_DAYS=7
|
|
||||||
# 购买联系微信;添加好友时备注请填写设备 ID(见 /license 页)
|
|
||||||
LICENSE_WECHAT_ID=dekun03
|
|
||||||
# LICENSE_WECHAT_REMARK=自定义备注文案
|
|
||||||
# 仅本地开发可设为 true,跳过许可
|
|
||||||
# LICENSE_DISABLED=false
|
# LICENSE_DISABLED=false
|
||||||
|
|
||||||
# 企业微信机器人 Webhook(用于行情/风控推送)
|
# 企业微信机器人 Webhook(用于行情/风控推送)
|
||||||
|
|||||||
@@ -29,12 +29,9 @@ APP_AUTH_DISABLED=true
|
|||||||
# Flask 会话密钥(必须替换为长随机字符串)
|
# Flask 会话密钥(必须替换为长随机字符串)
|
||||||
FLASK_SECRET_KEY=CHANGE_TO_LONG_RANDOM_SECRET
|
FLASK_SECRET_KEY=CHANGE_TO_LONG_RANDOM_SECRET
|
||||||
|
|
||||||
# ---------- 整机许可(也可写在仓库根目录 .env 的 LICENSE_* 变量)----------
|
# ---------- 整机许可(默认由仓库根目录 license.env 提供)----------
|
||||||
LICENSE_API_URL=https://license.example.com
|
# LICENSE_API_URL=https://sq.bz121.com
|
||||||
LICENSE_CLIENT_KEY=REPLACE_WITH_CLIENT_KEY
|
# LICENSE_CLIENT_KEY=见 license.env
|
||||||
LICENSE_CHECK_INTERVAL_DAYS=3
|
|
||||||
LICENSE_OFFLINE_GRACE_DAYS=7
|
|
||||||
LICENSE_WECHAT_ID=dekun03
|
|
||||||
# LICENSE_DISABLED=false
|
# LICENSE_DISABLED=false
|
||||||
|
|
||||||
# 企业微信机器人 Webhook(用于行情/风控推送)
|
# 企业微信机器人 Webhook(用于行情/风控推送)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
| **[关键位自动下单说明.md](./关键位自动下单说明.md)** | 关键位自动开仓的 RR、止盈止损、结案原因与 `.env` |
|
| **[关键位自动下单说明.md](./关键位自动下单说明.md)** | 关键位自动开仓的 RR、止盈止损、结案原因与 `.env` |
|
||||||
| **[部署文档.md](./部署文档.md)** | Ubuntu、PM2、**SSH SOCKS** 访问 Gate API 等 |
|
| **[部署文档.md](./部署文档.md)** | Ubuntu、PM2、**SSH SOCKS** 访问 Gate API 等 |
|
||||||
|
|
||||||
另:**Binance U 本位** 对等实现见同级的 **`crypto_monitor_binance`** 仓库。
|
另:**Binance U 本位** 对等实现见同级的 **`crypto_monitor_binance`** 目录。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -29,12 +29,9 @@ APP_AUTH_DISABLED=true
|
|||||||
# Flask 会话密钥(必须替换为长随机字符串)
|
# Flask 会话密钥(必须替换为长随机字符串)
|
||||||
FLASK_SECRET_KEY=CHANGE_TO_LONG_RANDOM_SECRET
|
FLASK_SECRET_KEY=CHANGE_TO_LONG_RANDOM_SECRET
|
||||||
|
|
||||||
# ---------- 整机许可(也可写在仓库根目录 .env 的 LICENSE_* 变量)----------
|
# ---------- 整机许可(默认由仓库根目录 license.env 提供)----------
|
||||||
LICENSE_API_URL=https://license.example.com
|
# LICENSE_API_URL=https://sq.bz121.com
|
||||||
LICENSE_CLIENT_KEY=REPLACE_WITH_CLIENT_KEY
|
# LICENSE_CLIENT_KEY=见 license.env
|
||||||
LICENSE_CHECK_INTERVAL_DAYS=3
|
|
||||||
LICENSE_OFFLINE_GRACE_DAYS=7
|
|
||||||
LICENSE_WECHAT_ID=dekun03
|
|
||||||
# LICENSE_DISABLED=false
|
# LICENSE_DISABLED=false
|
||||||
|
|
||||||
# 企业微信机器人 Webhook(用于行情/风控推送)
|
# 企业微信机器人 Webhook(用于行情/风控推送)
|
||||||
|
|||||||
@@ -29,12 +29,9 @@ APP_AUTH_DISABLED=true
|
|||||||
# Flask 会话密钥(必须替换为长随机字符串)
|
# Flask 会话密钥(必须替换为长随机字符串)
|
||||||
FLASK_SECRET_KEY=CHANGE_TO_LONG_RANDOM_SECRET
|
FLASK_SECRET_KEY=CHANGE_TO_LONG_RANDOM_SECRET
|
||||||
|
|
||||||
# ---------- 整机许可(也可写在仓库根目录 .env 的 LICENSE_* 变量)----------
|
# ---------- 整机许可(默认由仓库根目录 license.env 提供)----------
|
||||||
LICENSE_API_URL=https://license.example.com
|
# LICENSE_API_URL=https://sq.bz121.com
|
||||||
LICENSE_CLIENT_KEY=REPLACE_WITH_CLIENT_KEY
|
# LICENSE_CLIENT_KEY=见 license.env
|
||||||
LICENSE_CHECK_INTERVAL_DAYS=3
|
|
||||||
LICENSE_OFFLINE_GRACE_DAYS=7
|
|
||||||
LICENSE_WECHAT_ID=dekun03
|
|
||||||
# LICENSE_DISABLED=false
|
# LICENSE_DISABLED=false
|
||||||
|
|
||||||
# 企业微信机器人 Webhook(用于行情/风控推送)
|
# 企业微信机器人 Webhook(用于行情/风控推送)
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# 用户端授权配置(crypto_monitor_user)
|
||||||
|
|
||||||
|
## 已预置
|
||||||
|
|
||||||
|
仓库根目录 **[license.env](../license.env)** 已写好:
|
||||||
|
|
||||||
|
| 变量 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| `LICENSE_API_URL` | `https://sq.bz121.com` |
|
||||||
|
| `LICENSE_CHECK_INTERVAL_DAYS` | `3` |
|
||||||
|
| `LICENSE_OFFLINE_GRACE_DAYS` | `7` |
|
||||||
|
| `LICENSE_WECHAT_ID` | `dekun03` |
|
||||||
|
|
||||||
|
四个 `crypto_monitor_*` 启动时会通过 `license_lib.load_shared_env()` **自动加载**,无需在每个子目录重复配置。
|
||||||
|
|
||||||
|
## 你必须做的一步(云端密钥对齐)
|
||||||
|
|
||||||
|
在授权服务器上查看:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep '^CLIENT_API_KEY=' /opt/crypto_monitor_web/.env
|
||||||
|
```
|
||||||
|
|
||||||
|
把输出值改成与 **`license.env`** 中一致:
|
||||||
|
|
||||||
|
```env
|
||||||
|
LICENSE_CLIENT_KEY=cm_user_sq_bz121_8f3a2c1d9e4b7a6f5d0e1b2c3a4f5e6
|
||||||
|
```
|
||||||
|
|
||||||
|
**方式 A(推荐)**:改服务器,与用户端 `license.env` 一致:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nano /opt/crypto_monitor_web/.env
|
||||||
|
# CLIENT_API_KEY=cm_user_sq_bz121_8f3a2c1d9e4b7a6f5d0e1b2c3a4f5e6
|
||||||
|
sudo systemctl restart crypto-monitor-web
|
||||||
|
```
|
||||||
|
|
||||||
|
**方式 B**:若服务器已用安装脚本生成过随机密钥,则把该密钥复制到用户端 `license.env` 的 `LICENSE_CLIENT_KEY=` 一行。
|
||||||
|
|
||||||
|
## 本地覆盖(可选)
|
||||||
|
|
||||||
|
仓库根 `.env`(不提交 Git)可写:
|
||||||
|
|
||||||
|
```env
|
||||||
|
LICENSE_CLIENT_KEY=你的密钥
|
||||||
|
```
|
||||||
|
|
||||||
|
会覆盖 `license.env` 中的同名项。
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
1. 启动任意子项目,浏览器打开 `http://127.0.0.1:端口/license`
|
||||||
|
2. 应能看到设备 ID,且不再提示「未配置 LICENSE_API_URL」
|
||||||
|
3. 在 https://sq.bz121.com/admin/ 生成激活码并兑换
|
||||||
|
|
||||||
|
## 关闭许可(仅开发)
|
||||||
|
|
||||||
|
在 `license.env` 或根 `.env` 中设置:
|
||||||
|
|
||||||
|
```env
|
||||||
|
LICENSE_DISABLED=true
|
||||||
|
```
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
# 整机许可(仓库根目录,四个 crypto_monitor_* 共用)
|
||||||
|
# 授权服务: https://sq.bz121.com/
|
||||||
|
# 服务器 /opt/crypto_monitor_web/.env 中 CLIENT_API_KEY 须与本文件 LICENSE_CLIENT_KEY 完全一致
|
||||||
|
|
||||||
|
LICENSE_API_URL=https://sq.bz121.com
|
||||||
|
LICENSE_CLIENT_KEY=cm_user_sq_bz121_8f3a2c1d9e4b7a6f5d0e1b2c3a4f5e6
|
||||||
|
LICENSE_CHECK_INTERVAL_DAYS=3
|
||||||
|
LICENSE_OFFLINE_GRACE_DAYS=7
|
||||||
|
LICENSE_WECHAT_ID=dekun03
|
||||||
|
LICENSE_DISABLED=false
|
||||||
+35
-21
@@ -24,24 +24,7 @@ _TEMPLATE_DIR = os.path.join(_REPO_ROOT, "license_templates")
|
|||||||
BJ_TZ = timezone(timedelta(hours=8))
|
BJ_TZ = timezone(timedelta(hours=8))
|
||||||
|
|
||||||
|
|
||||||
def load_shared_env() -> None:
|
def _apply_env_lines(text: str, *, license_only: bool, override: bool) -> None:
|
||||||
"""从仓库根目录 .env 加载 LICENSE_*(子项目 .env 已存在同名变量时不覆盖)。"""
|
|
||||||
path = os.path.join(_REPO_ROOT, ".env")
|
|
||||||
if not os.path.isfile(path):
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
raw_bytes = open(path, "rb").read()
|
|
||||||
except OSError:
|
|
||||||
return
|
|
||||||
text = ""
|
|
||||||
for enc in ("utf-8-sig", "utf-16", "utf-16-le", "utf-16-be"):
|
|
||||||
try:
|
|
||||||
text = raw_bytes.decode(enc)
|
|
||||||
break
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
if not text:
|
|
||||||
text = raw_bytes.decode("utf-8", errors="ignore")
|
|
||||||
text = text.replace("\x00", "")
|
text = text.replace("\x00", "")
|
||||||
for line in text.splitlines():
|
for line in text.splitlines():
|
||||||
raw = line.strip()
|
raw = line.strip()
|
||||||
@@ -49,14 +32,45 @@ def load_shared_env() -> None:
|
|||||||
continue
|
continue
|
||||||
key, value = raw.split("=", 1)
|
key, value = raw.split("=", 1)
|
||||||
clean_key = key.strip().lstrip("\ufeff")
|
clean_key = key.strip().lstrip("\ufeff")
|
||||||
if not clean_key.startswith("LICENSE_"):
|
if license_only and not clean_key.startswith("LICENSE_"):
|
||||||
continue
|
|
||||||
if clean_key in os.environ and (os.environ.get(clean_key) or "").strip():
|
|
||||||
continue
|
continue
|
||||||
clean_value = value.strip().strip('"').strip("'")
|
clean_value = value.strip().strip('"').strip("'")
|
||||||
|
if not clean_value:
|
||||||
|
continue
|
||||||
|
if not override and clean_key in os.environ and (os.environ.get(clean_key) or "").strip():
|
||||||
|
continue
|
||||||
os.environ[clean_key] = clean_value
|
os.environ[clean_key] = clean_value
|
||||||
|
|
||||||
|
|
||||||
|
def _read_env_file(path: str) -> str:
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
raw_bytes = open(path, "rb").read()
|
||||||
|
except OSError:
|
||||||
|
return ""
|
||||||
|
for enc in ("utf-8-sig", "utf-16", "utf-16-le", "utf-16-be"):
|
||||||
|
try:
|
||||||
|
return raw_bytes.decode(enc)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
return raw_bytes.decode("utf-8", errors="ignore")
|
||||||
|
|
||||||
|
|
||||||
|
def load_shared_env() -> None:
|
||||||
|
"""
|
||||||
|
加载顺序(后加载的可补空位):
|
||||||
|
1. 仓库根 license.env — 默认授权地址 https://sq.bz121.com
|
||||||
|
2. 仓库根 .env 中的 LICENSE_* — 可覆盖密钥等(勿提交 Git)
|
||||||
|
子目录 .env 若已先加载且已有 LICENSE_*,则不被覆盖。
|
||||||
|
"""
|
||||||
|
license_path = os.path.join(_REPO_ROOT, "license.env")
|
||||||
|
_apply_env_lines(_read_env_file(license_path), license_only=True, override=True)
|
||||||
|
|
||||||
|
root_env = os.path.join(_REPO_ROOT, ".env")
|
||||||
|
_apply_env_lines(_read_env_file(root_env), license_only=True, override=True)
|
||||||
|
|
||||||
|
|
||||||
def _env_bool(name: str, default: bool = False) -> bool:
|
def _env_bool(name: str, default: bool = False) -> bool:
|
||||||
return (os.getenv(name) or "").strip().lower() in ("1", "true", "yes", "on")
|
return (os.getenv(name) or "").strip().lower() in ("1", "true", "yes", "on")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user