用 FastAPI 从零构建 REST API 与 URL 短链接服务

2026-07-22 33 预计阅读时间: 1 分钟
来源: realpython.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

FastAPI 的价值不只在于“快”:它把 Python 类型标注、请求校验、异步处理和 OpenAPI 文档连接在一起,让开发者可以用较少的样板代码交付结构清晰的 API。沿着 REST 接口、Jinja2 页面渲染和 URL 短链接项目逐层推进,可以覆盖一个小型 Web 服务从输入校验到页面交互的完整路径。

类型标注同时定义接口契约

在 FastAPI 中,路径参数、查询参数和请求体模型都可以通过 Python 类型声明。框架据此完成数据解析与校验,并生成交互式 API 文档。

例如,一个 URL 创建接口可以用 Pydantic 模型约束输入:

from pydantic import BaseModel, HttpUrl

class ShortenRequest(BaseModel):
    url: HttpUrl

HttpUrl 不只是编辑器提示。当客户端提交无法解析的 URL 时,FastAPI 会在业务函数执行前返回校验错误。这能把“检查字段是否存在、判断格式是否合法”从路由代码中移走,让处理函数专注于业务行为。

服务启动后,FastAPI 通常会暴露两套自动生成的接口文档:

  • /docs:适合直接调用和调试接口的 Swagger UI。
  • /redoc:适合阅读接口结构的 ReDoc 页面。
  • /openapi.json:供客户端生成器和测试工具消费的 OpenAPI 描述。

一个应用可以同时提供 API 和网页

URL 短链接服务至少涉及三类请求:创建短链接、查看创建页面,以及根据短码跳转到原地址。它们可以放在同一个 FastAPI 应用中:REST API 返回 JSON,页面路由通过 Jinja2 返回 HTML,跳转路由则返回重定向响应。

下面是一套可以这样实践的最小项目。示例为了便于运行,使用内存字典保存数据;进程重启后记录会消失,因此不应直接用于生产环境。

目录结构如下:

fastapi-shortener/
├── app.py
├── requirements.txt
└── templates/
    └── index.html

requirements.txt

fastapi
uvicorn[standard]
jinja2
python-multipart

app.py

import secrets
import string

from fastapi import FastAPI, Form, HTTPException, Request, status
from fastapi.responses import RedirectResponse
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel, HttpUrl

app = FastAPI(title='FastAPI URL Shortener')
templates = Jinja2Templates(directory='templates')
links: dict[str, str] = {}
ALPHABET = string.ascii_letters + string.digits


class ShortenRequest(BaseModel):
    url: HttpUrl


class ShortenResponse(BaseModel):
    code: str
    short_url: str
    target_url: str


def create_code(length: int = 7) -> str:
    while True:
        code = ''.join(secrets.choice(ALPHABET) for _ in range(length))
        if code not in links:
            return code


def store_url(url: str) -> str:
    code = create_code()
    links[code] = url
    return code


@app.get('/')
def home(request: Request):
    return templates.TemplateResponse(
        request=request,
        name='index.html',
        context={'result': None},
    )


@app.post('/shorten', response_model=ShortenResponse, status_code=status.HTTP_201_CREATED)
def shorten(payload: ShortenRequest, request: Request):
    target_url = str(payload.url)
    code = store_url(target_url)
    return ShortenResponse(
        code=code,
        short_url=str(request.url_for('redirect_to_target', code=code)),
        target_url=target_url,
    )


@app.post('/submit')
def submit(request: Request, url: HttpUrl = Form(...)):
    target_url = str(url)
    code = store_url(target_url)
    return templates.TemplateResponse(
        request=request,
        name='index.html',
        context={
            'result': str(request.url_for('redirect_to_target', code=code)),
        },
    )


@app.get('/{code}', name='redirect_to_target')
def redirect_to_target(code: str):
    target_url = links.get(code)
    if target_url is None:
        raise HTTPException(status_code=404, detail='Short URL not found')
    return RedirectResponse(target_url, status_code=status.HTTP_307_TEMPORARY_REDIRECT)

templates/index.html

<!doctype html>
<html lang='zh-CN'>
<head>
  <meta charset='utf-8'>
  <meta name='viewport' content='width=device-width, initial-scale=1'>
  <title>URL Shortener</title>
</head>
<body>
  <main>
    <h1>创建短链接</h1>
    <form action='/submit' method='post'>
      <label for='url'>原始 URL</label>
      <input id='url' name='url' type='url' required placeholder='https://example.com/article'>
      <button type='submit'>生成</button>
    </form>
    {% if result %}
      <p>短链接:<a href='{{ result }}'>{{ result }}</a></p>
    {% endif %}
  </main>
</body>
</html>

在项目目录中执行:

python -m venv .venv
source .venv/bin/activate
# Windows PowerShell 使用:.venv\Scripts\Activate.ps1
pip install -r requirements.txt
uvicorn app:app --reload

浏览器访问 http://127.0.0.1:8000/ 可以使用表单,访问 http://127.0.0.1:8000/docs 可以调试 REST API。也可以直接执行:

curl -X POST http://127.0.0.1:8000/shorten \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://example.com/a/long/path"}'

从演示项目走向可部署服务

最小实现展示了 FastAPI 的核心组合方式,但生产化需要补齐几个边界。

持久化与并发。 内存字典无法跨进程共享。使用多个 Uvicorn worker 时,请求可能访问不同的数据副本。可以将链接记录放入 PostgreSQL,并为短码建立唯一索引;如果业务更看重低延迟,也可以使用 Redis,但需要明确持久化和淘汰策略。

短码冲突。 示例通过随机生成和字典检查减少冲突,但数据库写入仍可能出现并发竞争。最终约束应放在数据库唯一索引中,应用捕获冲突后重新生成短码。

跳转安全。 公开短链接服务容易被用于钓鱼、恶意下载或绕过域名过滤。上线前应考虑域名黑名单、举报和封禁机制、访问频率限制,以及后台审计能力。若允许用户指定短码,还要保留系统路径,例如 docsopenapi.json 和管理端前缀。

HTTP 状态码。 示例使用 307 Temporary Redirect,便于以后修改目标地址。如果短链接目标永久固定,可以评估 301308,但浏览器和中间缓存可能长期记住永久重定向,变更和撤销会更困难。

建议的学习与落地顺序

可以先用一个资源的 CRUD API 熟悉路径参数、查询参数、Pydantic 模型和异常处理,再加入 Jinja2 页面,理解同一服务如何同时处理 JSON 与 HTML。随后完成短链接项目,并逐步替换内存存储、增加测试和部署配置。

准备上线时,至少检查以下项目:

  • 请求模型是否限制 URL 格式、字段长度和可选值。
  • 短码是否有唯一约束,冲突后是否能重试。
  • 未知短码是否稳定返回 404
  • 是否设置访问日志、速率限制和滥用处置流程。
  • 反向代理是否正确传递主机名和协议,否则生成的短链接可能使用内部地址。
  • 数据库迁移、备份和恢复流程是否经过验证。

FastAPI 可以快速搭起接口,但“开发速度快”不等于可以忽略数据一致性、安全和运维。把类型契约、自动文档和简洁路由作为起点,再用数据库约束、测试与监控守住边界,才是这类项目真正可复用的工程价值。


相关推荐