切换日光/暗黑模式
031. 文件上传调试与 AI 简历后端准备
学习目标
这一节用 Debug 方式走一遍文件上传流程,并开始进入 AI 简历项目的后端准备。
学完后,你应该能理解:
- ORM 查询条件为什么不是普通布尔值;
- 文件上传接口如何一步步保存文件;
- 为什么数据库里不建议直接存完整文件 URL;
- 保存路径和访问路径为什么只部分重合;
- 前端上传地址为什么要和后端接口地址分开;
- AI 简历后端会先做哪些基础能力;
- 通用新建 / 更新接口为什么要把数据包在
row里; - 全量更新和按需更新有什么区别。
查询条件不是布尔值
写 ORM 查询时,经常会看到类似:
py
Model.date_start <= max_date按普通 Python 直觉,比较运算应该返回 True 或 False。
但在 SQLModel / SQLAlchemy 里,这类运算符被重载了。
它返回的不是布尔值,而是一个查询表达式对象。
这个对象会被 ORM 转换成 SQL 条件。
所以不要把 ORM 条件理解成普通 if 判断。它本质上是在构造数据库查询。
用 Debug 走文件上传
文件上传接口可以通过断点观察完整流程。
大致步骤是:
- 接口收到上传文件;
- 生成文件 ID;
- 拼出保存目录;
- 创建目录;
- 读取文件二进制内容;
- 用异步文件 API 写入磁盘;
- flush 或完成写入;
- 返回文件记录。
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 简历完整功能前必须先铺好的基础。