Files
caiwuzongzhang/docs/decisions/002-persistence.md
T
腾讯WorkBuddy 763a940683 B-39: 数据库持久化与不可变导入
- 版本化 SQLite 迁移(向前/回滚)与连接助手。
- 导入管线:上传文件按 SHA-256 内容哈希不可变保存,文件/行/批次
  幂等;重复上传返回 duplicate 并复用已有批次,不产生第二份事实。
- 每条规范化源行反查文件、工作表、原始行号与模板版本。
- 解析失败保留 exception/failed 批次与诊断,不产生已确认交易。
- 决策记录见 docs/decisions/002-persistence.md。
2026-08-16 01:48:53 +08:00

2.5 KiB
Raw Blame History

002 持久化层技术决策

对应 IssueB-39docs/issues/002-p0-persistence-and-immutable-imports.md)。

数据库:SQLitePython 标准库 sqlite3

  • 单文件、事务完整、零新增依赖,与当前标准库服务器和离线内网部署环境匹配。
  • 外键约束、CHECK 约束、唯一约束和触发器均可用,足以承载不可变证据模型。
  • 金额以 TEXT 保存 Decimal 的原始字符串,读取时还原为 Decimal 绝不经过二进制浮点。
  • 后续若并发写入成为瓶颈,可开启 WAL 或平迁 PostgreSQL;迁移版本表 schema_migrations 不绑定具体引擎方言之外的特性。

迁移工具:仓库内置版本化迁移器(src/bank_importer/db.py

  • 每条迁移包含 up / down 两段 SQL,按版本号顺序执行并记录在 schema_migrations 表中;重复执行无副作用。
  • 不引入 Alembic 等外部工具:当前模型规模小,内置迁移器保持零依赖, 且回滚路径明确(python -m bank_importer.db <db路径> --rollback-to <版本>)。
  • 服务启动时自动执行 migrate,空数据库即可完整建表。

文件存储:内容寻址的本地文件系统(data/files/

  • 上传文件先计算 SHA-256,再写入 data/files/<哈希前两位>/<完整哈希>.<扩展名>
  • 发布采用临时文件 + 硬链接:目标要么完整出现、要么不存在,且永不覆盖。
  • 相同内容(即使文件名不同)只保存一份文件、一条 source_files 记录。
  • data/ 已加入 .gitignore,真实流水不进入版本库。

幂等与状态机

  • 文件级幂等:source_files.sha256 唯一约束。重复上传插入一条 status='duplicate' 的批次审计记录,指向首个批次,不产生第二份事实。
  • 行级幂等:source_rowsUNIQUE (sheet_batch_id, source_row)
  • 批次状态:parsing → parsed | exception | failed,另有 duplicate。 失败/异常批次保留 import_exceptions 诊断(阶段、消息、原始文件名), 不产生任何已确认源行。
  • 事务边界:文件落盘后,source_files + 批次 + 工作表批次 + 源行在单个 SQLite 事务内提交;解析失败时批次状态与异常记录同样在事务内落库。

不可变性

  • source_filessheet_batchessource_rows 三张表由数据库触发器禁止 UPDATEDELETE,任何修改只能走后续阶段的冲销/审计调整流程。
  • 该约束由 tests/test_persistence.py 自动化验证。