---
title: 项目架构
doc_type: architecture-guide
domain: system
keywords:
  - 项目架构
  - 运行时分层
  - 模块职责
  - 数据流
summary: 解释系统运行时结构、模块职责、关键调用链和后续改动切入口。
when_to_read:
  - 要改跨层调用或 Rust / renderer 边界
  - 想先理解系统怎么搭起来
read_next:
  - ../constraints/README.md
  - ../business/README.md
---

# 项目架构

如果你已经打开这篇架构文档，说明你现在关心的是“系统怎么搭起来”和“边界在哪里”。

这份文档不是简单罗列目录，而是回答 4 个更关键的问题：

1. 这个项目运行时到底由哪几层组成
2. 每层的职责边界是什么
3. 关键业务链路是怎么从页面走到本地数据库和外部数据源的
4. 后续改功能时，应该优先从哪几个模块切入

## 按问题进入

| 如果你现在想确认什么             | 先看本文哪个部分                               | 读完再看什么                                       |
| -------------------------------- | ---------------------------------------------- | -------------------------------------------------- |
| 整体运行时长什么样               | `项目定位`、`一图看懂运行时`                   | `目录地图`                                         |
| 前端、bridge、IPC 的边界怎么切   | `Renderer 层`、`Shared Contract 与 Bridge 层`  | `Rust 后端层`                                      |
| Rust 模块职责怎么分              | `Rust 后端层`、`本地数据模型`                  | `核心业务模块`                                     |
| 想先一次看完项目约束             | [项目约束总表](../constraints/README.md)       | [项目编码约束](../constraints/coding-rules.md)     |
| 项目里哪些代码设计是硬规则       | [项目编码约束](../constraints/coding-rules.md) | 本文的对应分层章节                                 |
| 公告、任务、原文这些链路怎么落地 | `本地数据模型`、`核心业务模块`                 | [公告原文模块说明](../features/notice-original.md) |
| 改功能时先从哪里下手             | `当前成本与风险`、`修改入口速查`               | [项目业务总览](../business/README.md)              |

## 本文职责

| 本文只负责什么                                 | 本文不负责什么             |
| ---------------------------------------------- | -------------------------- |
| 解释运行时结构、模块边界、数据流和演进切入口   | 业务目标本身               |
| 把 renderer、bridge、Rust、SQLite 的职责分清楚 | 单个功能的详细测试步骤     |
| 说明系统为什么这样拆，而不是只列目录           | 初始化包的导入导出操作手册 |

如果你现在更关心业务闭环而不是技术边界，先回到 [项目业务总览](../business/README.md)。如果你现在想先看一份汇总过的约束清单，优先看 [项目约束总表](../constraints/README.md)。如果你现在要先确认“禁止兜底兼容、采用 fail fast”的项目级硬规则，再看 [项目编码约束](../constraints/coding-rules.md)。如果你现在要排查公告原文功能，优先看 [公告原文模块说明](../features/notice-original.md)。

## 项目定位

这个项目不是“前端页面 + 远程后端 API”的传统 Web 形态，而是一个本地优先的桌面端业务应用：

- Vue 3 负责页面、交互、路由、状态和局部缓存
- Tauri bridge 负责把前端调用变成本地 IPC 协议
- Rust 负责业务编排、本地任务、数据库、同步、公告处理、指标计算和系统能力
- SQLite `market.db` 是本地状态中心
- Aktools sidecar、AkShare、Tushare、iFind、CNInfo 这类外部能力只能由 Rust 层统一访问
- 业务日期与市场相关调度统一按北京时间语义运行；存储时间戳保持 UTC，避免跨机器时区漂移

一句话概括：

“一个以 Vue 作为界面层、以 Tauri bridge 作为本地协议层、以 Rust 作为桌面业务中台、以 SQLite 作为本地状态中心，并通过多数据源适配器和本地调度器持续维护行情、公告和分析结果的桌面应用。”

这里还有一个容易被忽略的事实：

- 这个仓库不只有桌面窗口一个运行入口
- 桌面应用、调试预览 CLI、数据同步 CLI 共用同一套 Rust 业务层和同一份 SQLite schema
- 因此架构文档里说的“服务层 / 数据层边界”，同样约束 `src-tauri/src/bin/*`

## 一图看懂运行时

```mermaid
flowchart LR
    UI["Renderer\nviews / components / stores"] --> API["api/client.ts"]
    API --> Bridge["desktop bridge\nwindow.desktopApp"]
    Bridge --> IPC["Tauri commands"]
    IPC --> Services["Rust services\nmarket / notice / scheduler / auth / updates"]
    Services --> DB["SQLite market.db"]
    Services --> Runtime["task_runtime / history_sync / init_package"]
    Services --> Sidecar["Aktools sidecar\nbundled local HTTP service"]
    Services --> Sources["Tushare / iFind / CNInfo"]
    Sidecar --> AkShare["AkShare-compatible endpoints"]
    Services --> Artifacts["reports / snapshots / local artifacts"]
```

核心边界：

- 页面层不直接碰 SQLite
- 页面层不直接请求第三方数据源
- bridge 层不承载业务逻辑，只做协议适配和命名转换
- `commands.rs` 是 IPC 入口，不应该变成跨模块“大杂烩业务实现”
- 真正的业务编排放在 Rust 服务模块里

当前桌面端对 AkShare 口径的数据源还有一个额外运行时约束：

- 普通用户默认不要求自行安装 Docker 或 Python
- `aktools` 作为随安装包分发的 sidecar 本地服务运行
- sidecar 的启停、健康检查和错误暴露由 Rust 运行时负责
- renderer 只能消费 sidecar 产出的正式业务结果，不能直接探测或拼接 sidecar URL

当前时间口径还有一条必须遵守的运行时边界：

- 交易日、公告默认窗口、任务调度判断和“今天”这类业务语义，不跟随用户机器本地时区漂移
- Rust 和 renderer 都应通过统一时间入口获取北京时间，而不是直接读取宿主机本地时钟语义
- 写入 SQLite 的审计 / 运行轨迹时间戳继续与 `CURRENT_TIMESTAMP` 保持一致，统一按 UTC 存储

## 目录地图

### 顶层结构

| 路径                | 角色                                            |
| ------------------- | ----------------------------------------------- |
| `src/renderer/src`  | Vue renderer：页面、组件、路由、store、工具     |
| `src/shared`        | 前后端共享桌面契约                              |
| `src-tauri/src`     | Rust 本地业务中台                               |
| `src-tauri/src/bin` | 桌面外的 CLI 入口，例如同步、映射回填和链路预览 |
| `src-tauri/sql`     | SQLite schema 和数据字典                        |
| `docs`              | 项目架构、功能对齐、测试与导入方案文档          |

### Renderer 模块地图

| 路径                                      | 作用                                                    |
| ----------------------------------------- | ------------------------------------------------------- |
| `main.ts`                                 | 前端启动入口，安装 Pinia、Router、Element Plus 和桌面桥 |
| `App.vue`                                 | 应用外壳和顶层布局                                      |
| `router/index.ts`                         | 路由定义和登录态守卫                                     |
| `api/client.ts`                           | 页面调用的统一门面                                      |
| `platform/desktop/createDesktopBridge.ts` | Tauri invoke 适配层                                     |
| `platform/runtime.ts`                     | 运行时判断和登录跳转等桌面运行期工具                    |
| `stores/*`                                | 登录、主题、工作区、价格颜色等前端状态                  |
| `composables/*`                           | 设置中心、股票数据等页面复用逻辑                        |
| `views/*`                                 | 页面级组件                                              |
| `components/*`                            | 业务组件和基础组件                                      |
| `utils/*`                                 | 工具函数，例如公告链接、反调试和运行时辅助              |

### Rust 模块地图

| 路径                                           | 作用                                                          |
| ---------------------------------------------- | ------------------------------------------------------------- |
| `lib.rs`                                       | Tauri 组装入口，注册状态、命令、调度器、托盘和自动任务        |
| `commands.rs`                                  | IPC 命令入口，承接前端调用                                    |
| `db.rs`                                        | SQLite 初始化、迁移、默认数据和底层访问                       |
| `desktop_market_config.rs`                     | 桌面行情配置的 SQLite 读写、运行态视图，以及 legacy JSON 一次性导入 |
| `aktools_runtime.rs`                           | Aktools sidecar 启停、健康检查、状态查询与自动拉起            |
| `market_sync_service.rs`                       | 行情同步与指标计算的主编排服务                                |
| `data_source/*`                                | Aktools/AkShare、Tushare、iFind 数据源适配器                  |
| `indicators/*`                                 | 技术指标计算引擎与指标实现                                    |
| `history_sync.rs`                              | 历史同步进度管理器                                            |
| `init_package.rs`                              | 初始化包导入进度管理器                                        |
| `notice_sync.rs`                               | CNInfo 公告抓取和落库                                         |
| `cninfo_notice_type.rs`                        | CNInfo 公告类型码表同步、校验和落库                           |
| `notice_pipeline.rs`                           | 公告同步后的编排链：交易日历校验、`t_date` 回填、过滤跳过语义 |
| `notice_ai.rs`                                 | 公告分析与深度报告相关逻辑                                    |
| `notice_indicator.rs`                          | 公告相关指标补充计算                                          |
| `notice_original_read_model.rs`                | 公告原文列表/详情读模型                                       |
| `notice_status.rs`                             | 公告状态常量与约束                                            |
| `stock_filter.rs`                              | 公告业务过滤逻辑                                              |
| `scheduler.rs`                                 | 本地 cron 调度器                                              |
| `task_runtime.rs`                              | 任务运行记录、事件流和状态推进                                |
| `logging.rs`                                   | 本地日志系统                                                  |
| `llm.rs`                                       | LLM 请求封装和模型访问边界                                    |
| `security.rs`、`anti_debug.rs`、`obfuscate.rs` | 桌面运行期安全与反调试支持                                    |
| `trade_calendar_utils.rs`                      | 交易日相关工具                                                |
| `app_icon.rs`                                  | 图标与托盘图标相关支持                                        |

### CLI 入口

当前 `src-tauri/src/bin/*` 里至少有 4 类可直接执行的本地入口：

- `cninfo-sync.rs`：按日期窗口把 CNInfo 公告同步进指定 SQLite
- `cninfo-notice-type-sync.rs`：回填或刷新 `api_cninfo_notice_type_map`
- `notice-original-preview.rs`：在终端快速预览公告原文读模型结果
- `task-runner.rs`：按任务键顺序手动触发本地调度任务，适合做整链路闭环验证

这些 CLI 的定位不是另起一套逻辑，而是：

- 复用 `MarketDatabase`、同步服务和读模型
- 作为回归验证、运维修复和链路排查的辅助入口
- 帮助在不启动桌面 UI 的情况下精确验证数据链路

## Renderer 层

### 1. 页面层

页面大致分成 4 类：

- 工作台与公告分析：`TodayWatchView.vue`、`HomeView.vue`
- 标的详情与行情：`StockDetailView.vue`、`StockQuoteView.vue`
- 公告深入能力：`AnalysisDetailView.vue`、`NoticeOriginalView.vue`
- 设置与运维：`views/settings/*`

页面层原则：

- 只消费 `api/client.ts`
- 不直接调用 `invoke()`
- 不直接关心 SQLite 表结构

### 2. 状态层

当前核心 store 包括：

- `auth.ts`：登录恢复、退出、会话状态
- `workspace.ts`：市场工作区切换
- `theme.ts`：主题切换
- `priceColor.ts`：涨跌配色
- `globalError.ts`：全局错误提示

这说明 renderer 侧的状态中心仍然偏“界面态”，而不是“业务中台态”。

### 3. 设置页边界

当前设置页的设计边界已经收口：

- 用户通过同一套正式菜单访问基础设置、数据处理和控制面页面
- `任务调度` 页内部再拆成 `调度概览`、`调度历史` 和 `失败任务` 子页，避免把调度配置、运行记录和失败回收混在一个长页面里
- `调度历史`、`失败任务`、`LLM 日志` 这类控制面长列表统一走后端分页，页面只负责传递 `page / size` 并消费 `list / total / page / size / pages`，不允许再用固定 `limit` 做伪分页截断
- `router/index.ts` 负责登录态守卫和设置路由组织，不再区分开发者模式显隐
- 设置页统一采用“左侧分类导航 + 右侧工作区”的壳层；当前分类的摘要状态和说明应先于具体表单出现，避免重新退回到散乱长表单
- 通用 `/settings/sysconfig` 投影页已经下线；普通设置页不再暴露 `sys_config` 全表，也不再把 `sys_llm_*` 投影回“系统配置”做重复真值编辑
- 数据源设置统一收口到 `/settings/data-sources/*` 父页，内部再切 AkShare、Tushare、iFind、CNInfo 子页
- `LLM 设置` 统一收口到 `/settings/llm` 配置池父页；父页负责展示配置池列表、诊断摘要和新增入口，详情页通过动态 profile 路由进入
- LLM 配置池里的每条 profile 都区分“实例 ID”和“provider 类型”，允许同一类型存在多条实例
- provider 详情页只负责当前实例的连接参数、启用状态和凭据，不承担跨 provider 的市场策略配置
- `LLM` provider 详情页只保存当前 profile 的字段，不跨页提交其他 profile 草稿
- 公告过滤阈值这类业务规则不放进 provider 连接页，单独作为数据处理规则页面维护
- `LLM 设置`、`数据源设置`、`公告规则`、`系统状态` 各自维护自己的正式真值；如果某项已经有专用页面或专用表，就不允许再回流到通用配置总表
- `LLM 设置` 与 `数据源设置` 现在只展示和保存后端已落库的正式值；输入框里的示例地址只属于 placeholder，不再作为初始化默认值写入运行时
- provider 设置页可以展示“当前支持能力”清单，但必须区分“适配器已实现接口”和“当前业务已接入能力”
- 能力标注默认使用三种状态：`已接入业务`、`已实现接口`、`当前未纳入业务`
- 判定口径以 Rust 实际调用链为准，不按单个 adapter 是否实现某个 trait 方法做过度承诺
- CNInfo provider 配置现在也属于正式桌面运行时配置边界，地址、用户名和密码由 `desktop_market_config.rs -> sys_config` 统一存储，公告同步不再硬编码 CNInfo host；当 `cninfo.enabled=true` 时，公告同步会在进入抓取前对这 3 个字段做 fail fast 校验
- AkShare 设置页里的地址口径是 sidecar 暴露给本机的 HTTP 基地址，不要求普通用户理解 Docker 或 Python 启动细节
- AkShare / Tushare / iFind / CNInfo 以及 `sys_llm_provider_profile` 的 provider 地址都不再 seed 代码内默认 host；`apiUrl` 为空时，运行时统一按“未配置”处理并直接 fail fast，不再回退到 `127.0.0.1:8080`、`api.tushare.pro`、`api.openai.com` 之类内置地址

当前规则：

- 设置中心不再依赖构建环境或 `localStorage` 切换开发者菜单
- 菜单可见性以正式路由和导航分组为准

## Shared Contract 与 Bridge 层

这是这个项目最重要的一层“本地协议边界”。

### 1. 共享契约

`src/shared/desktop-api.ts` 的职责是定义 renderer 与 desktop bridge 之间的统一契约。

它的作用不是类型装饰，而是把前后端的本地调用边界固定下来：

- 哪些能力可以被前端调用
- 每个能力接受什么参数
- 返回结构长什么样
- 桌面市场配置要按 `akshare / tushare / ifind / cninfo` 分组暴露，避免把 provider 配置拍平成单页表单模型
- provider 设置页保存时只提交当前 profile 的字段，不能顺手改写其他 profile 的配置
- 尚未对普通用户开放的跨 provider 优先级策略，可以保留在 Rust 运行态，但不强行塞进当前设置 UI

### 2. Bridge 适配层

`src/renderer/src/platform/desktop/createDesktopBridge.ts` 做几件事：

- 暴露 `window.desktopApp`
- 调用 Tauri `invoke()`
- 把 Rust 的命令模型适配成前端更稳定的接口形式
- 聚合成 `auth / settings / market / notice / tracker / bootstrap / admin / updates` 这样的子域
- `createDesktopBridge.ts` 只负责组装，各子域实现在 `platform/desktop/modules/*`
- 市场数据里的 `source / sourceProvider` 必须由 Rust producer 直出；bridge 只能做字段改名和空值规整，不能再通过 `split('.')`、`split(':')` 之类的字符串规则猜 provider

### 3. API 门面层

`src/renderer/src/api/client.ts` 是页面真正消费的入口。

这一层已经体现出当前项目的明确路线：

- renderer 统一只走本地 bridge
- 不再保留远程后端 fallback
- 页面拿到的是“领域 API”，而不是“命令字符串”

这意味着这个项目已经不是浏览器/桌面双模应用，而是桌面端单运行时应用。

## Rust 后端层

### 1. 应用装配层：`lib.rs`

`src-tauri/src/lib.rs` 是桌面端的 composition root。

它负责：

1. 注册 Tauri 插件，例如 `shell`、`dialog`、`fs`、`http`、`autostart`、`updater`
2. 注册全局状态，例如 `MarketDatabase`、`HistorySyncManager`、`InitPackageImportManager`、`DesktopMarketConfigStore`
3. 注册全部 IPC 命令
4. 在 `setup()` 中初始化日志、数据库、市场配置、调度器、托盘和自动日更循环

这里还承接了几个桌面运行时专属能力：

- 托盘菜单
- 关闭窗口隐藏到后台
- 启动后自动调度
- 收盘后自动触发 A 股日线补齐

### 2. IPC 命令层：`commands.rs`

`commands.rs` 是前端可见能力的本地入口，职责应该始终保持在：

- 接收 IPC 参数
- 调用服务层或数据库层
- 返回前端可消费的结构
- 把错误转成清晰字符串

当前命令大致分成：

- 应用与设置：版本、数据目录、市场配置、更新
- 数据访问：行情、K 线、交易日历、指标、快照
- 数据同步：单股补齐、历史同步、初始化包导入
- 公告与分析：公告列表、公告详情、评分、深度报告、今日看盘、公告原文
- 运维系统：日志、任务运行、失败操作、系统状态、账户管理

### 3. 数据库层：`db.rs`

`db.rs` 管理 SQLite 生命周期、建表、默认数据和迁移。

几个关键点：

- 数据库存放在应用数据目录下的 `market.db`
- 启动时会设置 WAL、外键和 busy timeout
- `src-tauri/sql/desktop_schema.sql` 是 market.db 新库唯一基线，`db.rs` 启动时直接执行它
- Rust 运行时仍负责补列迁移、legacy 表搬迁、默认数据和升级修复，但不再内嵌第二份全量建表真值

## 本地数据模型

当前数据库可以按职责分成 7 组：

| 分组                         | 典型表                                                                                                |
| ---------------------------- | ----------------------------------------------------------------------------------------------------- |
| 行情主数据                   | `stock_basic_info`、`api_tushare_*`、`api_akshare_price_daily`、`stock_trade_calendar`                |
| 技术指标                     | `stock_indicator_*`、`stock_tech_indicator_daily`                                                     |
| 同步状态                     | `sync_status`、`sync_task_log`、`sync_import_batch`                                                   |
| 公告主数据                   | `notice_record`、`biz_notice_fact`、`biz_notice_analysis`、`biz_notice_deep_report`                   |
| 公告原始来源、分类映射与制品 | `api_cninfo_notice_raw`、`api_cninfo_notice_type_map`、`art_artifact`                                 |
| 系统与任务                   | `sys_config`、`sys_scheduled_task`、`tsk_run`、`tsk_run_event`、`sys_user_account`、`llm_request_log` |

### 公告链路的当前落点

公告相关数据现在分成两层：

- `notice_record`：当前运行时仍在消费的 legacy 混合表，里面同时有公告事实、过滤状态、分析结果和深度报告字段
- `biz_notice_fact`：新的统一公告事实层
- `biz_notice_analysis`：新的过滤与分析工作流表
- `biz_notice_deep_report`：新的深度报告表
- `api_cninfo_notice_raw`：CNInfo 原始元数据，例如 `provider_notice_id`、`source_url`、`category_code`、`raw_payload_json`
- `api_cninfo_notice_type_map`：保存 CNInfo 官方公告类型码表，提供 `type_code -> category_name / parent_code`
- `art_artifact`：制品位置和状态

当前阶段已经完成的是：

- 新 `biz_notice_*` 表真实落库
- 初始化时会把 `notice_record` 的历史数据单次事务回填到新表

当前阶段还没有完成的是：

- Rust 运行时查询和 IPC 全量切到 `biz_notice_*`
- 删除 legacy `notice_record`

这使得“公告原文”模块和后续公告分析切表可以在有真实数据底座的前提下分阶段推进，而不是停留在纯设计状态。

这里有一条新的硬边界：

- `api_*` 表只保存 API 原始返回值，保证原始事实可重现
- 业务消费需要的正式字段，例如 `noticeLink`、`publishTime`、`categoryName`，由 Rust 服务层或读模型层派生
- 派生后的业务字段不得反写回 `api_*` 原始表

这也意味着“公告分类显示”本身已经不是单表字段问题，而是一条独立的小链路：

- `notice_sync.rs` 负责保存原始 `category_code`
- `cninfo_notice_type.rs` 负责同步官方公告类型码表，并校验本地 `f006v` 是否全部可映射
- `notice_original_read_model.rs` 负责基于 `api_cninfo_notice_raw` 派生正式打开链接，并把 `f006v` 的全部分类码翻译成页面可见的中文分类名

详细对齐说明见：

- [公告原文模块对齐参考实现说明](../features/notice-original.md)
- [公告原文功能测试文档](../operations/notice-original-functional-test.md)

## 核心业务模块

### 1. 行情同步：`market_sync_service.rs`

这是行情数据编排器，而不是简单 repository。

职责包括：

- 基于 `DesktopMarketConfigStore` 构建 `DataSourceManager`
- 选择可用数据源和优先级
- 拉取基础信息、历史行情、多来源日线查询结果
- 计算技术指标和 EMA
- 输出历史同步进度

这里要区分两个边界：

- provider 配置边界：AkShare、Tushare、iFind 各自的地址、token、启用状态
- 市场策略边界：当同一类能力可由多个 provider 提供时，运行态如何选择可用 provider

前者属于设置页里的 provider 独立配置；后者属于 Rust 服务层和运行态配置，不要求在当前普通用户设置页暴露。

同时还要区分“能力实现边界”和“业务接入边界”：

- 能力实现边界：某个 adapter 是否实现了 `get_stock_history`、`get_trade_calendar`、`get_spot_quotes` 这类统一接口
- 业务接入边界：当前桌面端的同步、补齐、调度和查询链路，是否真的把该 provider 当作正式业务通道使用

例如：

- AkShare 和 iFind 适配器都实现了 `get_trade_calendar`
- 但当前交易日历同步业务固定使用 `TushareAdapter`
- 因此设置页里 AkShare / iFind 的交易日历能力只能标成“已实现接口”或“当前未纳入业务”，不能标成“已接入业务”
- Aktools/AkShare 适配器实现了 `get_stock_history`、`get_intraday`、`get_spot_quotes`
- 当前桌面端正式把它接入到标的信息补齐、历史日线补齐、个股分时查询、实时行情快照刷新，以及今日看盘行情刷新 / 读取链路
- 今日看盘链路的正式语义以 [当日看盘行情读写拆分方案](../features/today-watch-quote-separation.md) 为准：刷新由后台任务负责，查询只读本地正式结果

其中交易日历现在有单独边界：

- `sync_trade_calendar` 不再走通用 `DataSourceManager` 回退链
- 同步侧固定使用 `TushareAdapter`
- 外部只传市场分组 `A/H/U`
- `A` 会在 provider 内部展开为 `SSE/SZSE/BSE` 后按日期合并
- `H` 对应 `hk_tradecal`，`U` 对应 `us_tradecal`
- 本地查询仍然只读 `stock_trade_calendar`，不会在查询时临时访问远端 provider

相关辅助模块：

- `history_sync.rs`：历史同步进度状态机
- `init_package.rs`：初始化包导入进度状态机
- `data_source/*`：具体适配器

### 2. 公告同步与处理：`notice_sync.rs` + `notice_pipeline.rs`

公告链路已经从“抓一批公告写库”发展成一条编排链：

1. `NoticeSyncService` 从 CNInfo 抓公告
2. 全量写入 `api_cninfo_notice_raw`
3. 只把通过业务准入的公告投影到 `biz_notice_fact`
4. 为投影结果初始化 `biz_notice_analysis` 工作集
5. 计算待处理公告窗口
6. 校验公告窗口对应的本地交易日历
7. 交易日历就绪时回填 `t_date`
8. 执行业务过滤

其中：

- `notice_sync.rs` 负责抓取、`api_cninfo_notice_raw` 原始快照落库，以及把通过业务准入的记录投影到 `biz_notice_fact` / `biz_notice_analysis`
- `notice_pipeline.rs` 负责把同步结果、本地交易日历校验、`t_date` 回填和过滤串起来
- `stock_filter.rs` 负责公告业务过滤
- `notice_ai.rs` 负责独立的公告分析和报告
- `notice_indicator.rs` 负责公告指标
- 市场底座由独立任务准备：`tushare.trade_calendar.sync`、`tushare.stock_basic_daily_basic.sync`、`tushare.stk_factor_pro.sync`、`tushare.limit_list_d.sync`、`tushare.suspend_d.sync`

这里的正式边界是：

- `api_cninfo_notice_raw` 只保存 provider 原始快照和原始来源元数据，不写业务派生字段
- `biz_notice_fact` 只保存正式业务可消费事实，例如 `notice_link`
- `biz_notice_analysis` 承担桌面端轻量工作集角色，负责承接后续 `t_date` 回填、过滤、分析和重试状态
- `trade_date` 不是同步阶段字段；它只由 `notice_pipeline` 在交易日历就绪后回填

这里要特别区分两个“同步入口”：

- 调度任务 `cninfo.notice.sync` 走的是 `run_notice_sync_pipeline`
- 这条入口会顺序执行“抓取 -> 交易日历窗口校验 -> `t_date` 回填 -> 过滤”
- 如果本地交易日历窗口还没准备好，pipeline 会返回 warning 并跳过后处理，不会在链内临时补拉
- CLI `src-tauri/src/bin/cninfo-sync.rs` 现在也复用同一条 `notice_pipeline`
- CLI 额外支持传入显式日期窗口，适合做“按日期同步公告 + 验证后处理状态推进”验证
- CLI 会读取 SQLite 中的桌面市场配置；如果数据库仍是默认值且同目录存在 legacy `desktop-market-config.json`，会先做一次性导入，再复用与调度任务一致的运行时配置
- AI 解析不再挂在 `cninfo.notice.sync` 后面，而是由独立任务 `notice.ai.analysis` 消费 `biz_notice_analysis.analysis_status='PENDING'` 的工作集

更具体的任务链与手动触发方式见 [公告与市场任务链路](../business/notice-market-pipeline.md)。

排查“同步后为什么还没过滤”时，先确认实际跑的是完整 pipeline，再检查本地交易日历和涨停/停牌底座是否已经由独立任务准备好；当前调度任务和 CLI 都不会在公告链里临时补拉这些底座。

### 3. 公告原文查询：`notice_original_read_model.rs`

这是当前架构里一个非常明确的“读模型”模块。

它把公告原文分成：

- 列表模型
- 详情模型

并且在查询时优先联接：

- `biz_notice_fact`
- `api_cninfo_notice_raw`
- `art_artifact`

这样页面层可以稳定获得：

- 原始分类码
- 原文链接
- 提供方公告 ID
- 快照 ID
- 抓取时间
- 原始响应 JSON

当前列表和详情读模型都已经以 `biz_notice_fact + api_cninfo_notice_raw` 为正式来源，只在极少数历史数据场景里保留对 legacy 字段语义的单次迁移兼容判断。

而不是依赖页面自己拼字段。

### 4. 公告分类映射：`cninfo_notice_type.rs`

这部分是公告原文“分类名可见、可筛选”的关键支撑，而不是附属工具。

它负责：

- 同步 CNInfo 官方公告类型码表
- 生成并更新 `api_cninfo_notice_type_map`
- 对本地已落库的 `f006v` 做全量覆盖校验
- 为读模型提供 `category_name` 显示和筛选依据

这条链路有两个触发入口：

- `scheduler.rs` 中的定时任务
- `src-tauri/src/bin/cninfo-notice-type-sync.rs` 的手工 CLI

### 5. 调度与任务运行：`scheduler.rs` + `task_runtime.rs`

这部分是“后台持续运行”架构的核心。

`scheduler.rs` 负责：

- 每 60 秒 tick 一次
- 从 `sys_scheduled_task` 读取启用任务
- 用 cron 表达式判断是否到点
- 触发本地任务

当前交易日历已经接入独立自动任务：

- `tushare.trade_calendar.sync`
- 默认每天 `06:05` 执行
- 同步窗口与后端保持同一口径：回溯 2 年，前瞻 1 年
- 任务会顺序同步 `A/H/U` 三个市场分组对应的本地交易日历
- 失败按任务失败处理，不在查询链路做运行时兜底补拉

`task_runtime.rs` 负责：

- 创建运行记录 `tsk_run`
- 记录运行事件 `tsk_run_event`
- 维护运行状态机
- 在启动时恢复被中断的任务状态

这意味着当前项目里“调度配置”和“任务实际运行记录”已经是两层分离的结构，而不是只有一个简单的任务表。

当前与公告链直接相关的关键任务键包括：

- `tushare.trade_calendar.sync`
- `tushare.stock_basic_daily_basic.sync`
- `tushare.stk_factor_pro.sync`
- `tushare.limit_list_d.sync`
- `tushare.suspend_d.sync`
- `cninfo.notice.sync`
- `notice.t_date.backfill`
- `notice.filter`
- `notice.ai.analysis`

## 关键调用链

### 1. 页面读取行情

```text
View
-> api/client.ts
-> desktop bridge
-> Tauri command
-> MarketSyncService / MarketDatabase
-> SQLite
-> 返回页面渲染
```

### 2. 页面查询只读本地正式结果

```text
StockDetailView
-> api/client.ts
-> desktop bridge
-> Tauri command / MarketDatabase
-> SQLite
-> 页面渲染或显式缺口提示
```

### 3. 公告自动处理链路

```text
Scheduler tick
-> tushare.trade_calendar.sync / tushare.stock_basic_daily_basic.sync / tushare.stk_factor_pro.sync / tushare.limit_list_d.sync / tushare.suspend_d.sync
-> cninfo.notice.sync
-> notice.t_date.backfill / notice.filter
-> notice.ai.analysis
-> 页面读取本地结果
```

### 4. 公告原文查询链路

```text
NoticeOriginalView.vue
-> getNoticeOriginalList / getNoticeOriginalDetail
-> get_notice_original_list_local / get_notice_original_detail_local
-> notice_original_read_model.rs
-> notice_record + api_cninfo_notice_raw + art_artifact
-> 列表页 + 详情面板
```

### 5. 公告分类名显示与筛选链路

```text
cninfo-notice-type-sync / scheduler task
-> CninfoNoticeTypeService
-> api_cninfo_notice_type_map
-> notice_original_read_model.rs
-> categoryName 查询 + categoryName 展示
-> NoticeOriginalView.vue
```

## 当前的设计规则

后续继续演进时，建议把这些当成硬边界：

### 1. 页面层不越级

- 页面只能调 `api/client.ts`
- 不直接写 `invoke()`
- 不直接依赖 SQLite 表结构字段

### 2. Bridge 层不承载业务逻辑

- bridge 只做协议适配和命名转换
- 行情来源、provider 归属和多来源汇总口径属于 producer 语义，不能在 bridge 层通过字符串拆分临时推导
- 真正的业务处理放 Rust 服务层

### 3. 长任务必须由 Rust 管

- 同步、导入、调度、分析都应该在 Rust 后台执行
- renderer 只负责启动、展示状态和查看结果

### 4. SQLite 是本地状态中心

- 页面展示的关键结果应尽量从本地库读
- 不要让页面直接维护一套脱离数据库的“隐藏真实状态”

### 5. 运维能力分层

- 用户可直接进入 `任务调度`、`通知治理`、`LLM 日志` 等控制面菜单
- 如果某项操作需要更高门槛，应在对应动作处显式约束，而不是靠整页隐藏
- 控制面页面仍应保持研究工作台语义，不能退化成孤立后台

## 修改入口速查

如果你接下来要改某一块，建议从这里进：

| 目标                            | 优先入口                                                                                                                                                            |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 改页面布局和交互                | `src/renderer/src/views/*`                                                                                                                                          |
| 改前端调用方式                  | `src/renderer/src/api/client.ts`                                                                                                                                    |
| 改 bridge 协议                  | `src/renderer/src/platform/desktop/createDesktopBridge.ts` + `src/shared/desktop-api.ts`                                                                            |
| 新增/修改 Tauri 命令            | `src-tauri/src/commands.rs`                                                                                                                                         |
| 改数据库表或默认数据            | `src-tauri/src/db.rs`                                                                                                                                               |
| 改行情同步                      | `src-tauri/src/market_sync_service.rs`                                                                                                                              |
| 改数据源切换                    | `src-tauri/src/desktop_market_config.rs` + `src-tauri/src/data_source/*`                                                                                            |
| 改数据源设置壳层与子页切换      | `src/renderer/src/views/settings/*` + `src/renderer/src/router/index.ts` + `src/renderer/src/composables/useDesktopSettingsCenter.ts` + `src/shared/desktop-api.ts` |
| 改 LLM 设置配置池与 profile 详情页 | `src/renderer/src/views/settings/*` + `src/renderer/src/router/index.ts` + `src/shared/desktop-api.ts` + `src-tauri/src/llm_settings.rs`                               |
| 改公告抓取                      | `src-tauri/src/notice_sync.rs`                                                                                                                                      |
| 改公告分类映射                  | `src-tauri/src/cninfo_notice_type.rs` + `api_cninfo_notice_type_map`                                                                                                |
| 改公告编排链                    | `src-tauri/src/notice_pipeline.rs`                                                                                                                                  |
| 改公告原文查询                  | `src-tauri/src/notice_original_read_model.rs` + `src/renderer/src/views/NoticeOriginalView.vue`                                                                     |
| 改调度与任务运行                | `src-tauri/src/scheduler.rs` + `src-tauri/src/task_runtime.rs`                                                                                                      |
| 改 CLI 验证或运维入口           | `src-tauri/src/bin/*`                                                                                                                                               |
| 改日志和调试                    | `src-tauri/src/logging.rs` + `views/settings/DebugToolsSettings.vue`                                                                                                |

## 架构优点

- 本地优先，弱网和离线场景下仍然保留核心能力
- 业务能力集中在 Rust 层，页面层不容易长成“巨型脚本”
- 数据源适配器化，可扩展性比散落在页面里的请求逻辑更强
- SQLite 让行情、公告、调度状态、任务运行记录都具备本地可追溯性
- 调度器、托盘和后台常驻支持持续同步，不依赖用户手动触发

## 当前成本与风险

- renderer、shared contract、bridge、commands、db schema 是多层联动，接口改动成本高
- `commands.rs` 体量仍然偏大，后续继续加功能时容易膨胀
- `db.rs` 同时承担建表、迁移和默认数据职责，复杂度持续上升
- renderer 侧状态中心仍偏分散，一些跨页面业务状态还没有统一抽象

## 当前文档的使用方式

建议这样使用这份文档：

- 想知道“这个项目到底怎么跑”的时候，看“项目定位”和“运行时图”
- 想知道“某个模块应该去哪里改”的时候，看“目录地图”和“修改入口速查”
- 想知道“某条业务链从哪里到哪里”的时候，看“关键调用链”
- 想判断“这个改动该放前端、bridge 还是 Rust”的时候，看“设计规则”
