mirror of
https://github.com/shy3130/tick-stock-panel.git
synced 2026-11-02 10:26:13 +00:00
修复部分已知Bug
This commit is contained in:
@@ -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
@@ -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,导致请求失败。
|
||||
|
||||
@@ -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}"}
|
||||
|
||||
@@ -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/ 子目录 (与程序同一总目录, 覆盖安装不丢数据)
|
||||
|
||||
@@ -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,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" }
|
||||
|
||||
@@ -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
|
||||
Generated
+1
-1
@@ -2513,7 +2513,7 @@ all = [
|
||||
|
||||
[[package]]
|
||||
name = "tickflow-stock-panel-backend"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "apscheduler" },
|
||||
|
||||
@@ -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",
|
||||
Reference in New Issue
Block a user