news 2026/9/17 3:52:34

TaxHacker 数据迁移实战:v0.3 升级 v0.5 的 SQLite → PostgreSQL 完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TaxHacker 数据迁移实战:v0.3 升级 v0.5 的 SQLite → PostgreSQL 完整指南

TaxHacker 数据迁移实战:v0.3 升级 v0.5 的 SQLite → PostgreSQL 完整指南

【免费下载链接】TaxHackerSelf-hosted AI accounting app. LLM analyzer for receipts, invoices, transactions with custom prompts and categories项目地址: https://gitcode.com/GitHub_Trending/ta/TaxHacker

TaxHacker 在 v0.5 版本中完成了底层数据库从 SQLite 到 PostgreSQL 的切换,由于两种数据库无法无缝在线迁移,旧实例的数据需要借助应用内置的备份/恢复功能手动搬运。本文将围绕迁移文档(docs/migrate-0.3-0.5.md)给出的四步流程,结合当前仓库源码,讲解完整的迁移操作、备份 ZIP 归档的内部结构与恢复机制的底层实现,并给出大文件上传失败的排查方法。读完本文,你可以安全地在自己的自托管实例上完成 v0.3 → v0.5 的数据迁移,且不丢失任何交易记录、附件和配置。

迁移背景:为什么 v0.5 不能无缝升级

v0.5 将数据库从 SQLite 切换到了 PostgreSQL。从仓库的部署配置可以确认这一点:docker-compose.yml 中已经内置了独立的postgres服务(postgres:17-alpine),应用通过DATABASE_URL=postgresql://postgres:postgres@postgres:5432/taxhacker连接数据库;README 的环境变量表也将DATABASE_URL定义为 PostgreSQL 连接串(PostgreSQL 17+ 推荐)。

数据库引擎的更换意味着旧版本 SQLite 文件中积累的数据无法被新版本直接读取,因此官方文档明确说明:迁移需要手动完成。但有一个关键保证——即使你已经提前升级到了 v0.5,旧数据也没有丢失,它仍然安全地保存在旧实例的数据目录中,只要按正确顺序操作就能完整找回。

从源码结构看,Prisma 的迁移历史(prisma/migrations)从20250403104933_init起步,随后依次叠加了 storage、token limit、stripe、business details、app data、progress、cached parse result、split tx items 等迁移,最终演进到当前以 PostgreSQL 为底座的 schema,这从侧面印证了 v0.3 到 v0.5 之间数据库结构经历了重大变化。

整个迁移思路可以概括为四步:回滚到 v0.3.0 → 在旧版本上导出备份 → 升级到 latest → 在新版本上恢复备份

Step 1:把 docker-compose 锁定回 v0.3.0

迁移的第一步是把应用镜像固定回 v0.3.0。之所以必须先回滚,是因为备份归档需要由旧版本自己的备份导出逻辑生成,才能保证与 v0.5 的恢复器兼容。修改 docker-compose 中app服务的镜像标签:

services: app: image: ghcr.io/vas3k/taxhacker:v0.3.0 ports: - "7331:7331" // 其余配置保持不变

注意7331:7331是应用默认端口映射,容器内外均使用 7331 端口(对应 README 中PORT环境变量默认值)。除镜像标签外,其余所有配置(卷挂载、环境变量、服务依赖)都不要改动,避免引入额外变量。

Step 2:重启应用并导出备份归档

固定镜像版本后,重启应用使 v0.3.0 生效:

docker compose down docker compose up -d

重启完成后,打开浏览器访问http://localhost:7331,进入Settings → Backups页面,点击Download Data Archive按钮,把生成的.zip归档文件保存到本地机器。

从源码看,这个按钮对应备份设置页面的下载逻辑(app/(app)/settings/backups/page.tsx/settings/backups/page.tsx>)):点击后会先启动一个 "backup" 类型的进度任务,然后请求GET /settings/backups/data?progressId=...下载taxhacker-backup.zip。下载期间界面会显示 "Archiving x/y files" 的实时进度。

备份归档内部结构

备份导出路由的实现位于 app/(app)/settings/backups/data/route.ts/settings/backups/data/route.ts>),它使用JSZip在内存中构建归档,内部结构如下:

taxhacker-backup.zip ├── data/ │ ├── metadata.json # 备份元信息:版本号、时间戳、包含的模型文件清单 │ ├── settings.json # 用户设置(含 LLM 提示词等) │ ├── currencies.json # 自定义货币 │ ├── categories.json # 分类 │ ├── projects.json # 项目 │ ├── fields.json # 自定义字段 │ ├── files.json # 文件元数据索引 │ ├── transactions.json # 全部交易记录 │ └── uploads/ # 上传的原始附件(收据、发票、PDF 等)

几个值得注意的实现细节:

  • 版本化元数据:归档根目录固定写入metadata.json,其中version字段当前为"1.0",同时记录生成时间戳和模型清单。恢复端正是靠这个字段做兼容性校验。
  • 模型数据以 JSON 导出:每个数据表对应一个 JSON 文件,导出逻辑由 models/backups.ts 中的MODEL_BACKUP数组驱动,其顺序(settings → currencies → categories → projects → fields → files → transactions)在恢复时同样重要。
  • 单文件 64MB 上限:导出时单个附件超过 64MB 会被跳过并打印警告(Skipping large file ... > 64MB limit),避免把超大文件压入归档导致内存问题。
  • 进度上报:每处理 2 秒或全部完成时向进度服务上报current/total,这就是页面上进度文案的数据来源。

Step 3:升级 TaxHacker 到最新版本

备份归档安全落地后,把镜像标签改回最新版:

services: app: image: ghcr.io/vas3k/taxhacker:latest ports: - "7331:7331" // 其余配置保持不变

再次重启让新版本(v0.5+,PostgreSQL 底座)生效:

docker compose down docker compose up -d

此时新的实例使用全新的 PostgreSQL 数据库,是"干净"的状态,等待恢复数据。

Step 4:在新实例上恢复数据

进入Settings → Backups页面,找到Restore from a backup区域:选择之前保存的 ZIP 归档,勾选 "I understand that it will permanently delete all existing data" 确认框,点击Restore from backup。等待几秒钟(数据量大时按钮会显示 "Restoring from backup... (it can take a while)"),恢复成功后页面会展示详细的导入统计,列出每个数据文件的恢复条数。

恢复机制的源码级原理

恢复流程的服务端实现位于 app/(app)/settings/backups/actions.ts/settings/backups/actions.ts>) 的restoreBackupAction,整个流程分四步:

1. 归档校验。使用JSZip.loadAsync解压,若 ZIP 损坏直接报 "Bad zip archive";随后读取data/metadata.json校验版本,仅支持SUPPORTED_BACKUP_VERSIONS = ["1.0"],不匹配会提示 "Incompatible backup version"。另外上传文件超过MAX_BACKUP_SIZE = 256MB会直接拒绝。

2. 清空现有数据。cleanupUserTablesMODEL_BACKUP逆序逐表deleteMany,先删交易、文件等引用方,再删分类、项目等被引用方,以规避外键约束;同时递归删除用户上传目录。

3. 按序恢复各表。遍历MODEL_BACKUP读取对应 JSON 文件,逐条调用modelFromJSON(models/backups.ts)写入数据库。恢复前preprocessRowData会做类型清洗:空字符串转null、JSON 字符串反序列化、ISO 日期字符串转Date、数字字符串转数值(id*Code结尾的字段除外,保留字符串语义)。交易记录恢复时通过category: { connect: { userId_code: ... } }重新挂接分类与项目。

4. 恢复上传附件。根据files.json中记录的路径,从归档的data/uploads/目录提取对应二进制内容写回磁盘;写入前用safePathJoin并校验目标路径必须位于用户上传目录之内,若检测到路径穿越(path traversal)会记录错误并跳过,兼顾安全。文件元数据中的路径也会被规范化为相对路径后更新入库。

恢复成功后你能看到什么

页面会以绿色卡片展示 "Backup restored successfully" 及导入统计,例如:

  • settings.json: N items
  • transactions.json: N items
  • Uploaded attachments: N items

至此,旧实例的交易记录、分类、项目、自定义字段、货币、设置以及所有附件都完整回到了新实例中。

故障排查:上传报"文件太大"怎么办

如果恢复时遇到关于文件大小的错误,原因出在 Next.js Server Actions 的请求体上限。迁移文档明确提示:编辑项目根目录的 next.config.ts,把experimental.serverActions.bodySizeLimit调大:

const nextConfig: NextConfig = { images: { unoptimized: true }, serverExternalPackages: ["@prisma/adapter-pg"], experimental: { serverActions: { bodySizeLimit: "256mb", // 恢复备份上传走 Server Action,受此限制 }, }, }

当前仓库的默认值是"256mb"。恢复备份的上传请求正是通过 Server Action(restoreBackupAction)提交的,而服务端同时还有MAX_BACKUP_SIZE = 256MB的硬校验,所以当你的备份归档接近或超过 256MB 时,需要同时调大bodySizeLimit才能顺利上传。修改后重新构建并重启应用即可。

补充提示:导出侧还有单文件 64MB 的限制(data/route.ts/settings/backups/data/route.ts>) 中的MAX_FILE_SIZE),如果你的实例中有超大附件,即使恢复成功,超大文件也不会被包含进归档,迁移前需单独留意这部分文件。

附录:备份归档数据文件速查

归档内文件内容对应数据模型
metadata.json版本(1.0)、时间戳、模型清单
settings.json用户设置与 LLM 提示词Setting
currencies.json货币列表Currency
categories.json分类及分类级 LLM 提示词Category
projects.json项目及项目级 LLM 提示词Project
fields.json自定义字段及提取提示词Field
files.json附件元数据索引File
transactions.json全部交易(含金额、币种、分类/项目代码、extra 字段)Transaction
uploads/原始附件二进制磁盘文件

这套备份格式由 models/backups.ts 的MODEL_BACKUP统一定义导出与恢复双向映射,是 TaxHacker 数据可移植性的基石——它不仅支撑本次 v0.3 → v0.5 的数据库迁移,也意味着你随时可以把数据完整导出,迁移到任何一台新的自托管服务器上。

【免费下载链接】TaxHackerSelf-hosted AI accounting app. LLM analyzer for receipts, invoices, transactions with custom prompts and categories项目地址: https://gitcode.com/GitHub_Trending/ta/TaxHacker

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 3:52:14

STM32CubeMX串口DMA收发配置指南:原理、代码与避坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 3:52:10

JVS-IOT设备上线失败的七大核心概念排障指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 3:51:53

招聘数据可视化:Python爬虫到Flask图表展示的完整实现

简介:这是一份基于Python的招聘数据分析可视化系统毕业设计资料包,面向计算机相关专业毕业生及需要完成数据类课题设计的同学。资源围绕招聘数据的采集、处理与可视化展示,构建了从爬虫脚本、数据清洗分析到前端图表展示的完整闭环&#xff0…

作者头像 李华
网站建设 2026/9/17 3:51:05

OpenClaw配置实战手册:从文件路径到模型技能全解析

装好 OpenClaw 只是把事情做完了一半,真正的分水岭在配置。很多人启动成功后,卡在“不知道去哪改模型”“skill 装了没反应”“微信一接入就报错”这类问题上,翻遍文档也找不到靠谱答案。这篇手册不打算复述官方文档,就按我实际部…

作者头像 李华