下一篇我们准备接数据库。在动数据库代码前我想先把一个容易混的点讲清楚请求模型、响应模型、数据库里的数据不应该总是用同一个类。这篇先不写 SQL。我们继续用内存数据模拟但把三个模型分开ItemCreate用来接收创建请求ItemPublic用来返回给前端ItemInDB用来表示服务内部保存的数据。做完后我们会创建一个商品。请求里不传id、cost_price、created_at但服务端会自己补上响应里也不会把cost_price暴露出去。一个模型到处用会很快别扭前面我们已经有两个模型classItemCreate(BaseModel):name:strprice:floatis_offer:boolFalseclassItemPublic(BaseModel):id:intname:strprice:floatis_offer:boolFalse这两个模型的分工已经不同ItemCreate前端创建商品时能传什么。ItemPublic接口返回给前端什么。现在如果准备保存数据还会多出一些字段。比如成本价cost_price、创建时间created_at。这些字段服务端需要但前端创建商品时不应该传接口返回时也不一定要给。所以我会再加一个内部模型。抽出 ItemBase减少重复字段name、price、is_offer在几个模型里都会出现。可以先抽一个基础模型fromdatetimeimportdatetimefrompydanticimportBaseModelclassItemBase(BaseModel):name:strprice:floatis_offer:boolFalseclassItemCreate(ItemBase):passclassItemPublic(ItemBase):id:intclassItemInDB(ItemBase):id:intcost_price:floatcreated_at:datetime这里的重点不是继承语法而是边界ItemCreate没有id因为创建时 ID 应该由服务端生成。ItemPublic没有cost_price因为成本价不该从接口暴露出去。ItemInDB有完整字段因为服务端内部需要保存它们。这三个模型名字很直白读代码时不用猜它们分别在哪个地方用。用 ItemInDB 模拟服务端内部数据现在把内存数据从普通字典换成ItemInDBfromdatetimeimportdatetime,timezone fake_items_db{1:ItemInDB(id1,nameHammer,price9.99,is_offerFalse,cost_price4.0,created_atdatetime(2026,1,1,tzinfotimezone.utc),),}这段代码模拟的是“数据库里真实保存的数据”。它比接口返回给前端的数据多。后面换成 SQLModel 或 SQLAlchemy 时这个位置会变成数据库模型或 ORM 对象。现在先用ItemInDB把边界练清楚。创建接口只接收 ItemCreate创建商品时请求体仍然用ItemCreaterouter.post(,response_modelItemPublic,summary创建商品,)defcreate_item(item:ItemCreate,token:strDepends(get_token_header)):new_itemItemInDB(idmax(fake_items_db)1,**item.model_dump(),cost_priceitem.price*0.6,created_atdatetime.now(timezone.utc),)fake_items_db[new_item.id]new_itemreturnnew_item重点看函数签名和装饰器defcreate_item(item:ItemCreate,...)这表示请求体只允许传name、price、is_offer。response_modelItemPublic这表示响应只返回id、name、price、is_offer。函数里面虽然创建了ItemInDB里面有cost_price和created_at但返回给前端时会被ItemPublic过滤掉。在 /docs 里试一次启动服务.\.venv\Scripts\Activate.ps1 python-m pip install-e.$env:PYTHONIOENCODING utf-8$env:PYTHONUTF8 1fastapi dev app/main.py打开http://127.0.0.1:8000/docs调用POST /items请求头里填x-token: secret-token请求体填{name:Notebook,price:12.5,is_offer:true}响应应该类似这样{name:Notebook,price:12.5,is_offer:true,id:4}注意响应里没有cost_price也没有created_at。这不是因为函数里没创建它们而是因为response_modelItemPublic只允许这些字段出现在响应里。分清三个问题写接口时我会把这三个问题分开想前端创建时能传什么用ItemCreate回答。前端读取时能看到什么用ItemPublic回答。服务端内部需要保存什么用ItemInDB回答。如果用一个模型回答三个问题前期代码会少几行后面会开始互相牵扯想给数据库加字段结果请求体也多了字段想隐藏内部字段响应又要额外处理。分开以后每个变化只影响它该影响的地方。动手改一下给ItemInDB增加一个created_by字段classItemInDB(ItemBase):id:intcost_price:floatcreated_at:datetime created_by:str创建商品时由服务端补上new_itemItemInDB(idmax(fake_items_db)1,**item.model_dump(),cost_priceitem.price*0.6,created_atdatetime.now(timezone.utc),created_bysystem,)然后在/docs里调用POST /items。请求体里不用传created_by响应里也不应该出现created_by。如果这两点都成立说明你已经把请求模型、响应模型和内部模型分开了。到这里这篇的目标已经完成我们把ItemCreate、ItemPublic、ItemInDB分成了三个用途。我们创建商品时只接收业务输入字段。我们确认内部字段不会被接口响应暴露出去。本文代码https://github.com/tanghaojin/fastapi-beginner-lab/tree/article-10-model-boundary下一篇开始接数据库把现在的内存数据换成 SQLite让数据在服务重启后还能查到。参考资料FastAPI Response Model: https://fastapi.tiangolo.com/tutorial/response-model/FastAPI SQL Databases: https://fastapi.tiangolo.com/tutorial/sql-databases/