06.28.每日总结
今天好兄弟来了 所以有小半天时间基本没做事,剩余时间也都是在反思web开发和模拟项目上了,下面为今日进度:
农民工项目进度
今天实际进度没有多少,主要是学了些web开发里的常识和网课没讲的内容
登录功能和查询功能完成 添加工人功能写了一半
添加了一些关键功能:比如数据校验 自定义异常管理器 网页程序关闭
学习了校验模型 枚举类 3XX重定向
主要还是排查了一些代码里可能存在的错误
6月28日(周日)fastapi模拟项目与千问的问答总结
问:如果用户什么都不输入点击登录(相当于表单没有传任何参数)这时候没有跳转到登录失败界面,而是网页失效
答:当表单什么都不填直接点击登录时,前端发送了空请求,后端在处理时(比如尝试用空用户名去字典里查找,或者进行字符串操作)抛出了 Python 异常。因为你的后端没有“捕获”这个异常,FastAPI 就会默认返回一个 500 Internal Server Error,这就导致了你看到的“网页失效”。
要解决这个问题,我们需要在 FastAPI 后端增加参数校验和异常处理。
数据的参数校验
问:如何进行参数校验答:FastAPI 深度集成了 Pydantic。你可以定义一个数据模型,要求 username 和 password 不能为空。如果前端传了空值,FastAPI 会自动拦截并返回标准的 422 Unprocessable Entity 错误,而不会导致网页崩溃。
from fastapi import FastAPI, Form, HTTPException
from pydantic import BaseModelapp = FastAPI()
class LoginData(BaseModel):
username: str
password: str
@app.post("/login") async def login(data: LoginData = Form()):
pass除了后端处理,强烈建议在你的 HTML 登录表单的input标签中加上 required 属性。这样如果用户什么都不填,浏览器会直接弹出提示框,根本不会把请求发给后端。
问:是不是可以这样理解 对于所有需要前端传回数据䣌场合 最好都使用参数校验 添加校验模型
答:完全正确!👍 在 FastAPI(以及绝大多数现代后端框架)的开发中,“永远不要信任前端传来的数据” 是一条铁律。对于所有需要前端传回数据的场合,使用 Pydantic 模型进行参数校验不仅是最佳实践,更是保障后端安全、稳定的核心手段。
补充:如何处理数据校验失败的提示?
默认情况下,如果 Pydantic 校验失败,FastAPI 会返回一个包含 loc、msg、type 的 JSON 数组,这对于前端来说可能不够直观。如果你希望返回更友好的中文提示,可以在模型中使用 Field:
from pydantic import BaseModel, Field
class LoginData(BaseModel):
username: str = Field(..., min_length=1, description="用户名不能为空")
password: str = Field(..., min_length=6, description="密码长度不能少于6位")使用数据校验类的过程中我发现一个问题啊 如果这个数据本来是字符串,它一经过数据校验类之后类型就变成数据校验类本身了,这意味着它以前的字符串方法就全部不能用了,这不太好吧
思想:Pydantic 模型就像一个“海关安检机”。字符串进去,经过安检(类型转换、格式校验、清洗),出来的时候被包装成了一个“带有护照(对象属性)的旅客”。它确实不能直接当字符串用了,但它换来了绝对的安全和结构的清晰。
那么该如何查看原来数据的属性:“把它的护照给它拿掉”:Pydantic对象里的属性,依然是那个纯正的字符串。你只需要通过 .属性名 把它“拿”出来,就可以继续使用所有字符串方法了。
问:如果我在我的校验模型里写入 worker_age:int 如果传输来的数据不能顺利转换成int,就会被校验模型阻止吗
答:正确。当你在 Pydantic 模型中定义 worker_age: int 时,FastAPI 会在数据进入你的业务代码之前,自动执行一道“安检”程序。如果失败:FastAPI 根本不会执行你写的业务代码,而是直接给前端返回一个 HTTP 422 Unprocessable Entity(无法处理的实体)错误。
问:那如果我在模型校验里写入联合类型Union[int,str] 这时候数据传过来又会执行怎么样的操作呢
Pydantic 会执行“逐个尝试,成功即止”的宽容策略。在 Pydantic 的 Union 机制中,类型的排列顺序直接决定了校验的优先级。你提到的“把不好通过的类型(更严格的类型)写在前面”,正是实际开发中处理联合类型的黄金法则。
补充:数据校验的pydantic
Pydantic的Field函数简直就是一个“万能安检机”:
管字符串长度,用 min_length 和 max_length
管数值大小,用 ge, gt, le, lt
管格式,用 pattern(正则)
把这些规则全写在模型里,你的后端代码不仅绝对安全,而且非常清爽
异常处理----自定义异常处理器
问:针对输入空的用户名和密码返回422这个了解了 但是这个页面不好看 我想也链接到登录失败这个页面答:1 编写好异常后跳转的网页 2 自定义异常处理器:
我们需要导入 RequestValidationError(这是 FastAPI 校验失败时抛出的异常),并告诉 FastAPI:一旦遇到这个异常,就返回我们的漂亮页面,而不是默认的 JSON。
from fastapi.exceptions import RequestValidationError
#使用 @app.exception_handler 装饰器覆盖默认的校验错误处理器
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
# 提取 Pydantic 报错的具体信息
error_messages = [err['msg'] for err in exc.errors()]
friendly_msg = ";".join(error_messages)
# 返回自定义的登录失败页面
return render_login_fail_page(friendly_msg)问:有关HTTPException
答:语法:from fastapi import HTTPException
raise HTTPException(status_code=400, detail="用户名和密码不能为空!")
状态码:3XX:重定向
问:为什么使用status_code=303答:在 POST 请求发生错误时,官方推荐使用 303 See Other 状态码进行重定向。它会强制浏览器使用 GET 方法去请求 /worker_system/register/fail 页面,这样可以避免浏览器在刷新页面时重复提交登录表单(防止表单重复提交问题)。
补充:以后在使用 RedirectResponse 时,请遵循以下“黄金法则”:
1 表单提交后的页面跳转(如登录成功、注册成功):必须使用 303----以GET方式查看另一个网页 2 API 之间的内部转发(需要保留 POST/PUT 方法和参数):使用 307----临时重定向,并保持原来的请求方式不变
问:当其他业务文件用到app时,从main.py调用app这样会触发循环调用的报错,那我该怎么处理呢?
答:如果你为了代码整洁,把登录接口抽离到了 auth.py 文件中,这时候才涉及到“导入”的问题。但注意,不是 auth.py 导入 main.py 的 app(这会引发你之前遇到的循环导入报错),而是在 main.py 中导入 auth.py 的路由。
问:但如果按照上面写法在其他模块写分路由最后加载main文件的app中 会出现以下警告(非报错)
类 ‘APIRouter’ 的未解析的特性引用 ‘exception_handler’
答:表层:为什么会出现这个警告:因为 APIRouter 和 FastAPI 是两个不同的类。 FastAPI(也就是 app)是主应用,它自带 @app.exception_handler 装饰器。 APIRouter(也就是 router)是子路由模块,它的设计初衷是只负责处理路由和请求逻辑。 你的 IDE 检查到你在 router 对象上调用了 exception_handler,发现 APIRouter 类本身并没有定义这个特性,所以贴心地给你画了个黄线警告。
深层:核心原则:
异常处理器必须写在 main.py 中 在 FastAPI 的模块化架构中,全局的异常处理器、中间件(Middleware)等,都必须在主应用 app 上统一注册,而不是在子路由 router 中注册。
所以,正确的做法是: 把异常处理代码移到 main.py 中(使用 @app)。 子路由文件 auth.py 保持干净,只写业务逻辑。
问:那即使我想把异常处理器处理后(因为异常所以登录不上去)的页面也链接到登录失败的页面(这个页面是分属于register模块的)按照规范我也依然应该把异常处理器写在main里给app用,是这个意思吗
答:完全正确!你的理解非常精准。
在 FastAPI 的模块化架构中,无论你要跳转的目标页面属于哪个模块(哪怕它是 register 模块的),全局异常处理器都必须写在 main.py 的 app 上。
为什么必须这么设计?
1 全局视野:app 是整个应用的大管家,而 router 只是负责处理特定路径的“部门经理”。当发生异常(如 422 校验错误)时,这属于全局级别的事件,必须由大管家来统一接管和分发。
2 避免循环导入:如果你把异常处理器写在 register 模块里,或者试图在 register 里导入 main 的 app,就会触发你之前遇到的 circular import 报错。
3 URL 只是字符串:对 main.py 来说,它不需要知道 /worker_system/register/fail 这个页面是怎么实现的,它只需要知道这是一个合法的 URL 字符串。只要你的 main.py 中通过 app.include_router() 把 register 模块挂载进来了,这个 URL 就是生效的。
补充: FastAPI 默认期望接收 JSON 格式的数据,而你的 HTML 表单提交的是 application/x-www-form-urlencoded 格式。当 FastAPI 发现请求头不是 JSON 时,它会直接拒绝并抛出 RequestValidationError,导致你的异常处理器被触发。 要让 FastAPI 正确接收 HTML 表单数据,必须在路由函数的参数上显式加上 Form(...)。
问:GET和POST到底是什么东西 要保证哪两处的这两种请求一致才能顺利链接
简单来说,GET 和 POST 是 HTTP 协议中最常用的两种请求方式(动作)。你可以把它们理解为前端发给后端的“指令”:
GET(获取):语义是“只读查询”。用来向服务器要数据,比如打开一个网页、查询一个用户信息。它不会修改服务器上的任何数据。<>br POST(提交):语义是“写入/修改”。用来向服务器提交数据,比如登录、注册、下单。它会触发服务器的业务逻辑,可能会改变数据库里的数据。
要保证前端的method参数和后端的@router.xxx一致才能顺利链接
问:PRG模式
答:在 Web 开发中,处理完表单提交(POST)后跳转到新页面,业界标准的做法是 PRG 模式(Post-Redirect-Get)。
你需要显式设置 status_code=303。
它的行为是:明确要求客户端使用 GET 方法去访问新地址,并且会丢弃原来的请求体(表单数据)。
这不仅能完美解决 405 报错,还能防止用户在成功页面点击“刷新”时,浏览器弹出“是否重新提交表单”的警告。
问:我的swagger文档测试正常显示了登录成功的界面,但是我实际操作时明明输入同样的用户名和密码,就显示method not allowed
答:原因非常简单:Swagger 文档和浏览器直接访问的底层逻辑完全不同。
1 Swagger 的测试机制:
当你在 Swagger 界面点击 "Try it out" 并输入用户名密码时,Swagger 实际上是在后台帮你发送了一个标准的 POST 请求(通常还会带上 application/json 的请求头)。你的后端完美接收了这个 POST 请求,处理成功后返回了 303 重定向,Swagger 忠实地把这个结果展示给了你。
浏览器直接访问的机制:
当你在浏览器的地址栏直接输入 URL(比如 /worker_system/register/ok)并按下回车时,浏览器永远只会发送 GET 请求。 如果你的后端代码里,这个成功页面只定义了 @router.post("/ok"),或者因为某种原因 POST 路由没有生效,FastAPI 收到 GET 请求后发现“此路不通”,就会直接给你返回 405 Method Not Allowed。
问:那么现在场景是这样的:我的后端数据端的性别存储是字典主表+字典项表,根据1:男,2:女的逻辑来的 那么前端的下拉框(男,女)的value值是1和2
可是再想一下 我要是前端的value直接写男和女 后端也省事一点直接存性别是男和女 舍弃掉中间的1和2的两次后端转换不就好了嘛
答:因为有以下隐患:
1. 存储空间与查询性能(最直观的差距)
存汉字:在数据库中,一个汉字(UTF-8编码)通常需要占用 3个字节。
存数字:而一个数字(如 TINYINT)只需要 1个字节。
如果你的系统有 100 万条工人数据,存汉字需要约 3MB,存数字只需 1MB。更重要的是,在进行数据过滤、排序时,数字比较的速度远快于字符串比较。
2. 数据一致性(防不胜防的脏数据)
3. 国际化与多语言扩展(未雨绸缪)
4. 跨系统兼容性
关于你说的“后端转换麻烦”的误区 其实,“1和2”与“男和女”之间的转换,在后端根本不需要你手动写复杂的逻辑!
既然你使用了 FastAPI + Pydantic,最优雅的做法是利用枚举(Enum)。你只需要在后端定义一个枚举类,FastAPI 就会自动帮你完成转换
from enum import IntEnum
class GenderEnum(IntEnum):
MALE = 1
FEMALE = 2
class Worker(BaseModel):
name: str
gender: GenderEnum问:什么叫前后端解耦与展示(扩展性)
答:前端传 1,数据库存 1,那前端页面怎么显示“男/女”呢?
如果前端自己写死 1=男, 2=女,以后你要加一个 3=未知,前端、后端、数据库全都要改。
标准的做法是:后端提供一个接口,把枚举列表(包含 code 和 label)返回给前端。前端拿到后去渲染下拉框和列表。这样以后无论怎么加选项,前端代码一行都不用动。
问:通过数据校验和枚举类 感觉后端不相信任何前端做的数据过滤努力
答:用一句话来概括就是:“前端校验是为了哄用户开心,后端校验是为了保后端平安。”
1. 前端校验:属于“用户体验(UX)”范畴
它的目的:是为了让用户在点击“提交”按钮之前,就能知道“哎呀,我密码少填了一位”,从而立刻修改。
它的价值:极大地提升了用户体验,减少了无意义的网络请求,减轻了服务器的压力。
它的本质:前端是“门面担当”,它负责貌美如花,把界面做得漂漂亮亮、交互做得顺顺滑滑。
2. 后端校验:属于“系统安全与业务底线”范畴
它的目的:是为了保证数据库的绝对纯洁,以及业务逻辑的绝对安全。
它的价值:哪怕前端代码被黑客删光了,哪怕有人用脚本恶意攻击,系统依然能坚挺地拒绝非法数据。
它的本质:后端是“保安大爷”,它负责稳如泰山,死死守住大门,不管外面是谁,没有通行证(合法数据)绝对不让进。
