Skip to content

031. 文件上传调试与 AI 简历后端准备

学习目标

这一节用 Debug 方式走一遍文件上传流程,并开始进入 AI 简历项目的后端准备。

学完后,你应该能理解:

  • ORM 查询条件为什么不是普通布尔值;
  • 文件上传接口如何一步步保存文件;
  • 为什么数据库里不建议直接存完整文件 URL;
  • 保存路径和访问路径为什么只部分重合;
  • 前端上传地址为什么要和后端接口地址分开;
  • AI 简历后端会先做哪些基础能力;
  • 通用新建 / 更新接口为什么要把数据包在 row 里;
  • 全量更新和按需更新有什么区别。

查询条件不是布尔值

写 ORM 查询时,经常会看到类似:

py
Model.date_start <= max_date

按普通 Python 直觉,比较运算应该返回 TrueFalse

但在 SQLModel / SQLAlchemy 里,这类运算符被重载了。

它返回的不是布尔值,而是一个查询表达式对象。

这个对象会被 ORM 转换成 SQL 条件。

所以不要把 ORM 条件理解成普通 if 判断。它本质上是在构造数据库查询。

用 Debug 走文件上传

文件上传接口可以通过断点观察完整流程。

大致步骤是:

  1. 接口收到上传文件;
  2. 生成文件 ID;
  3. 拼出保存目录;
  4. 创建目录;
  5. 读取文件二进制内容;
  6. 用异步文件 API 写入磁盘;
  7. flush 或完成写入;
  8. 返回文件记录。

Debug 的价值在于能看到每一步的真实变量。

比如文件 ID、保存路径、访问路径、文件名、二进制对象,都会比只看代码更直观。

只保存相对访问路径

文件上传后,不建议在数据库里直接存完整 URL。

不要存成:

txt
http://1.2.3.4/upload/文件ID/文件名.png

更适合保存成:

txt
/upload/文件ID/文件名.png

原因是服务器以后可能迁移。

如果数据库里存了完整 IP 或域名,一旦换服务器、换域名,就要批量刷新数据库里的历史文件地址。

保存相对路径时,只需要在前端或服务端拼接当前域名即可。

保存路径和访问路径

文件保存路径和访问路径不是同一个东西。

保存路径是服务器磁盘路径,例如某个静态资源目录。

访问路径是浏览器 URL,例如 /upload/xxx/file.png

它们的关系由 Nginx 静态资源配置决定。

Nginx 把某个 URL 前缀映射到服务器磁盘目录后,浏览器才能通过 URL 访问磁盘里的文件。

所以文件上传时要同时理解两件事:

  • 文件实际写到哪里;
  • 用户通过什么地址访问它。

为什么不直接依赖对象存储

文件也可以上传到阿里云 OSS 或腾讯云 COS。

但对象存储会引入额外配置和成本。

如果只是学习项目或小规模部署,用 Nginx 托管本地静态文件更简单。

对象存储更适合:

  • 大量文件;
  • 多服务器共享;
  • 图片处理;
  • CDN;
  • 水印;
  • 缩略图;
  • 更正式的生产环境。

当前项目先用最直接、资源开销最低的方式完成闭环。

前端上传地址要单独配置

前端环境变量里需要区分:

  • 后端普通接口地址;
  • 文件上传地址。

开发时,普通接口可能连本地后端,但上传文件最好直接传到服务器。

因为页面最终展示图片时,访问的是服务器上的静态资源。如果文件只保存在本地电脑,线上页面访问不到。

打包到生产环境时,这两个地址可以相同;本地开发时,它们可能不同。

前端上传组件测试

可以临时写一个测试页面,用上传组件验证远程上传接口。

测试逻辑包括:

  • 是否正在上传;
  • 上传状态是否变化;
  • 后端返回结果是否正确;
  • 返回结果里是否有文件访问路径;
  • 前端是否能拼出完整图片地址;
  • 图片是否能显示。

这个测试页面只是为了验证上传流程,不一定要提交为正式业务页面。

服务器资源要注意

如果同一台小服务器上同时跑多个后端服务,很容易撑不住。

例如:

  • 完整实战项目后端;
  • AI 简历后端;
  • 当前课堂项目后端。

如果服务器配置较低,可以停掉暂时不用的服务。

有多台服务器时,也可以把不同项目拆开部署,避免端口和内存互相影响。

AI 简历后端范围

AI 简历项目的后端工作量相对较小。

主要包括:

  • 简历模板增删改查;
  • 用户简历增删改查;
  • 文件上传接口;
  • 模型配置;
  • AI 生成或处理相关接口。

真正复杂的部分在前端。

因为 AI 简历需要处理编辑、预览、对话、代码生成、图片导出等交互。

模型配置调整

项目会调整模型配置,减少不再使用的模型,保留更适合当前任务的模型。

模型选择会随着时间变化。

关键不是背某一个模型名字,而是理解选择模型时要看:

  • 价格;
  • 速度;
  • 输出质量;
  • 调用稳定性;
  • 是否适合当前业务。

模型配置应该集中管理,方便后续替换。

新建参数为什么包一层 row

通用新建接口不要直接把实体字段平铺在请求体里。

更推荐:

json
{
  "row": {
    "fullName": "张三"
  }
}

这样后续要加额外参数时更方便。

例如:

  • 当前用户职位;
  • 权限信息;
  • 业务上下文;
  • 是否全量更新;
  • 其他控制字段。

如果一开始把所有实体字段平铺在最外层,后面再加这些额外参数就容易混乱。

全量更新和按需更新

更新接口常见两种策略。

策略含义
全量更新前端传来的对象会覆盖整条记录
按需更新只更新前端真正传入的字段

按需更新更适合后台编辑场景。

例如前端只传了 fullName,后端就只更新 fullName,不要把其他没传的字段覆盖成空值。

Pydantic 的 exclude_unset 可以帮助拿到真正传入的字段。

通过参数控制更新策略

有些系统会允许前端通过额外参数控制更新策略。

例如:

  • 默认按需更新;
  • 传入某个标记后改为全量更新。

当前阶段不一定马上实现这个控制参数,但要知道真实项目里可能存在这种设计。

后续更通用的 CRUD 封装会把这类能力做得更完整。

这一节的重点

这一节把两个方向接起来。

文件上传方面:

  • 用 Debug 观察文件保存;
  • 数据库存相对路径;
  • 区分保存路径和访问路径;
  • 前端上传地址单独配置。

AI 简历后端方面:

  • 准备模型配置;
  • 明确后端功能范围;
  • 调整通用接口参数格式;
  • 区分全量更新和按需更新。

这些都是后面进入 AI 简历完整功能前必须先铺好的基础。

AI Agent 课程学习文档。