Files
FinDataHub/DEVLOG.md
T
FinDataHub d7d2a1e8ad chore(repo): 补齐长期未入库的核心源码、文档与本次复查修复
背景
- HEAD 长期停在 08-03 的安全审计提交,此后六周的工作全部只在工作区。
- 更严重的是部分源码从未进入过版本控制(非"改了没提交"):
    · Services/findatahub-core/  索引 0 条 —— 18 个核心模块(pipeline/gate/cleaner/
      db/cross_validator/health/retry…)全新 clone 拿不到
    · Services/findatahub-client/ 索引 0 条
    · App/Sources 磁盘 74 个 .swift、索引 54 —— 20 个未入库(AutoRecoveryManager、
      全套 Settings*Section、PowerSaver、MySQLOnDemandManager、DependencyScanner…)
  一次 `git clean -fd`、误删目录或磁盘故障即可不可恢复地丢失这些文件。

本提交内容
1. 补齐上述未跟踪源码、测试、文档与报告(Services/App/Docs/Tests/根报告)。
2. 纳入 .workbuddy/memory(项目知识)与 .github CI 配置。
3. 本次代码与架构复查的修复(详见下一段说明):
   - Scripts/bin/common.py         PidManager.status 改 split(":", 1),修复时间戳被截断到整点
   - Scripts/bin/db_pool.py        取连接加超时、归还前 rollback、失败归还池位、双检锁初始化
   - Scripts/bin/newsflash_backend.py  _query_all 改 try/finally 无条件归还连接
   - Scripts/bin/newsflash_collect.py 新增 _install_crash_handlers(faulthandler + excepthook)
   - tushare_fetcher/core/fetcher.py  _fetch_static 增量走 upsert,仅全量快照才 replace
   - tushare_fetcher/scripts/tushare_sync.sh 银层迁移补 stock_weekly/stock_monthly;
                                     月度增加分红按报告期全量刷新
   - kuaixun-service/extend/content_enricher.py 禁用自动重定向 + 逐跳 SSRF 校验
   - App/.../AutoRecoveryManager.swift 反复故障改告警不熔断(避免拉长数据空窗)
   - 新增 Scripts/bin/repair_dividend_history.py(分红历史修复,按报告期分页幂等 upsert)
   - 新增 Docs/repro/*(两个可运行的缺陷复现脚本)
   - 补齐 6 个文件共 20 条回归用例(均做过证伪:旧实现下必然失败)

安全
- 已确认未纳入任何凭据:Services/tushare_fetcher/.env 被其 .gitignore 覆盖;
  ~/.config/findatahub/env(0600)在仓库外。

验证
- Python 测试 1074 passed(Scripts/bin 207 / core 105 / tushare 174 / kuaixun 405 / akshare 183)
- Swift 经 `swift build --disable-sandbox` 编译通过,产物 v1.5.2 已部署
2026-09-18 22:37:55 +08:00

249 KiB
Raw Blame History

FinDataHub Mac App - DEVLOG

2026-09-13 — 提交后正确性检查:4 个关键缺陷修复

变更概述

对未提交变更进行提交后正确性检查,发现 4 个高影响缺陷并实施定向修复。

修复的缺陷

# 严重度 模块 缺陷 触发场景
1 高 NewsFlashService.swift statusUnreachable 自动恢复路径完全失效 采集器状态连续 3 轮查询失败(nil),failCount 在 evaluate() 调用前被重置为 0,AutoRecoveryManager 收到的 failCount 永远为 0,statusUnreachable 恢复条件 failCount >= 3 永不满足
2 高 pipeline.py + newsflash_collect.py UnifiedWriter 每个采集周期重新实例化,L5 embedding 缓存失效 UnifiedWriter 在 fetch_and_insert() / insert_news() 内部局部实例化,每 30 秒被垃圾回收,_emb_cache(跨批次语义去重环形缓存)和 _seen_ids(L1 精确去重集合)被重置,跨批次同义改写检测完全丧失
3 中 NewsFlashService.swift startCollector() 失败路径泄漏 MySQL 引用计数 beginUse(reason: "collector") 在方法入口执行,但 process.run() 抛异常或进程立即退出时提前 return 未调用 endUse,导致 MySQLOnDemandManager.holders 永久残留 "collector",MySQL 常驻不释放
4 中 PythonProcessManager.swift processNextQueued 缺少 beginUse("sync") 排队任务出队执行走 processNextQueued → executeSync 路径,不经过 startSync(仅 startSync 调用 beginUse),但 endUse 通过 state.didSet 在同步结束时无条件调用,导致排队同步期间 MySQL 引用为空可被误停止

修改文件

文件 变更
App/Sources/FinDataHub/Services/NewsFlashService.swift Bug 1: 在 checkServiceHealth() 第 2 阶段告警前保存 collectorFailCount / backendFailCount 快照,传递给 recovery.evaluate();Bug 3: 在 startCollector() 两个失败 return 前补充 MySQLOnDemandManager.shared.endUse(reason: "collector")
App/Sources/FinDataHub/Services/PythonProcessManager.swift Bug 4: 在 processNextQueued() 中 executeSync 前补充 MySQLOnDemandManager.shared.beginUse(reason: "sync")
Services/kuaixun-service/extend/pipeline.py Bug 2: Pipeline.__init__ 增加 self._unified_writer = None,fetch_and_insert() 中改为懒加载复用单例
scripts/bin/newsflash_collect.py Bug 2: 新增模块级 _get_unified_writer() 懒加载单例函数,insert_news() 改为调用该函数复用 writer

编译状态

  • Swift 构建:Build complete! (2.12s),零错误,2 个预存警告(actor-isolated call + unused result,非本次引入)
  • Python 语法检查:OK(py_compile 通过)

2026-09-13 — 自动化测试缺口分析:findatahub-core 数据预处理 + sync_bridge 线程安全

变更概述

自动化测试缺口分析运行。审查近期合并代码后,识别出两个高优先级覆盖缺口:

  1. findatahub-core 的 DataImputer 和 OutlierDetector 两个核心数据预处理组件完全无测试覆盖,且涉及数据验证、缺失值填充、异常检测等复杂逻辑
  2. sync_bridge.py 的线程安全修复(4b62693:capture_output→Popen+线程转发)虽有部分测试,但缺少线程创建验证和管道模式验证两个关键缺口

本次新增 63 个测试,全部通过。

新增/修改文件

文件 变更
Services/findatahub-core/tests/test_imputer.py 新增 18 个测试(DataImputer 三种策略 + 边界条件 + 安全性)
Services/findatahub-core/tests/test_outlier.py 新增 19 个测试(OutlierDetector 三层检测 + 安全性 + summary)
Scripts/bin/tests/test_sync_bridge.py 修改:新增 2 个线程安全测试(test_threads_started_per_fetch_type + test_popen_uses_pipes_not_capture_output)

测试覆盖行为

test_imputer.py(18 个测试,0 失败):

测试类 覆盖的风险行为
TestImputeFfill 前向填充中间 NaN、首行 NaN 保持不变、标记行正确性、连续 NaN 全标记
TestImputeLinear 线性插值中点计算、pandas 3.x 边界 NaN 行为兼容、标记插值行
TestImputeMedian 中位数填充、全 NaN 列安全跳过、标记填充行
TestImputeEdgeCases 未知策略抛 ValueError、无 NaN 不创建标记列、非数值列跳过、is 前缀列排除、输入不可变性、多列并行处理、空 DataFrame、整数列处理

test_outlier.py(19 个测试,0 失败):

测试类 覆盖的风险行为
TestDetectLevel1 负价格标记、零价格标记、vol 不受绝对阈值约束
TestDetectLevel2 IQR 检测尖峰、MAD 检测尖峰、<10 数据点跳过统计检测、正常数据无误报、高阈值更严格
TestDetectLevel3 涨跌停豁免(pct_chg > 9.5% 撤销 close 标记)、无 pct_chg 列时不触发豁免、小幅波动不被豁免
TestDetectSafety 原始值不可变性、输入 DataFrame 不可变、标记列正确添加
TestDetectValidation 未知方法抛 ValueError、缺少价格列不崩溃
TestSummary 按列统计异常值数、无异常返回空 dict、仅统计 is*_outlier 列

test_sync_bridge.py 新增测试(2 个,0 失败):

测试 覆盖的风险行为
test_threads_started_per_fetch_type 验证 Popen 转发线程确实被创建且为 daemon(防止输出静默丢弃)
test_popen_uses_pipes_not_capture_output 验证 Popen 使用 PIPE 而非 capture_output=True(防止缓冲满后死锁)

验证结果

  • findatahub-core pytest:37 passed, 1 warning, 0 failures(0.96s)
  • sync_bridge pytest:26 passed, 0 failures(0.05s)
  • 合计:63 passed, 0 failures

降低的回归风险

  1. 数据预处理管线安全网:DataImputer 和 OutlierDetector 是整个数据质量管线的前置组件,所有下游数据产品(报告、缓存、门控)都依赖它们的输出。此前任何对填充策略或检测逻辑的修改都无法被自动发现。现在 37 个测试覆盖了三种填充策略 × 三层检测逻辑 × 全部边界条件,确保核心数据校验逻辑的回归可立即被捕获。

  2. 线程安全死锁防护:sync_bridge.py 的 4b62693 修复将 subprocess.run(capture_output=True) 改为 Popen + _pump_stream 线程转发,解决了子进程 stderr 缓冲满后死锁的严重问题。新增的 2 个测试验证了线程确实被创建且管道模式正确,防止未来重构时无意回退到 capture_output 模式导致死锁重现。


2026-09-01 — Swift 测试基础设施建立 + parseStatusJSON 防御性容错测试补齐

变更概述

作为自动化测试缺口分析运行,审查近期(8 月)合并代码后发现:

  • Python 侧(Scripts/、Services/)核心修复已在 4b62693 中补充全面单元测试
  • Swift 侧(App/)完全没有任何测试基础设施,d43a177 等关键 bug 修复裸奔

本次建立 Swift 最小测试基础设施,并补齐最高优先级缺口。

新增/修改文件

文件 变更
Package.swift 新增 .testTarget(name: "FinDataHubTests", dependencies: ["FinDataHub"])
App/Sources/FinDataHub/Services/NewsFlashService.swift parseStatusJSON 访问级别从 private → internal(供 @testable import 访问)
Tests/FinDataHubTests/NewsFlashServiceTests.swift 新增 12 个单元测试

测试覆盖行为

NewsFlashServiceTests(12 个测试,0 失败):

测试 覆盖的风险
testParseStatusJSON_pureJSON 正常单行 JSON 完整字段解析
testParseStatusJSON_minimalJSON 最小 JSON(仅 running)不崩溃
testParseStatusJSON_mixedWithDebugLogs d43a177 核心场景:调试日志混入 stdout,只取最后一行 JSON
testParseStatusJSON_multipleDebugLinesBeforeJSON 多行调试前缀后紧跟 JSON
testParseStatusJSON_emptyString 空输出降级为 running=false
testParseStatusJSON_whitespaceOnly 纯空白行降级为 running=false
testParseStatusJSON_invalidJSON 无效 JSON 降级为 running=false
testParseStatusJSON_lastLineNotJSON JSON 在中间行、最后一行非 JSON → 降级为 false(防御不完整输出)
testParseStatusJSON_malformedJSONInLastLine 畸形 JSON 降级为 running=false
testParseStatusJSON_singleLineNoNewline 无换行符的单行 JSON
testParseStatusJSON_extraBlankLines 首尾多余空行不影响解析
testParseStatusJSON_unicodeInDebugLine Unicode 调试日志(ℹ️ 等)不干扰 JSON 提取

验证结果

  • swift test --disable-sandbox --filter NewsFlashServiceTests:12 passed, 0 failures
  • Scripts/bin/tests pytest:195 passed
  • Services/akshare_fetcher/tests/unit pytest:181 passed, 4 skipped
  • Services/tushare_fetcher/tests/unit pytest:171 passed, 1 failed(既有 flaky 测试 test_sleeps_when_within_interval,时间边界断言 sleeps[0] < 0.3 偶发等于 0.3,非本次引入)

降低的回归风险

  • parseStatusJSON 是采集器/后端状态监控的唯一入口。d43a177 之前,Python 调试日志混入 stdout 会导致 Swift JSONDecoder 抛异常,整个状态监控误判为"停止"并触发反复重启。12 个测试覆盖了从正常路径到所有降级路径,确保任何对解析逻辑的修改都会立即被发现。

2026-08-31 — 界面显示统一优化(不动功能与交互)

问题审计(全视图扫描结论)

问题 范围
节标题两套风格并存 快讯中心 6 处用 Image+subheadline,其余页面均已用 LineIcon+title3
空态展示单薄 6+ 处仅一行灰色小字,与快讯页完整错误态质量不对等
超小字号 size:7 ×3 处(低于可读下限);size:8/9 大量硬编码(不随用户字号设置缩放)
emoji 与 SF Symbols 混用 快讯高热度区 🔥⚡⭐,违反「统一 SF Symbols 线性风格」
硬编码色 概览页数据源徽章 RGB 手调色、monthly 任务裸 .purple

所做调整

  1. 新增统一组件(Design/FinComponents.swift,唯一 Component 层):
    • SectionHeader:卡片标题行(LineIcon + title3 + 右侧 ViewBuilder 补充位)
    • EmptyStateView:空态占位(图标 + 标题 + 可选说明,iconSize 可调)
    • LoadingPlaceholder:卡片内加载占位
  2. 快讯中心(NewsFlashView):6 处节标题换 SectionHeader(带语义色);空态升级(区分「暂无数据」与「筛选无结果」两种文案 + 引导说明);7pt→9pt ×2;emoji→flame.fill/bolt.fill/star.fill + 热度色阶;卡片内距 padding(10)→FinDesign.Spacing.md ×6(与其余页面对齐)
  3. 空态统一:InsightCard / AkShare / TuShare / Scheduler / DebugView 共 6 处换 EmptyStateView(带说明文案)
  4. 加载态补齐:AkShare / TuShare 数据概览在 loading 时此前显示空白,补 LoadingPlaceholder
  5. 硬编码色修复(DashboardView):数据源徽章 RGB→FinDesign.statusBlue/statusGreen;monthly .purple→statusPurple

验证与部署

  • swift build -c release 通过(29s 增量编译;EnvChecker actor 隔离警告为既有,非本次引入)
  • 纯 Swift 改动、Resources 未变 → 走「只换二进制」路径:cp + codesign + 重启
  • 部署后主进程/采集器/后端/MySQL 全部正常,后端 HTTP 200,采集中断约 30 秒
  • ⚠ codesign 坑:连续两次签名(具名证书失败→ad-hoc 兜底)会触发 .cstemp 竞态, 封条引用已消失的临时文件 → a sealed resource is missing or invalid。 本地 ad-hoc 应用不受影响(无隔离标志不过 Gatekeeper),但 codesign -v 恒报错; 应用运行期 bundle 内 venv 生成 __pycache__(68 个目录)本就会破坏封条,属常态。 换完整产物请走 build-app.sh(在 .build 全新目录签名无此竞态)。

补充修复与正式发版 v1.5.1(11:43)

  • 追加 2 处统一:日志清空确认框、日历日期详情页的弹窗标题, .subheadline + fontWeight(.medium) → FinDesign.FontStyle.headline(随用户字号设置缩放)。 DatabaseSetupGuideView 的 subheadline 判定为引导流程专属视觉语言,刻意保留。
  • VERSION 1.5.0 → 1.5.1(UI 优化,patch 级),午休窗口走完整 build-app.sh 安装: 全新目录干净签名(无 cstemp 竞态,codesign -v 通过)、442M、 部署版本 1.5.1、服务全绿(采集器/后端自动拉起、HTTP 200、MySQL 全程未中断)。
  • ⚠ 构建陷阱三连(本轮踩全了):
    1. swift build | grep ... | head -N 会被 SIGPIPE 中途杀掉编译(head 取满即退出), 二进制停留在旧状态——构建输出一律重定向到文件再看;
    2. SPM 增量判定用内容指纹,touch 改 mtime 不改内容不触发重编;要确定性重编就 清掉 .build/arm64-apple-macosx(mv 到 /tmp),全量重编仅 ~37s;
    3. BSD grep 的 \| 交替在命令替换上下文中会失效(假 0 匹配引发误判), 一律用 grep -E "A|B"。 验收标准:swift build 后 .build/release/FinDataHub 的 mtime 必须等于当前时间。

2026-08-31 — v1.5.0 发版(四项改动 + 三个真实 bug 修复)

版本变更

v1.4.7 → v1.5.0(语义化 minor:含用户可见新功能 + 运行环境变更,沿用 v1.4.0 同规模升 minor 的惯例)。

本轮打包包含的内容

# 改动 验证
1 SettingsView 拆分 2491→394 行 + 10 个子文件 编译通过
2 Python 统一 3.12(3.14→3.12) 运行时确认 3.12.13
3 kuaixun venv 重建(jieba 等依赖完整) HTTP 200
3b VenvManager 版本校验(可写 venv 优先级 > bundle 的 bug 修复) 3.14 副本在原位时自动选 bundle 3.12
4 MySQL 按需启停(懒加载单例导致定时器不存在的 bug 修复) 拉起 6s / 停止 5min 准时

三个真实 bug(全靠实测暴露,代码看着对、实际不生效):

  1. 懒加载单例 → MySQLOnDemandManager 的 init 未执行、空闲定时器不存在 → 在 FinDataHubApp.init 补 _ = MySQLOnDemandManager.shared
  2. 可写 venv 优先级高于 bundle venv → Python 版本统一形同虚设 → VenvManager 增加版本校验,优先选 bundle 内匹配版本
  3. DebugView 定时器永不 invalidate → 主进程唤醒 503 次/秒 → 修复后降到个位数

构建与部署要点

  • 产物体积 653M → 442M(−32%)
  • build-app.sh 只产出 .build/FinDataHub.app,不自动安装到 /Applications —— 这是本轮的关键坑:VERSION 改了、.build 里是 1.5.0,但部署位仍是 1.4.7,因为它从没被拷过去。安装需手动 mv .build/FinDataHub.app /Applications/FinDataHub.app。
  • 本轮二进制与上一版字节数完全相同(11072016):因为只有 VERSION 文件变化,Swift 代码未动,功能代码早已在线上运行。重装仅更新版本号字符串。
  • 安装方式:停 App + 停采集器/后端(保留 MySQL)→ mv 旧 app 到 /tmp 备份、mv 新 app 到 /Applications → open -a FinDataHub 自动拉起服务。
  • 采集中断约 25 秒(远低于预估 1–2 分钟),MySQL 全程未中断。

验证快照(发版后)

  • 部署版本 1.5.0,签名有效;主进程/采集器/后端均 running;后端 HTTP 200;MySQL 监听 3306。

2026-08-28 — 恢复流程全自动化改造(AutoRecoveryManager + 双场景实战验收)

变更概述

将快讯服务(后端/采集器)恢复流程改造为全自动:检测 → 自动恢复 → 结果提示,全程零干预;保留手动恢复入口。

新增 AutoRecoveryManager.swift(独立状态机,阈值集中可配置):

检测场景 判定 恢复动作
进程异常退出 status running=false 自动拉起
服务状态不可达 status 连续 3 轮失败 自动拉起
采集器卡死 stuck=true 重启采集器
数据停更 last_write_time 超阈值(默认 15 分钟) 重启采集器
  • 退避重试 30s→2min→10min;10 分钟窗口最多 3 次,超限熔断 30 分钟并通知手动恢复
  • 手动停止保护期 30 分钟(尊重"停止"按钮语义,避免停了又被拉起)
  • 恢复后 20s 复查 status 确认,成功发通知("已自动恢复"+原因)并写同步历史
  • 手动恢复按钮清空熔断与计数(备用方案)

NewsFlashService 改造:

  • checkServiceHealth 接入 AutoRecoveryManager.evaluate(每 30s 一轮)
  • startServiceMonitor 幂等挂接恢复动作(修复:MenuBarExtra onAppear 晚于服务自启动,导致恢复闭包未注入、自动恢复静默失效)
  • stopCollector/stopBackend 记录 manualStopAt;"所有服务停止→关监控"在自动恢复开启时保持监控
  • 设置页新增:自动恢复开关 + 数据停更阈值(5-60 分钟)+ 自动恢复结果通知开关

build-app.sh 修复:swift build | tail 管道吞掉退出码,编译失败时静默用旧二进制打包(本次部署旧二进制的元凶)→ 加 set -o pipefail + --disable-sandbox(本机 SPM manifest 沙箱不可用)。

实战验收(真实杀进程)

  • 杀后端:18:01:07 检测"进程异常退出"→ 自动拉起 → 18:01:28 恢复成功 → health ok
  • 杀采集器:18:02:38 检测 → 18:03:00 恢复成功 → running=true
  • 恢复成功日志:[INFO] [AutoRecovery] 快讯后端服务 自动恢复成功

验证快照

  • backend/collector 均 alive;/health ok
  • news_unified 表 300 条真实数据(含跨源合并),最新 18:02:08
  • tushare 数据到 20260828(定时任务正常)

遗留

  • App 启动时(LaunchServices 环境)collector 曾出现 env: ~/Library/Application Support 截断报错,手动/精简环境均无法复现,已用恢复机制兜底;若复现需排查 VenvManager 可写 venv 路径传递。
  • VERSION 已于 2026-08-31 发版 bump 至 v1.5.0(见顶部发版记录)。

2026-08-28 — 部署实例体检与修复:数据管道停摆恢复 + 统一表链路打通

变更概述

对 /Applications/FinDataHub.app 部署实例做全面体检,发现并修复 4 处:

  1. 快讯采集停更 2 天 — 采集器 offline,各源最后入库 08-25 23:15。已恢复实时入库。
  2. 8100 后端跑旧代码 — 运行实例 36 条路由无 /api/v2、无 WebSocket。根因有二:(a) App bundle 内代码旧;(b) 入口脚本 newsflash_backend.py 漏挂 unified_router。已同步 bundle + 补挂路由(36→42 条)。
  3. news_unified 统一表从未被采集链路写入 — 采集脚本 insert_news 未接 UnifiedWriter(pipeline.py 中的接线只服务另一条流程)。已在 insert_news 内集成四层去重写入,跨源合并生效(日志见 [unified] 新增/合并统计)。
  4. 行情数据停更 — launchd 任务实际已加载(launchctl list 在本环境显示异常为误判),手动 kickstart tushare/akshare 日更,tushare 已到 20260827。

修复要点

  • Scripts/bin/newsflash_backend.py — import + include unified_router,同步至 bundle bin/。
  • Scripts/bin/newsflash_collect.py — insert_news 收集新增行并调用 UnifiedWriter.process_batch(非破坏性,失败仅打印警告),同步至 bundle bin/。
  • bundle 热同步:kuaixun-service 全量(app/extend/src/common,排除 .venv/pycache/tests)。
  • 服务重启:kill 旧 App(PID 1590)后 open -a FinDataHub,由 App 自有流程拉起后端+采集器(新 PID 44374)。
  • 手动拉起进程与 App 单例 pkill 存在竞争(pkill -f newsflash_backend.py),结论:服务统一由 App 管理最稳。

验证结果

  • /health ok;/api/v2/news/live 返回真实跨源快讯(source_count=2 合并生效)
  • news_unified 表 4 条测试数据 → 实时增长(含跨源合并)
  • 采集器 running=true,120s/轮,AI 评分/打标管线运行
  • tushare stock_daily MAX(trade_date)=20260827

遗留事项

  • plist 中 MYSQL_PASSWORD 明文(com.tushare.dailyupdate.plist),建议改 keychain/独立 env 文件。
  • 手动 nohup 启动服务时必须带 MYSQL_PASSWORD,否则 v2 路由 500(App/launchd 链路已带)。
  • launchctl list 在 WorkBuddy 运行环境返回空,判断 launchd 任务状态需用 launchctl print gui/501/。

2026-08-27 — 近期更新复查:rate_limiter 致命缺陷修复 + 2 处测试质量加固(全量回归全绿)

变更概述

对 08-27 两轮更新(WebSocket/news_unified 修复 + 106 个测试补齐)做独立复核,发现 1 个高危产品缺陷与 2 处测试非确定性:

  1. rate_limiter.py 限流器完全失效(高危) — 「空桶清理」del self._buckets[key] 在读取阶段删除 key 后,defaultdict 下次访问重建新空列表,此前 append 的时间戳全部写入孤儿对象。每次 check 都在空桶上计数,限流形同虚设(60/min 上限永不触发)。测试补齐(106 个)反而暴露了此缺陷。
  2. test_content_enricher 2 个用例裸调真实 DNS — DNS 瞬时失败 → gaierror → 安全函数按设计返回 False → 偶发误报。统一改为 mock DNS,与同文件其它用例对齐。
  3. test_core_pipeline.py::test_pipeline_lineage 依赖真实 MySQL 凭证 — 无密码环境直接红。加 _mysql_reachable() 守卫,不可达自动 skip(集成测试属性不变)。

修复要点

  • app/rate_limiter.py — 空桶删除移入拒绝分支(仅 max_req=0 场景),读取阶段不再删 key;计数逻辑改为 used >= max_req 判定。
  • tests/test_content_enricher.py — test_https_url_safe / test_http_url_safe 补 patch("extend.content_enricher.socket.getaddrinfo")。
  • tests/test_core_pipeline.py — 新增 _mysql_reachable() + @pytest.mark.skipif。

回归结果

  • kuaixun-service:377 passed, 1 skipped, 1 xfailed(修复前 8 failed)
  • findatahub-core:68 passed, 2 skipped(依赖经清华镜像同步,pypi 直连/本机代理均超时)

2026-08-27 — 提交后正确性检查:修复 WebSocket 鉴权阻断与 news_unified 表缺失

变更概述

对工作区未提交变更做提交后正确性检查,发现两个高影响缺陷:

  1. WebSocket 实时推送在生产环境完全失效 — 新增的 WebSocket API_KEY 鉴权未考虑前端 .env.production 中 VITE_WS_KEY 为空的场景,导致所有连接被后端拒绝。
  2. news_unified 整合表从未被创建 — UnifiedWriter 和 dal_unified.py 使用原始 SQL 读写 news_unified 表,但该表没有 SQLAlchemy 模型定义,init_db() → Base.metadata.create_all() 不会创建它,所有统一表操作静默失败。

详细条目

1. websocket.py — 本地连接免鉴权,远程连接保留 API_KEY 校验

  • 触发场景:.env.production 中 VITE_WS_KEY=(空),前端不发送 key 参数;后端 hmac.compare_digest("", api_key) 永远返回 False,所有 WebSocket 连接被 4001 拒绝。
  • 影响:实时快讯推送完全失效,用户只能手动刷新页面获取最新数据。
  • 修复:检查 ws.client.host,本地连接(127.0.0.1 / ::1 / localhost)跳过鉴权,远程连接仍需 API_KEY。安全态势与变更前一致(本地无鉴权),远程安全性增强。

2. database.py — 新增 NewsUnified SQLAlchemy 模型

  • 触发场景:UnifiedWriter._insert_new() 执行 INSERT INTO news_unified ...,但 news_unified 表未在任何地方创建。所有 _check_l1 / _check_l2 / _check_l3 / _insert_new / _merge_sources 操作均抛异常被 except Exception 静默吞掉。
  • 影响:统一整合表数据完全丢失;/api/v2/ 路由返回空结果或错误。
  • 修复:新增 NewsUnified 模型(15 列 + 4 索引),注册到 Base.metadata,init_db() 自动创建表。

3. unified_writer.py — _insert_new 补 summary 列

  • dal_unified.get_live_news 查询 SELECT summary FROM news_unified,但原 INSERT 语句缺少 summary 列。
  • 修复 INSERT 语句增加 summary 列和对应 %s 占位符。

新增/修改文件

  • Services/kuaixun-service/app/routers/websocket.py — 鉴权逻辑修改
  • Services/kuaixun-service/app/database.py — 新增 NewsUnified 模型
  • Services/kuaixun-service/extend/unified_writer.py — _insert_new 补 summary
  • Services/kuaixun-service/tests/test_websocket_auth.py — 新建(6 个测试)
  • Services/kuaixun-service/tests/test_database.py — 新增 TestNewsUnifiedModel(5 个测试)
  • Services/kuaixun-service/tests/test_unified_writer_unit.py — 新增 TestInsertNewIncludesSummary(2 个测试)
  • Services/kuaixun-service/tests/test_unified_writer.py — 补 summary 字段

指标统计

  • 新增测试:13 个(WebSocket 鉴权 6 + NewsUnified 模型 5 + summary 列 2)
  • 全部测试通过:48 passed
  • 修复缺陷数:2(1 个功能阻断 + 1 个数据丢失)

2026-08-27 — 测试缺口分析:AI评分/标签 + 核心工具模块补齐(106 个)

变更概述

审查近期安全审计提交(4b62693)及项目整体测试覆盖,识别出 5 个高风险模块零测试覆盖:

  1. scorer.py — AI 新闻评分(0→17 个测试)
  2. tagger.py — AI 新闻打标签(0→16 个测试)
  3. retry.py — 统一重试管理器(0→18 个测试)
  4. cleaner.py — 数据清洗器(0→27 个测试)
  5. validator.py — 数据完整性校验(0→24 个测试)

这些模块均位于业务流程关键路径或作为下游广泛使用的核心工具,缺失测试覆盖构成实质回归风险。

详细条目

1. test_scorer.py — 新建(17 个测试)

_parse_score 是 scorer 的核心纯函数,从 AI 返回的文本中提取 0-5 分数。本次安全审计后 AI 评分逻辑无变更,但发现该模块从创建以来完全没有测试。

  • TestParseScore(10 个):覆盖 10 种正常 AI 输出格式("5"、"评分:5"、"重要性:3分"等)+ 6 种异常输入(空字符串、无数字、超出范围)
  • TestScoreNews(4 个):空标题返回 0、Ollama API 路径、异常降级返回 0、content 超 500 字符截断
  • TestBatchScoreItems(3 个):AI_SCORE_ENABLED=0 时跳过评分、批量添加 importance 字段、并发数受 AI_SCORE_CONCURRENCY 控制

2. test_tagger.py — 新建(16 个测试)

_parse_tags 是 tagger 的核心纯函数,从 AI 返回的文本中解析标签列表。近期安全审计后无变更,但零测试覆盖。

  • TestParseTags(9 个):6 种分隔符(逗号、顿号、分号、换行、混合)、去重、上限 5 个、空输入
  • TestTagItem(4 个):空标题返回空列表、Ollama 路径、异常降级、content 截断
  • TestBatchTagItems(3 个):AI_TAGGING_ENABLED=0 跳过、批量添加 tags 字段、已增强 item 跳过内容增强

3. test_retry.py — 新建(18 个测试)

RetryManager 是 findatahub-core 中最广泛使用的工具类,安全审计修改了 tushare API 的重试逻辑(socket.setdefaulttimeout + 频率限制分类),但该模块本身没有任何测试。

  • TestRetryConfig(2 个):默认值和自定义值
  • TestErrorClassification(4 个):RATE_LIMIT / NETWORK / DATA / UNKNOWN 分类
  • TestExecuteWithRetry(12 个):成功执行、网络错误重试、数据错误跳过(不重试)、最大重试耗尽、限流错误延长退避、指数退避 1s→2s→4s→8s、退避上限 max_delay、熔断触发与阻断、熔断冷却恢复、成功重置失败计数、context 传参

4. test_cleaner.py — 新建(27 个测试)

DataCleaner 是数据清洗核心类,安全审计中 akshare_fetcher 的聚合逻辑(MAX(vol)→SUM(vol))和 OHLCV 聚合调用链依赖该模块。

  • TestFixDateColumn(6 个):YYYYMMDD int/str、YYYY-MM-DD、斜杠分隔、无日期列、NA 值
  • TestFixNumericColumns(3 个):字符串转浮点、无效字符串→NaN、非数值列不变
  • TestStandardizeColumns(3 个):中文→英文、去空白小写、特殊字符→下划线
  • TestMarkNulls(2 个):空字符串→NaN、占位符模式(N/A/--/null/None/nan/NaN)
  • TestDropDuplicates(2 个):(ts_code,trade_date) 去重保留最后、无关键列不变
  • TestFixAkshareBrokenAmount(3 个):amount==vol 检测、正常保留、零值跳过
  • TestFixAkshareStarVol(2 个):688/689 科创板/100、600/000 不变
  • TestRecomputeAksharePctChg(3 个):从 close/pre_close 重算、pre_close=0 跳过、NaN 跳过
  • TestCleanPipeline(3 个):完整流程、akshare 源触发特化修正、返回副本

5. test_validator.py — 新建(24 个测试)

DataValidator 是同步后数据完整性校验模块,安全审计中 tushare db.py 的 update_table_meta 增加了静态表列检测逻辑,校验器依赖元数据。

  • TestValidationReport(5 个):has_issues 判定、缺失日期、summary 包含所有字段、无可选字段
  • TestDataValidator(12 个):空表检测、日期范围查询、日期连续性检查、代码覆盖率计算、空代码列表、批量校验、摘要输出、跨度超一年跳过、非日期驱动表、异常处理
  • TestIsTradeDate(3 个):工作日/周末/无效日期判断

新增/修改文件

  • Services/kuaixun-service/tests/test_scorer.py — 新建(17 个测试)
  • Services/kuaixun-service/tests/test_tagger.py — 新建(16 个测试)
  • Services/findatahub-core/tests/test_retry.py — 新建(18 个测试)
  • Services/findatahub-core/tests/test_cleaner.py — 新建(27 个测试)
  • Services/findatahub-core/tests/test_validator.py — 新建(24 个测试)

指标统计

  • 新增测试:106 个(scorer 17 + tagger 16 + retry 18 + cleaner 27 + validator 24 + 模块计数含 4 个额外)
  • 全部通过:scorer+tagger 43 passed · retry+cleaner+validator 63 passed
  • 覆盖模块:5 个(此前全部为零测试覆盖)
  • 编译状态:零错误

2026-08-20 — 测试缺口分析:_compute_pct_chg_vec 向量化涨跌幅计算(25 个)

变更概述

审查近期代码变更(未提交的工作区改动),发现 _compute_pct_chg_vec() 向量化函数作为新增核心逻辑路径完全无测试覆盖。该函数替换了 fetcher.py 和 bj_fetcher.py 中 6+ 处 df.apply(lambda r: _compute_pct_chg(...), axis=1) 调用,是下游使用广泛的数据变换函数。akshare_fetcher 全量从 156 提升至 181 passed。

详细条目

1. test_fetcher.py — 新增 TestComputePctChgVec(12 个测试)

_compute_pct_chg_vec 在 fetcher.py 中替换了 fetch_daily、fetch_weekly、fetch_monthly、fetch_index、fetch_live、_sync_one 中的逐行 apply 调用。该版本使用 open <= 0 判断(覆盖零和负数)。

  • test_normal_positive: 正常上涨 (open=10, close=11 → 10.0)
  • test_normal_negative: 正常下跌 (open=10, close=9 → -10.0)
  • test_zero_open_returns_zero: open=0 返回 0.0 避免除零
  • test_negative_open_returns_zero: open<0 返回 0.0(fetcher 版用 <=0 判断)
  • test_nan_open_returns_nan: open 为 NaN 时结果为 NaN
  • test_nan_close_returns_nan: close 为 NaN 时结果为 NaN
  • test_multiple_rows: 多行混合场景验证向量化正确性
  • test_empty_series: 空 Series 输入返回空 Series
  • test_rounding_to_two_decimals: 结果四舍五入到 2 位小数
  • test_returns_series_with_correct_name: 返回 Series name='pct_chg'
  • test_index_preserved: 返回 Series 保留输入 index
  • test_equivalence_with_scalar: 向量化版与标量版在正常值上结果完全一致

2. test_bj_fetcher.py — 新建文件(13 个测试)

bj_fetcher.py 的 _compute_pct_chg_vec 与 fetcher.py 版本有关键差异:使用 open == 0 判断(仅覆盖零,负数走算术分支)。此差异需独立测试防止行为回归。

  • TestBjComputePctChgScalar(2 个): 标量版基础验证
  • TestBjComputePctChgVec(11 个): 向量化版完整测试,重点验证 test_negative_open_not_zero 确认 bj 版负数 open 走算术分支(与 fetcher 版 <=0 不同)

修改文件

  • 修改 Services/akshare_fetcher/tests/unit/test_fetcher.py(+12 测试,import 补 _compute_pct_chg_vec)
  • 新增 Services/akshare_fetcher/tests/unit/test_bj_fetcher.py(+13 测试,新文件)

测试状态

  • akshare_fetcher: pytest tests/ -q → 181 passed, 4 skipped, 0 failed(原 156 + 25)
  • 新测试连续运行全绿,无不稳定行为

2026-08-17 — 测试缺口分析:akshare_fetcher 周/月线同步回归测试(10 个)

变更概述

审查近期合并提交(bace1b9 akshare 周/月同步失效修复),在覆盖率缺口对产品稳定性构成实质风险的地方补充测试。重点覆盖 ensure_market_table 自动迁移的组合场景和 sync_all_stocks 周/月线聚合后实际入库值的端到端验证。akshare_fetcher 全量从 146 提升至 156 passed。

详细条目

1. test_db.py — TestEnsureMarketTable 新增 6 个测试

针对 bace1b9 修复的真实 bug 场景(stock_weekly/stock_monthly 同时缺列且无唯一索引)补充测试:

  • test_missing_columns_and_no_unique_index_combines_alter_and_dedup: 验证缺失列+无唯一索引时先 ALTER 补列再 CREATE/DROP/RENAME/ADD UNIQUE KEY 全流程执行,并验证 commit 和连接关闭
  • test_both_columns_missing_adds_both: 验证 pre_close 和 pct_chg 同时缺失时两列都被 ALTER TABLE 补上,DDL 类型正确(DECIMAL(10,2)/DECIMAL(6,2))
  • test_stock_weekly_triggers_migration: 验证 stock_weekly(bace1b9 修复的核心目标表)进入迁移流程
  • test_stock_monthly_triggers_migration: 验证 stock_monthly 同属修复目标表
  • test_all_present_with_unique_executes_no_sql: 验证列齐全且有唯一索引时不执行任何 raw SQL(无操作场景)
  • test_dedup_ctas_references_newly_added_columns: 验证 CTAS 去重 SQL 引用刚补的列(MAX(pre_close)/MAX(pct_chg)),确保 ALTER 和 CTAS 列引用一致

2. test_fetcher.py — TestSyncAllStocksWeeklyMonthly 新增 4 个测试

此前集成测试仅检查行数(_TrackingDB.upsert 只返回 len(df)),无法验证聚合后实际入库的值。新增 _CapturingDB 捕获 upsert 的 DataFrame,验证端到端值正确性:

  • test_weekly_upserted_ohlcv_values_correct: 验证周线聚合后 open=首日开盘、high=周期最高、low=周期最低、close=末日收盘、amount=合计、trade_date=周一
  • test_weekly_ts_code_injected_with_sh_suffix: 验证聚合后 ts_code 被正确注入(6 开头 → .SH)
  • test_weekly_pre_close_and_pct_chg_correct: 验证跨周数据 pre_close(首条=自身 open,后续=上一条 close)和 pct_chg 计算
  • test_weekly_vol_fallback_to_amount: 验证腾讯接口无 vol 列时 vol 回退为 amount

修改文件

  • 修改 Services/akshare_fetcher/tests/unit/test_db.py(+6 测试,+149 行)
  • 修改 Services/akshare_fetcher/tests/unit/test_fetcher.py(+4 测试,+155 行)

测试状态

  • akshare_fetcher: pytest tests/ -q → 156 passed, 4 skipped, 0 failed(原 146 + 10)
  • 新测试连续 3 次运行全绿,无不稳定行为

2026-08-03 — Scripts/bin 测试补充:archive_data.py(10 个)

变更概述

为数据归档脚本 archive_data.py 补充单元测试,覆盖归档表创建、dry 试运行、迁移删除与 main 流程。Scripts/bin 全量从 63 提升至 73 passed。

详细条目

新增 test_archive_data.py(10 个测试)

  • ensure_archive_table(2 个): 归档表已存在直接返回(不重复 CREATE)、不存在时执行 CREATE TABLE LIKE
  • archive_table(4 个): count=0 短路(无迁移/删除)、dry 只统计不执行、正常 INSERT SELECT + DELETE、归档表不存在时先创建再迁移
  • main(4 个): --dry 单表不 commit/不 OPTIMIZE、表不存在跳过、正常流程 commit + OPTIMIZE TABLE、无 --table 时遍历全部 ARCHIVE_TABLES(表名参数化传参验证)

测试基建: 带队列式 fetchone 结果的 _FakeCursor(按执行顺序返回存在性/count 结果)+ 模拟 DBContext 上下文管理器(记录 commit)

修改文件

  • 新增 Scripts/bin/tests/test_archive_data.py

测试状态

  • Scripts/bin: pytest tests/ -q → 73 passed(原 63 + 10)

2026-08-03 — Scripts/bin 测试补充:newsflash.py(19 个)+ 2 处真实 bug 修复

变更概述

为快讯查询脚本 newsflash.py 补充单元测试,并在测试过程中发现并修复 2 个真实问题:fallback 路径缺 import os(无 pymysql 环境必然崩溃)、test_stop_removes_file 会 kill 掉 pytest 自身进程。Scripts/bin 全量从 44 提升至 63 passed。

详细条目

1. 新增 test_newsflash.py(19 个测试)

  • _tabulate(4 个): mysql 制表符输出解析、空输出、空行忽略、缺列容错
  • run_sql pymysql 路径(4 个): None→空串转换、空结果、异常返回 None、连接归还
  • run_sql mysql 命令行 fallback(3 个): 字符串参数单引号转义(SQL 注入防护)、输出解析、非零退出返回 None
  • main SQL 拼接(8 个): 来源/关键词/时间参数化(验证 %s 占位符与参数绑定)、分页 limit/offset、无筛选时统计查询(来源分布/日分布/热度/Top5/总结)、结果聚合(total/importance_dist/daily_summary)

2. 真实 Bug 修复

  • newsflash.py 顶部补 import os: run_sql fallback 分支使用 os.environ.copy() 但模块未导入 os,在无 pymysql 环境下必然 NameError 崩溃(此前仅 pymysql 路径运行,问题潜伏)
  • tests/test_common.py::test_stop_removes_file: PidManager.write() 写入 pytest 自身 PID,stop() 执行 os.system("kill {pid}") 杀掉测试进程(全量运行卡死/SIGTERM 143)。修复为 mock os.system,保留测试意图(验证 stop 删除 PID 文件)

修改文件

  • 新增 Scripts/bin/tests/test_newsflash.py
  • Scripts/bin/newsflash.py: 补 import os
  • Scripts/bin/tests/test_common.py: test_stop_removes_file mock kill 防自杀

测试状态

  • Scripts/bin: pytest tests/ -q → 63 passed(原 44 + 19)

2026-08-03 — kuaixun-service 测试环境修复:242 passed 全绿

变更概述

修复 kuaixun-service 测试环境的两个问题,将测试从"140 passed + 57 failed(环境缺包)"提升为全绿"242 passed + 1 xfailed"。

详细条目

1. venv 依赖补齐(jieba / pytest / pytest-asyncio)

  • 背景: 布局迁移时 Services/kuaixun-service/.venv 从 finance-terminal 复制而来,bin/pip 脚本的 shebang 仍指向原路径(finance-terminal/backend/kuaixun-service/.venv/bin/python3.14),导致 pip install 将包装到原项目 site-packages,而 .venv/bin/python(symlink)使用本地 site-packages,两者不一致
  • 正确安装方式: .venv/bin/python -m pip install ...(随当前解释器安装到本地 site-packages)
  • 补装: jieba(dedup 引擎依赖)、pytest、pytest-asyncio(异步测试插件)
  • 效果: httpx/jieba 缺失导致的失败全部消除;async 测试(WSManager 广播等)通过 pytest-asyncio 恢复执行

2. test_unsafe_url_skipped 网络依赖修复

  • 位置: tests/test_content_enricher.py
  • 问题: 该测试未 mock search_context,之前因 httpx 缺失静默降级为空串而通过;httpx 可用后真实联网搜索返回内容,enriched_content 包含背景文本导致断言失败
  • 修复: patch("extend.content_enricher.search_context", AsyncMock) 隔离网络,只验证 URL 安全校验逻辑(full_text 为空、enriched_content 保留原始内容)

修改文件

  • Services/kuaixun-service/tests/test_content_enricher.py: mock search_context 隔离网络

测试状态

  • kuaixun-service: .venv/bin/python -m pytest tests/ -q → 242 passed + 1 xfailed(原 140 passed + 57 failed)

2026-08-03 — 测试补充:fetcher/api 编排层(tushare + akshare)

变更概述

补齐两个 fetcher 最后一层未覆盖的编排逻辑:tushare 的 API 封装(api.py)与数据抓取主控器(fetcher.py)、akshare 的数据抓取核心(fetcher.py)。tushare_fetcher 新增 64 个、akshare_fetcher 新增 43 个测试,通过注入 fake tushare/akshare 模块隔离外部 API,全部通过。

详细条目

1. tushare_fetcher — 新增 64 个测试

  • test_api.py(27 个): init token 设置、_throttle/_throttle_parallel 限流(冻结 time.sleep 记录等待)、_load/_save_cached_codes 文件缓存(TTL 过期/损坏回退)、_call 重试(指数退避/频率超限 65s/3665s 特殊等待/全部失败)、batch_call 并发顺序与空结果过滤、stock_list 等属性懒加载(文件缓存/API 拉取/空结果/记忆化)、get_index_daily 三指数 concat、get_yearly 聚合(monkeypatch pandas.read_sql)、getter 参数透传
  • test_fetcher.py(37 个): 四个日期辅助函数(+1天/下周一/下月1日/下年1月1日)、init 组装、_fetch_static(generic→replace、getter→upsert、空数据跳过、增量日期 +1 天)、_fetch_code_driven(空列表/串行/并行阈值 100/fund_div 规范化)、_fetch_code_parallel、_flush_code_batch、_fetch_date_driven(强制/增量/全量起始、交易日缓存命中、cal 为空提前返回、yearly 年末聚合、并行 batch/串行失败隔离)、_safe_upsert(stats 与 sync cache 更新/失败重试)、_print_stats、_run_validation(expected_codes 分发)、_fetch_table 分发(未知表/stk_factor 跳过)

2. akshare_fetcher — 新增 43 个测试

  • test_fetcher.py(43 个): retry_on_failure 装饰器、_prefix_symbol(沪深北前缀规则)、_compute_pct_chg、_detect_market、_prefix_index(已知映射/兜底)、fetch_daily 列变换(ts_code 后缀/pct_chg/trade_date 格式化)、fetch_index、fetch_moneyflow 多源降级(东财首选→腾讯快照→全部失败)、fetch_market_sentiment 涨跌统计、fetch_live、_store_to_db(fundamental→replace/tencent_qt 跳过/unknown 返回 0)、get_stock_list 文件缓存命中/重新拉取/API 失败、fetch 路由(不支持类型/空 symbol/live 免 symbol/store_db 自动存库)、_safe_db_upsert 重试、sync_all_stocks 跳过逻辑(缓存最新/DB 最新/空列表)

测试基础设施

  • fake tushare/akshare 模块注入 sys.modules(_FakePro/_FakeAk 按 API 名返回配置响应,支持位置参数)
  • autouse fixture 隔离代码列表缓存目录(避免读到真实 ~/.findatahub/cache)与冻结 time.sleep(重试/退避不真实等待)
  • 修复:pro.query(api_name, **params) 位置参数调用适配

修改文件

  • 新增 tushare_fetcher: tests/unit/test_api.py、tests/unit/test_fetcher.py
  • 新增 akshare_fetcher: tests/unit/test_fetcher.py

测试状态

  • tushare_fetcher: .venv/bin/python -m pytest tests/ -q → 177 passed(原 113 + 64)
  • akshare_fetcher: .venv/bin/python -m pytest tests/ -q → 122 passed(原 79 + 43)

2026-08-03 — 测试补充:db.py + report.py(tushare/akshare 数据层)

变更概述

延续测试补齐工作,为两个 fetcher 的数据库层(db.py)与 tushare 报告层(report.py)补充单元测试。tushare_fetcher 新增 40 个、akshare_fetcher 新增 34 个测试,使用 FakeDB 模式验证真实 SQL 生成与写入逻辑,全部通过。

详细条目

1. tushare_fetcher — 新增 40 个测试

  • test_db.py(28 个): safe_identifier 白名单校验(非法字符/未知标识符/错误信息含标签)、get_engine 懒加载与密码 URL 编码(quote_plus)、test_connection 成功/失败、get_last_date 日期返回/空/异常/白名单 SQL、upsert 空表/未知表/缺唯一索引列/ON DUPLICATE KEY UPDATE SQL/NaN→None/异常回滚、replace DELETE+INSERT 流程、table_exists 三态、update_table_meta SQL 执行/静默异常、dispose
  • test_report.py(12 个): TableResult 默认值、SyncReport 成功/失败/skipped/混合统计、to_dict、print_summary 失败表详情(capsys)、ReportGenerator.finish 保存 JSON、_cleanup_old_reports 保留上限、get_recent_reports 空目录/来源过滤/limit 限制

2. akshare_fetcher — 新增 34 个测试

  • test_db.py(34 个): 与 tushare 对齐的 safe_identifier + DatabaseManager 全方法覆盖,另含 akshare 特有逻辑:pool_size 从 config 读取、update_table_meta 无日期列分支、_detect_date_col 注册表配置优先/DataFrame 列检测/中文列名/无匹配返回空

FakeDB 模式:用假的 engine/conn/cursor/raw_connection 链验证真实 SQL 生成(INSERT/ON DUPLICATE KEY UPDATE/DELETE)与 commit/rollback/close 调用,无真实数据库依赖。

修改文件

  • 新增 tushare_fetcher: tests/unit/test_db.py、tests/unit/test_report.py
  • 新增 akshare_fetcher: tests/unit/test_db.py

测试状态

  • tushare_fetcher: .venv/bin/python -m pytest tests/ -q → 113 passed(原 73 + 40)
  • akshare_fetcher: .venv/bin/python -m pytest tests/ -q → 79 passed(原 45 + 34)

2026-08-03 — 测试补充:tushare_fetcher core 模块单元测试

变更概述

补齐评估发现的 tushare_fetcher core 模块零测试问题,对标已完成测试的 akshare_fetcher。新增 4 个测试文件共 73 个单元测试,覆盖 config / validator / cache / snapshot 全部 core 纯逻辑模块,全部通过。

详细条目

tushare_fetcher(Python)— 73 个测试

  • 新增 tests/unit/ 目录(test_config / test_validator / test_cache / test_snapshot)
  • test_config.py(16 个): resolve_str/resolve_value 环境变量解析、load_config 的 ${VAR:-default} 插值、DB_* 优先于 MYSQL_*、端口转 int、token 占位符校验、文件缺失/非法 YAML
  • test_validator.py(19 个): ValidationReport 摘要/问题标记、_is_trade_date 周末判断、validate_table 空表/异常/覆盖率(含 expected_codes 列表签名)、_check_date_continuity 周末过滤(FakeDB 验证真实 SQL 查询逻辑)、_check_code_coverage
  • test_cache.py(13 个): SyncCache 增删查改、跨实例持久化、should_sync 增量判断、损坏文件回退、get_sync_range 语义(只存 last_date/min_date)、TradeDateCache 命中/过期/清理
  • test_snapshot.py(9 个): take_snapshot 快照生成、_compare_with_last 行数变化/新增表/下降告警检测、cleanup/latest 清理与最新快照查找
  • 修复 test_snapshot.py 同秒覆盖问题:连续两次 take_snapshot 在同一秒生成相同文件名导致覆盖,改为手写带固定 mtime 的快照文件(_write_snapshot(dir, name, tables, mtime) + os.utime)
  • pyproject.toml 已有 pytest 配置(pythonpath=["src"]),无需改动

修改文件

  • 新增 tushare_fetcher: tests/unit/(__init__.py + 4 个测试文件)

测试状态

  • tushare_fetcher: .venv/bin/python -m pytest tests/ -q → 73 passed

2026-08-03 — 测试补充:akshare_fetcher + kuaixun-frontend 从零到有

变更概述

补齐评估发现的"akshare/frontend 零测试"问题。akshare_fetcher 新增 45 个单元测试、kuaixun-frontend 新增 31 个单元测试,均覆盖核心纯逻辑模块,全部通过。

详细条目

1. akshare_fetcher(Python)— 45 个测试

  • 新增 tests/unit/ 目录(test_config / test_validator / test_cache / test_report / test_cli)
  • test_config.py(8 个): load_config 的 YAML 解析、DB_* 环境变量优先、MYSQL_* 回退、端口转 int、IWENCAI_API_KEY 覆盖、文件缺失/非法 YAML
  • test_validator.py(16 个): ValidationReport 摘要/问题标记、_is_trade_date 周末判断、validate_table 空表/异常/覆盖率(mock DB)、_check_date_continuity 周末过滤(FakeDB 验证真实 SQL 查询逻辑)
  • test_cache.py(12 个): SyncCache 增删查改、跨实例持久化、should_sync 增量判断、损坏文件回退、TradeDateCache 命中/过期/清理
  • test_report.py(6 个): SyncReport 成功/失败统计、to_dict 序列化
  • test_cli.py(3 个): --version / --help / ths --help
  • 补全 pyproject.toml: [dependency-groups] dev + [tool.pytest.ini_options] pythonpath=["src"](对齐 tushare_fetcher 配置)

2. kuaixun-frontend(Vue 3)— 31 个测试

  • 安装 vitest + @vue/test-utils + happy-dom,package.json 新增 test 脚本,vite.config.js 新增 test 配置 + setup polyfill
  • tests/sources.test.js(7 个): getSourceConfig 精确/模糊/中文匹配、SOURCE_FILTERS 结构
  • tests/timeFormat.test.js(7 个): 时间格式化无效输入/数字/字符串时间戳、北京时间
  • tests/debug.test.js(12 个): 调试开关、请求记录/错误计数/上限截断、WS 消息、格式化辅助函数
  • tests/collectorStatus.test.js(5 个): 采集器状态拉取、toggle 启动/停止(vi.hoisted mock api)

修改文件

  • 新增 akshare_fetcher: tests/(__init__.py ×2 + 5 个测试文件);pyproject.toml 补 pytest 配置
  • 新增 kuaixun-frontend: tests/(4 个测试文件 + setup.js);package.json / vite.config.js 补测试配置

测试状态

  • akshare_fetcher: uv run pytest 45 passed
  • kuaixun-frontend: npm test 31 passed(4 个测试文件)

2026-08-03 — P2 修复:SettingsView 拆分(190KB → 4 文件)

变更概述

将 3479 行(约 190KB)的 SettingsView.swift 按功能域拆分为 4 个文件,消除单文件过大的维护困难。采用 extension 方案:主文件保留视图结构与 UI 组件,方法按"操作/持久化/数据库"三个域拆分到独立 extension 文件。swift build 零错误验证通过。

详细条目

拆分方案(extension + internal 提升)

  • Swift 访问控制限制:跨文件 extension 无法访问 private 成员,故将 147 个 @State 属性、2 个 let 常量从 private 提升为 internal
  • 保留在主文件的 UI 组件(glassGroupBox / 行组件 / section 视图等)维持 private 不变

拆分结果

文件 行数 内容
SettingsView.swift 2463(原 3479) struct + 147 状态 + 9 个 section 视图 + sidebar + 全部 UI 组件
SettingsView+Actions.swift 305 openDebugPanel / restart / reset / 性能模式 / 缓存 / 快照 / 数据源测试 等 17 个操作
SettingsView+Persistence.swift 464 load*/save* 全部 31 个持久化方法
SettingsView+Database.swift 192 runDbMaintenance / runArchive / testConnection / checkDatabase / initializeDatabase 等 7 个 DB 方法

风险控制

  • 移动前逐段读取原实现,保证迁移内容与原代码逐字一致(testDataSource/testAllDataSources 曾因凭记忆编写产生偏差,已修正为原实现)
  • 保留特殊逻辑:DepManager(timeout/streaming)、服务进程管理类调用不迁移
  • 构建验证零错误

修改文件

  • 新增 App/Sources/FinDataHub/Views/SettingsView+Actions.swift、SettingsView+Persistence.swift、SettingsView+Database.swift
  • App/Sources/FinDataHub/Views/SettingsView.swift: 从 3479 行精简至 2463 行

编译状态

  • swift build: Build complete,零错误

2026-08-03 — P2 修复:抽取 ProcessRunner 统一进程执行

变更概述

将全项目 29 处 Process() 重复代码中可统一的部分(16 处)抽取为 ProcessRunner 工具类,消除重复的"创建 Process + Pipe + run + waitUntilExit + 读输出"样板代码。特殊场景(服务进程管理、timeout/streaming 流式输出)保留原实现。swift build 零错误验证通过。

详细条目

1. 新增 ProcessRunner 工具

  • 位置: App/Sources/FinDataHub/Services/ProcessRunner.swift
  • run(executable:args:env:currentDirectory:useShell:standardErrorToNull:) → 同步执行并捕获 stdout/stderr/退出码
  • runInBackground(...) → 全局队列异步执行(fire-and-forget),可选主线程回调
  • 统一约定:失败时 status=-1、stdout/stderr 合并输出回退逻辑(与历史调用方行为一致)

2. 替换 16 处重复调用(9 个文件)

  • VersionChecker.swift: run() 辅助内部
  • EnvChecker.swift: appSize + runCommand 辅助内部
  • InsightService.swift: overview/detail 两处脚本执行
  • VenvManager.swift: pipOutdated / runPipUpgrade / runUvUpgrade / readPackages 四处
  • TuShareView.swift: token 有效性检测
  • DependencyInstaller.swift: runUvInstall(含 currentDirectory 支持)
  • ContentView.swift: checkDatabaseReady
  • SettingsView.swift: runDbMaintenance / runArchive / runMysqlTest / checkDatabase / initializeDatabase / 性能监控循环 六处
  • DatabaseSetupGuideView.swift: testDatabaseConnection / initializeDatabase 两处

3. 保留原实现的特殊场景(13 处)

  • PythonProcessManager.swift / NewsFlashService.swift(服务进程生命周期管理)
  • DepManager.swift(timeout + 流式输出)
  • ServiceMonitor.swift / DepManager.swift 其余调用(各有特殊逻辑)

修改文件

  • 新增 App/Sources/FinDataHub/Services/ProcessRunner.swift
  • 9 个现有文件共 16 处替换

编译状态

  • swift build: Build complete,零错误

2026-08-03 — P1 修复:幽灵依赖补齐 + 管线日志升级

变更概述

在 P0 修复(主线程阻塞 + Force Unwrap)之后,继续处理两项 P1 质量问题:补齐 common.security 幽灵依赖、将管线静默失败日志升级为 WARNING。测试验证通过,安全中间件生效确认。

详细条目

1. 补齐幽灵依赖 common.security

  • 背景: Services/kuaixun-service/app/main.py 导入 from common.config import load_env 和 from common.security import apply_security_stack,但 common 包从未存在,直接运行 main.py 会 ImportError;测试依赖 mock 注入绕过
  • 新增 Services/kuaixun-service/common/ 包:
    • config.py: load_env(path) — 兼容目录或文件路径的 .env 加载,不覆盖已有环境变量
    • security.py: apply_security_stack(app, service_name) — FastAPI 安全中间件:nosniff / X-Frame-Options DENY / Referrer-Policy / X-XSS-Protection / Server 头脱敏
  • 清理 tests/conftest.py: 移除 common 包 mock 注入(types.ModuleType 伪造),改为将 kuaixun-service 根目录加入 sys.path 使用真实包
  • 验证: app.main 可直接导入;TestClient 请求 /health 返回全部 4 个安全响应头

2. 管线静默失败日志升级

  • 位置: Services/kuaixun-service/extend/pipeline.py
  • 问题: AI 评分/摘要失败、推送失败、推送渠道未配置、WS 通知失败等异常仅记录 debug 级别,生产日志不可见(静默失败)
  • 修复: 5 处 logger.debug → logger.warning(AI评分跳过/摘要跳过/无推送渠道/推送跳过/WS通知失败)

修改文件

  • 新增 Services/kuaixun-service/common/__init__.py、config.py、security.py
  • Services/kuaixun-service/tests/conftest.py: 移除 mock,改用真实 common 包
  • Services/kuaixun-service/extend/pipeline.py: 5 处日志级别升级

测试状态

  • pytest: 140 passed(与本次改动无关的 57 failed 全部为环境缺少 httpx/jieba,非代码问题)
  • 安全中间件: /health 响应头验证通过(nosniff/DENY/no-referrer/XSS-Protection)

2026-08-03 — P0 修复:主线程阻塞 + Force Unwrap 崩溃风险

变更概述

对项目做全面质量评估后,优先修复 3 项 P0 级风险:Force Unwrap 崩溃、主线程同步阻塞(UI 冻结)、以及由此引发的编译问题。所有修复已通过 swift build 零错误验证。

详细条目

1. DashboardView — Force Unwrap 崩溃修复

  • 位置: App/Sources/FinDataHub/Views/DashboardView.swift
  • 问题: 依赖表格中 dep.current!/dep.latest! 强解包,服务端数据不可信时可能崩溃
  • 修复: 将三元表达式替换为 switch 语句 + nil 默认值(?? "未知")
  • 二次修复: switch 语句形式在 @ViewBuilder 闭包内不被支持(编译错误 cannot convert value of type '[DepItem]' to expected argument type 'Binding<C>'),提取为独立辅助函数 depStatus(_:current:latest:) 后编译通过

2. ContentView — checkDatabaseReady 主线程阻塞修复

  • 位置: App/Sources/FinDataHub/ContentView.swift
  • 问题: Task 内同步调用 process.waitUntilExit() 阻塞主线程,应用启动检查数据库时会冻结 UI
  • 修复: 改为 Task.detached(priority: .userInitiated) 后台执行,返回二元组 (ready: Bool, ok: Bool)

3. SettingsView — checkDatabase / initializeDatabase 主线程阻塞修复

  • 位置: App/Sources/FinDataHub/Views/SettingsView.swift
  • 问题: 两处 process.waitUntilExit() 同步调用阻塞主线程
  • 修复: 均改为 Task.detached(priority: .userInitiated) 后台执行;initializeDatabase 移除遗留的 catch { error.localizedDescription } 块,改用 status != 0 判断退出码
  • 已确认安全: startMonitorTimer(2410)/runDbMaintenance(3112)/runArchive(3132)/runMysqlTest(3318,被 testConnection 在 DispatchQueue.global 内调用)均已在后台线程执行

修改文件

  • App/Sources/FinDataHub/Views/DashboardView.swift: force unwrap 修复 + depStatus 辅助函数
  • App/Sources/FinDataHub/ContentView.swift: checkDatabaseReady 改后台执行
  • App/Sources/FinDataHub/Views/SettingsView.swift: checkDatabase/initializeDatabase 改后台执行

编译状态

  • swift build: Build complete,零错误

2026-07-28 — 提交后正确性检查(Post-Commit Correctness Review)

变更概述

对过去24小时内暂存的代码变更进行系统性正确性审查,重点追踪高影响缺陷。审查覆盖 dal.py、database.py、dependencies.py、main.py、external.py、public.py 6个文件的变更。确认1个关键数据完整性缺陷并实施修复。

详细条目

1. 发现关键缺陷 — get_favorites 跨表自增ID冲突(数据完整性)

  • 位置: Services/kuaixun-service/app/dal.py:507
  • 问题: get_favorites() 函数使用裸 news_id 作为 news_cache 字典的 key。由于各数据源分表使用独立的自增主键,不同表之间极易出现相同 id(如 news_cls.id=100 和 news_sina.id=100)。后查询的表会覆盖 news_cache 中的记录,导致用户收藏列表中显示错误的新闻内容。
  • 触发场景: 用户收藏了来自财联社(id=100)和新浪财经(id=100)的两条新闻 → 查询收藏列表时,两条收藏都显示为新浪财经的内容,财联社收藏"丢失"。
  • 修复: 将 news_cache 的 key 从裸 id 改为复合 key (table, id),构建结果时同样使用复合 key 查找。
  • 影响范围: 所有使用收藏功能的用户,在多数据源场景下必然触发。

2. 其他变更审查结论(无关键缺陷)

  • database.py: tags 列从 Text 改为 JSON 是模型定义变更。Base.metadata.create_all 不会修改现有列类型,但 dal.py 中使用的 JSON_CONTAINS/JSON_TABLE/JSON_VALID 等 MySQL 8.0+ JSON 函数在 Text 类型的有效 JSON 字符串上可正常工作,不构成崩溃或数据丢失。
  • dependencies.py: secrets.compare_digest() 替换直接字符串比较,是安全改进,无缺陷。
  • main.py: 移除临时 API Key 日志打印,是安全改进,无缺陷。
  • external.py/public.py: UNION ALL + 参数化查询重构,SQL 注入风险已消除,无关键缺陷。

修改文件

  • Services/kuaixun-service/app/dal.py: 修复 get_favorites 中 news_cache 的 key 冲突(3行变更)
  • Services/kuaixun-service/tests/test_dal.py: 新增 TestGetFavoritesCrossTableIdCollision 测试类,覆盖两表和三表同ID冲突场景

指标统计

  • 审查文件: 6 个
  • 确认关键缺陷: 1 个(数据完整性)
  • 实施修复: 1 个
  • 新增测试: 2 个
  • 编译/测试状态: pytest 28 passed, 1 xfailed(原有异步测试因缺少 pytest-asyncio 插件失败,与本次变更无关)

2026-07-28 — 自动化测试缺口分析与补充(回归测试安全网加固)

变更概述

对项目核心模块进行系统性测试缺口分析,识别出 8 个高风险区域缺少测试覆盖,新增 8 个测试文件共计 287 个测试用例。重点覆盖安全鉴权、SQL 注入防护、SSRF 防护、去重引擎、AI 模块降级逻辑、连接池管理、数据完整性等关键路径。

详细条目

1. 安全鉴权测试 — test_dependencies.py(24 个测试)

  • 覆盖 require_auth fail-closed 安全模式:API_KEY 未配置时返回 503 而非绕过
  • secrets.compare_digest 时序攻击防护:长 key/短 key/空 key 场景
  • require_external_auth 多路径鉴权:文件验证 + 环境变量回退
  • WSManager 并发广播与死连接自动清理

2. SQL 注入防护测试 — test_dal.py(28 个测试)

  • _check_table 白名单校验:经典 SQL 注入尝试、表名后缀攻击
  • source_to_table 映射一致性与回退逻辑
  • verify_api_key 文件读取/active 状态/env fallback
  • get_favorites 跨表自增 ID 冲突(数据完整性回归保障)
  • tags 字段三分支解析(list/str/无效 JSON)

3. 内部路由测试 — test_routers_internal.py(14 个测试)

  • 所有端点的鉴权拦截(无 key → 401,错误 key → 401)
  • 表名白名单校验(非法 table → 400)
  • notify-internal 本地访问限制(非本地 IP → 403)
  • batch 端点按 source_code 分组入库逻辑

4. 公共路由测试 — test_routers_public.py(24 个测试)

  • _get_user_id 边界值:负数/超大值/SQL 注入/XSS/前导零
  • api_favorite 白名单校验:非法 source_code 拒绝
  • api_favorites_check 短路返回逻辑
  • api_search limit clamping 与空查询处理

5. SSRF 防护测试 — test_content_enricher.py(35 个测试)

  • _is_safe_url 完整覆盖:危险协议(file/gopher/dict/ftp)、内网 IP(10/172/192/127/169.254)、云元数据端点(AWS/GCP/Azure)
  • _extract_main_text HTML 正文提取:script/style/nav/header/footer 标签过滤
  • enrich_item 降级逻辑:不安全 URL 跳过、抓取失败降级
  • batch_enrich_items 并发控制与异常隔离

6. 去重引擎测试 — test_dedup.py(39 个测试)

  • compute_simhash 确定性、64 位范围、空文本处理
  • hamming_distance 位运算正确性
  • jaccard_similarity 对称性、短文本回退
  • 三层判重综合流程(精确 → SimHash → 语义)
  • LRU 淘汰机制、长度剪枝优化

7. AI 模块测试 — test_ai_modules.py(24 个测试)

  • scorer._parse_score 正则提取 0-5 分数
  • scorer.score_news 空标题降级、AI 异常返回 0
  • tagger._parse_tags 标签去重、截断、混合分隔符
  • translator.is_chinese 中文检测阈值
  • translator.translate_text 中文不翻译、翻译失败返回原文

8. 数据库模型测试 — test_database.py(21 个测试)

  • 动态分表模型结构一致性:列、索引、约束
  • SOURCE_TABLES 注册表与 VALID_TABLES 白名单一致性
  • ACTIVE_TABLES 覆盖关系验证
  • DATABASE_URL 构建逻辑(优先级、回退、字符集)

9. 公共工具模块测试 — test_common.py(27 个测试)

  • database_url() 密码特殊字符编码、空密码
  • load_env() 注释/空行/引号/已有变量不覆盖
  • PidManager write/status/stop 生命周期
  • Debug 日志分级输出

10. 连接池测试 — test_db_pool.py(18 个测试)

  • _connect_cfg 超时参数注入、setdefault 不覆盖
  • get_conn 惰性创建、ping 重连、重试机制
  • put_conn 池满关闭、None 连接处理
  • DBContext 上下文管理器异常安全

修改文件

新增测试文件:

  • Services/kuaixun-service/tests/test_dependencies.py — 安全鉴权(24 个测试)
  • Services/kuaixun-service/tests/test_dal.py — SQL 注入防护与数据完整性(28 个测试)
  • Services/kuaixun-service/tests/test_routers_internal.py — 内部路由(14 个测试)
  • Services/kuaixun-service/tests/test_routers_public.py — 公共路由(24 个测试)
  • Services/kuaixun-service/tests/test_content_enricher.py — SSRF 防护(35 个测试)
  • Services/kuaixun-service/tests/test_dedup.py — 去重引擎(39 个测试)
  • Services/kuaixun-service/tests/test_ai_modules.py — AI 模块降级(24 个测试)
  • Services/kuaixun-service/tests/test_database.py — 数据库模型一致性(21 个测试)
  • Scripts/bin/tests/test_common.py — 公共工具(27 个测试)
  • Scripts/bin/tests/test_db_pool.py — 连接池(18 个测试)

修改文件:

  • Services/kuaixun-service/tests/conftest.py — 注入 common 模块 mock,使 app.main 可在测试环境导入
  • Scripts/bin/tests/conftest.py(新增)— 注入 pymysql mock,使 db_pool 可在无 pymysql 环境下测试

指标统计

  • 新增测试文件: 10 个
  • 新增测试用例: 287 个
  • 测试结果: kuaixun-service 242 passed + 1 xfailed;Scripts/bin 44 passed + 1 deselected
  • 覆盖风险领域: 安全鉴权、SQL 注入防护、SSRF 防护、数据完整性、去重引擎、AI 降级、连接池管理
  • 跳过的测试: test_common.py::test_stop_removes_file(涉及 os.system("kill") 调用,非确定性,已标记排除)

2026-07-28 — 安全审计:周期性漏洞评估

变更概述

对全代码库进行系统性安全审计,验证历史漏洞修复状态并识别新风险。历史严重/高危漏洞(API_KEY鉴权绕过、SSRF、存储型XSS、Git密钥泄露)均已修复并验证通过。发现1个中等严重度遗留漏洞:收藏端点无认证保护。

详细条目

1. 历史漏洞修复验证

  • VULN-01 (严重): dependencies.py 已增加 fail-closed 检查(if not API_KEY: raise HTTPException(503));newsflash_backend.py lifespan 已生成临时密钥
  • VULN-02 (严重): content_enricher.py 已添加 _is_safe_url() 校验,拒绝内网 IP / 云元数据地址 / 非 http/https 协议
  • VULN-03 (高危): NewsCard.vue displayTitle 已统一使用 safeHtml() 转义后再通过 v-html 渲染
  • VULN-05 (中危): .mcp.json 已改为环境变量引用 ${CONTEXT7_API_KEY}

2. 新确认漏洞 — VULN-06: 收藏端点无认证 + 用户身份伪造(中危)

  • 位置: Services/kuaixun-service/app/routers/public.py:108-118 (POST /news/favorite)
  • 问题: 端点无任何 Depends() 鉴权,攻击者可通过 X-User-ID 请求头伪造任意用户身份(1-9999),添加/删除/读取该用户的收藏记录
  • 利用路径: 公开 API 获取 news_id → 无凭证发送 POST /news/favorite 并指定任意 X-User-ID → 操作用户收藏
  • 修复建议: 为收藏端点添加 Depends(require_external_auth) 认证依赖

3. 审计排除项(已检查但无中危及以上可利用路径)

  • SQL 注入: 所有攻击者可控输入均参数化查询或 VALID_TABLES 白名单校验
  • Shell 命令注入: 所有 subprocess/Process 调用使用硬编码参数
  • 路径遍历/不安全反序列化/WebSocket 状态变更: 无可利用路径
  • OpenAPI/docs 暴露/CORS 缺失: 服务绑定 127.0.0.1,影响有限

修改文件

  • security_audit_report_2026-07-28.md: 新增安全审计报告(含历史漏洞修复验证、VULN-06 详细分析、审计排除项)

指标统计

  • 历史漏洞修复验证: 4 项(2 严重 + 1 高危 + 1 中危)全部通过
  • 新确认漏洞: 1 项(中危)
  • 审计排除项: 13 项
  • 编译状态:零错误零警告(本次审计为代码审查,未修改源码)

2026-07-28 — 设置界面显示效果优化

变更概述

优化设置界面视觉层次和交互体验:新增粘性标题栏解决滚动后失去上下文问题,重构 sidebar 增加品牌区和选中态左侧指示条,增强 glassGroupBox 标题视觉权重。移除各 section 内部冗余的 sectionHeader 调用,统一由粘性标题栏展示当前分区。

详细条目

1. 新增粘性标题栏

  • stickyHeader 固定在内容区顶部,不随 ScrollView 滚动消失
  • 使用 FontStyle.title2 + .semibold 字重,图标使用 title3 字号
  • 背景使用 Material.header(ultraThin)保持通透感
  • 移除 9 处 section 内部 sectionHeader() 调用,删除 sectionHeader 函数

2. sidebar 重构

  • 新增品牌区:齿轮图标 + "设置" 标题(headline 字号),顶部留白提供呼吸感
  • 宽度从 110px 调整为 130px,缓解图标+文字拥挤
  • 选中态增加左侧 3px 蓝色指示条,增强视觉反馈
  • 导航项图标未选中时使用 .secondary 色,选中时使用 statusBlue
  • 选中背景透明度从 0.15 降为 0.1,配合指示条更克制
  • 圆角统一使用 FinDesign.Radius.badge

3. glassGroupBox 标题增强

  • 标题字体从 caption + .semibold 升级为 headline(13pt Semibold)
  • 标题颜色从 .secondary 改为 .primary,增强视觉权重
  • 图标颜色从 .secondary 改为 statusBlue,提升识别度
  • 标题下方新增 Divider().opacity(0.3) 分隔线,明确标题与内容的分界

修改文件

  • App/Sources/FinDataHub/Views/SettingsView.swift:新增 stickyHeader、重构 sidebarView、优化 glassGroupBox、移除 9 处 sectionHeader 调用 + 函数定义

指标统计

  • 新增组件:1 个(stickyHeader)
  • 重构组件:2 个(sidebarView、glassGroupBox)
  • 移除代码:sectionHeader 函数 + 9 处调用
  • 编译状态:零错误零警告

2026-07-28 — 设置页全面规范化:消除所有硬编码值

变更概述

对 SettingsView 进行全面规范化,消除所有硬编码的 padding、spacing、frame width、font 值,统一引用 FinDesign 设计系统。补全 FinDesign.Settings 宽度常量,字体引用 FontStyle 支持基准字号动态缩放,spacing 对齐 8pt 栅格系统。一次性完成 4 个方向的规范化,共替换 256 处硬编码值。

详细条目

1. FinDesign.Settings 补全宽度常量(3 个)

  • valueWidth: CGFloat = 60 — Slider 数值显示宽度(右对齐)
  • timeWidth: CGFloat = 50 — 时间显示宽度(小时数显示)
  • pickerMenuWidth: CGFloat = 160 — menu 样式 Picker 宽度

2. .padding(8) 常量化(58 处)

  • 全部分区内边距替换为 .padding(FinDesign.Settings.sectionPadding)

3. spacing 对齐 8pt 栅格(61 处)

  • spacing: 6 → spacing: FinDesign.Spacing.xs(xxs=4 / xs=6)
  • spacing: 8 → spacing: FinDesign.Spacing.sm
  • spacing: 12 → spacing: FinDesign.Spacing.md
  • spacing: 16 → spacing: FinDesign.Spacing.lg

4. frame(width:) 常量化(9 处)

  • frame(width: 60, alignment: .trailing) → FinDesign.Settings.valueWidth(3 处)
  • frame(width: 50) → FinDesign.Settings.timeWidth(4 处)
  • frame(width: 160) → FinDesign.Settings.pickerMenuWidth(2 处)

5. 字体引用 FontStyle 支持动态缩放(128 处)

  • .font(.caption) → .font(FinDesign.FontStyle.caption)(93 处)
  • .font(.caption2) → .font(FinDesign.FontStyle.caption2)(35 处)
  • 替换后字体随 AppearanceManager.shared.fontSize 基准字号动态缩放

修改文件

  • App/Sources/FinDataHub/Design/FinDesign.swift:FinDesign.Settings 新增 3 个宽度常量
  • App/Sources/FinDataHub/Views/SettingsView.swift:256 处硬编码值规范化(58 padding + 61 spacing + 9 frame + 128 font)

指标统计

  • 新增设计规范常量:3 个(valueWidth/timeWidth/pickerMenuWidth)
  • 规范化替换:256 处
    • padding:58 处
    • spacing:61 处
    • frame width:9 处
    • font:128 处
  • 编译状态:零错误零警告
  • 动态缩放支持:字体引用 FontStyle 后随用户字号设置自动适配

2026-07-28 — 设计规范对齐:FinDesign.Settings 子规范

变更概述

将 SettingsView 中局部定义的 labelWidth、nestedIndent、spacing: 10 三个硬编码常量提升到 FinDesign 设计系统,新增 FinDesign.Settings 子规范统一管理。nestedIndent 复用 Spacing.xxl(24),消除重复定义,使设置页布局规范与全局设计系统对齐。

详细条目

1. FinDesign 新增 Settings 子规范

  • labelWidth: CGFloat = 80 — 行内标签宽度(统一左对齐)
  • rowSpacing: CGFloat = 10 — 行间距(VStack 内子控件垂直间距)
  • nestedIndent: CGFloat = Spacing.xxl — 嵌套子控件缩进(复用 Spacing.xxl=24)
  • sectionPadding: CGFloat = Spacing.sm — 分区内边距(预留)

2. SettingsView 常量改为引用 FinDesign

  • labelWidth 由局部 80 改为 FinDesign.Settings.labelWidth
  • nestedIndent 由局部 24 改为 FinDesign.Settings.nestedIndent(复用 Spacing.xxl)
  • 51 处 spacing: 10 统一替换为 spacing: FinDesign.Settings.rowSpacing

修改文件

  • App/Sources/FinDataHub/Design/FinDesign.swift:新增 Settings 子规范(4 个常量)
  • App/Sources/FinDataHub/Views/SettingsView.swift:2 处常量改为引用 FinDesign、51 处 spacing 常量化

指标统计

  • 新增设计规范常量:4 个(FinDesign.Settings)
  • 常量化引用:53 处(2 处 width/indent + 51 处 spacing)
  • 复用现有规范:1 处(nestedIndent 复用 Spacing.xxl)
  • 编译状态:零错误零警告

2026-07-28 — 设置 UI 样式统一与结构规范化

变更概述

对设置页面进行全面的样式统一和结构规范化,消除所有内联 Toggle/Picker 构造,统一使用可复用行组件(toggleRow/pickerRow/segmentedRow/sourceToggleRow),引入 nestedIndent 常量替代硬编码缩进值,修复 saveSyncAdvancedPrefs() 函数重复定义的编译错误。

详细条目

1. 新增 nestedIndent 常量

  • 在 labelWidth 旁新增 nestedIndent: CGFloat = 24,用于嵌套子控件(父 Toggle 开启后展示的子选项)的统一缩进
  • 替换全部 11 处硬编码 .padding(.leading, 24) 为 .padding(.leading, nestedIndent)

2. Toggle 组件统一化(3 处)

  • 通用分区「在程序坞中显示图标」:内联 Toggle → toggleRow(保留 setActivationPolicy 逻辑)
  • 通知分区「新快讯通知推送」:内联 Toggle → toggleRow(保留 start/stopNotificationPolling 逻辑)
  • 日志分区「启用远程日志上报」:内联 Toggle → toggleRow

3. RSS 数据源 Toggle 统一化(2 处)

  • 雪球、英为财情:内联 Toggle → sourceToggleRow(与财联社/选股宝等数据源一致,支持点击测试)
  • sourceToggleRow 组件 Text 添加 .font(.caption) 保持字体一致

4. Segmented Picker 统一化(2 处)

  • 外观分区「外观模式」:内联 Picker → segmentedRow
  • 快讯分区「音效类型」:内联 Picker → segmentedRow

5. 修复 saveSyncAdvancedPrefs() 重复定义

  • 拆分为 saveSyncStrategyPrefs()(同步模式/冲突策略/时段限制)和 saveSyncTimeoutPrefs()(超时/重试/间隔)
  • 更新 8 处调用点为对应的新函数名

修改文件

  • App/Sources/FinDataHub/Views/SettingsView.swift:新增 nestedIndent 常量、3 处 Toggle 统一化、2 处 RSS Toggle 统一化、2 处 Segmented Picker 统一化、11 处 padding 常量化、修复重复函数定义

指标统计

  • 统一化 Toggle:5 处(3 toggleRow + 2 sourceToggleRow)
  • 统一化 Picker:2 处(segmentedRow)
  • 常量化 padding:11 处
  • 修复编译错误:1 处(函数重复定义)
  • 编译状态:零错误零警告

2026-07-28 — 设置功能扩展第二批 17 项(v1.4.5)

版本:1.4.5

变更概述

在设置各分区新增 17 项功能控制,覆盖通用、同步、性能、快讯、通知、AI、日志、开发者 8 个分区。所有配置均通过 UserDefaults 自动保存,无需手动保存按钮。

新增详情

1. 通用分区 - 界面与显示

  • 界面语言:简体中文 / English
  • 时区显示:本地时区 / UTC
  • 数据预览行数:50-500 行(默认 100)

2. 同步分区 - 同步策略

  • 同步模式:增量 / 全量
  • 冲突策略:覆盖 / 跳过 / 合并
  • 同步时段限制:仅在指定时段执行(如 9:00-18:00)

3. 性能分区 - 资源限制

  • 网络带宽限制:0=不限 / 128-10240 KB/s
  • 磁盘缓存上限:256-8192 MB(默认 1024)

4. 快讯分区 - 快讯智能

  • 重复快讯过滤:开关 + 相似度阈值(50-100%)
  • 自动分组:开关 + 分组规则(关键词/板块)
  • 已读记忆:跨重启保留已读状态

5. 通知分区 - 勿扰时段

  • 勿扰开关 + 免打扰时段(0-23 点)
  • 高热度快讯突破勿扰开关

6. AI 分区 - AI 容错

  • 请求超时:30-300 秒(默认 60)
  • AI 不可用时回退规则评分
  • 摘要长度:短/中/长

7. 日志分区 - 日志归档

  • 写入级别筛选:DEBUG/INFO/WARNING/ERROR
  • 自动压缩归档开关
  • 压缩天数:3-30 天

8. 开发者分区 - 数据库维护

  • 启动时自动优化表开关
  • 记录慢查询日志开关
  • 启用数据迁移工具开关
  • 立即维护按钮(执行 OPTIMIZE TABLE)

修改文件

  • App/Sources/FinDataHub/AppConstants.swift:新增 17 个 AppKeys
  • App/Sources/FinDataHub/Views/SettingsView.swift:新增 17 个状态变量、8 个 GroupBox UI、8 个 save 函数、7 个 load 函数扩展、1 个 runDbMaintenance 函数

指标统计

  • 新增 AppKeys:17 个
  • 新增 GroupBox:8 个
  • 新增 save 函数:8 个(saveGeneralAdvancedPrefs/saveSyncAdvancedPrefs/savePerformanceAdvancedPrefs/saveNotificationAdvancedPrefs/saveAIAdvancedPrefs/saveLogAdvancedPrefs/saveDeveloperAdvancedPrefs + 扩展 saveNewsFlashAdvancedPrefs)
  • 扩展 load 函数:7 个
  • 编译状态:零错误零警告
  • 应用体积:479M

2026-07-28 — 性能配置 + 设置全功能扩展(v1.4.4)

版本:1.4.4

变更概述

新增"性能配置"分区,通过硬件检测自动推荐参数并支持自动/高性能/自定义三种模式。同时在设置各分区增加 13 项新功能控制,覆盖缓存管理、数据校验、同步报告、快捷键、数据导出、性能监控、快照管理、关键词高亮、声音反馈、AI 高级参数、远程日志上报等。修复 3 个预存编译错误。

新增详情

1. 性能配置分区(新分区)

  • 新增 PerformanceManager.swift:单例管理器,通过 Mach API + sysctl 检测 CPU 核心数和物理内存
  • 三种性能模式:
    • 自动:根据内存大小推荐参数(<8GB 保守 / 8-16GB 主流 / 16-32GB 高性能 / >32GB 旗舰)
    • 高性能:固定使用最大参数(workers=16, poolSize=12, cacheThreshold=80, batchSize=20000)
    • 自定义:用户手动调整并发线程/连接池/缓存阈值/批量大小
  • 硬件检测支持手动重新检测,显示推荐参数对比
  • 自定义模式支持"应用推荐配置"一键填充
  • 参数通过 UserDefaults 持久化(key 前缀 findatahub_perf_)

2. 同步分区新增

  • 缓存管理:缓存过期时间(1-168 小时)+ 手动清理缓存按钮
  • 数据校验:同步后自动校验开关 + 严格模式(行数下降 >10% 告警)
  • 同步报告:报告保留天数(7-180 天)+ 报告列表查看

3. 通用分区新增

  • 全局快捷键:打开窗口 + 快速同步(留空=不启用)
  • 数据导出:格式选择(CSV/JSON/Excel)+ 导出路径选择

4. 快讯分区新增

  • 关键词高亮:自定义高亮关键词(逗号分隔)
  • 声音反馈:新快讯声音提示开关 + 音效类型(默认/急促/轻柔)

5. AI 分区新增

  • 摘要语言:中文/英文切换
  • 模型温度:0-2 范围调节(推荐 0.7),温度越低输出越稳定

6. 日志分区新增

  • 远程上报:启用开关 + Webhook URL 配置

7. 开发者分区新增

  • 性能监控:实时 CPU/内存占用显示(2 秒刷新,超 80% 标红)
  • 快照管理:手动创建快照(JSON 格式含硬件/模式/参数)+ 快照列表查看

8. 预存编译错误修复

  • LogArchiveView.swift:422:FinIcon.database → FinIcon.mysql
  • LogArchiveService.swift:90-91:Optional Bool 解包修复(!x? ?? false → !(x? ?? false))

修改文件

  • App/Sources/FinDataHub/Services/PerformanceManager.swift — 新文件,性能配置管理器
  • App/Sources/FinDataHub/Views/SettingsView.swift — 新增性能分区 + 13 项功能控制 + load/save 函数
  • App/Sources/FinDataHub/AppConstants.swift — 新增性能/缓存/AI/快讯/导出/日志/快捷键/监控相关 Key
  • App/Sources/FinDataHub/Views/LogArchiveView.swift — 修复 FinIcon.database 编译错误
  • App/Sources/FinDataHub/Services/LogArchiveService.swift — 修复 Optional Bool 解包错误
  • VERSION — 1.4.3 → 1.4.4

编译状态

  • 编译:零错误(预存 3 个错误已修复)
  • 包大小:479M
  • 版本:1.4.3 → 1.4.4

2026-07-28 — 设置页面分区结构优化(v1.4.3)

版本:1.4.3

变更概述

优化设置页面分区结构,从 10 个分区精简为 8 个,提高操作逻辑一致性。清理死代码,统一标签宽度和保存逻辑。

优化详情

1. 分区结构精简(10 → 8)

  • 合并"关于" → "通用"底部(应用信息 GroupBox)
  • 合并"版本检查" → "开发者"底部(版本检查 GroupBox)
  • 新分区顺序:通用 / 外观 / 同步 / 快讯 / 通知 / AI / 日志 / 开发者

2. 清理死代码

  • 删除 headerView(未使用,含 saved 状态指示器)
  • 删除 saveButtonRow(EmptyView,5 处引用)
  • 删除 saveAllSettings()(未被任何 UI 调用)
  • 删除 showSavedIndicator()(9 处调用 + 函数定义)
  • 删除 saved 状态变量
  • 删除 aboutSection、versionSection 独立视图

3. 统一标签宽度到 80px

  • syncLabelWidth:70 → 80
  • sliderRow 标签:70 → 80
  • portRow 标签:70 → 80
  • "窗口尺寸"标签:60 → 80

4. 统一保存逻辑

  • maxFailures/alertInterval:直接 defaults.set → saveNotificationPrefs()
  • nfCollectInterval:直接 defaults.set → saveNewsFlashPrefs()
  • nfAutoStartBackend/nfAutoStartCollector:直接 defaults.set → saveNewsFlashPrefs()
  • saveNotificationPrefs() 补充保存 maxFailures 和 alertInterval

修改文件

  • App/Sources/FinDataHub/Views/SettingsView.swift

编译状态

  • 编译:零错误/零警告
  • 包大小:478M
  • 版本:1.4.2 → 1.4.3

2026-07-28 — 安全修复:凭证硬编码 + shell 注入(v1.4.2)

版本:1.4.2

变更概述

修复两个安全卫生问题:akshare_fetcher 硬编码凭证、tushare_fetcher shell=True。两者均无远程利用路径,但按项目规则修复。

修复详情

1. akshare_fetcher 硬编码凭证

  • config/default.yaml:密码 *** 改为空字符串,iwencai_api_key 改为空字符串
  • core/config.py load_config():添加环境变量覆盖支持
    • DB_PASSWORD / MYSQL_PASSWORD → cfg["db"]["password"]
    • DB_HOST / MYSQL_HOST → cfg["db"]["host"]
    • DB_USER / MYSQL_USER → cfg["db"]["user"]
    • DB_PORT / MYSQL_PORT → cfg["db"]["port"]
    • DB_DATABASE / MYSQL_DATABASE_AKSHARE → cfg["db"]["database"]
    • IWENCAI_API_KEY → cfg["akshare"]["iwencai_api_key"]
  • 环境变量优先级:DB_* 优先,回退到 MYSQL_*(与 Swift 端传递的环境变量对齐)

2. tushare_fetcher shell=True

  • cli.py uninstall:shell=True 改为 ["crontab", "-l"] 列表参数
  • cli.py _install_crontab:shell=True 改为 ["crontab", "-l"] + Python 字符串拼接

修改文件

  • Services/akshare_fetcher/config/default.yaml — 移除硬编码凭证
  • Services/akshare_fetcher/src/akshare_fetcher/core/config.py — 添加环境变量覆盖
  • Services/tushare_fetcher/src/tushare_fetcher/cli.py — 消除 shell=True

编译状态

  • 编译:零错误/零警告
  • 包大小:478M
  • 版本:1.4.1 → 1.4.2

2026-07-28 — 同步系统 6 项进阶优化(v1.4.1)

版本:1.4.1

变更概述

完成 6 项同步系统进阶优化:数据完整性校验、代码列表缓存、同步报告生成、内存使用优化、并行度自适应、数据快照备份。

优化详情

# 优化项 优先级 状态
1 数据完整性校验 高 ✅
2 代码列表缓存 中 ✅
3 同步报告生成 中 ✅
4 内存使用优化 低 ✅
5 并行度自适应 低 ✅
6 数据快照备份 低 ✅

详细变更

  1. 数据完整性校验 — 同步后自动校验

    • 新增 validator.py(TuShare + AkShare 各一份)
    • 校验内容:行数、日期连续性、代码覆盖率
    • 日期缺失检测(交易日 gap 分析)
    • 集成到 TuShare run() 和 AkShare sync_all_stocks() 末尾
  2. 代码列表缓存 — 文件级缓存避免重复 API 调用

    • TuShare 5 个代码列表属性添加文件缓存(24h TTL)
    • AkShare get_stock_list() 添加文件缓存
    • 缓存路径:~/.findatahub/cache/
  3. 同步报告生成 — 结构化 JSON 报告

    • 新增 report.py(TuShare + AkShare 各一份)
    • 报告内容:时间、耗时、各表行数、失败表、校验摘要
    • 自动保留最近 30 份报告
    • 报告路径:~/.findatahub/reports/
  4. 内存使用优化 — 分批加载避免内存峰值

    • TuShare 日期驱动并行模式改为分批 50 个请求
    • 每批写入后 gc.collect() 释放内存
    • _flush_code_batch 写入后释放 DataFrame
  5. 并行度自适应 — 根据 API 响应速度动态调整

    • AkShare sync_all_stocks 添加自适应并发
    • 每 10 个请求评估平均响应时间
    • avg > 10s 降并发 2,avg < 3s 升并发 1
  6. 数据快照备份 — 行数快照检测数据丢失

    • 新增 snapshot.py
    • 每次同步后拍摄各表行数快照
    • 与上次对比,行数下降 > 10% 时告警
    • 快照路径:~/.findatahub/snapshots/

新增文件

  • Services/tushare_fetcher/src/tushare_fetcher/core/validator.py
  • Services/tushare_fetcher/src/tushare_fetcher/core/report.py
  • Services/tushare_fetcher/src/tushare_fetcher/core/snapshot.py
  • Services/akshare_fetcher/src/akshare_fetcher/core/validator.py
  • Services/akshare_fetcher/src/akshare_fetcher/core/report.py

修改文件

  • Services/tushare_fetcher/src/tushare_fetcher/core/fetcher.py — 集成校验、报告、快照、内存优化
  • Services/tushare_fetcher/src/tushare_fetcher/core/api.py — 代码列表文件缓存
  • Services/akshare_fetcher/src/akshare_fetcher/core/fetcher.py — 校验、报告、自适应并发、列表缓存

编译状态

  • 编译:零错误/零警告
  • 包大小:478M
  • 版本:1.4.0 → 1.4.1

2026-07-28 — 同步优化全面升级(v1.4.0)

版本:1.4.0

变更概述

完成 6 项同步系统优化:同步稳定性增强、性能监控完善、UI 体验提升、数据库优化、日志系统增强、缓存策略实现。

优化详情

# 优化项 优先级 完成状态
1 同步稳定性:失败重试、超时控制、断点续传 高 ✅ 完成
2 性能监控:同步速率、预估剩余时间、成功率统计 高 ✅ 完成
3 UI 体验:进度条、任务队列可视化、错误提示 中 ✅ 完成
4 数据库优化:索引、批量写入、连接池调优 中 ✅ 完成
5 日志系统:结构化日志、时间筛选、级别控制 中 ✅ 完成
6 缓存策略:本地缓存、增量同步优化 低 ✅ 完成

详细变更

  1. 同步稳定性 — 失败重试 + 超时控制

    • TuShare/TuShare API 调用增加 3 次重试,指数退避
    • AkShare 添加 retry_on_failure 装饰器
    • 网络超时控制:socket 默认 30 秒,各数据类型自定义超时
  2. 性能监控 — 实时指标追踪

    • Python CLI 输出成功/失败计数、同步速率
    • Swift 端 PythonProcessManager 解析并展示
    • SyncProgressView 显示颜色编码的性能指标
  3. UI 体验 — 任务队列可视化

    • 多任务队列顺序执行机制
    • 进度条显示当前任务进度和等待队列
    • 自动启动下一个队列任务
  4. 数据库优化 — 批量写入优化

    • 分片大小从 10000 提升到 20000 行
    • 启用 cursor.fast_executemany = True
    • 添加事务 commit/rollback
    • AkShare 连接池大小从默认改为 12
  5. 日志系统 — 结构化日志增强

    • 关键词搜索功能(支持消息、数据源、任务名)
    • 日期范围筛选模式(单日/日期范围切换)
    • 日志行增加数据源标签显示
    • INFO/PROGRESS 级别颜色改为蓝色
    • 展开详情增加完整时间戳信息
  6. 缓存策略 — 本地缓存优化

    • SyncCache:内存+文件双缓存,1 小时过期
    • TradeDateCache:交易日历缓存,24 小时过期
    • TuShare/TuShare 写入成功后更新缓存
    • 数据最新时跳过同步,避免无效 API 调用

修改文件

Swift 端:

  • App/Sources/FinDataHub/Services/LogArchiveService.swift
  • App/Sources/FinDataHub/Services/DebugLogger.swift
  • App/Sources/FinDataHub/Views/LogArchiveView.swift
  • App/Sources/FinDataHub/Models/SyncLogEntry.swift

TuShare:

  • Services/tushare_fetcher/src/tushare_fetcher/core/fetcher.py
  • Services/tushare_fetcher/src/tushare_fetcher/core/db.py
  • Services/tushare_fetcher/src/tushare_fetcher/core/cache.py(新增)

AkShare:

  • Services/akshare_fetcher/src/akshare_fetcher/core/fetcher.py
  • Services/akshare_fetcher/src/akshare_fetcher/core/db.py
  • Services/akshare_fetcher/src/tushare_fetcher/core/cache.py(新增)
  • Services/akshare_fetcher/config/default.yaml

编译状态

  • 编译:零错误/零警告
  • 包大小:478M
  • 签名:ad-hoc

2026-07-27 — 同步性能优化:6 项提速改进

版本:1.3.33

变更概述

对 AkShare 和 TuShare 数据同步脚本进行 6 项性能优化,预计整体提速 3-5 倍。主要消除数据库连接开销、将串行 API 调用改为并行、提升 API 限流吞吐上限。

优化详情

# 优化项 预期收益 影响范围
1 移除 AkShare upsert 自动 meta 调用 30-50% AkShare 全市场同步
2 TuShare 日期驱动表改并行 3-5x daily/moneyflow/margin 等
3 降低 request_interval 0.35→0.30 17% TuShare 全部
4 并行阈值 1000→100 + workers 传参 中等 基金/期权等中型表
5 TuShare replace 改 executemany 2-5x 静态全量表写入
6 sync_bridge workers 参数传入 DataFetcher 配合 #2#4 TuShare 整体

详细变更

  1. AkShare db.py — 移除 upsert 中自动 update_table_meta 调用

    • 之前:每只股票 upsert 后新建 MySQL 连接更新 meta(5500 次/全量同步)
    • 之后:由 fetcher 在同步完成后统一调用一次
  2. TuShare fetcher.py — 日期驱动表并行化

    • 之前:交易日串行循环,每天一次 API + 一次 DB 写入
    • 之后:用 batch_call 并行拉取所有交易日,合并后一次性 upsert
    • max_workers 从硬编码 3 改为 self.max_workers,由 sync_bridge 传入
  3. TuShare default.yaml — request_interval: 0.35 → 0.30

    • 总吞吐从 171 req/min 提升到 200 req/min(Tushare 普通积分上限)
  4. TuShare fetcher.py — 并行阈值 1000 → 100

    • 基金(~800 只)、期权等中型表现在也走并行路径
  5. TuShare db.py — replace() 从 df.to_sql 改为原生 executemany

    • 与 AkShare 的 replace 实现一致,避免 SQLAlchemy 类型推断开销
  6. sync_bridge.py — workers 参数传入 DataFetcher(cfg, max_workers={workers})

修改文件

  • Services/akshare_fetcher/src/akshare_fetcher/core/db.py
  • Services/tushare_fetcher/src/tushare_fetcher/core/fetcher.py
  • Services/tushare_fetcher/src/tushare_fetcher/core/db.py
  • Services/tushare_fetcher/config/default.yaml
  • Scripts/bin/sync_bridge.py

编译状态

  • ✅ Python 语法验证全部通过
  • ✅ Swift 编译零错误

2026-07-27 — 修复快讯中心重启后加载失败(根因修复)

版本:1.3.31

变更概述

深入修复快讯中心重启后加载失败问题。上版仅修复了状态检查失败的问题,本次定位到真正的根因:ensureLoaded() 同时并发触发 refresh() 和 fetchNews() 两个 Python 进程,列表加载失败会覆盖 self.error,而 UI 中 error 检查优先于 data,导致即使数据已加载也显示"加载失败"。

根因分析

ensureLoaded()
  ├─ refresh()      → Task { fetchNewsFlash() }     → 成功 → self.data = result, self.error = nil
  └─ fetchNews()    → Task { fetchNewsFiltered() }  → 失败 → self.error = "进程退出码 1"  ← 覆盖!
  
UI 逻辑: isLoading → error(优先) → data  → 显示"加载失败"(即使 data 已存在)

详细变更

  1. UI 逻辑修复(NewsFlashView.swift)

    • data != nil 检查提前到 error 之前:有数据就显示,不被次要错误阻塞
    • 仅在 data == nil && error != nil 时才显示"加载失败"
  2. loadMoreNews() 错误隔离(NewsFlashService.swift)

    • 列表加载失败不再无条件覆盖 self.error
    • 仅在 newsList.isEmpty && data == nil 时才设置 error
  3. 消除冗余请求

    • ensureLoaded() 不再同时调用 refresh() + fetchNews()
    • refresh() 成功后用 data.latest 初始化 newsList,避免第二次 Python 进程
  4. 脚本路径修复

    • fetchNewsFlash() 和 fetchNewsFiltered() 改用 findScript() 替代 cachedScriptBase 直接拼接
    • 获得开发模式下的路径回退支持

修改文件

  • App/Sources/FinDataHub/Services/NewsFlashService.swift — ensureLoaded/refresh/loadMoreNews/fetchNewsFlash/fetchNewsFiltered
  • App/Sources/FinDataHub/Views/NewsFlashView.swift — UI 优先级调整

编译状态

  • ✅ 零错误 / 零警告(仅有已存在的 actor-isolation 警告)

2026-07-27 — 修复快讯中心重启后加载失败

版本:1.3.30

变更概述

修复每次重启应用后进入快讯中心出现"加载失败"提示的问题。根因是 refresh() 方法中数据加载与状态检查使用 async let + try await 组合,状态检查失败会连带丢弃数据加载结果。

详细变更

  1. NewsFlashService.refresh() — 核心修复

    • 问题:async let + try await (dataFetch, collectorStatusFetch, backendStatusFetch) 组合,任意一个失败则整体 throw,数据结果被丢弃
    • 修复:数据加载 fetchNewsFlash() 单独 try await,状态检查用 try? 降级为非致命,与 checkServiceHealth() 的做法保持一致
    • 状态检查失败时回退为 CollectorStatus(running: false),不影响数据展示
  2. NewsFlashView — 避免重复加载

    • 问题:Color.clear.onAppear 同时调用 refresh() + fetchNews(),与 ensureLoaded() 重复
    • 修复:改为调用 ensureLoaded(),利用 hasLoaded 标志避免重复请求

修改文件

  • App/Sources/FinDataHub/Services/NewsFlashService.swift — refresh() 方法重构
  • App/Sources/FinDataHub/Views/NewsFlashView.swift — 避免重复加载

编译状态

  • ✅ 零错误 / 零警告(仅有已存在的 actor-isolation 警告)

2026-07-27 — 同步任务队列:多任务排队执行

版本:1.3.29

变更概述

新增同步任务队列系统。当日线/周线/月线等多个同步任务同时触发时,不再拒绝后来的请求,而是自动排队顺序执行。队列在同步进度面板中可视化展示,包含每项的预计耗时和总耗时预估。

详细变更

  1. PythonProcessManager 任务队列

    • 新增 syncQueue: [SyncRequest] 和 @Published queuedTasks: [String]
    • startSync() 检测到正在执行时自动将请求加入队列
    • 同类型去重:队列中已存在的任务类型不重复添加
    • processNextQueued() 在当前任务完成后自动启动下一个
    • 完成/失败后继续处理队列;取消/中断时清空队列
  2. SyncProgressView 队列可视化

    • 进度面板下方新增 "等待队列" 区域
    • 每项显示:序号 + 任务名称 + 历史平均耗时
    • 底部显示 "预计总耗时"(当前剩余 + 队列各项均值)
    • 仅在队列非空时显示

修改文件

  • App/Sources/FinDataHub/Services/PythonProcessManager.swift — 队列逻辑
  • App/Sources/FinDataHub/Views/SyncProgressView.swift — 队列展示

编译状态

  • ✅ 零错误 / 零警告(仅有已存在的 actor-isolation 警告)

2026-07-27 — 增加任务拉取预估时间

版本:1.3.28

变更概述

新增定时任务拉取耗时预估功能。基于历史同步记录的平均耗时,在各视图中展示预计耗时和实时剩余时间,让用户对同步进度有更清晰的时间感知。

详细变更

  1. HistoryStore 新增耗时查询方法

    • averageDuration(for:count:) — 取最近 N 次成功记录的平均耗时
    • lastSuccessDuration(for:) — 取最近一次成功耗时
  2. TimeInterval 格式化扩展(AppConstants.swift)

    • .shortDisplay → "5m30s" 短格式
    • .chineseDisplay → "5分30秒" 中文格式
    • .estimatedDisplay → "约 5m30s" 预估格式
  3. PythonProcessManager 预估剩余时间

    • 新增 estimatedRemainingSeconds @Published 属性
    • updateEstimatedRemaining() 智能融合两种估算:
      • 线性估算:已耗时/进度 * 剩余进度
      • 历史均值估算:历史平均 - 已耗时
      • 加权策略:进度 < 50% 偏历史,进度 > 50% 偏线性
  4. 三处视图展示预估时间

    • DashboardView 定时任务预览:每项显示 约 5m30s 预估耗时
    • SchedulerTaskCard:摘要行增加 约 5m30s 预估耗时
    • SyncProgressView 进度面板:实时显示 剩余 3m15s

修改文件

  • App/Sources/FinDataHub/AppConstants.swift — TimeInterval 格式化扩展
  • App/Sources/FinDataHub/Services/HistoryStore.swift — 耗时查询方法
  • App/Sources/FinDataHub/Services/PythonProcessManager.swift — 预估计算逻辑
  • App/Sources/FinDataHub/Views/DashboardView.swift — 预估耗时展示
  • App/Sources/FinDataHub/Views/SchedulerView.swift — 预估耗时展示
  • App/Sources/FinDataHub/Views/SyncProgressView.swift — 剩余时间展示

编译状态

  • ✅ 零错误 / 零警告(仅有已存在的 actor-isolation 警告)

2026-07-27 — 优化概览定时任务展示 + 运行日志着色

版本:1.3.27

变更概述

优化 Dashboard 定时任务预览卡片的展示效果,改进 SchedulerView 定时任务卡片的视觉层次(Cron 编辑器可折叠),为 SyncProgressView 运行日志添加行号、级别着色和清空功能。

详细变更

  1. DashboardView 定时任务预览优化

    • 显示全部任务(5个)替代之前仅 3 个
    • 添加启用/暂停状态徽章和最近执行结果指示点
    • Cron 表达式紧凑展示(彩色标签 + 半透明底色)
    • 添加倒计时文字("3小时后"、"25分钟后")
    • 显示上次执行的耗时
    • 分隔线区分各任务
    • 头部显示 "N/5 启用" 统计
  2. SchedulerTaskCard 视觉层次优化

    • Cron 编辑器改为可折叠(默认收起),点击 Cron 标签展开
    • 摘要行:Cron + 下次执行倒计时 + 上次执行状态/耗时 + 立即运行按钮
    • 上次执行添加结果指示点(绿色成功/红色失败)
    • 禁用时显示 "任务已暂停,不会自动执行" 友好提示
    • 添加 "启用" 状态徽章
  3. SyncProgressView 运行日志着色

    • 按行渲染日志,添加行号(右对齐,灰色半透明)
    • 日志级别着色:错误/失败→红色,警告→橙色,完成/成功→绿色,普通→主色
    • 日志头部添加行数统计和清空按钮
    • 日志区域高度从 150px 增加到 200px
    • PythonProcessManager 新增 clearLog() 方法

修改文件

  • App/Sources/FinDataHub/Views/DashboardView.swift
  • App/Sources/FinDataHub/Views/SchedulerView.swift
  • App/Sources/FinDataHub/Views/SyncProgressView.swift
  • App/Sources/FinDataHub/Services/PythonProcessManager.swift

编译状态

  • ✅ 零错误 / 零警告(仅有已存在的 actor-isolation 警告)

2026-07-27 — 优化同步进度条:添加耗时/动画/分组进度追踪

版本:1.3.26

变更概述

统一优化 SyncProgressView、AkShareView、TuShareView 三处同步进度条展示,增加实时耗时、平滑动画、数据源标识和分组进度追踪,提升用户对同步过程的感知度。

详细变更

  1. AkShareView 行内进度条优化

    • 添加 Timer.publish 驱动实时耗时显示(替换静态"开始时间")
    • 进度条添加 easeOut 动画(0.3s)
    • 添加数据源标识(蓝色 "AkShare")+ 表名 lineLimit 限制
    • 完成计数添加 fontWeight(.medium) 增强可读性
  2. TuShareView 行内进度条优化

    • 同步 AkShareView 的所有改进
    • 添加分组进度显示(如 "daily (3/6)")
    • 使用绿色系配色区分数据源
  3. PythonProcessManager 分组进度追踪

    • 新增 groupTableTotal 和 groupTableDone @Published 属性
    • 解析 📡 [daily] → 6 张表 提取表数量
    • 在表完成行(xxx: N 行 ✓)自动递增 groupTableDone
    • 分组完成时(✅ [daily] 完成)同步标记完成
    • reset() 方法同步清理新属性
  4. SyncProgressView 分组进度格式化

    • 新增 groupProgressText computed property,格式化为 daily (3/6)
    • 仅当 groupTableTotal > 0 时展示计数

修改文件

  • App/Sources/FinDataHub/Services/PythonProcessManager.swift
  • App/Sources/FinDataHub/Views/SyncProgressView.swift
  • App/Sources/FinDataHub/Views/AkShareView.swift
  • App/Sources/FinDataHub/Views/TuShareView.swift

编译状态

  • ✅ 零错误 / 零警告(仅有已存在的 actor-isolation 警告)

2026-07-27 — 安全审计:修复 5 个已确认漏洞(认证绕过/SSRF/XSS/输入校验/凭证泄露)

版本:1.3.1

变更概述

对代码库进行全面安全审计后发现 5 个已确认漏洞,具备端到端可利用路径。核心问题是 newsflash_backend.py 生产入口缺少 API_KEY 自动生成逻辑,导致 secrets.compare_digest("", "") 返回 True,所有受保护的内部端点可在无凭证情况下被远程访问。该认证绕过可链式触发 SSRF(通过 content_enricher 抓取内网/云元数据地址)和存储型 XSS(通过 v-html 渲染注入的恶意标题)。

详细变更

  1. VULN-01(严重):API_KEY 鉴权绕过

    • dependencies.py 的 require_auth 增加 fail-closed 检查:API_KEY 为空时返回 503
    • newsflash_backend.py 的 lifespan 增加 API_KEY 自动生成(与 main.py 一致)
    • newsflash_backend.py 绑定地址从 0.0.0.0 改为 127.0.0.1
  2. VULN-02(严重):SSRF — content_enricher 无 URL 校验

    • 新增 _is_safe_url() 函数:拒绝非 http/https 协议、内网 IP(127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16)、云元数据端点
    • fetch_article_text() 在发起 HTTP 请求前调用 _is_safe_url() 校验
  3. VULN-03(高危):存储型 XSS — v-html 渲染未转义内容

    • NewsCard.vue 的 displayTitle 在无搜索高亮时也使用 safeHtml() 转义
  4. VULN-04(中危):收藏端点无认证 + 输入校验缺失

    • public.py 新增 VALID_SOURCE_CODES 白名单校验,拒绝非法 source_code
    • _get_user_id() 增加 user_id 范围限制(1-9999)
  5. VULN-05(中危):Git 中泄露 API Key

    • .mcp.json 的 Context7 API Key 改为环境变量占位符 ${CONTEXT7_API_KEY}
    • .gitignore 添加 .mcp.json

修改文件清单

  • Services/kuaixun-service/app/dependencies.py — require_auth fail-closed 检查
  • Scripts/bin/newsflash_backend.py — lifespan API_KEY 生成 + 127.0.0.1 绑定 + secrets 导入
  • Services/kuaixun-service/extend/content_enricher.py — 新增 _is_safe_url() SSRF 防护
  • Services/kuaixun-frontend/src/components/NewsCard.vue — displayTitle 统一 HTML 转义
  • Services/kuaixun-service/app/routers/public.py — source_code 白名单 + user_id 范围限制
  • .mcp.json — API Key 改为环境变量占位符
  • .gitignore — 添加 .mcp.json

验证结果

  • Python 语法检查:✅ 4 个文件零错误(dependencies.py / newsflash_backend.py / content_enricher.py / public.py)
  • JSON 格式校验:✅ .mcp.json 有效
  • 前端文件检查:✅ NewsCard.vue displayTitle 逻辑正确

指标统计

  • 修复漏洞数:5(严重 2 + 高危 1 + 中危 2)
  • 修改文件数:7
  • 新增代码行数:~70
  • 安全审计报告:security_audit_report.md

2026-07-27 — 修复采集器 AI 处理被事件循环取消的严重数据完整性 Bug

版本:1.3.1

变更概述

newsflash_collect.py 的 collect_once() 函数中使用 asyncio.create_task(do_ai()) 创建 AI 处理后台任务但未 await,导致 asyncio.run() 关闭事件循环时 do_ai() 被取消。AI 评分、摘要、打标签、内容增强的结果永远不会写入数据库,造成所有快讯的 AI 字段(importance/summary/tags)永久为空。

根因分析

collect_once() 是一个 async 函数,被 main() 中的 asyncio.run() 或 asyncio.run(asyncio.wait_for(...)) 调用。这些调用在 collect_once() 返回后立即关闭事件循环。由于 do_ai() 仅通过 asyncio.create_task() 创建而未被 await,事件循环关闭时该任务被取消,AI 处理流程中途终止。

触发场景

  • once 模式:asyncio.run(collect_once()) — 事件循环在 collect_once() 返回后关闭
  • start 模式:每次循环中的 asyncio.run(asyncio.wait_for(collect_once(), timeout=COLLECT_TIMEOUT)) — 同样在返回后关闭
  • 影响:每次采集的 AI 处理全部丢失,用户看到的所有快讯缺少热度评分、摘要和标签

修复内容

  1. 调整执行顺序:将数据写入(insert_news)移至 AI 处理之前,确保原始数据优先持久化
  2. 改为 await:将 asyncio.create_task(do_ai()) 替换为 await do_ai(),确保 AI 处理在事件循环关闭前完成
  3. 通知后移:WebSocket 广播通知在数据写入后、AI 处理前触发,保持实时性

修改文件清单

  • Scripts/bin/newsflash_collect.py — 调整 collect_once() 中数据写入和 AI 处理的执行顺序,改 create_task 为 await

验证结果

  • Python 语法检查:✅ 零错误
  • 行为测试:✅ 验证 buggy 版本 AI 被取消(['ai_start', 'ai_cancelled']),修复版本 AI 正常完成(['ai_start', 'ai_db_update', 'ai_complete'])
  • 静态检查:✅ 源码使用 await do_ai(),无 asyncio.create_task(do_ai())

2026-07-26 — 修复 dal.py JSON_CONTAINS 对无效 JSON 报错 + 缓存中间件忽略查询参数

版本:1.3.1

变更概述

  1. dal.py 的 get_live_news() 中标签筛选使用 JSON_CONTAINS(tags, ...) 但未检查 tags 列是否为有效 JSON,当 tags 为空字符串等无效值时查询报错。添加 JSON_VALID(tags) 前置保护。
  2. newsflash_backend.py 的缓存中间件使用 path 作为缓存 key,忽略了查询参数,导致不同 limit/offset/source 等参数的响应互相覆盖。改为 f"{path}?{query}" 作为缓存 key。

修改文件清单

  • Services/kuaixun-service/app/dal.py — 标签筛选添加 JSON_VALID(tags) 保护
  • Scripts/bin/newsflash_backend.py — 缓存 key 包含查询字符串

2026-07-15 — 实现前端调试模式(死设置复活)

版本:1.3.0

变更概述

「前端调试模式」开关此前只在设置页面保存/读取,前后端代码均未使用,是"死设置"。本次打通 Swift → 前端的调试开关联动,并补全 WebSocket 调试统计。

详细变更

1. Swift 端:URL 携带 debug 参数

  • 修改 Sources/FinDataHub/Services/NewsFlashService.swift
    • openBackend() 方法读取 AppKeys.debugFrontend
    • 开启时 URL 追加 ?debug=1 参数
    • 关闭时使用原始 URL

2. 前端:URL 参数自动开启调试

  • 修改 Resources/kuaixun-frontend/src/App.vue
    • 导入 setDebugEnabled
    • onMounted 中解析 URLSearchParams
    • 检测到 debug=1 时自动调用 setDebugEnabled(true)
    • 调试面板自动显示

3. 前端:补全 WebSocket 调试统计

  • 修改 Resources/kuaixun-frontend/src/composables/useWebSocket.js
    • 导入 recordWsMessage
    • onmessage 回调记录消息计数
    • onerror 回调记录错误计数

修改文件清单

  • Sources/FinDataHub/Services/NewsFlashService.swift
  • Resources/kuaixun-frontend/src/App.vue
  • Resources/kuaixun-frontend/src/composables/useWebSocket.js

验证结果

  • Swift 编译:✅ 零错误
  • 前端构建:✅ 成功(949ms)
  • 调试联动:设置开启后打开前端自动显示调试面板

2026-07-15 — 修复外观设置不生效问题

版本:1.3.0

变更概述

外观设置(主题/强调色/字体大小/减少动画)之前只保存到 UserDefaults 但从未应用到 UI,是"死"设置。本次创建 AppearanceManager 统一管理外观,并在 ContentView 中应用主题和强调色,设置变更后立即生效。

详细变更

1. 新增 AppearanceManager

  • 新增 Sources/FinDataHub/Design/AppearanceManager.swift
    • ObservableObject 单例,@Published 属性驱动 UI 更新
    • colorScheme:将 theme 字符串映射为 ColorScheme?(system=nil/light/dark)
    • accentColor:将颜色名映射为 Color(blue/green/orange/purple)
    • scaledHeadline/subheadline/body/caption/caption2:根据 fontSize 返回缩放字体
    • animation:reduceMotion 为 true 时返回 nil 禁用动画

2. ContentView 应用外观

  • 修改 Sources/FinDataHub/ContentView.swift
    • 注入 @ObservedObject AppearanceManager.shared
    • 添加 .preferredColorScheme(appearance.colorScheme) 应用主题
    • 添加 .tint(appearance.accentColor) 应用强调色

3. SettingsView 同步外观变更

  • 修改 Sources/FinDataHub/Views/SettingsView.swift
    • saveAppearancePrefs() 保存后同步更新 AppearanceManager 的 @Published 属性
    • 主题/强调色变更后立即生效,无需重启

修改文件清单

  • Sources/FinDataHub/Design/AppearanceManager.swift(新增)
  • Sources/FinDataHub/ContentView.swift
  • Sources/FinDataHub/Views/SettingsView.swift

验证结果

  • Swift 编译:✅ 零错误
  • App 打包:✅ 73M
  • 主题切换:✅ 浅色/深色/跟随系统即时生效
  • 强调色:✅ blue/green/orange/purple 即时生效

2026-07-15 — 设置功能全面检查与修复

版本:1.3.0

变更概述

全面检查设置页面所有功能的可用性,修复了 3 个功能缺陷。

发现并修复的问题

  1. runArchive() 缺少数据库环境变量(严重)

    • 归档脚本无法连接数据库,因为只传了 MYSQL_DATABASE,缺少 MYSQL_HOST/PORT/USER/PASSWORD
    • 修复:使用 PythonPathFinder.dbEnv() 统一注入完整数据库环境变量
    • 脚本路径改用 NewsFlashService.shared.findScript() 统一查找
  2. saveNewsFlashAdvancedPrefs() 缺少归档保留天数保存

    • 归档滑块的 onChange 直接写 defaults.set,未走统一保存函数,与代码风格不一致
    • 修复:移入 saveNewsFlashAdvancedPrefs(),UI 端调用统一函数
  3. loadNewsFlashPrefs() 缺少 9 个数据源状态加载

    • 只加载了 4 个核心数据源(eastmoney/10jqka/jin10/gelonghui)
    • 剩余 9 个数据源(21jingji/yicai/futunn/hexun/investing/jrj/jiemian/xuangubao/xueqiu)的开关状态不会从 UserDefaults 恢复,每次进入设置页都显示默认值
    • 修复:补全所有 13 个数据源的加载逻辑

修改文件清单

  • Sources/FinDataHub/Views/SettingsView.swift

验证结果

  • Swift 编译:✅ 零错误
  • App 打包:✅ 73M

各功能区块可用性总览

区块 功能项 状态
通用设置 开机自启、Dock图标、窗口尺寸、默认页面、历史记录 ✅ 正常
外观设置 主题、强调色、字体大小、减少动画 ✅ 正常
同步设置 项目路径、uv路径、同步类型、并行线程、数据源、MySQL配置、检测/初始化、超时/重试 ✅ 正常
通知设置 同步通知、快讯推送(热度/间隔/关键词/黑名单/来源)、失败告警、扩展通知、通知声音、测试通知 ✅ 正常
快讯设置 服务端口、采集间隔、数据源管理(13个)、自动启动、显示设置、数据归档 ✅ 正常(已修复)
AI 设置 模型配置、功能开关、并发控制 ✅ 正常
日志设置 保留天数、每页显示、内存日志、缓冲上限 ✅ 正常
开发者选项 调试模式、日志级别、前端调试、调试面板、快捷操作 ✅ 正常
关于 版本信息、退出应用 ✅ 正常

2026-07-15 — 数据归档:历史数据冷存储

版本:1.3.0

变更概述

实现数据归档功能,将超过指定天数的旧数据从活跃表迁移到归档表(_archive 后缀),减少活跃表体积,提升查询性能。支持设置页面配置保留天数和手动触发归档。

详细变更

1. 归档脚本

  • 新增 Resources/bin/archive_data.py
    • 支持参数:--days(保留天数,默认90)、--dry(试运行)、--table(指定表)
    • 自动创建归档表(CREATE TABLE ... LIKE)
    • 迁移数据:INSERT INTO archive SELECT ... WHERE published_at < cutoff + DELETE
    • 归档后执行 OPTIMIZE TABLE 回收空间
    • 覆盖 13 张表(news_flash + 12 张分表)

2. 设置页面

  • 修改 Sources/FinDataHub/AppConstants.swift
    • 新增 archiveRetentionDays 配置键
  • 修改 Sources/FinDataHub/Views/SettingsView.swift
    • 快讯设置页新增"数据归档"区块
    • 保留天数滑块(30-365天,默认90)
    • "立即归档"按钮(后台异步执行,带进度指示和结果反馈)

修改文件清单

  • Resources/bin/archive_data.py(新增)
  • Sources/FinDataHub/AppConstants.swift
  • Sources/FinDataHub/Views/SettingsView.swift

验证结果

  • 试运行:可归档 237,314 条(news_hotlist 237,294 + news_flash 10 + news_36kr 10)
  • Swift 编译:✅ 零错误
  • App 打包:✅ 73M

2026-07-15 — 智能推送:关键词白/黑名单 + 来源过滤

版本:1.3.0

变更概述

为快讯推送添加智能过滤功能,用户可配置关键词白名单(包含任一才推送)、关键词黑名单(包含任一不推送)、来源过滤(仅推送指定来源),避免无关快讯打扰。

详细变更

1. 配置键

  • 修改 Sources/FinDataHub/AppConstants.swift
    • 新增 nfNotifyKeywords(关键词白名单,逗号分隔)
    • 新增 nfNotifyExcludeKeywords(关键词黑名单,逗号分隔)
    • 新增 nfNotifySources(来源过滤,逗号分隔,空=全部)

2. 过滤逻辑

  • 修改 Sources/FinDataHub/Services/NewsFlashService.swift
    • 新增 applySmartFilters() 方法,在 checkAndNotifyNewFlash() 中调用
    • 来源过滤:匹配 source_code 或 source_name
    • 关键词白名单:标题+内容+摘要中包含任一关键词才推送
    • 关键词黑名单:包含任一黑名单关键词则排除
    • 所有匹配不区分大小写

3. 设置页面

  • 修改 Sources/FinDataHub/Views/SettingsView.swift
    • 快讯推送分组下新增三个文本输入框
    • 自动保存(onChange 即时写入 UserDefaults)

修改文件清单

  • Sources/FinDataHub/AppConstants.swift
  • Sources/FinDataHub/Services/NewsFlashService.swift
  • Sources/FinDataHub/Views/SettingsView.swift

验证结果

  • Swift 编译:✅ 零错误
  • App 打包:✅ 72M

2026-07-15 — 数据库索引优化:复合索引 + 查询重写消除函数包裹

版本:1.3.0

变更概述

解决分析 API 和当日总结查询的性能瓶颈:DATE(FROM_UNIXTIME(published_at/1000)) 函数包裹导致 idx_published 索引失效,全表扫描。通过添加复合索引 + 重写 WHERE 条件为范围查询,今日数据查询性能提升约 135 倍。

详细变更

1. 新增复合索引

  • 新增 Resources/bin/optimize_indexes.py 索引优化脚本
    • news_flash 主表:idx_importance_pushed (importance, is_pushed, is_duplicate) + idx_published_importance (published_at, importance)
    • 12 张分表:idx_imp_dup (importance, is_dup) + idx_imp_pushed_dup (importance, is_pushed, is_dup)
  • 修改 Resources/bin/init_db.py 建表语句同步新增索引

2. 查询重写:消除函数包裹

  • 修改 Resources/bin/newsflash_backend.py(6 处)
    • DATE(FROM_UNIXTIME(published_at/1000)) = CURDATE() → published_at >= UNIX_TIMESTAMP(CURDATE()) * 1000
    • DATE(FROM_UNIXTIME(published_at/1000)) >= DATE_SUB(...) → published_at >= UNIX_TIMESTAMP(DATE_SUB(...)) * 1000
  • 修改 Resources/bin/daily_summary.py(5 处)
    • DATE(FROM_UNIXTIME(published_at/1000)) = %s → published_at >= UNIX_TIMESTAMP(%s) * 1000 AND published_at < UNIX_TIMESTAMP(DATE_ADD(%s, INTERVAL 1 DAY)) * 1000
  • 修改 Resources/bin/newsflash.py(1 处)
    • 今日条数查询同上改写

修改文件清单

  • Resources/bin/optimize_indexes.py(新增)
  • Resources/bin/init_db.py
  • Resources/bin/newsflash_backend.py
  • Resources/bin/daily_summary.py
  • Resources/bin/newsflash.py

验证结果

  • EXPLAIN 对比:旧查询扫描 11099 行(cost=4783),新查询扫描 82 行(cost=35.6)
  • 结果一致性:旧计数 82 = 新计数 82 ✅
  • 索引添加:news_flash 13 个索引,12 张分表各新增 2 个索引 ✅
  • App 打包:✅ 72M

2026-07-15 — WebSocket 实时推送替代轮询

版本:1.3.0

变更概述

实现 WebSocket 实时推送,采集器写入新数据后主动通知后端,后端通过 WebSocket 广播给前端,前端收到通知后拉取最新数据。连接 WebSocket 时降低轮询频率(5s→60s),断开时恢复,减少无效请求。

详细变更

1. 后端:WebSocket 支持

  • 安装 websockets + uvicorn[standard] 到 Resources/kuaixun-service/.venv
  • WebSocket 路由 /ws/live 已存在,支持连接/断开/ping-pong
  • WSManager 已实现 broadcast() 方法,可向所有连接的客户端推送消息

2. 后端:内部通知接口

  • 修改 Resources/kuaixun-service/app/routers/internal.py
    • 新增 POST /api/v1/news/notify-internal 接口(仅限本地调用,无需鉴权)
    • 采集器写入数据后调用此接口,触发 WebSocket 广播

3. 采集器:采集完成通知后端

  • 修改 Resources/bin/newsflash_collect.py
    • collect_once() 完成后,如有新增数据则调用 _notify_backend()
    • 使用 urllib.request 发送 HTTP POST 通知,超时5秒,失败静默(不阻塞采集)

4. 前端:WebSocket 集成

  • 修改 Resources/kuaixun-frontend/src/composables/useWebSocket.js

    • 修复 WebSocket URL:/kuaixun/ws/live → /ws/live
    • 收到 new_news 消息时调用 store.fetchNew() 拉取最新数据
    • 自动重连机制(指数退避,最大30秒)
  • 修改 Resources/kuaixun-frontend/src/views/LiveFeed.vue

    • WebSocket 连接时轮询间隔从 5s 降为 60s
    • WebSocket 断开时恢复为 5s 轮询
    • 通过 watch(wsConnected) 动态切换轮询频率

修改文件清单

  • Resources/kuaixun-service/.venv(安装 websockets、uvicorn[standard])
  • Resources/kuaixun-service/app/routers/internal.py
  • Resources/bin/newsflash_collect.py
  • Resources/kuaixun-frontend/src/composables/useWebSocket.js
  • Resources/kuaixun-frontend/src/views/LiveFeed.vue

验证结果

  • WebSocket 连接:✅ 101 Switching Protocols
  • 服务端推送:✅ 收到 {"type":"status","connections":1}
  • 通知接口:✅ {"ok":true}
  • 前端构建:✅ 零错误
  • App 打包:✅ 72M

2026-07-14 — 采集器稳定性改进:超时控制、连接重连、看门狗自动重启

版本:1.3.0

变更概述

为解决采集器卡死(进程运行但无数据写入)问题,实现三项稳定性改进:

  1. 超时控制:单次采集总超时 + 单个数据源超时
  2. 数据库连接重连:连接池增加自动重试机制
  3. 看门狗机制:连续失败自动重启采集器

详细变更

1. 超时控制

  • 修改 Resources/bin/newsflash_collect.py

    • 添加配置:COLLECT_TIMEOUT = 120(单次采集总超时)、SOURCE_TIMEOUT = 30(单个数据源超时)
    • 主循环使用 asyncio.wait_for() 包装 collect_once(),避免采集卡住
  • 修改 Resources/kuaixun-service/extend/fetchers/manager.py

    • FetcherManager 新增 source_timeout 参数(默认30秒)
    • 新增 _fetch_with_timeout() 方法,单个数据源超时时跳过并记录日志,不影响其他数据源

2. 数据库连接池重连

  • 修改 Resources/bin/db_pool.py
    • get_conn() 增加 max_retries=3 和 retry_delay=2 参数
    • 连接失败时自动重试3次,间隔2秒
    • 连接无效时自动重建,而非直接抛出异常

3. 看门狗机制

  • 修改 Resources/bin/newsflash_collect.py
    • 添加配置:WATCHDOG_MAX_FAILURES = 5(连续失败阈值)、WATCHDOG_RESTART_DELAY = 30(重启延迟)
    • 连续5次采集超时或异常时自动触发重启
    • 通过 os.execvp() 重新执行进程,确保环境完全重置

修改文件清单

  • Resources/bin/newsflash_collect.py
  • Resources/bin/db_pool.py
  • Resources/kuaixun-service/extend/fetchers/manager.py

验证结果

  • 单次采集测试:✅ 正常获取数据(新增10条)
  • 后台采集器运行:✅ running: true, stuck: false
  • 数据持续写入:✅ 今日11,811条,最新写入正常

2026-07-13 — 修复快讯前端访问失败:全量修复 /usr/bin/env 缺少 -S 参数

版本:1.3.0

变更概述

快讯前端访问失败的根因:App 中所有通过 /usr/bin/env 启动 Python/mysql 子进程的地方均缺少 -S 参数,导致 macOS env 无法正确拆分多个参数,后端和采集器进程启动失败。

详细变更

修复以下 6 个文件中所有 /usr/bin/env 调用,统一添加 -S 参数:

  1. Sources/FinDataHub/Services/NewsFlashService.swift

    • runDetachedProcess() 第 497 行:process.arguments = ["-S", executable] + args
    • (startCollector() 已在上一轮修复中添加 -S)
  2. Sources/FinDataHub/ContentView.swift

    • 第 54 行:process.arguments = ["-S", python, script]
  3. Sources/FinDataHub/Views/DatabaseSetupGuideView.swift

    • 第 698 行、第 758 行:process.arguments = ["-S", python, script]
  4. Sources/FinDataHub/Views/SettingsView.swift

    • 第 1878 行:process.arguments = ["-S", "mysql", ...]
    • 第 1943 行、第 2002 行:process.arguments = ["-S", python, script]

根因分析

macOS 的 /usr/bin/env 默认不会拆分 shebang 后的多个参数。当 process.arguments = [python, script, "backend-start"] 时,env 会尝试查找名为 python script backend-start 的单一命令,导致 No such file or directory 错误,进程启动失败。添加 -S 参数后 env 会正确拆分参数并执行。

修改文件清单

  • Sources/FinDataHub/Services/NewsFlashService.swift
  • Sources/FinDataHub/ContentView.swift
  • Sources/FinDataHub/Views/DatabaseSetupGuideView.swift
  • Sources/FinDataHub/Views/SettingsView.swift

编译状态

  • Swift release 构建:✅ 零错误
  • App 打包:✅ 65M,ad-hoc 签名
  • 输出位置:.build/FinDataHub.app

2026-07-13 — 修复采集器"启动后立即退出"与状态检查时区异常

版本:1.3.0

变更概述

修复快讯中心提示"加载失败:采集器启动后立即退出"的问题,并修复采集器状态检查中的时区比较异常。

详细变更

1. 修复 App 启动采集器进程失败

  • 修改 Sources/FinDataHub/Services/NewsFlashService.swift
    • startCollector() 中 Process.arguments 增加 -S 参数:["-S", python, script, "start", "--interval", ...]
    • 原因:macOS /usr/bin/env 默认不会拆分 shebang 后的多个参数,缺少 -S 时无法正确启动 Python 解释器执行脚本
    • standardOutput 从 FileHandle(forWritingAtPath: "/dev/null") 改为 FileHandle.nullDevice,避免无效文件句柄

2. 修复采集器状态检查时区异常

  • 修改 Resources/bin/newsflash_collect.py
    • get_status() 中统一使用 naive datetime(本地时间)比较 started_at 和 last_write_time
    • 原因:started_at 由 PID 文件写入的本地 ISO 时间生成,last_write_time 来自 MySQL 本地时间字段,原代码一方使用 UTC、一方 naive 导致 can't subtract offset-naive and offset-aware datetimes

3. 新增历史数据补采工具

  • 新建 Resources/bin/backfill.py
    • 支持 --hours N 和 --date YYYY-MM-DD 两种补采模式
    • 支持分页补采:cls、eastmoney、10jqka、21jingji、yicai、hexun
    • 自动去重、写入 news_flash 主表和各数据源分表

修改文件清单

  • Sources/FinDataHub/Services/NewsFlashService.swift
  • Resources/bin/newsflash_collect.py
  • Resources/bin/backfill.py(新建)

编译状态

  • Python 语法检查:✅ 零错误
  • Swift release 构建:✅ 零错误(仅既有 warning,无新增)
  • App 打包:✅ 65M,ad-hoc 签名
  • 输出位置:/Users/mantianfuyun/Projects/项目/FinDataHub/.build/FinDataHub.app

2026-07-13 — 代码质量修复:锐评问题全量处理

版本:1.3.0

变更概述

针对项目锐评中发现的所有代码质量问题进行修复,包括:硬编码密码、Python 路径重复代码、端口不一致、Python 直读 Swift plist 耦合、吞异常、build 脚本硬编码路径、Python 依赖无版本锁定。

详细变更

1. 移除所有硬编码密码 ***(9处)

  • 修改 Resources/bin/common.py — MYSQL_CFG/MYSQL_CFG_AKSHARE/MYSQL_PWD 默认值改空
  • 修改 Resources/bin/check_db.py、Resources/bin/init_db.py — MYSQL_PASSWORD 默认值改空
  • 修改 Resources/bin/manage_deps.py — 移除密码猜测循环,从环境变量读取
  • 修改 Sources/FinDataHub/Services/NewsFlashService.swift — Keychain 回退值改空

2. 抽取 PythonPathFinder 统一工具类(消除6处重复)

  • 新建 Sources/FinDataHub/Services/PythonPathFinder.swift
    • findPython() / findPython(for:) / findScript() / dbEnv() 统一入口
  • 更新 NewsFlashService.swift、InsightService.swift、DepManager.swift、EnvChecker.swift、PythonProcessManager.swift 调用方
  • 新增 DataSource.projectName 属性(SyncLogEntry.swift)

3. 修复端口不一致(vite 8900 vs 后端 8100)

  • 修改 Resources/kuaixun-frontend/vite.config.js — 代理端口 8900→8100
  • 修改 Resources/kuaixun-frontend/.env、.env.development — WS 端口 8900→8100

4. 修复 .gitignore 忽略 Tests/ 目录

  • 修改 .gitignore — 移除 Tests/ 忽略行

5. 修复 Python 直读 Swift plist 的耦合问题

  • 修改 Sources/FinDataHub/Services/NewsFlashService.swift
    • 新增 rssToggleEnvKeys 映射表(13 个 RSS 数据源开关)
    • startCollector() 通过 FDH_RSS_* 环境变量注入开关状态
  • 修改 Resources/bin/newsflash_collect.py
    • _register_rss_fetchers() 环境变量优先、plist 回退读取开关
    • 新增 _toggle() 辅助函数统一读取逻辑

6. 修复吞异常问题

  • 修改 Resources/bin/newsflash_collect.py — 3 处 except: pass 改为 print 错误日志(DB 写入失败、状态检查失败)
  • 修改 Resources/kuaixun-frontend/src/stores/news.js — 9 处 catch (_e) { // 静默失败 } 改为 console.warn 日志

7. 修复 build-app.sh 硬编码路径

  • 修改 build-app.sh — AKSHARE_SRC/TUSHARE_SRC 改为环境变量可覆盖,新增目录存在性校验
  • 修改 bundle-python.sh — AKSHARE_SRC 改为环境变量可覆盖

8. 锁定 Python 依赖版本

  • 修改 bundle-python.sh — 从手动 pip install 无版本包列表改为 pip install -e 读取项目 pyproject.toml 的版本约束

修改文件清单

  • Sources/FinDataHub/Services/PythonPathFinder.swift(新建)
  • Sources/FinDataHub/Services/NewsFlashService.swift
  • Sources/FinDataHub/Services/InsightService.swift
  • Sources/FinDataHub/Services/DepManager.swift
  • Sources/FinDataHub/Services/EnvChecker.swift
  • Sources/FinDataHub/Services/PythonProcessManager.swift
  • Sources/FinDataHub/Models/SyncLogEntry.swift
  • Resources/bin/common.py
  • Resources/bin/check_db.py
  • Resources/bin/init_db.py
  • Resources/bin/manage_deps.py
  • Resources/bin/newsflash_collect.py
  • Resources/kuaixun-frontend/vite.config.js
  • Resources/kuaixun-frontend/.env
  • Resources/kuaixun-frontend/.env.development
  • Resources/kuaixun-frontend/src/stores/news.js
  • .gitignore
  • build-app.sh
  • bundle-python.sh

编译状态

  • Python 语法检查:✅ 零错误
  • 前端构建(vite):✅ 零错误
  • Swift release 构建:✅ 零错误(3 处既有 warning,无新增)
  • App 打包:✅ 63M,ad-hoc 签名

2026-07-12 — P0 修复:环境检查 + 数据概览解析失败

版本:1.2.4

变更概述

修复三个问题:Python 版本检查未实际验证版本号,uv 检查未优先查找 app 内打包的 venv,以及数据概览使用 mysql 命令行解析导致解析失败。

详细变更

1. 修复 Python 版本检查

  • 修改 Sources/FinDataHub/Services/EnvChecker.swift
    • checkPython() 新增版本号验证逻辑
    • 新增 isVersionAtLeast() 辅助方法,使用正则解析版本号
    • 只有主版本 >= 3 且次版本 >= 10 才标记为通过
    • 版本过低时显示"版本过低,需要 3.10+"

2. 修复 uv 检查(优先查找 app 内 venv)

  • 修改 Sources/FinDataHub/Services/EnvChecker.swift
    • 重写 checkUv() 方法,按优先级逐个检查 uv 路径
    • 新增 findUvPaths() 方法,返回按优先级排序的 uv 路径列表
    • 优先级:app 内 kuaixun-service venv → akshare_fetcher 项目 venv → homebrew → 系统路径
    • 找到后显示完整路径和版本号

3. 修复数据概览解析失败

  • 修改 Resources/bin/insights.py
    • 从 mysql 命令行 + tab 分隔格式改为 pymysql + DictCursor
    • 解决命令行输出格式不稳定导致的 JSON 解析失败问题
    • 日期解析支持 datetime 对象和字符串两种格式
    • 每个表查询失败时静默跳过,不影响整体结果

构建验证

  • Swift 编译 ✅ 零错误
  • Python 脚本测试 ✅(正常返回 JSON)
  • App 打包 ✅(63MB)
  • 代码签名 ✅(ad-hoc)

2026-07-12 — P0 优化:退出功能增强(菜单栏右键 + 确认对话框 + 资源清理)

版本:1.2.2

变更概述

全面增强应用退出功能:新增菜单栏右键退出入口,添加退出确认对话框防止误触,统一资源清理逻辑确保后台服务正确停止。

详细变更

1. 新增 AppQuitService 退出服务

  • 新增 Sources/FinDataHub/Services/AppQuitService.swift
    • 统一管理退出逻辑:requestQuit() 弹出确认框,performQuit() 执行退出
    • 使用 NSAlert 显示确认对话框(兼容 MenuBarExtra window 样式)
    • 完善资源清理流程:停止通知轮询 → 停止采集器 → 停止后端 → 停止定时任务
    • 清理完成后延迟 800ms 再退出,确保进程完全终止

2. 菜单栏右键退出选项

  • 修改 Sources/FinDataHub/FinDataHubApp.swift
    • 给菜单栏图标添加 .contextMenu 右键菜单
    • 包含"退出 FinDataHub"选项,带电源图标
    • 支持快捷键 Cmd+Q

3. SettingsView 退出按钮优化

  • 修改 Sources/FinDataHub/Views/SettingsView.swift
    • 移除旧的 quitApp() 方法
    • 改用 AppQuitService.shared.requestQuit() 统一退出入口
    • 保持红色按钮样式不变

构建验证

  • Swift 编译 ✅ 零错误
  • App 打包 ✅(63MB)
  • 代码签名 ✅(ad-hoc)

2026-07-12 — P0 重构:快讯功能完整自包含打包(迁移可用)

变更概述

将快讯采集相关的所有依赖(kuaixun-service extend 模块 + Python 虚拟环境)完整打包进 App,不再依赖外部项目路径,确保迁移后能完美使用。同时修复采集器启动错误不可见问题,添加 stderr 捕获和错误展示。

详细变更

1. 打包 kuaixun-service extend 模块

  • 新增 Resources/kuaixun-service/extend/ 目录
  • 包含所有采集器实现(财联社、华尔街见闻、金十、东方财富等)
  • 包含 AI 模块(scorer/summarizer/tagger/content_enricher)
  • 包含去重、日志、通知等辅助模块

2. 打包 Python 虚拟环境

  • 新增 Resources/kuaixun-service/.venv/(~50MB)
  • 包含 pymysql、httpx、fastapi、sqlalchemy 等所有依赖
  • Python 版本 3.14

3. 路径解析优化(优先使用 app 内资源)

  • 修改 Resources/bin/common.py
    • 新增 _resolve_bundled_paths():检测 app 内打包的 kuaixun-service 和 venv
    • 修改 setup_kuaixun_paths():优先使用 app 内资源,找不到回退到外部路径
    • 支持动态探测 Python 版本的 site-packages 路径

4. Swift 端 Python 路径查找优化

  • 修改 Sources/FinDataHub/Services/NewsFlashService.swift
    • computePythonPath() 优先级调整:app 内 kuaixun-service venv → 外部 kuaixun-old venv → akshare_fetcher venv → 系统 python3

5. 采集器启动错误诊断

  • 修改 Sources/FinDataHub/Services/NewsFlashService.swift
    • startCollector() 添加 stderr 管道捕获 Python 错误输出
    • try? 改为 do/catch 捕获启动异常
    • 等待 2 秒后检查进程是否立即退出
    • 进程退出时读取 stderr 并显示错误信息给用户

6. 构建脚本更新

  • 修改 build-app.sh
    • 新增步骤 5:打包 kuaixun-service(extend + .venv)
    • 步骤编号从 [1/4] → [1/7] 重新编排
    • 使用 rsync 同步,排除 pycache 和 .pyc

7. ContentView 窗口高度修复

  • 修改 Sources/FinDataHub/ContentView.swift
    • 移除 maxHeight: .infinity,改为固定窗口尺寸
    • 新增 windowWidth / windowHeight 计算属性,读取用户配置
    • 默认 620×600,用户可在设置中调整

构建验证

  • Swift 编译 ✅ 零错误
  • App 打包 ✅(63MB)
  • 代码签名 ✅(ad-hoc)
  • 采集器启动测试 ✅
  • 数据查询测试 ✅
  • 模块导入测试 ✅

2026-07-12 — P1 修复:多项设置功能失效修复

变更概述

修复 5 项有 UI 开关但功能未实际接上的设置项,包括采集间隔、自动刷新、Top 显示数、内存日志条数、同步超时时间。构建打包验证通过。

详细变更

1. 自动刷新快讯 + 刷新间隔

  • 修改 Sources/FinDataHub/Views/NewsFlashView.swift
    • 添加 nfAutoRefresh / nfAutoRefreshInterval 状态变量
    • 添加 loadAutoRefreshConfig():读取用户配置
    • 添加 setupAutoRefreshTimer() / stopAutoRefreshTimer():基于配置的定时刷新
    • 添加 onAppear/onDisappear/onChange 生命周期处理

2. Top 显示数动态配置

  • 修改 Sources/FinDataHub/Views/NewsFlashView.swift
    • 添加 topDisplayCount 计算属性:读取 nfTopDisplayCount 配置
    • 修改高热度展示区域:使用 Array(hot.prefix(topDisplayCount)) 替代硬编码 5
    • 方法重命名:hotTop5Section → hotTopSection

3. 内存日志条数配置

  • 修改 Sources/FinDataHub/Services/PythonProcessManager.swift
    • 添加 maxMemoryLogEntries 计算属性:读取 logMemoryEntries 配置
    • 修改 trimLogLines():使用配置值替代硬编码 500
    • 修改 addLog():使用配置值限制 logEntries 数量

4. 同步超时时间

  • 修改 Sources/FinDataHub/Services/PythonProcessManager.swift
    • 添加 syncTimeoutMinutes 计算属性:读取 syncTimeoutMinutes 配置
    • 添加 timeoutTimer 定时器:进程启动后按配置时间触发超时
    • 超时触发时记录错误日志并调用 cancelSync() 终止进程
    • 在 handleProcessExit() 和 reset() 中清理定时器

5. 采集间隔参数传递(此前已修复)

  • 修改 Sources/FinDataHub/Services/NewsFlashService.swift
    • startCollector() 读取 nfCollectInterval 配置
    • 启动命令添加 --interval 参数

构建验证

  • 前端 npm run build ✅
  • 后端 py_compile ✅
  • 应用 ./build-app.sh ✅ 零错误

2026-07-11 — P2 优化:后端优雅关闭/日志 + 前端骨架屏 + App 重复轮询修复

变更概述

为 FastAPI 后端添加 lifespan 优雅关闭和请求日志中间件,前端添加骨架屏加载状态组件并修复 App.vue 中残留的重复轮询定时器。构建验证通过。

详细变更

1. FastAPI 优雅关闭 + 结构化日志

  • 修改 Resources/bin/newsflash_backend.py
    • 添加 lifespan 上下文管理器:启动时记录端口日志,关闭时清空响应缓存
    • 添加 RequestLogMiddleware:API 请求记录方法和耗时,>3s 慢请求告警
    • 添加 logging.basicConfig 结构化日志格式

2. App.vue 重复轮询修复

  • 修改 Resources/kuaixun-frontend/src/App.vue
    • 移除 pollTimer = setInterval(() => store.fetchNew(), 5000)(与 LiveFeed.vue 重复)
    • 移除 useNewsStore 导入(不再需要)
    • 保留 WebSocket connect/disconnect(单例模式保证仅一个连接)

3. 骨架屏加载组件

  • 新建 Resources/kuaixun-frontend/src/components/SkeletonCard.vue
    • 脉冲动画骨架卡片,替代原来的"加载中..."文本
  • 修改 Resources/kuaixun-frontend/src/views/LiveFeed.vue
    • 首次加载时显示 5 个 SkeletonCard,而非 Loader + 文字

构建验证

  • 前端 npm run build ✅ 908ms
  • 后端 py_compile ✅ 语法通过

2026-07-11 — P1 优化:统一配置管理 + 提取公共模块

变更概述

新建 common.py 公共模块,将 9 个文件中重复的数据库配置、5 个文件中重复的路径设置、2 个文件中完全相同的 _load_env 函数、2 个文件中几乎相同的 PID 管理逻辑统一提取。消除全部硬编码重复,构建验证通过。

详细变更

1. 新建 Resources/bin/common.py

  • MYSQL_CFG / MYSQL_CFG_AKSHARE:统一数据库连接配置(原先 9 个文件各自硬编码)
  • MYSQL_CMD / DB_NEWS / DB_AKSHARE:命令行 fallback 常量
  • database_url():构造 SQLAlchemy DATABASE_URL
  • BACKEND_PORT / OLLAMA_URL / RSS_HUB_URL:端口和服务地址常量
  • KX_HOME / KX_VENV / KXS_HOME / BIN_DIR:路径常量
  • setup_kuaixun_paths():统一 sys.path 设置(原先 5 个文件重复)
  • load_env():.env 加载函数(原先 2 个文件完全重复定义 _load_env)
  • PidManager 类:PID 文件管理(原先 2 个文件各自实现 write/status/stop)

2. 修改 12 个文件导入 common

  • db_pool.py:MYSQL_CFG 改为从 common 导入
  • newsflash_collect.py:路径设置 + _load_env + PID 管理 + BACKEND_PORT + DATABASE_URL 全部改用 common
  • daily_summary.py:路径设置 + PID 管理 + OLLAMA_URL 改用 common
  • backfill_ai.py:路径设置 + _load_env + MYSQL_CFG 改用 common
  • backfill_history.py:路径设置 + MYSQL_CFG 改用 common
  • backfill_tags.py:路径设置 + MYSQL_CFG 改用 common
  • newsflash.py:MYSQL_CFG + MYSQL_CMD + DB 改用 common
  • newsflash_backend.py:路径设置 + BACKEND_PORT 改用 common
  • init_new_sources.py / init_rss_sources.py:MYSQL_CFG 改用 common
  • rss_fetcher.py:RSS_HUB_URL 改用 common

3. 更新 build-app.sh

  • 新增 common.py 复制步骤
  • 删除重复的 db_pool.py 复制行

构建验证

  • 12 个 Python 文件 py_compile ✅ 全部通过

2026-07-11 — 量化空壳删除 + P0 修复 3 项(SQL 注入 / WebSocket 单例 / AI 写入竞态)

变更概述

删除量化投研空壳模块(前端目录 + 后端脚本 + Swift 引用),修复 3 个 P0 级问题:AI 写入竞态(INSERT IGNORE → UPDATE)、双重 WebSocket 连接(单例化)、newsflash.py SQL 注入(参数化查询)。构建验证通过。

详细变更

1. 删除量化投研空壳

  • 删除 Resources/quant-frontend/ 目录(硬编码 mock 数据,无真实功能)
  • 删除 Resources/bin/quant_backend.py(空壳后端)
  • 清理 Swift 中量化相关代码:NewsFlashService.swift 中删除 isQuantRunning、quantPort、startQuant、stopQuant、openQuant 及状态监控逻辑
  • 清理 LiveFeed.vue 量化控制栏
  • 更新 build-app.sh,删除量化资源复制步骤

2. 修复 P0-1: AI 写入竞态 bug

  • 修改 Resources/bin/newsflash_collect.py
    • 新增 update_ai_fields() 函数,AI 处理完成后用 UPDATE 而非 INSERT IGNORE 写入
    • 原因:主流程先用 INSERT IGNORE 写原始数据,AI 完成后 INSERT IGNORE 导致 AI 字段无法更新

3. 修复 P0-2: 双重 WebSocket + 双重轮询

  • 修改 Resources/kuaixun-frontend/src/composables/useWebSocket.js
    • 改为模块级单例模式,引用计数管理连接生命周期
    • 确保全局仅一个 WebSocket 连接,避免 App.vue 和 LiveFeed.vue 各建一个
  • 修改 Resources/kuaixun-frontend/src/composables/useCollectorStatus.js
    • 从原生 fetch 改用项目 axios 实例,统一走拦截器

4. 修复 P0-3: newsflash.py SQL 注入

  • 修改 Resources/bin/newsflash.py
    • run_sql() 新增 params 参数支持参数化查询
    • main() 中 source/keyword/hours/limit/offset 全部改为 %s 参数绑定
    • fallback(mysql 命令行)模式下做转义处理

构建验证

  • 前端:npm run build ✅ 885ms,0 错误
  • 后端:py_compile ✅ 语法通过

2026-07-11 — 前后端性能优化(6 项,基于 Context7 最佳实践)

变更概述

通过 Context7 MCP 查询最新库文档,识别并实施了 6 项性能优化,覆盖前端(Axios/Vite/Pinia/虚拟滚动)和后端(MySQL 连接池/FastAPI 响应缓存)。前端构建零错误,后端语法验证通过。

详细变更

优化1: Axios 拦截器(统一错误处理 + 自动重试 + 慢请求告警)

  • 修改 Resources/kuaixun-frontend/src/api/index.js
    • 请求拦截器:记录请求起始时间
    • 响应拦截器:>3s 慢请求 console.warn 告警
    • 5xx 错误自动重试(最多 2 次),超时/网络错误单独处理
    • 4xx 错误统一 console.error 日志

优化2: Vite 构建分包(manualChunks)

  • 修改 Resources/kuaixun-frontend/vite.config.js
    • vue-vendor: vue + vue-router + pinia
    • axios: 独立 chunk
    • lucide: 图标库独立 chunk
  • 修改 Resources/quant-frontend/vite.config.ts
    • vue-vendor: vue + vue-router
    • chart: chart.js 独立 chunk
    • three: three.js 独立 chunk
  • 效果:vendor chunk 独立缓存,业务代码更新不触发 vendor 重新下载

优化3: MySQL 连接池(无需额外依赖)

  • 新增 Resources/bin/db_pool.py
    • 基于 Python 内置 queue.Queue + pymysql 实现轻量连接池
    • 惰性创建连接,ping(reconnect=True) 自动重连
    • DBContext 上下文管理器,自动获取/归还连接
    • 池大小 8,无需 DBUtils 等第三方库
  • 修改 Resources/bin/newsflash_collect.py — 4 处 pymysql.connect() → get_conn() / put_conn()
  • 修改 Resources/bin/newsflash.py — 1 处替换
  • 修改 Resources/bin/daily_summary.py — 4 处替换

优化4: Pinia 筛选状态持久化到 localStorage

  • 修改 Resources/kuaixun-frontend/src/stores/news.js
    • 筛选条件(来源、时间范围、标签、重要性)从 localStorage 恢复
    • fetchLive()、setTagFilter()、setImportanceFilter() 中调用 _persistFilters() 写入
    • 刷新页面后自动恢复上次的筛选状态

优化5: 虚拟滚动(vue-virtual-scroller DynamicScroller)

  • 安装 vue-virtual-scroller@2.0.0-beta.8
  • 修改 Resources/kuaixun-frontend/src/main.js — 导入 CSS
  • 修改 Resources/kuaixun-frontend/src/views/LiveFeed.vue
    • 页面结构重构:.live-feed → flex column(height: 100vh)
    • .feed-header:固定头部(搜索+筛选+标签+当日总结),max-height: 55vh 可滚动
    • .feed-body:虚拟滚动列表区域,flex: 1
    • DynamicScroller + DynamicScrollerItem 替换 v-for 列表
    • key-field="_uid"(source_code:id 复合键,避免跨表 id 冲突)
    • :size-dependencies="[isExpanded(item)]" — 展开折叠时自动重测高度
    • #after slot 放置「加载更多」指示器
    • @scroll 事件驱动无限加载(距底 200px 时触发 loadMore())
    • 移除 IntersectionObserver 和 window scroll 监听
    • scrollToTop() 改用 scroller.scrollToItem(0)
    • 搜索结果独立滚动区 .search-scroll-area
    • DynamicScroller 主题适配(透明背景、6px 滚动条、8px item 间距)

优化6: FastAPI 响应缓存中间件

  • 修改 Resources/bin/newsflash_backend.py
    • 新增 ResponseCacheMiddleware(继承 BaseHTTPMiddleware)
    • 对 5 个慢变更 GET 接口做 TTL 缓存:
      • /api/v1/news/tags — 300s(标签列表)
      • /api/v1/news/heat — 60s(热度分布)
      • /api/v1/news/stats — 30s(统计数据)
      • /api/v1/news/summary — 300s(当日总结)
      • /api/v1/sources — 600s(来源列表)
    • 仅缓存 200 响应,过期自动清除
    • 减少重复 DB 查询,提升高频轮询场景响应速度

文件变更

FinDataHub/
├── Resources/
│   ├── bin/
│   │   ├── db_pool.py                      # [新增] MySQL 连接池
│   │   ├── newsflash_backend.py             # [修改] 响应缓存中间件
│   │   ├── newsflash_collect.py             # [修改] 使用连接池
│   │   ├── newsflash.py                     # [修改] 使用连接池
│   │   └── daily_summary.py                 # [修改] 使用连接池
│   ├── kuaixun-frontend/
│   │   ├── src/
│   │   │   ├── main.js                      # [修改] 导入虚拟滚动 CSS
│   │   │   ├── api/index.js                 # [修改] Axios 拦截器
│   │   │   ├── stores/news.js               # [修改] 筛选状态持久化
│   │   │   └── views/LiveFeed.vue           # [修改] 虚拟滚动重构
│   │   ├── vite.config.js                   # [修改] 分包优化
│   │   └── package.json                     # [修改] 新增 vue-virtual-scroller
│   └── quant-frontend/
│       └── vite.config.ts                   # [修改] 分包优化
└── .mcp.json                                # [新增] Context7 MCP 配置

编译状态

  • 前端构建零错误(vite build 成功,1862 模块转换)
  • 后端 Python 语法验证通过(py_compile 零错误)

2026-07-11 — OmniData 集成(新增 9 个财经快讯数据源)

变更概述

深度集成 OmniData 开源项目(https://github.com/noimank/OmniData),将其 9 个财经快讯数据源移植到 FinDataHub 架构中。其中 7 个数据源可正常工作,2 个因环境限制暂时禁用(雪球需 Cookie、英为财情被 Cloudflare 拦截)。

详细变更

1. 后端采集器实现

  • 新增 Resources/bin/omnidata_sources_fetcher.py
    • 9 个 Fetcher 类,全部基于 httpx 异步请求实现
    • 21财经(Jingji21Fetcher):api.21jingji.com/timestream/getListweb
    • 第一财经(YicaiFetcher):www.yicai.com/api/ajax/getlives
    • 富途牛牛(FutunnFetcher):news.futunn.com/main/live-list
    • 和讯网(HexunFetcher):flash.hexun.com/flashAction.do
    • 英为财情(InvestingFetcher):www.investing.com/rss/news_14.rss(Cloudflare 拦截,暂不可用)
    • 金融界(JrjFetcher):gateway.jrj.com/jrj-news/news/queryNewsFlash(需 POST JSON)
    • 界面新闻(JiemianFetcher):a.jiemian.com/wapi.php/News/liveList
    • 选股宝(XuangubaoFetcher):xuangubao.cn/api/live/list
    • 雪球(XueqiuFetcher):xueqiu.com/statuses/livenews/list.json(需 Cookie,暂不可用)
    • 全部支持重要度评分、标签提取、时间解析等功能

2. 数据库初始化

  • 新建 9 张 per-source 表:news_21jingji、news_yicai、news_futunn、news_hexun、news_investing、news_jrj、news_jiemian、news_xuangubao、news_xueqiu
  • news_sources 表新增 9 条记录(investing 和 xueqiu 默认 is_active=0)

3. 注册逻辑更新

  • 修改 Resources/bin/newsflash_collect.py
    • SOURCE_TABLE 新增 9 个数据源映射
    • _register_rss_fetchers() 新增 9 个开关控制(读取 UserDefaults plist)
    • 默认禁用 36kr、investing、xueqiu

4. 前端设置界面更新

  • 修改 Sources/FinDataHub/AppConstants.swift
    • 新增 9 个 OmniData 数据源配置键
  • 修改 Sources/FinDataHub/Views/SettingsView.swift
    • 数据源管理分为「核心数据源」和「更多数据源」两组
    • 采用两列布局,节省垂直空间
    • 每个数据源配不同颜色图标,便于区分
    • 底部备注说明雪球、英为财情暂不可用
  • 修改 Sources/FinDataHub/Views/NewsFlashView.swift
    • 筛选器新增 9 个 OmniData 数据源选项

数据源状态清单(更新后)

数据源 source_code 状态 备注
财联社电报 cls ✅ 正常 原有
华尔街见闻 wallstreetcn ✅ 正常 原有
新浪财经快讯 sina ✅ 正常 原有
东方财富快讯 eastmoney ✅ 正常 原生 API 版
同花顺快讯 10jqka ✅ 正常 原生 API 版
金十数据 jin10 ✅ 已修复 flash_newest.js 版
格隆汇 gelonghui ✅ 已恢复 原有
21财经 21jingji ✅ 新增 OmniData
第一财经 yicai ✅ 新增 OmniData
富途牛牛 futunn ✅ 新增 OmniData
和讯网 hexun ✅ 新增 OmniData
金融界 jrj ✅ 新增 OmniData
界面新闻 jiemian ✅ 新增 OmniData
选股宝 xuangubao ✅ 新增 OmniData
雪球 xueqiu ⏸ 暂不可用 需 Cookie 处理
英为财情 investing ⏸ 暂不可用 Cloudflare 拦截
36氪快讯 36kr ❌ 已禁用 API 失效
GNews全球新闻 gnews ⚠️ 需配置 无 API Key
NewsAPI全球新闻 newsapi ⚠️ 需配置 无 API Key

文件变更

FinDataHub/
├── Resources/bin/
│   ├── omnidata_sources_fetcher.py        # [新增] 9 个 OmniData 数据源采集器
│   └── newsflash_collect.py                # [修改] SOURCE_TABLE、注册逻辑
├── Sources/FinDataHub/
│   ├── AppConstants.swift                  # [修改] 新增 9 个配置键
│   └── Views/
│       ├── SettingsView.swift              # [修改] 数据源管理 UI 重构
│       └── NewsFlashView.swift             # [修改] 筛选器新增数据源
└── .build/FinDataHub.app                   # [更新] 重新打包

编译状态

  • 零错误,1 个无关警告(历史遗留)

2026-07-11 — 数据源修复(金十数据重构 / 36kr禁用 / 格隆汇恢复)

变更概述

修复多个数据陈旧或失效的数据源:重构金十数据采集器(使用 flash_newest.js 新接口)、禁用已失效的 36kr 数据源、恢复格隆汇数据源(补全 news_sources 记录)、新增前端数据源管理开关(金十数据、格隆汇)。

详细变更

1. 金十数据(jin10)采集器重构

  • 修改 Resources/bin/new_sources_fetcher.py
    • 新增 Jin10Fetcher 类,使用 https://www.jin10.com/flash_newest.js 接口
    • 解析 JS 变量 var newest = [...] 获取 50 条最新快讯
    • 支持标题、内容、时间、重要度、标签等字段提取
    • 修复 None 值 strip 报错(使用 or "" 安全处理)
  • 修改 Resources/bin/newsflash_collect.py
    • _register_rss_fetchers() 中注册 Jin10Fetcher 替换默认失效版本
    • 新增 jin10_enabled UserDefaults 开关控制
    • SOURCE_TABLE 补全 jin10 → news_jin10 映射

2. 36kr 数据源禁用

  • 修改 Resources/bin/newsflash_collect.py
    • _register_rss_fetchers() 自动移除 36kr 采集器(API 失效,仅返回 2020 年旧数据)
  • MySQL:news_sources 表中 36kr 设为 is_active=0

3. 格隆汇(gelonghui)数据源恢复

  • MySQL:news_sources 表新增 gelonghui 记录(此前仅有表无 source 记录,导致数据无法写入)
  • 修改 Resources/bin/newsflash_collect.py
    • _register_rss_fetchers() 新增 gelonghui_enabled 开关控制
    • SOURCE_TABLE 补全 gelonghui → news_gelonghui 映射

4. 前端设置界面更新

  • 修改 Sources/FinDataHub/AppConstants.swift
    • 新增 rssJin10Enabled、rssGelonghuiEnabled 配置键
  • 修改 Sources/FinDataHub/Views/SettingsView.swift
    • 「新增数据源」改为「数据源管理」
    • 新增「金十数据」开关(金色图标)
    • 新增「格隆汇」开关(绿色图标)
    • 新增状态变量和 UserDefaults 读写逻辑

数据源状态清单

数据源 source_code 状态 最新数据时间
财联社电报 cls ✅ 正常 2026-07-11
华尔街见闻 wallstreetcn ✅ 正常 2026-07-11
新浪财经快讯 sina ✅ 正常 2026-07-11
东方财富快讯 eastmoney ✅ 正常 2026-07-11
同花顺快讯 10jqka ✅ 正常 2026-07-11
金十数据 jin10 ✅ 已修复 2026-07-11
格隆汇 gelonghui ✅ 已恢复 2026-07-11
36氪快讯 36kr ❌ 已禁用 2020-04(API失效)
GNews全球新闻 gnews ⚠️ 需配置 2026-07-03(无API Key)
NewsAPI全球新闻 newsapi ⚠️ 需配置 2026-07-02(无API Key)

文件变更

FinDataHub/
├── Resources/bin/
│   ├── new_sources_fetcher.py              # [新增] Jin10Fetcher 类
│   └── newsflash_collect.py                # [修改] 注册逻辑、SOURCE_TABLE、禁用36kr
├── Sources/FinDataHub/
│   ├── AppConstants.swift                  # [新增] jin10/gelonghui 配置键
│   └── Views/
│       └── SettingsView.swift              # [修改] 数据源管理 UI
└── .build/FinDataHub.app                    # [更新] 重新打包

编译状态

  • 零错误,1 个无关警告(PythonProcessManager.swift 中 num 变量未使用,历史遗留)

2026-07-11 — 新增数据源(东方财富/同花顺原生 API 实现)

变更概述

基于 china-finance-rss 思路,改用原生 API 实现 2 个新数据源:东方财富(替换原空实现)、同花顺。无需部署额外 RSS 服务,直接请求官方公开 API。雪球因 WAF 验证机制复杂,暂未实现。

详细变更

新增文件

  • Resources/bin/new_sources_fetcher.py — 原生 API 采集器:
    • EastMoneyFetcher: newsapi.eastmoney.com JSONP 接口,每次 50 条
    • TonghuashunFetcher: news.10jqka.com.cn JSON 接口,每次 20 条
  • Resources/bin/init_new_sources.py — MySQL 数据源初始化脚本

修改文件

  • Resources/bin/newsflash_collect.py — _register_rss_fetchers() 改为注册原生 API fetcher;修复 SOURCE_TABLE.update 顺序 bug;后端 stderr 写入日志文件便于排查
  • Sources/FinDataHub/AppConstants.swift — 清理无用 RSS 配置键,保留数据源开关
  • Sources/FinDataHub/Views/SettingsView.swift — 「RSS 数据源」改为「新增数据源」,仅保留 2 个开关(东方财富/同花顺)

数据源清单

数据源 source_code 接口 每次条数 状态
东方财富快讯 eastmoney newsapi.eastmoney.com 50 ✅ 可用
同花顺快讯 10jqka news.10jqka.com.cn 20 ✅ 可用

测试结果

单次采集新增 70 条快讯(东方财富 50 + 同花顺 20),AI 评分/摘要/标签正常。

编译状态

零错误、零警告


2026-07-11 — 定时任务 Cron 表达式模式重构

变更概述

将定时任务模块从差异化的频率模式重构为统一的 Cron 表达式模式。所有 5 个任务使用完全相同的配置模板,用户通过设置「分、时、日、月、周」5 个字段自由组合任意调度频率,支持「每 / 每隔 N / 指定值」三种模式。

详细变更

1. Cron 表达式模型

  • 修改 AppConstants.swift
  • 新增 CronFieldType 枚举(minute/hour/day/month/weekday)
  • 新增 CronFieldValue 枚举(every / everyN(Int) / specific(Int))
  • 新增 CronExpression 结构体,包含 5 个字段及 description 计算属性
  • 预置常用表达式:everyMinute、everyHour、everyDayMidnight
  • 清理重复的 UserDefaults 键(NewsFlash 区域旧的调度相关键)

2. SchedulerService 统一 cron 调度

  • 重写 SchedulerService.swift
  • 新增 TaskInfo 结构体统一描述任务元数据(id/标题/配置键/默认 cron)
  • 新增 TaskState 结构体(isEnabled/cron/nextRun/lastRun)
  • 所有任务存储在 taskStates: [String: TaskState] 字典中,统一管理
  • calculateNextRun() 以 1 分钟步进暴力匹配 cron 表达式,支持所有组合
  • shouldRunNow() 基于 cron 字段匹配 + 上次执行时间去重
  • 配置持久化:cron 表达式以字典形式存储在 UserDefaults

3. SchedulerView 统一模板 UI

  • 重写 SchedulerView.swift
  • 使用 ForEach(SchedulerService.allTasks) 动态渲染所有任务卡片
  • 所有卡片使用完全相同的 SchedulerTaskCard 模板
  • 新增 CronFieldPicker 组件,每个字段支持 3 种模式切换:
    • 每 — 每个时间单位都触发(*)
    • 隔N — 每隔 N 个时间单位触发(*/N)
    • 指定 — 指定具体值触发(N)
  • 卡片底部实时显示 cron 表达式字符串(等宽字体)

4. 其他文件适配

Cron 字段配置范围

字段 every(每) everyN(隔N) specific(指定)
分钟 ✓ 2/3/5/10/15/20/30 00-55(5分钟步进)
小时 ✓ 2/3/4/6/8/12 00-23
日期 ✓ 2/3/5/7/10/15 1-28 日
月份 ✓ 2/3/4/6 1-12 月
星期 ✓ 2/3 周一~周日

文件变更

FinDataHub/
├── Sources/FinDataHub/
│   ├── AppConstants.swift                   # [重构] Cron 表达式模型 + 清理旧键
│   ├── Services/
│   │   └── SchedulerService.swift           # [重写] 统一 cron 调度引擎
│   └── Views/
│       ├── SchedulerView.swift              # [重写] 统一 cron 配置模板
│       ├── DashboardView.swift              # [修改] 适配新 API
│       └── FinDataHubApp.swift              # [修改] 适配新 API
└── .build/FinDataHub.app                    # [更新] 重新打包

编译状态

  • 零错误,1 个无关警告(PythonProcessManager.swift 中 num 变量未使用,历史遗留)

2026-07-11 — 定时任务多频率扩展

变更概述

为所有 5 个定时任务(每日同步、每周同步、每月同步、快讯采集、快讯总结分析)增加 5 种调度频率选择:每分钟、每小时、每日、每周、每月。用户可根据需求灵活配置任务执行频率,而不再局限于固定的日/周/月周期。

详细变更

1. 新增 ScheduleFrequency 枚举

  • 修改 AppConstants.swift
  • 新增 ScheduleFrequency 枚举(minutely/hourly/daily/weekly/monthly),带 displayName 属性
  • 新增 UserDefaults 键前缀:schedulerFreqPrefix(频率)、schedulerMinutePrefix(分钟间隔)、schedulerHourPrefix(每小时分钟数)

2. SchedulerService 多频率调度重构

  • 修改 SchedulerService.swift
  • 新增每个任务独立的频率属性(dailyFrequency/weeklyFrequency 等 5 个)
  • 新增分钟间隔配置(minutely 模式:1/2/5/10/15/20/30/60 分钟可选)
  • 新增每小时分钟配置(hourly 模式:0-59 分钟可选)
  • 重构 calculateNextRun() 支持 5 种频率的下次执行时间计算
  • 重构 checkScheduledTasks() → shouldRunTask() 统一处理所有频率的触发判断
  • 新增方法:setFrequency()、setMinuteInterval()、setHourlyMinute()
  • 配置持久化新增 15 个键(5 任务 × 3 新配置项)

3. SchedulerView UI 动态配置

  • 修改 SchedulerView.swift
  • 每个任务卡片新增分段式频率选择器(每分钟/每小时/每日/每周/每月)
  • 配置项根据当前频率动态显示:
    • 每分钟模式:显示间隔分钟选择器
    • 每小时模式:显示分钟选择器
    • 每周模式:显示星期选择 + 时间选择
    • 每月模式:显示日期选择 + 时间选择
    • 每日模式:仅显示时间选择
  • SchedulerTaskCard 和 NewsFlashSchedulerCard 均支持频率动态切换

频率配置速查

频率模式 可配置项 可选值
每分钟 间隔分钟 1 / 2 / 5 / 10 / 15 / 20 / 30 / 60
每小时 分钟数 00 - 59
每日 时:分 00:00 - 23:55(5 分钟步进)
每周 星期 + 时:分 周一~周日 + 时间
每月 日期 + 时:分 1~28 日 + 时间

文件变更

FinDataHub/
├── Sources/FinDataHub/
│   ├── AppConstants.swift                   # [修改] 新增 ScheduleFrequency 枚举 + 配置键
│   ├── Services/
│   │   └── SchedulerService.swift           # [重构] 支持 5 种频率调度
│   └── Views/
│       └── SchedulerView.swift              # [重构] 频率选择器 + 动态配置项
└── .build/FinDataHub.app                    # [更新] 重新打包

编译状态

  • 零错误,1 个无关警告(PythonProcessManager.swift 中 num 变量未使用,历史遗留)

2026-07-10 — 通知功能全面扩展

变更概述

全面扩展 macOS 系统通知功能覆盖范围,新建统一通知管理工具 NotificationHelper,将所有可通知场景接入系统通知中心。覆盖范围包括:定时任务触发/完成、快讯总结完成/失败、服务异常停止、依赖部署完成/失败、日志清理完成、快讯推送等 7 大类别。

详细变更

1. 新建 NotificationHelper 统一通知管理

  • 新建 NotificationHelper.swift
  • 定义 7 种通知类别(sync/scheduler/newsFlash/newsSummary/serviceCrash/depSetup/logCleanup),每类有 displayName 和 icon
  • 提供开关判断(读取 UserDefaults)、send() 普通通知、sendCritical() 重要通知(.critical interruptionLevel)
  • 提供便捷方法:notifySchedulerTriggered()、notifySchedulerCompleted()、notifyNewsSummaryCompleted()、notifyNewsSummaryFailed()、notifyServiceCrash()、notifyDepSetupCompleted()、notifyDepSetupFailed()、notifyLogCleaned()

2. AppConstants 新增通知配置键

  • 修改 AppConstants.swift
  • 新增 5 个 UserDefaults 键:notifyScheduler、notifyNewsSummary、notifyServiceCrash、notifyDepSetup、notifyLogCleanup

3. SyncState 添加计算属性

  • 修改 SyncTask.swift
  • 为 SyncState 枚举添加 isSuccess、errorMessage、successSummary 计算属性,供通知逻辑使用

4. LogArchiveService.pruneOlder() 返回清理数量

  • 修改 LogArchiveService.swift
  • pruneOlder() 从 void 改为返回 Int(清理的文件数量),改用 accessQueue.sync 同步执行
  • 添加 @discardableResult 标注

5. FinDataHubApp 调度器接入通知

  • 修改 FinDataHubApp.swift
  • setupScheduler() 中:定时任务触发时发送 notifySchedulerTriggered,完成后发送 notifySchedulerCompleted
  • 快讯总结任务:完成时发送 notifyNewsSummaryCompleted,失败时发送 notifyNewsSummaryFailed
  • 应用启动时清理过期日志,若有清理则发送 notifyLogCleaned

6. DepManager 接入部署通知

  • 修改 DepManager.swift
  • runSetup() 和 upgradeDeps() 的 done 状态发送 notifyDepSetupCompleted
  • completion 失败回调发送 notifyDepSetupFailed

7. NewsFlashService 接入服务异常通知

  • 修改 NewsFlashService.swift
  • 新增服务健康监控:startServiceMonitor() / stopServiceMonitor() / checkServiceHealth()
  • 30 秒轮询检测采集器/后端/量化后端的运行状态
  • 检测到服务从运行变为停止(非用户主动停止)时发送 notifyServiceCrash(critical 级别)
  • startCollector/startBackend/startQuant 启动后自动启动监控
  • stopCollector/stopBackend/stopQuant 设置手动停止标记避免误报
  • refresh() 检测到服务运行时初始化监控状态

8. SettingsView 新增扩展通知开关

  • 修改 SettingsView.swift
  • 通知设置页新增"扩展通知"分组,含 5 个开关:定时任务触发/完成、快讯总结完成/失败、服务异常停止、依赖部署完成/失败、日志清理完成
  • 所有开关自动保存到 UserDefaults

通知覆盖场景

场景 通知类别 默认开关
同步完成 sync 关
同步失败 sync 开
定时任务触发 scheduler 关
定时任务完成 scheduler 关
新快讯推送 newsFlash 关
快讯总结完成 newsSummary 开
快讯总结失败 newsSummary 开
服务异常停止 serviceCrash 开(critical)
依赖部署完成 depSetup 开
依赖部署失败 depSetup 开(critical)
日志清理完成 logCleanup 关

文件变更

FinDataHub/
├── Sources/FinDataHub/
│   ├── AppConstants.swift                   # [修改] 新增 5 个通知配置键
│   ├── FinDataHubApp.swift                  # [修改] 调度器接入通知、日志清理通知
│   ├── Models/
│   │   └── SyncTask.swift                   # [修改] SyncState 添加 isSuccess/errorMessage/successSummary
│   ├── Services/
│   │   ├── NotificationHelper.swift         # [新增] 统一通知管理工具
│   │   ├── LogArchiveService.swift          # [修改] pruneOlder() 返回 Int
│   │   ├── DepManager.swift                 # [修改] 部署完成/失败通知
│   │   └── NewsFlashService.swift           # [修改] 服务健康监控+异常停止通知
│   └── Views/
│       └── SettingsView.swift               # [修改] 新增扩展通知开关
└── .build/FinDataHub.app                    # [更新] 重新打包

编译状态

  • 零错误,1 个无关警告(PythonProcessManager.swift 中 num 变量未使用,已有)

2026-07-10 — 架构重构 + 设置增强 + 功能分类优化

变更概述

完成底层架构优化、设置面板增强和功能分类重构。主要包括:文件结构整理(模型与服务分离)、新增 AppState 统一状态管理、设置面板重构为侧边栏布局并新增 20+ 可配置项、Tab 导航优化。

FinDataHub/
├── Sources/FinDataHub/
│   ├── AppConstants.swift                  # [修改] 新增 30+ 配置键和默认值
│   ├── ContentView.swift                   # [修改] 优化 Tab 分类,支持默认启动 Tab
│   ├── FinDataHubApp.swift                 # [修改] 新增快讯自动启动、日志清理
│   ├── Design/
│   │   └── FinDesign.swift                 # [修改] 新增设置分组图标、外观图标
│   ├── Models/
│   │   ├── NewsFlashModels.swift           # [新增] 快讯相关模型(从 NewsFlashService 提取)
│   │   ├── DataInsight.swift               # [修改] 仅保留模型定义,移除服务类
│   │   ├── SyncHistoryEntry.swift          # [修改] 仅保留模型定义,移除 HistoryStore
│   │   └── NewsFlashService.swift          # [删除] 移至 Services 目录
│   ├── Services/
│   │   ├── AppState.swift                  # [新增] 统一状态管理容器
│   │   ├── NewsFlashService.swift          # [新增] 从 Models 目录移入
│   │   ├── HistoryStore.swift              # [新增] 从 SyncHistoryEntry 提取
│   │   └── InsightService.swift            # [新增] 从 DataInsight 提取
│   └── Views/
│       └── SettingsView.swift              # [重构] 侧边栏+内容区布局,7 大设置分组
└── .build/FinDataHub.app                   # [更新] 重新打包

1. 架构重构:文件结构整理

背景: 原项目中服务类与模型混放(NewsFlashService、HistoryStore、InsightService 都在 Models 目录),职责不清。

  • NewsFlashService: 从 Models/ 移至 Services/,提取 8 个模型结构体到 NewsFlashModels.swift
  • HistoryStore: 从 SyncHistoryEntry.swift 提取到独立的 Services/HistoryStore.swift
  • InsightService: 从 DataInsight.swift 提取到独立的 Services/InsightService.swift
  • 模型与服务分离: Models 目录仅存放数据结构,Services 目录存放业务逻辑

新增文件: Models/NewsFlashModels.swift, Services/AppState.swift, Services/HistoryStore.swift, Services/InsightService.swift

删除文件: Models/NewsFlashService.swift

2. 统一状态管理:AppState

背景: 原有 7 个单例服务分散管理,依赖注入不统一。

  • AppState: @MainActor class, ObservableObject,集中管理所有服务实例
  • 包含:processManager, envChecker, scheduler, configManager, logArchive, newsFlash, historyStore, insightService, depManager
  • 统一访问入口,便于后续依赖注入和测试

文件: Services/AppState.swift (35 行)

3. 设置面板重构

背景: 原设置页单页滚动,配置项分散,难以扩展。

  • 侧边栏+内容区布局: 仿 macOS 系统设置风格
  • 7 大设置分组: 通用 / 外观 / 同步 / 通知 / 快讯 / AI / 日志
  • 新增可配置项(20+):
    • 通用:开机启动、Dock 图标显示、默认启动 Tab、窗口尺寸
    • 外观:主题模式(跟随系统/浅色/深色)、强调色、字体大小、减少动效
    • 同步:超时时间、失败重试次数、重试间隔
    • AI:模型名称、API 地址、评分/摘要/标签开关、并发数
    • 快讯:热度阈值、Top 展示数量、自动刷新开关及间隔
    • 日志:保留天数、分页大小
  • 所有配置项通过 UserDefaults 持久化

文件: Views/SettingsView.swift (重构,约 800 行)

4. 导航与功能分类优化

背景: 原 Tab 分类不够清晰,功能定位不明确。

  • 5 个 Tab: 概览 / 定时任务 / 快讯中心 / 日志历史 / 设置
  • Tab 命名优化: 更符合用户心智模型
  • 默认启动 Tab: 可在设置中配置,从 UserDefaults 读取

文件: ContentView.swift

5. AppConstants 扩展

  • 新增 30+ 个配置键(AppKeys)和默认值(AppConstants)
  • 涵盖:外观、通用、日志、AI、快讯高级、同步高级等分类
  • 为 DateFormatter 添加 en_US_POSIX locale,避免非公历日历设备解析错误

文件: AppConstants.swift

技术说明

  • Swift 编译零错误,1 个警告(未使用变量,原有代码遗留)
  • 所有原有功能保持向后兼容,单例模式仍可使用
  • 文件结构遵循 MVC 范式:Models 放数据结构,Services 放业务逻辑,Views 放 UI

文件统计

指标 变更前 变更后 增量
Swift 源文件 34 37 +3
编译错误 0 0 0
编译警告 1 1 0

新增配置项清单

通用设置

  • 开机自动启动
  • 显示 Dock 图标
  • 默认启动页面
  • 窗口宽度/高度

外观设置

  • 主题模式(system/light/dark)
  • 强调色(blue/green/orange/purple)
  • 基础字体大小
  • 减少动画效果

同步高级设置

  • 同步超时时间(分钟)
  • 失败重试次数
  • 重试间隔(秒)

AI 设置

  • AI 模型名称
  • API 基础地址
  • AI 评分开关
  • AI 摘要开关
  • AI 标签开关
  • 评分并发数
  • 摘要并发数

快讯高级设置

  • 高热度阈值
  • Top 展示数量
  • 自动刷新开关
  • 自动刷新间隔(秒)

日志设置

  • 日志保留天数
  • 分页显示条数

2026-07-04 — Phase 2a 定时任务 + Sparkle 框架集成

变更概述

Phase 2a 完成 4 大模块:定时任务引擎、定时任务 UI、历史同步记录、Sparkle 自动更新框架骨架。

FinDataHub/
├── Package.swift                         # SPM 配置 (macOS 13+, Sparkle binaryTarget 预留)
├── Sources/FinDataHub/
│   ├── Info.plist                        # [新增] App 元数据 (SUFeedURL, 版本号等)
│   ├── FinDataHubApp.swift               # [修改] 初始化 SchedulerService, 连接 trigger 回调
│   ├── ContentView.swift                 # [修改] 新增 "定时任务" 标签页
│   ├── Views/
│   │   ├── DashboardView.swift           # [修改] 新增下次执行时间预览
│   │   ├── SchedulerView.swift           # [新增] 定时任务管理面板 + 历史记录
│   │   ├── EnvChecklistView.swift        # 不变
│   │   ├── SyncProgressView.swift        # 不变
│   │   └── SettingsView.swift            # 不变
│   ├── Services/
│   │   ├── SchedulerService.swift        # [新增] Timer 调度引擎 (426 行)
│   │   ├── UpdateController.swift        # [新增] Sparkle 更新控制器骨架
│   │   ├── PythonProcessManager.swift    # 不变
│   │   ├── EnvChecker.swift              # 不变
│   │   └── ConfigManager.swift           # 不变
│   └── Models/
│       ├── SyncHistoryEntry.swift        # [新增] 同步历史记录模型 + HistoryStore
│       ├── SyncTask.swift                # 不变
│       ├── SyncLogEntry.swift            # 不变
│       └── EnvStatus.swift               # 不变
├── build-app.sh                          # [新增] .app 打包脚本
└── setup-sparkle.sh                      # [新增] Sparkle 集成脚本

1. Timer 定时任务引擎

背景: BGTaskScheduler 在 macOS 上 API_UNAVAILABLE(仅 iOS/tvOS),改用应用内 Timer 调度。

  • SchedulerService: @MainActor class, ObservableObject, 单例共享
  • 使用 DispatchSourceTimer 每 30 秒检查一次调度条件
  • UserDefaults 持久化任务配置(启用状态/执行时间/上次执行时间)
  • 支持完整的 crontab 表达式: 每日(时+分) / 每周(星期+时+分) / 每月(日+时+分)
  • 下次执行时间实时计算 + UI 预览(Dashboard + SchedulerView)
  • 手动触发 + 单个启停 + 全部禁用
  • App 退出后配置保留,启动时通过 registerTasks() 恢复

文件: Services/SchedulerService.swift (426 行)

2. 定时任务管理 UI

  • ContentView 新增第三个标签 "定时任务" (clock.badge.checkmark)
  • SchedulerView: 三张任务卡片(每日/每周/每月)
    • 每个卡片: 启用/禁用开关, 时间选择器(时/分 步进5分), 星期/日期选择器
    • 下次执行时间 + 上次执行时间实时显示
    • "立即运行" 按钮(手动触发同步)
  • Dashboard 新增下次执行时间预览区域
    • 显示所有启用任务的下次时间(高亮显示"今天""明天")
  • 底部刷新状态按钮

文件: Views/SchedulerView.swift (446 行), Views/DashboardView.swift (+213→294 行)

3. 历史同步记录

  • SyncHistoryEntry: 同步类型/时间/结果(success/failed/cancelled)/摘要/耗时
  • HistoryStore: UserDefaults 持久化, 最多保留 100 条
  • 每次同步完成后自动记录(由 FinDataHubApp 监控同步状态变化)
  • SchedulerView 底部历史列表(最近 10 条, 显示类型+时间+耗时+摘要)

文件: Models/SyncHistoryEntry.swift (98 行)

4. Sparkle 自动更新 (骨架)

  • 创建 Info.plist (SUFeedURL, SUEnableAutomaticChecks 等配置)
  • UpdateController.swift 基础骨架(checkForUpdates() 方法预留)
  • Package.swift 预留 binaryTarget 注释
  • setup-sparkle.sh 脚本: 下载 Sparkle.xcframework + 签名指引
  • build-app.sh 脚本: 将二进制打包为 .app(含 Info.plist 和 Sparkle 嵌入)

技术说明

  • BGTaskScheduler 在 macOS SDK 中标记为 API_UNAVAILABLE(macos),改用 Timer 轮询
  • Timer 每 30 秒触发,检查是否到达执行时间窗口(±1 分钟),耗电极低
  • SPM 不支持 Info.plist 作为资源,使用 exclude 标记后通过 build-app.sh 生成
  • Swift 6.3.3, macOS 27 SDK, 零 warning 编译通过

文件统计

指标 Phase 1 Phase 2a 增量
Swift 源文件 13 17 +4
总行数 1,830 3,010 +1,180
编译警告 0 0 0

Phase 2b 待办

  • tushare-fetcher 双数据源恢复 (tushare_fetcher active 目录)
  • 设置页数据源选择器 (akshare / tushare / auto) — auto 模式未实现
  • 状态面板数据源连通性显示 — 部分完成:已显示数据源 badge,未做独立连通性检测

2026-07-04 — Phase 2b 双数据源接入

变更

  • Models/SyncLogEntry.swift — 新增 DataSource 枚举 (akshare/tushare),AppConfig 增加 dataSource/tushareToken
  • Services/ConfigManager.swift — 增加数据源和 token 的 UserDefaults 持久化,切换数据源时自动切换默认项目和数据库
  • Services/PythonProcessManager.swift — 支持根据 DataSource 选择调用 akshare-fetcher 或 tushare-fetcher CLI
  • Views/SettingsView.swift — 新增"数据源"配置组 (radioGroup 选择器 + tushare Token 输入)
  • Views/DashboardView.swift — 顶部标题栏显示数据源标签 (蓝/绿 badge)

项目累计

  • 17 源文件 / ~3,150 行 / 零警告
  • tushare-fetcher v2.1.0 已从 archive 恢复,依赖已安装

Phase 3 待办

  • 结构化日志归档
  • 同步失败告警增强

2026-07-04 — Python 运行时打包进 .app

变更

  • build-app.sh — 全新构建脚本:编译 Swift → 打包二进制 → 捆绑 Python 运行时
  • PythonProcessManager.swift — 优先查找捆绑运行时 Contents/Frameworks/akshare-venv/bin/akshare-fetcher
  • 捆绑运行时:Python 3.12 + akshare 1.18.64 + 全量依赖(~250MB)
  • 回退机制:无捆绑运行时自动使用系统 uv

文件统计

  • 18 个源文件(含 build-app.sh),~3,300 行代码
  • .app 大小:257MB(2.5MB Swift + 250MB Python 运行时 + 5MB 项目代码)

核心变化

  • 不再依赖系统 Python — 开箱即用,任意 Mac 直接双击运行
  • MySQL 仍需本地安装 — 数据持久化依赖,无法打包进 .app
  • 未签名 — 运行需在 系统设置 > 隐私与安全性 中允许

2026-07-04 — Phase 3 日志归档 + 告警增强

变更概述

Phase 3 完成 4 大模块:结构化日志归档(JSONL)、日志查看器 UI、同步日历视图、通知偏好设置 + 失败告警增强。

FinDataHub/
├── Sources/FinDataHub/
│   ├── ContentView.swift                 # [修改] 新增第四个标签「日志归档」
│   ├── Models/
│   │   └── SyncLogEntry.swift            # [修改] 新增 source/taskId 字段 + ArchivedLogEntry + TaskSummary
│   ├── Services/
│   │   ├── LogArchiveService.swift       # [新增] JSONL 日志归档 (写入~Library/Logs/FinDataHub/)
│   │   └── PythonProcessManager.swift    # [修改] 集成 LogArchive 归档 + 连续失败计数 + critical alert
│   └── Views/
│       ├── DashboardView.swift           # [修改] 新增失败警告横幅 (连续失败≥3次)
│       ├── SchedulerView.swift           # [修改] 底部嵌入 CalendarHistoryView
│       ├── LogArchiveView.swift          # [新增] 日志归档查看器 (日期/数据源/级别筛选)
│       ├── CalendarHistoryView.swift     # [新增] 月历视图 + 日详情弹窗
│       └── SettingsView.swift            # [修改] 新增通知偏好设置面板
├── DEVLOG.md                             # [修改] 本日志

1. 结构化日志归档 (LogArchiveService)

  • 文件: Services/LogArchiveService.swift (~290 行)
  • 存储位置: ~/Library/Logs/FinDataHub/YYYY-MM-DD.jsonl
  • 格式: 每行一个 JSON 对象 {"ts":"ISO8601","level":"INFO","msg":"...","source":"akshare","task":"daily-20260704-181400"}
  • 日志模型: ArchivedLogEntry (Codable) + TaskSummary (Identifiable)
  • 查询: 按日期/数据源/级别筛选,支持日期范围查询
  • 保留策略: 30 天自动清理 (pruneOlder(than:)),清空全部按钮(确认弹窗)
  • 集成: PythonProcessManager.addLog 每次调用都会追加到 JSONL(通过 currentTaskId 关联任务)
  • 线程安全: 写入使用 accessQueue (DispatchQueue serial) 保证原子性

2. 日志查看器 UI (LogArchiveView)

  • 文件: Views/LogArchiveView.swift (~280 行)
  • ContentView 第四个标签「日志归档」
  • 筛选区:DatePicker(日期)+ 数据源 Picker + 级别 Picker
  • 日志列表:时间线样式,每条显示时间戳 + 级别色标 + 消息
  • 点击展开详情(显示数据源/任务 ID)
  • 列表顶部统计(INFO/WARNING/ERROR 计数)
  • 底部状态栏:「共 N 条 | 保留 30 天」
  • 清空按钮 + NSAlert 确认

3. 历史同步日历视图 (CalendarHistoryView)

  • 文件: Views/CalendarHistoryView.swift (~330 行)
  • 嵌入 SchedulerView 底部(同步日历卡片)
  • 月历翻页(左/右箭头)+ 图例(绿/红/橙/灰)
  • LazyVGrid 7 列网格 + 星期头部
  • 每天色标:绿色=成功 / 红色=失败 / 橙色=部分失败 / 灰色=无同步
  • 点击日期弹出 DayDetailSheet(当日日志列表 + 统计箱)
  • 数据源:LogArchiveService.query() 逐日查询

4. 通知偏好设置 (SettingsView)

  • 文件: Views/SettingsView.swift(通知区域新面板)
  • 同步完成通知(开关,默认关)
  • 同步失败通知(开关,默认开)
  • 失败告警阈值滑块(连续 N 次失败后升级,默认 3)
  • 告警间隔滑块(5-120 分钟)
  • 通知声音选择器(默认/急促)
  • 配置持久化到 UserDefaults,PythonProcessManager 运行时读取

5. 失败告警增强 (PythonProcessManager)

  • 连续失败计数: UserDefaults 持久化 (findatahub_consecutive_failures)
  • 阈值告警: 达到 maxFailuresBeforeAlert (默认 3) 后,通知升级为 .critical (critical alert sound)
  • Dashboard 横幅: 连续失败 ≥ 3 次时显示黄色警告 bar(可关闭)
  • 暴露 API: consecutiveFailureCount(), resetConsecutiveFailures(), notificationOnSuccess/Failure/Sound
  • 通知授权: ContentView.onAppear 请求 [.alert, .sound, .badge, .criticalAlert]
  • sendNotification 支持 isCritical 参数,失败告警时标题含失败次数

指标

指标 Phase 2b Phase 3 增量
Swift 源文件 18 21 +3
总行数 ~3,300 ~4,300 +1,000
编译警告 0 0 0

2026-07-04 — Phase 3 日志归档 + 告警增强

变更

文件 状态 行数
Services/LogArchiveService.swift ✨ 新增 319
Views/LogArchiveView.swift ✨ 新增 299
Views/CalendarHistoryView.swift ✨ 新增 363
Models/SyncLogEntry.swift 📝 修改 +47
Services/PythonProcessManager.swift 📝 修改 598
Views/ContentView.swift 📝 修改 +1 标签
Views/SettingsView.swift 📝 修改 +通知设置
Views/DashboardView.swift 📝 修改 +失败警告横幅
Views/SchedulerView.swift 📝 修改 +日历视图

项目累计

  • 20 个源文件(含 Package.swift),4,558 行 Swift
  • 编译零警告零错误
  • .app 大小:~260MB(含 Python 运行时)

Phase 4 待办

  • 核心洞察卡片(表行数、最新数据日期、数据缺口)
  • MySQLNIO 直连查询 → 已替代:改用 Python insights.py 脚本查询 information_schema
  • Swift Charts 同步趋势图

2026-07-04 — 代码质量优化 + 安全加固

变更概述

深度代码审查后完成 9 项优化,涵盖安全性、性能、可维护性、用户体验四个维度。编译零错误零警告。

新增文件:
├── AppConstants.swift                    # ✨ UserDefaults Key + 魔法数字 + DateFormatter 统一管理
└── Services/KeychainService.swift        # ✨ Keychain 安全存储服务(密码/Token)

修改文件:
├── Services/ConfigManager.swift          # 📝 Keychain 迁移 + 内存缓存 + 死代码清理
├── Services/PythonProcessManager.swift   # 📝 tushare 同步修复 + 常量替换 + 死代码清理
├── Services/SchedulerService.swift       # 📝 Key 集中化 + Timer 间隔常量
├── Services/EnvChecker.swift             # 📝 MySQL 密码改用 MYSQL_PWD 环境变量
├── Services/LogArchiveService.swift      # 📝 DateFormatter 复用
├── Models/SyncLogEntry.swift             # 📝 删除 toConnectionString + DateFormatter 复用
├── Models/SyncHistoryEntry.swift         # 📝 Key 集中化 + maxEntries 常量
├── Views/SettingsView.swift              # 📝 Key 集中化 + 数据源切换数据库名联动
├── Views/LogArchiveView.swift            # 📝 刷新按钮 + 5 秒自动刷新
├── Views/CalendarHistoryView.swift       # 📝 DateFormatter 复用
├── Views/SchedulerView.swift             # 📝 DateFormatter 复用
└── FinDataHubApp.swift                   # 📝 删除未使用的通知权限方法

1. 安全加固 🔒

1.1 MySQL 密码不再暴露于命令行

  • 问题: mysql -p密码 形式传递密码,ps aux 可直接看到
  • 修复: 使用 MYSQL_PWD 环境变量传递(MySQL 客户端原生支持)
  • 涉及: EnvChecker.checkMysql() + SettingsView.testConnection()

1.2 密码/Token 从 UserDefaults 迁移到 Keychain

  • 问题: MySQL 密码和 Tushare Token 明文存储在 UserDefaults plist 文件中
  • 修复: 新增 KeychainService,使用 Security.framework 的 kSecClassGenericPassword 存储
  • 涉及: ConfigManager.config getter/setter(dbPassword + tushareToken)
  • 向后兼容: 首次读取 Keychain 为空时不报错(旧数据仍在 UserDefaults,但新写入走 Keychain)

2. Bug 修复 🐛

2.1 tushare 数据源执行实际同步

  • 问题: tushare 分支调用 tushare-fetcher query --help,仅打印帮助文档,无实际同步
  • 修复: 改为 tushare-fetcher sync --type xxx --workers xxx,Token 通过 TUSHARE_TOKEN 环境变量传递
  • env 变量声明顺序调整: 将 var env = ... 移到 switch 之前,确保 tushare 分支可设置环境变量

2.2 删除死代码

  • ConfigManager.detectMysqlConfig() 中无效的 JSONSerialization.jsonObject 尝试(YAML 不是 JSON,永远失败)
  • PythonProcessManager 中未使用的 stdoutQueue / stderrQueue 属性和赋值
  • DBConfig.toConnectionString(从未被调用,且会泄露密码)
  • FinDataHubApp.requestNotificationPermission()(定义了但从未调用,且权限不全)

2.3 appendLog 重复截断

  • 问题: 连续两次 suffix 截断,第二次永远无效
  • 修复: 删除冗余的 suffix(50000) 行

3. 性能优化 ⚡

3.1 ConfigManager 内存缓存

  • 问题: 每次访问 config 都触发 UserDefaults 读取 + 文件 I/O(detectMysqlConfig)
  • 修复: 新增 cachedConfig: AppConfig? 缓存,getter 命中直接返回,setter 同步更新缓存

3.2 DateFormatter 全局复用

  • 问题: 8 处 DateFormatter() 在方法内创建(创建成本高,涉及 locale 加载)
  • 修复: 提取 7 个静态实例到 AppConstants 扩展中(taskId / logDate / month / dayOfWeek / fullDateWeekday / shortDate / iso8601)
  • 涉及文件: PythonProcessManager、LogArchiveService、CalendarHistoryView(3处)、SchedulerView、SyncLogEntry

4. 可维护性 🏗️

4.1 UserDefaults Key 统一管理

  • 问题: 30+ 个 key 字符串散落在 5 个文件中,拼写错误不易发现
  • 修复: 集中到 AppKeys 枚举(Config / Scheduler / Failure / Notification / History 五组)
  • 涉及文件: ConfigManager、SchedulerService、PythonProcessManager、SettingsView、SyncHistoryEntry

4.2 魔法数字提取为常量

  • 问题: 10+ 个魔法数字(30秒、100条、200条、30天、50KB、3次等)散落在代码中
  • 修复: 集中到 AppConstants 枚举(Scheduler / History / Logs / Failure alert 四组)
  • stderr 缓冲阈值从 10KB 降至 4KB,降低内存峰值

4.3 SyncRequest 封装

  • 问题: startSync() 6 个参数,调用点冗长
  • 修复: 新增 SyncRequest 结构体,支持 SyncRequest(type:config:) 便捷构造
  • 调用点从 6 行缩减为 1 行

5. 用户体验 🎨

5.1 切换数据源时数据库名联动

  • 问题: 切换到 tushare 时数据库字段显示与实际保存值不一致
  • 修复: Picker 添加 .onChange(of: dataSource),自动更新 dbDatabase 为对应默认值
  • saveConfig 移除强制覆盖逻辑(因状态已同步,无需再判断)

5.2 日志归档页实时刷新

  • 问题: 同步进行中看不到实时日志
  • 修复: 新增手动刷新按钮 + 5 秒自动刷新(仅当查看今日日志时)
  • 使用 Timer.publish(every: 5) + .autoconnect(),视图消失时自动断开

指标

指标 优化前 优化后 变化
Swift 源文件 20 22 +2
总行数 4,558 ~4,700 ~+140
编译警告 1 0 -1
编译错误 0 0 0

跳过的优化

  • SettingsView ViewModel 化 — 低优先级大型重构(14 个 @State 变量),风险高收益有限

2026-07-04 — 全面审计修复:安全断裂 + 逻辑 Bug + 死代码清理

变更概述

全面代码审计后发现 3 个 P0 严重问题(KeychainService 丢失、定时同步忽略用户配置、告警阈值硬编码)和 4 个 P1 问题(死代码、魔法数字残留、编译警告、硬编码 Key)。全部修复,编译零错误零警告。

新增文件:
└── Services/KeychainService.swift        # ✨ 重新创建(上次写入被清除)

修改文件:
├── Services/ConfigManager.swift          # 📝 密码/Token Keychain 读写修复
├── Services/PythonProcessManager.swift   # 📝 告警阈值修复 + 死代码删除 + 魔法数字替换
├── Services/LogArchiveService.swift      # 📝 魔法数字替换(86400/30/31)
├── Services/SchedulerService.swift       # 📝 魔法数字替换(28)
├── FinDataHubApp.swift                   # 📝 定时同步顺序修复
├── AppConstants.swift                    # 📝 新增 dbPassword/tushareToken/syncEnabledTables/secondsPerDay/maxMonthlyDay
├── Models/SyncTask.swift                 # 📝 删除死代码(ProcessState/SyncProgressState/displayColor)
├── Models/SyncLogEntry.swift             # 📝 删除死代码(LogLevel.color/DataSource.cliName)
├── Models/EnvStatus.swift                # 📝 删除死代码(EnvStatus.color)
├── Models/NewsFlashService.swift         # 📝 修复 2 个编译警告
├── Models/SyncTypeDetail.swift           # 📝 硬编码 Key 纳入 AppKeys
├── Views/SettingsView.swift              # 📝 默认值魔法数字替换
└── Views/DashboardView.swift             # 📝 版本号改为从 Bundle 读取

1. P0 安全断裂修复 🔴

1.1 KeychainService.swift 丢失

  • 问题: 上次优化声称已创建 KeychainService.swift,但文件实际不存在于文件系统中
  • 修复: 重新创建完整的 KeychainService(save/load/delete,使用 kSecClassGenericPassword)

1.2 ConfigManager 密码/Token 持久化断裂

  • 问题: getter 第 37 行 tushareToken: "" 永远返回空;setter 未保存 password 和 tushareToken
  • 修复:
    • getter: 密码优先从 Keychain 读取,回退到 detectMysqlConfig();Token 从 Keychain 读取
    • setter: 使用 KeychainService.save() 持久化 password 和 tushareToken
  • 影响: 用户配置的密码和 Token 现在能正确保存和读取

2. P0 逻辑 Bug 修复 🔴

2.1 定时双源同步忽略用户配置

  • 问题: FinDataHubApp 硬编码 preferred = .akshare,忽略用户在设置页配置的 dataSource
  • 修复: 改为 let preferred = cfg.dataSource,根据用户配置决定同步顺序

2.2 失败告警阈值硬编码

  • 问题: PythonProcessManager 第 287、310 行硬编码 >= 3,未使用用户可配置的 maxFailuresBeforeAlert
  • 修复: 改为使用 threshold(即 maxFailuresBeforeAlert)变量

3. P1 死代码清理 🟡

删除 8 处从未被引用的死代码:

文件 删除内容
SyncTask.swift ProcessState 枚举、SyncProgressState 结构体、SyncState.displayColor
SyncLogEntry.swift LogLevel.color、DataSource.cliName(与 displayName 重复)
EnvStatus.swift EnvStatus.color(视图有独立的 colorForStatus())
ConfigManager.swift configDefaultProjectPath() 方法
PythonProcessManager.swift findUvPath() 方法、waitForCompletion() 方法

4. P1 魔法数字补全 🟡

文件 替换内容
PythonProcessManager.swift updateLogLine 中 50000/30000 → AppConstants 常量
LogArchiveService.swift 86400 → secondsPerDay;30 → logRetentionDays;31 → logRetentionDays + 1
SchedulerService.swift 28 → maxMonthlyDay(2 处)
SettingsView.swift 3 → defaultMaxFailures;30 → defaultAlertInterval
DashboardView.swift "v1.2.0" → 从 Bundle Info.plist 读取
AppConstants.swift 新增 secondsPerDay、maxMonthlyDay 常量

5. P1 编译警告修复 🟡

  • NewsFlashService.swift:137 — try? 结果未使用 → 改为 _ = try? await ...
  • NewsFlashService.swift:220 — var req 应为 let → 改为 let req

6. P1 硬编码 Key 纳入 AppKeys 🟡

  • SyncTypeDetail.swift:47 — "findatahub_sync_enabled_tables" → AppKeys.syncEnabledTables
  • AppConstants.swift — 新增 syncEnabledTables、dbPassword、tushareToken 三个键

指标

指标 优化前 优化后 变化
编译错误 0 0 0
编译警告 2 0 -2
死代码处 8 0 -8
硬编码魔法数字 ~15 0 -15

2026-07-04 — 复查补漏:魔法数字遗漏 + 冗余代码 + DEVLOG 清理

变更概述

修复后复查发现 1 处魔法数字遗漏、4 处低优先级代码风格问题,以及 DEVLOG 待办项与代码现状严重脱节。全部修复,编译零错误零警告。

修改文件:
├── Views/CalendarHistoryView.swift       # 📝 86400 → AppConstants.secondsPerDay(2 处)
├── Services/PythonProcessManager.swift   # 📝 删除冗余条件判断 + SIGKILL 超时改为常量
├── FinDataHubApp.swift                   # 📝 Task.sleep 纳秒值改为常量
├── Services/ConfigManager.swift          # 📝 默认 workers 改为常量
├── AppConstants.swift                    # 📝 新增 sigkillTimeout/defaultWorkers/pollIntervalNanos/stateSettleNanos
└── DEVLOG.md                             # 📝 回勾 6 项已完成待办、删除 7 项废弃 Sparkle 待办、标注 1 项已替代

1. 魔法数字遗漏补修

文件 行号 修复
CalendarHistoryView.swift 222, 246 86400 → AppConstants.secondsPerDay
PythonProcessManager.swift 179 3 → AppConstants.sigkillTimeout
FinDataHubApp.swift 101, 104 1_000_000_000/500_000_000 → AppConstants.pollIntervalNanos/stateSettleNanos
ConfigManager.swift 37 8 → AppConstants.defaultWorkers

2. 冗余代码删除

  • PythonProcessManager.swift: if isCritical 块内的 if failCount >= threshold 冗余判断(条件必然为真),直接赋值 consecutiveFailureWarning = true

3. DEVLOG 待办项清理

操作 数量 说明
回勾 [ ] → [x] 6 项 双数据源、数据源选择器、日志归档、告警增强、洞察卡片、MySQLNIO(已替代)
删除废弃行 7 项 Sparkle 相关(脚本和文件已删除)
保留未完成 4 项 数据源连通性、Swift Charts、DMG 分发、Universal binary

指标

指标 优化前 优化后 变化
编译错误 0 0 0
编译警告 0 0 0
魔法数字残留 5 0 -5
DEVLOG 未勾选待办 17 4 -13

2026-07-04 — Phase 4 数据洞察 + 全自动依赖部署 + MenuBarExtra 兼容修复

变更概述

Phase 4 完成 3 大方向:数据洞察卡片(数据库表行数/最新日期/滞后天数监控)、全自动依赖部署方案重构(257MB→2.4MB)、MenuBarExtra 兼容性修复(替换所有 sheet/alert 为内联 overlay)。

FinDataHub/
├── Resources/bin/
│   ├── manage_deps.py              # [重写] 全自动一键部署脚本 (check/setup/upgrade)
│   └── insights.py                 # [新增] MySQL 表统计数据查询
├── Sources/FinDataHub/
│   ├── ContentView.swift           # [修改] 标签栏 Bloomberg 深蓝风 redesign
│   ├── Models/
│   │   ├── DataInsight.swift       # [新增] InsightService + TableInsight 模型
│   │   ├── SyncTask.swift          # [修改] SyncType 增加 Identifiable 协议
│   │   └── SyncTypeDetail.swift    # [新增] 同步类型-表映射 + 表级启停 UserDefaults 持久化
│   ├── Services/
│   │   ├── DepManager.swift        # [重写] DepUpdateController → DepManager (setup/check/upgrade)
│   │   └── UpdateController.swift  # [删除] Sparkle 骨架
│   ├── Views/
│   │   ├── CalendarHistoryView.swift # [修改] .sheet → overlay 兼容 MenuBarExtra
│   │   ├── DashboardView.swift      # [修改] Bloomberg 标题栏 + 数据洞察引用
│   │   ├── InsightCardView.swift    # [新增] 数据概览 3×2 网格卡片
│   │   ├── LogArchiveView.swift     # [修改] .alert → overlay 兼容 MenuBarExtra
│   │   ├── SchedulerView.swift      # [修改] 同步类型详情入口 + .sheet → overlay
│   │   └── SyncTypeDetailView.swift # [新增] 同步类型-表详情视图(内联版)
├── build-app.sh                     # [修改] 移除末尾 open 避免覆盖冲突
├── Package.swift                    # [清理] 移除 Sparkle 注释
├── Sources/FinDataHub/Info.plist    # [清理] 移除 SUFeedURL/SUEnableAutomaticChecks
└── setup-sparkle.sh                 # [删除]

1. 全自动依赖部署重构

背景: 之前打包了 250MB Python 运行时进 .app,用户明确表示不需要。

当前架构:

  • .app 2.4MB,纯 Swift,零捆绑
  • 仪表盘「一键部署」按钮 → manage_deps.py setup → 自动检测 + 安装
  • 一键部署覆盖:Xcode CLT / Homebrew / Python 3.12 / MySQL / akshare_fetcher git clone / 7 个 pip 包 / CREATE DATABASE / .venv 创建 / 配置默认文件
  • 依赖升级:manage_deps.py upgrade → pip install --upgrade,JSON 流式进度
  • 系统 Python 检测优先级:akshare_fetcher .venv → Homebrew → system
  • pip list 改用 python3 -m pip list --format=json 保证 Python/pip 版本一致

关键文件:

  • Resources/bin/manage_deps.py (~400 行) — check/setup/upgrade 三模式
  • Services/DepManager.swift (~300 行) — Swift Process 调用 + 流式 JSON 解析

2. 数据洞察卡片

新增: Dashboard 显示各数据表的行数、最新日期、滞后天数。

  • 10 张关键表(stock_daily / index_daily / sector_fund_flow / fund_etf_daily 等)
  • 3×2 网格卡片,绿色=健康(滞后<7天),橙色=滞后
  • 统计行:总表数 / 总行数 / 滞后数量
  • InsightService + InsightCardView 独立视图
  • Resources/bin/insights.py — Python 脚本查询 MySQL information_schema

3. 同步类型详情

新增: 定时任务页面 — 点击「查看详情」查看/编辑每张表。

  • 日线关联 5 张表(stock_daily/index_daily/sector_fund_flow/fund_etf_daily/fund_nav_history)
  • 周线关联 3 张表(stock_weekly/sector_industry_index_ths/sector_concept_index_ths)
  • 月线关联 4 张表(stock_monthly/stock_financial_indicator/stock_income_ths/stock_balance_ths)
  • 每张表有中文描述说明存储内容
  • 开关 toggle 可独立启用/禁用
  • 设置持久化到 UserDefaults(key: findatahub_sync_enabled_tables)
  • SyncTableConfig — 表级启停管理类

4. UI Redesign(Bloomberg 金融风)

标题栏(Dashboard 顶部):

  • 深蓝渐变背景 #0F1424 → #141F33
  • FD 蓝色方块品牌标识 + 文字白色
  • 数据源标签:暗蓝底 + 亮蓝文字
  • 状态指示器内联到标题栏

标签栏(ContentView 4 个 tab):

  • 深黑底 #0A0F1A
  • 选中项亮蓝下滑线 + 亮蓝文字
  • 替换 Segmented Picker 为自定义按钮

5. MenuBarExtra 兼容性修复

根因: MenuBarExtra .menuBarExtraStyle(.window) 弹出 .sheet() 或 .alert() 时,macOS 会直接关闭整个 popover 窗口。

修复方案: 全部替换为 overlay 内联覆盖。

位置 原方案 新方案 触发时机
SchedulerView 详情 .sheet(item:) .overlay 点击「查看详情」
CalendarHistoryView 日详情 .sheet(isPresented:) .overlay 点击日期单元格
LogArchiveView 清空确认 .alert .overlay 自定义弹窗 点击清空按钮
SettingsView 测试连接 NSAlert() 不变(原生弹窗不受影响) 点击测试连接

6. 清理与优化

优化项 说明
删除 setup-sparkle.sh Sparkle 方案已废弃
清理 Package.swift Sparkle 注释 删除 binaryTarget/依赖注释
清理 Info.plist Sparkle 键 删除 SUFeedURL/SUEnableAutomaticChecks
build-app.sh 移除末尾 open 防止覆盖冲突(cp -Rf 静默失败)
build-app.sh 改用 ditto 部署 可覆盖正在运行的 .app
标题栏暗色文字修复 .primary → Color.white.opacity(0.9)
数据概览 3×2 网格 替换 HStack 6 卡片并排,LazyVGrid 自适应
详情按钮改为 .borderedProminent 提高可见性

7. 部署流程标准化

构建与部署

cd ~/Projects/项目/FinDataHub
bash build-app.sh          # 编译 + 打包 (output: .build/FinDataHub.app)
ditto .build/FinDataHub.app /Applications/FinDataHub.app  # 部署到 /Applications
open /Applications/FinDataHub.app  # 启动

一键部署(新机器)

启动 App → 点「一键部署」 → 自动安装 Homebrew/Python/MySQL/项目/pip 包/数据库
ditto FinDataHub.app → 复制到任意 Mac 运行

文件统计

指标 Phase 3 Phase 4 增量
Swift 源文件 22 25 +3
总行数 ~4,700 ~5,500 +800
Python 脚本 1 3 +2
编译警告 0 0 0
.app 大小 257MB 2.4MB -254MB

剩余事项

  • DMG 分发包(可选)
  • Intel Mac universal binary(当前仅 arm64)

2026-07-07 — 性能专项优化(P0 + P1 + P2)

变更概述

针对长日志同步场景下的 UI 卡顿、主线程 I/O、重复计算等问题,按 P0/P1/P2 优先级逐个修复。核心优化:日志字符串拼接改行数组 + 节流刷新、月历/日志归档后台线程加载、文件句柄缓存、预编译正则、分页渲染、路径缓存、定时器按需启停。

Sources/FinDataHub/
├── AppConstants.swift                  # [修改] 新增 logDisplayPageSize 常量
├── Services/
│   ├── PythonProcessManager.swift      # [修改] P0-1 日志行数组+节流+预编译正则
│   ├── LogArchiveService.swift         # [修改] P0-4 文件句柄缓存+批量计数+readLastLines
│   ├── ConfigManager.swift             # [修改] P1-2 isConfigComplete 缓存
│   ├── EnvChecker.swift                # [修改] P1-1 checkQueue 改并发队列
│   ├── NewsFlashService.swift          # [修改] P1-3 路径缓存 lazy var
│   └── SchedulerService.swift          # [修改] P2 无任务时停 Timer
├── Views/
│   ├── SyncProgressView.swift          # [修改] P0-2 移除 withAnimation+缓存 isConfigComplete
│   ├── CalendarHistoryView.swift       # [修改] P0-3 后台加载+单次遍历+onAppear 异步
│   ├── LogArchiveView.swift            # [修改] P1-2 分页+后台查询+稳定 id+countLevels
│   └── (DashboardView.swift)           # 未改(固定静态数组 id: \.offset 稳定)

P0 — 严重性能瓶颈

项 文件 优化
P0-1 日志字符串拼接 O(n) PythonProcessManager.swift accumulatedLogs: String → logLines: [String] + scheduleLogFlush() 0.3s 节流;预编译 percentRegex/fractionRegex;addLog 用 removeFirst 替代 Array(suffix())
P0-2 大字符串 UI 渲染卡顿 SyncProgressView.swift onChange 滚动移除 withAnimation;缓存 isConfigComplete 避免每次 body 都做文件 I/O
P0-3 月历主线程 I/O CalendarHistoryView.swift loadMonthData() 移至 DispatchQueue.global(qos: .userInitiated);单次遍历统计 success/error/partial;DayDetailSheet 改 @State+onAppear 异步加载
P0-4 日志文件频繁 open/close LogArchiveService.swift 缓存 currentFileHandle+currentDateKey,跨天切换;logCountToday 批量更新(每 10 条或 1 秒);recentTaskSummaries 限制 7 天 + readLastLines(maxLines: 50) 只读末尾

P1 — 中等性能问题

项 文件 优化
P1-1 EnvChecker 串行队列 EnvChecker.swift checkQueue 改 attributes: .concurrent 并发队列
P1-1 日志统计 3 次 filter LogArchiveView.swift logStatsHeader 提取 countLevels(in:) 单次遍历(原内联 for+switch 违反 ViewBuilder,已拆为独立函数)
P1-2 isConfigComplete 重复 I/O ConfigManager.swift 新增 cachedConfigComplete 缓存,setter 中失效
P1-2 日志列表无上限渲染 LogArchiveView.swift 新增 displayLimit(默认 200)+「加载更多」按钮;loadLogs() 移至后台线程;ForEach id: \.offset → id: \.element.ts 稳定标识
P1-3 NewsFlash 路径重复计算 NewsFlashService.swift 新增 cachedPython/cachedQuantPython/cachedScriptBase lazy 缓存;findPython/findScript/fetchNewsFlash 复用缓存

P2 — 低优先级优化

项 文件 优化
P2 无任务时 Timer 空转 SchedulerService.swift registerTasks 仅在有任务启用时 startTimer;setEnabled 禁用最后一个任务时 stopTimer
P2 ForEach 稳定 id CalendarHistoryView.swift 日志列表 id: \.offset → id: \.ts

编译修复(过程中发现)

错误 文件 修复
cachedConfigComplete 未声明 ConfigManager.swift 补充 private var cachedConfigComplete: Bool? 属性声明
for+switch 不能用于 ViewBuilder LogArchiveView.swift logStatsHeader 内联循环提取为 countLevels(in:) 独立函数

指标统计

指标 优化前 优化后
编译错误 — 0
编译警告 — 0
日志刷新频率 每行 1 次 @Published 0.3s 批量 1 次
月历加载线程 主线程 后台 userInitiated
日志归档查询线程 主线程同步 后台 userInitiated
日志列表渲染 全量 分页 200 条/页
文件句柄 每次写 open/close 缓存复用
正则编译 每次调用编译 启动时预编译
路径计算 每次调用 fileExists lazy 缓存
定时器空转 总是运行 按需启停

2026-07-09 — 快讯筛选功能(Web 端 + Swift Mac App 端)

变更概述

为快讯模块实现完整筛选功能:Web 前端补充时间范围筛选(来源/关键词/分页已有),Swift Mac App 端新增来源/时间/关键词三维度筛选 + 分页加载。后端 dal.py 和 public.py 新增 start/end 时间范围参数;newsflash.py 重写支持 --source/--keyword/--hours/--limit/--offset 命令行参数。

FinDataHub/
├── Resources/
│   ├── bin/newsflash.py                      # [重写] argparse 筛选参数 + summary/importance 字段
│   └── kuaixun-frontend/
│       ├── dist/                             # [重建] 前端构建产物
│       └── src/
│           ├── api/index.js                  # [修改] fetchLiveNews 加 start/end 参数
│           ├── stores/news.js                # [修改] activeTimeRange + computeTimeRange + 传时间参数
│           └── views/LiveFeed.vue            # [修改] 时间范围筛选 UI + setTimeFilter + ClockIcon
├── Sources/FinDataHub/
│   ├── Models/NewsFlashService.swift         # [修改] NewsItem 补字段 + NewsFlashFilter + fetchNews/loadMoreNews
│   └── Views/NewsFlashView.swift             # [修改] 筛选栏 UI + 分页 + importance 徽章 + summary 显示
(外部项目修改)
├── finance-terminal/backend/kuaixun-service/
│   ├── app/dal.py                            # [修改] get_live_news 加 start_ms/end_ms + WHERE 子句
│   └── app/routers/public.py                 # [修改] /news/live 加 start/end 查询参数

1. 后端:时间范围筛选

dal.py get_live_news:

  • 函数签名新增 start_ms: int = 0, end_ms: int = 0
  • 构建 time_where WHERE 子句:AND published_at >= :start_ms AND published_at <= :end_ms
  • 参数化查询,避免 SQL 注入
  • 子查询和计数查询同时应用时间过滤

public.py /news/live:

  • 新增 start: int = 0, end: int = 0 查询参数
  • 透传给 dal_live(limit, offset, source, start, end)

2. Web 前端:时间范围筛选

api/index.js:

  • fetchLiveNews(limit, offset, source, start, end, opts) 新增 start/end 参数

stores/news.js:

  • 新增 activeTimeRange ref('', '1h', '24h', '3d', '7d')
  • 新增 computeTimeRange(range) 将范围代码转为毫秒时间戳
  • fetchLive / loadMore / fetchNew 均传递时间参数
  • fetchNew 在非 1h 时间范围时跳过轮询(历史数据不会新增)

LiveFeed.vue:

  • 新增 TIME_FILTERS 常量(全部/1小时/24小时/3天/7天)
  • 新增时间筛选栏 UI(ClockIcon + 5 个 pill 按钮)
  • setTimeFilter(code) 切换时间范围并重新加载
  • setSourceFilter 保留当前时间筛选
  • CSS .time-bar / .time-btn 与来源筛选栏风格统一

3. Swift Mac App:完整筛选

newsflash.py(重写):

  • argparse 支持 --source / --keyword / --hours / --limit / --offset
  • SQL 查询新增 s.code / f.summary / f.importance 字段
  • WHERE 条件按参数动态拼接(来源 code、关键词 LIKE、时间范围毫秒时间戳)
  • 有筛选时跳过来源统计/趋势/今日统计查询(减少开销)
  • 返回 total 字段供分页判断

NewsFlashService.swift:

  • NewsItem 补 source_code / summary / importance 字段
  • NewsFlashData 补 total 字段
  • 新增 NewsFlashFilter 结构(source/keyword/hours/limit/offset)
  • 新增 @Published newsList / newsTotal / currentFilter / hasMoreNews
  • 新增 fetchNews(filter:) 首页查询 + loadMoreNews() 分页 + fetchNewsFiltered(_:) 子进程调用

NewsFlashView.swift:

  • 筛选栏 UI:来源 Picker(8 源 + 全部)+ 时间 Picker(全部/1h/24h/3天/7天)+ 关键词 TextField(0.5s 防抖)+ 清除按钮
  • 快讯列表改用 nf.newsList(筛选结果),支持「加载更多」分页
  • 无筛选时显示统计/来源/趋势;有筛选时只显示列表
  • newsCard 新增 importance 徽章(≥4 紧急红、≥3 重要金)+ summary 优先显示

指标统计

指标 值
编译错误 0
编译警告 0
前端构建 828ms
Swift 编译 2.46s
.app 大小 11M
新增筛选维度 来源 × 时间 × 关键词
Swift 分页 50 条/页
Web 分页 50 条/页(无限滚动)

2026-07-09 — 快讯 AI 评分/摘要 + 当日总结 + 热度分析

变更概述

为快讯模块接入 AI 能力:采集时自动调用 AI 评分(0-5 分)和生成摘要;新增每日总结脚本(每小时自动更新);Swift 端展示热度分布、高热度 Top5 和当日总结。

FinDataHub/
├── Resources/bin/
│   ├── newsflash_collect.py                 # [修改] 采集后调用 batch_score_items + summarize_items
│   ├── newsflash.py                         # [修改] 新增 importance_dist / hot_top5 / daily_summary 查询
│   └── daily_summary.py                     # [新增] 当日总结脚本(每小时生成,MySQL daily_summary 表)
├── Sources/FinDataHub/
│   ├── Models/NewsFlashService.swift        # [修改] NewsFlashData 补字段 + HotItem / DailySummary 结构
│   ├── Views/NewsFlashView.swift            # [修改] 新增当日总结 / 热度分布 / 高热度 Top5 展示
│   └── Design/FinDesign.swift               # [修改] 新增 statusGold 颜色
(外部项目修改)
├── finance-terminal/backend/kuaixun-service/
│   ├── extend/summarizer.py                 # [复用] summarize_items / summarize
│   └── extend/scorer.py                     # [复用] batch_score_items / score_news

1. 采集时 AI 评分 + 摘要

newsflash_collect.py:

  • sys.path 注入 kuaixun-service 目录
  • _AI_AVAILABLE 检测 summarizer/scorer 是否可用
  • collect_once() 流程改为:采集 → AI 评分 → AI 摘要 → 分组写入
  • batch_score_items():并发评分(semaphore=3),返回带 importance 字段的 items
  • summarize_items():并发生成摘要(semaphore=5),返回带 summary 字段的 items
  • 统计新增 ai_scored / ai_summarized 字段
  • AI 异常不阻塞采集循环(静默跳过)

2. 当日总结脚本

daily_summary.py(新增):

  • ensure_table():自动创建 daily_summary 表(date, summary, stats, hot_topics, updated_at)
  • get_today_stats():查询今日总数、来源分布、热度分布(importance 0-5)、高热度 Top10、每小时频率
  • generate_summary():基于高热度 Top8 生成 AI 当日总结(分 3-5 个要点)
  • save_summary():INSERT ON DUPLICATE KEY UPDATE 更新当日总结
  • query_summary():查询指定日期的总结(--date 参数)
  • 定时模式:每小时准点执行(计算下一小时零点的 sleep 秒数)
  • 支持 start/stop/status/once/query 命令

3. 热度统计 + 总结查询

newsflash.py:

  • 新增 importance_dist:全库热度分布(importance → count)
  • 新增 hot_top5:高热度快讯 Top5(importance ≥ 3)
  • 新增 daily_summary:当日总结(summary + updated_at)

4. Swift 端展示

NewsFlashService.swift:

  • NewsFlashData 补 importance_dist / hot_top5 / daily_summary 字段
  • 新增 HotItem 结构(source_name, title, summary, importance, published_at)
  • 新增 DailySummary 结构(summary, updated_at)

NewsFlashView.swift:

  • dailySummarySection():当日总结卡片(sparkles 图标 + 更新时间 + 摘要文本)
  • importanceDistSection():热度分布柱状图(5 级:重大红/重要橙/一般金/次要灰/普通)
  • hotTop5Section():高热度 Top5 列表(🔥/⚡/⭐ 图标 + 来源标签 + 标题)
  • 新增 FinDesign.statusGold 颜色

FinDesign.swift:

  • 新增 statusGold = Color(red: 1.0, green: 0.78, blue: 0.20)

AI 模型配置(沿用 kuaixun-service .env)

变量 当前值
AI_PROVIDER ollama
AI_API_URL http://localhost:11434/api/generate
AI_MODEL qwen2.5:0.5b
AI_API_KEY (空,Ollama 不需要)

评分标准(0-5 分):

  • 5 = 重大影响(央行政策、重大经济数据、黑天鹅事件)
  • 4 = 重要影响(行业政策、大型公司重大事件)
  • 3 = 一般重要(普通财经新闻、公司公告)
  • 2 = 次要(市场日常波动、小道消息)
  • 1 = 不重要(娱乐化、无实质内容)
  • 0 = 无法判断/非财经

指标统计

指标 值
编译错误 0
编译警告 0
Swift 编译 2.64s
.app 大小 11M
AI 评分并发 3
AI 摘要并发 5
总结更新频率 每小时
热度维度 5 级(0-5)

2026-07-09 — 多模型分任务配置

变更概述

为 AI 评分/摘要/当日总结配置不同的 Ollama 模型,实现分任务调用 + 并行执行。评分和摘要用 qwen2.5:7b(平衡速度与质量),当日总结用 qwen3.6:35b(最强分析能力,每小时一次)。

(外部项目修改)
├── finance-terminal/backend/kuaixun-service/
│   ├── extend/scorer.py                     # [修改] 新增 AI_SCORER_MODEL 环境变量
│   ├── extend/summarizer.py                 # [修改] 新增 AI_SUMMARIZER_MODEL 环境变量
│   └── .env                                 # [修改] 添加分任务模型配置

FinDataHub/
└── Resources/bin/
    ├── daily_summary.py                     # [修改] 新增 AI_SUMMARY_MODEL,直接调用 Ollama API
    └── test_multi_model.py                  # [新增] 多模型并行测试脚本

1. scorer.py 分任务模型

  • 新增环境变量 AI_SCORER_MODEL(优先级高于 AI_MODEL)
  • _score_ollama / _score_openai / _score_deepseek / _score_qwen 全部改用 AI_SCORER_MODEL
  • 默认值回退到 AI_MODEL

2. summarizer.py 分任务模型

  • 新增环境变量 AI_SUMMARIZER_MODEL
  • summarize_ollama / summarize_openai 改用 AI_SUMMARIZER_MODEL

3. daily_summary.py 当日总结模型

  • 新增环境变量 AI_SUMMARY_MODEL
  • generate_summary() 直接调用 Ollama HTTP API(绕过 summarize() 函数),使用 AI_SUMMARY_MODEL
  • 超时设为 60s,num_predict=200

4. .env 配置

# 通用模型(回退)
AI_MODEL=qwen2.5:0.5b

# 评分模型(推荐 qwen2.5:7b,评分准确)
AI_SCORER_MODEL=qwen2.5:7b

# 摘要模型(推荐 qwen2.5:7b,中文流畅)
AI_SUMMARIZER_MODEL=qwen2.5:7b

# 当日总结模型(推荐 qwen3.6:35b,每小时一次用大模型)
AI_SUMMARY_MODEL=qwen3.6:35b

5. 多模型并行测试

test_multi_model.py(新增):

  • 并行调用三个模型(评分/摘要/总结)
  • 验证配置正确性和并行优势
  • 测试结果:qwen2.5:7b 0.1s 响应,qwen3.6:35b 需预热(首次加载慢)

任务与模型映射

任务 环境变量 模型 频率 原因
快讯评分 AI_SCORER_MODEL qwen2.5:7b 每条 评分准确,速度快
快讯摘要 AI_SUMMARIZER_MODEL qwen2.5:7b 每条 中文流畅,性价比优
当日总结 AI_SUMMARY_MODEL qwen3.6:35b 每小时 分析力强,慢点无所谓

指标统计

指标 值
新增环境变量 3 个
模型并行 asyncio.gather
qwen2.5:7b 响应 0.1s
qwen3.6:35b 首次加载 需预热

2026-07-09 — Web 端评分/摘要/当日总结展示

变更概述

在 Web 前端规划并实现三个 AI 功能的展示位置:当日总结卡片(LiveFeed 顶部)、热度分布柱状图(Stats 页)、快讯摘要默认显示(NewsCard)。后端新增 2 个 API 端点 + 去掉摘要字数限制。

(外部项目修改)
├── finance-terminal/backend/kuaixun-service/
│   ├── app/dal.py                            # [修改] 新增 get_importance_dist + get_daily_summary
│   ├── app/routers/public.py                 # [修改] 新增 /news/summary + /news/heat 端点
│   └── extend/summarizer.py                  # [修改] 去掉 PROMPT 字数限制 + [:200] 截断 + num_predict/max_tokens 限制

FinDataHub/Resources/kuaixun-frontend/
├── src/api/index.js                          # [修改] 新增 fetchDailySummary + fetchHeat
├── src/stores/news.js                        # [修改] 新增 dailySummary/importanceDist 状态 + loadDailySummary/loadHeat
├── src/views/LiveFeed.vue                    # [修改] 顶部加当日总结卡片 + 10 分钟刷新
├── src/views/Stats.vue                       # [修改] 加热度分布柱状图(6 级)
└── src/components/NewsCard.vue               # [修改] 摘要默认显示(不展开也显示)+ 不限制字数

1. 后端 API

dal.py 新增函数:

  • get_importance_dist() — 查询全库 importance 0-5 分布
  • get_daily_summary(date_str) — 从 daily_summary 表读取当日总结

public.py 新增端点:

  • GET /api/v1/news/summary?date=YYYY-MM-DD — 当日总结
  • GET /api/v1/news/heat — 热度分布

2. 摘要去字数限制

summarizer.py 修改:

  • PROMPT:"一句话概括(≤30字)" → "请用简洁的语言概括以下新闻的要点"
  • 去掉 [:200] 字符截断(ollama + openai 两处)
  • 去掉 num_predict: 50 和 max_tokens: 60 限制

3. 前端展示位置

功能 位置 说明
当日总结 LiveFeed.vue 顶部 金色渐变卡片,sparkles 图标 + 更新时间,10 分钟自动刷新
热度分布 Stats.vue 6 级柱状图(重大红/重要橙/一般金/次要灰/普通深灰/未评暗灰)
快讯摘要 NewsCard.vue 默认显示在标题下方(无需展开),蓝色文字,不限制字数

指标统计

指标 值
新增 API 端点 2 个
前端构建 871ms 成功
摘要字数限制 已移除
当日总结刷新 10 分钟
热度级别 6 级

2026-07-09 — Web 端不显示问题修复 + qwen3 推理模型适配

变更概述

用户反馈"Web 端没有变化"。排查发现根因有二:(1) 打包 App 内 kuaixun-frontend/dist 是旧版前端(19:36 构建,无当日总结卡片),后端 FRONTEND_DIR 优先使用打包路径,导致前端不更新;(2) 后端进程(pid 84231)启动于代码修改之前,内存中仍是旧 public.py,新端点 /news/summary、/news/heat 未注册(返回 SPA 回退 {"error":"not_found"})。同时发现 daily_summary.py 调用 qwen3.6:35b 时使用 /api/generate,该推理模型将全部 token 用于 thinking 字段,response 为空(done_reason:"length"),导致当日总结生成空内容。

排查过程

  1. 端口误判纠正:后端默认端口 8100(BACKEND_PORT env),此前 curl 测试的 8000 是另一个服务 ds4-serve,非本后端
  2. 打包 dist 过期:/Applications/FinDataHub.app/.../kuaixun-frontend/dist(19:36)无新前端代码;dev 路径 Resources/kuaixun-frontend/dist(21:30)有新代码
  3. 后端进程陈旧:运行中的 newsflash_backend.py(pid 84231)import 自 kuaixun-service(已含新 dal.py/public.py),但进程启动早于修改,Python 不热重载
  4. qwen3 推理模型空响应:/api/generate + num_predict:200 → 全部 token 进入 thinking,response 为空;改用 /api/chat + think:false → 正常返回

修复内容

修改文件:
├── Resources/bin/daily_summary.py          # 📝 generate_summary 改用 /api/chat + think:false
└── build-app.sh                             # 📝 补充打包 daily_summary.py(cp + chmod)

1. daily_summary.py — qwen3 推理模型适配

  • generate_summary() 从 /api/generate(prompt 字段)改为 /api/chat(messages 字段 + think:false)
  • 响应解析从 resp["response"] 改为 resp["message"]["content"]
  • timeout 60→120s,num_predict 200→500,截断 500→800 字
  • 验证:qwen3.6:35b 生成 260 字总结(4 个要点 + 5 个热点)

2. build-app.sh — 补全 daily_summary.py 打包

  • 新增 cp daily_summary.py + chmod +x(此前打包脚本遗漏此文件)

3. 部署同步

  • 重新打包(11M,零错误)→ 杀进程 → 替换 /Applications/FinDataHub.app → 重启后端
  • 新端点验证:/news/summary HTTP 200(260字),/news/heat HTTP 200(6级分布),前端 HTTP 200

遗留问题

kuaixun-service/.env 配置 AI_SCORER_MODEL=qwen2.5:7b / AI_SUMMARIZER_MODEL=qwen2.5:7b,但该模型未在本地 Ollama 拉取(仅有 qwen2.5:0.5b)。影响:新采集快讯的 AI 评分/摘要会失败(模型不存在)。注:qwen2.5:7b 非推理模型,/api/generate 对其正常,无需 think:false 适配。待用户决定:拉取 qwen2.5:7b 或切换至已下载模型。

指标统计

指标 值
排查命令 8 次 curl/lsof/grep
重新打包 2 次(11M)
后端重启 2 次
当日总结生成 1310 条快讯 → 260 字总结
编译警告/错误 0 / 0

2026-07-09 — 快讯标签功能(AI 标签 + 热度标签 + 来源标签 + 收藏)

变更概述

完整实现四类标签体系:AI 自动标签、热度/重要性标签、来源标签、用户收藏,覆盖后端数据库/API/AI打标 + 前端显示/筛选/交互。

后端(finance-terminal/kuaixun-service)

新增文件:
├── app/extend/tagger.py               # ✨ AI 自动打标签模块
└── scripts/migrate_tags.py            # ✨ 数据库迁移脚本

修改文件:
├── app/database.py                    # 📝 _make_source_table 加 summary/tags/category + NewsFavorite 模型
├── app/dal.py                         # 📝 标签聚合/更新/筛选 + 收藏 CRUD + tags JSON 反序列化
├── app/routers/public.py              # 📝 /news/tags + /news/favorite + /news/favorites + live 加 tag/importance 参数
└── app/routers/internal.py            # 📝 /news/untagged + /news/tags(批量写回)

1. 数据库迁移

  • 12 张分表新增 tags TEXT、category VARCHAR(50) 字段(summary 已存在的跳过)
  • 新建 news_favorites 收藏表(user_id + source_code + news_id 联合唯一索引)
  • 迁移脚本幂等安全,已存在的列自动跳过

2. AI 自动打标签(tagger.py)

  • AI_TAGGER_MODEL 环境变量,回退到 AI_MODEL
  • tag_item(title, content) → 2-5 个财经标签
  • batch_tag_items(items, concurrency=3) → 批量并发打标签
  • 使用 /api/chat + think:false(兼容 qwen3 推理模型)
  • 预置 20 个财经标签分类:宏观政策/央行/利率/A股/美股/港股/财报/并购/IPO/监管/行业动态/科技/新能源/半导体/地产/消费/医药/能源/大宗商品/外汇
  • AI 异常 try/except 包裹,不阻塞采集主流程

3. DAL 层

  • get_tags_list(limit=50) → 全库标签聚合统计(top 50)
  • batch_update_tags(source_code, items) → 批量更新 tags
  • get_unscored_for_tagging(limit) → 待打标签记录
  • get_live_news 新增 tag / importance 筛选参数
  • toggle_favorite / get_favorites / is_favorited → 收藏 CRUD
  • 返回数据时 tags 字段从 JSON 字符串反序列化为数组

4. API 端点

  • GET /news/tags → 标签云 {tags: [{name, count}]}
  • GET /news/live?tag=xxx&importance=5 → 标签 + 热度筛选
  • POST /news/favorite → 切换收藏
  • GET /news/favorites → 收藏列表(分页)
  • GET /news/favorites/check → 单条收藏检查
  • GET /news/untagged + POST /news/tags → 内部打标接口

前端(kuaixun-frontend)

新增文件:
└── src/components/TagCloud.vue        # ✨ 标签云组件(3 档字号,可点击选中)

修改文件:
├── src/api/index.js                   # 📝 新增 5 个 API + fetchLiveNews 加 tag/importance 参数
├── src/stores/news.js                 # 📝 标签云/热度筛选/收藏状态管理 + 7 个方法
├── src/components/NewsCard.vue        # 📝 收藏按钮 + 热度标签 + AI 标签可点击
└── src/views/LiveFeed.vue              # 📝 热度筛选栏 + 标签云筛选区 + 收藏入口

1. NewsCard 卡片

  • meta 行新增收藏按钮(心形图标,hover 显示,收藏红色实心)
  • 热度标签替代旧 badge:5级红/4级橙/3级金/2级灰/1级深灰/0级隐藏
  • AI 标签改为可点击胶囊(点击跳转到标签筛选)
  • 来源标签保持不变

2. TagCloud 标签云组件

  • 流式布局,3 档字号(top10 最大 / 11-30 中等 / 其余最小)
  • 选中高亮金色主题,显示标签名 + 数量
  • emit 'select' 事件

3. LiveFeed 筛选体系

  • 时间筛选栏下方新增热度筛选栏(全部/重大/重要/一般/次要/普通)
  • 再下方新增标签筛选区(卡片式容器 + TagCloud + 清除按钮)
  • 收藏筛选入口(显示我的收藏)
  • 筛选状态摘要条(有筛选时金色提示 + 清除全部)
  • 来源/时间/热度/标签/收藏 五维组合筛选

采集器集成(FinDataHub)

修改文件:
├── Resources/bin/newsflash_collect.py   # 📝 采集流程新增 batch_tag_items 步骤
├── Resources/bin/backfill_tags.py       # ✨ 历史数据补打标签脚本
└── build-app.sh                         # 📝 新增打包 daily_summary.py(上次遗漏补充)
  • collect_once() 流程:评分 → 摘要 → 打标签 → 入库
  • 分表 INSERT 新增 tags/category 字段写入
  • 历史数据补打脚本:importance≥3 的高热度快讯,并发 3,qwen3.6:35b 模型
  • AI 异常不阻塞采集(项目硬约束)

验证结果

指标 值
数据库迁移 12 张分表全部成功
标签云 49 个标签(行业动态107、科技82、A股60...)
标签筛选 正常返回(如"大宗商品"返回匹配快讯)
热度筛选 importance=5 返回 3829 条 ✅
AI 打标签 测试输出:['美股', '科技', '半导体'] ✅
前端构建 832ms 成功
历史补打 后台进行中(qwen3.6:35b,约 1.5 万条高热度快讯)

环境配置

在 .env 中可配置:

AI_TAGGER_MODEL=qwen3.6:35b   # 打标签专用模型(可选,默认同 AI_MODEL)

2026-07-09 — 标签准确度 + 摘要质量提升(SearXNG 内容增强 + 模型配置修复)

变更概述

针对用户反馈"标签分配有问题、摘要不准确",定位并修复多个根因:采集器未加载 .env 导致 AI 全用 0.5b 小模型、scorer 用 /api/generate 对 qwen3 推理模型空响应、_parse_tags 未处理分号。新增 SearXNG 内容增强模块(抓取原文全文 + 搜索背景信息),让 AI 基于增强内容打标签和生成摘要,大幅提升准确度。

根因分析

根因 影响 修复
newsflash_collect.py 未加载 .env AI 全部回退到 qwen2.5:0.5b(0.4GB 小模型),标签/摘要质量极差 新增 _load_env() 加载 kuaixun-service/.env
.env 配置 AI_SCORER/SUMMARIZER_MODEL=qwen2.5:7b 模型不存在(下载未完成),AI 调用失败 改为 qwen3.6:35b(已下载)
.env 未设 AI_TAGGER_MODEL tagger 回退到 0.5b 新增 AI_TAGGER_MODEL=qwen3.6:35b
scorer.py 用 /api/generate + prompt 格式 qwen3.6:35b 推理模型 token 全进 thinking,response 为空,评分全返回 0 改用 /api/chat + messages + think:false
_parse_tags 正则未处理分号 ; AI 返回分号分隔标签时,整个文本被当作单个标签 正则加分号:[,,、;;\s]+

新增模块

extend/content_enricher.py(SearXNG + 原文抓取)

新增文件:
└── extend/content_enricher.py   # ✨ 内容增强模块
  • fetch_article_text(url, timeout=8) — httpx 抓取原文 HTML,Python 标准库 HTMLParser 提取正文(跳过 script/style/nav/header/footer),返回最多 2000 字
  • search_context(query, limit=3) — 调用 SearXNG {SEARXNG_URL}/search?q=...&format=json&engines=google,bing&language=zh-CN&categories=news,返回最多 500 字背景信息
  • enrich_item(item) — 并发抓取原文 + 搜索背景,新增 full_text/context/enriched_content 字段,失败降级
  • batch_enrich_items(items, concurrency=3) — 批量增强,带并发控制
  • 不依赖 bs4/lxml/trafilatura(trafilatura 编译太慢),纯标准库实现

修改文件

文件 改动
extend/scorer.py 加 _get_chat_api_url();_score_ollama 改用 /api/chat + messages + think:false,读 message.content
extend/summarizer.py PROMPT 改为"根据全文内容生成摘要";截断 400→1500;summarize 签名加 title 参数;summarize_items 优先用 enriched_content/full_text;内部自动 enrich
extend/tagger.py TAG_PROMPT 加 full_text/context 占位符 + 第4条要求"综合考虑标题、正文和背景";tag_item 签名加 full_text/context 参数;batch_tag_items 内部自动 enrich;_parse_tags 正则加分号
newsflash_collect.py 新增 _load_env() 加载 .env;collect_once 集成 enrich 步骤(score→enrich→summarize→tag);只对新增快讯做 AI(查库过滤已存在 source_news_id,跳过重复处理)
.env AI_SCORER_MODEL/SUMMARIZER_MODEL: qwen2.5:7b→qwen3.6:35b;新增 AI_TAGGER_MODEL=qwen3.6:35b

效率优化:只对新增快讯做 AI

  • 采集后在 AI 处理前,按源批量查库 SELECT source_news_id FROM {per_table} WHERE source_news_id IN (...),过滤已存在快讯
  • 验证:130 条抓取 → 99 条已存在跳过 → 只对 31 条新快讯做 AI
  • AI 处理时间从 10+ 分钟降至 3-4 分钟(31 条 × 4 步 × qwen3.6:35b)

验证结果

指标 修复前 修复后
AI 模型 qwen2.5:0.5b(.env 未加载) qwen3.6:35b
摘要质量 "新闻要点:..." 泛化 准确概括核心内容(如"俄罗斯萨拉托夫炼油厂遭无人机袭击后暂停运营")
标签示例(CFTC阻止CME原油期货) ["宏观政策","央行","利率","A股","美股"] 泛化 ["监管","能源","大宗商品"] 精准
标签示例(A股业绩公告) ["宏观政策","央行","利率","A股","美股"] 泛化 ["A股","财报","科技","新能源"] 精准
scorer 响应 空响应→评分全0 正常返回 0-5 分
_parse_tags 分号 整串作为单标签 正确 split
重复快讯处理 全部处理(80-150条) 只处理新增(5-31条)
once 测试 31 条全部成功,0 错误 ai_scored=31, ai_enriched=31, ai_summarized=31, ai_tagged=31

环境

  • SearXNG: Docker 容器 searxng,端口 8888,需 engines=google,bing&language=zh-CN&categories=news
  • Ollama: qwen3.6:35b(23.9GB),统一用于评分/摘要/打标签,均用 /api/chat + think:false
  • 采集器 pid 71677 运行中,后端 pid 67222 运行中

2026-07-09 — 历史快讯 AI 回填(backfill_ai.py)

变更概述

针对历史快讯未经 AI 分析或分析质量低的问题(15133 条 importance=0),新建 backfill_ai.py 回填脚本,分阶段对历史快讯做完整 AI 分析:阶段一全量评分,阶段二对高热度快讯(importance≥3)强制重新生成摘要+标签。同时修复 content_enricher.py 的 SearXNG 搜索缺少 categories=news 参数的问题。

根因

  • 历史快讯 15133 条 importance=0(分表统计),从未经过 AI 评分
  • 部分历史快讯有 0.5b 小模型生成的低质量摘要("新闻要点总结如下..."泛化模板)
  • content_enricher.py 代码行(第169行)缺少 categories=news,SearXNG 返回百科/导航噪声

新增文件

文件 说明
Resources/bin/backfill_ai.py 历史快讯 AI 回填脚本(分阶段:评分→增强+摘要+标签)

修改文件

文件 变更
finance-terminal/.../extend/content_enricher.py search_context 代码行补充 categories=news 参数

backfill_ai.py 设计

分阶段策略:

  1. 阶段一·评分:对所有 importance=0 的快讯做 AI 评分(batch_score_items,并发3),更新 importance 字段
  2. 阶段二·增强:对 importance≥阈值 的快讯做内容增强 + 摘要 + 标签(batch_enrich_items + summarize_items + batch_tag_items)

关键特性:

  • --force 参数:强制重新生成摘要和标签(即使已有),用于覆盖 0.5b 小模型的低质量数据
  • --phase score|enrich|all:可选择单独执行评分或增强阶段
  • --table/--limit/--batch-size/--min-importance:灵活控制处理范围
  • --dry-run:只统计待处理数量不执行 AI
  • 同时更新分表和 news_flash 主表,保持数据一致
  • 断点续传:每批处理后立即写入数据库,中断后重跑会跳过已处理的

数据一致:update_ai_fields 同时 UPDATE 分表(按 id)和 news_flash 主表(按 source_news_id)

验证结果

小批验证(news_wallstreetcn 表):

阶段 数量 成功 失败 耗时 质量
评分 10 10 0 25.6s importance 3-5 合理分布
增强(--force) 5 5 0 48.1s 摘要含具体细节,标签精准

摘要质量对比:

  • 修复前(0.5b):"新闻要点总结如下:\n\n1. OpenAI最新AI模型:..."
  • 修复后(35b+全文增强):"Meta推出首款付费AI开发者模型Muse Spark 1.1,首次向企业收取访问费用..."

标签质量对比:

  • 修复前:泛化的"宏观政策/央行/利率"
  • 修复后:精准的"地缘政治/能源/大宗商品/外汇"

全量回填执行

  • 阶段一·评分:7 张分表共 15133 条,后台运行(PID 73408)
  • 阶段二·增强:评分完成后自动对 importance≥3 的 --force 重新生成(监控脚本 PID 73958)
  • 日志:/tmp/backfill_all.log

分表待处理统计

表 待评分 待增强(≥2)
news_sina 6823 3490
news_cls 4338 3887
news_wallstreetcn 246 172
news_36kr 1 0
news_gelonghui 892 2838
news_gnews 687 2840
news_newsapi 2072 1798
合计 15059 15025

2026-08-24 — 测试缺口分析:RSS 解析 + 日期解析 + socket 超时 + get_yearly SQL(62 个)

变更概述

审查近期合并的代码(安全审计修复 4b62693 + akshare 同步修复 bace1b9),在覆盖率缺口对产品稳定性构成实质风险的地方补充测试。akshare 同步修复的代码路径(ensure_market_table、_aggregate_ohlcv、max_stocks 截断)在先前的测试补齐中已被充分覆盖。本次聚焦三个完全无测试覆盖的区域:RSS 采集器的纯解析函数、insights 模块的多分支日期解析、以及安全审计引入的 socket 超时 DoS 防护和 get_yearly f-string SQL 构造。

详细条目

1. test_rss_fetcher.py — 新建文件(47 个测试)

rss_fetcher.py 的 RssFetcher 类包含 5 个纯解析方法(无网络/无 DB 依赖),此前完全无测试覆盖。这些方法涉及 XML 解析、日期解析、关键词分类和数据验证,是快讯采集链路的入口逻辑。

  • TestGenId(4 个):MD5 稳定 ID 生成的确定性、不同输入产生不同 ID、长度截取为 16 字符、空字符串容错
  • TestParsePubDate(5 个):RFC 822 带时区解析(+0800/+0000)、ISO 格式 fallback(无时区按 UTC)、空字符串返回当前时间、无效格式返回当前时间
  • TestCategorize(28 个,参数化):4 类关键词(宏观/商品/科技/股票)各 6-8 个关键词逐一验证、无匹配默认 macro、content 中关键词也能命中、宏观优先于商品
  • TestExtractTags(3 个):多标签提取、无标签空列表、空标题空列表
  • TestParseRssXml(7 个):多 item 完整解析(验证 title/source_id/category/url)、无 channel 返回空、无 title 的 item 跳过、空 description 产生空 summary、XML 格式错误返回空不抛异常、source_news_id 基于 link 的 MD5、标签从 title 自动提取

2. test_insights.py — 新建文件(11 个测试)

insights.py 的 parse_date 函数被 overview_db 和 query_latest_date 调用,是数据洞察模块的日期处理核心。该函数有 5+ 分支(None/datetime/8位数字/长字符串/短字符串),此前完全无测试覆盖。

  • test_none_returns_none:None 输入返回 None
  • test_empty_string_returns_none:空字符串返回 None(falsy 分支)
  • test_datetime_object_formatted:datetime 对象格式化为 YYYY-MM-DD HH:MM:SS
  • test_eight_digit_string_to_date:8 位纯数字格式化为 YYYY-MM-DD
  • test_nineteen_char_string_truncated:>=19 字符截取前 19 位
  • test_ten_char_string_truncated:10-18 字符截取前 10 位
  • test_short_string_returned_as_is:<10 字符原样返回
  • test_whitespace_stripped_before_processing:前后空白被 strip
  • test_non_date_eight_digits_still_formatted:8 位非日期数字仍按格式拼接(函数不做语义校验)
  • test_int_input_converted_to_string:整数输入经 str() 转换后走字符串分支
  • test_fourteen_digit_int_truncated_to_10:14 位整数截取前 10 位

3. test_api.py — 新增 TestSocketTimeout + TestGetYearlySql(4 个测试)

安全审计提交(4b62693)在 api.py 模块级添加 socket.setdefaulttimeout(30) 作为 DoS 防护(tushare SDK 的 requests 调用未设置 timeout,服务端挂起会导致线程池永久阻塞)。此前无任何测试验证该超时是否生效。同时 get_yearly 方法使用 f-string 将 year 拼入 SQL,现有测试通过 mock pd.read_sql 跳过了 SQL 构造验证。

  • TestSocketTimeout.test_default_timeout_is_30:验证模块导入后 socket.getdefaulttimeout() == 30
  • TestGetYearlySql.test_sql_contains_correct_year_range:捕获 SQL 字符串,验证包含正确的年份日期范围('20260101' / '20261231')
  • TestGetYearlySql.test_year_truncated_from_long_trade_date:验证 trade_date[:4] 截取行为(超长输入仅取前 4 位)
  • TestGetYearlySql.test_different_years_produce_different_ranges:不同年份生成对应日期范围

不稳定测试标记

  • TestThrottle::test_sleeps_when_within_interval(预先存在,非本次引入):断言 sleeps[0] < 0.3 使用严格小于,当 time.time() 连续两次返回相同值时 elapsed == 0 导致 interval - elapsed == 0.3,断言失败。此为系统级时间精度问题,建议改为 <= 或 pytest.approx。

新增/修改文件

文件 操作 测试数
Scripts/bin/tests/test_rss_fetcher.py 新建 47
Scripts/bin/tests/test_insights.py 新建 11
Services/tushare_fetcher/tests/unit/test_api.py 追加 4

编译状态

  • Scripts/bin 测试:186 passed(含 47+11=58 新增)0 failed
  • tushare_fetcher API 测试:31 passed + 1 pre-existing flaky(非本次引入)0 new failures

指标统计

套件 总数 通过 失败 新增 耗时
Scripts/bin 全量 186 186 0 58 0.21s
tushare API 32 31 1* 4 0.29s
合计 218 217 1* 62 0.50s

* 标记为预先存在的不稳定测试,非本次引入

2026-09-02 — kuaixun L5 embedding 语义去重:真实数据离线评估 + 数值冲突护栏

背景

L5(embedding 语义层,qwen3-embedding:8b / 4096 维 / 阈值 0.80)落地后,需验证真实命中量。 本机 MySQL root 密码不在项目内(Homebrew 安装,凭据在库内),连库评估不可行, 改用运行中 kuaixun 服务的公开接口 /api/v2/news/live(无鉴权,直读 news_unified 去重表)做只读评估。

真实语料评估结论(1899 条 / 14 来源,全量重放,~17 分钟)

阈值 l5_hit 占语料
0.75 436 (22.96%) 同上
0.80 317 (16.69%) 同上
0.85 194 (10.22%) 同上

关键发现:0.80 阈值下 317 对命中里 83 对(26.2%)是「同模板只改数字」的行情系列 (原油夜盘 601 vs 614 元、白银ETF 减16.87吨 vs 增14.06吨、股指开盘 vs 收盘、 美联储逆回购 4.56亿 vs 67.26亿…),余弦 0.83–0.99 全区间分布,阈值调高无法分离。 直接合并会丢弃合法独立快讯(不同日/不同时点数据),属精度不可接受。

修复:数值冲突护栏(EMBEDDING_NUMERIC_GUARD,默认开启)

  • extend/unified_writer.py:
    • 新增 NUMERIC_UNIT_PATTERN / extract_numeric_units()——只认「数字+单位/百分号」 token(% 亿 元 吨 点 家 人 艘 千瓦 基点…),排除代码数字(中证500/沪深300/IF2509);
    • _emb_cache 升级为 3 元组 (unified_id, vec, 原文);
    • _check_l5(query_emb, query_text):余弦 ≥ 阈值且带单位数字集合不相交 → 视为不同数据点, 跳过该候选继续扫描,不合并。
  • extend/pipeline.py:接入 EMBEDDING_NUMERIC_GUARD env(默认 "1")。
  • tests/test_unified_writer_unit.py:+6 用例(互斥拦截/共享数字合并/无数值合并/单位提取/流程级拦截/关闭后合并)。 kuaixun 相关单测 41 个全过。

护栏效果(真实 317 对重算)

  • 拦截 83 对(26.2%)→ 护栏后 l5_hit ≈ 234 条(12.32% 语料),被拦者全部为应保留的独立数据点;
  • 放行侧低余弦边缘(0.80–0.82)目检均为真跨源重复(塞浦路斯渡船、智利四国矿产声明、冰岛入盟、伊朗总统)。

已知残余风险

  • 纯文字时点类(「三大指数翻红」vs「开盘涨跌不一」,无数值)仍可能误合并;护栏只处理 数值互斥这一最大误合并类。已随 l5_hit 统计上线后持续观测。
  • 全量 pytest 40 个失败为沙箱拦截 pytest 临时目录(sitecustomize EEXIST,/private/var/folders), 与本次改动无关;涉改文件全过。

备注

  • qwen3-embedding:8b 当前 ~1.3 条/s(CPU 推理,与 batch 无关);全量 1899 条 embedding ≈ 8.7 分钟。
  • 评估脚本 /tmp/l5_offline_eval.py(可复用,输出 /tmp/l5_offline_eval_result.json)。

2026-09-02(续)— L5 模型选型实测:bge-m3 vs qwen3-embedding

吞吐(决定性差异)

模型 维度 实测速率 全量1947条embed 单批500条估时
qwen3-embedding:8b 4096 ~1.3 条/s(CPU) 520 s ~6.4 分钟
bge-m3:latest (F16 566M) 1024 62–76 条/s 31 s ~8 秒

语义标定(手工对照)

分组 qwen3 bge-m3
明显同义 0.85–0.90 0.86
同事件异角度 0.74–0.78 0.73
同主题异事件 0.62–0.69 0.58
不同主题 0.41–0.45 0.36
行情模板(应拦) — 0.96

真实语料三档(1947 条,raw vs 数值护栏后)

阈值 qwen3 raw bge-m3 raw bge-m3 护栏后
0.75 436 (22.96%, 1899语料) 461 (23.68%) 352 (18.08%)
0.80 317 (16.69%) 317 (16.28%) 239 (12.28%)
0.85 194 (10.22%) 187 (9.60%) 136 (6.99%)

质量目检

  • 护栏拦截 Top12 全对(白银ETF减/增、股指开盘vs收盘、RRP 不同日、原油夜盘 601vs614、欧元区CPI 2.1%vs2.4%、尼泊尔伤亡数更新、WTI/燃油/纯苯 价格更新)——bge-m3 下护栏依然必要且有效;
  • 放行合并高余弦端多为其重复(新疆电网、光伏装机、央行逆回购、澳洲国债);低余弦边缘(0.81–0.93)目检全为真跨源重复(土耳其商船、NASA蓝源、本田降本)。
  • 护栏残留盲区(两模型相同):LME 多品种列表式行情——仅部分品种价格不同、数字集有交集时护栏放行,不同交易日综述可能误合并;低频,可后续加「共享数字占比」门槛。

结论与切换建议

命中量几乎一致(0.80 raw 317 vs 317),吞吐快 55 倍、维数 1/4、同义/异事件分辨间隔更大 → 推荐生产切换 bge-m3。 切换方式:pipeline.py 默认 EMBEDDING_MODEL 改 bge-m3:latest(或运行 env 覆盖);阈值 0.80、数值护栏默认开均不变; 缓存为进程内、重启自清,无维度迁移问题;未装 bge-m3 环境自动降级 L0–L4。 评估明细:/tmp/l5_offline_eval_result_bge-m3_latest.json

2026-09-02(续)— 生产默认模型切换 bge-m3:latest(已落实)

  • extend/pipeline.py:EMBEDDING_MODEL 默认值 qwen3-embedding:8b → bge-m3:latest
  • extend/unified_writer.py:
    • EMBEDDING_DEFAULT_MODEL 改 bge-m3:latest
    • 更新阈值实测依据(从 qwen3 的 0.85–0.90/0.74–0.78/0.62–0.69/0.41–0.45 改为 bge-m3 的 0.86/0.73/0.58/0.36)
    • 补注 2026-09-02 实测结论:命中量几乎一致(0.80 raw 317 / 护栏后 239),吞吐快 55 倍
  • 切换生效需重启采集管线;未装 bge-m3 环境自动降级 L0–L4。