基于FastAPI的AI智能体Web系统构建(二)

目录

一.FastAPI表单数据

二.FastAPI异步处理

三.文件上传

四.FastAPI请求对象Request

五.FastAPI响应类型

1.JSON格式

2.列表格式

3.文件格式

4.其他格式


一.FastAPI表单数据

先检查是否安装mutilpart包,如果没有安装uv pip install python-multipart

python 复制代码
# 表单数据
# 安装pip install python-multipart==0.0.20
from fastapi import FastAPI, Form
from pydantic import BaseModel
from typing import Annotated

app = FastAPI()


@app.post('/login1')
def login1(username: str = Form(...), password: str = Form(...)):
    return {'username': username, 'password': password}


class User1(BaseModel):
    username: str
    password: str


@app.post('/login2')
def login2(user: Annotated[User1, Form()]):
    return user


class User2(BaseModel):
    username: str = Form(...)
    password: str = Form(...)


@app.post('/login3')
def login3(user: Annotated[User2, Form()]):
    return user


if __name__ == '__main__':
    import uvicorn
    uvicorn.run(app='main11:app', host='127.0.0.1', port=8000, reload=True)

二.FastAPI异步处理

在 FastAPI 中,异步( async )和非异步(同步)编程方式是其核心特性之一,它们在处理请求、性能以及并发能力上有着显著的区别。


异步 (async def)
事件循环 :异步代码运行在 Python 的异步事件循环(如 asyncio )中。事件循环负责协调多个协
程( coroutines ),在某个协程等待 I/O 操作(如数据库查询或 HTTP 请求)时,事件循环可以切
换到其他协程执行。
非阻塞 :当一个异步函数调用 await ,它会暂停执行,将控制权交回事件循环,允许其他任务运 行,直到等待的操作完成。
并发性 :异步模式允许单个线程处理大量并发请求,特别适合高并发场景(如 Web 服务器处理大
量客户端请求)。
非异步 (def)
阻塞式执行 :同步函数在调用时会完全占用线程,直到函数执行完成才会释放线程。
线程池 :在 FastAPI 中,同步函数由工作线程( worker threads )处理, Uvicorn ( FastAPI 常用
的 ASGI 服务器)会将同步函数放入线程池运行。
并发限制 :线程池的大小限制了同步函数的并发能力。如果线程池耗尽(例如,处理大量阻塞请
求),新请求将排队等待。

python 复制代码
# 测试异步
from fastapi import FastAPI
import asyncio
import time

app = FastAPI()

# 异步 endpoint:模拟并发 I/O 操作


@app.get("/async")
async def async_endpoint():
    start = time.time()
    # 模拟 5 次异步 I/O 操作(并发执行)
    tasks = [asyncio.sleep(1) for _ in range(5)]
    await asyncio.gather(*tasks)
    end = time.time()
    return {"异步时长": f"{end - start:.2f}秒"}

# 同步 endpoint:模拟相同的 I/O 操作


@app.get("/sync")
def sync_endpoint():
    start = time.time()
    # 模拟 5 次同步 I/O 操作(顺序执行)
    for _ in range(5):
        time.sleep(1)
    end = time.time()
    return {"同步时长": f"{end - start:.2f}秒"}


if __name__ == "__main__":
    import uvicorn
    uvicorn.run('main12:app', host="127.0.0.1", port=8000, reload=True)

异步执行明显时间快很多。

三.文件上传

python 复制代码
# 文件上传
# pip install aiofiles==24.1.0
from pathlib import Path

from fastapi import FastAPI, File, UploadFile, HTTPException, Form
import aiofiles

app = FastAPI()

# 小文件(10MB以下的文件)用bytes类型接收,不必异步,因为太小
@app.post("/upload1/")
def upload_file1(file: bytes = File(...)):
    # write 写入模式,如果这个文件已存在会直接清空覆盖;不存在就新建文件
    with open('./data/file.jpg', 'wb') as f:
        f.write(file)
    return {"msg": "文件上传成功"}

# 大文件(10MB以上的文件)用UploadFile类型接收,
# 会按照块逐步读取文件内容,避免一次性将整个文件加载到内存中
@app.post('/upload2/')
async def upload_file2(file: UploadFile):
    # 使用 aiofiles 异步 IO 提升性能;分块读取避免内存溢出
    async with aiofiles.open(f'./data/{file.filename}', 'wb') as f:
        # chunk = await file.read(1024*1024)
        # while chunk:
        #     await f.write(chunk)
        #     chunk = await file.read(1024*1024)

        # 分块读取文件内容(和上面四行效果一致)
        while chunk := await file.read(1024*1024):  # 分块读取1MB
            await f.write(chunk)

    return {"msg": "文件上传成功"}

# 批量上传文件,一次上传多个文件
@app.post('/batch-upload/')
def batch_upload(files: list[UploadFile] = File(...)):
    '''批量上传文件'''
    return {"count": len(files), "names": [f.filename for f in files]}


# 限制上传格式
ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.gif'}

@app.post('/upload-image/')
def upload_image(file: UploadFile):
    '''限制上传格式'''
    # 提取文件名后缀,并转为小写
    ext = Path(file.filename).suffix.lower()
    print('='*20, ext)
    if ext not in ALLOWED_EXTENSIONS:
        raise HTTPException(400, '不支持的文件扩展名')
    # 保存文件的逻辑
    return {"msg": "文件上传成功"}

#表单和图片一块上传
@app.post('/submit-form')
def submit_form(uname: str = Form(...),
                file: UploadFile = File(...)):
    return {"uname": uname, "filename": file.filename}


if __name__ == '__main__':
    import uvicorn
    uvicorn.run('main13:app', host="127.0.0.1", port=8000, reload=True)

四.FastAPI请求对象Request

python 复制代码
# Request获取请求信息

from fastapi import FastAPI, Request

app = FastAPI()

@app.get('/client-info')
async def client_info(request: Request):
    return {
        "请求URL": request.url,
        "请求方法": request.method,
        "请求IP": request.client.host,
        "请求参数": request.query_params,
        "请求头": request.headers,
        # "请求json": await request.json(),
        "请求cookies": request.cookies,
        # "请求form": await request.form(),
        # "请求files": request.files,
        "请求path_params": request.path_params,

    }

if __name__ == '__main__':
    import uvicorn
    uvicorn.run(app='main14:app', host='127.0.0.1', port=8000, reload=True)

请求信息如下:

五.FastAPI响应类型

在企业级应用中,以下响应类型最为常见:
JSON 响应 :用于 RESTful API 的数据交互,占主导地位(如用户信息、订单、配置)。
1
列表响应:用于分页查询或批量数据返回(如商品列表、日志记录)。
2
文件响应:用于报表导出、文件下载(如 CSV 、 PDF )。
字符串响应:用于健康检查或简单状态反馈。
4
HTML 响应 :用于管理后台或简单的 Web 页面。
5
重定向响应:用于认证流程或 URL 迁移。
6
流式响应:用于实时数据传输或大文件处理。

1.JSON格式

JSON 响应( 80%+ 场景)
JSON 是 Web API 中最常见的响应格式, FastAPI 天然支持通过返回Python 字典或 Pydantic 模型自动序列化为 JSON 响应。

python 复制代码
# 响应数据Json
from typing import Union, TypeVar, Generic
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    id: int
    name: str
    tags: list[str] = []


@app.get('/items/dict')
async def get_items_dict():
    return {"name": "张三", "age": 18}


@app.get('/items/model')
async def get_items_model():

    return Item(id=2, name="Iphone")


@app.get('/items/model2', response_model=Item, response_model_exclude_unset=True)
async def get_items_model2():

    return Item(id=2, name="Iphone")

# 定义泛型模型
T = TypeVar('T')


class SuccessResponse(BaseModel, Generic[T]):
    status: str = 'success'
    data: T


class ErrorResponse(BaseModel):
    status: str = 'error'
    message: str
    code: int


@app.get('/items/{item_id}', response_model=Union[SuccessResponse[Item], ErrorResponse])
async def get_items_model3(item_id: int):
    if item_id == 1:
        # 定义要返回数据
        item = Item(id=1, name="Iphone", tags=["red", "black"])
        return SuccessResponse[Item](data=item)
    else:
        return ErrorResponse(message="Item没有找到", code=404)


if __name__ == '__main__':
    import uvicorn
    uvicorn.run(app='main15:app', host='127.0.0.1', port=8000, reload=True)

企业级技巧:
通过 response_model 实现响应数据校验和 OpenAPI 文档自动生成
使用 response_model_exclude_unset=True 过滤未设置的默认值字段
组合模型: response_model=UnionSuccessResponse\[Item, ErrorResponse]

2.列表格式

列表响应是指 API 返回一组数据的响应,通常以 JSON 格式返回一个数组或包含数组的对象。常见场景包括:
分页查询 :返回数据的一部分(如每页 10 条记录),避免一次性加载所有数据。
批量数据返回 :返回符合条件的全部或部分数据(如所有订单、日志)。
列表响应通常包含:
数据列表 :核心数据(例如商品列表、日志记录)。
分页元数据 :如总记录数、当前页码、每页记录数、总页数等。
状态信息 :如请求状态(成功或失败)。
过滤 / 排序信息 :描述当前返回的数据是如何过滤或排序的

python 复制代码
# 响应列表
from typing import List, Optional
from fastapi import FastAPI, Query
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    id: int
    name: str
    price: float
    category: str


class Pagination(BaseModel):
    total: int
    page: int
    page_size: int
    total_pages: int


class ListResponse(BaseModel):
    status: str = "success"
    data: List[Item]
    pagination: Pagination


DB = [Item(id=i, name=f'Apple {i}', price=100.0*i, category='ipad' if i %
           2 == 0 else 'iphone') for i in range(1, 101)]


@app.get('/items1')
async def get_items1():
    return ["Apple1", "Apple2", "Apple3"]


@app.get('/items2')
async def get_items2():
    return DB

'''
这两种写法是等价的
name: Optional[str] = None
name: str | None = None
'''
# 过滤信息
@app.get('/items3')
async def get_items3(category: Optional[str] = Query(None, description="分类")):
    filtered_items = DB
    if category:
        filtered_items = [item for item in DB if item.category == category]
    return filtered_items

#分页
@app.get('/items4')
async def get_items4(
        page: int = Query(1, ge=1, description="页码"),
        page_size: int = Query(10, ge=1, le=100, description="数量"),
        category: Optional[str] = Query(None, description="分类")):

    # 对数据进行过滤
    filtered_items = DB
    if category:
        filtered_items = [item for item in DB if item.category == category]
    # 对数据进行分页
    total = len(filtered_items)
    # 计算总页数快捷算法
    total_pages = (total + page_size - 1) // page_size

    start = (page-1)*page_size
    end = start + page_size

    return filtered_items[start:end]
    '''
    total//page_size = count
    total%page_size = remain  if remain > 0  count+1
    '''

#按需封装分页信息
@app.get('/items5')
async def get_items5(
        page: int = Query(1, ge=1, description="页码"),
        page_size: int = Query(10, ge=1, le=100, description="数量"),
        category: Optional[str] = Query(None, description="分类")):

    # 对数据进行过滤
    filtered_items = DB
    if category:
        filtered_items = [item for item in DB if item.category == category]
    # 对数据进行分页
    total = len(filtered_items)
    # 计算总页数
    total_pages = (total + page_size - 1) // page_size
    start = (page-1)*page_size
    end = start + page_size

    return ListResponse(data=filtered_items[start:end],
                        pagination=Pagination(total=total,
                                              page=page,
                                              page_size=page_size,
                                              total_pages=total_pages))

if __name__ == '__main__':
    import uvicorn
    uvicorn.run(app='main16:app', host='127.0.0.1', port=8000, reload=True)

3.文件格式

文件响应是指 API 在响应客户端请求时,返回一个文件内容的HTTP 响应,通常包含文件的二进制数据或文本数据。
文件响应的关键特点:
内容类型 :通过 Content-Type 头指定文件的 MIME 类型,如 application/pdf ( PDF 文件)、
text/csv ( CSV 文件)、 application/vnd.ms-excel ( Excel 文件)。
文件内容 :响应的 body 包含文件的实际数据,可能是二进制(如 PDF 、图片)或文本(如 CSV 、JSON 文件)。

python 复制代码
# 文件响应

from fastapi import FastAPI
from fastapi.responses import Response, FileResponse, StreamingResponse
app = FastAPI()


@app.get('/download_file')
async def get_custom_file():
    info = b'File Content'
    return Response(
        content=info,
        media_type='text/plain',  # 文本内容
        # attachment 直接下载
        headers={'Content-Disposition': 'attachment;filename="file.txt"'}
    )


@app.get('/download_pdf')
async def get_custom_pdf():
    path = './data/fastapi文档.pdf'
    return FileResponse(
        path=path,
        media_type='application/pdf',  # PDF文件
        headers={'Content-Disposition': 'attachment;filename="file.pdf"'}
    )


def generate_chunks(file_path: str, chunk_size: int = 1024*1024*10):
    with open(file_path, 'rb') as f:
        while chunk := f.read(chunk_size):
            #当函数执行到 yield 时,会暂停执行并返回一个值给调用者与 return 不同,return 会终止函数,而 yield 只是暂停
            yield chunk

# 大文件采用流式响应
@app.get('/download_mp4')
async def get_custom_mp4():
    path = './data/AI变声器.mp4'
    return StreamingResponse(
        content=generate_chunks(path),
        media_type='video/mp4',  # mp4文件
        headers={'Content-Disposition': 'attachment;filename="file.mp4"'}
    )
if __name__ == '__main__':
    import uvicorn
    uvicorn.run('main17:app', host="127.0.0.1", port=8000, reload=True)

4.其他格式

  • 字符串响应
  • 重定向
  • HTML 响应
  • 静态文件
python 复制代码
# 其响应
from fastapi import FastAPI
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles

app = FastAPI()


@app.get('/string1')
async def get_string1():
    return 'Hello World'  # Content-Type: text/plain


@app.get('/string2')
async def get_string2():
    return '<html><h1>Hello</h1></html>'

# response_class=HTMLResponse,返回HTML格式的响应
@app.get('/string3', response_class=HTMLResponse)
async def get_string3():
    return '<html><h1>Hello</h1></html>'


@app.get('/redirect1')
async def get_redirect1():
    return RedirectResponse(url='/string1')


@app.get('/items')
async def get_items(name: str):
    return {'name': name}


@app.get('/redirect2')
async def get_redirect2():
    return RedirectResponse(url='/items?name=Jack')

# 挂载HTML目录
# templates目录下有个index.html文件,访问http://localhost:8000/html/index.html即可查看
app.mount('/html', StaticFiles(directory='templates', html=True))
# static目录下有个style.css文件,访问http://localhost:8000/static/style.css即可查看
app.mount('/static', StaticFiles(directory='static'))

if __name__ == '__main__':
    import uvicorn
    uvicorn.run(app='main18:app', host='127.0.0.1', port=8000, reload=True)
相关推荐
TheBestRucy1 小时前
RAG知识库问答系统落地:从向量检索到上下文增强的全链路实践
人工智能·python·langchain·aigc·交互
TechEdu2026061 小时前
[人工智能]生成式AI开源生态:库、工具与工作流
人工智能·ai
Hui Baby1 小时前
大模型微调完整分类
人工智能·机器学习
吐了啊取名字太难1 小时前
美颜系统AI修图本地跑并支持Mac、win、安卓、iOS不卡顿
android·人工智能·windows·数码相机·mac·ai编程
coder_zrx1 小时前
大语言模型训练范式:从 GPT 到 Llama 的 RLHF 演进
人工智能·深度学习
TsingtaoAI2 小时前
脑控机器人项目交付|用意念指挥机器人,情绪交互与抓取功能全面实现
人工智能·ai·机器人·具身智能
春日见2 小时前
算法与数据结构----哈希表
数据结构·人工智能·算法·机器学习·自动驾驶·哈希算法·散列表