修复部分已知Bug

This commit is contained in:
shy3130
2026-10-06 12:40:16 +08:00
parent 2b73e89b81
commit 4ba0c9bcc0
10 changed files with 95 additions and 18 deletions
+19 -6
View File
@@ -16,7 +16,7 @@ on:
workflow_dispatch:
inputs:
version:
description: '版本号 (如 v0.1.33, 用作 Release tag 和标题)'
description: '版本号 (如 v0.1.33, 用作 Release tag 和标题; 必须与 frontend/package.json 的 version 一致, 否则流水线直接失败)'
required: true
default: 'v0.3.3'
platforms:
@@ -39,8 +39,21 @@ jobs:
matrix: ${{ steps.filter.outputs.matrix }}
ver: ${{ steps.filter.outputs.ver }}
steps:
- name: 检出代码
# 仅为校验版本一致性 (读 frontend/package.json), 浅检出足够
uses: actions/checkout@v4
- id: filter
run: |
# 版本号唯一权威 = frontend/package.json (安装包、页面显示、更新检查同源)。
# 发版输入与它不一致直接失败, 防止 tag/安装包/页面三处版本漂移。
PKG_VER=$(python3 -c "import json; print(json.load(open('frontend/package.json'))['version'])")
REQ_VER='${{ github.event.inputs.version }}'
if [ "v$PKG_VER" != "$REQ_VER" ]; then
echo "::error::版本不一致: 发版输入 $REQ_VER, frontend/package.json 为 v$PKG_VER。请以 frontend/package.json 为准, 统一版本号后再发版。"
exit 1
fi
REQUESTED="${{ github.event.inputs.platforms }}"
# macOS 仅构建 ARM64 (Apple Silicon), 不构建 Intel:
# 1) GitHub 最后一个原生 Intel runner (macos-13) 已于 2025-12-04 退役;
@@ -124,12 +137,12 @@ jobs:
- name: 打包 (PyInstaller)
# 在 backend 目录跑 (用该目录的 venv: pywebview/polars 等依赖)。
# 产物输出到 backend/dist/ (PyInstaller 默认路径), 与 packaging/tickflow.iss
# 第 78 行的 Source 路径 (..\backend\dist\TSP\*) 保持一致。
# SPECPATH 由 spec 文件位置决定 (packaging/tickflow.spec → 项目根),
# 产物输出到 backend/dist/ (PyInstaller 默认路径), 与 packaging/tsp.iss
# 的 Source 路径 (..\backend\dist\TSP\*) 保持一致。
# SPECPATH 由 spec 文件位置决定 (packaging/tsp.spec → 项目根),
# 与 cwd 无关, 故 frontend/dist、tiers.yaml 等相对路径仍正确。
working-directory: backend
run: uv run pyinstaller ../packaging/tickflow.spec --noconfirm
run: uv run pyinstaller ../packaging/tsp.spec --noconfirm
- name: 检查 Polars 兼容内核 (仅 Windows)
if: matrix.platform == 'windows'
@@ -158,7 +171,7 @@ jobs:
run: |
$ver = (Get-Content frontend/package.json | ConvertFrom-Json).version
echo "版本号: $ver"
ISCC.exe "/DMyAppVersion=$ver" packaging/tickflow.iss
ISCC.exe "/DMyAppVersion=$ver" packaging/tsp.iss
# 把生成的 Setup.exe 移到工作区根 (Release 上传用)
Move-Item packaging/Output/TSP-Setup-$ver.exe ${{ matrix.artifact }} -Force
shell: pwsh
+22 -1
View File
@@ -1,8 +1,29 @@
"""Tick Stock Panel backend."""
import json
import sys
from pathlib import Path
__version__ = "0.3.2"
# 版本号唯一权威 = frontend/package.json: 安装包 AppVersion (release.yml 读它传给
# ISCC)、macOS 包元数据 (tsp.spec 读它)、页面版本徽标与更新检查 (本模块) 全部同源,
# 改版本只改那一份文件。dev 模式读仓库文件; PyInstaller 冻结后读 _MEIPASS/package.json
# (tsp.spec 的 datas 会收集)。读取失败回退硬编码值 —— 版本号绝不阻塞应用启动。
_FALLBACK_VERSION = "0.3.3"
def _load_version() -> str:
if getattr(sys, "frozen", False):
pkg = Path(getattr(sys, "_MEIPASS", ".")) / "package.json"
else:
# backend/app/__init__.py 上溯两级 = 项目根
pkg = Path(__file__).resolve().parents[2] / "frontend" / "package.json"
try:
return json.loads(pkg.read_text(encoding="utf-8"))["version"]
except Exception:
return _FALLBACK_VERSION
__version__ = _load_version()
# Windows 默认 stdout/stderr 编码为 GBK(cp936),TickFlow SDK 内部输出含 emoji 的
# 指数/标的名称(如 \U0001f193)时会抛 UnicodeEncodeError,导致请求失败。
+1 -1
View File
@@ -841,7 +841,7 @@ def get_version(request: Request) -> dict:
"""
from app import __version__
# 1. 优先用 app.__version__ (唯一权威版本, 打包期由 PyInstaller 注入)
# 1. 优先用 app.__version__ (唯一权威版本, 运行时从 frontend/package.json 加载)
if __version__:
v = __version__.strip()
return {"version": v if v.startswith("v") else f"v{v}"}
+1 -1
View File
@@ -33,7 +33,7 @@ def _user_data_root() -> Path:
- 用户体验: 用户选了安装目录, 自然期望「程序和数据都在这」, 单一总目录更直观。
- 数据安全: Inno Setup 覆盖安装(升级)时只往 {app} 写新程序文件, 不会清空
目录里不在安装清单上的运行时文件 (data/ 即此类), 故覆盖安装不丢数据。
(注意: 卸载时需在 .iss 中豁免 data/, 见 packaging/tickflow.iss 的 [UninstallDelete]。)
(注意: 卸载时需在 .iss 中豁免 data/, 见 packaging/tsp.iss 的 [UninstallDelete]。)
旧版本数据迁移: 见 DataStore._migrate_legacy_data_dir(), 老用户首次启动自动搬迁。
"""
# 打包桌面版: exe 同级的 data/ 子目录 (与程序同一总目录, 覆盖安装不丢数据)
+1 -1
View File
@@ -25,7 +25,7 @@ from pathlib import Path
logger = logging.getLogger(__name__)
_APP_NAME = "TSP 股票面板"
_APP_NAME = "TSP"
_BASE_PORT = 3018
_PORT_PROBE_RANGE = 50 # 从 3018 起最多试 50 个端口
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "tickflow-stock-panel-backend"
version = "0.3.2"
version = "0.3.3"
description = "A 股选股 + 监控 + 回测面板 — TickFlow 适配"
requires-python = ">=3.11"
license = { text = "MIT" }
+37
View File
@@ -0,0 +1,37 @@
"""版本号单一来源: app.__version__ 运行时从 frontend/package.json 加载。
dev 模式读仓库文件, PyInstaller 冻结后读 _MEIPASS/package.json (tsp.spec 收集),
两者必须返回同一版本 —— 保证安装包、页面徽标、更新检查的"当前版本"永不漂移。
"""
import json
import sys
from pathlib import Path
import app as app_pkg
from app import __version__
REPO_ROOT = Path(__file__).resolve().parents[2]
PKG_JSON = REPO_ROOT / "frontend" / "package.json"
def test_dev_version_matches_package_json():
"""dev 模式: __version__ 与 frontend/package.json 完全一致。"""
assert __version__ == json.loads(PKG_JSON.read_text(encoding="utf-8"))["version"]
def test_frozen_reads_mepass_package_json(monkeypatch, tmp_path):
"""frozen 模式: 从 _MEIPASS/package.json 读版本 (tsp.spec 收集的那份)。"""
(tmp_path / "package.json").write_text(
json.dumps({"version": "9.9.9"}), encoding="utf-8"
)
monkeypatch.setattr(sys, "frozen", True, raising=False)
monkeypatch.setattr(sys, "_MEIPASS", str(tmp_path), raising=False)
assert app_pkg._load_version() == "9.9.9"
def test_frozen_missing_file_falls_back(monkeypatch, tmp_path):
"""frozen 模式文件缺失/损坏: 回退硬编码值, 不抛异常 (版本读取不能阻塞启动)。"""
monkeypatch.setattr(sys, "frozen", True, raising=False)
monkeypatch.setattr(sys, "_MEIPASS", str(tmp_path), raising=False) # 空目录
assert app_pkg._load_version() == app_pkg._FALLBACK_VERSION
+1 -1
View File
@@ -2513,7 +2513,7 @@ all = [
[[package]]
name = "tickflow-stock-panel-backend"
version = "0.3.2"
version = "0.3.3"
source = { editable = "." }
dependencies = [
{ name = "apscheduler" },
+7 -4
View File
@@ -5,8 +5,8 @@
; 单个 Setup.exe 安装程序 (双击→安装向导→快捷方式→可卸载)。
;
; 构建 (本地):
; 1. 先跑 PyInstaller: cd backend && uv run pyinstaller ../packaging/tickflow.spec
; 2. 再跑 Inno Setup: ISCC.exe packaging\tickflow.iss
; 1. 先跑 PyInstaller: cd backend && uv run pyinstaller ../packaging/tsp.spec
; 2. 再跑 Inno Setup: ISCC.exe packaging\tsp.iss
; 3. 产物: packaging\Output\TSP-Setup-x.x.x.exe
;
; 设计决策:
@@ -18,7 +18,7 @@
; - 卸载入口 (控制面板可见)
; ===========================================================================
#define MyAppName "TSP 股票面板"
#define MyAppName "TSP"
#define MyAppNameEN "Tick Stock Panel"
#define MyAppExeName "TSP.exe"
#define MyAppPublisher "TSP"
@@ -96,7 +96,10 @@ Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: de
; 刷新 Windows 图标缓存: 覆盖安装时 exe 路径不变 (D:\TSP\TSP.exe), 资源管理器
; 会一直显示缓存里的旧图标 (首个版本为透明底)。ie4uinit -show 强制重建缓存,
; 装完快捷方式立即显示当前 exe 内嵌图标, 无需用户重启资源管理器。
Filename: "{sys}\ie4uinit.exe"; Parameters: "-show"; Flags: runhidden
; ie4uinit.exe 是 IE 组件, 在已移除 IE 的系统 (如 Win11 24H2+) 上不存在 ——
; 不 Check 存在性直接执行会弹「无法执行文件」错误, 并中断后续 [Run] 条目
; (安装完成自动启动应用也会被跳过)。
Filename: "{sys}\ie4uinit.exe"; Parameters: "-show"; Flags: runhidden; Check: FileExists(ExpandConstant('{sys}\ie4uinit.exe'))
; 安装完成后启动应用
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#MyAppName}}"; Flags: nowait postinstall skipifsilent
@@ -10,7 +10,7 @@
构建 (在项目根目录):
cd frontend && pnpm build # 先构建前端到 frontend/dist
pyinstaller packaging/tickflow.spec # 产物在 dist/TSP/
pyinstaller packaging/tsp.spec # 产物在 dist/TSP/
"""
import sys
from importlib.util import find_spec
@@ -127,6 +127,9 @@ for pkg in (
datas += [(FRONTEND_DIST, "static")]
# tiers.yaml → 包根 (config.py frozen 模式读 _MEIPASS/tiers.yaml)
datas += [(TIERS_YAML, ".")]
# frontend/package.json → 包根 (app/__init__.py frozen 模式读 _MEIPASS/package.json,
# __version__ 与安装包 AppVersion / 页面显示同源, 版本号只改 package.json 一处)
datas += [(str(ROOT / "frontend" / "package.json"), ".")]
# 内置策略 → app/strategy/builtin/ (importlib 动态加载, 不能进 PYZ)
datas += [(BUILTIN_STRATEGIES, "app/strategy/builtin")]
@@ -220,7 +223,7 @@ if _IS_MACOS:
version=APP_VERSION, # → CFBundleShortVersionString / CFBundleVersion
info_plist={
"CFBundleName": "Tick Stock Panel",
"CFBundleDisplayName": "TSP 股票面板",
"CFBundleDisplayName": "TSP",
"CFBundleVersion": APP_VERSION,
"NSHighResolutionCapable": True,
"LSMinimumSystemVersion": "10.13",