- 版本化 SQLite 迁移(向前/回滚)与连接助手。 - 导入管线:上传文件按 SHA-256 内容哈希不可变保存,文件/行/批次 幂等;重复上传返回 duplicate 并复用已有批次,不产生第二份事实。 - 每条规范化源行反查文件、工作表、原始行号与模板版本。 - 解析失败保留 exception/failed 批次与诊断,不产生已确认交易。 - 决策记录见 docs/decisions/002-persistence.md。
45 lines
2.5 KiB
Markdown
45 lines
2.5 KiB
Markdown
# 002 持久化层技术决策
|
||
|
||
对应 Issue:B-39(`docs/issues/002-p0-persistence-and-immutable-imports.md`)。
|
||
|
||
## 数据库:SQLite(Python 标准库 `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_rows` 上 `UNIQUE (sheet_batch_id, source_row)`。
|
||
- 批次状态:`parsing → parsed | exception | failed`,另有 `duplicate`。
|
||
失败/异常批次保留 `import_exceptions` 诊断(阶段、消息、原始文件名),
|
||
不产生任何已确认源行。
|
||
- 事务边界:文件落盘后,`source_files` + 批次 + 工作表批次 + 源行在单个
|
||
SQLite 事务内提交;解析失败时批次状态与异常记录同样在事务内落库。
|
||
|
||
## 不可变性
|
||
|
||
- `source_files`、`sheet_batches`、`source_rows` 三张表由数据库触发器禁止
|
||
`UPDATE` 和 `DELETE`,任何修改只能走后续阶段的冲销/审计调整流程。
|
||
- 该约束由 `tests/test_persistence.py` 自动化验证。
|