FastAPI依赖注入

1. 依赖注入(Dependency Injection)

1.1 什么是依赖注入

依赖注入(Dependency Injection, DI) 是一种设计模式,用于实现组件之间的解耦。它通过将依赖项(如数据库连接、配置、认证等)注入到需要它们的组件中,而不是组件自行创建或管理这些依赖项,从而提高代码的模块化、可测试性和可维护性。

1.2 FastAPI 中的依赖注入

FastAPI 内置了强大的依赖注入系统,允许您轻松地声明和管理依赖。通过 Depends,您可以将共享的功能(如数据库连接、用户认证)注入到路由处理函数中。

1.3 基本使用示例

示例:简单的依赖注入

from fastapi import FastAPI, Depends

app = FastAPI()

# 定义一个依赖函数
def get_query(q: str = None):
    return q

# 在路由中使用依赖
@app.get("/items/")
async def read_items(q: str = Depends(get_query)):
    return {"q": q}

解释:

  • get_query 是一个依赖函数,它可以接收请求参数并返回处理后的结果。
  • read_items 路由中,通过 Depends(get_query) 注入了 q 参数。

访问 /items/?q=fastapi 将返回 {"q": "fastapi"}

1.4 共享依赖

当多个路由需要相同的依赖时,可以将其封装在一个函数中,并在多个路由中使用 Depends 注入。

示例:共享依赖

from fastapi import FastAPI, Depends

app = FastAPI()

def common_parameters(q: str = None, skip: int = 0, limit: int = 10):
    return {"q": q, "skip": skip, "limit": limit}

@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
    return commons

@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
    return commons

解释:

  • common_parameters 定义了一组通用的查询参数。
  • read_itemsread_users 路由都依赖于 common_parameters,实现参数共享。

访问 /items/?q=fastapi&skip=5&limit=15/users/?q=admin&skip=2&limit=5 将分别返回相应的参数。

1.5 依赖的嵌套

依赖可以嵌套,即一个依赖函数可以依赖于另一个依赖函数。

示例:依赖的嵌套

from fastapi import FastAPI, Depends

app = FastAPI()

def get_db():
    db = "database_connection"
    try:
        yield db
    finally:
        db = None  # 关闭数据库连接

def get_current_user(db = Depends(get_db)):
    return {"user": "current_user", "db": db}

@app.get("/users/me")
async def read_current_user(current_user: dict = Depends(get_current_user)):
    return current_user

解释:

  • get_db 是一个生成器函数,模拟数据库连接的获取和关闭。
  • get_current_user 依赖于 get_db,获取当前用户的信息。
  • read_current_user 路由依赖于 get_current_user,最终返回当前用户的信息。

访问 /users/me 将返回 {"user": "current_user", "db": "database_connection"}

1.6 类的依赖注入

可以使用类来组织依赖,使依赖更加结构化和可扩展。

示例:类的依赖注入

from fastapi import FastAPI, Depends

app = FastAPI()

class CommonQueryParams:
    def __init__(self, q: str = None, skip: int = 0, limit: int = 10):
        self.q = q
        self.skip = skip
        self.limit = limit

@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends()):
    return {"q": commons.q, "skip": commons.skip, "limit": commons.limit}

@app.get("/users/")
async def read_users(commons: CommonQueryParams = Depends()):
    return {"q": commons.q, "skip": commons.skip, "limit": commons.limit}

解释:

  • CommonQueryParams 类封装了通用的查询参数。
  • 在路由中,通过 Depends() 自动实例化并注入 CommonQueryParams 实例。

访问 /items/?q=fastapi&skip=5&limit=15/users/?q=admin&skip=2&limit=5 将分别返回相应的参数。

1.7 实践示例:数据库连接

让我们通过一个更实际的示例,展示如何在 FastAPI 中使用依赖注入管理数据库连接。假设我们使用 SQLAlchemy 作为 ORM。

步骤:

  1. 安装依赖

    pip install fastapi sqlalchemy databases pydantic uvicorn
    
  2. 项目结构

    project/
    ├── main.py
    ├── models.py
    ├── schemas.py
    └── database.py
    
  3. 定义数据库配置和依赖

    database.py

    from sqlalchemy import create_engine, MetaData
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm import sessionmaker
    from contextlib import contextmanager
    
    DATABASE_URL = "sqlite:///./test.db"  # 使用 SQLite 数据库
    
    engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
    SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
    Base = declarative_base()
    metadata = MetaData()
    
    @contextmanager
    def get_db():
        db = SessionLocal()
        try:
            yield db
        finally:
            db.close()
    
  4. 定义数据模型

    models.py

    from sqlalchemy import Column, Integer, String
    from database import Base
    
    class User(Base):
        __tablename__ = "users"
    
        id = Column(Integer, primary_key=True, index=True)
        name = Column(String, index=True)
        email = Column(String, unique=True, index=True)
    
  5. 定义 Pydantic 模型

    schemas.py

    from pydantic import BaseModel
    
    class UserBase(BaseModel):
        name: str
        email: str
    
    class UserCreate(UserBase):
        pass
    
    class UserResponse(UserBase):
        id: int
    
        class Config:
            orm_mode = True
    
  6. 创建 FastAPI 应用并定义路由

    main.py

    from fastapi import FastAPI, Depends, HTTPException, status
    from sqlalchemy.orm import Session
    from typing import List
    
    from database import engine, Base, get_db
    from models import User
    from schemas import UserCreate, UserResponse
    
    app = FastAPI()
    
    # 创建数据库表
    Base.metadata.create_all(bind=engine)
    
    @app.post("/users/", response_model=UserResponse)
    async def create_user(user: UserCreate, db: Session = Depends(get_db)):
        # 检查用户是否已存在
        db_user = db.query(User).filter(User.email == user.email).first()
        if db_user:
            raise HTTPException(status_code=400, detail="Email already registered")
        new_user = User(name=user.name, email=user.email)
        db.add(new_user)
        db.commit()
        db.refresh(new_user)
        return new_user
    
    @app.get("/users/", response_model=List[UserResponse])
    async def read_users(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
        users = db.query(User).offset(skip).limit(limit).all()
        return users
    
    @app.get("/users/{user_id}", response_model=UserResponse)
    async def read_user(user_id: int, db: Session = Depends(get_db)):
        user = db.query(User).filter(User.id == user_id).first()
        if user is None:
            raise HTTPException(status_code=404, detail="User not found")
        return user
    
  7. 启动应用

    在终端中运行:

    uvicorn main:app --reload
    
  8. 测试 API

    • 创建用户

      curl -X POST "http://127.0.0.1:8000/users/" -H "Content-Type: application/json" -d '{"name": "Alice", "email": "alice@example.com"}'
      

      响应:

      {
          "id": 1,
          "name": "Alice",
          "email": "alice@example.com"
      }
      
    • 获取用户列表

      curl "http://127.0.0.1:8000/users/"
      

      响应:

      [
          {
              "id": 1,
              "name": "Alice",
              "email": "alice@example.com"
          }
      ]
      
    • 获取单个用户

      curl "http://127.0.0.1:8000/users/1"
      

      响应:

      {
          "id": 1,
          "name": "Alice",
          "email": "alice@example.com"
      }
      

学习建议

  • 理解 Depends 的工作原理:深入理解 FastAPI 的依赖注入系统,了解如何声明和管理依赖。
  • 实践共享和嵌套依赖:在不同的项目中实践共享和嵌套依赖,提高代码的复用性和模块化。
  • 依赖的生命周期管理:了解依赖的生命周期,特别是在使用生成器函数(如数据库连接)时的资源管理。

2. 回调机制(Callback Mechanism)

2.1 什么是回调机制

回调机制 通常用于异步任务的通知和状态更新。当一个任务在后台执行时,完成后需要通知客户端或触发某些操作。回调机制通过提供一个 URL(回调 URL),让服务器在任务完成后发送请求到该 URL,以通知任务的结果。

2.2 使用 FastAPI 的 BackgroundTasks

FastAPI 提供了 BackgroundTasks,允许您在响应返回后在后台执行任务。这适用于简单的异步任务,但不适合需要复杂任务队列或分布式处理的场景。

示例:使用 BackgroundTasks 实现回调

from fastapi import FastAPI, BackgroundTasks, HTTPException
from pydantic import BaseModel
import requests
import time

app = FastAPI()

class TaskRequest(BaseModel):
    data: str
    callback_url: str

def process_task(data: str, callback_url: str):
    try:
        # 模拟任务处理
        time.sleep(5)  # 假设任务需要5秒
        result = f"Processed data: {data}"
        # 发送回调请求
        payload = {"result": result}
        response = requests.post(callback_url, json=payload, timeout=5)
        response.raise_for_status()
    except Exception as e:
        print(f"Failed to send callback: {e}")

@app.post("/start-task/")
async def start_task(task: TaskRequest, background_tasks: BackgroundTasks):
    background_tasks.add_task(process_task, task.data, task.callback_url)
    return {"message": "Task started"}

解释:

  • TaskRequest 定义了请求体,包括 datacallback_url
  • process_task 是在后台执行的任务函数,处理数据并发送回调请求。
  • start_task 路由中,通过 BackgroundTasks 添加 process_task 任务。

注意:

  • BackgroundTasks 适用于简单的后台任务,但无法处理复杂的任务队列或分布式任务处理。
  • 若需要更强大的异步任务处理,建议使用 Celery。

2.3 使用 Celery 实现异步任务和回调

Celery 是一个强大的分布式任务队列,适用于需要复杂任务调度、分布式处理和可靠性的场景。

步骤:

  1. 安装 Celery 和 Redis

    pip install celery redis
    
  2. 项目结构

    project/
    ├── main.py
    ├── celery_app.py
    ├── tasks.py
    ├── schemas.py
    └── dependencies.py
    
  3. 配置 Celery

    celery_app.py

    from celery import Celery
    
    celery = Celery(
        "tasks",
        broker="redis://localhost:6379/0",
        backend="redis://localhost:6379/0"
    )
    
  4. 定义 Celery 任务

    tasks.py

    from celery_app import celery
    import requests
    import time
    
    @celery.task
    def process_task(data: str, callback_url: str):
        try:
            # 模拟任务处理
            time.sleep(10)  # 假设任务需要10秒
            result = f"Processed data: {data}"
            # 发送回调请求
            payload = {"result": result}
            response = requests.post(callback_url, json=payload, timeout=5)
            response.raise_for_status()
        except Exception as e:
            print(f"Failed to send callback: {e}")
            raise e
    
  5. 定义 FastAPI 路由

    main.py

    from fastapi import FastAPI, HTTPException
    from pydantic import BaseModel
    from tasks import process_task
    from typing import Dict
    
    app = FastAPI()
    
    class TaskRequest(BaseModel):
        data: str
        callback_url: str
    
    @app.post("/start-task/")
    async def start_task(task: TaskRequest):
        """
        启动一个异步任务,并在任务完成后通过回调 URL 通知客户端。
        """
        task_instance = process_task.delay(task.data, task.callback_url)
        return {"task_id": task_instance.id, "message": "Task started"}
    
    @app.post("/task-callback/")
    async def task_callback(payload: Dict[str, str]):
        """
        处理来自异步任务的回调请求。
        """
        result = payload.get("result")
        if not result:
            raise HTTPException(status_code=400, detail="Missing result")
        # 处理回调结果,例如更新数据库状态
        print(f"Received callback with result: {result}")
        return {"message": "Callback received"}
    
  6. 启动 Celery Worker 和 FastAPI

    • 启动 Celery Worker

      在终端中运行:

      celery -A tasks worker --loglevel=info
      
    • 启动 FastAPI 应用

      在另一个终端中运行:

      uvicorn main:app --reload
      
  7. 测试流程

    • 启动任务

      发送一个 POST 请求到 /start-task/,包含 datacallback_url

      示例请求:

      curl -X POST "http://127.0.0.1:8000/start-task/" \
      -H "Content-Type: application/json" \
      -d '{"data": "Sample Data", "callback_url": "http://127.0.0.1:8000/task-callback/"}'
      

      响应:

      {
          "task_id": "some_unique_id",
          "message": "Task started"
      }
      
    • 任务处理与回调

      • Celery Worker 接收到任务,开始处理。
      • 处理完成后,Worker 通过 callback_url 发送 POST 请求,携带结果。
      • FastAPI 应用接收到回调请求,处理结果。

      回调请求示例:

      {
          "result": "Processed data: Sample Data"
      }
      

      回调响应:

      {
          "message": "Callback received"
      }
      

2.4 实践示例:异步任务与回调

以下是一个完整的示例,展示如何使用 FastAPI、Celery 和 Redis 实现异步任务处理和回调机制。

项目结构:

project/
├── main.py
├── celery_app.py
├── tasks.py
├── schemas.py
└── dependencies.py

1. 安装依赖

pip install fastapi uvicorn celery redis pydantic requests

2. 配置 Celery

celery_app.py

from celery import Celery

celery = Celery(
    "tasks",
    broker="redis://localhost:6379/0",
    backend="redis://localhost:6379/0"
)

3. 定义 Celery 任务

tasks.py

from celery_app import celery
import requests
import time

@celery.task
def process_task(data: str, callback_url: str):
    try:
        # 模拟任务处理
        print(f"Processing data: {data}")
        time.sleep(10)  # 模拟耗时任务
        result = f"Processed data: {data}"
        # 发送回调请求
        payload = {"result": result}
        response = requests.post(callback_url, json=payload, timeout=5)
        response.raise_for_status()
        print("Callback sent successfully.")
    except Exception as e:
        print(f"Failed to send callback: {e}")
        raise e

4. 定义 Pydantic 模型

schemas.py

from pydantic import BaseModel

class TaskRequest(BaseModel):
    data: str
    callback_url: str

class TaskResponse(BaseModel):
    task_id: str
    message: str

5. 定义依赖

dependencies.py

from fastapi import Depends, HTTPException, status
from typing import Generator

def get_db():
    # 模拟数据库连接
    db = "database_connection"
    try:
        yield db
    finally:
        db = None  # 关闭数据库连接

def verify_token(token: str = None):
    # 简单的 token 验证
    if token != "expected_token":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Invalid or missing token"
        )
    return token

6. 定义 FastAPI 路由

main.py

from fastapi import FastAPI, Depends, HTTPException, status
from pydantic import BaseModel
from typing import Dict

from tasks import process_task
from schemas import TaskRequest, TaskResponse
from dependencies import get_db, verify_token

app = FastAPI()

@app.post("/start-task/", response_model=TaskResponse)
async def start_task(task: TaskRequest, token: str = Depends(verify_token), db: str = Depends(get_db)):
    """
    启动一个异步任务,并在任务完成后通过回调 URL 通知客户端。
    """
    # 启动 Celery 任务
    task_instance = process_task.delay(task.data, task.callback_url)
    return {"task_id": task_instance.id, "message": "Task started"}

@app.post("/task-callback/")
async def task_callback(payload: Dict[str, str]):
    """
    处理来自异步任务的回调请求。
    """
    result = payload.get("result")
    if not result:
        raise HTTPException(status_code=400, detail="Missing result")
    # 处理回调结果,例如更新数据库状态
    print(f"Received callback with result: {result}")
    return {"message": "Callback received"}

7. 启动 Celery Worker 和 FastAPI

  • 启动 Redis

    确保 Redis 服务已启动。如果未安装 Redis,可以参考以下安装步骤:

    在 macOS 上使用 Homebrew 安装:

    brew install redis
    brew services start redis
    

    在 Ubuntu 上安装:

    sudo apt update
    sudo apt install redis-server
    sudo systemctl enable redis-server.service
    sudo systemctl start redis-server.service
    
  • 启动 Celery Worker

    在项目根目录中运行:

    celery -A tasks worker --loglevel=info
    

    您应该会看到 Celery Worker 启动并等待任务。

  • 启动 FastAPI 应用

    在另一个终端中运行:

    uvicorn main:app --reload
    

    FastAPI 应用将启动在 http://127.0.0.1:8000

8. 测试流程

  • 发送任务请求

    使用 curl 或 Postman 发送一个 POST 请求到 /start-task/,包含 datacallback_url,并添加正确的 token

    示例请求:

    curl -X POST "http://127.0.0.1:8000/start-task/" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer expected_token" \
    -d '{"data": "Sample Data", "callback_url": "http://127.0.0.1:8000/task-callback/"}'
    

    响应:

    {
        "task_id": "some_unique_id",
        "message": "Task started"
    }
    
  • 任务处理

    • Celery Worker 接收到任务,开始处理数据。
    • 处理完成后,Worker 向指定的 callback_url 发送 POST 请求,包含处理结果。
  • 回调处理

    FastAPI 应用接收到回调请求后,处理结果并返回确认消息。

    回调请求示例:

    {
        "result": "Processed data: Sample Data"
    }
    

    回调响应:

    {
        "message": "Callback received"
    }
    
  • 查看 Celery Worker 日志

    Celery Worker 终端将显示任务处理和回调发送的日志信息。

完整测试流程:

  1. 启动 Celery Worker 和 FastAPI 应用
  2. 发送任务请求
  3. 等待任务处理完成
  4. 查看回调处理结果

2.4 实践示例:异步任务与回调

为了帮助您更好地理解和实践,以下是一个完整的示例项目,展示了如何使用 FastAPI、Celery 和 Redis 实现异步任务处理和回调机制。

项目结构:

project/
├── main.py
├── celery_app.py
├── tasks.py
├── schemas.py
├── dependencies.py
└── requirements.txt

1. 创建 requirements.txt

fastapi
uvicorn
celery
redis
pydantic
requests

2. 安装依赖

在项目根目录中运行:

pip install -r requirements.txt

3. 配置 Celery

celery_app.py

from celery import Celery

celery = Celery(
    "tasks",
    broker="redis://localhost:6379/0",
    backend="redis://localhost:6379/0"
)

4. 定义 Celery 任务

tasks.py

from celery_app import celery
import requests
import time

@celery.task
def process_task(data: str, callback_url: str):
    try:
        # 模拟任务处理
        print(f"Processing data: {data}")
        time.sleep(10)  # 模拟耗时任务
        result = f"Processed data: {data}"
        # 发送回调请求
        payload = {"result": result}
        response = requests.post(callback_url, json=payload, timeout=5)
        response.raise_for_status()
        print("Callback sent successfully.")
    except Exception as e:
        print(f"Failed to send callback: {e}")
        raise e

5. 定义 Pydantic 模型

schemas.py

from pydantic import BaseModel

class TaskRequest(BaseModel):
    data: str
    callback_url: str

class TaskResponse(BaseModel):
    task_id: str
    message: str

6. 定义依赖

dependencies.py

from fastapi import Depends, HTTPException, status
from typing import Generator

def get_db():
    # 模拟数据库连接
    db = "database_connection"
    try:
        yield db
    finally:
        db = None  # 关闭数据库连接

def verify_token(token: str = None):
    # 简单的 token 验证
    if token != "expected_token":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Invalid or missing token"
        )
    return token

7. 定义 FastAPI 路由

main.py

from fastapi import FastAPI, Depends, HTTPException, status
from typing import Dict

from tasks import process_task
from schemas import TaskRequest, TaskResponse
from dependencies import get_db, verify_token

app = FastAPI()

@app.post("/start-task/", response_model=TaskResponse)
async def start_task(task: TaskRequest, token: str = Depends(verify_token), db: str = Depends(get_db)):
    """
    启动一个异步任务,并在任务完成后通过回调 URL 通知客户端。
    """
    # 启动 Celery 任务
    task_instance = process_task.delay(task.data, task.callback_url)
    return {"task_id": task_instance.id, "message": "Task started"}

@app.post("/task-callback/")
async def task_callback(payload: Dict[str, str]):
    """
    处理来自异步任务的回调请求。
    """
    result = payload.get("result")
    if not result:
        raise HTTPException(status_code=400, detail="Missing result")
    # 处理回调结果,例如更新数据库状态
    print(f"Received callback with result: {result}")
    return {"message": "Callback received"}

8. 启动服务

  • 启动 Redis

    确保 Redis 服务正在运行。如果未安装 Redis,请参考前面的安装步骤。

  • 启动 Celery Worker

    在项目根目录中运行:

    celery -A tasks worker --loglevel=info
    

    您将看到类似如下的输出,表示 Celery Worker 已启动并在等待任务:

    [2023-XX-XX 12:34:56,789: INFO/MainProcess] Connected to redis://localhost:6379/0
    [2023-XX-XX 12:34:56,790: INFO/MainProcess] mingle: searching for neighbors
    [2023-XX-XX 12:34:57,800: INFO/MainProcess] mingle: sync with 1 nodes
    [2023-XX-XX 12:34:57,900: INFO/MainProcess] celery@hostname ready.
    
  • 启动 FastAPI 应用

    在另一个终端中运行:

    uvicorn main:app --reload
    

    您将看到类似如下的输出,表示 FastAPI 应用已启动:

    INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
    INFO:     Started reloader process [28716] using statreload
    INFO:     Started server process [28718]
    INFO:     Waiting for application startup.
    INFO:     Application startup complete.
    

9. 测试综合流程

  • 发送任务请求

    使用 curl、Postman 或任何 HTTP 客户端发送一个 POST 请求到 /start-task/,包含 datacallback_url,并提供正确的 token

    示例请求:

    curl -X POST "http://127.0.0.1:8000/start-task/" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer expected_token" \
    -d '{"data": "Sample Data", "callback_url": "http://127.0.0.1:8000/task-callback/"}'
    

    响应:

    {
        "task_id": "e2f4f1d3-3e6f-4cde-8b9c-3b3b7e7f1c9d",
        "message": "Task started"
    }
    
  • 等待任务处理

    Celery Worker 将接收到任务并开始处理。您可以在 Celery Worker 的终端中看到任务的处理日志。

    Celery Worker 输出示例:

    [2023-XX-XX 12:35:00,123: INFO/Worker-1] Processing data: Sample Data
    [2023-XX-XX 12:35:10,456: INFO/Worker-1] Callback sent successfully.
    
  • 回调处理

    当任务完成后,Celery Worker 会向指定的 callback_url 发送一个 POST 请求,包含任务的结果。

    回调请求示例:

    {
        "result": "Processed data: Sample Data"
    }
    

    FastAPI 应用接收到回调请求后,执行 task_callback 路由处理函数,并返回确认消息。

    回调响应:

    {
        "message": "Callback received"
    }
    

    FastAPI 应用控制台输出示例:

    Received callback with result: Processed data: Sample Data
    

学习建议

  • 理解 Celery 的工作原理:了解 Celery 的任务队列、消息代理(如 Redis)、任务执行和结果存储机制。
  • 学习任务调度:掌握如何调度定时任务、周期性任务以及处理任务失败的策略。
  • 深入研究回调机制:了解如何设计和实现更复杂的回调逻辑,如重试机制、回调验证等。
  • 监控与管理:学习如何监控 Celery Worker 的状态,使用工具如 Flower 进行任务监控和管理。

3. 综合示例

为了更好地理解依赖注入和回调机制,以下是一个综合示例项目,展示如何将这些概念结合起来构建一个功能完整的 FastAPI 应用。

3.1 项目结构

project/
├── main.py
├── celery_app.py
├── tasks.py
├── schemas.py
├── dependencies.py
├── models.py
├── database.py
└── requirements.txt

3.2 定义 Pydantic 模型

schemas.py

from pydantic import BaseModel
from typing import Optional

class TaskRequest(BaseModel):
    data: str
    callback_url: str

class TaskResponse(BaseModel):
    task_id: str
    message: str

class UserBase(BaseModel):
    name: str
    email: str

class UserCreate(UserBase):
    pass

class UserResponse(UserBase):
    id: int

    class Config:
        orm_mode = True

3.3 配置依赖

dependencies.py

from fastapi import Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import Generator

from database import SessionLocal
from models import User

def get_db() -> Generator:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

def verify_token(token: str = None):
    # 简单的 token 验证
    if token != "expected_token":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Invalid or missing token"
        )
    return token

3.4 定义数据库模型

database.py

from sqlalchemy import create_engine, MetaData
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

DATABASE_URL = "sqlite:///./test.db"  # 使用 SQLite 数据库

engine = create_engine(
    DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()
metadata = MetaData()

models.py

from sqlalchemy import Column, Integer, String
from database import Base

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    email = Column(String, unique=True, index=True)

3.5 定义 Celery 任务

tasks.py

from celery_app import celery
import requests
import time

@celery.task
def process_task(data: str, callback_url: str):
    try:
        # 模拟任务处理
        print(f"Processing data: {data}")
        time.sleep(10)  # 模拟耗时任务
        result = f"Processed data: {data}"
        # 发送回调请求
        payload = {"result": result}
        response = requests.post(callback_url, json=payload, timeout=5)
        response.raise_for_status()
        print("Callback sent successfully.")
    except Exception as e:
        print(f"Failed to send callback: {e}")
        raise e

3.6 定义 FastAPI 路由

main.py

from fastapi import FastAPI, Depends, HTTPException, status
from typing import Dict, List

from tasks import process_task
from schemas import TaskRequest, TaskResponse, UserCreate, UserResponse
from dependencies import get_db, verify_token
from models import User
from database import engine, Base

from sqlalchemy.orm import Session

app = FastAPI()

# 创建数据库表
Base.metadata.create_all(bind=engine)

@app.post("/start-task/", response_model=TaskResponse)
async def start_task(task: TaskRequest, token: str = Depends(verify_token), db: Session = Depends(get_db)):
    """
    启动一个异步任务,并在任务完成后通过回调 URL 通知客户端。
    """
    # 启动 Celery 任务
    task_instance = process_task.delay(task.data, task.callback_url)
    return {"task_id": task_instance.id, "message": "Task started"}

@app.post("/task-callback/")
async def task_callback(payload: Dict[str, str]):
    """
    处理来自异步任务的回调请求。
    """
    result = payload.get("result")
    if not result:
        raise HTTPException(status_code=400, detail="Missing result")
    # 处理回调结果,例如更新数据库状态
    print(f"Received callback with result: {result}")
    return {"message": "Callback received"}

@app.post("/users/", response_model=UserResponse)
async def create_user(user: UserCreate, db: Session = Depends(get_db)):
    """
    创建一个新用户。
    """
    db_user = db.query(User).filter(User.email == user.email).first()
    if db_user:
        raise HTTPException(status_code=400, detail="Email already registered")
    new_user = User(name=user.name, email=user.email)
    db.add(new_user)
    db.commit()
    db.refresh(new_user)
    return new_user

@app.get("/users/", response_model=List[UserResponse])
async def read_users(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
    """
    获取用户列表。
    """
    users = db.query(User).offset(skip).limit(limit).all()
    return users

@app.get("/users/{user_id}", response_model=UserResponse)
async def read_user(user_id: int, db: Session = Depends(get_db)):
    """
    获取指定用户的信息。
    """
    user = db.query(User).filter(User.id == user_id).first()
    if user is None:
        raise HTTPException(status_code=404, detail="User not found")
    return user

3.7 启动 Celery Worker 和 FastAPI

  • 启动 Celery Worker

    在项目根目录中运行:

    celery -A tasks worker --loglevel=info
    
  • 启动 FastAPI 应用

    在另一个终端中运行:

    uvicorn main:app --reload
    

3.8 测试综合流程

  1. 创建用户

    发送一个 POST 请求到 /users/,创建一个新用户。

    示例请求:

    curl -X POST "http://127.0.0.1:8000/users/" \
    -H "Content-Type: application/json" \
    -d '{"name": "Alice", "email": "alice@example.com"}'
    

    响应:

    {
        "id": 1,
        "name": "Alice",
        "email": "alice@example.com"
    }
    
  2. 启动异步任务

    发送一个 POST 请求到 /start-task/,启动一个异步任务,并提供回调 URL。

    示例请求:

    curl -X POST "http://127.0.0.1:8000/start-task/" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer expected_token" \
    -d '{"data": "Sample Data", "callback_url": "http://127.0.0.1:8000/task-callback/"}'
    

    响应:

    {
        "task_id": "e2f4f1d3-3e6f-4cde-8b9c-3b3b7e7f1c9d",
        "message": "Task started"
    }
    
  3. 任务处理与回调

    • Celery Worker 将接收到任务并开始处理。您可以在 Celery Worker 终端中看到任务的日志:

      [2023-XX-XX 12:35:00,123: INFO/Worker-1] Processing data: Sample Data
      [2023-XX-XX 12:35:10,456: INFO/Worker-1] Callback sent successfully.
      
    • 处理完成后,Celery Worker 会向指定的 callback_url 发送 POST 请求,包含任务结果。

    • FastAPI 应用接收到回调请求后,执行 task_callback 路由处理函数,并返回确认消息。

      回调请求示例:

      {
          "result": "Processed data: Sample Data"
      }
      

      回调响应:

      {
          "message": "Callback received"
      }
      
    • FastAPI 应用控制台将显示:

      Received callback with result: Processed data: Sample Data
      

3.9 优化与扩展

3.9.1 添加任务状态查询

为了让客户端能够查询任务状态,可以添加一个路由来获取任务状态。

修改 main.py

from celery.result import AsyncResult

@app.get("/task-status/{task_id}")
async def get_task_status(task_id: str):
    """
    获取任务状态。
    """
    task_result = AsyncResult(task_id)
    if task_result.state == 'PENDING':
        response = {"status": "Pending"}
    elif task_result.state == 'SUCCESS':
        response = {"status": "Success", "result": task_result.result}
    elif task_result.state == 'FAILURE':
        response = {"status": "Failure", "reason": str(task_result.info)}
    else:
        response = {"status": task_result.state}
    return response

测试任务状态查询

  • 启动任务

    同上。

  • 查询任务状态

    使用任务 ID 查询任务状态。

    示例请求:

    curl "http://127.0.0.1:8000/task-status/e2f4f1d3-3e6f-4cde-8b9c-3b3b7e7f1c9d"
    

    响应:

    • 当任务在处理中:

      {
          "status": "Pending"
      }
      
    • 当任务完成:

      {
          "status": "Success",
          "result": "Processed data: Sample Data"
      }
      
    • 当任务失败:

      {
          "status": "Failure",
          "reason": "Error message"
      }
      

3.9.2 实现重试机制

为了提高系统的鲁棒性,可以在 Celery 任务中实现重试机制。

修改 tasks.py

from celery import Celery, shared_task
from celery_app import celery
import requests
import time

@celery.task(bind=True, max_retries=3)
def process_task(self, data: str, callback_url: str):
    try:
        # 模拟任务处理
        print(f"Processing data: {data}")
        time.sleep(10)  # 模拟耗时任务
        result = f"Processed data: {data}"
        # 发送回调请求
        payload = {"result": result}
        response = requests.post(callback_url, json=payload, timeout=5)
        response.raise_for_status()
        print("Callback sent successfully.")
    except requests.exceptions.RequestException as e:
        print(f"Failed to send callback: {e}, retrying...")
        try:
            self.retry(exc=e, countdown=5)
        except MaxRetriesExceededError:
            print("Max retries exceeded. Failed to send callback.")
            raise e
    except Exception as e:
        print(f"Unexpected error: {e}")
        raise e

解释:

  • 使用 bind=True 允许任务访问其自身(self)。
  • 设置 max_retries=3 限制最大重试次数。
  • 在捕获到 requests.exceptions.RequestException 时,调用 self.retry 进行重试,延迟 5 秒后再次尝试。
  • 如果超过最大重试次数,则记录失败并抛出异常。

3.9.3 实现回调验证

为了确保回调请求的安全性,可以在回调请求中添加验证机制,例如签名验证或预共享密钥。

示例:简单的回调验证

  1. 在任务请求中添加验证参数

    修改 TaskRequest 模型和任务启动逻辑。

    schemas.py

    class TaskRequest(BaseModel):
        data: str
        callback_url: str
        callback_token: str  # 添加回调验证 token
    

    main.py

    @app.post("/start-task/", response_model=TaskResponse)
    async def start_task(task: TaskRequest, token: str = Depends(verify_token), db: Session = Depends(get_db)):
        """
        启动一个异步任务,并在任务完成后通过回调 URL 通知客户端。
        """
        # 启动 Celery 任务,传递 callback_token
        task_instance = process_task.delay(task.data, task.callback_url, task.callback_token)
        return {"task_id": task_instance.id, "message": "Task started"}
    
  2. 修改 Celery 任务以包含回调 token

    tasks.py

    from celery_app import celery
    import requests
    import time
    
    @celery.task(bind=True, max_retries=3)
    def process_task(self, data: str, callback_url: str, callback_token: str):
        try:
            # 模拟任务处理
            print(f"Processing data: {data}")
            time.sleep(10)  # 模拟耗时任务
            result = f"Processed data: {data}"
            # 发送回调请求,包含 token
            payload = {"result": result, "token": callback_token}
            response = requests.post(callback_url, json=payload, timeout=5)
            response.raise_for_status()
            print("Callback sent successfully.")
        except requests.exceptions.RequestException as e:
            print(f"Failed to send callback: {e}, retrying...")
            try:
                self.retry(exc=e, countdown=5)
            except MaxRetriesExceededError:
                print("Max retries exceeded. Failed to send callback.")
                raise e
        except Exception as e:
            print(f"Unexpected error: {e}")
            raise e
    
  3. 在回调处理函数中验证 token

    main.py

    @app.post("/task-callback/")
    async def task_callback(payload: Dict[str, str]):
        """
        处理来自异步任务的回调请求。
        """
        result = payload.get("result")
        token = payload.get("token")
        if not result or not token:
            raise HTTPException(status_code=400, detail="Missing result or token")
        # 验证 token
        if token != "expected_callback_token":
            raise HTTPException(status_code=403, detail="Invalid callback token")
        # 处理回调结果,例如更新数据库状态
        print(f"Received callback with result: {result}")
        return {"message": "Callback received"}
    

解释:

  • 在任务请求中添加 callback_token,用于验证回调请求的合法性。
  • 在 Celery 任务中,将 callback_token 一同发送到回调 URL。
  • 在回调处理函数中,验证接收到的 callback_token,确保回调请求来自可信来源。

学习建议

  • 深入理解 Celery:学习 Celery 的高级功能,如任务调度、任务链、任务结果存储等。
  • 安全性:设计回调机制时,考虑安全因素,如回调验证、加密传输等,确保系统的安全性。
  • 错误处理和重试策略:设计健壮的错误处理和重试机制,提高系统的可靠性和稳定性。
  • 监控与管理:使用监控工具(如 Flower)监控 Celery Worker 的状态和任务执行情况,及时发现和解决问题。

4. 综合示例

以下是一个完整的示例项目,结合依赖注入和回调机制,展示如何构建一个功能全面的 FastAPI 应用。

4.1 项目结构

project/
├── main.py
├── celery_app.py
├── tasks.py
├── schemas.py
├── dependencies.py
├── models.py
├── database.py
└── requirements.txt

4.2 完整代码

requirements.txt

fastapi
uvicorn
celery
redis
pydantic
requests
sqlalchemy

database.py

from sqlalchemy import create_engine, MetaData
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

DATABASE_URL = "sqlite:///./test.db"  # 使用 SQLite 数据库

engine = create_engine(
    DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()
metadata = MetaData()

models.py

from sqlalchemy import Column, Integer, String
from database import Base

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    email = Column(String, unique=True, index=True)

schemas.py

from pydantic import BaseModel
from typing import Optional

class TaskRequest(BaseModel):
    data: str
    callback_url: str
    callback_token: str  # 添加回调验证 token

class TaskResponse(BaseModel):
    task_id: str
    message: str

class UserBase(BaseModel):
    name: str
    email: str

class UserCreate(UserBase):
    pass

class UserResponse(UserBase):
    id: int

    class Config:
        orm_mode = True

dependencies.py

from fastapi import Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import Generator

from database import SessionLocal
from models import User

def get_db() -> Generator:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

def verify_token(token: str = None):
    # 简单的 token 验证
    if token != "expected_token":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Invalid or missing token"
        )
    return token

celery_app.py

from celery import Celery

celery = Celery(
    "tasks",
    broker="redis://localhost:6379/0",
    backend="redis://localhost:6379/0"
)

tasks.py

from celery_app import celery
import requests
import time

@celery.task(bind=True, max_retries=3)
def process_task(self, data: str, callback_url: str, callback_token: str):
    try:
        # 模拟任务处理
        print(f"Processing data: {data}")
        time.sleep(10)  # 模拟耗时任务
        result = f"Processed data: {data}"
        # 发送回调请求,包含 token
        payload = {"result": result, "token": callback_token}
        response = requests.post(callback_url, json=payload, timeout=5)
        response.raise_for_status()
        print("Callback sent successfully.")
    except requests.exceptions.RequestException as e:
        print(f"Failed to send callback: {e}, retrying...")
        try:
            self.retry(exc=e, countdown=5)
        except self.MaxRetriesExceededError:
            print("Max retries exceeded. Failed to send callback.")
            raise e
    except Exception as e:
        print(f"Unexpected error: {e}")
        raise e

main.py

from fastapi import FastAPI, Depends, HTTPException, status
from typing import Dict, List

from tasks import process_task
from schemas import TaskRequest, TaskResponse, UserCreate, UserResponse
from dependencies import get_db, verify_token
from models import User
from database import engine, Base

from sqlalchemy.orm import Session

app = FastAPI()

# 创建数据库表
Base.metadata.create_all(bind=engine)

@app.post("/start-task/", response_model=TaskResponse)
async def start_task(task: TaskRequest, token: str = Depends(verify_token), db: Session = Depends(get_db)):
    """
    启动一个异步任务,并在任务完成后通过回调 URL 通知客户端。
    """
    # 启动 Celery 任务
    task_instance = process_task.delay(task.data, task.callback_url, task.callback_token)
    return {"task_id": task_instance.id, "message": "Task started"}

@app.post("/task-callback/")
async def task_callback(payload: Dict[str, str]):
    """
    处理来自异步任务的回调请求。
    """
    result = payload.get("result")
    token = payload.get("token")
    if not result or not token:
        raise HTTPException(status_code=400, detail="Missing result or token")
    # 验证 token
    if token != "expected_callback_token":
        raise HTTPException(status_code=403, detail="Invalid callback token")
    # 处理回调结果,例如更新数据库状态
    print(f"Received callback with result: {result}")
    return {"message": "Callback received"}

@app.post("/users/", response_model=UserResponse)
async def create_user(user: UserCreate, db: Session = Depends(get_db)):
    """
    创建一个新用户。
    """
    db_user = db.query(User).filter(User.email == user.email).first()
    if db_user:
        raise HTTPException(status_code=400, detail="Email already registered")
    new_user = User(name=user.name, email=user.email)
    db.add(new_user)
    db.commit()
    db.refresh(new_user)
    return new_user

@app.get("/users/", response_model=List[UserResponse])
async def read_users(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
    """
    获取用户列表。
    """
    users = db.query(User).offset(skip).limit(limit).all()
    return users

@app.get("/users/{user_id}", response_model=UserResponse)
async def read_user(user_id: int, db: Session = Depends(get_db)):
    """
    获取指定用户的信息。
    """
    user = db.query(User).filter(User.id == user_id).first()
    if user is None:
        raise HTTPException(status_code=404, detail="User not found")
    return user

8. 启动服务

  • 启动 Redis

    确保 Redis 服务正在运行。

  • 启动 Celery Worker

    在项目根目录中运行:

    celery -A tasks worker --loglevel=info
    
  • 启动 FastAPI 应用

    在另一个终端中运行:

    uvicorn main:app --reload
    

9. 测试综合流程

  • 创建用户

    发送一个 POST 请求到 /users/,创建一个新用户。

    示例请求:

    curl -X POST "http://127.0.0.1:8000/users/" \
    -H "Content-Type: application/json" \
    -d '{"name": "Alice", "email": "alice@example.com"}'
    

    响应:

    {
        "id": 1,
        "name": "Alice",
        "email": "alice@example.com"
    }
    
  • 启动异步任务

    发送一个 POST 请求到 /start-task/,包含 datacallback_urlcallback_token

    示例请求:

    curl -X POST "http://127.0.0.1:8000/start-task/" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer expected_token" \
    -d '{"data": "Sample Data", "callback_url": "http://127.0.0.1:8000/task-callback/", "callback_token": "expected_callback_token"}'
    

    响应:

    {
        "task_id": "e2f4f1d3-3e6f-4cde-8b9c-3b3b7e7f1c9d",
        "message": "Task started"
    }
    
  • 任务处理与回调

    • Celery Worker 将接收到任务并开始处理,您可以在 Celery Worker 终端中看到日志:

      [2023-XX-XX 12:35:00,123: INFO/Worker-1] Processing data: Sample Data
      [2023-XX-XX 12:35:10,456: INFO/Worker-1] Callback sent successfully.
      
    • 处理完成后,Worker 会向 callback_url 发送 POST 请求,包含处理结果和回调 token。

    • FastAPI 应用接收到回调请求后,执行 task_callback 路由处理函数,并返回确认消息。

      回调请求示例:

      {
          "result": "Processed data: Sample Data",
          "token": "expected_callback_token"
      }
      

      回调响应:

      {
          "message": "Callback received"
      }
      
    • FastAPI 应用控制台将显示:

      Received callback with result: Processed data: Sample Data
      
  • 查询任务状态(可选)

    您可以添加一个路由来查询任务状态。

    修改 main.py

    from celery.result import AsyncResult
    
    @app.get("/task-status/{task_id}")
    async def get_task_status(task_id: str):
        """
        获取任务状态。
        """
        task_result = AsyncResult(task_id)
        if task_result.state == 'PENDING':
            response = {"status": "Pending"}
        elif task_result.state == 'SUCCESS':
            response = {"status": "Success", "result": task_result.result}
        elif task_result.state == 'FAILURE':
            response = {"status": "Failure", "reason": str(task_result.info)}
        else:
            response = {"status": task_result.state}
        return response
    

    测试任务状态查询

    使用任务 ID 查询任务状态。

    示例请求:

    curl "http://127.0.0.1:8000/task-status/e2f4f1d3-3e6f-4cde-8b9c-3b3b7e7f1c9d"
    

    响应:

    • 当任务在处理中:

      {
          "status": "Pending"
      }
      
    • 当任务完成:

      {
          "status": "Success",
          "result": "Processed data: Sample Data"
      }
      
    • 当任务失败:

      {
          "status": "Failure",
          "reason": "Error message"
      }
      

4. 总结

通过本节的详细讲解和实践示例,您已经了解了如何在 FastAPI 中实现依赖注入和回调机制。以下是本节的关键点总结:

  1. 依赖注入(Dependency Injection)
    • 基础:使用 Depends 声明依赖,管理共享资源如数据库连接、认证等。
    • 共享依赖:多个路由可以共享同一个依赖函数或类。
    • 嵌套依赖:依赖函数可以依赖于其他依赖函数,实现复杂的依赖关系。
    • 类的依赖注入:使用类来组织依赖,提升代码的结构化和可扩展性。
    • 实践:通过实际项目实现数据库连接管理,提升代码的模块化和可维护性。
  2. 回调机制(Callback Mechanism)
    • 基础:通过回调 URL 通知客户端异步任务的完成状态和结果。
    • 使用 BackgroundTasks:适用于简单的后台任务处理,但不适合复杂的任务队列。
    • 使用 Celery:适用于复杂的异步任务处理和分布式任务队列,提供可靠的任务调度和重试机制。
    • 安全性:设计回调机制时,考虑回调请求的验证和安全性,防止未经授权的请求。
    • 实践:通过实际项目实现异步任务的处理和回调通知,提升系统的响应能力和用户体验。
  3. 综合应用
    • 项目结构:组织项目代码,确保模块化和可维护性。
    • 实际操作:通过完整的示例项目,理解和应用依赖注入和回调机制,构建一个功能全面的 FastAPI 应用。
    • 扩展与优化:添加任务状态查询、重试机制和回调验证,提升系统的鲁棒性和安全性。

持续学习的建议

  • 深入文档:阅读 FastAPI 官方文档 中关于依赖注入和异步任务的章节,了解更多高级特性和最佳实践。
  • 学习 Celery:深入学习 Celery 的更多功能,如任务链、任务组、定时任务等,提升异步任务处理能力。
  • 实践项目:通过实际项目应用所学知识,解决真实问题,积累经验。
  • 安全性与优化:学习如何在回调机制中实现更高级的安全措施,如签名验证、加密传输等,同时优化任务处理性能。
  • 监控与调试:掌握如何监控 Celery Worker 的状态,使用工具如 Flower 监控任务执行情况,及时发现和解决问题。

通过系统性的学习和持续的实践,您将能够熟练掌握 FastAPI 的依赖注入和回调机制,构建高效、可靠且可维护的后端服务。

祝您在 Python 后端开发的道路上取得更大的进步和成就!

### FastAPI依赖注入的用法 在 FastAPI 中,依赖注入是一种强大的机制,用于简化代码结构并提高可维护性和重用性。以下是关于如何在 FastAPI 中实现依赖注入的具体方法。 #### 使用类型注解声明依赖 可以通过 Python 的类型注解来定义依赖项。FastAPI 能够识别这些注解,并根据它们自动生成所需的依赖关系[^1]。 ```python from fastapi import FastAPI, Depends app = FastAPI() async def common_parameters(q: str = None, skip: int = 0, limit: int = 100): return {"q": q, "skip": skip, "limit": limit} @app.get("/items/") async def read_items(commons: dict = Depends(common_parameters)): return commons ``` 上述代码展示了 `common_parameters` 函数作为依赖被注入到 `/items/` 路由中。当请求到达该路径时,FastAPI 自动调用 `common_parameters` 并将其返回值传递给 `read_items` 函数。 #### 生命周期管理中的依赖注入 FastAPI 不仅支持简单的依赖注入,还能够通过其内置的生命周期管理功能优化资源分配和释放过程。这使得开发者可以更有效地控制应用程序运行期间的各种状态变化[^2]。 例如,在启动阶段初始化数据库连接池或者关闭服务前清理缓存等操作都可以借助于这种机制完成: ```python import asyncio from contextlib import asynccontextmanager from fastapi import FastAPI, Depends @asynccontextmanager async def lifespan(app: FastAPI): # 启动时执行的任务 await startup_event() yield # 关闭时执行的任务 await shutdown_event() def get_db(): db = ... try: yield db finally: db.close() @app.post("/users/") async def create_user(user: User, session=Depends(get_db)): ... app = FastAPI(lifespan=lifespan) ``` 这里展示了一个完整的例子,其中包含了从应用启动直到终止过程中涉及到的所有重要环节——包括但不限于设置全局变量、加载配置文件以及销毁不再使用的对象等等。 #### 实际案例分析 下面给出一个具体的场景说明:假设我们需要获取 URL 查询字符串中的某个特定参数,则可以直接利用 `Depends()` 方法配合辅助函数一起工作[^3]: ```python from typing import Optional from fastapi import FastAPI, Depends app = FastAPI() def get_query_token(token: Optional[str]): if not token: raise HTTPException(status_code=400, detail="No token provided") return token @app.get("/") def read_root(query_token: str = Depends(get_query_token)): return {"token": query_token} ``` 在这个例子当中,我们先定义好了一个名为 `get_query_token` 的小型工具类;随后再把它当作参数传入目标 API 接口之中即可轻松达成目的。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值