# 自动汇总文件夹文件目录与页数流程数据库设计 ## 文档信息 | 项目 | 内容 | | --- | --- | | 需求分析文档 | 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. 编写模型级测试,覆盖同名附件版本、批次附件绑定、唯一约束和权限查询。