docs(docs): 调整数据库与详细设计目录编号

This commit is contained in:
zhiye.sun
2026-06-10 15:15:02 +08:00
parent db0e94cf26
commit a060c23ba7
8 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,651 @@
# 自动汇总文件夹文件目录与页数流程数据库设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 需求分析文档 | docs/1.需求分析/1.自动汇总.md |
| 功能设计文档 | docs/2.功能设计/1.自动汇总.md |
| 详细设计文档 | docs/3.详细设计/1.自动汇总.md |
| 数据库类型 | SQLite / Django ORM |
| 表名前缀 | ra_ |
| 设计日期 | 2026-06-05 |
| 设计版本 | V1.0 |
---
## 一、设计原则
| 原则 | 说明 |
| --- | --- |
| ORM 优先 | 当前项目使用 Django实际落地以 Django Model 与 migration 为准 |
| SQLite 兼容 | 字段类型、索引和约束优先保证 SQLite 可运行 |
| 短表名前缀 | 使用 `ra_` 作为审核智能体文件汇总相关表前缀 |
| 不建枚举表 | 状态枚举使用 Django `TextChoices`,数据库存储字符串 |
| 对话隔离 | 所有附件、批次、导出文件均可追溯到 Conversation 和 User |
| 多版本附件 | 同一对话同名附件允许多次上传,以版本号区分 |
| 批次固化 | 每次汇总批次通过中间表绑定本次使用的附件版本,防止串文件 |
| 事件留痕 | 保留 WorkflowEvent用于 SSE 断线续传、页面刷新恢复和排查问题 |
---
## 二、ER 图
```mermaid
erDiagram
AUTH_USER ||--o{ CONVERSATION : owns
CONVERSATION ||--o{ MESSAGE : contains
CONVERSATION ||--o{ RA_FILE_ATTACHMENT : has
CONVERSATION ||--o{ RA_FILE_SUMMARY_BATCH : has
AUTH_USER ||--o{ RA_FILE_ATTACHMENT : uploads
AUTH_USER ||--o{ RA_FILE_SUMMARY_BATCH : runs
MESSAGE ||--o{ RA_FILE_SUMMARY_BATCH : triggers
RA_FILE_SUMMARY_BATCH ||--o{ RA_FILE_SUMMARY_BATCH_ATTACHMENT : binds
RA_FILE_ATTACHMENT ||--o{ RA_FILE_SUMMARY_BATCH_ATTACHMENT : selected
RA_FILE_SUMMARY_BATCH ||--o{ RA_FILE_SUMMARY_ITEM : produces
RA_FILE_SUMMARY_BATCH ||--o{ RA_WORKFLOW_NODE_RUN : tracks
RA_FILE_SUMMARY_BATCH ||--o{ RA_WORKFLOW_EVENT : emits
RA_FILE_SUMMARY_BATCH ||--o{ RA_EXPORTED_SUMMARY_FILE : exports
```
---
## 三、表结构设计
### 3.1 ra_file_attachment
用户在对话右侧上传区上传后的附件记录。上传即存储,不代表已启动工作流。
| 字段名 | Django 类型 | SQLite 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| id | BigAutoField | integer | 是 | 主键 |
| conversation_id | ForeignKey | bigint | 是 | 绑定对话 |
| user_id | ForeignKey | bigint | 是 | 上传用户 |
| original_name | CharField(255) | varchar(255) | 是 | 原始文件名 |
| version_no | PositiveIntegerField | integer | 是 | 同一对话同名文件版本号,从 1 递增 |
| is_active | BooleanField | bool | 是 | 是否当前默认版本 |
| storage_path | CharField(500) | varchar(500) | 是 | 文件存储路径 |
| file_size | BigIntegerField | bigint | 是 | 文件大小 |
| content_type | CharField(120) | varchar(120) | 否 | MIME 类型 |
| upload_status | CharField(20) | varchar(20) | 是 | uploaded、bound、deleted |
| created_at | DateTimeField | datetime | 是 | 上传时间 |
唯一约束:
| 约束名 | 字段 |
| --- | --- |
| uq_ra_attachment_conv_name_version | conversation_id, original_name, version_no |
索引:
| 索引名 | 字段 | 说明 |
| --- | --- | --- |
| idx_ra_attachment_conv_created | conversation_id, created_at | 查询对话附件列表 |
| idx_ra_attachment_user_created | user_id, created_at | 查询用户上传记录 |
| idx_ra_attachment_active | conversation_id, original_name, is_active | 查询当前默认版本 |
---
### 3.2 ra_file_summary_batch
一次文件目录与页数汇总工作流批次。
| 字段名 | Django 类型 | SQLite 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| id | BigAutoField | integer | 是 | 主键 |
| conversation_id | ForeignKey | bigint | 是 | 绑定对话 |
| user_id | ForeignKey | bigint | 是 | 执行用户 |
| trigger_message_id | ForeignKey | bigint | 否 | 触发工作流的用户消息 |
| batch_no | CharField(64) | varchar(64) | 是 | 批次编号,唯一 |
| product_name | CharField(200) | varchar(200) | 否 | 识别出的产品名称 |
| status | CharField(20) | varchar(20) | 是 | pending、running、success、failed |
| total_files | IntegerField | integer | 是 | 文件总数 |
| supported_files | IntegerField | integer | 是 | 支持统计文件数 |
| success_files | IntegerField | integer | 是 | 统计成功文件数 |
| failed_files | IntegerField | integer | 是 | 统计失败文件数 |
| unsupported_files | IntegerField | integer | 是 | 不支持文件数 |
| uncertain_files | IntegerField | integer | 是 | 页数不可确定文件数 |
| total_pages | IntegerField | integer | 是 | 总页数 |
| work_dir | CharField(500) | varchar(500) | 否 | 批次工作目录 |
| error_message | TextField | text | 否 | 批次异常说明 |
| created_at | DateTimeField | datetime | 是 | 创建时间 |
| started_at | DateTimeField | datetime | 否 | 开始时间 |
| finished_at | DateTimeField | datetime | 否 | 完成时间 |
唯一约束:
| 约束名 | 字段 |
| --- | --- |
| uq_ra_batch_no | batch_no |
索引:
| 索引名 | 字段 | 说明 |
| --- | --- | --- |
| idx_ra_batch_conv_created | conversation_id, created_at | 查询对话下批次 |
| idx_ra_batch_user_created | user_id, created_at | 查询用户批次 |
| idx_ra_batch_status | status, created_at | 查询执行中或失败批次 |
---
### 3.3 ra_file_summary_batch_attachment
批次与附件版本绑定表。一个对话可多次上传同名附件,批次必须固化本次使用的附件版本。
| 字段名 | Django 类型 | SQLite 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| id | BigAutoField | integer | 是 | 主键 |
| batch_id | ForeignKey | bigint | 是 | 汇总批次 |
| attachment_id | ForeignKey | bigint | 是 | 本次使用的附件版本 |
| source_role | CharField(20) | varchar(20) | 是 | archive、multi_file |
| created_at | DateTimeField | datetime | 是 | 绑定时间 |
唯一约束:
| 约束名 | 字段 |
| --- | --- |
| uq_ra_batch_attachment | batch_id, attachment_id |
索引:
| 索引名 | 字段 | 说明 |
| --- | --- | --- |
| idx_ra_batch_attachment_batch | batch_id, created_at | 查询批次附件 |
| idx_ra_batch_attachment_attachment | attachment_id | 查询附件被哪些批次使用 |
---
### 3.4 ra_file_summary_item
文件明细表,记录扫描到的每个文件及页数统计结果。
| 字段名 | Django 类型 | SQLite 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| id | BigAutoField | integer | 是 | 主键 |
| batch_id | ForeignKey | bigint | 是 | 所属批次 |
| file_index | PositiveIntegerField | integer | 是 | 文件序号 |
| directory_level | CharField(300) | varchar(300) | 否 | 目录层级 |
| file_name | CharField(255) | varchar(255) | 是 | 文件名 |
| file_type | CharField(20) | varchar(20) | 是 | 文件类型 |
| relative_path | CharField(500) | varchar(500) | 是 | 相对路径,用于展示和导出 |
| storage_path | CharField(500) | varchar(500) | 是 | 实际处理路径 |
| page_count | IntegerField | integer | 否 | 页数,失败或不可确定时为空 |
| statistics_status | CharField(20) | varchar(20) | 是 | success、failed、unsupported、uncertain、skipped |
| retry_count | PositiveIntegerField | integer | 是 | 页数统计重试次数 |
| error_message | TextField | text | 否 | 异常说明 |
| created_at | DateTimeField | datetime | 是 | 创建时间 |
| updated_at | DateTimeField | datetime | 是 | 更新时间 |
唯一约束:
| 约束名 | 字段 |
| --- | --- |
| uq_ra_item_batch_relative_path | batch_id, relative_path |
索引:
| 索引名 | 字段 | 说明 |
| --- | --- | --- |
| idx_ra_item_batch_index | batch_id, file_index | 按序展示文件明细 |
| idx_ra_item_batch_status | batch_id, statistics_status | 查询失败/不可确定文件 |
| idx_ra_item_batch_type | batch_id, file_type | 按类型统计 |
---
### 3.5 ra_workflow_node_run
工作流节点运行状态表,用于右侧工作流卡片状态恢复。
| 字段名 | Django 类型 | SQLite 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| id | BigAutoField | integer | 是 | 主键 |
| batch_id | ForeignKey | bigint | 是 | 所属批次 |
| node_code | CharField(40) | varchar(40) | 是 | 节点编码 |
| node_name | CharField(80) | varchar(80) | 是 | 节点名称 |
| status | CharField(20) | varchar(20) | 是 | pending、running、retrying、success、failed、skipped |
| progress | PositiveIntegerField | integer | 是 | 进度百分比0-100 |
| message | TextField | text | 否 | 节点提示 |
| started_at | DateTimeField | datetime | 否 | 开始时间 |
| finished_at | DateTimeField | datetime | 否 | 完成时间 |
唯一约束:
| 约束名 | 字段 |
| --- | --- |
| uq_ra_node_batch_code | batch_id, node_code |
索引:
| 索引名 | 字段 | 说明 |
| --- | --- | --- |
| idx_ra_node_batch_status | batch_id, status | 查询批次节点状态 |
---
### 3.6 ra_workflow_event
工作流事件表,用于 SSE 事件持久化、断线续传和调试。
| 字段名 | Django 类型 | SQLite 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| id | BigAutoField | integer | 是 | 主键,同时可作为 event_id |
| batch_id | ForeignKey | bigint | 是 | 所属批次 |
| event_type | CharField(40) | varchar(40) | 是 | workflow_started、node_progress 等 |
| payload | JSONField | text/json | 是 | 事件载荷 |
| created_at | DateTimeField | datetime | 是 | 事件时间 |
索引:
| 索引名 | 字段 | 说明 |
| --- | --- | --- |
| idx_ra_event_batch_id | batch_id, id | SSE after 续传 |
| idx_ra_event_batch_created | batch_id, created_at | 按时间查询事件 |
---
### 3.7 ra_exported_summary_file
导出文件记录表。下载链接运行时根据 export_id 生成。
| 字段名 | Django 类型 | SQLite 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| id | BigAutoField | integer | 是 | 主键 |
| batch_id | ForeignKey | bigint | 是 | 所属批次 |
| export_type | CharField(20) | varchar(20) | 是 | markdown、excel |
| file_name | CharField(255) | varchar(255) | 是 | 导出文件名 |
| storage_path | CharField(500) | varchar(500) | 是 | 保存路径 |
| status | CharField(20) | varchar(20) | 是 | success、failed |
| error_message | TextField | text | 否 | 导出异常说明 |
| created_at | DateTimeField | datetime | 是 | 生成时间 |
索引:
| 索引名 | 字段 | 说明 |
| --- | --- | --- |
| idx_ra_export_batch_type | batch_id, export_type | 查询批次导出文件 |
| idx_ra_export_batch_created | batch_id, created_at | 按生成时间查询 |
---
## 四、枚举设计
本功能不建立枚举表,枚举通过 Django `TextChoices` 定义,数据库存储字符串。
### 4.1 附件状态 upload_status
| 值 | 中文 | 说明 |
| --- | --- | --- |
| uploaded | 已上传 | 上传完成,尚未绑定批次 |
| bound | 已绑定 | 已被某个批次使用 |
| deleted | 已删除 | 用户逻辑删除,不再作为默认候选 |
### 4.2 批次状态 batch.status
| 值 | 中文 | 说明 |
| --- | --- | --- |
| pending | 待执行 | 批次已创建 |
| running | 执行中 | 后台工作流运行中 |
| success | 成功 | 工作流完成 |
| failed | 失败 | 批次级失败 |
### 4.3 节点状态 node.status
| 值 | 中文 | 说明 |
| --- | --- | --- |
| pending | 等待中 | 节点未开始 |
| running | 执行中 | 节点正在执行 |
| retrying | 重试中 | 单文件解析失败后重试 |
| success | 成功 | 节点执行成功 |
| failed | 失败 | 节点失败 |
| skipped | 跳过 | 当前批次不需要执行该节点 |
### 4.4 文件统计状态 statistics_status
| 值 | 中文 | 说明 |
| --- | --- | --- |
| success | 成功 | 页数统计成功 |
| failed | 失败 | 重试后仍失败 |
| unsupported | 不支持 | 文件类型不在支持范围 |
| uncertain | 不确定 | 文件可读,但无可靠页数元数据 |
| skipped | 跳过 | 空文件、隐藏文件或规则跳过 |
### 4.5 导出类型 export_type
| 值 | 中文 | 说明 |
| --- | --- | --- |
| markdown | Markdown | Markdown 汇总报告 |
| excel | Excel | Excel 明细文件 |
### 4.6 导出状态 export.status
| 值 | 中文 | 说明 |
| --- | --- | --- |
| success | 成功 | 导出文件生成成功 |
| failed | 失败 | 导出失败 |
---
## 五、关系与业务规则
### 5.1 对话与附件
```text
Conversation 1:N ra_file_attachment
```
规则:
| 规则 | 说明 |
| --- | --- |
| 上传即存储 | 用户上传后立即创建 FileAttachment |
| 对话隔离 | 附件只能被同一 Conversation 下的批次使用 |
| 多版本 | 同一 conversation + original_name 可存在多个 version_no |
| 默认版本 | is_active=true 的记录作为默认候选版本 |
| 逻辑删除 | 删除附件时设置 upload_status=deleted不立即物理删除 |
### 5.2 对话与批次
```text
Conversation 1:N ra_file_summary_batch
```
规则:
| 规则 | 说明 |
| --- | --- |
| 多次汇总 | 同一对话允许多次触发自动汇总 |
| 提示词触发 | 批次由用户消息触发,可关联 trigger_message_id |
| 批次固化 | 批次启动时固化本次使用的附件版本 |
### 5.3 批次与附件版本
```text
ra_file_summary_batch N:M ra_file_attachment
```
通过 `ra_file_summary_batch_attachment` 实现。
规则:
| 规则 | 说明 |
| --- | --- |
| 不串文件 | 工作流只能读取中间表绑定的附件 |
| 保留历史 | 即使附件后续上传新版本,历史批次仍指向旧版本 |
| 版本选择 | 用户未选择时默认使用同名文件的最新 active 版本 |
### 5.4 批次与文件明细
```text
ra_file_summary_batch 1:N ra_file_summary_item
```
规则:
| 规则 | 说明 |
| --- | --- |
| 相对路径唯一 | 同一批次下 relative_path 唯一 |
| 处理路径保留 | relative_path 用于展示storage_path 用于后台处理 |
| 单文件失败不阻断 | 文件解析失败记录 failed批次继续处理其他文件 |
---
## 六、索引设计汇总
| 表 | 索引/约束 | 字段 | 用途 |
| --- | --- | --- | --- |
| ra_file_attachment | uq_ra_attachment_conv_name_version | conversation_id, original_name, version_no | 同名附件版本唯一 |
| ra_file_attachment | idx_ra_attachment_conv_created | conversation_id, created_at | 对话附件列表 |
| ra_file_attachment | idx_ra_attachment_user_created | user_id, created_at | 用户上传记录 |
| ra_file_attachment | idx_ra_attachment_active | conversation_id, original_name, is_active | 默认版本查询 |
| ra_file_summary_batch | uq_ra_batch_no | batch_no | 批次编号唯一 |
| ra_file_summary_batch | idx_ra_batch_conv_created | conversation_id, created_at | 对话批次列表 |
| ra_file_summary_batch | idx_ra_batch_user_created | user_id, created_at | 用户批次列表 |
| ra_file_summary_batch | idx_ra_batch_status | status, created_at | 查询运行中/失败批次 |
| ra_file_summary_batch_attachment | uq_ra_batch_attachment | batch_id, attachment_id | 批次附件唯一 |
| ra_file_summary_item | uq_ra_item_batch_relative_path | batch_id, relative_path | 批次内文件唯一 |
| ra_file_summary_item | idx_ra_item_batch_index | batch_id, file_index | 文件明细排序 |
| ra_file_summary_item | idx_ra_item_batch_status | batch_id, statistics_status | 查询异常文件 |
| ra_workflow_node_run | uq_ra_node_batch_code | batch_id, node_code | 每批次每节点唯一 |
| ra_workflow_event | idx_ra_event_batch_id | batch_id, id | SSE 断点续传 |
| ra_exported_summary_file | idx_ra_export_batch_type | batch_id, export_type | 查询导出文件 |
---
## 七、SQLite 参考 DDL
> 说明:以下 DDL 为设计参考,实际落地以 Django migration 为准。
```sql
CREATE TABLE ra_file_attachment (
id INTEGER PRIMARY KEY AUTOINCREMENT,
conversation_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
original_name VARCHAR(255) NOT NULL,
version_no INTEGER NOT NULL DEFAULT 1,
is_active BOOLEAN NOT NULL DEFAULT 1,
storage_path VARCHAR(500) NOT NULL,
file_size BIGINT NOT NULL DEFAULT 0,
content_type VARCHAR(120) NOT NULL DEFAULT '',
upload_status VARCHAR(20) NOT NULL DEFAULT 'uploaded',
created_at DATETIME NOT NULL,
UNIQUE (conversation_id, original_name, version_no)
);
CREATE INDEX idx_ra_attachment_conv_created
ON ra_file_attachment (conversation_id, created_at);
CREATE INDEX idx_ra_attachment_user_created
ON ra_file_attachment (user_id, created_at);
CREATE INDEX idx_ra_attachment_active
ON ra_file_attachment (conversation_id, original_name, is_active);
```
```sql
CREATE TABLE ra_file_summary_batch (
id INTEGER PRIMARY KEY AUTOINCREMENT,
conversation_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
trigger_message_id BIGINT NULL,
batch_no VARCHAR(64) NOT NULL UNIQUE,
product_name VARCHAR(200) NOT NULL DEFAULT '',
status VARCHAR(20) NOT NULL DEFAULT 'pending',
total_files INTEGER NOT NULL DEFAULT 0,
supported_files INTEGER NOT NULL DEFAULT 0,
success_files INTEGER NOT NULL DEFAULT 0,
failed_files INTEGER NOT NULL DEFAULT 0,
unsupported_files INTEGER NOT NULL DEFAULT 0,
uncertain_files INTEGER NOT NULL DEFAULT 0,
total_pages INTEGER NOT NULL DEFAULT 0,
work_dir VARCHAR(500) NOT NULL DEFAULT '',
error_message TEXT NOT NULL DEFAULT '',
created_at DATETIME NOT NULL,
started_at DATETIME NULL,
finished_at DATETIME NULL
);
CREATE INDEX idx_ra_batch_conv_created
ON ra_file_summary_batch (conversation_id, created_at);
CREATE INDEX idx_ra_batch_user_created
ON ra_file_summary_batch (user_id, created_at);
CREATE INDEX idx_ra_batch_status
ON ra_file_summary_batch (status, created_at);
```
```sql
CREATE TABLE ra_file_summary_batch_attachment (
id INTEGER PRIMARY KEY AUTOINCREMENT,
batch_id BIGINT NOT NULL,
attachment_id BIGINT NOT NULL,
source_role VARCHAR(20) NOT NULL DEFAULT 'multi_file',
created_at DATETIME NOT NULL,
UNIQUE (batch_id, attachment_id)
);
CREATE INDEX idx_ra_batch_attachment_batch
ON ra_file_summary_batch_attachment (batch_id, created_at);
CREATE INDEX idx_ra_batch_attachment_attachment
ON ra_file_summary_batch_attachment (attachment_id);
```
```sql
CREATE TABLE ra_file_summary_item (
id INTEGER PRIMARY KEY AUTOINCREMENT,
batch_id BIGINT NOT NULL,
file_index INTEGER NOT NULL,
directory_level VARCHAR(300) NOT NULL DEFAULT '',
file_name VARCHAR(255) NOT NULL,
file_type VARCHAR(20) NOT NULL,
relative_path VARCHAR(500) NOT NULL,
storage_path VARCHAR(500) NOT NULL,
page_count INTEGER NULL,
statistics_status VARCHAR(20) NOT NULL DEFAULT 'skipped',
retry_count INTEGER NOT NULL DEFAULT 0,
error_message TEXT NOT NULL DEFAULT '',
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
UNIQUE (batch_id, relative_path)
);
CREATE INDEX idx_ra_item_batch_index
ON ra_file_summary_item (batch_id, file_index);
CREATE INDEX idx_ra_item_batch_status
ON ra_file_summary_item (batch_id, statistics_status);
CREATE INDEX idx_ra_item_batch_type
ON ra_file_summary_item (batch_id, file_type);
```
```sql
CREATE TABLE ra_workflow_node_run (
id INTEGER PRIMARY KEY AUTOINCREMENT,
batch_id BIGINT NOT NULL,
node_code VARCHAR(40) NOT NULL,
node_name VARCHAR(80) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
progress INTEGER NOT NULL DEFAULT 0,
message TEXT NOT NULL DEFAULT '',
started_at DATETIME NULL,
finished_at DATETIME NULL,
UNIQUE (batch_id, node_code)
);
CREATE INDEX idx_ra_node_batch_status
ON ra_workflow_node_run (batch_id, status);
```
```sql
CREATE TABLE ra_workflow_event (
id INTEGER PRIMARY KEY AUTOINCREMENT,
batch_id BIGINT NOT NULL,
event_type VARCHAR(40) NOT NULL,
payload TEXT NOT NULL DEFAULT '{}',
created_at DATETIME NOT NULL
);
CREATE INDEX idx_ra_event_batch_id
ON ra_workflow_event (batch_id, id);
CREATE INDEX idx_ra_event_batch_created
ON ra_workflow_event (batch_id, created_at);
```
```sql
CREATE TABLE ra_exported_summary_file (
id INTEGER PRIMARY KEY AUTOINCREMENT,
batch_id BIGINT NOT NULL,
export_type VARCHAR(20) NOT NULL,
file_name VARCHAR(255) NOT NULL,
storage_path VARCHAR(500) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'success',
error_message TEXT NOT NULL DEFAULT '',
created_at DATETIME NOT NULL
);
CREATE INDEX idx_ra_export_batch_type
ON ra_exported_summary_file (batch_id, export_type);
CREATE INDEX idx_ra_export_batch_created
ON ra_exported_summary_file (batch_id, created_at);
```
---
## 八、Django ORM 落地注意事项
### 8.1 db_table
每个模型通过 `class Meta: db_table = "ra_xxx"` 固定表名,避免 Django 默认生成较长表名。
### 8.2 JSONField
`WorkflowEvent.payload` 使用 Django `models.JSONField(default=dict)`。SQLite 下实际以文本形式存储Django 负责序列化与反序列化。
### 8.3 版本号生成
同一对话同名文件上传时:
```text
version_no = max(existing version_no) + 1
```
若新版本设为默认版本,需要将旧版本 `is_active` 更新为 false。
### 8.4 逻辑删除
附件删除时:
```text
upload_status = deleted
is_active = false
```
历史批次仍可通过中间表追溯该附件。
### 8.5 批次选择附件
用户发送提示词触发工作流时:
| 场景 | 处理 |
| --- | --- |
| 用户显式选择附件版本 | 使用所选 attachment_id |
| 用户未选择版本 | 使用当前对话下 is_active=true 且未删除的附件 |
| 存在多个同名 active 异常 | 取 created_at 最新,并记录待修复数据异常 |
---
## 九、数据保留策略
| 数据 | Demo 策略 | 正式部署建议 |
| --- | --- | --- |
| 上传附件记录 | 永久保留 | 随会话归档周期清理 |
| 上传原始文件 | 永久保留 | 可按用户/项目配置保留期限 |
| 汇总批次 | 永久保留 | 保留用于审计追溯 |
| 文件明细 | 永久保留 | 保留用于历史报告复现 |
| 工作流事件 | 永久保留 | 可定期清理已完成批次的事件 |
| 导出文件 | 永久保留 | 可设置下载有效期或归档 |
---
## 十、待确认事项
| 序号 | 问题 | 当前设计 | 状态 |
| --- | --- | --- | --- |
| 1 | 正式部署是否从 SQLite 迁移到 PostgreSQL/MySQL | 当前按 SQLite/Django ORM 设计,保留 ORM 兼容性 | 待后续确认 |
| 2 | 同名附件 active 是否允许多个 | 设计上不允许,代码更新时应关闭旧 active | 待开发实现 |
| 3 | 文件物理删除时机 | Demo 不物理删除 | 待后续确认 |
---
## 十一、开发顺序建议
1.`review_agent/models.py` 中新增上述 7 个模型。
2. 为状态字段定义 Django `TextChoices`
3. 配置 `db_table``indexes``constraints`
4. 执行 `python manage.py makemigrations review_agent` 生成迁移。
5. 执行 `python manage.py migrate` 验证 SQLite 可落表。
6. 编写模型级测试,覆盖同名附件版本、批次附件绑定、唯一约束和权限查询。