vue3搭配fastapi - 完整解决方案与实战教程
在现代前后端分离架构中,Vue3 与 FastAPI 的组合已成为高并发、强类型 Web 应用的热门选择。然而,许多开发者在从单体应用或传统 Django/Flask 迁移时,常遇到 跨域预检失败、Pydantic 模型与 TypeScript 类型不同步、以及文件上传时 FormData 解析异常 等棘手问题。本文基于真实生产环境踩坑经验,提供可直接复用的解决方案。
问题现象
- 跨域请求被拦截: Vue3 前端运行在
http://localhost:5173,FastAPI 运行在http://localhost:8000,浏览器控制台报错Access-Control-Allow-Origin缺失,且OPTIONS预检请求返回 405。 - 类型不一致导致运行时错误: 后端 Pydantic 模型字段为
snake_case,前端 TypeScript 接口使用camelCase,数据绑定后显示undefined。 - 文件上传失败: Vue3 使用
FormData发送文件,FastAPI 接口定义file: UploadFile = File(...),但后端始终返回 422 Unprocessable Entity。
原因分析
- 跨域中间件配置顺序错误: FastAPI 的
CORSMiddleware必须在路由注册之前添加,否则预检请求无法被正确处理。 - Pydantic v2 默认不再自动转换别名: 即使设置了
alias_generator,若未开启populate_by_name,前端发送的 camelCase 数据无法映射到 snake_case 字段。 - Content-Type 冲突: 当使用
FormData时,Axios 默认会设置Content-Type: application/json,导致 FastAPI 无法解析 multipart 数据。
解决方案(附完整代码)
1. 后端:FastAPI 跨域与 Pydantic 别名配置
# main.py
from fastapi import FastAPI, File, UploadFile, Form
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel
app = FastAPI()
# 关键:CORS 必须在路由注册前添加,且 allow_origins 不能为 ["*"] 同时 allow_credentials=True
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"], # 明确指定前端地址
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Pydantic v2 模型:自动生成 camelCase 别名,并允许通过字段名填充
class UserCreate(BaseModel):
model_config = ConfigDict(
alias_generator=to_camel,
populate_by_name=True, # 允许同时使用 snake_case 和 camelCase
from_attributes=True
)
user_name: str
user_age: int
@app.post("/api/user")
async def create_user(user: UserCreate):
# 前端发送 { "userName": "Alice", "userAge": 30 } 也能正确解析
return {"message": f"Created {user.user_name}", "age": user.user_age}
# 文件上传接口:必须使用 File 和 Form 显式声明
@app.post("/api/upload")
async def upload_file(
file: UploadFile = File(...),
description: str = Form(None)
):
content = await file.read()
return {"filename": file.filename, "size": len(content), "desc": description}
2. 前端:Vue3 + Axios 封装与类型定义
// api/client.ts
import axios from 'axios';
const api = axios.create({
baseURL: 'http://localhost:8000',
timeout: 10000,
// 注意:不要全局设置 Content-Type,否则 FormData 会失效
});
// 请求拦截器:自动附加 token(示例)
api.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
// 响应拦截器:统一错误处理
api.interceptors.response.use(
response => response.data,
error => {
console.error('API Error:', error.response?.data);
return Promise.reject(error);
}
);
export default api;
// types/user.ts
// 与后端 Pydantic 别名保持一致
export interface UserCreate {
userName: string;
userAge: number;
}
// components/UserForm.vue
<script setup lang="ts">
import { ref } from 'vue';
import api from '@/api/client';
import type { UserCreate } from '@/types/user';
const form = ref<UserCreate>({ userName: '', userAge: 0 });
const fileInput = ref<HTMLInputElement | null>(null);
// 提交 JSON 数据
const submitUser = async () => {
try {
const res = await api.post('/api/user', form.value);
console.log('Success:', res);
} catch (err) {
alert('提交失败,请检查控制台');
}
};
// 上传文件:必须使用 FormData,且不能手动设置 Content-Type
const uploadFile = async () => {
const file = fileInput.value?.files?.[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
formData.append('description', '测试文件');
try {
// 关键:让浏览器自动设置 multipart/form-data; boundary=...
const res = await api.post('/api/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' } // 某些 Axios 版本需要显式声明
});
console.log('Upload success:', res);
} catch (err) {
console.error('Upload failed:', err);
}
};
</script>
<template>
<form @submit.prevent="submitUser">
<input v-model="form.userName" placeholder="用户名" />
<input v-model.number="form.userAge" type="number" placeholder="年龄" />
<button type="submit">提交</button>
</form>
<div>
<input ref="fileInput" type="file" />
<button @click="uploadFile">上传</button>
</div>
</template>
3. 排查步骤清单(按顺序执行)
- 打开浏览器开发者工具 → Network 面板,检查
OPTIONS预检请求是否返回 200,响应头是否包含Access-Control-Allow-Origin。 - 若预检失败,检查 FastAPI 的
CORSMiddleware是否在app = FastAPI()之后立即添加,且allow_origins未使用通配符*与allow_credentials=True共存。 - 对于 422 错误,在 FastAPI 控制台查看详细校验错误,确认前端发送的 JSON 键名是否与 Pydantic 的
alias或字段名匹配。 - 文件上传时,在 Network 面板查看请求头
Content-Type是否为multipart/form-data; boundary=...,若为application/json则需修改 Axios 配置。 - 使用
curl或 Postman 直接测试 FastAPI 接口,排除前端干扰。
遵循以上方案,可解决 Vue3 + FastAPI 组合中 90% 的联调障碍。建议将 Pydantic 的 alias_generator 与前端 TypeScript 类型生成工具(如 openapi-typescript)结合,实现端到端类型安全。