前言

作为一名前端仔,一直想自己写点后端接口来进行一些功能的测试。结合前端 + 后端的视角,完整的走一遍整体的技术实现流程。既能巩固知识,又能融会贯通,达到真正搞懂的目的

比如 Token 鉴权,图片上传,大文件上传,Websocket 联调等。这些功能虽然做过很多次,可始终是浮于表面,只知道前端做了什么,至于传递参数调用接口之后的事情是一概不知。感觉前端只是一个切图仔 + API 调用侠,很难真正理解深层次的技术原理。所以我打算学点后端的东西,打通整个流程,真正的去理解它

我选择 Python 作为后端语言,UV 作为 Python 包和环境管理工具。想要学习 UV 入门教程请直接查阅我写的另一篇文章,本文不再赘述 UV 如何安装 Python 以及创建项目和虚拟环境等基础操作。

《Python 入门(一)- 用 UV 管理 Python》:juejin.cn/post/760585…

快来跟我一起学习吧!

1. FastAPI 介绍

1.1 FastAPI 是什么?

FastAPI 官网:fastapi.tiangolo.com/zh/

pypi 上的地址:pypi.org/project/fas…

简介:FastAPI 是一个用于构建 API 的现代、快速(高性能)的 Web 框架,使用标准的 Python 语法,不需要学习新的语法、特定库的方法或类等等

FastAPI 使用了一种用于构建 Python Web 框架和服务器的标准,称为 ASGI。FastAPI 本质上是一个 ASGI Web 框架

1.2 为什么选 FastAPI ?

Python 主流的 web 框架有 flask,Django 和 FastAPI,经过一番调研,最后我选择使用 FastAPI

有如下原因:

  • flask 太毛坯,Django 太重,并且两者的官网文档都太丑陋..(个人认为)

  • 官网文档简洁美观

  • 快速的开发 api 接口

  • 自动生成交互式 API 文档(Swagger UI 和 ReDoc)

2. FastAPI 安装使用和运行

2.1 安装 FastAPI

如何用 UV 创建 Python 项目就不演示了,创建项目后也不必特意创建和激活虚拟环境,UV 会在合适的时机智能的自动创建使用虚拟环境。关于 UV 的知识我已经在这篇文章内写的很清楚了: 《Python 入门(一)- 用 UV 管理 Python》:juejin.cn/post/760585…

在 Python 项目中安装 FastAPI

uv add "fastapi[standard]"

[standard] 在 Python 里被称为 “Extra”(额外选项),它告诉 UV 除了安装 FastAPI 核心库外,还要把标准可选依赖一起安装

UV 解析 fastapi 的 Extra (standard) 声明,发现其包含了 uvicorn、pydantic、httptools 等子依赖。详见官网介绍:fastapi.tiangolo.com/zh/#depende…

UV 会自动下载并利用硬链接将它们安装到项目的 .venv 中

并同步更新 pyproject.toml(声明依赖)

和 uv.lock(锁定精确版本)。

2.2 使用 FastAPI

创建一个名为 main.py 的文件,其中包含 FastAPI 的导入以及创建 get 请求接口,完整内容如下:

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def read_root():
    return {"Hello": "World"}


@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
    return {"item_id": item_id, "q": q}

2.3 运行 FastAPI

uv run fastapi dev main.py

  • uv run:自动定为.venv 环境(自动寻找当前目录或上级目录的 pyproject.toml 文件所在目录中的 .venv),用这个虚拟环境运行命令

  • fastapi dev:FastAPI 官方 CLI,内部启动 uvicorn 服务器,默认 8000 端口,并自动开启 Reload(热部署) 模式(代码改了服务器自动重启)。默认运行在 ,并自动生成交互式文档

2.4 交互式 API 文档

此时 fastapi 会自动生成两个交互式 API 文档,能在浏览器中直接调用和测试你的 API

Swagger UI:

点击 Try it out

点击 Execute

成功获取到接口返回内容

修改接口返回内容,将 World 改为闲云一鹤

再次点击 Execute,可以看到接口返回值同步更新了,说明热部署功能已开启,不需要重启服务就能自动更新内容(注意,如果更改了接口字段或者新增接口需要刷新文档页面)

ReDoc:

这是访问 redoc 文档

3. vscode 配置

3.1 VSCode 安装 Python 扩展,检查语法错误

Python 是一个对于格式要求极其严格的语言,如果缩进或者一些格式错误,会报错导致无法运行

这里我故意在 app = FastAPI() 前面加上几个空格,产生了缩进错误,运行失败

在 VS Code 里安装 Python 扩展,它会自动帮你检查语法错误

不符合规范以及语法错误的地方,会标红提示

3.2 安装 Ruff + VSCode 配置 实现保存代码自动格式化

我们可以将配置再进一步优化,保存后让编辑器帮你自动格式化代码,就像前端的 eslint 和 prettier

强烈推荐 Ruff,它是一个用 rust 编写的,速度极快(比 Flake8 和 Black 快 10-100 倍)的 python 代码检查和格式化工具;并且 Ruff 和 uv 都是同一家公司(Astral)开发的

3.2.1 第一步:项目集成 (The Tooling)

在你的项目终端运行,这会把 Ruff 记录在你的开发依赖中:

uv add --dev ruff

安装后会在 pyproject.toml 文件中添加 ruff 依赖

并且在 uv.lock 文件中锁定了 ruff 版本

3.2.2 第二步:VSCode 安装 Ruff 扩展

在 VS Code 扩展市场搜索并安装 Ruff (作者是 Astral Software)。

3.2.3 第三步:配置 VS Code 用户设置 (User Settings)

{
  // 设置 Python 文件的默认格式化程序为 Ruff
  "[python]": {
    "editor.defaultFormatter": "charliermarsh.ruff",
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
      // 自动修复可修复的错误(比如多余的空格、没用的 import)
      "source.fixAll.ruff": "explicit",
      // 自动对 import 语句进行排序
      "source.organizeImports.ruff": "explicit"
    }
  },
  // 让 Ruff 插件优先使用你项目里 uv add 安装的那个版本
  "ruff.importStrategy": "fromEnvironment"
}

建议用户区和工作区都配置,这样无论是你个人在电脑创建多个 Python 项目,还是团队合作让别人 git clone 你的项目时,都能确保拥有同样的格式化配置!

tip: 记得提醒同事装插件:配置是有了,但如果同事没装 VS Code 的 Ruff 插件,配置是不会生效的

下面测试保存代码自动格式化的配置是否生效

将下面的代码复制到 main.py 文件中,然后保存

import time, datetime

def hello():
  x=1;
  y=2;
  print('hello world!',x + y)

这是格式化后的代码

def hello():
  x = 1
  y = 2
  print("hello world!", x + y)

自动完成了下面的操作:

  1. 自动删除已导入却未使用的库:time, datetime

  2. 缩进从两个空格变成了四个空格

  3. x和y赋值,等号前后去掉了空格,并且后面去掉了多余的分号

  4. 'hello world!' 单引号也变成了双引号,并且逗号后面加上了空格

3.3 忽略 __pycache__ 文件夹

前面运行 uv run fastapi dev main.py 后项目根目录下新增了一个 __pycache__ 文件夹,并且里面新增了一个 main.cpython-313.pyc 文件

这是 Python 自己生成的“编译缓存”。运行 main.py 时,它会把代码编译成更快执行的字节码(.pyc 文件),然后存到 __pycache__ 文件夹里。

下次运行如果你没改代码,Python 跳过编译过程直接读这个缓存文件,启动速度会更快一点

千万不要将它们提交到仓库,因为这是临时文件、每台电脑都会自己生成,提交上去完全没用

在项目根目录创建一个 .gitignore 文件,写上 __pycache__

__pycache__ 文件可以随便删除,没有任何坏影响,只是下次启动时 Python 需要重新编译一下,启动时间会稍微长那么零点几秒

4. 路由

4.1 定义

路由是 url 地址和处理函数之间的映射关系,它决定了当用户访问某个网址时,服务器应该执行哪段代码来返回结果

人话就是路由 = 装饰器 函数

用装饰器装饰函数,当请求路径的时候,执行路由对应的函数

4.2 完整代码

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def read_root():
    return {"Hello": "掘金-闲云一鹤"}


@app.get("/song")
async def get_song():
    return {"门前大桥下": "游过一群鸭"}

4.3 代码讲解

上面代码中 app 是 FastApi 实例

get 是请求方法

/ 是请求路径

@app.get("/") 组成了装饰器

read_root() 是函数

return {"Hello": "掘金-闲云一鹤"} 是函数的响应结果

访问路由查看效果

输入不同的路由,返回不同的接口

5. 参数

5.1 参数介绍

  • 什么是参数?

    参数就是客户端发送请求时附带的额外信息和指令

    参数的作用是让同一个接口能根据不同的输入参数,返回不同的输出结果,实现接口的动态交互

  • 参数类型

    参数分为: 路径参数,查询参数,请求体参数

5.2 路径参数

5.2.1 定义

路径参数出现在 Url 路径的后面,例如:/user/{name}

路径参数用于指向唯一的,特定的资源,方法为 GET

完整代码:

from fastapi import FastAPI

app = FastAPI()


@app.get("/users/{id}")
async def get_user(id: int, name: str | None = None):
    return {"id": id, "name": name}

Request URL 示例:

http://127.0.0.1:8000/users/6?name=xyyh

Response body 示例:

{
  "id": 6,
  "name": "xyyh"
}

5.2.2 类型注解

类型注解用于给参数添加额外的信息和校验,比如限制 ID 的范围值。

如果只限制参数的类型,就用 Python 的原生注解,比如 id: int 或者 name: str

如果有额外的限制,比如范围值和长度限制等,就需要用到 Path 注解,Path 函数需要先导入再使用

完整代码:

from fastapi import FastAPI, Path

app = FastAPI()


@app.get("/users/{id}")
async def get_user(
    id: int = Path(..., gt=0, lt=101, description="用户ID,取值范围1-100"),
    name: str | None = None,
):
    return {"msg": f"这是一个用户接口, id为{id}, 姓名为{name}"}

其中 ... 表示必填,gt 表示值必须大于此值,lt 表示值必须小于此值

具体以及更多用法请查阅 FastAPI 官方文档:fastapi.tiangolo.com/zh/referenc…

Request URL 示例:

http://127.0.0.1:8000/users/6?name=xyyh

Response body 示例:

{
  "msg": "这是一个用户接口, id为6, 姓名为xyyh"
}

我故意将 ID 输入为不符合范围内的值,它提醒我输入值应大于0

输入正常范围的 ID 值,接口正常返回

5.3 查询参数

5.3.1 定义

查询参数出现在 Url 路径的后面,紧跟一个问号,然后通过 key=value 形式展示参数,如果有多个参数中间用 & 符号进行拼接,例如:user?name=张三&age=18

查询参数用于对数据进行过滤,排序,分页等操作,方法为 GET

完整代码:

from fastapi import FastAPI

app = FastAPI()


# 查询用户列表 page: 当前页, size:每页条数
@app.get("/users/user_list")
async def get_user_list(page: int = 1, size: int = 10):
    return {"msg": f"这是查询用户列表的接口, 当前为{page}页, 每页条数为{size}"}

= 号后面直接赋值,表示默认值

Request URL 示例:

http://127.0.0.1:8000/users/user_list?page=1&size=10

Response body 示例:

{
  "msg": "这是查询用户列表的接口, 当前为1页, 每页条数为10"
}

5.3.2 类型注解

与路径参数相同,查询参数也支持 python 原生注解

与路径参数不同(路径参数使用 Path 注解),查询参数需要使用 Query 来做类型注解,同样需要先引入再使用

完整代码:

from fastapi import FastAPI, Query

app = FastAPI()


# 查询用户列表 page: 当前页, size:每页条数
@app.get("/users/user_list")
async def get_user_list(
    page: int = Query(1, ge=1, description="当前页码,必须大于等于1"),
    size: int = Query(10, ge=1, le=100, description="每页条数必须在1-100之间"),
):
    return {"msg": f"这是查询用户列表的接口, 当前为{page}页, 每页条数为{size}"}

Request URL 示例:

http://127.0.0.1:8000/users/user_list?page=1&size=10

Response body 示例:

{
  "msg": "这是查询用户列表的接口, 当前为1页, 每页条数为10"
}

将页码和每页条数输入不符合范围内的值,也正常收到了提示

输入正常范围的值,接口正常返回

5.4 请求体参数

5.4.1 定义

请求体参数出现在 http 请求的消息体(body)中,

请求体参数用于创建,更新资源,携带大量数据,如 json,方法为 POSTPUT

在 http 协议中,一个完整的请求由三部分组成:

  1. 请求行:包含方法,url,协议版本
  2. 请求头:元数据信息(content-type,authorization等)
  3. 请求体:实际要发送的数据内容

完整代码:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


# 用户注册
class User(BaseModel):
    username: str
    password: str


@app.post("/users/register")
async def register_user(user: User):
    # 在这里可以添加用户注册的逻辑,例如将用户信息保存到数据库
    return {"msg": f"用户 {user.username} 注册成功!"}

Request URL 示例:

http://127.0.0.1:8000/users/register

Response body 示例:

{
  "msg": "用户 掘金-闲云一鹤 注册成功!"
}

5.4.2 类型注解

请求体参数可以用 python 原生注解或者 Field 注解

要先导入 Field 函数,用法跟 Path 和 Query 差不多

完整代码:

from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()


# 用户注册
class User(BaseModel):
    username: str = Field(default="掘金-闲云一鹤", description="用户名")
    password: str = Field(
        ..., min_length=6, max_length=20, description="密码,长度必须在6到20之间"
    )


@app.post("/users/register")
async def register_user(user: User):
    # 在这里可以添加用户注册的逻辑,例如将用户信息保存到数据库
    return {"msg": f"用户 {user.username} 注册成功!"}

密码长度必须在6到20之间,我输入三位数

提示我长度不够

输入六位数密码,正常提交接口

5. 响应

默认情况下,FastAPI 会将代码中的 Python 对象(字典,列表,pydantic 模型等),自动转换为 json 响应格式(JSONResponse)

FastAPI 内置的响应类型有:JSONResponse(json格式),HTMLResponse(HTML 内容),PlainTextResponse(纯文本),FileResponse(文件下载),StreamingResponse(流式响应),RedirectResponse(重定向)

如果想要接口返回其他的响应格式,需要手动设置,下面将详细介绍如何使用不同的响应

5.1 HTMLResponse 响应

HTMLResponse 用于返回 HTML 内容

HTMLResponse 响应的 content-type 值为:text/html; charset=utf-8

完整代码:

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.get("/", response_class=HTMLResponse)
async def get_html():
    return """
    <html>
        <head>
            <title>闲云一鹤</title>
        </head>
        <body>
            <h1>这是一个简单的HTML响应</h1>
            <p>我是掘金-闲云一鹤</p>
            <p>欢迎访问我的掘金主页:>
        </body>
    </html>
    """

浏览器访问可查看渲染效果:

5.2 FileResponse 响应

FileResponse 用于返回文件下载

FileResponse 响应的 content-type 值为:content-type: image/jpeg

完整代码:

from fastapi import FastAPI
from fastapi.responses import FileResponse

app = FastAPI()


@app.get("/file")
async def get_file():
    file_path = "./img/壁纸.jpg"
    return FileResponse(
        file_path,
    )

浏览器访问可查看图片:

5.3 自定义响应数据的格式

可以理解为提前定义了接口的数据字段以及类型,如果接口返回的数据字段以及类型不符合提前定义的值,就会报错。前端仔表示感觉很像前端的 Typescript

完整代码:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None


@app.post("/items/")
async def create_item(item: Item):
    return item

5.4 处理异常响应

FastAPI 通过 HTTPException 来处理异常

完整代码:

from fastapi import FastAPI, HTTPException

app = FastAPI()


@app.get("/users/{id}")
async def get_user(id: int):

    # 这里模拟一个用户ID列表,实际应用中可以从数据库中查询
    id_list = [1, 2, 3, 4, 5]
    if id not in id_list:
        raise HTTPException(status_code=404, detail="用户不存在")
    return {
        "id": id,
    }

如果输入的 ID 值不在 id_list 中,就会抛出错误:

{ "detail": "用户不存在" }

6. 中间件

6.1 定义

  • 中间件是一个在每次请求进入 FastAPI 应用时都会被执行的函数

  • 中间件的作用是为每个请求添加统一的处理逻辑(记录日志,身份认证,跨域,设置响应头,性能坚控等)

  • 它在请求到达实际的路径操作(路由处理函数)之前运行,并且在响应返回给客户端之前再运行一次

  • 多个中间件的执行顺序是,从下到上

6.2 使用

完整代码:

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def read_root():
    return {"Hello": "World"}


@app.middleware("http")
async def middleware1(request, call_next):
    print("中间件-1: 开始处理请求.")
    response = await call_next(request)
    print("中间件-1: 请求处理完成.")
    return response


@app.middleware("http")
async def middleware2(request, call_next):
    print("中间件-2: 开始处理请求.")
    response = await call_next(request)
    print("中间件-2: 请求处理完成.")
    return response

调用接口后,返回 json 数据

{
  "Hello": "World"
}

并且 vscode 终端打印结果:

中间件-2: 开始处理请求.
中间件-1: 开始处理请求.
中间件-1: 请求处理完成.
中间件-2: 请求处理完成.

7. 依赖注入

7.1 定义

  • 作用:使用依赖注入系统来共享通用逻辑,减少代码重复

  • 依赖项:可重用的组件(函数/类),负责提供某种功能或数据

  • 注入:FastAPI 自动帮你调用依赖项,并将结果“注入”到路径操作函数中

  • 使用场景:处理请求参数,共享业务逻辑,共享数据库连接,安全和认证

7.2 使用

完整代码:

from fastapi import Depends, FastAPI, Query

app = FastAPI()


# 依赖项:公共分页
async def common_page_and_size(
    page: int = Query(1, ge=1, description="当前页码,必须大于等于1"),
    size: int = Query(10, ge=1, le=100, description="每页条数必须在1-100之间"),
):
    return {"msg": f"这是查询用户列表的接口, 当前为{page}页, 每页条数为{size}"}


@app.get("/users/user_list")
async def get_user_list(pagination: dict = Depends(common_page_and_size)):
    return pagination

8. ORM

8.1 定义

ORM(Object-RelationalMapping,对象关系映射) 是一种编程技术,用于在面向对象编程语言和关系型数据库之间建立映射。它允许开发者通过操作对象的方式与数据库进行交互 ,而无需直接编写复杂的 SQL 语句

ORM 工具有:SQLALchemy ORM,Django ORM,Tortoise ORM

具体的操作以及如何连接数据库,放在下一篇文章来写吧,如果有人看的话

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com