我最终选择了使用 Tauri 来构建我的桌面回测应用。本文记录了开发过程中需求梳理、初始化步骤、遇到的坑以及一些性能优化技巧。


需求梳理

核心功能

  1. 跨平台运行
  2. 触发 Python 脚本执行
  3. 与 MongoDB 后端数据库交互
  4. 前端展示任务管理卡片(Card)、结果表格(Table)、收益图表(Chart)

任务流程设计

  1. 在本地生成待回测任务列表
  2. 将任务列表推送到远程
  3. 远程启动任务
  4. 支持中途暂停任务执行
    1. 任务的执行要求是:异步限流 + 分组并发
    2. 这类任务需要数据库分页管理,适合使用“生产者-消费者”模型来处理。
    3. 在代码中循环(或监听)数据库的更新, 适应随时插入priority任务.

初始化与开发环境搭建

工具/依赖 说明
node.js + npm包管理 https://nodejs.org/ 下载与安装
Rust工具链 安装命令: curl https://sh.rustup.rs -sSf | sh
Rust 依赖(macOS) 安装命令: xcode-select –install
Tauri CLI 命令:npm create tauri-app,可自动创建 Tauri 2 项目框架
启动项目 进入项目目录后运行:npm install && npm run tauri dev
开启调试模式 编辑 tauri.conf.json 中的 "window": { "devtools": true }

🧱 开发与常见问题记录

前端部分(Vue + Naive UI)

  • 打开 DevTools:mac 快捷键为 Cmd + Option + I,不是 F12。
  • 状态共享:使用 PiniataskStore 来管理任务列表,实现多组件共享,减少无意义的读数据库, 可以理解成一种本地缓存吧。
  • 错误调试:如 Unhandled Promise Rejection: TypeError: null is not an object (evaluating 'instance.subTree.component'),可类比为 Python 的 KeyError,说明代码访问了不存在的变量。
  • 禁用拼写检查:在 v-input 中添加 spellcheck: false
  • 请求竞态问题(Race Condition)
    • 输入搜索框 1 → 15 会同时触发 115 的请求,后发请求(15)可能被先返回的(1)结果覆盖。
    • 解决方法:前端维护请求版本号,只保留最新的请求结果。
  • 分页问题naive-uidata-table 不会记录当前页
    • 初步尝试自己管理分页 + 滚动加载,但最后放弃了这个方法😮‍💨, 因为不仅是翻页, 排序也被naive-ui封装在内部, 自己实现太麻烦。
    • 最终选择:从渲染后的 DOM(Document object Model) 中直接读取当前页元素 ID
      • DOM(Document object Model) 是浏览器解析html之后生成的树形结构
    • 最后我还是自己管理了分页, 因为不想一次性加载全部数据库. 并且把query, 排序都移到后端处理.
  • css样式问题
    • 有些样式必须加 !important 才能覆盖 naive-ui 的默认值。
    • 表格显示不全时, 我希望增加行高显示完全. 但是在用ai写代码的时候, 我一直自认为这是expand. 误导了ai, 一直在给我推expand组件, 走了很多弯路😮‍💨 其实应该用定义css实现。
    • 在调整样式时, 用调试功能inspect element确认组件的真实名称. 不要让ai从代码猜测.

后端部分(Rust)

  • invoke 的使用:位于 @tauri-apps/api/core(tauri 2.0的情况),只能在 WebView 中调用,不能在npm run dev打开的浏览器端口中调试
  • 参数匹配问题:Rust 会自动将 小驼峰(camelCase) 转成 蛇形(snake_case),需确保参数名一致。
  • 函数注册:所有后端 API 函数都需在 main.rs 中注册
  • 日志调试:打印完整log用于调试非常重要, 尤其调用外部脚本时, 统一调用函数, 把log打印写在里面.
    • 用async函数和tokio 库设置stdout, stderr实时刷新
  • 异步数据库访问:使用 futures_util::stream::TryStreamExt 提供的 try_next,需手动引入 trait。
  • 共享状态管理(State)
    • 使用 tauri::State 来注册共享数据,如数据库客户端、全局监听状态, 脚本环境和参数等。
    • Rust 的所有多线程共享必须显式声明同步策略
    • 使用 OnceCell 实现全局状态的懒加载,避免重复初始化。

数据库部分(MongoDB)

  • 监听任务变化:使用 ChangeStreamOptions 监听数据库变更,需开启 replica set (副本集)模式。
    • 打开本地数据库命令: mongod --config /usr/local/mongodb/mongod.conf --replSet rs0 --fork
    • 用全局的监听, 否则每次app运行时间一长, 加载就变慢.
  • 任务删除后自动停止监听
  • 使用异步库 Motor
    • 替代传统的 PyMongo(伪异步),性能提升明显。
  • 同步数据问题
    • 使用 BSON 快速传输数据时,会产生10倍以上的瞬时占用,超过 MongoDB Atlas免费 500MB 限额。
    • 解决方法:开启 mongorestorenumInsertionWorkersPerCollection 限制。
  • 增量拉取时防止重复数据
  • MongoDB API 使用
    • 在 MongoDB Atlas 中创建 Service Account 与 API Key: 在 IDENTITY&ACCESS-> Applications 里创建
    • Projectid是网址 可从浏览器 URL 中获取。
  • 查询优化
    • 创建索引加速查询。
    • 查询时排除大字段字段,避免无谓传输。

脚本触发与执行

  • JSON 参数转义地狱,最终选择使用temp file进行参数传递。
  • 所有脚本路径、参数与环境变量统一写入 config.toml 进行集中管理。
  • 使用事件循环运行带异常捕获的主逻辑(asyncio.get_event_loop()traceback.format_exc()), 输出定位到错误代码级别的log.

release部分

  • 把配置的逻辑全部从代码中抽出, 写在配置文件里. 用设置界面显示和修改配置文件
  • build时给用户生成一个resource, 把配置文件放在里面, 让用户可以不通过源码更改配置

Mongodb数据库总结

回测过程(相比csv)

  • 存储具备压缩特性,节省大量空间。
  • 使用异步 API 调用,避免频繁本地 I/O。
  • 支持实时监听任务进度。

结果查看

  • 支持灵活的条件查询。
  • 前后端分离,API 接口易于调用。
    • 缺点是必须配合前端,无法像 CSV 文件直接系统自带app打开处理。