装闭 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。

相关推荐
Mid_search11 分钟前
随机排列与Fisher-Yates算法
人工智能·深度学习·强化学习·随机排列·fisher-yates
ZGIAI5 小时前
ZGI Workflow:条件分支走错时先查哪一层
人工智能·架构
X54先生(人文科技)5 小时前
《元创力》纪实录 · 桥段 《窑变纪元:一份来自星历2227年的深空考古笔记》
人工智能·开源·ai写作·零知识证明
ZGIAI5 小时前
ZGI 文件产物:生成报告后怎样交付
人工智能·架构
东方-教育技术博主5 小时前
自动编码在教育场景中的重要性:一项基于多源证据的深度综述
大数据·人工智能
骥龙5 小时前
模块二:Ollama本地模型部署与OpenCode代理配置
人工智能
阡之尘埃5 小时前
Python数据分析案例85——大模型微调全流程(SFT的LoRA微调)
人工智能·python·深度学习·语言模型·llm·微调·千问
Regentsoft丽晶软件5 小时前
品牌方新品上市促销政策无法实时同步经销商,有没有支持总部-经销商-终端一站式的分销解决方案?
人工智能·经验分享·数据库架构
liuqs3325 小时前
机器人产业加速发展,制造业正在迎来新的自动化探索
大数据·人工智能
剑客的茶馆5 小时前
开发者,运维怎样转行FDE?
运维·ai·开发·fde