FastAPI静态资源、跨域与后台任务
转载说明文章最新在掘金上发用户名为CaffeineProCSDN是我最早加入的社区但由于vip文章的原因不想然文章付费所以选择首发在掘金。假设现在有个需求要做一个最小可用的用户注册功能前端页面展示Logo、样式用户填写手机号和昵称后提交前端运行在http://127.0.0.1:5500API在http://127.0.0.1:8000——跨域注册成功后API立刻返回但还要在后台发欢迎短信、写审计日志——不能让用户等。这三个需求正好对应 FastAPI 的三块能力静态资源挂载、CORS 中间件、BackgroundTasks。推荐项目结构demo/ ├── main.py # FastAPI入口CORS、静态挂载、注册接口 ├── tasks/ │ └── background.py # 后台任务发短信、写日志 ├── static/ │ ├── css/style.css # 样式 │ ├── js/app.js # 前端fetch含自定义请求头 │ ├── images/logo.svg # Logo图片 │ └── demo.html # 注册页也可用 Live Server 打开 ├── logs/ # 运行后自动生成注册日志 ├── requirements.txt └── test_main.http # HTTP 测试用例一、挂载本地静态文件夹/static/css/style.css—页面样式/static/js/app.js—提交表单的JavaScript/static/images/logo.svg—页面图标这些文件放在服务器本地磁盘不需要经过Python业务逻辑FastAPI用StaticFiles直接映射URL到目录。需要在main中实现代码实现代码frompathlibimportPathfromfastapi.staticfilesimportStaticFiles STATIC_DIRPath(__file__).resolve().parent/staticapp.mount(/static,StaticFiles(directorystr(STATIC_DIR)),namestatic)定义静态目录时代码通过Path(__file__).resolve().parent/static构建路径__file__代表当前脚本文件resolve()将其彻底解析为绝对路径并清除符号链接parent获取该文件所在的上级目录最后利用/运算符将static子文件夹拼接到其后生成一个确定的Path对象。这一做法的核心目的是确保静态文件目录与项目代码处于同一级目录下无论应用从何处启动路径定位始终可靠从而增强了项目的可移植性和部署时的稳定性。挂载操作通过app.mount()方法实现它将路由前缀/static与本地静态目录绑定使得客户端访问http://域名/static/xxx时能够直接获取对应的本地文件资源。需要注意的是StaticFiles的directory参数要求传入字符串形式因此必须用str(STATIC_DIR)显式转换同时指定namestatic为挂载点命名便于在应用内部通过request.url_for(static, path...)动态反向生成静态资源的完整 URL既保证了静态文件的正常服务也提升了代码中资源引用的灵活性与可维护性。访问http://127.0.0.1:8000/static/images/logo.svg时FastAPI会从static/images/logo.svg读取文件并返回并自动设置合适的Content-Type。返回HTML页面用FileResponse返回demo.html示例代码为fromfastapi.responsesimportFileResponseapp.get(/demo)asyncdefdemo_page():returnFileResponse(STATIC_DIR/demo.html)StaticFiles适合css/js/图片等纯静态资源单个HTML页面用FileResponse更灵活可以加鉴权、动态变量等。下面是几点注意事项要点说明mount路径以/static挂载后目录内css/style.css对应URL/static/css/style.css挂载顺序mount的路由优先级低于普通app.get路由避免路径冲突生产环境大量静态资源建议走Nginx/CDNPython进程专注API缓存生产可在Nginx层加Cache-Control开发阶段--reload即可二、CORS 跨域中间件完整配置前端在http://127.0.0.1:5500API在http://127.0.0.1:8000——协议相同、域名相同、端口不同浏览器判定为跨域。若不做处理浏览器控制台会出现Access to fetch at http://127.0.0.1:8000/api/users/register from origin http://127.0.0.1:5500 has been blocked by CORS policy在本案例中的代码示例为fromfastapi.middleware.corsimportCORSMiddleware ALLOWED_ORIGINS[http://127.0.0.1:5500,http://localhost:5500,http://127.0.0.1:5173,# Vite的默认端口http://localhost:5173,]app.add_middleware(CORSMiddleware,allow_originsALLOWED_ORIGINS,allow_credentialsTrue,allow_methods[GET,POST,PUT,DELETE,OPTIONS],allow_headers[*],expose_headers[X-Request-Id],max_age600,)下面是对上面代码的参数解析参数作用案例取值理由allow_origins允许哪些源访问开发环境列出前端地址生产写具体域名allow_credentials是否允许携带Cookie登录态场景需要Trueallow_methods允许的HTTP方法必须包含OPTIONS预检用和POSTallow_headers允许客户端发送的请求头Demo前端发了X-Client-Versionexpose_headers允许JS读取的响应头如X-Request-Id用于链路追踪max_age预检结果缓存秒数600秒内重复请求不再发OPTIONS前端预检请求OPTIONS的坑constresawaitfetch(${API_BASE}/api/users/register,{method:POST,headers:{Content-Type:application/json,X-Client-Version:demo-1.0,// 非简单头触发预检},body:JSON.stringify(payload),});浏览器在真正发POST之前会先发送OPTIONS预检请求询问服务器我能不能从5500端口、用POST、带X-Client-Version头访问你。整体实际的流程如下图所示常见的踩坑与修改如下表所示现象原因修复OPTIONS 404路由只注册了POST没处理OPTIONS使用CORSMiddleware它会自动处理OPTIONS预检通过但POST失败allow_headers未包含自定义头加入X-Client-Version或设allow_headers[*]Cookie带不过去allow_origins[*]与allow_credentialsTrue冲突必须写具体域名不能*127.0.0.1vslocalhost浏览器视为不同源两个都加到allow_origins改了CORS仍报错浏览器缓存了失败的预检清缓存或用无痕窗口使用三、BackgroundTasks注册后的短信与日志案例中的需求用户点击立即注册后主流程进行校验手机号、写入内存数据库、返回201用户无需等待后置任务发送欢迎短信、写JSONL审计日志。这些后置任务失败不应影响注册结果且不应拖慢HTTP响应。后台任务函数示例defsend_sms(phone:str,message:str)-None:importtime time.sleep(0.5)# 模拟同步I/Oprint(f[SMS]-{phone}:{message})defwrite_registration_log(user_id:int,phone:str,nickname:str)-None:# 追加写入logs/registrations.jsonl...注册接口示例fromfastapiimportBackgroundTasksapp.post(/api/users/register)asyncdefregister_user(body:UserRegisterRequest,background_tasks:BackgroundTasks):# 校验、保存用户...background_tasks.add_task(send_sms,body.phone,f欢迎{body.nickname})background_tasks.add_task(write_registration_log,user_id,body.phone,body.nickname)returnUserRegisterResponse(user_iduser_id,...)执行的时序图如下图所示四、后台任务与异步任务别搞混概念对照async/await异步路由BackgroundTasks后台任务目的在等待I/O时释放事件循环响应返回后执行善后工作用户是否等待是必须等路由函数执行完否响应已先返回典型场景查数据库、调HTTP API异步客户端发短信、写日志、发邮件函数类型路由用async defawait任务函数通常是普通 def同步失败影响直接导致HTTP 5xx不影响已返回的响应简单的说这次请求还需要等它做完用异步I/O这次请求已经答完了顺便帮我把后面的事做了用后台任务。五、BackgroundTasks与Celery的适用边界针对后台任务的处理BackgroundTasks与Celery在适用边界上差异显著。前者作为FastAPI内置组件无需额外依赖在同一进程的线程池中执行任务部署简单且启动成本极低适合低 QPS、短耗时且允许任务丢失的场景如发送通知或记录日志但它存在明显局限——任务完全驻留内存进程重启即丢失不支持延迟执行、定时触发、自动重试且缺乏有效的监控手段而Celery需引入Redis或RabbitMQ作为消息代理并独立运行Worker进程部署和维护成本较高却能提供消息持久化、countdown/eta定时调度、内置重试机制如autoretry_for以及Flower等可视化监控其独立进程和水平扩展能力使其能够承载高QPS、长耗时且要求可靠交付的生产级任务。因此开发初期或轻量级需求可优先选用BackgroundTasks以快速迭代一旦业务对可靠性、延迟容忍度或任务规模提出更高要求则应果断迁移至Celery实现架构的平滑演进。下面提供对比表维度BackgroundTasksCelery Redis/RabbitMQ部署零依赖FastAPI内置需Broker、Worker进程进程模型同一FastAPI进程内线程池独立Worker可水平扩展持久化任务在内存进程重启即丢失消息持久化在队列延迟/定时不支持支持countdown、eta、Crontab重试需自己写try/except内置autoretry_for、max_retries监控仅 print / 日志Flower、Prometheus 等适用规模低QPS、短任务、可丢失高QPS、长任务、必须可靠七、小结在案例实践中静态资源通过app.mount(/static, StaticFiles(...))挂载页面所需的CSS、JS及图标为解决前端5500端口调用后端8000端口服务的跨域问题利用CORSMiddleware配置中间件并通过设定allow_headers与allow_methods对X-Client-Version等自定义头部触发的OPTIONS预检请求做好应答对于注册后发短信、写日志等非即时响应任务则使用BackgroundTasks.add_task()快速实现异步后台执行。在技术选型上MVP阶段直接采用轻量的BackgroundTasks即可满足需求随着业务对可靠性、延迟或大规模任务处理的更高要求可逐步演进至Celery等专业任务队列体现按业务阶段渐进式迭代的务实思路。