文档修改

This commit is contained in:
2026-05-21 17:51:21 +08:00
parent ab854b2c3c
commit b2823713c9
9 changed files with 126 additions and 53 deletions
+3 -1
View File
@@ -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/`** 缓存。
- 未授权或已过期:关键位监控、复盘、统计等 **全部不可用**(后台监控线程亦停止)。
-**3** 天联网校验一次(`LICENSE_CHECK_INTERVAL_DAYS`);断网宽限默认 **7** 天。
- 购买:微信 **`dekun03`**,添加好友时 **备注必须填写设备 ID**(打开任意实例的 **`/license`** 页复制)。
- 定价:月卡 ¥199 / 季卡 ¥399 / 年卡 ¥699;续费在剩余天数上叠加(由云端实现)。
- 签发与管理 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`
---
+6 -12
View File
@@ -29,18 +29,12 @@ APP_AUTH_DISABLED=true
# Flask 会话密钥(必须替换为长随机字符串)
FLASK_SECRET_KEY=CHANGE_TO_LONG_RANDOM_SECRET
# ---------- 整机许可(也可写在仓库根目录 .env 的 LICENSE_* 变量----------
# 云端授权服务根地址(独立项目部署,见 docs/LICENSE_API.md
LICENSE_API_URL=https://license.example.com
LICENSE_CLIENT_KEY=REPLACE_WITH_CLIENT_KEY
# 联网校验间隔(天),默认 3
LICENSE_CHECK_INTERVAL_DAYS=3
# 断网后仍可使用的天数(基于上次校验成功时间),默认 7
LICENSE_OFFLINE_GRACE_DAYS=7
# 购买联系微信;添加好友时备注请填写设备 ID(见 /license 页)
LICENSE_WECHAT_ID=dekun03
# LICENSE_WECHAT_REMARK=自定义备注文案
# 仅本地开发可设为 true,跳过许可
# ---------- 整机许可(默认由仓库根目录 license.env 提供,此处可留空或覆盖----------
# LICENSE_API_URL=https://sq.bz121.com
# LICENSE_CLIENT_KEY=见 license.env,须与云端 CLIENT_API_KEY 一致
# LICENSE_CHECK_INTERVAL_DAYS=3
# LICENSE_OFFLINE_GRACE_DAYS=7
# LICENSE_WECHAT_ID=dekun03
# LICENSE_DISABLED=false
# 企业微信机器人 Webhook(用于行情/风控推送)
+3 -6
View File
@@ -29,12 +29,9 @@ APP_AUTH_DISABLED=true
# Flask 会话密钥(必须替换为长随机字符串)
FLASK_SECRET_KEY=CHANGE_TO_LONG_RANDOM_SECRET
# ---------- 整机许可(也可写在仓库根目录 .env 的 LICENSE_* 变量----------
LICENSE_API_URL=https://license.example.com
LICENSE_CLIENT_KEY=REPLACE_WITH_CLIENT_KEY
LICENSE_CHECK_INTERVAL_DAYS=3
LICENSE_OFFLINE_GRACE_DAYS=7
LICENSE_WECHAT_ID=dekun03
# ---------- 整机许可(默认由仓库根目录 license.env 提供----------
# LICENSE_API_URL=https://sq.bz121.com
# LICENSE_CLIENT_KEY=见 license.env
# LICENSE_DISABLED=false
# 企业微信机器人 Webhook(用于行情/风控推送)
+1 -1
View File
@@ -10,7 +10,7 @@
| **[关键位自动下单说明.md](./关键位自动下单说明.md)** | 关键位自动开仓的 RR、止盈止损、结案原因与 `.env` |
| **[部署文档.md](./部署文档.md)** | Ubuntu、PM2、**SSH SOCKS** 访问 Gate API 等 |
另:**Binance U 本位** 对等实现见同级的 **`crypto_monitor_binance`** 仓库
另:**Binance U 本位** 对等实现见同级的 **`crypto_monitor_binance`** 目录
---
+3 -6
View File
@@ -29,12 +29,9 @@ APP_AUTH_DISABLED=true
# Flask 会话密钥(必须替换为长随机字符串)
FLASK_SECRET_KEY=CHANGE_TO_LONG_RANDOM_SECRET
# ---------- 整机许可(也可写在仓库根目录 .env 的 LICENSE_* 变量----------
LICENSE_API_URL=https://license.example.com
LICENSE_CLIENT_KEY=REPLACE_WITH_CLIENT_KEY
LICENSE_CHECK_INTERVAL_DAYS=3
LICENSE_OFFLINE_GRACE_DAYS=7
LICENSE_WECHAT_ID=dekun03
# ---------- 整机许可(默认由仓库根目录 license.env 提供----------
# LICENSE_API_URL=https://sq.bz121.com
# LICENSE_CLIENT_KEY=见 license.env
# LICENSE_DISABLED=false
# 企业微信机器人 Webhook(用于行情/风控推送)
+3 -6
View File
@@ -29,12 +29,9 @@ APP_AUTH_DISABLED=true
# Flask 会话密钥(必须替换为长随机字符串)
FLASK_SECRET_KEY=CHANGE_TO_LONG_RANDOM_SECRET
# ---------- 整机许可(也可写在仓库根目录 .env 的 LICENSE_* 变量----------
LICENSE_API_URL=https://license.example.com
LICENSE_CLIENT_KEY=REPLACE_WITH_CLIENT_KEY
LICENSE_CHECK_INTERVAL_DAYS=3
LICENSE_OFFLINE_GRACE_DAYS=7
LICENSE_WECHAT_ID=dekun03
# ---------- 整机许可(默认由仓库根目录 license.env 提供----------
# LICENSE_API_URL=https://sq.bz121.com
# LICENSE_CLIENT_KEY=见 license.env
# LICENSE_DISABLED=false
# 企业微信机器人 Webhook(用于行情/风控推送)
+62
View File
@@ -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
View File
@@ -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
View File
@@ -24,24 +24,7 @@ _TEMPLATE_DIR = os.path.join(_REPO_ROOT, "license_templates")
BJ_TZ = timezone(timedelta(hours=8))
def load_shared_env() -> 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")
def _apply_env_lines(text: str, *, license_only: bool, override: bool) -> None:
text = text.replace("\x00", "")
for line in text.splitlines():
raw = line.strip()
@@ -49,14 +32,45 @@ def load_shared_env() -> None:
continue
key, value = raw.split("=", 1)
clean_key = key.strip().lstrip("\ufeff")
if not clean_key.startswith("LICENSE_"):
continue
if clean_key in os.environ and (os.environ.get(clean_key) or "").strip():
if license_only and not clean_key.startswith("LICENSE_"):
continue
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
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:
return (os.getenv(name) or "").strip().lower() in ("1", "true", "yes", "on")