# 自动汇总文件夹文件目录与页数流程详细设计 ## 文档信息 | 项目 | 内容 | | --- | --- | | 需求分析文档 | docs/1.需求分析/1.自动汇总.md | | 功能设计文档 | docs/2.功能设计/1.自动汇总.md | | 功能名称 | 自动汇总文件夹文件目录与页数 | | 所属模块 | 审核智能体 review_agent | | 设计日期 | 2026-06-05 | | 设计版本 | V1.0 | --- ## 一、详细设计目标 本详细设计用于指导“自动汇总文件夹文件目录与页数”功能开发落地,覆盖代码目录、数据模型、接口契约、后台工作流、Skill 拆分、轻量依赖、前端三栏布局、SSE 实时状态、异常重试和测试用例。 核心约束: | 约束 | 说明 | | --- | --- | | 对话绑定 | 上传文件与当前 Conversation 绑定,一个对话对应一套文件,不能串文件 | | 上传即存储 | 用户拖拽或选择文件后立即保存,但不启动工作流 | | 提示词触发 | 用户发送消息后,根据提示词判断是否启动自动汇总工作流 | | 后台异步 | 工作流后台执行,右侧第三栏工作流卡片实时更新 | | 轻量依赖 | 优先使用 Python 内部库和轻量第三方库,不强依赖 LibreOffice | | 老格式支持 | doc、xls、ppt 进入处理流程,能读到页数则统计,读不到则记录异常 | | 结果存档 | 批次、文件、节点、事件、明细、导出文件全部入库 | --- ## 二、代码结构设计 ### 2.1 目录结构 在现有 `review_agent` 应用内按模块重新划分文件处理能力。Django 模型仍集中放在 `review_agent/models.py`,其余代码放入 `review_agent/file_summary/`。 ```text review_agent/ models.py urls.py views.py services.py file_summary/ __init__.py constants.py schemas.py storage.py workflow.py events.py urls.py views.py services/ __init__.py archive.py inventory.py page_count.py product_detect.py report.py export_excel.py workflow_trigger.py skills/ __init__.py base.py registry.py upload_intake.py archive_extract.py file_inventory.py document_page_count.py product_detect.py summary_report.py excel_export.py ``` ### 2.2 文件职责 | 文件 | 职责 | | --- | --- | | review_agent/models.py | 集中定义 Conversation、Message、文件汇总相关模型 | | file_summary/constants.py | 状态、节点、文件类型、事件类型常量 | | file_summary/schemas.py | dataclass 入参出参结构,避免业务层直接传散乱 dict | | file_summary/storage.py | 上传文件、工作目录、导出文件路径生成与保存 | | file_summary/workflow.py | WorkflowExecutor,串行执行节点图 | | file_summary/events.py | 工作流事件持久化与 SSE 格式化 | | file_summary/views.py | 上传暂存、启动工作流、状态查询、SSE、下载接口 | | services/archive.py | 压缩包识别、zip/7z/rar 解压 | | services/inventory.py | 文件遍历与清单生成 | | services/page_count.py | 文件页数统计与 3 次重试 | | services/product_detect.py | 产品名识别 | | services/report.py | Markdown 报告和对话简表生成 | | services/export_excel.py | Excel 文件导出 | | services/workflow_trigger.py | 根据提示词判断是否触发自动汇总工作流 | | skills/base.py | Skill 基类与统一返回结构 | | skills/registry.py | Skill 注册与按需加载 | | skills/*.py | 各工作流节点对应 Skill | --- ## 三、依赖设计 ### 3.1 requirements 建议 ```text Django==5.2.14 pypdf python-docx python-pptx openpyxl xlrd olefile py7zr ``` ### 3.2 格式处理策略 | 格式 | 处理库 | 统计口径 | 失败策略 | | --- | --- | --- | --- | | pdf | pypdf | PDF 页面数 | 重试 3 次,仍失败记录异常 | | docx | python-docx | 优先读取内置页数属性 | 读不到记录“页数不可确定” | | doc | olefile | 读取 OLE 元数据页数 | 读不到记录“页数不可确定” | | pptx | python-pptx | 幻灯片数量 | 重试 3 次,仍失败记录异常 | | ppt | olefile | 读取 OLE 元数据页数/幻灯片数 | 读不到记录“页数不可确定” | | xlsx | openpyxl | 工作表数量 | 重试 3 次,仍失败记录异常 | | xls | xlrd | 工作表数量 | 重试 3 次,仍失败记录异常 | ### 3.3 压缩包处理策略 | 格式 | 处理方式 | 说明 | | --- | --- | --- | | zip | Python 标准库 zipfile | 必须支持 | | 7z | py7zr | 必须支持 | | rar | 优先系统 7z 命令 | Docker 镜像需安装 7-Zip/p7zip | ### 3.4 Docker 部署说明 Demo 运行不强依赖 LibreOffice。若未来要求 doc/docx/ppt/pptx 页数与 Office 打开后的分页完全一致,可在 Docker 镜像中额外安装 LibreOffice headless,再通过“转换 PDF 后统计页数”的增强策略实现。 RAR 解压如需稳定支持,Docker 镜像需要安装 7-Zip/p7zip,并确保 `7z` 命令在 PATH 中可调用。 --- ## 四、数据模型详细设计 模型集中放在 `review_agent/models.py`,按“会话模型”和“文件汇总模型”分段。 ### 4.1 FileAttachment 用户上传即存储的文件记录。此时尚未启动工作流。 | 字段 | 类型 | 约束 | 说明 | | --- | --- | --- | --- | | id | BigAutoField | PK | 主键 | | conversation | ForeignKey(Conversation) | CASCADE, db_index | 绑定对话 | | user | ForeignKey(User) | CASCADE, db_index | 上传用户 | | original_name | CharField(255) | required | 原始文件名 | | storage_path | CharField(500) | required | 本地保存路径 | | file_size | BigIntegerField | default=0 | 文件大小 | | content_type | CharField(120) | blank | MIME 类型 | | upload_status | CharField(20) | choices | uploaded、bound、deleted | | created_at | DateTimeField | auto_now_add | 上传时间 | 索引: ```text (conversation, created_at) (user, created_at) ``` ### 4.2 FileSummaryBatch 一次自动汇总工作流批次。 | 字段 | 类型 | 约束 | 说明 | | --- | --- | --- | --- | | id | BigAutoField | PK | 主键 | | conversation | ForeignKey(Conversation) | CASCADE, db_index | 绑定对话 | | user | ForeignKey(User) | CASCADE, db_index | 执行用户 | | trigger_message | ForeignKey(Message) | SET_NULL, null | 触发工作流的用户消息 | | batch_no | CharField(64) | unique | 批次编号 | | product_name | CharField(200) | blank | 产品名称 | | status | CharField(20) | choices | pending、running、success、failed | | total_files | IntegerField | default=0 | 文件总数 | | supported_files | IntegerField | default=0 | 支持统计数 | | success_files | IntegerField | default=0 | 成功数 | | failed_files | IntegerField | default=0 | 失败数 | | unsupported_files | IntegerField | default=0 | 不支持数 | | uncertain_files | IntegerField | default=0 | 页数不可确定数 | | total_pages | IntegerField | default=0 | 总页数 | | work_dir | CharField(500) | blank | 工作目录 | | error_message | TextField | blank | 批次错误 | | created_at | DateTimeField | auto_now_add | 创建时间 | | started_at | DateTimeField | null | 开始时间 | | finished_at | DateTimeField | null | 结束时间 | ### 4.3 FileSummaryBatchAttachment 批次与上传文件的绑定表,确保工作流只读取本批次文件。 | 字段 | 类型 | 约束 | 说明 | | --- | --- | --- | --- | | id | BigAutoField | PK | 主键 | | batch | ForeignKey(FileSummaryBatch) | CASCADE | 批次 | | attachment | ForeignKey(FileAttachment) | CASCADE | 上传文件 | | created_at | DateTimeField | auto_now_add | 绑定时间 | 唯一约束: ```text unique(batch, attachment) ``` ### 4.4 FileSummaryItem 文件明细记录。 | 字段 | 类型 | 约束 | 说明 | | --- | --- | --- | --- | | id | BigAutoField | PK | 主键 | | batch | ForeignKey(FileSummaryBatch) | CASCADE, db_index | 所属批次 | | file_index | IntegerField | required | 文件序号 | | directory_level | CharField(300) | blank | 目录层级 | | file_name | CharField(255) | required | 文件名 | | file_type | CharField(20) | required | 扩展名 | | relative_path | CharField(500) | required | 相对路径 | | storage_path | CharField(500) | required | 实际处理路径 | | page_count | IntegerField | null | 页数 | | statistics_status | CharField(20) | choices | success、failed、unsupported、uncertain、skipped | | retry_count | IntegerField | default=0 | 重试次数 | | error_message | TextField | blank | 异常说明 | | created_at | DateTimeField | auto_now_add | 创建时间 | | updated_at | DateTimeField | auto_now | 更新时间 | 唯一约束: ```text unique(batch, relative_path) ``` ### 4.5 WorkflowNodeRun 工作流节点状态记录。 | 字段 | 类型 | 约束 | 说明 | | --- | --- | --- | --- | | id | BigAutoField | PK | 主键 | | batch | ForeignKey(FileSummaryBatch) | CASCADE, db_index | 批次 | | node_code | CharField(40) | required | 节点编码 | | node_name | CharField(80) | required | 节点名称 | | status | CharField(20) | choices | pending、running、retrying、success、failed、skipped | | progress | IntegerField | default=0 | 进度百分比 | | message | TextField | blank | 节点说明 | | started_at | DateTimeField | null | 开始时间 | | finished_at | DateTimeField | null | 完成时间 | 唯一约束: ```text unique(batch, node_code) ``` ### 4.6 WorkflowEvent SSE 事件持久化记录,用于页面刷新后恢复和调试。 | 字段 | 类型 | 约束 | 说明 | | --- | --- | --- | --- | | id | BigAutoField | PK | 主键 | | batch | ForeignKey(FileSummaryBatch) | CASCADE, db_index | 批次 | | event_type | CharField(40) | required | 事件类型 | | payload | JSONField | default=dict | 事件载荷 | | created_at | DateTimeField | auto_now_add | 创建时间 | ### 4.7 ExportedSummaryFile 导出文件记录。 | 字段 | 类型 | 约束 | 说明 | | --- | --- | --- | --- | | id | BigAutoField | PK | 主键 | | batch | ForeignKey(FileSummaryBatch) | CASCADE, db_index | 批次 | | export_type | CharField(20) | choices | markdown、excel | | file_name | CharField(255) | required | 文件名 | | storage_path | CharField(500) | required | 保存路径 | | status | CharField(20) | choices | success、failed | | error_message | TextField | blank | 异常 | | created_at | DateTimeField | auto_now_add | 生成时间 | 下载链接运行时根据 `export_id` 生成,不建议长期存储静态 URL。 --- ## 五、常量与状态设计 ### 5.1 支持格式 ```python SUPPORTED_PAGE_TYPES = {"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx"} ARCHIVE_TYPES = {"zip", "7z", "rar"} ``` ### 5.2 工作流节点 ```python WORKFLOW_NODES = [ ("upload", "上传中"), ("extract", "解压中"), ("inventory", "扫描中"), ("page_count", "解析页数中"), ("product_detect", "识别产品名中"), ("report", "输出 Markdown 中"), ("excel_export", "输出 Excel 中"), ("completed", "已完成"), ] ``` ### 5.3 触发词规则 `workflow_trigger.py` 先用规则判断,后续可升级为 LLM 意图识别。 ```python SUMMARY_TRIGGER_KEYWORDS = [ "自动汇总", "文件目录", "页数", "统计文件", "汇总目录", "目录与页数", ] ``` 规则: | 条件 | 结果 | | --- | --- | | 当前对话存在未绑定或最近上传文件,且提示词命中关键词 | 启动自动汇总工作流 | | 未命中关键词 | 走普通 LLM 对话 | | 命中关键词但没有上传文件 | AI 回复提示“请先上传文件或压缩包” | --- ## 六、服务与方法签名 ### 6.1 storage.py ```python def save_attachment(conversation, user, uploaded_file) -> FileAttachment: """保存上传文件并绑定当前对话。""" def build_batch_work_dir(batch: FileSummaryBatch) -> Path: """生成批次工作目录。""" def build_export_path(batch: FileSummaryBatch, suffix: str) -> Path: """生成导出文件路径。""" ``` 存储目录: ```text media/review_agent/ user_{user_id}/ conversation_{conversation_id}/ attachments/ batches/ batch_{batch_id}/ input/ extracted/ exports/ ``` ### 6.2 archive.py ```python def is_archive(path: Path) -> bool: """判断是否压缩包。""" def extract_archive(source: Path, target_dir: Path) -> list[Path]: """解压 zip、7z、rar,返回解压后的文件路径列表。""" def extract_zip(source: Path, target_dir: Path) -> list[Path]: """使用 zipfile 解压。""" def extract_7z(source: Path, target_dir: Path) -> list[Path]: """使用 py7zr 解压。""" def extract_rar(source: Path, target_dir: Path) -> list[Path]: """优先调用系统 7z 命令解压 rar。""" ``` 安全规则: | 规则 | 说明 | | --- | --- | | 路径穿越检查 | 解压后的最终路径必须仍在 target_dir 内 | | 文件名清理 | 保留原名,但禁止绝对路径和上级目录跳转 | | 解压失败 | 抛出 ArchiveExtractError,批次失败 | ### 6.3 inventory.py ```python def scan_files(batch: FileSummaryBatch, roots: list[Path]) -> list[FileSummaryItem]: """扫描目录或散装文件,创建 FileSummaryItem。""" def build_directory_level(relative_path: Path) -> str: """根据相对路径生成目录层级。""" def normalize_file_type(path: Path) -> str: """返回小写扩展名,不含点。""" ``` ### 6.4 page_count.py ```python def count_pages(item: FileSummaryItem) -> PageCountResult: """根据文件类型分发页数统计。""" def count_pages_with_retry(item: FileSummaryItem, max_retry: int = 3) -> PageCountResult: """失败最多重试 3 次。""" def count_pdf(path: Path) -> int: """使用 pypdf 统计 PDF 页数。""" def count_docx(path: Path) -> PageCountResult: """使用 python-docx 读取内置页数属性。""" def count_doc(path: Path) -> PageCountResult: """使用 olefile 读取老 doc 的 OLE 元数据页数。""" def count_xlsx(path: Path) -> int: """使用 openpyxl 统计工作表数量。""" def count_xls(path: Path) -> int: """使用 xlrd 统计工作表数量。""" def count_pptx(path: Path) -> int: """使用 python-pptx 统计幻灯片数量。""" def count_ppt(path: Path) -> PageCountResult: """使用 olefile 读取老 ppt 的 OLE 元数据页数或幻灯片数。""" ``` `PageCountResult`: ```python @dataclass class PageCountResult: status: str page_count: int | None = None error_message: str = "" ``` 状态规则: | 情况 | status | page_count | | --- | --- | --- | | 成功读取页数 | success | 整数 | | 不支持类型 | unsupported | None | | 文件可读但页数无元数据 | uncertain | None | | 解析异常且重试失败 | failed | None | ### 6.5 product_detect.py ```python def detect_product_name(batch: FileSummaryBatch) -> ProductDetectResult: """从目录名、文件名和少量元数据中识别产品名。""" def update_conversation_title(batch: FileSummaryBatch, product_name: str) -> None: """按规则更新对话标题。""" ``` 产品名识别优先级: | 优先级 | 来源 | | --- | --- | | 1 | 顶层目录名 | | 2 | 文件名中包含“产品”“试剂盒”“说明书”等关键词的片段 | | 3 | docx 文档属性 title | | 4 | PDF 元数据 title | ### 6.6 report.py ```python def build_summary_stats(batch: FileSummaryBatch) -> dict: """汇总统计数据。""" def build_chat_markdown(batch: FileSummaryBatch) -> str: """生成对话框展示 Markdown 简表。""" def build_full_markdown_report(batch: FileSummaryBatch) -> str: """生成完整 Markdown 报告。""" def save_markdown_report(batch: FileSummaryBatch) -> ExportedSummaryFile: """保存 Markdown 报告并创建导出记录。""" ``` ### 6.7 export_excel.py ```python def build_excel_workbook(batch: FileSummaryBatch) -> Workbook: """构建 Excel Workbook。""" def save_excel(batch: FileSummaryBatch) -> ExportedSummaryFile: """保存 Excel 并创建导出记录。""" ``` 工作表: | Sheet | 字段 | | --- | --- | | 汇总信息 | 批次编号、产品名、文件总数、成功数、失败数、不可确定数、总页数 | | 文件明细 | 序号、目录层级、文件名、类型、页数、相对路径、状态、重试次数、异常说明 | --- ## 七、Skill 详细设计 ### 7.1 BaseSkill ```python class BaseSkill: name: str node_code: str def run(self, context: WorkflowContext) -> SkillResult: raise NotImplementedError ``` `WorkflowContext`: ```python @dataclass class WorkflowContext: batch_id: int conversation_id: int user_id: int message_id: int | None = None ``` `SkillResult`: ```python @dataclass class SkillResult: success: bool message: str = "" data: dict = field(default_factory=dict) ``` ### 7.2 Skill 列表 | Skill 类名 | 节点 | 调用服务 | | --- | --- | --- | | UploadIntakeSkill | upload | storage.py | | ArchiveExtractSkill | extract | archive.py | | FileInventorySkill | inventory | inventory.py | | DocumentPageCountSkill | page_count | page_count.py | | ProductDetectSkill | product_detect | product_detect.py | | SummaryReportSkill | report | report.py | | ExcelExportSkill | excel_export | export_excel.py | --- ## 八、工作流执行器详细设计 ### 8.1 执行入口 ```python def start_file_summary_workflow(batch_id: int) -> None: thread = threading.Thread( target=WorkflowExecutor().run, args=(batch_id,), daemon=True, ) thread.start() ``` ### 8.2 执行伪代码 ```python class WorkflowExecutor: def run(self, batch_id: int) -> None: batch = FileSummaryBatch.objects.get(pk=batch_id) self.mark_batch_running(batch) self.emit("workflow_started", batch, {"batch_id": batch.id}) try: for node_code in self.resolve_nodes(batch): self.run_node(batch, node_code) self.mark_batch_success(batch) self.emit("workflow_completed", batch, self.build_completed_payload(batch)) except Exception as exc: self.mark_batch_failed(batch, str(exc)) self.emit("workflow_failed", batch, {"message": str(exc)}) ``` ### 8.3 节点跳过规则 | 节点 | 跳过条件 | | --- | --- | | extract | 当前批次没有压缩包 | | product_detect | 没有任何可用于识别的文件名、目录名或元数据 | --- ## 九、接口详细设计 ### 9.1 上传暂存接口 ```text POST /api/review-agent/conversations/{conversation_id}/attachments/ Content-Type: multipart/form-data ``` 请求: | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | files[] | File[] | 是 | 一个或多个文件 | 响应: ```json { "attachments": [ { "id": 101, "original_name": "注册资料.zip", "file_size": 204800, "upload_status": "uploaded" } ] } ``` 权限: ```text conversation.user 必须等于 request.user ``` ### 9.2 发送消息并按需触发工作流 沿用现有 `POST /chat/stream/` SSE 能力,在 `stream_chat` 中增加判断: ```text 用户发送 prompt -> 保存 Message -> 判断 prompt 是否命中自动汇总工作流 -> 命中则创建 FileSummaryBatch 并启动后台工作流 -> SSE 返回 workflow_meta -> 未命中则走原 LLM 流式回复 ``` 新增 SSE meta: ```json { "conversation_id": 1, "title": "新对话", "workflow": { "type": "file_summary", "batch_id": 12, "status": "running" } } ``` ### 9.3 查询批次状态 ```text GET /api/review-agent/file-summary/{batch_id}/ ``` 响应: ```json { "batch": { "id": 12, "batch_no": "FS202606050001", "status": "running", "product_name": "", "total_files": 24, "success_files": 10, "failed_files": 1, "uncertain_files": 2, "total_pages": 180 }, "nodes": [ { "node_code": "page_count", "node_name": "解析页数中", "status": "running", "progress": 45, "message": "正在解析 11/24" } ], "exports": [] } ``` ### 9.4 工作流事件流 ```text GET /api/review-agent/file-summary/{batch_id}/events/?after={event_id} ``` 响应类型:`text/event-stream` 事件: ```text event: node_progress data: {"event_id": 301, "batch_id": 12, "node_code": "page_count", "status": "running", "progress": 45, "message": "正在解析 11/24"} ``` ### 9.5 下载导出文件 ```text GET /api/review-agent/file-summary/exports/{export_id}/download/ ``` 权限: ```text ExportedSummaryFile -> batch -> conversation -> user 必须为当前用户 ``` --- ## 十、前端详细设计 ### 10.1 三栏布局 页面调整为三栏: | 区域 | 内容 | | --- | --- | | 左侧栏 | 对话历史 | | 中间栏 | 聊天消息、输入框 | | 右侧栏上半部分 | 拖拽式文件导入区 | | 右侧栏下半部分 | 工作流卡片列表 | HTML 结构建议: ```html
``` ### 10.2 上传交互 JS 方法: ```javascript function bindUploadDropzone() function uploadConversationFiles(files) function renderAttachmentList(attachments) ``` 流程: ```text 用户拖拽或选择文件 -> POST attachments 接口 -> 保存成功后右侧上传区展示文件名 -> 不启动工作流 -> 用户发送提示词 -> 命中工作流后创建工作流卡片 ``` ### 10.3 工作流卡片 JS 方法: ```javascript function createWorkflowCard(batch) function updateWorkflowNode(batchId, nodePayload) function markWorkflowCompleted(batchId, payload) function markWorkflowFailed(batchId, payload) function connectWorkflowEvents(batchId) function restoreWorkflowCards() ``` 卡片结构: ```html
文件目录与页数汇总 运行中
  1. 上传中
  2. 解压中
  3. 扫描中
  4. 解析页数中
  5. 识别产品名中
  6. 输出 Markdown 中
  7. 输出 Excel 中
``` ### 10.4 Markdown 渲染 现有消息使用 `nl2br`,无法正常渲染 Markdown 表格。需要改造: | 消息类型 | 渲染策略 | | --- | --- | | 普通用户消息 | escapeHtml + nl2br | | 普通助手消息 | 安全 Markdown 渲染 | | 文件汇总结果 | 安全 Markdown 渲染,允许 table、a、strong、code | 可选方案: | 方案 | 说明 | | --- | --- | | 前端 marked + DOMPurify | 渲染体验好,但增加前端依赖 | | 后端 markdown + bleach | 后端输出安全 HTML,前端直接展示 | Demo 建议使用前端 `marked` + `DOMPurify` CDN 或本地静态文件。 --- ## 十一、对话标题更新设计 产品名识别成功后更新标题: ```python def update_conversation_title(batch, product_name): conversation = batch.conversation if conversation.title.startswith("新对话"): conversation.title = f"{product_name}-文件汇总"[:120] conversation.save(update_fields=["title", "updated_at"]) ``` 规则: | 场景 | 处理 | | --- | --- | | 新对话默认标题 | 更新为产品名 | | 用户已有自定义标题 | 不覆盖 | | 产品名为空 | 不更新 | --- ## 十二、测试设计 ### 12.1 单元测试 | 用例 | 目标 | | --- | --- | | test_trigger_keywords | 提示词命中时触发自动汇总 | | test_save_attachment_binds_conversation | 上传文件绑定当前对话 | | test_zip_extract_safe_path | zip 解压禁止路径穿越 | | test_scan_files_builds_relative_path | 扫描生成正确相对路径 | | test_count_pdf_pages | PDF 页数统计 | | test_count_xlsx_sheets | xlsx 工作表数量统计 | | test_count_pptx_slides | pptx 幻灯片数量统计 | | test_retry_three_times | 单文件失败重试 3 次 | | test_uncertain_old_doc | 老 doc 元数据缺失时标记 uncertain | ### 12.2 接口测试 | 用例 | 目标 | | --- | --- | | test_upload_attachment_api | 上传接口返回 attachment_id | | test_upload_permission_denied | 不能向他人对话上传文件 | | test_stream_triggers_workflow | 发送命中提示词后返回 workflow meta | | test_batch_status_permission | 不能查询他人批次 | | test_export_download_permission | 不能下载他人导出文件 | ### 12.3 集成测试 | 用例 | 目标 | | --- | --- | | test_file_summary_zip_workflow | zip 上传后完整工作流成功 | | test_file_summary_multi_file_workflow | 多文件上传后完整工作流成功 | | test_single_file_failure_not_blocking | 单文件失败不阻断批次 | | test_workflow_events_created | 节点事件按顺序写入数据库 | | test_markdown_and_excel_exports | Markdown 与 Excel 文件生成成功 | ### 12.4 前端验证 | 用例 | 目标 | | --- | --- | | 拖拽上传 | 右侧上传区展示文件列表 | | 提示词触发 | 发送“自动汇总文件目录与页数”后创建工作流卡片 | | 状态实时更新 | SSE 事件驱动节点状态变化 | | 页面刷新恢复 | 刷新后右侧卡片恢复当前批次状态 | | Markdown 表格 | 对话消息中表格和下载链接正常显示 | --- ## 十三、开发顺序 1. 增加依赖与模型字段,生成迁移。 2. 实现文件上传暂存接口和存储目录策略。 3. 实现 workflow_trigger,根据提示词决定是否启动工作流。 4. 实现 SkillRegistry、WorkflowExecutor 和 WorkflowEvent。 5. 实现压缩包解压、文件扫描、页数统计服务。 6. 实现 Markdown 报告与 Excel 导出。 7. 改造前端三栏布局、拖拽上传区和工作流卡片。 8. 增加 Markdown 渲染能力。 9. 补齐权限测试、工作流测试和前端手工验证。 --- ## 十四、参考依据 本设计采用轻量 Python 库优先方案,依据如下: | 能力 | 依据 | | --- | --- | | PDF 页数 | pypdf 的 PdfReader 可读取 pages | | docx 元数据 | python-docx 支持 core properties | | pptx 幻灯片 | python-pptx 可读取 presentation slides | | xlsx 工作表 | openpyxl 可读取 workbook worksheets | | xls 工作表 | xlrd 支持读取历史 xls 工作簿 | | 老 Office 元数据 | olefile 可读取 OLE2 复合文档结构 | | 7z 解压 | py7zr 支持 7z 压缩格式处理 | | rar 解压 | rarfile 通常依赖外部 unrar/unar/7z 工具,故本设计优先系统 7z |