装闭 RenoPit 源码解析(04):装修图纸和合同文件上传处理流程

装修闭坑分析的输入既有户型图、效果图,也有合同、报价单和用户文字说明。装闭 RenoPit 的完整上传实现可以在 fthux/RenoPit 查看,本篇重点追踪文件从浏览器进入磁盘和数据库的过程。

一、前端如何组织多文件上传

CreateProjectPage 使用 <input type="file" multiple> 接收多个文件,允许扩展名包括 PDF、PNG、JPG、WEBP、TXT、DOCX 和 Markdown。文件先保存在 selectedFiles 状态中,页面显示名称、大小和图片或文档图标。

项目创建成功后,前端把所有文件追加到同一个 FormData

tsx 复制代码
const formData = new FormData()
selectedFiles.forEach((file) => formData.append('files', file))

await fetch(`${API}/projects/${projectId}/upload`, {
  method: 'POST',
  body: formData,
})

项目详情页后续追加素材时也调用同一接口。上传成功后,它并行刷新文件、图片和项目信息,使数量与列表立即更新。

二、后端先按扩展名分流

upload_files() 接收 List[UploadFile],然后逐个读取扩展名。PNG、JPG、JPEG、WEBP、GIF 和 BMP 被视为图片,其余文件进入普通文档分支。
flowchart TD AUploadFile --> B{是否图片扩展名} B -->|是| Csave_upload_file: images C --> Dcompress_image D --> Eget_image_dimensions E --> FProjectImage B -->|否| Gsave_upload_file: files G --> Hextract_text H --> IProjectFile

两条分支最后在同一个数据库事务中提交,并返回本次保存的图片数和文件数。

三、文件如何生成磁盘路径

file_storage.py 不直接使用用户上传的文件名作为磁盘名。generate_storage_filename() 先校验扩展名,再生成 UUID 文件名,避免同名文件互相覆盖。

项目目录按如下形式组织:

text 复制代码
uploads/
└── projects/
    └── {project_id}/
        ├── images/
        │   └── {uuid}.jpg
        └── files/
            └── {uuid}.pdf

save_upload_file() 读取 UploadFile 内容,计算字节数并写入目标路径,最后返回存储文件名、完整路径和文件大小。数据库中仍保存 original_filename,所以页面展示的是用户原始文件名,下载和分析使用的则是唯一存储路径。

四、图片上传后立即预处理

图片落盘后会调用 compress_image()。函数使用 Pillow 打开图片,读取宽高;如果任一边超过最大尺寸,就按比例缩放,并使用 LANCZOS 重采样。

带透明通道的 RGBA 或调色板图片会先转换为 RGB,最终以指定 JPEG 质量原地保存。函数还会尽量保留 EXIF 数据,避免手机照片方向信息丢失。

压缩完成后,get_image_dimensions() 再读取最终宽高,创建 ProjectImage

python 复制代码
img = ProjectImage(
    project_id=project_id,
    original_filename=file.filename,
    storage_path=storage_path,
    file_size=file_size,
    width=width,
    height=height,
)

后续多模态分析会再次读取该路径,把图片转换成 Base64 后发送给 LLM。

五、文档如何提取纯文本

普通文件落盘后交给 extract_text()。它使用一个扩展名到解析函数的映射表:

  • TXT、Markdown:按 UTF-8 读取;
  • DOCX:通过 python-docx 遍历段落;
  • PDF:通过 PyMuPDF 提取各页文本。

提取结果写入 ProjectFile.extracted_text。如果扩展名不在支持列表,extract_text() 抛出 ValueError,上传接口捕获后将提取文本设为空字符串,文件记录仍然可以保存。

这里处理的是 PDF 中的文本层;PDF 内嵌图片不会自动成为 ProjectImage。因此前端也会提示用户:需要视觉分析的图纸最好直接上传图片格式。

六、预览、下载和删除共用文件记录

项目详情页通过 /files/images 获取元数据。图片预览、图片下载和文件下载分别请求带资源 ID 的接口,后端根据数据库中的 storage_path 返回 FileResponse

删除单个素材时,接口先检查资源确实属于当前项目,再删除磁盘文件和数据库记录。前端成功后刷新对应列表,image_countfile_count 也会由项目响应重新计算。

七、上传链路小结

RenoPit 把上传过程拆成"浏览器收集、扩展名分流、唯一命名落盘、图片压缩或文本提取、数据库登记"五个阶段。最终,图片以磁盘路径和尺寸进入多模态链路,文档以磁盘路径和 extracted_text 进入文本分析链路。

完整实现位于 fthux/RenoPitfile_storage.pyimage_processor.pyfile_parser.py 和项目 API 中。下一篇将从上传后的项目状态出发,分析 FastAPI 如何把耗时 AI 任务交给 Celery Worker。

相关推荐
weixin_446260853 小时前
从被动镜像到主动智能体:面向网络物理人工智能的整体论数字孪生(HDT-Net)
网络·人工智能
秋枫要学习3 小时前
你的鼠标,正在被 AI 抢走
人工智能·计算机外设
满怀冰雪4 小时前
19-图像数据处理:PaddleVision Transform 实战
人工智能·深度学习·计算机视觉·paddle
IT_陈寒4 小时前
Vue的响应式让我原地破防,原来问题出在这
前端·人工智能·后端
带娃的IT创业者4 小时前
Kimi-K3 开源背后:2.8 万亿参数的“暴力美学”与智能体的新拐点
人工智能·开源·大模型·智能体·开源模型·kimi-k3·moonshot ai
m0_380743875 小时前
实战看出 LLM API 的隐性消耗
人工智能
南巷羽5 小时前
用 TRAE Work 把 20 条 GitHub Issue 变成可复核的需求优先级清单
github·产品
安逸Ai5 小时前
Claude Code、Cursor、Copilot 这类工具通常如何理解项目上下文?
人工智能