背景
- 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 已部署
179 KiB
FinDataHub Mac App - DEVLOG
2026-07-27 — 日志分析后优化 Akshare/Tushare 数据同步脚本
版本:1.3.25
日志分析发现
- Akshare 同步秒级完成(189ms~660ms),实际未拉取数据:日志显示 akshare 进程"成功完成"但耗时不到 1 秒,正常全市场 5500+ 股票同步应需数分钟。根因是
get_stock_list()调用ak.stock_info_a_code_name()返回空列表,sync 循环 0 只股票后退出码=0,Swift 端误报成功。 - Tushare 日志路径显示错误:tushare 同步日志中
项目路径显示为 akshare_fetcher 而非 tushare_fetcher。 - stderr 完全丢失:
sync_bridge.py使用subprocess.run()不捕获输出,所有子进程错误信息丢失。
修复
sync_bridge.py:
run_akshare():改用capture_output=True,失败时打印 stderr/stdout 前 500 字符,成功时提取摘要行run_tushare():同上,捕获 stderr 并显示最后 3 行进度信息
akshare_fetcher/core/fetcher.py — sync_all_stocks():
- 空股票列表检测:
get_stock_list()返回空时打印明确错误并返回 - "已是最新"跳过:
last_date >= today时直接跳过,不再发起无效 API 调用 - 增量/全量日志:打印同步模式 + 股票数,便于诊断
涉及文件
Scripts/bin/sync_bridge.py(stderr 捕获)Services/akshare_fetcher/src/akshare_fetcher/core/fetcher.py(空列表检测 + 最新跳过)
编译状态
- Python 语法检查:全部通过
- Swift 构建:0 错误(无变更)
2026-07-26 — 增加调试日志覆盖面(NewsFlash + Scheduler)
版本:1.3.24
问题
调试面板仅显示 3 条启动日志,运行时操作(采集器启停、通知轮询、定时任务触发等)均无记录。
修复
NewsFlashService 新增日志点:
startNotificationPolling()/stopNotificationPolling()— 通知轮询启停stopCollector()/stopBackend()— 服务停止checkServiceHealth()— 采集器/后端异常停止(ERROR 级别)+ 健康监控关闭
SchedulerService 新增日志点:
registerTasks()— 注册时打印已启用任务数量triggerNow()— 手动触发任务checkScheduledTasks()— 定时触发任务
涉及文件
App/Sources/FinDataHub/Services/NewsFlashService.swiftApp/Sources/FinDataHub/Services/SchedulerService.swift
编译状态
- Swift 构建:0 错误
2026-07-26 — 修复调试面板 sheet 关闭主窗口 + 日志为空的 Bug
版本:1.3.23
问题
- 打开调试面板会关闭应用界面:DebugView 使用
.sheet()呈现,sheet 弹出后 MenuBarExtra 窗口失焦被系统自动关闭 - 日志始终显示"暂无日志":DebugLogger 默认
enabled=false且minLevel=.info,app 中几乎没有 info 级别日志写入
修复
调试面板改为独立 NSWindow:
- 移除
@State showDebugPanel+.sheet(isPresented:) - 新增
openDebugPanel()方法,使用NSWindow(contentViewController:)创建独立窗口 - 窗口属性:titled + closable + resizable + miniaturizable,
isReleasedWhenClosed = false - 添加关闭按钮(
xmark.circle.fill)到 DebugView header
DebugLogger 默认配置:
enabled默认值false→trueminLevel默认值.info→.debug- 新增
DebugLogger 初始化完成日志确认 logger 正常工作
增加关键日志点:
FinDataHubApp.init():启动日志 + 自动启动配置日志NewsFlashService.startCollector():采集器启动日志NewsFlashService.startBackend():后端启动日志
涉及文件
App/Sources/FinDataHub/Views/SettingsView.swift(sheet → NSWindow + 关闭按钮)App/Sources/FinDataHub/Views/DebugView.swift(dismiss 环境变量 + 关闭按钮)App/Sources/FinDataHub/Services/DebugLogger.swift(默认值修改)App/Sources/FinDataHub/FinDataHubApp.swift(启动日志)App/Sources/FinDataHub/Services/NewsFlashService.swift(服务启动日志)
编译状态
- Swift 构建:0 错误
2026-07-26 — 修复 MenuBarExtra 窗口反复打开后不显示的问题
版本:1.3.21
问题
应用重启后第一次点击菜单栏图标可正常显示窗口,但后续再次点击无法显示界面。
根因
applyWindowSize() 使用了 window.setFrame() 直接修改窗口 frame(含位置),而 MenuBarExtra .window 风格的窗口位置由系统管理。setFrame() 会重置窗口 origin,导致窗口出现在错误位置甚至屏幕外。此外 windowSizeChanged 通知在每次通用设置变更时都触发(不限窗口尺寸),进一步干扰窗口显示/隐藏生命周期。
修复
setFrame()→setContentSize():仅修改内容区尺寸,不触碰窗口位置- 移除 0.3s 异步重试:若窗口不存在则放弃,不进行延迟重试
windowSizeChanged通知从saveGeneralPrefs()移至宽高滑块的onChange,仅窗口尺寸变更时触发
涉及文件
App/Sources/FinDataHub/ContentView.swiftApp/Sources/FinDataHub/Views/SettingsView.swift
编译状态
- Swift 构建:0 错误
2026-07-26 — 安全审计:修复 7 个漏洞(2严重 + 2高危 + 2中危 + 1低危)
版本:1.3.20
修复详情
🔴 严重 (2)
- API Key 吊销失效 —
dal.py:verify_api_key()字段名不匹配:admin.py 写入active: False,验证时读取status: "active"。修复:统一使用active字段,吊销返回 None。 - /api/shutdown 无鉴权 — 添加
_auth=Depends(require_auth)。
🟠 高危 (2)
3. 外部 API Key 存储结构不匹配 — dict 以 kid 为 key 存储,但 verify_api_key 用 raw key 查找。修复:改为遍历比对 info["key"],并使用 secrets.compare_digest 防时序攻击。
4. /api/v1/source/test 无鉴权+SSRF — 添加 _auth=Depends(require_auth)。
🟡 中危 (2)
5. /api/v1/news/notify-internal 无 IP 验证 — 添加 request.client.host 检查,仅允许 127.0.0.1/localhost/::1。
6. .api_keys.json 未加入 .gitignore — 添加 .api_keys.json 和 .collector_status.json。
🟢 低危 (1) 7. /api/v1/collector/status 信息泄露 — 添加本地 IP 白名单限制。
涉及文件
Services/kuaixun-service/app/dal.py(verify_api_key 重写:遍历匹配 + active 字段修复 + 时序攻击防护)Services/kuaixun-service/app/routers/internal.py(notify-internal IP 验证)Services/kuaixun-service/app/routers/collector.py(collector/status IP 验证)Scripts/bin/newsflash_backend.py(shutdown + source/test 添加鉴权).gitignore(添加敏感运行时文件排除)
编译状态
- Python 语法检查:5/5 文件通过
- Swift 构建:0 错误(无变更)
2026-07-26 — 修复窗口尺寸调整、数据库检测、调试面板等设置问题
版本:1.3.19
问题
- 窗口尺寸调整不生效:设置中调整窗口宽高后,实际窗口尺寸无变化
- 项目配置路径未使用 App 内部路径:项目已内置到 App 中,不应再调用外部文件路径
- MySQL 数据库检测失败:
checkDatabase()和initializeDatabase()使用 Keychain 读取密码,而非 UI 中用户输入的dbPassword - 调试面板界面显示异常:
NavigationLink在NavigationSplitView内无NavigationStack包裹导致无法正常跳转
修复
窗口尺寸:
- 将
windowWidth/windowHeight从计算属性改为@State变量 +loadWindowSize()方法 applyWindowSize()使用DispatchQueue.main.async延迟执行 + 0.3s 重试机制,确保 MenuBarExtra 窗口已创建- 添加
NSNotification.Name.windowSizeChanged通知,saveGeneralPrefs()中 post,ContentView中监听并重新应用尺寸 - 设置了
currentWindowWidth/currentWindowHeight后调用applyWindowSize()立即生效
项目配置路径:
- 移除"项目路径"和"uv 路径"配置项
- 添加绿色对勾 + "项目已内置到 App 中,无需配置外部路径"提示
MySQL 数据库检测:
checkDatabase():MYSQL_PASSWORD从KeychainService.load()改为dbPassword状态变量initializeDatabase():同上ContentView.checkDatabaseReady():MYSQL_PASSWORD从KeychainService.load()改为cfg.dbConfig.password
调试面板:
NavigationLink改为Button+.sheet(isPresented:)方式呈现- 添加
@State private var showDebugPanel = false控制 sheet 显示
涉及文件
App/Sources/FinDataHub/ContentView.swift(窗口尺寸监听 + applyWindowSize 改进 + 密码修复)App/Sources/FinDataHub/Views/SettingsView.swift(项目配置简化 + 密码修复 + 调试面板 Sheet + 窗口通知)
编译状态
- Swift 构建:0 错误,2 警告(均为预存在的 Resources 路径警告)
2026-07-26 — 修复字体大小设置无效 + 移除 pnpm 版本检查 + 统一 sectionHeader
版本:1.3.18
问题
- 字体大小设置完全无效:AppearanceManager 提供
scaledHeadline/Body/Caption等缩放字体,但所有视图使用FinDesign.FontStyle.xxx硬编码字号,导致用户拖动字体滑块后无任何视觉变化。 - pnpm 始终显示缺失:VersionChecker 检查 pnpm 是否存在,但 pnpm 仅前端开发需要,App 运行时不需要,一直红色 ❌ 误导读。
- SettingsView sectionHeader 使用硬编码
.subheadline.fontWeight(.semibold)而非FinDesign.FontStyle.title3。
修复
FinDesign.FontStyle 动态缩放:
- 所有
static let改为static var计算属性 - 每个字体层级基于
AppearanceManager.shared.fontSize(默认 13pt)加固定偏移:- largeTitle: base+13, title1: base+9, title2: base+4, title3: base+2
- headline/body: base, callout: base-1, caption: base-2, caption2: base-3
monoFont/smallMono/TabularFont同步改为计算属性动态缩放- 用户拖动字体滑块后,14 个视图全部即时生效,无需逐视图修改
移除 pnpm 版本检查:
- 从
VersionChecker.items中移除 pnpm - 删除
checkPnpm()方法(40 行代码) - SettingsView 的"系统工具"分组过滤移除 pnpm
统一 sectionHeader:
- 使用
FinDesign.FontStyle.title3+LineIcon+FinDesign.Spacing.xs间距
涉及文件
App/Sources/FinDataHub/Design/FinDesign.swift(FontStyle/TabularFont/monoFont 全部改为计算属性)App/Sources/FinDataHub/Services/VersionChecker.swift(移除 pnpm 检查)App/Sources/FinDataHub/Views/SettingsView.swift(sectionHeader 统一 + pnpm 过滤移除)
编译状态
- Swift 构建:0 错误
2026-07-26 — 统一各栏目设计规范
版本:1.3.17
背景
14 个视图中存在多个设计规范不一致问题:标题字体(subheadline/headline vs title3)、VStack 间距(硬编码 8/10/16 vs FinDesign.Spacing)、图标使用(Image vs LineIcon)、分隔线类型(GlassDivider vs Divider)、卡片包裹(有/无 glassPanel)。
审计结果
| 视图 | 问题 |
|---|---|
| SchedulerView | VStack 间距硬编码 16,"同步历史"标题无图标 |
| SchedulerTaskCard | 标题用 body+medium,VStack 间距 10,GlassDivider |
| CalendarHistoryView | 标题用 subheadline+medium,间距 8 |
| SyncTypeDetailView | 标题用 headline,内层间距 10 |
| SyncProgressView | 缺少 .glassPanel() 包裹 |
| DashboardView | nextRunPreview 标题用 subheadline,间距 6 |
| TuShareView | Token 状态卡片颜色用 .orange 而非 FinDesign.statusOrange |
统一标准后
| 规则 | 值 |
|---|---|
| 卡片标题 | LineIcon(name:color:) + Text(...).font(.title3) |
| 卡片包裹 | .padding(.md) + .glassPanel() |
| 主 VStack 间距 | FinDesign.Spacing.lg(页面级)/ .sm(卡片内) |
| 分隔线 | Divider().opacity(0.2) |
| 按钮 | .buttonStyle(.borderless) + .font(.caption) |
| 颜色 | FinDesign.statusXXX |
涉及文件
App/Sources/FinDataHub/Views/SchedulerView.swift(间距/字体/分隔线/标题)App/Sources/FinDataHub/Views/CalendarHistoryView.swift(标题字体/间距)App/Sources/FinDataHub/Views/SyncTypeDetailView.swift(标题字体/间距)App/Sources/FinDataHub/Views/SyncProgressView.swift(添加 glassPanel)App/Sources/FinDataHub/Views/DashboardView.swift(nextRunPreview 标题/间距/图标)App/Sources/FinDataHub/Views/TuShareView.swift(Token 卡片颜色)
编译状态
- Swift 构建:0 错误
2026-07-26 — 修复 TuShare/AkShare 数据概览(动态表查询 + 任务统计)
版本:1.3.16
问题
TuShareView 和 AkShareView 的数据概览调用 insights.py --mode detail,该模式使用硬编码的 KEY_TABLES 列表(10 张 akshare 表名),导致:
- TuShare 数据库中这些表大部分不存在,概览显示全为 0 行空表
- AkShare 数据库中部分表名不匹配(如
stock_financial_indicator实际不存在) - 数据概览不展示数据库中真实存在的表
修复
insights.py detail 模式重写:
- 移除硬编码 KEY_TABLES / TRADE_COLUMNS
- 改为通过
information_schema.TABLES动态查询数据库中的所有基础表 - 新增
detect_date_column()自动探测每张表的日期列:- 先查
information_schema.COLUMNS获取 date/datetime/timestamp/bigint 类型列 - 按常见日期列名优先级排序(trade_date > report_date > nav_date > created_at > ...)
- 先查
- 新增
query_latest_date()遍历候选列获取最新日期 - 结果按行数降序排列,数据多的表在前
TuShareView / AkShareView 数据概览卡片优化:
- 摘要行新增:最新更新时间(取所有表中最新日期)、定时任务成功/失败计数
- 表格列表:只显示有数据的表(
rows > 0),最多显示 10 张,其余折叠 - 底部显示空表数量提示
- 表行格式:行数使用格式化缩写(K/M),gap_days 替代原始日期显示
- 新增
tushareTaskStats/akshareTaskStats计算属性,从 HistoryStore 读取任务统计
涉及文件
Scripts/bin/insights.py(detail 模式:动态全表查询 + 日期列自动探测)App/Sources/FinDataHub/Views/TuShareView.swift(数据概览卡片重构)App/Sources/FinDataHub/Views/AkShareView.swift(数据概览卡片重构)
编译状态
- Swift 构建:0 错误
2026-07-26 — 重构数据概览为三大数据源汇总视图
版本:1.3.15
背景
旧数据概览只展示单一数据库的表级详情(3x2 网格),不支持多数据源汇总,且未集成定时任务执行统计。用户需要在一个视图中快速了解快讯中心、AkShare、TuShare 三大数据源的总体数据量和任务运行状态。
变更内容
insights.py 重写:
- 新增
--mode overview模式,默认查询news_flash/akshare_data/tushare_data三个数据库 - 每个数据库返回:总行数、总表数、最后更新时间、显示名称、图标、主题色
- 日期探测策略:优先
information_schema.UPDATE_TIME,InnoDB 回退到关键日期列查询(如MAX(trade_date)、FROM_UNIXTIME(MAX(published_at)/1000)) - 保留
--mode detail兼容 TuShareView / AkShareView 的表级详情查询
DataInsight 模型扩展:
- 新增
SourceOverview模型:source、displayName、icon、color、totalRows、totalTables、lastUpdate、error - 支持
taskSuccessCount/taskFailureCount运行时注入(不在 JSON 中传输) - 新增
OverviewResponse顶层解析模型 - 新增
lastUpdateShort格式化方法、parsedColor十六进制颜色解析
InsightService 重构:
- 新增
refreshOverview()方法,调用 insights.py--mode overview - 新增
enrichWithTaskStats()方法,从 HistoryStore 获取任务成功/失败统计并注入到 SourceOverview 中 - 快讯中心统计 newsflash + news_summary 任务;AkShare/TuShare 统计 daily + weekly + monthly 任务
- 保留
refresh(database:)方法兼容旧版 detail 查询
InsightCardView 重新设计:
- 旧:3x2 表级迷你卡片网格 + 单一摘要行
- 新:三张数据源总览卡片横向排列,每张卡片包含:
- 图标 + 名称 + 状态指示点
- 总行数/表数统计 + 最后更新时间
- 分隔线 + 定时任务成功/失败计数(绿色/红色状态点)
- 每张卡片有独立主题色边框(快讯紫 / AkShare 绿 / TuShare 橙)
- 错误状态时显示红色状态点 + "连接失败"提示
- 首次加载时自动调用
refreshOverview()
任务统计映射
| 数据源 | 关联任务类型 |
|---|---|
| 快讯中心 | newsflash、news_summary |
| AkShare | daily、weekly、monthly(双源同步,共享统计) |
| TuShare | daily、weekly、monthly(双源同步,共享统计) |
涉及文件
Scripts/bin/insights.py(重写,支持 overview + detail 双模式)App/Sources/FinDataHub/Models/DataInsight.swift(新增 SourceOverview、OverviewResponse)App/Sources/FinDataHub/Services/InsightService.swift(新增 refreshOverview / enrichWithTaskStats)App/Sources/FinDataHub/Views/InsightCardView.swift(新 UI 设计)App/Sources/FinDataHub/Views/DashboardView.swift(无需变更,已自动适配)App/Sources/FinDataHub/Views/TuShareView.swift(无需变更,使用 detail 模式)App/Sources/FinDataHub/Views/AkShareView.swift(无需变更,使用 detail 模式)
编译状态
- Swift 构建:0 错误,2 警告(遗留警告,非本次变更引入)
2026-07-26 — 修正定时任务详情表名与数据库对齐
版本:1.3.14
问题
SyncType.tables 中列出的表名与实际数据库不符:
fund_nav_history— AkShare 库中不存在stock_income_ths— 不存在,tushare 对应incomestock_balance_ths— 不存在,tushare 对应balancesheetstock_financial_indicator— AkShare 中存在但 0 行,tushare 对应fina_indicator
修复
基于实际 MySQL 数据库 akshare_data / tushare_data 表结构和 sync_bridge.py 的批次映射,补全全部表名:
| 同步类型 | 表数 | 数据源分布 |
|---|---|---|
| 每日 | 14 张 | AkShare 1 + TuShare 13(日线/基础指标/资金流/融资融券/龙虎榜/大宗交易/沪深港通) |
| 每周 | 22 张 | AkShare 1 + TuShare 21(周线/三大报表/财务指标/审计/预告/快报/分红/基金) |
| 每月 | 19 张 | AkShare 1 + TuShare 18(月线/指数/宏观/涨跌停/年线/股东增减持) |
SyncTable.id改为source_tableName格式,解决同表名双源冲突 |
涉及文件
App/Sources/FinDataHub/Models/SyncTypeDetail.swift(修正全部表定义)
编译状态
- Swift 构建:0 错误
2026-07-26 — 定时任务详情标注数据源归属
版本:1.3.13
背景
定时任务详情页只列出表名,未标注每张表来自哪个数据源(AkShare / TuShare),用户无法快速判断数据来源。
修复
SyncTable模型新增dataSources: [DataSource]字段,每张表明确标注数据来源SyncTypeDetailView:- 头部增加数据源彩色标签(AkShare 蓝 / TuShare 绿)
- 每张表行旁显示来源徽章(compact 模式)
SchedulerTaskCard:卡片内增加数据源标签行,不点详情也能看到- 新增
DataSourceBadge组件,统一数据源视觉标识
表数据源映射
| 同步类型 | AkShare 独有 | TuShare 共有 | 双源共有 |
|---|---|---|---|
| 每日同步 | 板块资金流、ETF日线、基金净值 | — | 日线行情、指数行情 |
| 每周同步 | 行业/概念指数周线 | — | 周线行情 |
| 每月同步 | 利润表、资产负债表 | — | 月线行情、财务指标 |
涉及文件
App/Sources/FinDataHub/Models/SyncTypeDetail.swift(新增 dataSources 字段)App/Sources/FinDataHub/Views/SyncTypeDetailView.swift(头部+表行数据源标签)App/Sources/FinDataHub/Views/SchedulerView.swift(卡片内数据源标签)
编译状态
- Swift 构建:0 错误
2026-07-26 — 目录结构重构:标准化项目布局
版本:1.3.12
背景
原项目根目录混杂,Resources/ 塞了 6 个子项目 + 脚本 + 资源文件,缺少归类。Sources/ 和 Resources/ 平铺在根目录,而非按模块分层。
重构
原结构 新结构
Sources/FinDataHub/ → App/Sources/FinDataHub/
Resources/kuaixun-service/ → Services/kuaixun-service/
Resources/kuaixun-frontend/ → Services/kuaixun-frontend/
Resources/akshare_fetcher/ → Services/akshare_fetcher/
Resources/tushare_fetcher/ → Services/tushare_fetcher/
Resources/alpha-common/ → Services/alpha-common/
Resources/bin/ → Scripts/bin/
Resources/AppIcon.* → App/Assets/
build-app.sh → Scripts/build-app.sh
bundle-python.sh → Scripts/bundle-python.sh
scripts/bump_version.sh → Scripts/bump_version.sh
DEVLOG.md → Docs/DEVLOG.md
DESIGN.md → Docs/DESIGN.md
路径修正
Package.swift:path更新为App/Sources/FinDataHub,resources引用更新Scripts/build-app.sh:所有Resources/引用 →Services//Scripts/bin/,PROJECT_DIR加..适配新位置Scripts/bundle-python.sh:PROJECT_ROOT加..适配新位置- Swift dev fallback 路径:
Resources/bin→Scripts/bin(3 处) - 子服务 shell 脚本:硬编码
PROJECT_DIR→ 动态路径 (3 处)
涉及文件
App/Sources/FinDataHub/Services/NewsFlashService.swift、PythonPathFinder.swift、PythonProcessManager.swiftPackage.swiftScripts/build-app.sh、Scripts/bundle-python.shServices/akshare_fetcher/scripts/install_launchd.sh、ths_archive_daily.shServices/tushare_fetcher/scripts/install_launchd.sh
编译状态
- Swift 构建:0 错误
- 打包验证:通过 (471MB)
2026-07-26 — 优化退出流程:确保子进程随主应用退出而终止
版本:1.3.11
问题
主应用退出后(包括直接关闭窗口、Cmd+Q),快讯采集器(newsflash_collect.py)和后端服务(newsflash_backend.py)子进程残留,未被清理。
根因分析:
startCollector()创建的Process对象是局部变量,出作用域后无法直接控制AppQuitService的清理仅在用户走标准"退出"按钮流程时触发- 未注册
NSApplication.willTerminateNotification兜底异常退出/系统终止
修复
NewsFlashService:- 新增
collectorProcess: Process?属性,在startCollector()中存储子进程引用 stopCollector():先process.terminate()直接终止,再pkill兜底清理残留stopBackend():先同步pkill,再异步确认清理- 新增
terminateSubprocessesSync()方法,供willTerminate同步调用
- 新增
AppQuitService:init()中注册NSApplication.willTerminateNotification,调用同步清理
FinDataHubApp:init()中提前访问AppQuitService.shared,确保通知注册在最早时机
涉及文件
Sources/FinDataHub/Services/NewsFlashService.swift(存储进程引用 + 同步清理方法)Sources/FinDataHub/Services/AppQuitService.swift(注册 willTerminateNotification)Sources/FinDataHub/FinDataHubApp.swift(提前初始化退出服务)
编译状态
- Swift 构建:0 错误
2026-07-26 — 修复数据概览按数据库隔离:insights.py 支持 --database 参数
版本:1.3.10
问题
AkShare 和 TuShare 视图的数据概览显示相同(仅 2 张表有数据)——insights.py 只查询一个数据库(MYSQL_DATABASE_AKSHARE),而 KEY_TABLES 混含两个数据源的表,TuShare 表在 tushare_data 库中不存在故返回 0 行。
修复
insights.py:新增--databaseCLI 参数,优先使用命令行参数,回退到环境变量InsightService.refresh(database:):新增可选database参数,传递给 insights.pyAkShareView/TuShareView:各自创建独立的@StateObject private var insights = InsightService(),传入"akshare_data"/"tushare_data"- 概览页
InsightCardView保持使用InsightService.shared(无参,走默认数据库)
涉及文件
Resources/bin/insights.py(新增 argparse --database)Sources/FinDataHub/Services/InsightService.swift(refresh 支持 database 参数)Sources/FinDataHub/Views/AkShareView.swift(独立 InsightService + 传入 akshare_data)Sources/FinDataHub/Views/TuShareView.swift(独立 InsightService + 传入 tushare_data)
编译状态
- Swift 构建:0 错误
2026-07-26 — 新增 AkShare / TuShare 独立数据管理视图
版本:1.3.9
变更概述
在侧边栏「数据」分组新增 AkShare 和 TuShare 两个独立页面,按数据源拆分同步控制、数据概览和同步历史。
AkShareView
| 区块 | 内容 |
|---|---|
| 同步控制 | 日线/周线/月线切换 + 进度条 + 当前表名/完成数 + 开始/停止按钮 |
| 数据概览 | 总表数/总行数/数据库大小 + 分表行数/最新日期/健康状态 |
| 同步历史 | 最近 10 条同步记录(类型/结果/摘要/耗时) |
TuShareView
在 AkShareView 基础上增加 Token 状态卡片:
- 检测 Token 是否配置
- 「检测」按钮验证 Token 有效性(调用 Python 脚本)
- Token 未配置时禁用同步按钮并提示
Sidebar 结构调整
主功能 → 概览
数据 → AkShare / TuShare / 快讯中心
管理 → 定时任务 / 日志历史
应用 → 设置
快捷键更新
| 快捷键 | 目标 |
|---|---|
| ⌘1 | 概览 |
| ⌘2 | AkShare |
| ⌘3 | TuShare |
| ⌘4 | 快讯中心 |
| ⌘5 | 定时任务 |
| ⌘6 | 日志历史 |
涉及文件
新增:
Sources/FinDataHub/Views/AkShareView.swiftSources/FinDataHub/Views/TuShareView.swift
修改:
Sources/FinDataHub/ContentView.swift(NavSection + Sidebar + detailView + toolbar)Sources/FinDataHub/FinDataHubApp.swift(快捷键更新)Sources/FinDataHub/Views/SettingsView.swift(默认页选择器新增 AkShare/TuShare)
编译状态
- Swift 构建:0 错误
2026-07-26 — 修复版本检查误报:Python 包跨 venv 搜索 + pnpm 路径扩展
版本:1.3.7
问题
版本检查中,除 Python/uv/Homebrew 外全部显示「未安装」:
- Python 包检查只用一个 python3 路径 —
findBestPython()返回第一个路径(kuaixun-service/.venv),但 akshare 在 akshare_fetcher/.venv、tushare 在 tushare_fetcher/.venv,跨 venv 无法找到 - pnpm 路径不全 — 只检查 4 个固定路径,未覆盖
which、.local/share、.npm-global等常见位置
修复
- Python 包检查:遍历所有可用 python3 路径(App 内 3 个 venv + 外部 2 个 + 系统 3 个),每个包在所有路径中搜索,任一 venv 命中即通过
- pnpm:优先
/usr/bin/env bash -c "which pnpm"查找,再回退到 6 个已知路径(brew/local/npm-global) - 移除不再需要的
findBestPython()方法
涉及文件
Sources/FinDataHub/Services/VersionChecker.swift(修复 2 处)
编译状态
- Swift 构建:0 错误
2026-07-26 — 版本检查功能迁移至设置页,新增 6 项 Python 包检查
版本:1.3.6
变更概述
- 创建 VersionChecker 独立服务 — 集中管理所有依赖版本检查(10 项)
- 迁移到设置页 — 概览页 EnvChecklistView 移除,新增「版本检查」Settings 栏目
- 新增 6 项检查 — Homebrew、pnpm、akshare、numpy、cryptography、tqdm、tushare、openpyxl
检测项目清单
系统工具(4 项): Python、uv、Homebrew、pnpm Python 包(6 项): akshare、numpy、cryptography、tqdm、tushare、openpyxl
检测方式
| 类型 | 方法 |
|---|---|
| 系统工具 | 执行 {cmd} --version |
| Python 包 | python3 -c "import {pkg}; print({pkg}.__version__)" |
UI 结构
- 设置页 → 版本检查 → 系统工具 / Python 包 / 统计汇总
- 空状态引导 + 检测中 ProgressView + 通过/失败颜色区分
- 通过次数统计(绿色)和失败次数统计(红色)
涉及文件
新增:
Sources/FinDataHub/Services/VersionChecker.swift
修改:
Sources/FinDataHub/Views/DashboardView.swift(移除 EnvChecklistView + envChecker 参数)Sources/FinDataHub/Views/SettingsView.swift(新增 versionSection + versionRow)Sources/FinDataHub/ContentView.swift(DashboardView 调用移除 envChecker)
编译状态
- Swift 构建:0 错误
2026-07-26 — 环境检查重构:移除 MySQL、简化 Python、新增应用目录检查
版本:1.3.5
变更概述
- 移除 MySQL 连接检查 — 数据库连通性检测移至 ServiceMonitor 和设置页的"检测数据库"按钮
- 简化 Python 检查 — 从"检查 Python 3.10+ 且找到第一个合格版本就停止"改为"扫描所有可用 python3,报告最新版本号";遍历所有路径取最高版本,显示
X.Y.Z — /path/to/python3 - 项目目录 → 应用目录 — 检查
/Applications/FinDataHub.app是否存在,读取 Info.plist 版本号 + du 体积 runAllChecks()不再需要projectPath/dbConfig参数
涉及文件
Sources/FinDataHub/Services/EnvChecker.swift(重写检测逻辑)Sources/FinDataHub/ContentView.swift(简化 2 处调用)Sources/FinDataHub/Views/EnvChecklistView.swift(简化调用)Sources/FinDataHub/Views/DashboardView.swift(更新 Preview 数据)
编译状态
- Swift 构建:0 错误
2026-07-26 — 新增 Ollama / MySQL 运行状态监测
版本:1.3.4
变更概述
在 Dashboard 概览页新增「外部服务」卡片,实时监测 Ollama 和 MySQL 进程是否运行。
监测方法
| 服务 | 检测方式 | 超时 |
|---|---|---|
| Ollama | HTTP GET {base_url}/api/tags(轻量级健康检查端点),解析 JSON 确认模型列表 |
3s |
| MySQL | /usr/bin/nc -z -w 3 {host} {port} TCP 端口连通性检测 |
3s |
行为
- 进入概览页自动启动 30 秒定时轮询
- 状态用绿/红 StatusDot 直观展示
- 支持手动刷新按钮,检测中显示 ProgressView
- MySQL 地址从设置中的数据库配置读取
涉及文件
新增:
Sources/FinDataHub/Services/ServiceMonitor.swift(服务状态监测器)
修改:
Sources/FinDataHub/Views/DashboardView.swift(新增 serviceStatusCard + onAppear 启动定时监测)
编译状态
- Swift 构建:0 错误
2026-07-26 — 修复窗口默认尺寸过小导致内容显示不全
版本:1.3.3
问题
默认窗口尺寸 620×600 不足以容纳 NavigationSplitView(侧边栏 220pt + 内容区),且 .frame(minWidth: 800) 被后续 .frame(width: 620) 覆盖,导致窗口实际以 620px 打开。
修复
AppConstants.defaultWindowWidth: 620 → 960AppConstants.defaultWindowHeight: 600 → 680ContentView.swift: 合并两个.frame()为.frame(minWidth:idealWidth:minHeight:idealHeight:),避免显式 width 覆盖 min 约束
涉及文件
Sources/FinDataHub/AppConstants.swift(默认窗口尺寸)Sources/FinDataHub/ContentView.swift(合并 frame modifier)
编译状态
- Swift 构建:0 错误
2026-07-26 — 视图级 HIG 合规优化
版本:1.3.2
变更概述
在 FinDesign 设计 Token 系统已落地的基础上,对所有核心视图进行 Apple HIG 合规优化,移除自定义标题栏,统一使用系统材质、语义字体和标准化间距。
核心改动
1. 移除所有自定义标题栏
所有页面视图不再使用自定义 header,标题由 navigationTitle 统一管理,工具栏按钮按要求按页面上下文动态显示:
NewsFlashView:移除自定义标题栏,新增serviceControlBar替代原有控制区SchedulerView:移除自定义 header,新增状态栏(.ultraThinMaterial + 分隔线)LogArchiveView:移除自定义 header,新增工具栏(.ultraThinMaterial + 操作按钮)SettingsView:移除自定义 header,标题由 navigationTitle 接管
2. 组件标准化(FinDesign Token 迁移)
SyncProgressView:StatusDot/LineIcon +FontStyle.title3/caption+Spacing.md/xs/smInsightCardView:LineIcon +FontStyle.title3/caption+Spacing.sm/xsEnvChecklistView:LineIcon +FontStyle.title3/body/caption+Spacing.sm/xs/sm
3. 材质系统统一
- 工具栏/状态栏:
.ultraThinMaterial+FinDesign.separatorColor底部分隔线 - 卡片/面板:
.glassPanel()修饰符(.regular + Radius.panel) - 控件容器:
FinDesign.SystemColor.secondaryFill半透明背景 + Capsule 形状
4. 间距与排版统一
- 页面左右边距统一为
Spacing.xl(20pt) - 卡片内 padding 统一为
Spacing.md(12pt) - 标题统一使用
FontStyle.title3(15pt Semibold) - 次要文字统一使用
FontStyle.caption(11pt) - 正文字统一使用
FontStyle.body(13pt) - 图标统一使用
LineIcon+ SF Symbols 线性风格
涉及文件
修改:
Sources/FinDataHub/Views/NewsFlashView.swift(移除自定义标题栏 + serviceControlBar)Sources/FinDataHub/Views/SchedulerView.swift(移除自定义 header + 状态栏)Sources/FinDataHub/Views/LogArchiveView.swift(移除自定义 header + 工具栏)Sources/FinDataHub/Views/SettingsView.swift(移除自定义 header)Sources/FinDataHub/Views/SyncProgressView.swift(Token 迁移:间距/字体/颜色)Sources/FinDataHub/Views/InsightCardView.swift(Token 迁移:间距/字体/颜色)Sources/FinDataHub/Views/EnvChecklistView.swift(Token 迁移:间距/字体/颜色)Sources/FinDataHub/Design/FinDesign.swift(新增 icon 别名:syncProgress/envCheck/tables)DESIGN.md(更新实施进度)DEVLOG.md(本文档)
编译状态
- Swift 构建:0 错误,4 个既有 warning(jsonStr 未使用、actor isolation、try? 未使用)
2026-07-26 — Apple HIG 设计规范落地 & 依赖自包含打包
版本:1.3.1
变更概述
两大核心改动:
- 设计系统重构:按 Apple HIG 规范全面重构 UI 架构,从 Liquid Glass 风格迁移到 macOS 原生设计语言
- App 自包含:修复打包后外部依赖问题,将 alpha-common 和三个 venv 的路径全部修正为 App 内部路径
详细变更
1. 设计系统升级(FinDesign.swift)
- 新增
Spacing枚举(8pt 栅格:xxxs~xxxxl 共 10 级) - 新增
Radius枚举(window/panel/card/button/badge/icon 6 级) - 新增
Material枚举(sidebar/header/panel/card/menu/tooltip) - 新增
Shadow枚举(card/popover/subtle 三级阴影) - 新增
FontStyle枚举(9 级语义字体:largeTitle ~ caption2) - 新增
TabularFont枚举(金融数据等宽数字字体) - 新增
SystemColor枚举(系统动态色:label/fill/separator/accent 等) - 新增
FinancialColor枚举(涨跌 + 5 级热度色阶) - 新增
Motion枚举(5 级动效:fast/default/panel/spring/list) - 新增
Sidebar/List/Toolbar布局规范 - 状态色迁移为系统动态色(自动适配明暗模式)
- 扩展
FinIcon图标库(新增 30+ 图标,按功能分组) - 保留所有旧 token 作为兼容别名,零破坏性变更
2. 导航架构升级(NavigationSplitView)
- 用
NavigationSplitView替换 segmented picker 导航 - Sidebar 分三组:主功能(概览/快讯中心)、数据(定时任务/日志历史)、应用(设置)
- Sidebar 使用
.listStyle(.sidebar)+ 毛玻璃材质 - Detail 区域使用
.navigationTitle统一标题 - Toolbar 按页面上下文动态显示操作按钮
- 侧边栏宽度:默认 220pt,范围 180~400pt
3. DashboardView 重构
- 移除自定义标题栏(由 navigationTitle 接管)
- 移除底部状态栏版本号(About 面板中显示)
- 内容区 padding 统一为 20pt(Spacing.xl)
- 依赖管理区域改为
safeAreaInset(edge: .bottom)固定在底部
4. 菜单栏与快捷键
- 新增「数据」菜单:立即同步(⌘R)、刷新快讯(⌘⇧R)
- 新增「视图」菜单:切换边栏(⌘⌃S)、⌘1~4 快速导航、设置(⌘⇧,)
- 保留「偏好设置...」⌘, 快捷键
- 新增 About 面板
- 通知驱动导航:
Notification.Name.navigateTo/.openSettings/.toggleSidebar
5. 打包依赖自包含修复
build-app.sh 增强:
- 新增 alpha-common 打包步骤(src/ + pyproject.toml)
- 新增
fix_venv_paths()函数,打包后批量修正:- bin/ 下所有 CLI 入口的 shebang
- activate / activate.csh / activate.fish 的 VIRTUAL_ENV 路径
- .pth 文件中的 alpha-common 路径指向 App 内部
- direct_url.json 中的本地路径引用
- 路径目标统一为
/Applications/FinDataHub.app/(标准部署位置) - rsync 排除
logs/目录(所有层级) - 修正 shell 脚本硬编码
PROJECT_DIR为动态路径
涉及的三个 venv:
- kuaixun-service/.venv(98M)
- akshare_fetcher/.venv(228M)
- tushare_fetcher/.venv(230M)
设计规范文档
新增 DESIGN.md,包含:
- 设计原则(Clarity / Deference / Depth)
- 完整的 Token 参考(间距/圆角/字体/颜色/动效)
- Sidebar / List / Toolbar 布局规范
- 组件使用规范(按钮/徽章/玻璃面板)
- 快捷键清单
- 图标规范
- 明暗模式规则
- 无障碍要求
- 实施进度追踪
涉及文件
新增:
DESIGN.md(设计规范文档)
修改:
Sources/FinDataHub/Design/FinDesign.swift(设计系统全面升级)Sources/FinDataHub/ContentView.swift(NavigationSplitView + Toolbar + 通知监听)Sources/FinDataHub/Views/DashboardView.swift(移除自定义标题栏 + safeAreaInset)Sources/FinDataHub/FinDataHubApp.swift(菜单栏命令 + 快捷键)build-app.sh(alpha-common 打包 + venv 路径修正 + logs 排除)
编译状态
- Swift 构建:0 错误,1 个既有 warning(jsonStr 未使用)
- App 体积:~470M(含三个 venv + alpha-common)
版本:1.3.0
变更概述
测试项目三大拉取任务(akshare/tushare/快讯)时发现两个阻断性问题并修复:
tushare_fetcher依赖alpha-common但pyproject.toml中配置的相对路径../alpha-common在 FinDataHub 项目内部找不到,导致 venv 创建失败- 快讯采集器通过 API
/api/v1/collector/start启动时调用系统python3(3.9.6),不支持代码中使用的 PEP 604 语法(int | None),导致采集器进程立即崩溃
详细变更
1. 集成 alpha-common 到 Resources/
- 新增
Resources/alpha-common/(从~/Projects/项目/alpha-common复制)- 让
tushare_fetcher/pyproject.toml中的alpha-common = { path = "../alpha-common", editable = true }配置在项目内部可解析 - 修复
uv run tushare-fetcher报错Distribution not found at: file:///.../Resources/alpha-common
- 让
2. 修复快讯采集器 Python 路径
- 修改
Resources/kuaixun-service/app/routers/collector.pycollector_start()函数优先使用 venv 内的.venv/bin/python3,回退到sys.executable- 避免 subprocess 调用系统
python3(3.9.6)导致TypeError: unsupported operand type(s) for |: 'type' and 'NoneType' - 增加
import sys
- 同步修改到
/Applications/FinDataHub.app/Contents/Resources/kuaixun-service/app/routers/collector.py(用 cp 复制,让当前运行的 App 立即生效)
拉取任务测试结果
| 拉取任务 | 测试项 | 结果 |
|---|---|---|
| akshare_fetcher | ETF spot 同步 | ✅ 1646 只 ETF |
| akshare_fetcher | 概念日线同步 | ✅ 374 个概念 |
| akshare_fetcher | 资金流同步 | ❌ schema 列名"序号"错误 |
| akshare_fetcher | daily 任务 | ❌ bse.cn DNS 解析失败(网络问题) |
| tushare_fetcher | CLI 命令 | ✅ 可用 |
| tushare_fetcher | 拉取 000001 daily | ✅ 6 张表全部更新 |
| 快讯系统 | newsflash_collect.py | ✅ 运行 17 小时,1 小时内拉取 324 条 |
| 快讯系统 | collector_start API | ⚠️ 已修复源码,需重启后端服务生效 |
| launchd 定时任务 | 状态检查 | ⚠️ 仅 com.ths.dailyarchive 加载(退出码127),akshare/tushare 任务未加载 |
涉及文件
Resources/alpha-common/(新增)Resources/kuaixun-service/app/routers/collector.py(修改)/Applications/FinDataHub.app/Contents/Resources/kuaixun-service/app/routers/collector.py(同步修改)
编译状态
- Python 源码改动无需编译,uv 安装零错误
- 待重新打包 App 后,collector_start API 修复将永久生效
后续修复:launchd 定时任务加载
- 执行
bash Resources/akshare_fetcher/config/launchd/install.sh - 7 个定时任务全部加载成功:
- com.akshare.dailyupdate / weeklyupdate / monthlyupdate
- com.tushare.dailyupdate / weeklyupdate / monthlyupdate
- com.ths.dailyarchive
- 所有 plist 已通过 install.sh 自动替换路径,指向
/Applications/FinDataHub.app/Contents/Resources/ - 任务状态:退出码 0(等待到点触发)
2026-11-12 — Ollama 模型自动获取 & 快讯通知频率优化
版本:1.3.0
变更概述
根据实际使用反馈,修复两个问题:
- AI 设置中无法自动获取本地 Ollama 已有的模型列表,需要手动输入
- 快讯采集任务的弹窗通知过于频繁,干扰正常使用
详细变更
1. Ollama 模型自动获取
- 新增
Sources/FinDataHub/Services/OllamaService.swift- 实现
OllamaService单例类,负责与本地 Ollama 实例通信 - 通过调用
/api/tags端点获取已安装的模型列表 - 支持模型名称、大小、修改时间等信息的解析
- 完善的错误处理和加载状态管理
- 实现
- 修改
Sources/FinDataHub/Views/SettingsView.swift- AI 设置的模型名称输入框旁新增「刷新」按钮
- 点击刷新从本地 Ollama 获取模型列表
- 模型列表非空时显示下拉选择菜单,可一键选择
- 加载过程显示旋转动画,错误时显示红色提示文字
- 支持自定义 API 地址(默认
http://localhost:11434)
2. 快讯通知频率优化
- 修改
Sources/FinDataHub/AppConstants.swift- 默认通知最小间隔从 60 秒提高到 300 秒(5 分钟)
- 新增
defaultNFNotifyMaxPerHour默认每小时最大通知数(20条) - 新增
nfNotifyMaxPerHourUserDefaults 键
- 修改
Sources/FinDataHub/Services/NewsFlashService.swift- 新增每小时通知窗口计数机制
- 达到每小时上限后暂停通知,下一小时自动恢复
- 单条通知和汇总通知都计入配额
- 修改
Sources/FinDataHub/Views/SettingsView.swift- 通知设置中新增「每小时上限」滑块(5-60 条,步进 5)
- 推送间隔滑块范围从 30-300 秒调整为 60-900 秒
修改文件清单
Sources/FinDataHub/Services/OllamaService.swift(新增)Sources/FinDataHub/Views/SettingsView.swiftSources/FinDataHub/Services/NewsFlashService.swiftSources/FinDataHub/AppConstants.swift
验证结果
- Swift 编译:✅ 零错误(仅有既有警告)
- 功能验证:
- Ollama 模型列表可正常获取和选择
- 通知频率限制逻辑正确
- 设置项可正常保存和加载
2026-07-15 — 实现前端调试模式(死设置复活)
版本:1.3.0
变更概述
「前端调试模式」开关此前只在设置页面保存/读取,前后端代码均未使用,是"死设置"。本次打通 Swift → 前端的调试开关联动,并补全 WebSocket 调试统计。
详细变更
1. Swift 端:URL 携带 debug 参数
- 修改
Sources/FinDataHub/Services/NewsFlashService.swiftopenBackend()方法读取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.swiftResources/kuaixun-frontend/src/App.vueResources/kuaixun-frontend/src/composables/useWebSocket.js
验证结果
- Swift 编译:✅ 零错误
- 前端构建:✅ 成功(949ms)
- 调试联动:设置开启后打开前端自动显示调试面板
2026-07-15 — 修复外观设置不生效问题
版本:1.3.0
变更概述
外观设置(主题/强调色/字体大小/减少动画)之前只保存到 UserDefaults 但从未应用到 UI,是"死"设置。本次创建 AppearanceManager 统一管理外观,并在 ContentView 中应用主题和强调色,设置变更后立即生效。
详细变更
1. 新增 AppearanceManager
- 新增
Sources/FinDataHub/Design/AppearanceManager.swiftObservableObject单例,@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.swiftsaveAppearancePrefs()保存后同步更新 AppearanceManager 的 @Published 属性- 主题/强调色变更后立即生效,无需重启
修改文件清单
Sources/FinDataHub/Design/AppearanceManager.swift(新增)Sources/FinDataHub/ContentView.swiftSources/FinDataHub/Views/SettingsView.swift
验证结果
- Swift 编译:✅ 零错误
- App 打包:✅ 73M
- 主题切换:✅ 浅色/深色/跟随系统即时生效
- 强调色:✅ blue/green/orange/purple 即时生效
2026-07-15 — 设置功能全面检查与修复
版本:1.3.0
变更概述
全面检查设置页面所有功能的可用性,修复了 3 个功能缺陷。
发现并修复的问题
-
runArchive()缺少数据库环境变量(严重)- 归档脚本无法连接数据库,因为只传了
MYSQL_DATABASE,缺少MYSQL_HOST/PORT/USER/PASSWORD - 修复:使用
PythonPathFinder.dbEnv()统一注入完整数据库环境变量 - 脚本路径改用
NewsFlashService.shared.findScript()统一查找
- 归档脚本无法连接数据库,因为只传了
-
saveNewsFlashAdvancedPrefs()缺少归档保留天数保存- 归档滑块的 onChange 直接写
defaults.set,未走统一保存函数,与代码风格不一致 - 修复:移入
saveNewsFlashAdvancedPrefs(),UI 端调用统一函数
- 归档滑块的 onChange 直接写
-
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.swiftSources/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.swiftSources/FinDataHub/Services/NewsFlashService.swiftSources/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)
- news_flash 主表:
- 修改
Resources/bin/init_db.py建表语句同步新增索引
2. 查询重写:消除函数包裹
- 修改
Resources/bin/newsflash_backend.py(6 处)DATE(FROM_UNIXTIME(published_at/1000)) = CURDATE()→published_at >= UNIX_TIMESTAMP(CURDATE()) * 1000DATE(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.pyResources/bin/newsflash_backend.pyResources/bin/daily_summary.pyResources/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.pycollect_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秒)
- 修复 WebSocket URL:
-
修改
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.pyResources/bin/newsflash_collect.pyResources/kuaixun-frontend/src/composables/useWebSocket.jsResources/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. 超时控制
-
修改
Resources/bin/newsflash_collect.py- 添加配置:
COLLECT_TIMEOUT = 120(单次采集总超时)、SOURCE_TIMEOUT = 30(单个数据源超时) - 主循环使用
asyncio.wait_for()包装collect_once(),避免采集卡住
- 添加配置:
-
修改
Resources/kuaixun-service/extend/fetchers/manager.pyFetcherManager新增source_timeout参数(默认30秒)- 新增
_fetch_with_timeout()方法,单个数据源超时时跳过并记录日志,不影响其他数据源
2. 数据库连接池重连
- 修改
Resources/bin/db_pool.pyget_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.pyResources/bin/db_pool.pyResources/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 参数:
-
Sources/FinDataHub/Services/NewsFlashService.swiftrunDetachedProcess()第 497 行:process.arguments = ["-S", executable] + args- (
startCollector()已在上一轮修复中添加-S)
-
Sources/FinDataHub/ContentView.swift- 第 54 行:
process.arguments = ["-S", python, script]
- 第 54 行:
-
Sources/FinDataHub/Views/DatabaseSetupGuideView.swift- 第 698 行、第 758 行:
process.arguments = ["-S", python, script]
- 第 698 行、第 758 行:
-
Sources/FinDataHub/Views/SettingsView.swift- 第 1878 行:
process.arguments = ["-S", "mysql", ...] - 第 1943 行、第 2002 行:
process.arguments = ["-S", python, script]
- 第 1878 行:
根因分析
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.swiftSources/FinDataHub/ContentView.swiftSources/FinDataHub/Views/DatabaseSetupGuideView.swiftSources/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.swiftstartCollector()中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.pyget_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.swiftResources/bin/newsflash_collect.pyResources/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.swiftfindPython()/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.swiftSources/FinDataHub/Services/InsightService.swiftSources/FinDataHub/Services/DepManager.swiftSources/FinDataHub/Services/EnvChecker.swiftSources/FinDataHub/Services/PythonProcessManager.swiftSources/FinDataHub/Models/SyncLogEntry.swiftResources/bin/common.pyResources/bin/check_db.pyResources/bin/init_db.pyResources/bin/manage_deps.pyResources/bin/newsflash_collect.pyResources/kuaixun-frontend/vite.config.jsResources/kuaixun-frontend/.envResources/kuaixun-frontend/.env.developmentResources/kuaixun-frontend/src/stores/news.js.gitignorebuild-app.shbundle-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.swiftcheckPython()新增版本号验证逻辑- 新增
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.swiftcomputePythonPath()优先级调整:app 内 kuaixun-service venv → 外部 kuaixun-old venv → akshare_fetcher venv → 系统 python3
5. 采集器启动错误诊断
- 修改
Sources/FinDataHub/Services/NewsFlashService.swiftstartCollector()添加 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.swiftstartCollector()读取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_URLBACKEND_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 全部改用 commondaily_summary.py:路径设置 + PID 管理 + OLLAMA_URL 改用 commonbackfill_ai.py:路径设置 + _load_env + MYSQL_CFG 改用 commonbackfill_history.py:路径设置 + MYSQL_CFG 改用 commonbackfill_tags.py:路径设置 + MYSQL_CFG 改用 commonnewsflash.py:MYSQL_CFG + MYSQL_CMD + DB 改用 commonnewsflash_backend.py:路径设置 + BACKEND_PORT 改用 commoninit_new_sources.py/init_rss_sources.py:MYSQL_CFG 改用 commonrss_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.pyrun_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.jsvue-vendor: vue + vue-router + piniaaxios: 独立 chunklucide: 图标库独立 chunk
- 修改
Resources/quant-frontend/vite.config.tsvue-vendor: vue + vue-routerchart: chart.js 独立 chunkthree: three.js 独立 chunk
- 效果:vendor chunk 独立缓存,业务代码更新不触发 vendor 重新下载
优化3: MySQL 连接池(无需额外依赖)
- 新增
Resources/bin/db_pool.py- 基于 Python 内置
queue.Queue+pymysql实现轻量连接池 - 惰性创建连接,
ping(reconnect=True)自动重连 DBContext上下文管理器,自动获取/归还连接- 池大小 8,无需 DBUtils 等第三方库
- 基于 Python 内置
- 修改
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: 1DynamicScroller+DynamicScrollerItem替换v-for列表key-field="_uid"(source_code:id复合键,避免跨表 id 冲突):size-dependencies="[isExpanded(item)]"— 展开折叠时自动重测高度#afterslot 放置「加载更多」指示器@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.pySOURCE_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_enabledUserDefaults 开关控制 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. 其他文件适配
- 修改 DashboardView.swift — 更新定时任务预览区适配新 API
- 修改 FinDataHubApp.swift — 菜单栏图标钟表标记适配新 API
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状态发送notifyDepSetupCompletedcompletion失败回调发送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_POSIXlocale,避免非公历日历设备解析错误
文件: 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/tushareTokenServices/ConfigManager.swift— 增加数据源和 token 的 UserDefaults 持久化,切换数据源时自动切换默认项目和数据库Services/PythonProcessManager.swift— 支持根据 DataSource 选择调用 akshare-fetcher 或 tushare-fetcher CLIViews/SettingsView.swift— 新增"数据源"配置组 (radioGroup 选择器 + tushare Token 输入)Views/DashboardView.swift— 顶部标题栏显示数据源标签 (蓝/绿 badge)
项目累计
- 17 源文件 / ~3,150 行 / 零警告
tushare-fetcherv2.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
- getter: 密码优先从 Keychain 读取,回退到
- 影响: 用户配置的密码和 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,用户明确表示不需要。
当前架构:
.app2.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_whereWHERE 子句: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:
- 新增
activeTimeRangeref('', '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字段的 itemssummarize_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:7b0.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"),导致当日总结生成空内容。
排查过程
- 端口误判纠正:后端默认端口 8100(
BACKEND_PORTenv),此前 curl 测试的 8000 是另一个服务ds4-serve,非本后端 - 打包 dist 过期:
/Applications/FinDataHub.app/.../kuaixun-frontend/dist(19:36)无新前端代码;dev 路径Resources/kuaixun-frontend/dist(21:30)有新代码 - 后端进程陈旧:运行中的
newsflash_backend.py(pid 84231)import 自kuaixun-service(已含新dal.py/public.py),但进程启动早于修改,Python 不热重载 - 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/summaryHTTP 200(260字),/news/heatHTTP 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_MODELtag_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)→ 批量更新 tagsget_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 设计
分阶段策略:
- 阶段一·评分:对所有 importance=0 的快讯做 AI 评分(batch_score_items,并发3),更新 importance 字段
- 阶段二·增强:对 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-07-25 — 统一日志路径 & newsflash_collect stderr 日志
变更概述
将所有组件的日志从易失的 /tmp/ 目录统一迁移到持久化的 ~/Library/Logs/FinDataHub/ 目录结构下,解决系统重启后日志丢失问题。同时修复 newsflash_collect.py 的 backend stderr 日志路径,使其与 Swift 端 LogArchiveService 使用的日志目录保持一致。
根因
- 所有 Python 组件和 launchd 定时任务的日志都散落在
/tmp/下,系统重启后丢失 - DebugLogger.swift 的 debug.log 写入
~/Library/Application Support/FinDataHub/,与 LogArchiveService 的~/Library/Logs/FinDataHub/不一致 - 日志路径分散,难以统一排查问题
统一日志目录结构
~/Library/Logs/FinDataHub/
├── YYYY-MM-DD.jsonl # LogArchiveService 结构化日志(原有)
├── debug.log # DebugLogger 调试日志(从 Application Support 迁入)
├── newsflash/
│ ├── backend-stderr.log # newsflash_collect.py backend stderr
│ └── collector.log # kuaixun collector.py 管道日志
├── akshare/
│ ├── daily.log / daily-err.log
│ ├── weekly.log / weekly-err.log
│ └── monthly.log / monthly-err.log
├── tushare/
│ ├── daily.log / daily-err.log
│ ├── weekly.log / weekly-err.log
│ └── monthly.log / monthly-err.log
└── ths/
└── daily.log / daily-err.log
修改文件
| 文件 | 变更 |
|---|---|
Sources/FinDataHub/Services/DebugLogger.swift |
logFileURL 从 Application Support/FinDataHub/ 改为 Logs/FinDataHub/ |
Resources/bin/newsflash_collect.py |
新增 _LOG_DIR/BACKEND_STDERR_LOG 常量,stderr 路径改为 ~/Library/Logs/FinDataHub/newsflash/backend-stderr.log |
Resources/kuaixun-service/app/routers/collector.py |
log_path 改为 ~/Library/Logs/FinDataHub/newsflash/collector.log,添加目录创建 |
Resources/akshare_fetcher/config/launchd/install.sh |
新增 __HOME__ → $HOME 替换,添加日志子目录 mkdir -p |
Resources/akshare_fetcher/config/launchd/com.akshare.dailyupdate.plist |
日志路径改为 __HOME__/Library/Logs/FinDataHub/akshare/daily{,-err}.log |
Resources/akshare_fetcher/config/launchd/com.akshare.weeklyupdate.plist |
日志路径改为 __HOME__/Library/Logs/FinDataHub/akshare/weekly{,-err}.log |
Resources/akshare_fetcher/config/launchd/com.akshare.monthlyupdate.plist |
日志路径改为 __HOME__/Library/Logs/FinDataHub/akshare/monthly{,-err}.log |
Resources/akshare_fetcher/config/launchd/com.tushare.dailyupdate.plist |
日志路径改为 __HOME__/Library/Logs/FinDataHub/tushare/daily{,-err}.log |
Resources/akshare_fetcher/config/launchd/com.tushare.weeklyupdate.plist |
日志路径改为 __HOME__/Library/Logs/FinDataHub/tushare/weekly{,-err}.log |
Resources/akshare_fetcher/config/launchd/com.tushare.monthlyupdate.plist |
日志路径改为 __HOME__/Library/Logs/FinDataHub/tushare/monthly{,-err}.log |
Resources/akshare_fetcher/config/launchd/com.ths.dailyarchive.plist |
日志路径改为 __HOME__/Library/Logs/FinDataHub/ths/daily{,-err}.log |
Resources/akshare_fetcher/src/akshare_fetcher/cli.py |
schedule 命令的 plist 模板日志路径改为 ~/Library/Logs/FinDataHub/akshare/,添加目录创建 |
技术要点
__HOME__占位符:launchd plist 不支持环境变量展开,源文件中使用__HOME__占位符,install.sh 在复制时替换为实际$HOME,保证可移植性- 目录创建:launchd 不会自动创建日志子目录,install.sh 和 Python 脚本都添加了
mkdir -p确保目录存在 - 与 LogArchiveService 对齐:Swift 端的 LogArchiveService 已使用
~/Library/Logs/FinDataHub/写入 JSONL 结构化日志,本次变更使所有组件统一到同一根目录 - Console.app 集成:使用
~/Library/Logs/标准目录后,日志自动出现在 macOS Console.app 中,便于系统级排查
验证结果
- Swift 构建:
swift build成功,零错误(仅预先存在的 actor 隔离警告) - 代码检查:全项目 grep 确认无遗留的
/tmp/com.*.log或/tmp/findatahub-newsflash-backend-stderr引用 - launchd 任务需重新运行
install.sh后生效