Files
DEMO-AGENT/docs/4.数据库设计/1.自动汇总.md

652 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 自动汇总文件夹文件目录与页数流程数据库设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 需求分析文档 | docs/需求分析/1.自动汇总.md |
| 功能设计文档 | docs/功能设计/1.自动汇总.md |
| 详细设计文档 | docs/详细设计/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. 编写模型级测试,覆盖同名附件版本、批次附件绑定、唯一约束和权限查询。