vue3搭配fastapi - 完整解决方案与实战教程

在现代前后端分离架构中,Vue3 与 FastAPI 的组合已成为高并发、强类型 Web 应用的热门选择。然而,许多开发者在从单体应用或传统 Django/Flask 迁移时,常遇到 跨域预检失败、Pydantic 模型与 TypeScript 类型不同步、以及文件上传时 FormData 解析异常 等棘手问题。本文基于真实生产环境踩坑经验,提供可直接复用的解决方案。

问题现象

原因分析

解决方案(附完整代码)

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. 排查步骤清单(按顺序执行)

  1. 打开浏览器开发者工具 → Network 面板,检查 OPTIONS 预检请求是否返回 200,响应头是否包含 Access-Control-Allow-Origin
  2. 若预检失败,检查 FastAPI 的 CORSMiddleware 是否在 app = FastAPI() 之后立即添加,且 allow_origins 未使用通配符 *allow_credentials=True 共存。
  3. 对于 422 错误,在 FastAPI 控制台查看详细校验错误,确认前端发送的 JSON 键名是否与 Pydantic 的 alias 或字段名匹配。
  4. 文件上传时,在 Network 面板查看请求头 Content-Type 是否为 multipart/form-data; boundary=...,若为 application/json 则需修改 Axios 配置。
  5. 使用 curl 或 Postman 直接测试 FastAPI 接口,排除前端干扰。

遵循以上方案,可解决 Vue3 + FastAPI 组合中 90% 的联调障碍。建议将 Pydantic 的 alias_generator 与前端 TypeScript 类型生成工具(如 openapi-typescript)结合,实现端到端类型安全。