feat(open-api): 0.3.2 开放能力版 — API Token 网关 + Tier A 契约 + 版本清单

- Token 域模块: tsp_ 前缀 + SHA-256 哈希存储 (0600), 明文仅创建时返回一次,
  吊销立即生效, last_used_at 节流落盘 (≥60s)
- Bearer 网关中间件 (与密码会话并行): 五档 scope 校验 (read:market/read:ext/
  read:analysis/run:backtest/paper:trade) + 滑动窗口限流 (默认 120/min,
  OPEN_API_RATE_LIMIT_PER_MIN 可调) + X-RateLimit 响应头; 管理接口永不对 Token 开放
- GET /api/openapi.json?tier=a 契约视图: 按网关规则表过滤 OpenAPI 规范 (58 路径),
  规则表即唯一契约源
- 设置 → 开放接口: Token 管理 UI (创建表单/scope 说明/明文一次性展示/吊销)
- 设置 → 系统设置 检查更新: 静态链接升级为真实版本对比 (GitHub API 优先,
  latest.json 清单兜底), 发现新版本给下载入口
- release.yml 新增 manifest job: Release 附 latest.json (版本/三平台下载地址/sha256),
  支持分批发布按实际资产生成
- 版本 0.3.1 → 0.3.2 (后端 __init__/pyproject + 前端 package.json)
- docs: features.md 开放接口章节; open-platform-plan.md V1/V2 标记已实现
- 测试: test_api_gateway.py 8 项 (域模块/scope 映射/E2E 401-403-200 矩阵/
  429+Retry-After/吊销即拒), 全量 2394 通过
This commit is contained in:
shy3130
2026-09-25 22:38:58 +08:00
parent 5f39f7dd8d
commit 7f628816b9
16 changed files with 953 additions and 24 deletions
+74 -1
View File
@@ -18,7 +18,7 @@ on:
version:
description: '版本号 (如 v0.1.33, 用作 Release tag 和标题)'
required: true
default: 'v0.1.33'
default: 'v0.3.2'
platforms:
description: '要构建的平台 (逗号分隔: windows,macos,linux)'
required: true
@@ -234,3 +234,76 @@ jobs:
- 含纯 Polars 回测引擎; 不含 vectorbt 回测(为控制体积)
- 系统通知: 设置 → 实时监控 → 系统通知
- 检查更新: 设置 → 系统设置 → 关于
# 第三步: 生成 latest.json 更新清单 (客户端「检查更新」的稳定契约)
manifest:
needs: build
# fail-fast: false 时部分平台失败会让 build 整体标红, 但已成功的平台资产
# 依然有效 — 清单照样生成 (以 Release 上实际存在的资产为准, 天然支持
# "今天发 Windows, 明天补 mac" 的分批发布: 每次按当前 Release 全量重建)。
if: ${{ !cancelled() && needs.build.result != 'skipped' }}
runs-on: ubuntu-latest
steps:
- name: 生成并上传 latest.json
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.event.inputs.version }}
run: |
set -euo pipefail
mkdir -p assets && cd assets
# 只拉已知的三个安装包 (latest.json 自身等其他资产不进清单)
gh release download "$TAG" --repo "$GITHUB_REPOSITORY" \
--pattern 'TickFlowStockPanel-Setup-x64.exe' \
--pattern 'TickFlowStockPanel-macos-arm64.dmg' \
--pattern 'TickFlowStockPanel-linux-x64.tar.gz' \
--clobber || true
cd ..
RELEASE_JSON=$(gh api "repos/$GITHUB_REPOSITORY/releases/tags/$TAG")
TAG="$TAG" REPO="$GITHUB_REPOSITORY" RELEASE_JSON="$RELEASE_JSON" python3 - <<'EOF'
import hashlib, json, os, sys
from pathlib import Path
tag = os.environ["TAG"]
repo = os.environ["REPO"]
rel = json.loads(os.environ["RELEASE_JSON"])
# 资产文件名 → 渠道名 (客户端按渠道取下载地址)
channels_cfg = {
"TickFlowStockPanel-Setup-x64.exe": "windows",
"TickFlowStockPanel-macos-arm64.dmg": "macos-arm64",
"TickFlowStockPanel-linux-x64.tar.gz": "linux-x64",
}
by_name = {a["name"]: a for a in rel.get("assets", [])}
channels = {}
for name, channel in channels_cfg.items():
local = Path("assets") / name
if not local.exists():
continue # 本次未构建该平台 (分批发布), 清单只列实际存在的
sha = hashlib.sha256(local.read_bytes()).hexdigest()
channels[channel] = {
"name": name,
"url": by_name[name]["browser_download_url"],
"size": by_name[name]["size"],
"sha256": sha,
}
if not channels:
print("::warning::Release 上没有任何安装包资产, 跳过 latest.json 生成")
sys.exit(0)
# version 去掉 v 前缀, 与 package.json 语义版本一致
version = tag[1:] if tag.startswith("v") else tag
manifest = {
"version": version,
"tag": tag,
"released_at": rel.get("published_at") or rel.get("created_at"),
"prerelease": bool(rel.get("prerelease")),
"notes_url": f"https://github.com/{repo}/releases/tag/{tag}",
"channels": channels,
}
Path("latest.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(manifest, indent=2))
EOF
# --clobber: 同名资产覆盖 (重跑同一版本时更新清单而不是追加 _01 副本)
gh release upload "$TAG" latest.json --repo "$GITHUB_REPOSITORY" --clobber