P12第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

本机服务与前端协作

本机服务与前端协作 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

这一课把 Python 服务接到真实前端上:核对地址端口、配 CORS、对齐数据契约,并把「服务没起」「重复提交」这类联调日常变成可预期的处理。

问题场景

前端跑在 http://localhost:3000(Next.js),Python 服务在 http://127.0.0.1:8100。浏览器里 fetch 一发,控制台一排红:CORS 策略拦下、Failed to fetch。修好跨域又发现前端期望 createdAt,后端返回 created_at。这就是本机联调三大件:地址、跨域、契约。

核心解释

跨域:3000 调 8100 属于跨源,浏览器先发 OPTIONS 预检,服务不点头就拦。FastAPI 用中间件声明允许的源:

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],   # 本机前端;生产环境收窄到实际域名
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["Content-Type"],
)

注意三点:allow_origins=["*"] 在带凭据(cookie)的请求下无效,本机开发明确写端口最稳;改了中间件要重启 uvicorn;判断「服务不可达」先在浏览器直接访问 http://127.0.0.1:8100/docs,能开就是跨域问题,开不了就是服务没起。

数据契约:前后端对字段名、结构、状态码的共识。本站的做法值得照搬——app/api 各路由统一返回 {total, items} 外壳,错误统一 {error: {category, message}},前端处理逻辑全站一套。Python 侧等价实现就是 Pydantic Out 模型:字段名定死,加字段不破坏老前端。

重复调用:React 19 的 StrictMode 下 useEffect 会执行两次,加上按钮双击,同一笔记可能写两遍。防线两边都设:后端靠唯一约束返回 409(P11 已建),前端靠加载态禁用按钮。后端防线是底线——前端的 loading 有 bug 也炸不出重复数据。

@app.post("/notes", status_code=201)
def create_note(note: NoteIn, conn=Depends(get_db)):
    try:
        with conn:
            cur = conn.execute("INSERT INTO note (id, title) VALUES (?, ?)",
                               (f"note_{note.title}", note.title))
        return {"id": cur.lastrowid, "title": note.title}
    except ValueError:
        raise HTTPException(status_code=409, detail="笔记已存在,请勿重复提交")

示例拆解

前端侧的最小对接代码,放在 Next.js 页面里验证全链路:

// app/test-api/page.tsx(联调用,验证后删)
async function loadNotes() {
  try {
    const res = await fetch("http://127.0.0.1:8100/notes");
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    const data = await res.json();
    console.log(data.total, data.items);      // 契约:total + items
  } catch {
    console.error("服务不可达:先确认 uvicorn 是否在跑");
  }
}

预期验证顺序:先关掉 uvicorn 请求一次,看到「服务不可达」的兜底信息;再启动服务请求,拿到 total + items;然后去把 allow_origins 里端口改成 3001 重启,复现 CORS 报错——三种失败各自长什么样,见过一次以后就不用猜了。

动手任务

  1. 给服务配 CORS 允许 localhost:3000,用一个临时 Next.js 页面成功 fetch /notes。
  2. 故意改错端口(8101)复现「服务不可达」,再核对 netstat -ano | findstr 8100 确认正确端口在监听。
  3. 给「创建笔记」按钮加双击防护(loading 态 + 禁用),并确认后端唯一约束对第二次请求返回 409。

常见错误

  • localhost vs 127.0.0.1 混用:对 CORS 是两个不同的源,允许了 127.0.0.1 拦 localhost;两边写法统一。
  • 以为 CORS 是服务端拦截:它是浏览器行为,curl 正常、浏览器报错就是它;解法在响应头不在请求。
  • 契约靠口头约定:字段名两边各写一份迟早漂移;用 Pydantic 模型生成/导出契约,P19 的契约测试兜底。
  • 前端吞掉非 2xx:fetch 对 404/409 不抛异常,必须手动判 res.ok,否则错误被当成功渲染。

检查与复盘

自查清单:

  • 能在 30 秒内定位「跨域、未启动、端口错」三种联调报错
  • 响应结构全站统一(total+items / error{category,message}),前端只写一套处理
  • 重复写入有两层防线:前端加载态 + 后端唯一约束 409

参考资料

检查题共 1 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1单选中等

前端 http://localhost:5173(Vite)调用 http://127.0.0.1:8000 的 FastAPI 接口,浏览器控制台报 CORS policy: No 'Access-Control-Allow-Origin',但用 curl 直接请求接口一切正常。原因与修法是?

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...